web-dev-qa-db-ja.com

2つのブランチを同一にする

AとBの2つのブランチがあります。BはAから作成されました。

両方のブランチでの作業は並行して続けられました。ブランチBでの作業は良好でしたが、ブランチAでの作業は不良でした(結果として機能しないバージョンになりました)。この間、ブランチBがブランチAにマージされることがありました(ただし、その逆ではありません)。

ブランチAをブランチBと同一にしたいと思います。あまりにも多くのコミットを元に戻す必要があるため、git revertは使用できません-ブランチBのマージの結果としてではなく、ブランチAで行われたコミットのみを元に戻したい。

私が見つけた解決策は、ブランチBを別のフォルダーにクローンし、ブランチAの作業フォルダーからすべてのファイルを削除し、一時ブランチBフォルダーからファイルをコピーし、追跡されていないファイルをすべて追加することでした。

同じことを行うgitコマンドはありますか?私が逃したいくつかのgit revertスイッチ?

25
zmbq

これを行うには多くの方法があり、どの方法を使用するかは、どのような結果が必要か、特にあなたとあなたと協力している人(これが共有リポジトリの場合)が将来何を期待するかによって異なります。

これを行う主な3つの方法:

  1. 気にしないでください。ブランチAを放棄し、新しいA2を作成し、それを使用します。
  2. git resetまたは同等のものを使用して、Aを他の場所に再ポイントします。

    方法1と2は、長期的には事実上同じです。たとえば、Aを放棄することから始めたとします。しばらくの間、誰もがA2で開発します。次に、誰もがA2を使用したら、名前Aを完全に削除します。 Aがなくなったので、A2の名前をAに変更することもできます(もちろん、それを使用する人は誰でも同じ名前変更を行う必要があります)。この時点で、方法1を使用した場合と方法2を使用した場合の違いは、あるとすれば何ですか? (1つありますまだ違いを見ることができる場合がありますが、「長期」の期間と古いreflogの有効期限によって異なります。 )

  3. 特別な戦略を使用してマージを行います(以下の「代替マージ戦略」を参照)。ここで欲しいのは-s theirsですが、それは 存在しない であり、あなたはそれを偽造しなければなりません。

サイドノート:ブランチの「作成元」の場所は基本的に無関係です。gitはそのようなことを追跡しないため、一般に後で発見することはできないため、おそらくあなたにとっても問題ではありません。 (本当に重要な場合は、ブランチを操作する方法にいくつかの制限を課すか、タグで「開始点」をマークして、後でこの情報を機械的に回復できるようにすることができます。あなたはそもそも何か間違ったことをしている-少なくとも、gitが提供しないgitの何かを期待している。以下の脚注1も参照。)

ブランチの定義( ブランチとはどういう意味ですか? も参照してください)

Gitのブランチ(より正確にはブランチname)は、コミットグラフ内の特定のコミットへのポインタです。これは、タグ名などの他の参照にも当てはまります。たとえば、タグと比較してブランチを特別なものにしているのは、onブランチにいてnewcommit、gitはブランチ名を自動的に更新し、新しいコミットを指すようにします。

たとえば、oノード(および*とマークされたノード)もコミットを表し、ABがブランチ名である、次のようなコミットグラフがあるとします。

o <- * <- o <- o <- o <- o   <-- A
      \
       o <- o <- o           <-- B

AおよびBはそれぞれ、ブランチの最も先端のコミット、より正確には、あるコミットから開始して戻ることによって形成されるブランチデータ構造を指しますすべての到達可能なコミットを通じて、各コミットはいくつかの親コミットを指します。

git checkout Bを使用してブランチBにアクセスし、新しいコミットを作成すると、新しいコミットは、前のBのヒントを(単一の)親としてセットアップされ、gitは、ブランチ名Bは、Bが新しい最新のコミットを指すようにします。

o <- * <- o <- o <- o <- o   <-- A
      \
       o <- o <- o <- o      <-- B

*とマークされたコミットは、2つのブランチチップのマージベースです。このコミットおよび以前のすべてのコミットは、bothブランチにあります。1 マージベースは、マージ(duh :-)にとって重要ですが、git cherryやその他のリリース管理タイプの操作などにも重要です。

あなたは、ブランチBが時折Aにマージされたと言いました:

git checkout A; git merge B

