gitで1つのファイルを以前のバージョンに戻す

あるファイルの異なるコミットをさかのぼる方法はありますか? 例えば、あるファイルを5回変更した後、コミットしてリポジトリにプッシュした後に、変更点2に戻りたいとします。

私の理解では、唯一の方法は多くのブランチを維持することですが、これは正しいでしょうか? もし私の理解が正しければ、数日のうちに何百ものブランチを持つことになるでしょうから、たぶん本当に理解していないのだと思います。

どなたか教えてください。

ソリューション

まず、私たちがやりたいことを定性的に説明しましょう(多くはBen Straub'の回答に書かれています)。いくつかのコミットを行い、そのうちの5つがあるファイルを変更したので、そのファイルを以前のバージョンに戻したいと考えています。まず第一に、git は個々のファイルのバージョン番号を記録しません。つまり、コミットは基本的に作業ツリーのスナップショットであり、いくつかのメタデータ (コミットメッセージなど) も含まれています。そのため、どのコミットに目的のファイルのバージョンがあるのかを知る必要があります。それがわかれば、ファイルをその状態に戻すための新しいコミットを作成する必要があります。(履歴をいじることはできません。なぜなら、私たちはすでにこのコンテンツをプッシュしており、履歴を編集すると他の人たちに迷惑がかかるからです)

では、正しいコミットを見つけることから始めましょう。あるファイルに変更を加えたコミットは、とても簡単に見ることができます。

git log path/to/file

コミットメッセージでは不十分で、各コミットでファイルに何が行われたかを確認する必要がある場合は、-p/--patchオプションを使用します。

git log -p path/to/file

あるいは、gitk のグラフィカルな表示のほうが好きなら

gitk path/to/file

これは、gitk を起動した後でビューメニューから行うこともできます。ビューのオプションのひとつに、インクルードするパスのリストが用意されています。

どちらにしても、あなたが欲しいファイルのバージョンを含むコミットの SHA1 (ハッシュ) を見つけることができます。あとは、これだけです。

# get the version of the file from the given commit
git checkout  path/to/file
# and commit this modification
git commit

(checkoutコマンドは、まずファイルをインデックスに読み込んでから作業ツリーにコピーしますので、git addを使ってコミットの準備としてインデックスに追加する必要はありません)

ファイルに単純な履歴がない場合(リネームやコピーなど)は、VonC'さんの素晴らしいコメントを参照してください。git` は、速度を犠牲にしても、そのようなものをより注意深く検索するように指示することができます。履歴が単純であると確信しているのであれば、気にする必要はありません。

解説 (11)

Gitは、ファイルのバージョンという観点からは考えません。gitのバージョンとは、ツリー全体のスナップショットです。

そう考えると、あなたが本当に欲しいのは、ほとんどのファイルの内容が最新のものになっていて、かつあるファイルの内容が5コミット前と同じものになっているツリーです。これは、古いコミットの上に新しいコミットをするという形で行われ、ツリーの最新バージョンはあなたが望むものになります。

1つのファイルを5コミット前の内容に戻すワンライナーがあるかどうかはわかりませんが、「master~5をチェックアウトして、ファイルをどこかにコピーして、masterをチェックアウトして、ファイルをコピーして、コミットする」というローファイな解決策は機能するはずです。

解説 (2)

必要な変更を元に戻す diff を取り、それをコミットすることができます。

例:from..toの範囲の変更を元に戻したい場合、以下のようにします。

git diff to..from > foo.diff  # get a reverse diff
patch < foo.diff
git commit -a -m "Undid changes from..to".
解説 (5)