web-dev-qa-db-ja.com

Gitブランチはどこから始まり、その長さはどのくらいですか?

私は時々、gitの特定のブランチがどのコミットを開始するか、または特定のブランチで特定のコミットが作成されたかどうかを尋ねられます。ブランチの終点は非常に明確です。ブランチラベルが置かれている場所です。しかし-それはどこから始まったのですか?些細な答えは次のとおりです:作成済みそのブランチのコミットで。しかし、その情報は、私が知る限り、最初のコミット後に失われたため、質問をしている理由です。

分岐したコミットがわかっている限り、グラフを描画して明確にすることができます。

A - B - C - - - - J     [master]
     \
      D - E - F - G     [branch-A]
           \
            H - - I     [branch-B]

コミットEでブランチBを作成したので、それが「開始」です。私はそれを知っている、私はそれをやったからだ。しかし、他の人はそれを同じように認識できますか?同じようなグラフを描くことができます:

A - B - C - - - - J     [master]
     \
      \       F - G     [branch-A]
       \     /
        D - E
             \
              H - I     [branch-B]

では、グラフを見て、どのブランチがEで始まり、どのブランチがBで始まったのでしょうか?コミットDは両方のブランチのメンバーですか、それともブランチAとブランチBのどちらに属するかを明確に決定できますか?

これはいくぶん哲学的に聞こえますが、実際はそうではありません。スーパーバイザーは時々、ブランチが開始されたとき(通常はタスクの開始をマークします)と、一部の変更がどのブランチに属するか(何らかの変更の目的を得るために-作業に必要でしたか)を知りたいと思います。 gitがそれらの質問に正しく答えるための情報(ツール、コマンド)または定義を提供しているかどうかを知りたいです。

63
Andreas_D

Gitでは、すべてのブランチはルートコミットから始まると言うことができますが、これは文字通り非常に真実です。しかし、それはあなたにとってあまり役に立たないと思います。代わりにできることは、他のブランチに関連して「ブランチの開始」を定義することです。これを行う1つの方法は、

git show-branch branch1 branch2 ... branchN

そして、出力の下部にある指定されたすべてのブランチ間の共通コミットを表示します(実際に共通コミットがある場合)。

show-branchのLinux Kernel Gitドキュメント の例を次に示します。

$ git show-branch master fixes mhf
* [master] Add 'git show-branch'.
 ! [fixes] Introduce "reset type" flag to "git reset"
  ! [mhf] Allow "+remote:local" refspec to cause --force when fetching.
---
  + [mhf] Allow "+remote:local" refspec to cause --force when fetching.
  + [mhf~1] Use git-octopus when pulling more than one heads.
 +  [fixes] Introduce "reset type" flag to "git reset"
  + [mhf~2] "git fetch --force".
  + [mhf~3] Use .git/remote/Origin, not .git/branches/Origin.
  + [mhf~4] Make "git pull" and "git fetch" default to Origin
  + [mhf~5] Infamous 'octopus merge'
  + [mhf~6] Retire git-parse-remote.
  + [mhf~7] Multi-head fetch.
  + [mhf~8] Start adding the $GIT_DIR/remotes/ support.
*++ [master] Add 'git show-branch'.

その例では、masterfixesおよびmhfブランチと比較されています。この出力をテーブルとして考えてください。各ブランチは独自の列で表され、各コミットは独自の行を取得します。コミットを含むブランチには、そのコミットの行の列に+または-が表示されます。

出力の一番下で、3つのブランチすべてが共通の祖先コミットを共有しており、実際にはheadmasterコミットであることがわかります。

*++ [master] Add 'git show-branch'.

これは、fixesmhfの両方がmasterのコミットから分岐したことを意味します。

代替ソリューション

もちろん、それはGitで共通の基本コミットを決定するための1つの可能な方法です。他の方法には、git merge-baseが共通の祖先を見つけること、およびgit log --all --decorate --graph --onelineまたはgitk --allが含まれます。

元のポスターからの他の質問

これらの質問について:

