Gitで分岐点を見つけますか?


454

ブランチマスターとAを含むリポジトリと、2つの間のたくさんのマージアクティビティがあります。ブランチAがマスターに基づいて作成されたときに、リポジトリでコミットを見つけるにはどうすればよいですか?

私のリポジトリは基本的に次のようになります:

-- X -- A -- B -- C -- D -- F  (master) 
          \     /   \     /
           \   /     \   /
             G -- H -- I -- J  (branch A)

リビジョンAを探していますが、これはgit merge-base (--all)見つかりません。


回答:


498

同じことを探していて、この質問を見つけました。聞いてくれてありがとう!

しかし、ここで見た答えは、あなたが求めた答え(または私が探していた答え)を完全には提供していないようです- Gコミットではなくコミットを提供しているようですA

それで、次のツリー(時系列で割り当てられた文字)を作成しました。

A - B - D - F - G   <- "master" branch (at G)
     \   \     /
      C - E --'     <- "topic" branch (still at E)

これはあなたのものとは少し異なって見えます。なぜなら、私は(このグラフを参照して、あなたのものではなく)Bを得たことを確認したかったからです。SHAプレフィックスとコミットメッセージに添付されている文字は次のとおりです(誰かが興味を持っている場合は、ここから私のリポジトリを複製できます)。

G: a9546a2 merge from topic back to master
F: e7c863d commit on master after master was merged to topic
E: 648ca35 merging master onto topic
D: 37ad159 post-branch commit on master
C: 132ee2a first commit on topic branch
B: 6aafd7f second commit on master before branching
A: 4112403 initial commit on master

したがって、目標はBを見つけることです。少しいじった後、私が見つけた3つの方法を次に示します。


1.視覚的に、gitkで:

次のようなツリーが視覚的に表示されるはずです(マスターから表示)。

マスターからのgitk画面キャプチャ

またはここ(トピックから見た場合):

トピックからのgitk画面キャプチャ

どちらの場合も、Bグラフにあるコミットを選択しました。クリックすると、グラフのすぐ下のテキスト入力フィールドに完全なSHAが表示されます。


2.視覚的に、ただしターミナルから:

git log --graph --oneline --all

(編集/サイドノート:追加--decorateも興味深いかもしれません。ブランチ名、タグなどの表示が追加されます。以下の出力はその使用を反映していないため、これを上のコマンドラインに追加しません。)

これは(と仮定してgit config --global color.ui auto):

git logの出力--graph --oneline --all

または、ストレートテキストで:

*トピックからマスターにa9546a2マージ
| \  
| * 648ca35マスターをトピックにマージ
| | \  
| * | 132ee2aがトピックブランチで最初にコミット
* | | マスターがトピックにマージされた後、マスターでe7c863dコミット
| | /  
| / |   
* | 37ad159マスターでブランチ後コミット
| /  
* 6aafd7fブランチ前のマスターでの2番目のコミット
*マスターでの4112403初期コミット

どちらの場合でも、6aafd7fコミットが最も低い共通点として、つまりB私のグラフまたはAあなたのグラフで見られます。


3.シェルマジック:

上記のようなもの、または1つのリビジョンだけを取得する単一のコマンドが必要かどうかを質問で指定することはありません。さて、これが後者です:

diff -u <(git rev-list --first-parent topic) \
             <(git rev-list --first-parent master) | \
     sed -ne 's/^ //p' | head -1
6aafd7ff98017c816033df18395c5c1e7829960d

〜/ .gitconfigに次のように入力することもできます(注:末尾のダッシュが重要です。それに注目してくれたBrianに感謝します)

[alias]
    oldest-ancestor = !zsh -c 'diff -u <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | sed -ne \"s/^ //p\" | head -1' -

これは、次の(引用符で囲んだ)コマンドラインで実行できます。

git config --global alias.oldest-ancestor '!zsh -c '\''diff -u <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | sed -ne "s/^ //p" | head -1'\'' -'

注:zsh同じように簡単だったかもしれないbash、しかしshますない作品- <()構文はバニラに存在しませんsh。(@conny、このページの別の回答のコメントで教えてくれてありがとう!)

注:上記の代替バージョン:

lioriのおかげで、同一のブランチを比較するときに上記が低下する可能性があることを指摘し、ミックスからsedフォームを削除して、これを「より安全」にする(つまり、結果を返す)(つまり、最新のコミット)マスターをマスターと比較した場合でも):

.git-config行として:

[alias]
    oldest-ancestor = !zsh -c 'diff --old-line-format='' --new-line-format='' <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | head -1' -

シェルから:

git config --global alias.oldest-ancestor '!zsh -c '\''diff --old-line-format='' --new-line-format='' <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | head -1'\'' -'

そのため、私のテストツリー(しばらく使用できませんでしたが、戻ってきました)では、マスターとトピックの両方で機能します(それぞれコミットGとBを与える)。lioriさん、別のフォームをありがとうございます。


だから、それが私(およびリオリ)が思いついたものです。それは私のために働くようです。また、便利なエイリアスがいくつか追加されています。

git config --global alias.branchdiff '!sh -c "git diff `git oldest-ancestor`.."'
git config --global alias.branchlog '!sh -c "git log `git oldest-ancestor`.."'

幸せなgit-ing!


5
lindesに感謝します。シェルオプションは、長時間実行されるメンテナンスブランチのブランチポイントを見つけたい場合に最適です。過去に1000回のコミットがあったかもしれないリビジョンを探しているとき、視覚的なオプションは本当にそれをカットするつもりはありません。* 8 ')
マークブース

