最新のコミットをGitを使用して新しいブランチに移動する


4985

マスターにコミットした最後のいくつかのコミットを新しいブランチに移動し、それらのコミットが行われる前にマスターを戻したいのですが。残念ながら、私のGit-fuはまだ十分に強力ではありません。

つまりこれからどうやって行くのですか

master A - B - C - D - E

これに?

newbranch     C - D - E
             /
master A - B 

114
注:私はここで
Benjol

3
eddmann.com/posts/…これは機能します
Sagar Naliyapara

7
ここのコメントは削除されましたか?私が隔月でこの質問に訪れている間、私はいつもそのコメントでスクロールするので、私は尋ねます。
Tejas Kale

回答:


6452

既存のブランチに移動する

コミットを既存のブランチに移動する場合は、次のようになります。

git checkout existingbranch
git merge master         # Bring the commits here
git checkout master
git reset --keep HEAD~3  # Move master back by 3 commits.
git checkout existingbranch

この--keepオプションは、関係のないファイルにある可能性のあるコミットされていない変更を保持するか、それらの変更を上書きする必要がある場合は、処理と同様に中止git checkoutします。中止した場合はgit stash、変更して再試行するか、またはを使用--hardして変更を失います(コミット間で変更されなかったファイルからでも!)

新しいブランチに移動する

この方法は、最初のコマンド(git branch newbranch)で新しいブランチを作成することによって機能しますが、それには切り替えません。次に、現在のブランチ(マスター)をロールバックし、新しいブランチに切り替えて作業を続けます。

git branch newbranch      # Create a new branch, containing all current commits
git reset --keep HEAD~3   # Move master back by 3 commits (Make sure you know how many commits you need to go back)
git checkout newbranch    # Go to the new branch that still has the desired commits
# Warning: after this it's not safe to do a rebase in newbranch without extra care.

ただし、戻すコミットの数を確認してください。または、の代わりにHEAD~3origin/master元に戻したいコミット(またはのような参照)のハッシュを単に提供することもできます。例:

git reset --keep a1b2c3d4

警告: Gitバージョン2.0以降では、後でgit rebase元の(master)ブランチ上に新しいブランチを作成する場合--no-fork-point、マスターブランチから移動したコミットが失われないように、リベース中に明示的なオプションが必要になる場合があります。持ってbranch.autosetuprebase alwaysセットすると、この可能性が高くなります。詳細については、John Mellorの回答を参照してください。


249
特に、他の誰かがプルした可能性のある別のリポジトリに最後にコミットをプッシュした時点からさかのぼってはいけません
グレッグ・ヒューギル

107
これが機能する理由を説明できるかどうか疑問に思います。私にとっては、新しいブランチを作成し、まだ残っている古いブランチから3つのコミットを削除してから、作成したブランチをチェックアウトしています。では、削除したコミットを新しいブランチに魔法のように表示するにはどうすればよいですか?
Jonathan Dumaine、

142
@Jonathan Dumaine:古いブランチからコミットを削除する前に新しいブランチを作成したからです。彼らはまだ新しいブランチにいます。
sykora 2010

86
gitのブランチは、履歴内のコミットを指す単なるマーカーであり、複製、作成、または削除されるものはありません(マーカーを除く)
knittl