コミットDは両方のブランチのメンバーですか、それともbranch-Aまたはbranch-Bのどちらに属するかを明確に決定できますか?

Dは両方のブランチのメンバーであり、両方のブランチの祖先コミットです。

スーパーバイザーは、時々、ブランチが開始されたとき(通常はタスクの開始をマークします)...

Gitでは、コミットツリー全体とそのブランチの履歴を書き換えることができるため、when「開始する」ブランチは、TFSやSVNのようなものではありません。 Gitツリー内の任意の時点にrebase分岐できます。ルートコミットの前に配置することもできます。したがって、これを使用して、ツリー内の任意の時点でタスクを「開始」できます。

これはgit rebaseの一般的な使用例で、ブランチをアップストリームブランチの最新の変更と同期させ、コミットグラフに沿って「前方に」プッシュします。あなたが実際にしばらく作業しているにもかかわらず、ブランチ。必要に応じて、コミットグラフに沿ってブランチを時間的に戻すこともできます(ただし、ブランチの内容によっては、多くの競合を解決する必要があるかもしれませんが、そうでない場合もあります)。開発履歴の途中からブランチを挿入または削除することもできます(そうすると、多くのコミットのコミットが変更される可能性があります)。履歴の書き換えは、Gitを非常に強力かつ柔軟にする主要な機能の1つです。

これが、コミットが作成日(コミットが最初に作成されたとき)とコミット日(コミットがコミットツリーに最後にコミットされたとき)の両方を伴う理由です。それらは、日時と最終変更日時を作成することに似ていると考えることができます。

スーパーバイザーは時々知りたい...いくつかの変更が属するブランチ(目的を得るためにいくつかの変更の-それは仕事に必要だった)。

繰り返しになりますが、Gitでは履歴を書き換えることができるため、必要なコミットグラフ内のほとんどすべてのブランチ/コミットに一連の変更を(リ)ベースできます。 git rebaseを使用すると、ブランチ全体を自由に移動できます(ただし、ブランチの移動先と内容によっては、競合を解決する必要がある場合があります)。

そうは言っても、Gitでどのブランチまたはタグに一連の変更が含まれているかを判断するために使用できるツールの1つは--containsです。

# Which branches contains commit X?
git branch --all --contains X

# Which tags contains commit X?
git tag --contains X
59
user456814

この質問の報奨金通知は、

ルートコミット以外の定義済みの「開始」コミットがあるとGitブランチを考えるのが理にかなっているかどうかを知りたいですか。

以下を除く

つまり:

  • 最初の定義では、固定コミットが提供されます(大規模なfilter-branchの場合を除いて、変更されることはありません)
  • 2番目の定義は、相対コミット別のブランチに対して)を提供します。これはいつでも変更できます(他のブランチは削除できます)

2つ目はgitの方が理にかなっています。これはブランチ間のマージとリベースに関するものです。

スーパーバイザーは、ブランチがいつ開始されたのか(通常はタスクの開始をマークします)および一部の変更がどのブランチに属するのか(何らかの変更の目的を得るために-作業に必要でしたか)を知りたい場合があります

ブランチはそのための単なる間違ったマーカーです。ブランチの一時的な性質(名前の変更/移動/リベース/削除/ ...)により、ブランチで「変更セット」または「アクティビティ」を模倣することはできません。 「タスク」を表します。

それは XY問題 :OPは実際の問​​題(Gitのタスクと考えられるもの)ではなく、試行された解決策(分岐が開始する場所)を求めています)。

それを行うには(タスクを表す)、次を使用できます。

  • タグ:これらは不変であり(コミットに関連付けられると、そのコミットは移動/リベースされることはなくなります)、2つの適切な名前のタグ間のコミットはアクティビティを表すことができます。
  • some git notes コミットが作成された「ワークアイテム」を記憶するコミット(タグとは反対に、コミットが修正またはリベースされた場合、メモを書き換えることができます)。
  • フック(コミットメッセージに基づいて、「ワークアイテム」のような「外部」オブジェクトにコミットを関連付けるため)。 ブリッジGit-RTC-IBM Rational Team Concert-pre-receiveフックで行うこと )ポイントは次のとおりです。ブランチの開始は、タスクの開始を常に反映するわけではありません。しかし、単に変化する可能性のある歴史の継続であり、誰のシーケンスが変化の論理的なセットを表すべきか。