4
3番目の方法では、コンテキストが最初の変更されていない行を表示するかどうかに依存しています。これは、特定のエッジケースや、わずかに異なる要件がある場合には発生しません(たとえば、履歴の1つだけが--first-parentである必要がありますが、同じメソッドを使用する可能性があるスクリプトでこのメソッドを使用しています両側の枝)。diffのif-then-elseモードを使用して、変更/削除された行を出力から削除する方が、十分なコンテキストを持つことに頼るのではなく、より安全であることがわかりましたdiff --old-line-format='' --new-line-format='' <(git rev-list …) <(git rev-list …)|head -1
liori

3
git log -n1 --format=format:%H $(git log --reverse --format=format:%H master..topic | head -1)~同様に動作しますが、私は思う
トビアス・シュルテ

4
@JakubNarębski:このツリーにgit merge-base --fork-point ...コミットB(コミット6aafd7f)を与える方法はありますか?あなたがそれを投稿したとき、私は他のもので忙しかったし、それは良さそうに聞こえたが、私はようやくそれを試してみたが、動作しない...私はより最近の何かを取得するか、単にサイレントエラー(エラーなし)を取得する。メッセージが、終了ステータス1)、のような議論をしようとgit co master; git merge-base --fork-point topicgit co topic; git merge-base --fork-point mastergit merge-base --fork-point topic master)のいずれかのチェックアウトのために(、など私が間違っているのか、欠けているものはありますか?
lindes 14年

25
@JakubNarębski@lindesはreflog --fork-pointに基づいているため、ローカルで変更を加えた場合にのみ機能します。それでも、reflogエントリが期限切れになった可能性があります。便利ですが、まったく信頼できません。
Daniel Lubarov、2015年

131

あなたが探している可能性がありますgit merge-base

git merge-baseは、3者間マージで使用する2つのコミット間の最も一般的な祖先を検索します。後者が前者の祖先である場合、1つの共通の祖先は別の共通の祖先より優れています。より優れた共通の祖先を持たない共通の祖先は、最適な共通の祖先、つまりマージベースです。コミットのペアに対して複数のマージベースが存在する可能性があることに注意してください。


10
--allgit merge-base」のオプションにも注意してください
JakubNarębski09年

10
これは元の質問には答えませんが、ほとんどの人がこれが答えであるはるかに単純な質問をします:)
FelipeC

6
彼はgit merge-baseの結果は望ましくないと言った
Tom Tanner

9
@TomTanner:質問の履歴を確認したところ、元の質問が編集され、git merge-base私の回答が投稿されてから約5時間後に(おそらく私の回答に応じて)そのメモが含まれるようになりました。それでも、検索でこの質問を見つけた他の人にとっては役立つ場合があるため、この回答はそのままにしておきます。
グレッグ・ヒューギル

merge-base --allの結果を使用してから、merge-base --is-ancestorを使用してリターンコミットのリストをふるいにかけて、リストから最も古い共通の祖先を見つけることができると思いますが、これは最も簡単な方法ではありません。
Matt_G 2018

42

私はgit rev-listこの種のものに使用しました。例(3つのドットに注意)

$ git rev-list --boundary branch-a...master | grep "^-" | cut -c2-

分岐点を吐き出します。今、それは完璧ではありません。マスターをブランチAに数回マージしたので、考えられるいくつかのブランチポイント(基本的に、元のブランチポイントと、マスターをブランチAにマージした各ポイント)が分割されます。ただし、少なくとも可能性を絞り込む必要があります。

そのコマンドをエイリアスに追加しました~/.gitconfig

[alias]
    diverges = !sh -c 'git rev-list --boundary $1...$2 | grep "^-" | cut -c2-'

だから私はそれを次のように呼ぶことができます:

$ git diverges branch-a master

注:これは、共通の祖先ではなく、ブランチの最初のコミットを与えるようです。(つまり、元の質問のグラフごとにのG代わりにを与えAます。)私は、A現在投稿する予定である、という答えを持っています。
lindes

@lindes:私が試したすべてのケースで共通の祖先になります。ない例はありますか?
mipadi

はい。で、私の答えは、(あなたが複製することができレポへのリンクを持っているgit checkout topicし、その後でこれを実行topicする代わりにbranch-a、そのリスト)648ca357b946939654da12aaf2dc072763f3caee37ad15952db5c065679d7fc31838369712f0b338の両方- 37ad159648ca35、現在の枝(後者は現在のHEADであることのancestoryでありtopic、)しかし、どちらも分岐が発生する前のポイントではありません。何か違うものはありますか?
lindes

@lindes:リポジトリのクローンを作成できませんでした(おそらく権限の問題ですか?)。
mipadi

おっと、ごめんなさい!お知らせいただきありがとうございます。git update-server-infoを実行するのを忘れました。今行くのは良いことです。:)
lindes

31

簡潔なコマンドが好きなら、

git rev-list $(git rev-list --first-parent ^ branch_name master | tail -n1)^^! 

ここに説明があります。

次のコマンドは、branch_nameの作成後に発生したマスター内のすべてのコミットのリストを提供します

git rev-list --first-parent ^ branch_name master 

あなたはそれらのコミットの最も早いものだけを気にするので、出力の最後の行が欲しいです:

git rev-list ^ branch_name --first-parent master | 尾-n1

最も初期の親は、それが「BRANCH_NAME」の祖先ではありません、定義によって、あるコミット、それはで何かの祖先だから「BRANCH_NAME、」および「マスター」にある「マスターは。」したがって、両方のブランチにある最も早いコミットが得られます。

コマンド