これにより、新しいmerge commitが作成されます。2 親。 first親は現在のブランチの前のチップ、つまりAの前のチップであり、2番目の親は名前付きコミット、つまりブランチBの先端コミット。上記のコードをもう少しコンパクトに再描画します(ただし、osのラインアップを改善するために-sをさらに追加します)。

o--*--o--o--o--o      <-- A
    \
     o---o--o---o     <-- B

最終的には:

o--*--o--o--o--o--o   <-- A
    \            /
     o---o--o---*     <-- B

*ABの新しいマージベース(実際はBの先端)に移動します。おそらく、Bにさらにコミットを追加し、さらに数回マージする可能性があります。

...---o--...--o--o    <-- A
     /       /
...-o--...--*--o--o   <-- B

Gitがデフォルトでgit merge <thing>で行うこと

マージするには、いくつかのブランチ(これらの場合はA)をチェックアウトし、git mergeを実行して、少なくとも1つの引数(通常はBなどの別のブランチ名)を指定します。マージコマンドは、名前をコミットIDに変えることから始まります。ブランチ名は、ブランチで最も先端のコミットのIDに変わります。

次に、git mergemerge baseを見つけます。これらは、*でずっとマークしているコミットです。マージベースの技術的な定義は、コミットグラフの最下位共通祖先です(場合によっては複数存在することもあります)が、ここでは簡単にするために「*とマークされたコミット」を使用します。

最後に、通常のマージでは、gitは2つのgit diffコマンドを実行します。3 最初のgit diffは、コミット*HEADコミット、つまり現在のブランチの先端と比較します。 2番目のdiffは、commit *を引数commit(他のブランチの先端)と比較​​します(特定のコミットに名前を付けることができ、ブランチの先端である必要はありませんが、ここではBをマージしています) Aに追加して、2つのブランチチップコミットを取得します)。

bothブランチで、gitがマージベースバージョンと比較して変更されたファイルを見つけると、gitはそれらの変更をセミスマート(しかし、あまり賢くない)方法:両方の変更が同じ領域に同じテキストを追加する場合、gitは追加のコピーを1つ保持します。両方の変更が同じ領域の同じテキストを削除する場合、gitはそのテキストを一度だけ削除します。両方の変更が同じ領域のテキストを変更する場合、変更が完全に一致しない限り、競合が発生します(変更のコピーが1つ取得されます)。一方が一方の変更を行い、他方が別の変更を行いますが、変更が重複していないようであれば、gitは両方の変更を取得します。これは、3方向マージの本質です。

最後に、すべてがうまくいくと仮定すると、gitは2つの(または脚注2で既に述べたように)親を持つ新しいコミットを作成します。この新しいコミットに関連付けられたwork-treeは、gitが3者間マージを行ったときに思いついたものです。

代替マージ戦略

git mergeのデフォルトのrecursiveストラテジーには-Xオプションoursおよびtheirsがありますが、ここで必要なことは行いません。これらは単にconflictの場合、gitは "our change"(-X ours)または "それらの変更」(-X theirs)。

mergeコマンドには、完全に別の戦略-s oursがあります。これは、2つのコミットに対してマージベースを比較する代わりに、ソースツリーを使用するだけです。言い換えると、ブランチAにいるときにgit merge -s ours Bを実行すると、gitは2番目の親をブランチBの先端として新しいマージコミットを行いますが、ソースツリーは前のチップのバージョンに一致しますブランチA。つまり、新しいコミットのコードは、その親のコードと正確に一致します。

この他の回答 で概説されているように、gitに-s theirsを効果的に実装させるいくつかの方法があります。 explainの最も簡単なものは次のシーケンスだと思います:

git checkout A
git merge --no-commit -s ours B
git rm -rf .         # make sure you are at the top level!
git checkout B -- .
git commit

最初のステップは、いつものようにAブランチにいることを確認することです。 2番目は、マージを起動することですが、結果をまだコミットしないようにします(--no-commit)。 gitのマージを簡単にするために-これは必要ではなく、単に高速で静かになります--s oursを使用して、gitがdiffステップを完全にスキップし、マージの競合に関する苦情をすべて回避します。

この時点で、トリックの要点に到達します。最初にマージ結果全体を削除します。実際には価値がないためです。Aのtipコミットからのワークツリーではなく、Bのtipからのワークツリーが必要です。次に、Bの先端からすべてのファイルをチェックアウトし、コミットの準備をします。

最後に、最初の親としてAの古いヒントを持ち、2番目の親としてBのヒントを持つ新しいマージをコミットしますが、treeコミットBから。

