Git で最新のコミットを新しいブランチに移動します。

git git-branch branching-and-merging


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

つまり、この状態からどのようにして

master A - B - C - D - E

これに?

newbranch     C - D - E
             /
master A - B 



Answer 1 Duc Filan


既存の支店への移動

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

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~3 代わりに、元に戻したいコミットのハッシュ(または origin/master のような参照)を提供することもできます。例:

git reset --keep a1b2c3d4

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




Answer 2 Ryan Lundy


なぜ効果があるのか疑問に思っている方へ(最初は私もそうでしたが)。

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を移動しなかったので、それは最初に行ったコミットをまだ指し示しています。




Answer 3 Ivan


一般的には...

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

OPが欲しいものを実現するためには、2段階のプロセスが必要です。

ステップ1- newbranch 必要なマスターからのコミットをメモする

Execute

git checkout master
git log

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

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

ステップ2-それらを newbranch 置く

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

OR (Git 1.7.2 以降では ranges を使用)

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

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




Answer 4 John Mellor


過去の回答のほとんどが危険なほど間違っています

絶対にやってはいけないことです。

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} ということにコミットされていない HEAD は我々 1.チェックアウトする前に、すなわち、2つの操作前に参照するために使用 newbranch と2.使用 git reset して3つのコミットを破棄します。

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

リブログに頼らない代替案は

# 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

(必要に応じて、 oldbranch の代わりに、以前にチェックアウトしたブランチである @{-1} 記述できます)。


技術的な説明

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

origin/master に M1,M2,M3 のコミットが含まれているときに分岐し、自分で 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 は、3つのコミットを含むリビジョンでアップストリームブランチを分岐したものとして newbranch を認識し、次に reset --hard はアップストリームの履歴を書き換えてコミットを削除します。そのため、次に git rebase を実行すると、他のコミットと同様にそれらを破棄します。アップストリームから削除されました。

しかし、この特定のケースでは、これらの 3 つのコミットをトピックブランチの一部とみなしたいのです。そのためには、3 つのコミットを含まない以前のリビジョンのアップストリームをフォークオフする必要があります。これが私の提案する解決策です。

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




Answer 5 aragaer


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. 知っておくと便利です。