git rev-list commit ^^!

親のコミット参照を表示する方法にすぎません。あなたは使うことができます

git log -1コミット^

または何でも。

PS:私は祖先の順序は無関係であるという主張に同意しません。それはあなたが望むものに依存します。たとえば、この場合

_C1___C2_______マスター
  \ \ _XXXXX_ブランチA(XはマスターとAの間の任意のクロスオーバーを示す)
   \ _____ /ブランチB

C2を「分岐」コミットとして出力することは完全に理にかなっています。これは、開発者が「マスター」から分岐したときです。彼がブランチしたとき、ブランチ「B」は彼のブランチにマージされていませんでした!これは、この投稿のソリューションが提供するものです。

ブランチAでの起点から最後のコミットまでのすべてのパスがCを通過するような最後のコミットCが必要な場合は、祖先の順序を無視します。これは純粋にトポロジー的なものであり、2つのバージョンのコードが同時に実行されるようになったことを示しています。それはあなたがマージベースベースのアプローチで行くときであり、それは私の例ではC1を返します。


3
これは明らかに最も明確な答えです。これをトップに投票しましょう。推奨される編集:次のgit rev-list commit^^!ように簡略化できますgit rev-parse commit^
Russell Davis

これが答えになるはずです!
14

2
この答えはいいです。私が置き換えたのgit rev-list --first-parent ^branch_name mastergit rev-list --first-parent branch_name ^master、マスターブランチが0の場合、他のブランチよりも先にコミットされている(高速で転送可能)と、出力が作成されないためです。私の解決策では、マスターが完全に進んでいる(つまり、ブランチが完全にマージされている)場合、出力は作成されません。
MichaelSchmeißer12年

3
これは私が何かを完全に見逃していない限り機能しません。例のブランチには両方向のマージがあります。あなたはそれを考慮に入れようとしたようですが、これはあなたの答えが失敗する原因になると思います。git rev-list --first-parent ^topic masterあなただけは最初から最後のマージ後にコミットすることを取り戻すだろうmastertopic(つまりでも存在する場合)。
ジェリー

1
@jerry正解です。この答えはごみです。たとえば、バックマージが行われたばかり(マスターがトピックにマージされた)で、その後マスターが新しいコミットを持たない場合、最初のgit rev-list --first-parentコマンドは何も出力しません。
TamaMcGlinn

19

このスレッドの回答の多くが質問が求めていた回答を提供していないことを考慮して、ここで各ソリューションの結果の要約と、質問で指定されたリポジトリを複製するために使用したスクリプトを示します。

ログ

指定された構造でリポジトリを作成すると、次のgitログが取得されます。

$ git --no-pager log --graph --oneline --all --decorate
* b80b645 (HEAD, branch_A) J - Work in branch_A branch
| *   3bd4054 (master) F - Merge branch_A into branch master
| |\  
| |/  
|/|   
* |   a06711b I - Merge master into branch_A
|\ \  
* | | bcad6a3 H - Work in branch_A
| | * b46632a D - Work in branch master
| |/  
| *   413851d C - Merge branch_A into branch master
| |\  
| |/  
|/|   
* | 6e343aa G - Work in branch_A
| * 89655bb B - Work in branch master
|/  
* 74c6405 (tag: branch_A_tag) A - Work in branch master
* 7a1c939 X - Work in branch master

私の唯一の追加は、ブランチを作成したポイント、したがって見つけたいコミットを明示的にするタグです。

機能するソリューション

機能する唯一のソリューションは、lindesが正しく返すものAです。

$ diff -u <(git rev-list --first-parent branch_A) \
          <(git rev-list --first-parent master) | \
      sed -ne 's/^ //p' | head -1
74c6405d17e319bd0c07c690ed876d65d89618d5

チャールズ・ベイリーがしかし指摘し、このソリューションは非常に脆いです。

あなたの場合branch_Amaster、その後は、合併masterbranch_Aコミットを介在せずに、その後lindes'ソリューションは、あなたに与え、最新の最初diverganceを

つまり、私のワークフローでは、後で確実に見つけられることを保証できないため、長時間実行されているブランチのブランチポイントにタグを付ける必要があると思います。

これは、結局、名前付きブランチ呼ばれるgitものhgが不足していることを意味します。ブロガーjhw呼び出しこれらの系統家族彼の記事でより多くのGitリポジトリよりなぜ私のようにMercurialのと彼のフォローアップ記事(グラフ付き!)Gitの対Mercurialの詳細。一部の水銀性改宗者がで名前付きブランチを持たない理由を理解するために、それらを読むことをお勧めしgitます。

機能しないソリューション

が提供するソリューションmipadiは、 2つの答えを返し、IそしてC

$ git rev-list --boundary branch_A...master | grep ^- | cut -c2-
a06711b55cf7275e8c3c843748daaa0aa75aef54
413851dfecab2718a3692a4bba13b50b81e36afc

グレッグ・ヒューギルが返すソリューションI

$ git merge-base master branch_A
a06711b55cf7275e8c3c843748daaa0aa75aef54
$ git merge-base --all master branch_A
a06711b55cf7275e8c3c843748daaa0aa75aef54

Karlが提供するソリューションは次を返しますX

$ diff -u <(git log --pretty=oneline branch_A) \
          <(git log --pretty=oneline master) | \
       tail -1 | cut -c 2-42
7a1c939ec325515acfccb79040b2e4e1c3e7bbe5

スクリプト