14
VonC

おそらくあなたは間違った質問をしているのでしょう。 IMO、与えられたブランチにはすべてのファイルeverに加えられたすべての変更が含まれているため(つまり、最初のコミット以降、 )。

一方、2つのブランチがどこに分岐したかを尋ねることは、間違いなく有効な質問です。実際、これはまさにあなたが知りたいことのようです。つまり、1つのブランチに関する情報を知りたくないのです。代わりに、2つのブランチの比較に関する情報を知りたいと思います。

少しの研究が判明しました gitrevisions manページ これは、特定のコミットおよびコミットの範囲を参照する詳細を説明しています。特に、

コミットから到達可能なコミットを除外するには、プレフィックス^表記が使用されます。例えば。 ^ r1 r2は、r2から到達可能なコミットを意味しますが、r1から到達可能なコミットは除外します。

この集合演算は非常に頻繁に現れるため、省略形があります。 2つのコミットr1およびr2(上記のリビジョンの指定で説明した構文に従って名前が付けられている)がある場合、r1から^ r1 r2で到達可能なコミットを除いて、r2から到達可能なコミットを要求でき、r1として記述できます。 .r2。

したがって、質問の例を使用すると、branch-Amasterとは異なります

git log master..branch-A
10
Code-Apprentice

これはおそらく教育の良い機会だと思います。 gitは実際にはブランチの開始点を記録しません。そのブランチのreflogにまだ作成レコードが含まれていない限り、開始場所を明確に決定する方法はありません。ブランチがどこかにマージされている場合、実際には複数のルートコミットと多くの異なる可能性のあるポイントがあります作成された可能性があり、元のソースから分岐し始めた可能性があります。

そのような場合に反対の質問をすることは良い考えかもしれません-なぜあなたはそれがどこから分岐したかを知る必要がありますか、それはそれが分岐した場所に何か有用な方法で重要ですか?これが重要であるという正当な理由があるかもしれないし、そうでないかもしれない-その理由の多くはおそらくあなたのチームが採用し、実施しようとしている特定のワークフローに結びついており、何らかの方法でワークフローが改善されるかもしれない領域を示しているかもしれないおそらく1つの改善点は、「どこでbranch-B branch from "、多分"ブランチに含まれる修正/新しい機能が含まれるbranch-B "...

この質問に対する完全に満足のいく答えが本当に存在するかどうかはわかりません...

9
twalberg

ここには2つの別個の懸念事項があります。あなたの例から始めて、

A - B - C - - - - J     [master]
     \
      \       F - G     [branch-A]
       \     /
        D - E
             \
              H - I     [branch-B]

[...]スーパーバイザーは、ブランチが開始されたとき(通常はタスクの開始をマークします)と、一部の変更がどのブランチに属するか(変更の目的を得るために-作業に必要でしたか)を知りたい場合があります

肉に着く前の2つの事実の観察:

最初の観察:上司が知りたいのは、コミットといくつかの外部の作業指示のようなレコードとの間のマッピングです:コミットはbug-43289またはfeatureBに対処しますか?なぜlongmsg.cstrcatの使用を変更するのですか?前回のプッシュと今回のプッシュの間の20時間の支払いは誰になりますか?ブランチ名自体はここでは重要ではありません。重要なのは、外部管理レコードとのコミットの関係です。

2番目の観察:branch-Aまたはbranch-Bが最初に(たとえば、マージまたはリベースまたはプッシュ経由で)公開されるかどうかにかかわらず、コミットDおよびEの作業はそれに従って行われ、後続の操作で複製されない。これらのコミットが行われたときの現在の状態にまったく違いはありません。ブランチ名もここでは関係ありません。重要なのは、祖先グラフを介したコミットの相互関係です。


