私は数日前に master から分岐し、newbranch
.
後で、最初に作成したコミットのファイルが変更されていないことに気付きました。マスター ブランチにはまだ何もプッシュしていません。すべての変更は にありnewbranch
ます。newbranch
この 1 つのファイルを、最初に master から分岐したときの場所にロールバックするにはどうすればよいですか?
私は数日前に master から分岐し、newbranch
.
後で、最初に作成したコミットのファイルが変更されていないことに気付きました。マスター ブランチにはまだ何もプッシュしていません。すべての変更は にありnewbranch
ます。newbranch
この 1 つのファイルを、最初に master から分岐したときの場所にロールバックするにはどうすればよいですか?
git checkout master -- filepath
必要な結果に応じて、これを行うにはいくつかの方法があります。
euphoria83 からの回答はこれを行います...
最初は、次のようなコミット履歴があります (各文字はコミットを表します)。
A - B - C <-- master
\
D
\
E <-- HEAD=newbranch
コミットC
は、開始時にマスターにあったものです。いくつかの変更を加えてコミットし ( を作成していますD
)、さらにいくつか変更してコミットしました ( を作成していますE
)。
今、あなたは実行しますgit checkout HEAD~1
。D
これにより、コミット D に一致するように作業ディレクトリが更新され、ブランチ名を含むのではなく、commit を直接指す「切り離された HEAD」(フランス革命の色合い!) が得られます。
A - B - C <-- master
\
D <-- HEAD
\
E <-- newbranch
(この形式のチェックアウト , はgit checkout <branch-name-or-rev>
、どのHEAD
名前を変更するかを git に指示します。<rev>
この場合の ,HEAD~1
は、「どのコミットの HEAD 名を調べて、1 つ前に移動するか」を意味するため、これは commit から 1 つ前に移動するようE
に<rev>
指示します。ブランチ名ではなく、「HEAD を切り離す」効果もあります。)
今、あなたは実行しますgit checkout HEAD~1 filename
。git に対してより明確にするには、git checkout HEAD~1 -- filename
. は、--
「残りの引数はファイル名であり、ブランチ名やリビジョン名ではありません」と述べています。 を省略すると--
、git はそれがブランチ名かファイル名かを推測しようとします。
引数を指定したこの形式の はcheckout
、-- filename
かなり異なることを行います。HEAD~1
通常どおりリビジョンを検索するように指示されますが、今回は を変更 HEAD
せずに、指定されたファイルを抽出するだけです。HEAD
現在は rev. という名前が付けられているためD
、git はもう 1 つ、 rev. にバックアップし、そのリビジョンからC
ファイルのバージョンを抽出しfilename
て、作業ディレクトリに配置します。
(ブランチ名はリビジョンにmaster
も名前を付けていることに注意してください。したがって、この時点でC
書くことができます。)git checkout master -- filename
次に、git commit -a --amend
変更されたファイルを追加するように git に指示します。この場合、実際には必要ありませんが、通常は、修正した他のファイルが追加されます。その後、「修正コミット」を実行します。「修正コミット」とは、「新しいコミットを作成するが、その親コミットを親コミットと同じにする」ことを意味します。D'
これにより、新しいコミットが作成されます—親が であるとしましょうC
。いつものように、新しいコミットはHEAD
. HEAD
はデタッチされているため、ブランチ名はまだ移動していません。結果のコミット ツリーは次のようになります。
A - B - C <-- master
| \
\ D
\ \
\ E <-- newbranch
\
D' <-- HEAD
のファイルはD'
、D
file を除いて のファイルと同じです。ただし、filename
現在は Revision と同じC
です。
次に、git cherry-pick newbranch
によって名前が付けられたリビジョンを取得するように指示します—それはnewbranch
リビジョンですE
—新しいコミットとして HEAD リビジョンに同じ変更を加えます (それを と呼びましょうE'
):
A - B - C <-- master
| \
\ D
\ \
\ E <-- newbranch
\
D' - E' <-- HEAD
あとは、 にブランチ名を付けるだけですHEAD
。次に、 を削除するnewbranch
か、邪魔にならないように名前を変更してから、 に名前newnew
を変更しても安全newbranch
です。
git checkout -b newnew # now HEAD=newnew which points to E'
git branch -m newbranch oldnew # rename out of the way
git branch -m newnew newbranch # and rename newnew to newbranch
(これを少し短くすることもできますが、次に進みましょう...)
同じことをもっと簡単に行う方法があります。実行するだけgit rebase -i master
です。newbranch
これは、現在のブランチ ( ) をmaster
「上流」として使用してリベースすることを示しています。つまり、以降のすべてのコミットを検索しmaster
(つまり、D
and )、それらを(以前に基づいていた場所にE
) リベースします。master
これがなけれ-i
ばばかげています—リベースD
しE
て以前とまったく同じようにすることは、何もしないのに費用がかかる方法です—しかし、-i
git を使用すると、一連のコマンドでエディターが開きます。コマンドはpick
コミットD
し、E
. pick
for commitD
を に変更edit
し、ファイルを書き出します。
その後、リベースはチェリーピックD
して停止し、コミットを修正します。シェルで、次のように入力します。
git checkout master -- filename
git commit -a --amend
前と同じように、修正されたコミットを保存します。これは になりD'
ます。次に実行します。
git rebase --continue
Git は commit をチェリーピックし、 commitE
を与えE'
ます。これですべてが完了したので、リベースが終了し、ブランチnewbranch
には必要なコミットが含まれます。
ДМИТРИЙ МАЛИКОВ からの回答は、何か違うことをします。同じgit checkout master -- filename
(綴りfilepath
の ) が含まれていますが、リベースのようなシーケンスは含まれていません。
したがって、前と同様に、次のように開始します。
A - B - C <-- master
\
D
\
E <-- HEAD=newbranch
filename
次に、改訂中の のバージョンをC
作業ディレクトリに抽出します。あなたが今git commit
(またはgit commit -a
、この特定のケースで同じこと - git これはcheckout
、抽出されたrev-Cファイルを最初にステージング領域に書き込むためです)、F
ファイルfilename
を元の状態に戻す新しいコミットを取得しますC
.
言い換えれば、コミットD
しE
てもファイルへの変更は残ります。新しいコミットF
は変更を元に戻します。