mkdir $1
cd $1
git init
git commit --allow-empty -m "X - Work in branch master"
git commit --allow-empty -m "A - Work in branch master"
git branch branch_A
git tag branch_A_tag     -m "Tag branch point of branch_A"
git commit --allow-empty -m "B - Work in branch master"
git checkout branch_A
git commit --allow-empty -m "G - Work in branch_A"
git checkout master
git merge branch_A       -m "C - Merge branch_A into branch master"
git checkout branch_A
git commit --allow-empty -m "H - Work in branch_A"
git merge master         -m "I - Merge master into branch_A"
git checkout master
git commit --allow-empty -m "D - Work in branch master"
git merge branch_A       -m "F - Merge branch_A into branch master"
git checkout branch_A
git commit --allow-empty -m "J - Work in branch_A branch"

gitバージョンがこれに大きな違いをもたらすとは思えませんが、

$ git --version
git version 1.7.1

サンプルリポジトリのスクリプトを作成するよりコンパクトな方法を教えてくれたCharles Baileyに感謝します。


2
Karlによる解決策は簡単に修正できますdiff -u <(git rev-list branch_A) <(git rev-list master) | tail -2 | head -1。レポを作成するための指示を提供してくれてありがとう:)
FelipeC

「カールが提供するソリューションのクリーンアップされたバリエーションはXを返す」という意味だと思います。オリジナルはうまくいきませんでした:-)
Karl

いいえ、元の画像は正常に機能しません。確かに、バリエーションは最悪でも機能します。しかし、オプション--topo-orderを追加すると、バージョンが機能します:)
FelipeC '27

@felipec- Charles Baileyの回答に関する私の最後のコメントを参照してください。残念ながら、私たちのチャット(つまり、古いコメントはすべて)が削除されました。時間になったら回答を更新していきます。
マークブース

面白い。私は、トポロジーがデフォルトであると想定していました。愚かな私:-)
Karl

10

通常、これは不可能です。ブランチ履歴では、名前付きブランチが分岐する前のブランチとマージ、および2つの名前付きブランチの中間ブランチは同じように見えます。

gitでは、ブランチは履歴セクションのヒントの現在の名前にすぎません。彼らは本当に強いアイデンティティを持っていません。

2つのコミットのマージベース(Greg Hewgillの回答を参照)は通常、2つのブランチが共有した最新のコミットを提供するので、これは通常より大きな問題ではありません。

コミットの親の順序に依存するソリューションは、ブランチの履歴のある時点でブランチが完全に統合されている状況では明らかに機能しません。

git commit --allow-empty -m root # actual branch commit
git checkout -b branch_A
git commit --allow-empty -m  "branch_A commit"
git checkout master
git commit --allow-empty -m "More work on master"
git merge -m "Merge branch_A into master" branch_A # identified as branch point
git checkout branch_A
git merge --ff-only master
git commit --allow-empty -m "More work on branch_A"
git checkout master
git commit --allow-empty -m "More work on master"

この手法は、親を逆にして統合マージを行った場合にも失敗します(たとえば、一時的なブランチを使用してマスターへのテストマージを実行し、機能ブランチに早送りしてさらにビルドします)。

git commit --allow-empty -m root # actual branch point
git checkout -b branch_A
git commit --allow-empty -m  "branch_A commit"
git checkout master
git commit --allow-empty -m "More work on master"
git merge -m "Merge branch_A into master" branch_A # identified as branch point
git checkout branch_A
git commit --allow-empty -m "More work on branch_A"

git checkout -b tmp-branch master
git merge -m "Merge branch_A into tmp-branch (master copy)" branch_A
git checkout branch_A
git merge --ff-only tmp-branch
git branch -d tmp-branch

git checkout master
git commit --allow-empty -m "More work on master"


3
チャールズさん、ありがとうございます。ブランチが最初に分岐したポイントを知りたい場合は、タグを付ける必要があります。私は本当にの名前付きブランチgitと同等のものがあればいいのにhg、それが長命の保守ブランチを管理するようになります非常に簡単になります。
マークブース

1
「gitでは、ブランチは歴史のセクションのヒントの現在の名前にすぎません。それらは実際には強いアイデンティティを持っていません」これは言うのが恐ろしいことであり、Gitブランチをより深く理解する必要があると私は確信しました-ありがとう(+ 1)
dumbledad 2015年

ブランチ履歴では、名前付きブランチが分岐する前のブランチとマージ、および2つの名前付きブランチの中間ブランチは同じように見えます。うん。+1。
user1071847

5

のようなものはどうですか

git log --pretty=oneline master > 1
git log --pretty=oneline branch_A > 2

git rev-parse `diff 1 2 | tail -1 | cut -c 3-42`^

これは機能します。それは本当に厄介ですが、実際に仕事をしているように見えるのは私が見つけた唯一のものです。
GaryO

1
同等のGitエイリアス:(diverges = !bash -c 'git rev-parse $(diff <(git log --pretty=oneline ${1}) <(git log --pretty=oneline ${2}) | tail -1 | cut -c 3-42)^'一時ファイルなし)
conny

@conny:ああ、すごい-<(foo)構文を見たことがない...信じられないほど便利だ、ありがとう!(FYIでもzshで動作します。)
lindes

これは、共通の祖先ではなく、ブランチでの最初のコミットを与えるようです。(つまり、元の質問のグラフでGA、の代わりにが与えられます。)しかし、私は答えを見つけたと思います。
lindes

'git log --pretty = oneline'の代わりに 'git rev-list'を実行するだけで、カットをスキップすることもできます。さらに、これは親ポイントに分岐点をコミットするため、末尾に-2 | ヘッド1. So:diff -u <(git rev-list branch_A) <(git rev-list master) | tail -2 | head -1
FelipeC、2012