私の答えは、歴史に関する限り、ブランチ名はまったく関係ありません。これらは、そのリポジトリに固有の何らかの目的で現在どのコミットが現在のものであるかを示す便利なタグであり、それ以上のものはありません。デフォルトのmerge-commitメッセージの件名行に便利なモニカが必要な場合は、git branch some-useful-nameをマージする前にヒントを入力し、それをマージします。いずれにしても同じコミットです。

開発者がコミット時にチェックアウトしたブランチ名を外部レコード(またはまったく何でも)で結び付けることは、「すべてが機能する限りすべて問題ない」領域に深く入り込んでいます。それをしないでください。ほとんどのVCSで一般的に使用が制限されている場合でも、D-E-{F-G,H-I}は後で発生するのではなく早く発生し、それを処理するためにブランチの命名規則を適合させる必要があります。 。 。

なぜわざわざ?コミットメッセージの下部にあるキャッチフレーズに、作業を促すレポート番号を入力して完了します。 git log --grep(および一般的なgit)は、正当な理由により非常に高速です。

このようなタグラインを挿入するためのかなり柔軟な準備フックですら簡単です:

branch=`git symbolic-ref -q --short HEAD`                     # branch name if any
workorder=`git config branch.${branch:+$branch.}x-workorder`  # specific or default config
tagline="Acme-workorder-id: ${workorder:-***no workorder supplied***}"
sed -i "/^ *Acme-workorder-id:/d; \$a$tagline" "$1"

すべてのコミットを検査する必要がある場合の基本的なpre-receiveフックループを次に示します。

while read old new ref; do              # for each pushed ref
        while read commit junk; do      # check for bad commits

                # test here, e.g. 
                git show -s $commit | grep -q '^ *Acme-workorder-id: ' \
                || { rc=1; echo commit $commit has no workorder associated; }
                # end of this test

        done <<EOD
        $(git rev-list $old..$new)
EOD
done
exit $rc

カーネルプロジェクトは、著作権のサインオフとコードレビューの記録にこのようなタグラインを使用します。本当にシンプルでも堅牢でもありませんでした。

C&pの後、実際のスクリプトを非専門化するためにいくつかの手マングルを行ったことに注意してください。キーボードから編集ボックスへの警告

9
jthill

哲学的な観点から、ブランチの歴史の問題はグローバルな意味で答えることができません。ただし、 reflog は各ブランチの履歴を追跡しますその特定のリポジトリ内

したがって、誰もがプッシュする単一の中央リポジトリがある場合、reflogを使用してこの情報を追跡できます(詳細については この質問 を参照してください)。まず、その中央リポジトリで、reflogが記録され、クリーンアップされないことを確認します。

$ git config core.logAllRefUpdates true
$ git config gc.reflogExpire never

次に、git reflog <branchname>を実行して、ブランチの履歴を調べます。

テストリポジトリに数回プッシュして、サンプルコミットグラフを再現しました。今、私はこのようなことをすることができます:

$ git log --graph --all --oneline --decorate
* 64c393b (branch-b) commit I
* feebd2f commit H
| * 3b9dbb6 (branch-a) commit G
| * 18835df commit F
|/  
* d3840ca commit E
* b25fd0b commit D
| * 8648b54 (master) commit J
| * 676e263 commit C
|/  
* 17af0d2 commit B
* bdbfd6a commit A

$ git reflog --date=local master branch-a branch-b
64c393b branch-b@{Sun Oct 11 21:45:03 2015}: Push
3b9dbb6 branch-a@{Sun Oct 11 21:45:17 2015}: Push
18835df branch-a@{Sun Oct 11 21:43:32 2015}: Push
8648b54 master@{Sun Oct 11 21:42:09 2015}: Push
17af0d2 master@{Sun Oct 11 21:41:29 2015}: Push
bdbfd6a master@{Sun Oct 11 21:40:58 2015}: Push

