Git就活筆記試験対策道場

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35712/e1e6d646-6700-cafd-38a9-6de782f770d4.png)
photo by [Jennifer Duda](https://www.flickr.com/photos/codnewsroom/)

### はじめに

ちょっと前に面白いことに気がついた。開発に携わる友人たちの多くがGitというツールの重要性はよくよく分かっているのに、それが一体どういうツールなのかを時間をかけて分かろうとはしていないということだ。とは言っても、プログラマーとしてのサバイバルスキル的な観点から言えば、Gitは例えば演算法やデータ構造、他のプログラミング言語やフレームワーク開発のように深入りして学ぶ必要はなく、多くの人が日常的に必要なGitの操作といえば`add`に`commit`、`push`、`pull`、それに加えてブランチを切ることとマージが出来さえすれば十分といったところだろう。そういうこともあって、求人中の会社にとってGitを使えるというアピールポイントは採用を決めるにあたって「少しプラスに加味される条件」に過ぎず、使えないとかよく知らないからといって、すぐさま採用に影響があるとは言えないところだ。

うん…確かに今のところ職場でのGitの立ち位置というのはそんな感じだ。そんなわけだから、GitをFTP代わりに使うのもあながち間違っているとは言い切れない。…言い切れないのだが、だがしかし！開発に携わる、とりわけチーム開発に携わる人間にとっては、不正確な使用法は時折ファイルに予期せぬ混乱と喪失を招き、しかもチームのメンバーにも多大なる迷惑をかけかねない。

というわけで、自分が面接官だったら面接者に聞きたいなという採用試験の例題を幾つか用意してみた。この例題は「私Git使えます！」という開発者であれば、たぶん…たぶん問題なく答えられるのではないかと思うので、是非採用試験前の参考にしてみてほしい。もし、採用試験時にここに用意した例題以外でも面白い問題があったという人がいたら、是非下のコメント欄に報告をお願いします。

### 免責事項：

1. これはどこかの企業の面接試験の過去問とかいった類のものではありませんので、就職及び転職活動にあたっては各自しっかりと準備をして下さい。試験に全く違うことが出て、結果不採用であったとしても責任は負いかねます。
2. もし万が一台湾の弊社[5xRuby](https://5xruby.tw)の求人に応募して頂いたとしても、これらの問題が出るとは限りません。

## 用語問題

> 問題：
> `git push origin master`このコマンドは一体何をするものか？

このコマンドは手もとのmasterブランチの内容を、（GitHubや、GitLabもしくは会社内部のGitサーバ内にある）`origin`にpushし、かつ`origin`内に同名の`master`ブランチを作成するものだ。

ただし、多くの人が完全なpushコマンドはこのようになることを知らない：

    $ git push origin master:master

上のコマンドは「ローカルの`master`ブランチの内容を`origin`にpushし、しかも`origin`上に`master`ブランチを作成する」という意味なので、もしこのコマンドを以下のようにすると：

    $ git push origin master:cat

これは「ローカルの`master`ブランチの内容を`origin`にpushし、`origin`上に`cat`ブランチを作成する」という意味になる。

----

> 問題：
> `git clone`、`git fetch`と`git pull`この3つのコマンドの違いを説明しなさい。

__git clone__
cloneコマンドはネット上のプロジェクトを「丸ごと」手元のPCにコピーしてきて、尚且つPC内に同名のディレクトリを作成（.gitディレクトリを含む）するもの。通常このコマンドは一番初めに使うのみで、clone後にプロジェクトを更新したいのであれば、`git fetch` もしくは`git pull`コマンドを使う。 

__git fetch__
例えば`origin`という名のリモートから`git fetch`コマンドを実行したとすると、Gitはローカルとリモート（ここでいう`origin`）のプロジェクトを比較して「`origin`上にはあるのにローカルにはまだない」内容をダウンロードしてくれる上に、ローカルに相応のブランチを作成してくれる。ただし、fetchコマンドはダウンロードしてくれるだけで、マージはしてくれない。

__git pull__
pullコマンドは、実はやることはfetchと変わらない。違いはfetchが単にプロジェクトをダウンロードしてくれるのに対して、pullはダウンロードしてくるだけではなくマージまでしてくれる。つまり`git pull`というのは`git fetch`に`git merge`コマンドを加えたものだという言い方をすることが出来る。

----

> 問題：
> GitにおけるHEADとは一体何か？

HEADというのは本質的には一つのタグに過ぎず、通常特定のローカル上のブランチやコミットを指している。なので、HEADは今いるブランチ（current branch）を指すものと考えてもらって構わない。

----

> 問題：
> マージ後のブランチは通常どのように処理するのが適切か？また、それは何故か？

ブランチというのは、本質的には40桁のファイルに過ぎず、ブランチやファイルまたはコミットを削除したからといって消えるものではない。なので、マージ後のブランチを削除しようが、記念にとっておこうが実はどちらでも構わない。逆に言えば、取っておいたとしてもたかだが40Bytesの空間を占領するだけのことなので、数十、数百ギガの動画ファイルなんかと比べれば…というわけだ。

----

> 問題：
> マージ済のブランチを削除してしまうと何が起こる？

ブランチというのは実はタグや付箋のようなものに過ぎず、特定のコミットの上に貼られているだけに過ぎない。つまりブランチはディレクトリやファイルといった類のものではない（確かに幾つかのバージョン管理システムではそういう形式をとるものも存在する）。Gitでは、削除されたブランチというのはちょうど、箱の上に貼られたシールを剥がすようなもので、シールを剥がしてしまったからといって箱自体に影響があるわけではない。つまり、マージ済のブランチを削除してしまったからといってどうってことはなく、ファイルやディレクトリまでが消えてしまうというような事態は起こらない。

----

> 問題：
> `git diff`このコマンドは一体どんな時に使うものか？

このコマンドは2つのコミット間の差異を比較したい時に使用する。例えば：

    $ git diff e37078e d4d8d9d

こうすると、`e37078e`と`d4d8d9d`という2つのコミットの違いを比べることが出来る。もしコマンドの後にSHA1ハッシュ値を1つしかのせなかった場合は：

    $ git diff e37078e

`e37078e`と現在のHEADが指すコミットの違いを比較してくれる。それでは、もし全くSHA1ハッシュ値をつけなかった場合どうなるかというと：

    $ git diff

Gitは「ワーキングディレクトリ」と「ステージングエリア」の違いを比較してくれる。つまり、`git add`を実行する前にこのコマンドを実行すれば、自分がどこを修正したのかを確認出来るのだ。もし`git add`を既に実行して修正をステージングエリアに追加してしまっている場合は、--cachedオプションをつけると：

    $ git diff --cached

現在のステージングエリアとHEADが指すコミットの違いを比較してくれる。

----

> 問題：
> Gitを使用する際、通常どのようにタグを使っているか答えなさい。

通常はソフトウェア開発などでマイルストーンとして使ったりする。例えばソフトウェアのバージョン1.0.0とかbeta、releaseといった具合だ。こういう時にタグは非常に使いやすい。例えば：

    $ git tag 1.0.0

こうするとあるコミット上に`1.0.0`というタグをつけることが出来る。

----

> 問題：
> GitHub上にforkという機能があるが、このforkと`git branch`コマンドの違いは何か？

GitHubのfork機能というのは「プロジェクトを丸々指定したアカウントにコピーしてくる」というもので、fork を実行するとGitHub上の表示はこのようになる：

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35712/66727cd3-ea7b-6d61-1280-12816a12a7dd.png" width="600px">

Forkを実行後、修正箇所をコミットした上でPull Request（PR）を送ると原作者と交流を持ちながら、原案プロジェクトに自分のコミットで貢献をすることが出来る。

対して`git branch`というのは、プロジェクト内に一つブランチを切って、そこで修正作業をし、作業終了後は`git merge`もしくは`git rebase`コマンドで修正点を合流させるというもの。

----

> 問題：
> 一般的なマージとrebaseを使った合流方式の違いは何か？またそれぞれのメリット、デメリットをあげよ。

一般的なマージというのは、基本的に（Fast Forward方式以外で）新しくコミットを作り出してそこで2つのブランチを合流させるものをいう。それに対してrebaseというのは、一般的なマージと違って新しいコミットを作り出したりしないという点に違いがある。

とは言っても、最後の結果だけを見れば、ファイルの内容からしても両者に違いは無いのだが、ログには明らかな違いが出てくる。つまり、誰が誰をrebaseしたかがログにはしっかりと記録として残されるのだ。例えば`cat`ブランチが`dog`ブランチ上にrebaseされた場合、`cat`ブランチは`dog`ブランチの後ろに表示がくる。反対に`dog`ブランチが`cat`ブランチ上にrebaseされた場合`dog`ブランチは`cat`ブランチの後ろに表示される。

Rebaseを使うメリットは、合流するのにコミットを作り出したりはしないので幾分かログがすっきりと見えること（そこまで違わないと思う人もいるとは思うが）、それにログの順序を照らせばどちらがどちらをrebaseしたのか前後関係がわかり易いという点が挙げられるだろう（とは言え、これもメリットデメリットかは使用する状況や人によるところだろう）。デメリットは、一般的なマージに比べその使い方がやや直感的ではないことと、小さなうっかりが取り返しのつかないミスに繋がりやすいこと、またコンフリクトが発生してしまうと作業が止まってしまい、rebaseを使い慣れていないと立ち往生してしまう可能性があることなどが挙げられる。

個人的には通常まだpushしていない些末な（もしくはまだ綺麗にまとまっていない）コミットに対しては先にrebaseを使って整理してからpushすることが多い。ただしrebaseというのはログを修正するに等しいので、このコマンドによってパラレルワールドが生み出されてしまい、他の人に多大な迷惑をかけてしまう可能性も十分あり得る。なので既にpushした内容については、rebaseの使用は避けた方が賢明だろう。

----

> 問題：
> マージをしようとしたらコンフリクト（Conflict）が起きてしまった。どう対処するのが望ましいか？

__これはアウト__

「トラブル発生でーす！はい、問題起こした人が自分で解決してくださーい！」

__こうしてみよう__

ブランチをマージしようとしたらコンフリクトが発生。こういう時多くの場合、作業の分担範囲をしっかりと確認していなかったことや、元々のフレームワーク設計が上手くいっていなくて皆が同じファイルの修正に取り掛かってしまっていたことが原因だったりする。通常コンフリクトが発生したら、当事者同士を呼んで話し合い、どちらのcodeを使うのかを決めて再びコミットすればいいだけのことだったりする。

----

> 問題：
> `git checkout SHA1`、`git reset SHA1`及び`git revert SHA1`この3つのコマンドの違いを説明しなさい。

__git checkout SHA1__
このコマンドはHEADを指定したcommit上に移動させ、且つ現在の状態を指定したコミットの状態に戻すもの。ただし、ブランチを移動させるわけではない（つまりブランチは元の位置を保ったまま、HEADの位置が移動しただけの状態）。

なので、ログには全く変化はなく、単にHEADが指定した位置に移動したというだけのことである。

__git reset SHA1__
このコマンドはHEADとブランチを指定したコミット上に移動させるもの。同時にコマンドの後ろに追加したオプション（よく使うのは`--mixed`、`--soft`と`--hard`だ）によって、どのファイルやディレクトリを残し、どれを消すのかを決めることが出来る。デフォルトの`--mixed`はリセットをかけたファイルをワーキングディレクトリに残しておくことが出来るし、`--soft`ならばステージングエリアに残しておくことが出来、`--hard`ならば問答無用にとにかくリセットしてしまう。共通して言えるのはどのオプションも、HEADの位置が変わるだけではなく、ログ全体にも変化を及ぼす（ログが短くも長くもなる）点だ。 

__git revert SHA1__
このコマンドは新しくコミットを作り出し、その新しく生み出されたコミットによって特定のコミットを取り消してしまうこと（正確に言えばundo）が出来る。ただ、本質はコミットなのでGitログが短くなるということはなく、単にrevertが長くなるだけである。

## ケーススタディ

> 問題：
> もしうっかりまだマージしていない`develop`ブランチを削除してしまった場合、どうすればいいだろうか？

__これはアウト__

「部長、ここに辞表置いときます」（重っ）

__こうしてみよう__

慌てなくて大丈夫。ブランチというのは付箋のようなものなので、ブランチを消してしまったからといって、例えそれがマージ前のブランチであったとしても、コミットまでもが消えたりすることはあり得ない。単にブランチという付箋が剥がされてしまったので、一時的に見えなくなってしまっただけで、またブランチの付箋を貼り直せばいいだけのことなのだ。例えば元々のブランチがこのような感じだったとして：

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35712/a8f4732c-c5dc-7996-d1ac-26c24c871ef0.png" width="600px">

`develop`ブランチを試しに削除してみよう：

    $ git branch -D develop

するとこのような状態になる：

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35712/5bb6d903-93d3-5205-c400-e55c0f1b4f6f.png" width="600px">

まだ存在はしているものの、見えなくなってしまっただけ。なので、これらのコミットを見えるようにする為には、再びブランチという付箋を貼り直せばいいわけだ：

    $ git branch new_develop 84aa2e6

これで元々の`develop`という付箋が剥がされてしまった位置に（ここでいう`84aa2e6`）再び`new_develop`という付箋が貼り直されたことで再度見えるようになった。

もしかしたら、その`84aa2e6`というのはどうやって探し出せばいいのと思ったかもしれない。種明かしをすると、これは通常ブランチを削除する際にこのようなメッセージが画面に表示されるので、わかるようになっている：

    $ git branch -D develop
    Deleted branch develop (was 84aa2e6).

ただよく見ていなかったり、うっかりメッセージ画面を消してしまったという時でも、`git reflog`から探し出せば、恐らく見つけられるはずだ。

----

> 問題：
> うっかり`git reset --hard`コマンドでファイルを消してしまいました、助かる方法はありますか？

__これはアウト__

「また新しくダウンロードし直せばよくない？（笑）」（うん…まぁそれでもいいんだけどさ）

__こうしてみよう__

大丈夫、基本的にGitのオブジェクトは憎まれっ子同様簡単に消えたりはしないから。オブジェクトは目には見えない方法でGit空間にきちんと保管されているので、`git reflog`コマンドでresetをくらった当該コミットのSHA1ハッシュ値を調べだした後、再びhard resetをかければ元通りになる。

`git reset`は覆水盆に返るのだ:)

----

> 問題：
> `git cherry-pick`コマンドとはいつ使うべきものか？またどのように使えばよいか述べよ。

とあるブランチのとあるコミットだけ欲しい、ただしブランチ全体をマージしたくはないな（例えばそのブランチの他のコミットに問題があるとかで）という時、`git cherry-pick`コマンドを使えば欲しいコミットを現在のブランチの後に持ってくることが出来る。例えば：

    $ git cherry-pick 6a498ec

このコマンドだと`6a498ec`というコミットを拾い出して、現在のコミットの後ろに持ってくることが出来る。また、一度に複数を持ってくることも可能で、その場合：

    $ git cherry-pick fd23e1c 6a498ec f4f4442

このようにすれば一度に3つのコミットを持ってくることが出来る。
その他にも`git cherry-pick`コマンドの後に`--no-commit`というオプションをつけると、コピーしてきたコミットを直接後ろにつけることはせず、とりあえずステージングエリアに置いておくことも出来る。


----

> 問題：
> プロジェクト内に比較的敏感な又は機密性の高いファイルがあるとする（例`/config/database.yml`）。Gitを使用する際、こうしたファイルをどのように扱うのが妥当か述べなさい

__これはアウト__

    $ rm config/database.yml

（これじゃあ元も子もないですやん…）

__こうしてみよう__

機密性の高いファイルだけに限らず、例えば書きかけで仮保存しているだけなのでまだGitに入れておくべきではないファイルというものだって存在する。こういう時、プロジェクト内に`.gitignore`ファイルを作っておいて、Gitの管理対象とするルールを設定しておけばいい。

----

> 問題：
> とあるコードを誰が書いたのか知りたい時にはどうすればいいか？

__これはアウト__

グループライン等で皆に向かって「ここのXって部分のコード、誰が書いたんですかー！」と思い切って聞いてみる

__こうしてみよう__

    $ git blame ファイル名

このコマンドを使えば、ファイル内の各コードは誰がいつ、どこのコミットに書き込んだのかが一目瞭然だ。

----

> 問題：
> `git push -f`コマンドというコマンドを使ったことはあるだろうか。このコマンド、どのような状況下で使用するものか述べよ。

もちろん使ったことがある。しかも、すごく使えるやつだ。

時々プロジェクトのコミットログがぐちゃぐちゃなのに我慢が出来なくなって、rebaseコマンドでバッサバッサと整理したりするのだが、rebaseを使うというのは既に起きた事実を修正してパラレルワールドを作り出すことに等しいので、とてもじゃないけれどpushは出来ない。じゃあ、どうすればいいかというと、そういう時にこそ`git push -f`コマンドの出番だ。ただし、このコマンドを実行する前には必ず同じプロジェクトを扱うチームメイトに今後は自分がpushしたものに進度を合わせるよう一声かけてほしい。

また蛇足になるのだが、例えば一つタスクを終えた後、今しがたのコミットはあまりに細かすぎたなというような時、個人的にはrebaseを使ってブランチを整理することが多い。確かにrebaseはログを変えてしまうのだが、この場合影響を及ぼす範囲が自分のブランチだけに限られているので、安心して使うことが出来るからだ：

    $ git push -f origin feature/my_cat

このように他人のブランチに影響を及ぼさず、影響範囲が自分だけに限定される時、`git push –f`は使っても問題ないといえる。ただし、状況がどうあれ、やはりこのコマンドを使う際には数十秒しっかり考えて、コマンドに間違いが無いかもしっかりと確認した上で、Enterキーを押すようにしてもらいたい。

----

> 問題：
> チームで作業中、自分がpushしたコミットに問題を発見した。取り消したいのだが、どうすればいいか。

__これはアウト__

まずはこれ：

    $ git reset HEAD^ --hard

続いて大技！：

    $ git push -f

はい、これにて終了！

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35712/ec944edb-2d53-5a2a-9d97-02a676266c35.png" width="600px">

__こうしてみよう__

もしチームで開発を進めているのであれば、軽々しく共用ブランチに`git push –f`をキメるのは絶対にやめておこう。既にpushしてしまった上に、そのコミットが他の人に使われている時、そんな時はrevertコマンドで当該コミットを取り消してみよう：

    $ git revert SHA1

こうすれば自分が指定したコミットを取り消す新しいコミットを作成することが出来る。

----

> 問題：
> もし特定のブランチ上で作業を進行中に、突然上司に呼ばれて別の箇所を修正するよう言われてしまった。こんな時、やりかけの仕事はどうすればいいだろうか？

幾つか方法があるのだが、まずは一先ず`git commit`をしておき、トラブル処理が終わってから再びブランチに戻り、`git reset`をかけて先ほどのコミットの続きに取り掛かるというやり方が考えられる。

もう一つは`git stash`コマンドを使用して、手元の仕事を一旦stash上に置いておき、トラブル処理が終わってから再び`git stash pop`もしくは`git stash apply`コマンドを実行して先ほどの進度を呼び出すやり方もある。

----

> 問題：複数のコミットを一つにマージするにはどうしたらいいか？

__これはアウト__

まずはこれ：

    $ rm -rf .git

ログを抹消して、初期化しておいてから：

    $ git init

最後に全部一気にぶっこめば…

    $ git add --all
    $ git commit -m "add all files"

__こうしてみよう__

複数のコミットを一つにまとめたい時は、`git rebase`コマンドのインタラクティブモードを使ってみよう：

    $ git rebase -i SHA1

「インタラクティブモード」の時に、squashを選択してrebaseを実行すれば、複数のコミットを一つにまとめることが出来る。

----

> 問題：
> 例えばGitを使ったことがない上司に：「今さっき更新したファイルまとめて全部もらえない？」と言われた時、あなたならばどうする？

__これはアウト__

直接上司に「少しはGitの使い方覚えて下さいよ」もしくは「ああ、GitHub 上にあるんで、自分で見てみて下さい」と言ってみる

__こうしてみよう__

GUIソフト（SourceTree等）を開いて一つ一つ該当するファイルを選ぶ、もしくはターミナルコマンドを使って：

    $ git diff-tree HEAD --name-only -r --no-commit-id

`git diff-tree`コマンドというのは、2つのコミット間の違いを比較してくれるもので、後ろにつけた`--name-only`というのは「ファイル名だけを羅列」すればいいという意味だ。-rオプションをつければディレクトリ、子ディレクトリ、孫ディレクトリと全部リストアップすることが出来る。

またSHA1ハッシュ値をつければ、Gitはこの当該コミットを1つ前の状態の時と比べてくれるので、HEADオプションをつければ最新のコミットと一つ前の状態を比較して異なるファイルリストを出してくれる。

その後は、`git archive`コマンドを使えば、`git diff-tree`でリストアップされたファイルをまとめて圧縮ファイルにしてくれるので、それを上司に送ればミッションクリアというわけだ。

----

> 問題：
> 空のディレクトリはGitの管理下には置けないのだが、もしその空のディレクトリが重要なもので必ずGitの管理下に置きたいという場合、どうすればいいだろうか？

Gitというのは「ファイルの内容」によってバージョン管理をしているものなので、もしディレクトリ内に何もファイルが入っていないとなると、それはGitに認識されていないということになる（つまりは無視されている）。とにかくどんなファイルでもいいから1つ入れておけばいいのに、何も入れておくものが思いつかない、そんな時は`.keep`という空のファイルを入れておこう。

----

> 問題：
> 過去のとあるコミットから新たにコミットを切ることは出来るか？

幾つか方法がある：

まず第一に過去に戻る（例`657fce7`）：

    $ git checkout 657fce7

次にそこから新しいブランチを切る：

    $ git branch my_cat

もしくはこのようにしても構わない：

    $ git checkout -b my_cat

ただし、もっと簡潔にいきたければたった1行で済ませることも出来る：

    $ git branch my_cat 657fce7

このコマンド「`657fce7`というコミット上に`my_cat`という名のブランチの付箋を貼る」という意味だ。

----

> 問題：
> `git push`コマンドを実行したところ、このようなメッセージが出た：
>
> $ git push origin master
> To /tmp/git-kata.git
>  ! [rejected]        master -> master (fetch first)
> error: failed to push some refs to '/tmp/git-kata.git'
> hint: Updates were rejected because the remote contains work that you do
> hint: not have locally. This is usually caused by another repository pushing
> hint: to the same ref. You may want to first integrate the remote changes
> hint: (e.g., 'git pull ...') before pushing again.
> hint: See the 'Note about fast-forwards' in 'git push --help' for details.
> 
> これはどのような意味か？またどのように解決すればいいか？

__これはアウト__

「これは簡単、フォースを使えば…解決出来ないものなどないわ！」

    $ git push -f

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35712/ec944edb-2d53-5a2a-9d97-02a676266c35.png" width="600px">

__こうしてみよう__

こういうメッセージが表示されたのは、あなたがpushコマンドを実行する前に既に別の人がpushしてしまい、進度に食い違いが起こったことに原因がある。これは必ずしもコンフリクトが起こったからというわけではなく、コンフリクトがあるのかどうかはpullしてくればわかることだ。この問題の答え、実は上のエラーメッセージの中に既に答えが書かれている。こういう時は先ず`git pull`コマンドを実行して、オンライン上の進度を引っ張ってくればいい（通常は自分の手元でマージを実行する）、そして問題がないことを確認した後に再びpushすればいいのだ。

----

> 問題：
> 時々過去のコミットを見返すと、こういうメッセージが表示される：
>
> $ git checkout 5bca205
> You are in 'detached HEAD' state. You can look around, make experimental
> changes and commit them, and you can discard any commits you make in this
> state without impacting any branches by performing another checkout.
> 
> If you want to create a new branch to retain commits you create, you may
>  do so (now or later) by using -b with the checkout command again. Example:
> 
>  git checkout -b <new-branch-name>
> 
> HEAD is now at 5bca205... add database config files
> 
> これはどのような意味か？またどのように解決すればいいか？

このメッセージ、パッと見はエラーメッセージのように見えるのだが、HEADがどのローカルブランチも指していないという状態を伝えるだけのもので、大した問題ではない。単にHEADを再びどこかのローカルのブランチを指すようにすればいいだけの話なのだ。例えば：

    $ git checkout master

これで解決。

とはいえ、HEADはどこでもいいからどこかブランチを指していればいいかと言うとそうでもなく、例えばリモート上の`origin/master`ブランチを指していたりすると、これも同様にdetached HEADのメッセージが表示されてしまう。


----

> 問題：
> 時々ブランチを切り換える際にこのようなメッセージが表示されることがある：
> 
> $ git checkout master
> error: Your local changes to the following files would be overwritten by checkout:
> 	index.html
> Please commit your changes or stash them before you switch branches.
> Aborting
> 
> この後もしスムーズにブランチを切り換えられなかった場合、どうすればいいだろうか？

エラーメッセージに表示された通り「Please commit your changes or stash them before you switch branches.」、もし今しがたの修正を手元に残しておきたいという場合は先にコミットを保存しておいてから、少ししてからresetを使って続きにあたってもいいだろう。もしくは`git stash`を使って、その後から`git stash pop`もしくは`git stash apply`で戻ってきて作業を続ければいい。

また、もしこれらの修正が不要だという場合は`git reset HEAD index.html`もしくは`git checkout index.html`でこれらの修正を取り消しにすることが出来るし`git checkout`にオプション-fをつければ直接ブランチを切り換えてくれる：

    $ git checkout master -f

----

> 問題：
> `feature/develop`ブランチを`master`ブランチにマージしたのだが、どうすればこのマージを取り消すことが出来るか？
> 
> マージ前：
> 
>  <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35712/b6aa5184-0124-4003-f4dd-8777b2e86d05.png" width="600px">
> 
> マージ後：
> 
> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35712/a34aaf4d-968c-8985-9188-d5193090739e.png" width="600px">

__これはアウト__

「まずは今しがた修正したファイルをコピーして別の場所に保存、その後新しいプロジェクトをcloneしてきて、最後にさっきのファイルを放り込めば、はい一丁あがり！」

__こうしてみよう__

実は新しくダウンロードし直してくる必要などなくて、単にブランチを「マージ前」の状態に戻せばいいだけのことだ。上の図を例に使うと、マージ前の`master`のコミットは`bb92e0e`なので：

    $ git reset bb92e0e --hard

これでマージ前の状態に戻すことが出来た。もしくは、わざわざ`bb92e0e`と打ち込まなくても「代名詞」を使って「相対する位置」に持ってくることが出来る：

    $ git reset HEAD^ --hard

こうすればHEAD（`bfd8e86`）の「一つ前」のコミット、つまり`bb92e0e`の状態になったし、これで先ほどのマージを取り消したのと同じことになった。

----

> 問題：
> `feature/develop`ブランチを`master`ブランチにrebaseしたのだが、取り消したい。どうすればいいだろうか？
> 
> Rebase前：
> 
> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35712/8687300c-59c5-823d-9efd-1803522b6653.png" width="600px">
> 
> Rebase後：
> 
> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35712/6743973e-0635-ea03-1cb1-b0f127bda8b6.png" width="600px">

__これはアウト__

「またダウンロードし直せばいいじゃんね！ハハ」（うん、正直に打ち明ければ僕も同じことしたことあるよ…）

__こうしてみよう__

Rebaseは一般的なマージと違うので、もし直接`git reset HEAD^ --hard`を使っても思うような結果は得られない。比較的簡単な解決法は、`ORIG_HEAD`のログを使ってrebase前の状態に戻すことだろう：

    $ git reset ORIG_HEAD --hard

この`ORIG_HEAD`は直近の危険な操作（例えばreset、rebase、mergeなど）を行う手前の状態を記録しておいてくれる。ただし記録しておいてくれるのは直近のもの1つだけ。なので万が一うっかりと消してしまった場合は再度`git reflog`から探し出せば、きっとrebase前のコミットのSHA1ハッシュ値を見つけ出せるはずだ。その後は再びこのコマンドを実行して：

    $ git reset REBAS前のSHA1ハッシュ値 --hard

これで先ほどのrebaseを取り消すことが出来た。

----

> 問題：
> もしチーム内の誰かが`git push –f`を発動してしまった場合、どうすればいいだろうか？

__これはアウト__

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35712/22ce615b-7d89-546b-5e2c-34ffbe3b5e0f.png" width="400px">

__こうしてみよう__

Gitは一種の分散式バージョン管理システムなので、皆それぞれ手元にそれぞれの進度で進めているプロジェクトがあるはずだ。なので、この場合自分もしくは他のチームメイトが再度手元にあるプロジェクトを`git push -f`コマンドを実行してpushし、前回の`git push -f`によってもたらされた被害を新たに覆いつくしてしまえばいい。

もちろん復旧作業を終えたら、やらかしたチームメイトと、なぜ事前通告なしに`git push -f`コマンドを選択して実行したのかしっかりと話し合うことをおすすめする。

## その他

> 問題：
> まだ社内でGitもしくはその他のバージョン管理システムを導入していない場合、Git導入の為に何と言って上司を説得するか述べよ。

この問題に答えは用意していないので、各自自由に考えて差し支えなければ下のコメント欄に答えを書き込んでいってほしい。もしくはあなたの代わりに上司のもとへ説得に行くのも大歓迎。まずはメッセージを貰えれば、Git導入にどうやって着手していけばいいか相談にのります :)

## 最後に

原文（中国語）のリンク：
<https://gitbook.tw/interview>

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35712/5dc0dd43-3dca-020d-685b-a3710fecb0ad.jpeg" width="350px" alt="やりたいことが今すぐわかる逆引きGit入門">

もっと詳しく知りたい方は、拙著「[__やりたいことが今すぐわかる逆引きGit入門__](https://ubin.io/gitbookjp)」をご参照ください。Amazonでの購入はこちらから：
<https://www.amazon.co.jp/やりたいことが今すぐわかる-逆引きGit入門/dp/4798059595/>