5

確かに何かが足りないのですが、IMO、上記のすべての問題は、常に履歴からさかのぼって分岐点を見つけようとしているために発生します。これにより、使用可能なマージの組み合わせにより、あらゆる種類の問題が発生します。

代わりに、両方のブランチが多くの履歴を共有するという事実に基づいて、私は別のアプローチに従いました。ブランチする前のすべての履歴は100%同じなので、戻るのではなく、前進することです(最初からcommit)、両方のブランチの最初の違いを探します。分岐点は、単純に、最初に見つかった違いの親になります。

実際には:

#!/bin/bash
diff <( git rev-list "${1:-master}" --reverse --topo-order ) \
     <( git rev-list "${2:-HEAD}" --reverse --topo-order) \
--unified=1 | sed -ne 's/^ //p' | head -1

そして、それは私のすべての通常のケースを解決しています。確かにカバーされていない境界線があります... ciao :-)


diff <(git rev-list "$ {1:-master}" --first-parent)<(git rev-list "$ {2:-HEAD}" --first-parent)-U1 | 尾-1
アレクサンダーバード

私はこれがより速いことを発見しました(2-100x):comm --nocheck-order -1 -2 <(git rev-list --reverse --topo-order topic) <(git rev-list --reverse --topo-order master) | head -1
Andrei Neculau '


4

多くの研究と議論の後、少なくとも現在のバージョンのGitでは、すべての状況で機能する魔法の弾丸がないことは明らかです。

そのため、tailブランチの概念を追加するパッチをいくつか作成しました。ブランチが作成されるたびに、元のポイントへのポインターも作成され、tail。この参照は、ブランチがリベースされるたびに更新されます。

develブランチの分岐点を見つけるには、使用devel@{tail}するのはそれだけです。

https://github.com/felipec/git/commits/fc/tail


1
唯一の安定した解決策かもしれません。これがgitに入る可能性があるかどうか確認しましたか?プルリクエストはありませんでした。
Alexander Klimetschek

@AlexanderKlimetschekパッチを送信しなかったので、受け入れられないと思います。ただし、私は別の方法を試しました。非常によく似た「更新ブランチ」フックです。この方法では、デフォルトではGitは何もしませんが、フックを有効にしてテールブランチを更新できます。ただし、devel @ {tail}はありませんが、tails / develを使用するのはそれほど悪くありません。
FelipeC、2014年

3

これが私の以前の回答前回の回答の改良版です。マージからのコミットメッセージを利用して、ブランチが最初に作成された場所を見つけます。

ここで説明したすべてのリポジトリで動作し、メーリングリスト生成されたいくつかのトリッキーなリポジトリにも対処しました。私はこのためのテスト書きました。

find_merge ()
{
    local selection extra
    test "$2" && extra=" into $2"
    git rev-list --min-parents=2 --grep="Merge branch '$1'$extra" --topo-order ${3:---all} | tail -1
}

branch_point ()
{
    local first_merge second_merge merge
    first_merge=$(find_merge $1 "" "$1 $2")
    second_merge=$(find_merge $2 $1 $first_merge)
    merge=${second_merge:-$first_merge}

    if [ "$merge" ]; then
        git merge-base $merge^1 $merge^2
    else
        git merge-base $1 $2
    fi
}

3

次のコマンドは、コミットAのSHA1を明らかにします

git merge-base --fork-point A


これは、親ブランチと子ブランチの間に中間のマージが存在する場合には当てはまりません。
nawfal

2

私はいくつかの喜びを得ているようです

git rev-list branch...master

取得する最後の行はブランチの最初のコミットなので、その親を取得するだけです。そう

git rev-list -1 `git rev-list branch...master | tail -1`^

私にとってはうまくいくようで、diffなどは必要ありません(そのバージョンのdiffがないので便利です)

修正:これは、masterブランチにいる場合は機能しませんが、私はスクリプトでこれを行っているため、問題は少なくなります。


2

分岐点からコミットを見つけるには、これを使用できます。

git log --ancestry-path master..topicbranch

このコマンドは、指定された例では機能しません。コミット範囲のパラメーターとして何を提供しますか?
JesperRønn-Jensen2014

2

場合によっては、事実上不可能であり(追加のデータがあると幸運な場合を除いて)、ここでのソリューションは機能しません。

Gitは(ブランチを含む)参照履歴を保持しません。各ブランチ(ヘッド)の現在の位置のみを保存します。つまり、時間の経過とともにgitの一部のブランチ履歴が失われる可能性があります。たとえば、ブランチを作成すると、どのブランチが元のブランチであったかはすぐに失われます。ブランチが行うことはすべて次のとおりです。

git checkout branch1    # refs/branch1 -> commit1
git checkout -b branch2 # branch2 -> commit1

あなたは最初にコミットされたものがブランチであると仮定するかもしれません。これはそうなる傾向がありますが、常にそうとは限りません。上記の操作の後、最初にいずれかのブランチにコミットすることを妨げるものは何もありません。また、gitタイムスタンプの信頼性は保証されません。あなたが両方にコミットするまで、それらが本当に構造的に枝になることはありません。

ダイアグラムでは、概念的にコミットに番号を付ける傾向がありますが、gitには、コミットツリーが分岐するときのシーケンスの実際の安定した概念がありません。この場合、数値(表示順)はタイムスタンプによって決定されると想定できます(すべてのタイムスタンプを同じに設定した場合にgit UIがどのように処理するかを見るのは楽しいかもしれません)。

これは人間が概念的に期待することです:

