10

一般的な問題:一連のコミットが与えられた場合、それらすべてのコミットを先祖として持つコミットのリスト、または関連して、それらすべてのコミットを含む最初のコミットを見つけるにはどうすればよいですか。

git branch --contains <commit>セット内のすべてのコミットに対して返されるブランチを探すことで、コミットを含むブランチ (同様にタグ) を見つけることができますが、オプションgit rev-listはありません。事実上、通常の引数を と組み合わせて、出力を、それらのいずれかではなく、リストされているすべてのコミットを含むコミットに制限する--contains方法を探しています (これが正常に機能する方法です)。--containsgit rev-list--contains

具体例:aコミット, b,が与えられた場合c、その祖先に 3 つのコミットすべてがある最初のコミットを見つけるにはどうすればよいですか?

たとえば、以下のツリーで、X とマークされたコミットを見つけるにはどうすればよいですか?

* (master)
|
X
|\
a *
| |
b c
|/
*
|
*

でできる魔法がいくつかあると思いますがgit rev-list、おそらく<commit1>...<commit2>記法が関係していますが、それ以上のことはできません。

4

3 に答える 3

2

その質問に対する答えは、git はこのために作られたものではないということだと思います。Git は「コミットの子」という考えを本当に好まないのですが、それには十分な理由があります。それはあまり明確に定義されていないからです。コミットはその子について知らないため、非常にあいまいなセットです。実際にはレポにすべてのブランチがあるとは限らないため、一部の子が欠落している可能性があります。

また、Gits の内部ストレージ構造により、コミットの子を見つけるのはかなりコストのかかる操作になります。すべてのヘッドのリビジョン グラフを、対応するルートまで、または知りたい子を持つすべてのコミットが表示されるまでたどらなければならないからです。

git がサポートするこの種の唯一の概念は、1 つのコミットが別のコミットを含むという考えです。しかし、この機能は非常に少数の git コマンドでしかサポートされていません (git branchそのうちの 1 つです)。また、git がサポートしている場合でも、任意のコミットではサポートされていませんが、ブランチ ヘッドのみがサポートされています。

これはすべて git のかなり厳しい制限のように思えるかもしれませんが、実際には、コミットの「子」は必要なく、通常は特定のコミットが含まれるブランチを知るだけでよいことがわかります。


つまり、質問に対する回答を本当に取得したい場合は、それを見つける独自のスクリプトを作成する必要があります。これを行う最も簡単な方法は、 の出力から始めることですgit rev-list --parents --reverse --all。その行ごとに解析してツリーを作成し、各ノードについて、探しているコミットの子であるかどうかをマークします。これを行うには、コミットに出会ったらコミット自体をマークし、そのプロパティをすべての子に継承します。

すべてのコミットを含むとマークされたコミットを作成したら、それを「ソリューション リスト」に追加し、そのすべての子をデッドとしてマークします。最初のコミットを含めることはできません。このプロパティは、そのすべての子孫にも渡されます。

要求したコミットを含まないツリーの部分を保存しない場合は、ここでメモリを少し節約できます。


editいくつかの Python コードをハッキングしました

#!/usr/bin/python -O
import os
import sys

if len(sys.argv) < 2:
    print ("USAGE: {0} <list-of-revs>".format([sys.argv[0]]))
    exit(1)

rev_list = os.popen('git rev-list --parents --reverse --all')

looking_for = os.popen('git rev-parse {0}'
                       .format(" ".join(sys.argv[1:]))).read().splitlines()
solutions = set()
commits = {}

for line in rev_list:
    line = line.strip().split(" ")
    commit = set()
    sha = line[0]
    for parent in line[1:]:
        if not parent in commits:
            continue
        commit.update(commits[parent])
        if parent in solutions:
            commit.add("dead")
    if sha in looking_for:
        commit.add(sha)
    if not "dead" in commit and commit.issuperset(looking_for):
        solutions.add(sha)
    # only keep commit if it's a child of looking_for
    if len(commit) > 0:
        commits[sha] = commit

print "\n".join(solutions)
于 2012-12-20T12:15:00.587 に答える
1

考えられる解決策の 1 つ:

rev-list の呼び出しの開始点として使用するコミットを取得するには、「git merge-base ab c」を使用します。これを $MERGE_BASE と呼びます。

「git rev-list $MERGE_BASE..HEAD」呼び出しを使用して、共通の祖先から HEAD までのすべてのコミットを一覧表示します。この出力をループします (疑似コード):

if commit == a || b || c
  break
else 
  $OLDEST_DESCENDANT = commit
return $OLDEST_DESCENDANT

これは上記の例では機能しますが、マージされていない場合、a、b、c の最も若いコミットの直後のコミットでマージされていない場合、または a、b、c をまとめる複数のマージコミットがあった場合、誤検知が発生します。 b、および c (それぞれが独自のブランチに存在する場合)。その最古の子孫を見つけるには、少し作業が残っています。

次に、上記に続いて、$OLDEST_DESCENDANT で始まり、そこから HEAD に向かって DAG を逆方向に進み (rev-list --reverse $OLDEST_DESCENDANT~..HEAD)、'rev-list $MERGE_BASE の出力をテストします。 ~..$OLDEST には、必要なすべてのコミット a、b、および c が含まれています (rev-list よりも到達可能であることをテストするためのより良い方法があるかもしれません)。

twalberg が言及しているように、このようにコミットを個別にテストすることは、最適とは言えず遅いように思えますが、それは始まりです。このアプローチは、すべての入力コミットが同じブランチにある場合に有効な応答を提供するという点で、マージ コミット リスト メソッドよりも優れています。

パフォーマンスは、主に、マージ ベース、ヘッド、X、および目的のコミット セット (a、b、および c) の最も若い間の距離によって影響を受けます。

于 2012-12-19T01:33:33.067 に答える
-1

どうですか :

MERGE_BASE=`git merge-base A B C`
git log $MERGE_BASE...HEAD --merges

マージが1つしかないと仮定します。より多くのマージがある場合でも、最も古いものは 3 つのコミットすべてからの変更を含むものです。

于 2012-12-19T16:35:58.777 に答える