コミット直前のグラフが次の場合:

...---o--...--o--o    <-- A
     /       /
...-o--...--*--o--o   <-- B

新しいグラフは次のとおりです。

...---o--...--o--o--o   <-- A
     /       /     /
...-o--...--o--o--*     <-- B

新しいmerge-baseは通常どおりBのヒントであり、コミットgraphの観点から見ると、このマージは他のマージとまったく同じように見えます。珍しいのは、Aの先端の新しいマージのソースツリーBの先端のソースツリーと正確に一致することです。


1この特定のケースでは、2つのブランチは独立したままであるため(マージされることはありません)、おそらくおそらく2つのブランチの1つが作成されたポイント(または、両方が作成された場合でも)、この時点では証明できませんが(過去にブランチラベルを移動するために誰かがgit resetまたは他のさまざまなトリックを使用した可能性があるため)。ただし、マージを開始するとすぐに、マージベースは明らかに開始点ではなくなり、適切な開始点を見つけるのが難しくなります。

2技術的には、マージコミットは、2つのまたはそれ以上の親を持つコミットです。 Gitは、3つ以上の親とのマージを「タコマージ」と呼びます。私の経験では、これらはgit自体のgitリポジトリを除いて一般的ではなく、最終的には、複数の通常の2つの親のマージと同じことを実現します。

3差分は通常、実際のコマンドを実行するのではなく、ある程度内部的に行われます。これにより、多くのショートカットの最適化が可能になります。また、カスタムマージドライバーを記述する場合、両方のdiffでファイルが変更されていることがgitで検出されない限り、そのカスタムマージドライバーは実行されません。 2つのdiffのうちの1つでのみ変更された場合、デフォルトのマージは変更されたものを使用します。

41
torek

ターゲットブランチをチェックアウトします。

git checkout A;

ブランチAのすべてを削除し、Bと同じにするには:

git reset B --hard;

ローカルの変更をすべて破棄する必要がある場合(元に戻すようなものですが、git revert):

git reset HEAD --hard;

完了したら、リモートブランチを更新することを忘れないでください(--forceまたは-fフラグを使用して、履歴を上書きする必要があります)。

git Push Origin A -f;
9
Ievgen

ここに最後の手段のアイデアがあります(それは汚れていて、物事のgit方法ではありません) ...

  1. ブランチBに移動します(git checkout B)。
  2. そのブランチのすべてのコンテンツをコピーします(ctrl + c)
  3. ブランチAに移動します(git checkout A)
  4. ブランチAからすべてを削除(マウスですべて選択して削除)
  5. すべてのブランチAのものがあったフォルダー内のブランチBからすべてのコンテンツをコピーします。 (ctrl + v)
  6. すべての新しい変更をステージングします(git add)。
  7. 段階的な変更をコミットします(git commit -m "ブランチAはBと同じになりました")
2
Pero122

必要がある git resetブランチAからブランチB。 docs を参照してください。

0
AlexZam

これがTortoiseGitで私にとってうまくいったことです

手順:

  1. [〜#〜] export [〜#〜]branchをZipとして、希望する場所と同一にしたい: enter image description here

  2. [〜#〜] unzip [〜#〜]アーカイブ/ Zipファイル。

  3. .gitフォルダを元のコードベースからこのunzipped-folder[〜#〜] ensure [〜#〜]target branchは、元のコードベースで選択されています)

  4. git add .

  5. git commit -m "Achieving identicality with abc-branch"

  6. git Pushで変更をリポジトリにプッシュします。 (--up-stream必要な場合)

TortoiseGit: https://tortoisegit.org

幸運を...

0
Akash

これは私がこれを行うために使用している素敵な方法です。アイデアは、AとBの差分を取得し、その差分をコミットし、それを元に戻し、反対のコミットを作成し、最後にコミットを元に戻すチェリーピックよりも

ここにコマンドがあります

git checkout B
git checkout -b B_tmp 
git merge --squash A
git commit -a -m 'diff'
git revert ⬆_commit_sha(paste here 'diff' commit sha, get from 'git log -1')
git checkout A                    
git cherry-pick revert_commit_sha (B_tmp branch last commit)
git branch -d B_tmp

そして、あなたは、AはBと同じであり、Bブランチと同じになるようにリセットされるA履歴には1つのコミットしかありません。

0
Sergey Sahakyan