AとBの2つのブランチがあります。BはAから作成されました。
両方のブランチでの作業は並行して続けられました。ブランチBでの作業は良好でしたが、ブランチAでの作業は不良でした(結果として機能しないバージョンになりました)。この間、ブランチBがブランチAにマージされることがありました(ただし、その逆ではありません)。
ブランチAをブランチBと同一にしたいと思います。あまりにも多くのコミットを元に戻す必要があるため、git revertは使用できません-ブランチBのマージの結果としてではなく、ブランチAで行われたコミットのみを元に戻したい。
私が見つけた解決策は、ブランチBを別のフォルダーにクローンし、ブランチAの作業フォルダーからすべてのファイルを削除し、一時ブランチBフォルダーからファイルをコピーし、追跡されていないファイルをすべて追加することでした。
同じことを行うgitコマンドはありますか?私が逃したいくつかのgit revertスイッチ?
これを行うには多くの方法があり、どの方法を使用するかは、どのような結果が必要か、特にあなたとあなたと協力している人(これが共有リポジトリの場合)が将来何を期待するかによって異なります。
これを行う主な3つの方法:
A
を放棄し、新しいA2
を作成し、それを使用します。git reset
または同等のものを使用して、A
を他の場所に再ポイントします。
方法1と2は、長期的には事実上同じです。たとえば、A
を放棄することから始めたとします。しばらくの間、誰もがA2
で開発します。次に、誰もがA2
を使用したら、名前A
を完全に削除します。 A
がなくなったので、A2
の名前をA
に変更することもできます(もちろん、それを使用する人は誰でも同じ名前変更を行う必要があります)。この時点で、方法1を使用した場合と方法2を使用した場合の違いは、あるとすれば何ですか? (1つありますまだ違いを見ることができる場合がありますが、「長期」の期間と古いreflogの有効期限によって異なります。 )
-s theirs
ですが、それは 存在しない であり、あなたはそれを偽造しなければなりません。サイドノート:ブランチの「作成元」の場所は基本的に無関係です。gitはそのようなことを追跡しないため、一般に後で発見することはできないため、おそらくあなたにとっても問題ではありません。 (本当に重要な場合は、ブランチを操作する方法にいくつかの制限を課すか、タグで「開始点」をマークして、後でこの情報を機械的に回復できるようにすることができます。あなたはそもそも何か間違ったことをしている-少なくとも、gitが提供しないgitの何かを期待している。以下の脚注1も参照。)
Gitのブランチ(より正確にはブランチname)は、コミットグラフ内の特定のコミットへのポインタです。これは、タグ名などの他の参照にも当てはまります。たとえば、タグと比較してブランチを特別なものにしているのは、onブランチにいてnewcommit、gitはブランチ名を自動的に更新し、新しいコミットを指すようにします。
たとえば、o
ノード(および*
とマークされたノード)もコミットを表し、A
とB
がブランチ名である、次のようなコミットグラフがあるとします。
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
の先端コミット。上記のコードをもう少しコンパクトに再描画します(ただし、o
sのラインアップを改善するために-
sをさらに追加します)。
o--*--o--o--o--o <-- A
\
o---o--o---o <-- B
最終的には:
o--*--o--o--o--o--o <-- A
\ /
o---o--o---* <-- B
*
をA
とB
の新しいマージベース(実際はB
の先端)に移動します。おそらく、B
にさらにコミットを追加し、さらに数回マージする可能性があります。
...---o--...--o--o <-- A
/ /
...-o--...--*--o--o <-- B
git merge <thing>
で行うことマージするには、いくつかのブランチ(これらの場合はA
)をチェックアウトし、git merge
を実行して、少なくとも1つの引数(通常はB
などの別のブランチ名)を指定します。マージコマンドは、名前をコミットIDに変えることから始まります。ブランチ名は、ブランチで最も先端のコミットのIDに変わります。
次に、git merge
はmerge 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つでのみ変更された場合、デフォルトのマージは変更されたものを使用します。
ターゲットブランチをチェックアウトします。
git checkout A;
ブランチAのすべてを削除し、Bと同じにするには:
git reset B --hard;
ローカルの変更をすべて破棄する必要がある場合(元に戻すようなものですが、git revert
):
git reset HEAD --hard;
完了したら、リモートブランチを更新することを忘れないでください(--force
または-f
フラグを使用して、履歴を上書きする必要があります)。
git Push Origin A -f;
ここに最後の手段のアイデアがあります(それは汚れていて、物事のgit方法ではありません) ...
必要がある git reset
ブランチAからブランチB。 docs を参照してください。
これがTortoiseGit
で私にとってうまくいったことです
branch
をZipとして、希望する場所と同一にしたい: .git
フォルダを元のコードベースからこのunzipped-folder
([〜#〜] ensure [〜#〜]target branch
は、元のコードベースで選択されています)git add .
git commit -m "Achieving identicality with abc-branch"
git Push
で変更をリポジトリにプッシュします。 (--up-stream
必要な場合)幸運を...
これは私がこれを行うために使用している素敵な方法です。アイデアは、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つのコミットしかありません。