私の例では、branch-aが最初に存在したとき、それはcommit Fを指しており、中央サーバーへの2番目のプッシュがGbranch-bが最初に存在したとき、それはcommit Iを指しており、まだ更新を見ていません。

注意事項

これは履歴のみを表示します中央レポにプッシュされたとき。たとえば、同僚がコミットAbranch-Aを開始したが、プッシュする前にコミットBにリベースした場合、その情報は中央リポジトリのreflogに反映されません。

また、これはブランチが始まった場所の最終的なレコードも提供しません。どのブランチが、最初にマスターから分岐したDEコミットを「所有」しているのか、確かに言えません。 branch-aで作成され、branch-bで取得されましたか、またはその逆ですか?

両方のブランチは、最初にそれらのコミットを含む中央リポジトリに表示され、reflogは最初に中央リポジトリに表示されたブランチを示します。ただし、これらのコミットは、format-patchなどを介して、いくつかのエンドユーザーリポジトリ間で「受け渡される」可能性があります。彼らの究極の起源を知っている。

6
Matt McHenry

@cupcakeが説明したように、ブランチの開始点はありません。あるブランチが別のブランチに最初に触れた場所のみを確認できます。ほとんどの場合、これはおそらくあなたがやりたいことです。 @ code-guruは、コミットの範囲を参照する構文をすでに説明しました。

すべてをまとめる:このコマンドは、branch-Aにあるがmasterにない最初のコミットの前の最初のコミットを表示します。

git show `git rev-list branch-A ^master --topo-order | tail -n 1`~1

5
tobib

いくつかのアーキテクチャの詳細

Gitは、リビジョンを一連のコミットとしてリポジトリに保存します。これらのコミットには、最後のコミット以降のファイルへの変更に関する情報へのリンク、および重要なことに、前のコミットへのリンクが含まれています。大まかに言うと、ブランチのコミット履歴は、最新のリビジョンからリポジトリのルートまでさかのぼる単一リンクリストです。コミット時のリポジトリの状態は、ルートに戻るまでのすべてのコミットと結合されたコミットです。

では、HEADとは何ですか?そして、ブランチとは何ですか?

HEADは、現在アクティブなブランチの最新のコミットへの特別なポインタです。マスターを含む各ブランチ1は、その履歴の最新リビジョンへのポインタでもあります。

泥だらけ? Pro Git book からの画像を使用した例を見てみましょう。2

Simple Git Tree

この図では、4つのコミットを持つ比較的単純なリポジトリがあります。 98ca9はルートです。マスターとテストの2つのブランチがあります。マスターブランチはコミットf30abにあり、テストブランチは87ab2にあります。現在、masterブランチで作業しているので、HEADはmasterブランチを指します。サンプルリポジトリのブランチの履歴は(最新のものから古いものへ):

testing:  87ab2 -> f30ab -> 34ac2 -> 98ca9
 master:           f30ab -> 34ac2 -> 98ca9

このことから、f30abで始まる2つのブランチが同じであることがわかります。したがって、テストはそのコミットでのブランチであると言えます。

Pro Gitの本はさらに詳細に説明されており、一読する価値があります。

これで対処できます

特定の質問

私たちが得る図を空想する:

Fancy like a pinky up drinking tea.

コミットDは両方のブランチのメンバーですか、それともブランチAとブランチBのどちらに属するかを明確に決定できますか?

現在わかっていることを知ると、コミットDはブランチポインターからルートへの両方のチェーンのメンバーであることがわかります。したがって、Dはbothブランチのメンバーであると言えます。

どのブランチがEで始まり、どのブランチがBで始まりましたか?

ブランチAとブランチBは両方とも、Bのマスターブランチから始まり、Eで互いに分岐します。Git自体は、どのブランチがEを所有しているかを区別しません。最古のルートで終わる。


1おもしろい事実:マスターブランチは単なる普通のブランチです。他のブランチと違いはありません。

2Pro Gitブックは、Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported Licenseでライセンスされています。

5
theB