218
また、注意:作業コピーにコミットされていない変更を加えないでください!これはちょうど私を噛んだ!:(
アダム・タトル

1039

なぜそれが機能するのかと思っている人のために(最初はそうでした):

Cに戻り、DとEを新しいブランチに移動します。最初は次のようになります。

A-B-C-D-E (HEAD)
        ↑
      master

git branch newBranch

    newBranch
        ↓
A-B-C-D-E (HEAD)
        ↑
      master

git reset --hard HEAD~2

    newBranch
        ↓
A-B-C-D-E (HEAD)
    ↑
  master

ブランチは単なるポインタなので、マスターは最後のコミットをポイントしました。newBranchを作成したときは、最後のコミットへの新しいポインタを作成しただけです。次にgit resetマスターポインターを2つのコミットに戻しました。しかし、newBranchを移動しなかったので、それは最初に行ったコミットをまだ指し示しています。


56
またgit push origin master --force、変更をメインリポジトリに表示するために必要な作業もありました。
Dženan

9
この回答により、コミットが失われます。次回git rebaseに、3つのコミットがから暗黙的に破棄されnewbranchます。参照してください私の答えの詳細とより安全な代替のために。
John Mellor 2016

14
@ジョン、それはナンセンスです。何をしているかを知らずにリベースすると、コミットが失われます。コミットを失った場合は申し訳ありませんが、この回答はコミットを失いませんでした。origin/master上の図には表示されていないことに注意してください。プッシュしてorigin/master上記の変更を行った場合、確かに状況はおかしくなります。しかし、それは「医師、私がこれを行うと痛い」という種類の問題です。そして、それは元の質問が尋ねたものの範囲外です。このシナリオを乗っ取るのではなく、シナリオを探索するために独自の質問を書くことをお勧めします。
Ryan Lundy

1
@ジョン、あなたの答えで、「これをしないでください!git branch -t newbranch」と言いました 。戻ってもう一度答えを読んでください。 誰もそれをすることを提案しませんでした。
Ryan Lundy

1
@Kyralessa、確かに、でも質問の図を見ると、newbranch彼らが既存のローカルmasterブランチをベースにしたいことは明らかです。ユーザーが実行している程度取得するときに受け入れ答えを実行した後、git rebasenewbranch、gitのは、彼らが実行されますので、上流分岐を設定するのを忘れていること、それらを思い出させるgit branch --set-upstream-to=master、その後git rebase、同じ問題を抱えています。彼らはgit branch -t newbranchそもそも使うかもしれません。
John Mellor

455

一般に...

この場合、sykoraによって公開されるメソッドが最良のオプションです。しかし、これは最も簡単なことではなく、一般的な方法ではありません。一般的な方法では、git cherry-pickを使用します。

OPが望むものを達成するために、その2ステップのプロセス:

ステップ1-マスターからのどのコミットを newbranch

実行する

git checkout master
git log

必要な(たとえば3つの)​​コミットのハッシュに注意してくださいnewbranch。ここでは私が使用します:
Cコミット:9aa1233
Dコミット:453ac3d
Eコミット:612ecb3

注:最初の7文字またはコミットハッシュ全体を使用できます

ステップ2-それらを newbranch

git checkout newbranch
git cherry-pick 612ecb3
git cherry-pick 453ac3d
git cherry-pick 9aa1233

または(Git 1.7.2以降では範囲を使用)

git checkout newbranch
git cherry-pick 612ecb3~1..9aa1233

git cherry-pickはこれら3つのコミットをnewbranchに適用します。


13
OP マスターから新しいブランチに移動しようとしていませんでしたか?マスターでチェリーピックする場合は、マスターブランチに追加することになります-実際には、すでに持っているコミットを追加します。そして、それはどちらのブランチヘッドもBに戻りません。または、私が得ていない微妙でクールなものはありますか?
RaveTheTadpole 2012

6
これは、新しい機能ブランチを作成する必要があるときに、誤って非マスターブランチを誤ってコミットした場合に非常にうまく機能します。
julianc 14

5
状況によっては、+ 1を使用すると便利です。これは、自分のコミット(他のユーザーが混在している)だけを新しいブランチにプルしたい場合に適しています。
タイラーV.

8
より良い答えです。このようにして、コミットを任意のブランチに移動できます。
スカイワインダー2014年

8
桜狩りの順番は大事?
kon psych 2015

328

これを行うもう1つの方法は、2つのコマンドだけを使用することです。また、現在の作業ツリーをそのまま維持します。

git checkout -b newbranch # switch to a new branch
git branch -f master HEAD~3 # make master point to some older commit

古いバージョン -私が知る前にgit branch -f

git checkout -b newbranch # switch to a new branch
git push . +HEAD~3:master # make master point to some older commit 

することができることpushをすることは.知っている素敵なトリックです。


1
カレントディレクトリ。これは、トップディレクトリにいる場合にのみ機能すると思います。
aragaer 2014年

1
ローカルプッシュはにやにや笑いを誘発しますが、考えてみると、git branch -fこことどう違うのですか?
jthill 2014

2
@GerardSexton .が現在のディレクターです。gitはREMOTESまたはGIT URLにプッシュできます。path to local directoryサポートされているGit URL構文。のGIT URLSセクションを参照してくださいgit help clone
2014年

31
なぜこれより高く評価されないのかわかりません。非常にシンプルで、git resetの小さな、しかし潜在的な危険がない--hard。
Godsmith、2015

4
@Godsmith私の推測では、人々は2つの少しあいまいなコマンドよりも3つの単純なコマンドを好むと思います。また、上位投票の回答は、最初に表示されるという性質上、より多くの投票を獲得します。
JS_Riddler

323

以前のほとんどの答えは危険なほど間違っています!

こんなことしないで:

git branch -t newbranch
git reset --hard HEAD~3
git checkout newbranch

次回git rebase(またはgit pull --rebase)を実行すると、これらの3つのコミットはnewbranch!(以下の説明を参照)

代わりにこれを行います:

git reset --keep HEAD~3
git checkout -t -b newbranch
git cherry-pick ..HEAD@{2}
  • 最初に、最新の3つのコミットを破棄します(--keepに似--hardていますが、コミットされていない変更を破棄するよりも失敗するため、より安全です)。
  • その後、分岐しnewbranchます。
  • 次に、これらの3つのコミットをに戻しますnewbranch。彼らはもはや、分岐によって参照しているので、それはそれはgitの使用しないREFLOGをHEAD@{2}それはコミットされたHEAD2つの操作前に参照するために使用される、すなわち前に我々 1.チェックアウトしないnewbranchと2を使用git reset3つのコミットを破棄します。

警告:reflogはデフォルトで有効になっていますが、手動で無効にした場合(「裸」のgitリポジトリを使用するなど)、実行後に3つのコミットを取得することはできませんgit reset --keep HEAD~3

reflogに依存しない別の方法は次のとおりです。

# newbranch will omit the 3 most recent commits.
git checkout -b newbranch HEAD~3
git branch --set-upstream-to=oldbranch
# Cherry-picks the extra commits from oldbranch.
git cherry-pick ..oldbranch
# Discards the 3 most recent commits from oldbranch.
git branch --force oldbranch oldbranch~3

(必要に応じて@{-1}、以前にチェックアウトしたブランチ-の代わりに書き込むことができますoldbranch)。


技術説明

git rebase最初の例の後で3つのコミットを破棄するのはなぜですか?これgit rebaseは、引数を指定しないと--fork-pointデフォルトでオプションが有効になるためです。このオプションでは、ローカルreflogを使用して、強制的にプッシュされる上流ブランチに対してロバストになるように試みます。

コミットM1、M2、M3が含まれている場合にorigin / masterを分岐し、次に3つのコミットを自分で作成したとします。

M1--M2--M3  <-- origin/master
         \
          T1--T2--T3  <-- topic

しかし、誰かが起点/マスターを強制的にプッシュすることで履歴を書き換え、M2を削除します。

M1--M3'  <-- origin/master
 \
  M2--M3--T1--T2--T3  <-- topic

ローカルのreflogを使用するgit rebaseと、origin / masterブランチの初期の化身から分岐したことがわかります。したがって、M2とM3のコミットは、実際にはトピックブランチの一部ではありません。したがって、M2は上流のブランチから削除されたため、トピックブランチがリベースされると、トピックブランチでM2が不要になると合理的に想定しています。

M1--M3'  <-- origin/master
     \
      T1'--T2'--T3'  <-- topic (rebased)

この動作は理にかなっており、通常、リベースするときに行うのが適切です。

したがって、次のコマンドが失敗する理由:

git branch -t newbranch
git reset --hard HEAD~3
git checkout newbranch

これは、reflogを間違った状態のままにするためです。Git newbranchは、3つのコミットを含むリビジョンでアップストリームブランチから分岐したと見なします。次に、reset --hardアップストリームの履歴を書き換えてコミットを削除します。次回実行するgit rebaseと、アップストリームから削除された他のコミットと同様に、コミットが破棄されます。

ただし、この特定のケースでは、これらの3つのコミットをトピックブランチの一部と見なす必要があります。そのためには、3つのコミットを含まない以前のリビジョンでアップストリームを分岐する必要があります。これが私の提案する解決策です。したがって、どちらもreflogを正しい状態のままにします。

詳細について--fork-pointは、git rebaseおよびgit merge-baseのドキュメントの定義をご覧ください。


15
この答えは「これを行わないでください!」誰も提案していない何かの上に。
Ryan Lundy

4
ほとんどの人は、特にで、公開された履歴を書き換えませんmaster。したがって、それらは危険なほど間違っているわけではありません。
Walf

4
@Kyralessa、-tあなたが言及しているあなたはgit branchあなたがgit config --global branch.autosetuprebase always設定した場合暗黙のうちに起こります。そうでない場合でも、これらのコマンドを実行した後にトラッキングを設定すると同じ問題が発生することをすでに説明しました。OPはおそらくそれらの質問を想定しているためです。
John Mellor

2
@RockLee、はい、そのような状況を修正する一般的な方法は、安全な開始点から新しいブランチ(newbranch2)を作成し、保持したいすべてのコミット(badnewbranchからnewbranch2まで)をチェリーピックすることです。チェリーピッキングはコミットに新しいハッシュを与えるので、安全にnewbranch2をリベースできるようになります(今ではbadnewbranchを削除できます)。
John Mellor

2
@Walf、あなたは誤解しました:git rebaseは、履歴が書き直されるアップストリームに対して堅牢になるように設計されています。残念ながら、その堅牢性の副作用は、たとえどちらも上流も履歴を書き換えなかったとしても、全員に影響を与えます。
John Mellor

150

git stashを使用したはるかに単純なソリューション

これは、間違ったブランチへのコミットのためのはるかに簡単な解決策です。master3つの誤ったコミットがあるブランチから開始します。

git reset HEAD~3
git stash
git checkout newbranch
git stash pop

いつこれを使うのですか?

  • 主な目的がロールバックである場合 master
  • ファイルの変更を保持したい
  • 誤ったコミットのメッセージは気にしません
  • まだプッシュしていません
  • 覚えやすくしたい
  • 一時的/新しいブランチ、コミットハッシュの検索とコピー、その他の頭痛の種のような複雑化を望まない

これが行番号で行うこと

  1. への最後の3つのコミット(およびそのメッセージ)を元masterに戻しますが、すべての作業ファイルはそのまま残します
  2. すべての作業ファイルの変更を隠して、master作業ツリーをHEAD〜3状態と完全に一致させます
  3. 既存のブランチに切り替えます newbranch
  4. stashされた変更を作業ディレクトリに適用し、stashをクリアします

これで、使用することができますgit addし、git commit通常どおりに。新しいコミットはすべてに追加されnewbranchます。

これがしないこと

  • ランダムな一時的なブランチがあなたのツリーを散らかすことはありません
  • 誤ったコミットメッセージは保持されないため、この新しいコミットに新しいコミットメッセージを追加する必要があります
  • 更新!上矢印を使用してコマンドバッファーをスクロールし、以前のコミットをそのコミットメッセージで再適用します(@ARKに感謝)

ゴール

OPの目標は、変更を失うことなく「マスターがそれらのコミットが行われる前に戻す」ことであり、このソリューションはそれを実現します。

これを少なくとも週に1回、誤ってのmaster代わりに新しいコミットを作成したときに行いdevelopます。通常、ロールバックするコミットは1つだけです。その場合git reset HEAD^、1行目を使用すると、1つのコミットだけをロールバックする最も簡単な方法になります。

マスターの変更を上流にプッシュした場合は、これを行わないでください

他の誰かがそれらの変更をプルした可能性があります。ローカルマスターのみを書き換える場合、上流にプッシュしても影響はありませんが、書き換えた履歴を共同編集者にプッシュすると頭痛の種になる可能性があります。


4
おかげで、ここまでたどり着くために過去/全体を読んだことをうれしく思います。これも私にとってかなり一般的な使用例であるためです。私たちはとても変則的ですか?
ジム・マック

6
私たちは完全に典型的だと思います。「誤ってマスターすることを約束したループ」は、少数のコミットまたは少数のコミットを元に戻す必要がある最も一般的なユースケースです。幸運なことに、この解決策はとてもシンプルなので、今覚えています。
スラム

9
これは受け入れられる答えになるはずです。分かりやすく、覚えやすく、覚えやすい
Sina Madani 2018年

1
隠し場所が必要だとは思いません。私はそれなしでそれをやっただけでうまくいきました。
カンポス

1
コミットメッセージをCLI(コマンドライン)履歴に記録しておくと、コミットメッセージを簡単に戻すことができます。たまたま使用したコマンドgit addgit commitコマンドの両方があったので、上矢印キーを押して数回入力してブームするだけで済みました!すべてが戻ってきましたが、今は正しいブランチにあります。
ルークゲデオン

30

これは技術的な意味でそれらを「移動」させませんが、同じ効果があります:

A--B--C  (branch-foo)
 \    ^-- I wanted them here!
  \
   D--E--F--G  (branch-bar)
      ^--^--^-- Opps wrong branch!

While on branch-bar:
$ git reset --hard D # remember the SHAs for E, F, G (or E and G for a range)

A--B--C  (branch-foo)
 \
  \
   D-(E--F--G) detached
   ^-- (branch-bar)

Switch to branch-foo
$ git cherry-pick E..G

A--B--C--E'--F'--G' (branch-foo)
 \   E--F--G detached (This can be ignored)
  \ /
   D--H--I (branch-bar)

Now you won't need to worry about the detached branch because it is basically
like they are in the trash can waiting for the day it gets garbage collected.
Eventually some time in the far future it will look like:

A--B--C--E'--F'--G'--L--M--N--... (branch-foo)
 \
  \
   D--H--I--J--K--.... (branch-bar)

1
rebase同じ事に使えませんか?
Bergi、2015年

はいrebase、上記のシナリオの分離ブランチで代わりに使用できます。
スキマスイッチ

24

履歴を書き換えずにこれを行うには(つまり、すでにコミットをプッシュしている場合):

git checkout master
git revert <commitID(s)>
git checkout -b new-branch
git cherry-pick <commitID(s)>

その後、両方のブランチを無理なく押すことができます!


ただし、状況によっては、かなり複雑になる可能性のある復帰シナリオに対処する必要があります。ブランチでコミットを元に戻しても、Gitはそれらのコミットが行われたと見なします。そのため、元に戻すには、元に戻す必要があります。これは、特にマージを元に戻してブランチをマージしようとするときにかなりの数の人を燃やしますが、Gitはブランチがすでにマージされていると信じていることに気づきます(これは完全に真実です)。

1
そのため、最後のコミットを新しいブランチにチェリーピックします。そうすれば、gitはそれらを新しいコミットと見なし、問題を解決します。
teh_senaus

この状態の影響を実際に理解せずにリポジトリの履歴の状態を変更しているため、これは最初に思われるよりも危険です。

5
私はあなたの主張には従いません-この回答の要点は、履歴を変更するのではなく、単に新しいコミットを追加することです(変更を効果的にやり直します)。これらの新しいコミットは、通常どおりにプッシュおよびマージできます。
teh_senaus

13

このような状況でした:

Branch one: A B C D E F     J   L M  
                       \ (Merge)
Branch two:             G I   K     N

私が実行した:

git branch newbranch 
git reset --hard HEAD~8 
git checkout newbranch

コミットはヘッドになると思っていましたが、コミットLは今です...

履歴の正しい場所に確実に到達するために、コミットのハッシュを操作する方が簡単です

git branch newbranch 
git reset --hard #########
git checkout newbranch

8

これからどうやって行くの?

A - B - C - D - E 
                |
                master

これに?

A - B - C - D - E 
    |           |
    master      newbranch

2つのコマンドを使用

  • git branch -m master newbranch

与える

A - B - C - D - E 
                |
                newbranch

そして

  • gitブランチマスターB

与える

A - B - C - D - E
    |           |
    master      newbranch

うん、これは動作し、非常に簡単です。SourcetreeのGUIは、gitシェルで行われた変更について少し混乱していますが、フェッチ後は問題ありません。
lars k。

はい、質問のとおりです。最初の2組の図は、問題の図と同等であることを目的としています。回答で説明するために、希望する方法で再描画します。基本的にマスターブランチの名前をnewbranchに変更し、必要な場所に新しいマスターブランチを作成します。
Ivan

これが正しい答えだと思います。
レオナルド・エレーラ

4

プッシュされていないすべてのコミットを新しいブランチに移動する必要があるだけの場合は、

  1. 作成新しいブランチを現在の1から:git branch new-branch-name

  2. 新しいブランチをプッシュします。git push origin new-branch-name

  3. あなたの古い(現在の)ブランチを最後にプッシュされた/安定した状態に戻します:git reset --hard origin/old-branch-name

一部の人々は、他のを持っているupstreamsのではなくorigin、彼らが適切なを使用する必要がありますupstream


3

1)すべての変更をnew_branchに移動する新しいブランチを作成します。