After branch:
       C1 (B1)
      /
    -
      \
       C1 (B2)
After first commit:
       C1 (B1)
      /
    - 
      \
       C1 - C2 (B2)

これはあなたが実際に得るものです:

After branch:
    - C1 (B1) (B2)
After first commit (human):
    - C1 (B1)
        \
         C2 (B2)
After first commit (real):
    - C1 (B1) - C2 (B2)

B1は元のブランチであると想定しますが、実際には単にデッドブランチである可能性があります(誰かがcheckout -bを実行しましたが、コミットしていません)。両方にコミットするまでは、git内で正当なブランチ構造を取得できません。

Either:
      / - C2 (B1)
    -- C1
      \ - C3 (B2)
Or:
      / - C3 (B1)
    -- C1
      \ - C2 (B2)

C1がC2とC3の前にあることは常にわかりますが、C2がC3より前かC3がC2より前かを確実に知ることはできません(たとえば、ワークステーションの時刻を任意に設定できるため)。B1とB2はどちらのブランチが最初に来たのかわからないため、誤解を招く可能性もあります。多くの場合、非常に適切で通常は正確な推測を行うことができます。レース場に少し似ています。すべてのものが一般的に車と同じである場合、1周遅れの車が1周遅れを始めたと想定できます。また、非常に信頼性の高い規則もあります。たとえば、マスターはほとんどの場合、最も長く存続するブランチを表しますが、悲しいことに、これが当てはまらない場合もあります。

ここに示す例は、履歴を保存する例です。

Human:
    - X - A - B - C - D - F (B1)
           \     / \     /
            G - H ----- I - J (B2)
Real:
            B ----- C - D - F (B1)
           /       / \     /
    - X - A       /   \   /
           \     /     \ /
            G - H ----- I - J (B2)

私たち人間がそれを左から右に、根から葉に(ref)読んでいるので、ここの本当も誤解を招くものです。Gitはそれを行いません。頭の中で(A-> B)を実行する場所では、gitは(A <-BまたはB-> A)を実行します。refからrootにそれを読み込みます。参照はどこにあってもかまいませんが、少なくともアクティブなブランチでは、リーフになる傾向があります。refはコミットを指し、コミットには親への「いいね!」のみが含まれ、子は含まれません。コミットがマージコミットの場合、複数の親が存在します。最初の親は常に、マージされた元のコミットです。他の親は常に、元のコミットにマージされたコミットです。

Paths:
    F->(D->(C->(B->(A->X)),(H->(G->(A->X))))),(I->(H->(G->(A->X))),(C->(B->(A->X)),(H->(G->(A->X)))))
    J->(I->(H->(G->(A->X))),(C->(B->(A->X)),(H->(G->(A->X)))))

これは非常に効率的な表現ではなく、gitが各参照(B1およびB2)から取ることができるすべてのパスの表現です。

Gitの内部ストレージは次のようになります(親としてのAが2回表示されるわけではありません)。

    F->D,I | D->C | C->B,H | B->A | A->X | J->I | I->H,C | H->G | G->A

raw git commitをダンプすると、0個以上の親フィールドが表示されます。ゼロがある場合、それは親がないことを意味し、コミットはルートです(実際には複数のルートを持つことができます)。存在する場合は、マージがなかったことを意味し、ルートコミットではありません。複数ある場合は、コミットはマージの結果であり、最初の親はすべてマージコミットであることを意味します。

Paths simplified:
    F->(D->C),I | J->I | I->H,C | C->(B->A),H | H->(G->A) | A->X