git checkout -b new_branch

2)次に、古いブランチに戻ります。

git checkout master

3)git rebaseを実行します

git rebase -i <short-hash-of-B-commit>

4)次に、開いたエディターには最後の3つのコミット情報が含まれます。

...
pick <C's hash> C
pick <D's hash> D
pick <E's hash> E
...

5)これら3つのコミットすべてpickdropに変更します。次に、エディターを保存して閉じます。

...
drop <C's hash> C
drop <D's hash> D
drop <E's hash> E
...

6)最後の3つのコミットが現在のブランチから削除されmasterます()。次に、+ブランチ名の前に記号を付けて、ブランチを強制的にプッシュします。

git push origin +master

2

これは、私が使用した3つの簡単なステップです。

1)最近の更新をコミットする場所に新しいブランチを作成します。

git branch <branch name>

2)新しいブランチでコミットするための最近のコミットIDを検索します。

git log

3)そのコミットIDをコピーします。最新のコミットリストが一番上に表示されることに注意してください。あなたはあなたのコミットを見つけることができます。また、メッセージを介してこれを見つけます。

git cherry-pick d34bcef232f6c...

また、いくつかのコミットIDを提供することもできます。

git cherry-pick d34bcef...86d2aec

これであなたの仕事は終わりました。正しいIDと正しいブランチを選択した場合、成功します。したがって、これを行う前に注意してください。そうしないと、別の問題が発生する可能性があります。

これでコードをプッシュできます

git push


0

これを行う別の方法:

[1]masterブランチの名前を自分の名前に変更しますnewbranchmasterブランチにいる場合):

git branch -m newbranch

[2]master希望するコミットからブランチを作成します。

git checkout -b master <seven_char_commit_id>

例:git checkout -b master a34bc22

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.