Paths first parents only:
    F->(D->(C->(B->(A->X)))) | F->D->C->B->A->X
    J->(I->(H->(G->(A->X))) | J->I->H->G->A->X
Or:
    F->D->C | J->I | I->H | C->B->A | H->G->A | A->X
Paths first parents only simplified:
    F->D->C->B->A | J->I->->G->A | A->X
Topological:
    - X - A - B - C - D - F (B1)
           \
            G - H - I - J (B2)

両方がAを押すと、チェーンは同じになりますが、その前にチェーンはまったく異なります。他の2つのコミットに共通する最初のコミットは、共通の祖先であり、そこから分岐しました。ここで、commit、branch、refの用語の間には多少の混乱があるかもしれません。実際にコミットをマージすることができます。これは実際にマージが行うことです。refは単にコミットを指し、ブランチは.git / refs / headsフォルダー内のrefにすぎません。フォルダーの場所は、refがタグなどではなくブランチであることを決定するものです。

履歴を失うところは、マージは状況に応じて2つのことのいずれかを行うということです。

検討してください:

      / - B (B1)
    - A
      \ - C (B2)

この場合、いずれかの方向のマージにより、現在のチェックアウトされたブランチによって指し示されるコミットとして最初の親があり、現在のブランチにマージしたブランチの先端で2番目の親がコミットとして新しいコミットが作成されます。両方のブランチには共通の祖先を組み合わせる必要があるため、両方のブランチに変更があるため、新しいコミットを作成する必要があります。

      / - B - D (B1)
    - A      /
      \ --- C (B2)

この時点で、D(B1)には、両方のブランチ(それ自体とB2)からの両方の変更セットがあります。ただし、2番目のブランチにはB1からの変更はありません。B1からB2への変更をマージして同期化すると、次のような結果が期待できます(gitマージで--no-ffを使用してこのようにすることもできます)。

Expected:
      / - B - D (B1)
    - A      / \
      \ --- C - E (B2)
Reality:
      / - B - D (B1) (B2)
    - A      /
      \ --- C

B1に追加のコミットがある場合でも、それを取得します。B1にない変更がB2にない限り、2つのブランチはマージされます。リベースと同様に早送りを行います(ただし、リベースとは異なり、1つのブランチにのみ変更セットがあるため、1つのブランチの変更セットを別のブランチの変更セットの上に適用する必要はありません。

From:
      / - B - D - E (B1)
    - A      /
      \ --- C (B2)
To:
      / - B - D - E (B1) (B2)
    - A      /
      \ --- C

B1での作業をやめれば、長期的には履歴を保存するのにほとんど問題ありません。通常はB1(マスターである可能性があります)のみが進むため、B2の履歴におけるB2の位置は、B1にマージされたポイントを正常に表します。これはgitがあなたに期待していることで、AからBを分岐し、変更を蓄積するだけ好きなだけAをBにマージできます。 。ブランチを作業中のブランチに早送りでマージした後、ブランチの作業を続けると、毎回Bの以前の履歴が消去されます。ソースにコミットしてからブランチにコミットするたびに、実際に毎回新しいブランチを作成しています。

         0   1   2   3   4 (B1)
        /-\ /-\ /-\ /-\ /
    ----   -   -   -   -
        \-/ \-/ \-/ \-/ \
         5   6   7   8   9 (B2)

1から3と5から8は、4または9の履歴をたどると表示される構造ブランチです。gitには、名前のないブランチと参照のないブランチが、名前付きブランチと参照ブランチのどちらに属しているかを知る方法がありません。構造の終わり。この図から、0から4はB1に属し、4から9はB2に属していると想定するかもしれませんが、4と9は別として、どのブランチがどのブランチに属しているかを知ることができませんでした。その幻想。0はB2に属し、5はB1に属している可能性があります。この場合、16の異なる可能性があり、それぞれの構造分岐が属する名前付き分岐があります。

これを回避する多くのgit戦略があります。gitマージを強制的に早送りせず、常にマージブランチを作成することができます。ブランチの履歴を保存するための恐ろしい方法は、選択したいくつかの慣例に従って、タグやブランチ(タグは本当に推奨されます)を使用することです。マージするブランチでダミーの空のコミットを実行することはお勧めしません。非常に一般的な慣習は、ブランチを完全に閉じるまで統合ブランチにマージしないことです。これは、他の方法ではブランチがあるという点を回避しているのと同じように、人々が従おうとする習慣です。ただし、現実の世界では、理想は必ずしも実用的ではありません。つまり、正しいことを行うことがすべての状況で実行可能であるとは限りません。もしあなたが


「Gitはref履歴を保存しません」デフォルトではなく、長期間は保持されません。参照man git-reflogして日付に関する部分:「master@{one.week.ago}は、「マスターがこのローカルリポジトリで1週間前を指していた場所」を意味します。または上の議論<refname>@{<date>}の中でman gitrevisions。そしてcore.reflogExpireの中でman git-config
Patrick Mevzek

2

質問に対する完全な解決策ではありませんが、長命のブランチがある場合に使用するアプローチに注目する価値があると思いました。

私はブランチを作成すると同時に、私はまた、同じ名前ではなくて、タグを作成して-init、たとえば、接尾辞feature-branchfeature-branch-init

(これが答えるのがとても難しい質問であることは奇妙なことです!)


1
いつ、どこで作成されたのかを意識せずに、「ブランチ」のコンセプトを設計することのまったく気が遠くなるような愚かさを考えてみてください。加えて、他の提案されたソリューションの計り知れない複雑さ-この方法をスマートにしようとする人々による複雑さ事、私はあなたの解決策を好むと思います。ブランチを作成するたびにこれを行うことを覚えておく必要があるのは、gitユーザーが非常に頻繁に行っていることだけです。さらに、「タグ」には「重い」というペナルティがあることをどこかで読みました。それでも、それは私がやると思うことです。
Motti Shneor

1

分岐点を見やすくする簡単な方法git log --graphは、オプションを使用すること--first-parentです。

たとえば、受け入れられた回答からリポジトリを取得します

$ git log --all --oneline --decorate --graph

*   a9546a2 (HEAD -> master, origin/master, origin/HEAD) merge from topic back to master
|\  
| *   648ca35 (origin/topic) merging master onto topic
| |\  
| * | 132ee2a first commit on topic branch
* | | e7c863d commit on master after master was merged to topic
| |/  
|/|   
* | 37ad159 post-branch commit on master
|/  
* 6aafd7f second commit on master before branching
* 4112403 initial commit on master

追加し--first-parentます:

$ git log --all --oneline --decorate --graph --first-parent

* a9546a2 (HEAD -> master, origin/master, origin/HEAD) merge from topic back to master
| * 648ca35 (origin/topic) merging master onto topic
| * 132ee2a first commit on topic branch
* | e7c863d commit on master after master was merged to topic
* | 37ad159 post-branch commit on master
|/  
* 6aafd7f second commit on master before branching
* 4112403 initial commit on master

それは簡単になります!

リポジトリに多数のブランチがある場合は、次のコマンドを使用する代わりに、比較する2つのブランチを指定する必要があります--all

$ git log --decorate --oneline --graph --first-parent master origin/topic

0

問題は、一方の側の両方のブランチ間で最も最近の単一コミットのカットを見つけ、もう一方の最も古い共通の祖先(おそらくリポジトリの最初のコミット)を見つけることです。これは、「分岐点」が何であるかという私の直感に一致します。

これを念頭に置いて、これは通常のgit shellコマンドで計算するのが簡単ではありません。なぜなら、git rev-list私たちの最も強力なツールは、コミットに到達するパスを制限できないからです。git rev-list --boundary私たちが最も近いのはであり、これにより、「私たちの方法をブロックした」すべてのコミットのセットが得られます。(注:git rev-list --ancestry-path興味深いですが、ここでそれを有用にする方法はありません。)

スクリプトは次のとおりです:https : //gist.github.com/abortz/d464c88923c520b79e3d。それは比較的単純ですが、ループのために要旨を保証するのに十分複雑です。

ここで提案されている他のほとんどのソリューションは、単純な理由ですべての状況で機能する可能性がないことに注意してください。git rev-list --first-parent履歴の線形化では、どちらの順序でもマージできるため、信頼できません。

git rev-list --topo-order一方、は、トポロジの順序でコミットをウォークする場合に非常に便利ですが、差分の作成は不安定です。特定のグラフには、可能なトポロジの順序が複数あるため、順序の特定の安定性に依存しています。そうは言っても、strongk7のソリューションはおそらくほとんどの場合上手く機能します。ただし、リポジトリの履歴全体を2回歩く必要があるため、鉱山の速度は遅くなります。:-)


0

以下はsvn log --stop-on-copyに相当するgitを実装しており、ブランチの起点を見つけるためにも使用できます。

アプローチ

  1. すべての支店に向かいます
  2. ターゲットブランチと他のブランチのmergeBaseを収集します
  3. git.logと反復
  4. mergeBaseリストに表示される最初のコミットで停止する

すべての川が海に流れ込むように、すべての枝がマスターに走るため、一見関係のないように見える枝の間でマージベースが見つかります。ブランチヘッドから祖先まで戻ると、理論的にはこのブランチの起点になるはずなので、最初の潜在的なマージベースで停止できます。

ノート

  • 私は、兄弟といとこのブランチが互いにマージするこのアプローチを試していません。
  • より良い解決策があるに違いない。

詳細:https : //stackoverflow.com/a/35353202/9950


-1

次のコマンドを使用して、masterから到達できない、branch_aの最も古いコミットを返すことができます。

git rev-list branch_a ^master | tail -1

おそらく、そのコミットの親実際にマスターから到達可能であることを追加の健全性チェックで...


1
これは機能しません。branch_aが一度マスターにマージされ、その後継続する場合、そのマージでのコミットはマスターの一部と見なされるため、^ masterに表示されません。
FelipeC 2012

-2

ブランチAのreflogを調べて、作成されたコミットから、そのブランチが指し示したコミットの完全な履歴を見つけることができます。Reflogはにあり.git/logsます。


2
reflogが除去される可能性があるため、これは一般的には機能しないと思います。また、reflogsもプッシュされない(?)とは思わないため、これは単一リポジトリの状況でのみ機能します。
GaryO

-2

私はここで言及されているすべてのコーナーケースを処理する方法を見つけたと思います:

branch=branch_A
merge=$(git rev-list --min-parents=2 --grep="Merge.*$branch" --all | tail -1)
git merge-base $merge^1 $merge^2

チャールズベイリーは、祖先の順序に基づくソリューションには限られた価値しかないというのはまったく正しい。結局のところ、「このコミットはブランチXからのものです」という何らかのレコードが必要ですが、そのようなレコードはすでに存在しています。デフォルトでは、「git merge」は「Merge branch 'branch_A' into master」などのコミットメッセージを使用します。これは、2番目の親(commit ^ 2)からのすべてのコミットが「branch_A」からのものであり、最初の親にマージされたことを示します親(commit ^ 1)、つまり「マスター」。

この情報で武装すると、 'branch_A'の最初のマージを見つけ( 'branch_A'が実際に存在するようになったとき)、ブランチポイントとなるマージベースを見つけることができます:)

Mark BoothとCharles Baileyのリポジトリを試してみましたが、解決策は機能しました。どうしてそれができなかったのですか?これが機能しない唯一の方法は、ブランチ情報が本当に失われるようにマージのデフォルトのコミットメッセージを手動で変更した場合です。

有用性のために:

[alias]
    branch-point = !sh -c 'merge=$(git rev-list --min-parents=2 --grep="Merge.*$1" --all | tail -1) && git merge-base $merge^1 $merge^2'

次に、「git branch-point branch_A」を実行できます。

楽しい ;)


4
マージメッセージに依存することは、親の順序についての仮説を立てるよりも脆弱です。それは単なる仮説的な状況でもありません。私は頻繁に、一時的な一時的なブランチの名前ではなく、をマージしたgit merge -mを言います(たとえば、「メインラインの変更をフィーチャーxyzリファクタリングにマージする」)。私の例では自分の役に立たなかったと思いますか?私は1つまたは2つの一時的なブランチで同じ履歴を作成でき、違いを区別する方法がないため、問題はその完全な一般性では単純に解けません。-m
CBベイリー

1
@CharlesBaileyそれはあなたの問題です。これらの行をコミットメッセージから削除しないでください。残りのメッセージを元のメッセージの下に追加する必要があります。現在、「git merge」は自動的にエディターを開き、必要なものを追加できます。古いバージョンのgitの場合は「git merge --edit」を実行できます。どちらの方法でも、本当に必要な場合は、コミットフックを使用して、「すべてのコミットに「ブランチ 'foo'でコミット」」を追加できます。ただし、このソリューションはほとんどの人に有効です。
FelipeC、2012年

2
うまくいきませんでした。すでに多くのマージが行われているmasterから、branch_Aがフォークされました。このロジックでは、branch_Aが作成された正確なコミットハッシュは得られませんでした。
Venkat Kotra
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.