2012-12-18 123 views
9

一般問題:如果提交了一組提交,如何查找包含所有提交作爲祖先的提交列表,或者相關地,包含所有提交的提交列表那些提交。查找包含多個特定提交的Git提交

我可以通過查找git branch --contains <commit>針對集合中的所有提交返回的分支來找到包含提交的分支(類似標籤),但git rev-list沒有--contains選項。實際上,我正在尋找一種將常規--contains自變量與git rev-list相結合的方法,並將輸出限制爲僅包含所有列出的提交,而不是其中任何一個(這是--contains正常工作的方式)。

具體例子:鑑於提交abc,我怎麼能找到的第一個承諾是在其祖先的所有三個提交?

例如,給定下面的樹,我如何找到標記爲X的提交?

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

我認爲有一些神奇的,我可以做git rev-list,並可能涉及<commit1>...<commit2>符號,但我不能比制定出進一步。

+0

我想不出一個簡單的(有效)的方式來做到這一點,短生成所有的列表的合併提交,每一個測試分別查看是否可以從那裏訪問所述提交中的每個提交。可以相對容易地編寫腳本,但它會*慢*。我認爲最近(即1.8+版本)的'git'在幾個地方增加了一個'--contains'選項,這可能會讓這個更容易一些。 – twalberg

+0

B和C屬於不同的分支嗎? – ShadyKiller

+0

@ShadyKiller:在具體的例子中,是的;一般來說,沒有。所有這三個人可能都在同一個分支(在這種情況下,答案只會是最新的提交)或不同的分支。地獄,可能會多於或少於三次提交;這是一個相對任意的數字。 –

回答

1

一個可能的解決方案:

使用「git的合併基礎A B C」,讓承諾爲出發點,用在呼叫到REV-列表;我們將其稱爲$ 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(如果它們各自駐留在它們自己的分支上)合併在一起。還有一點工作要找到最古老的後代。

然後,您應該按照上面的內容開始使用$ OLDEST_DESCENDANT,然後在DAG中向後朝向HEAD(rev-list --reverse $ OLDEST_DESCENDANT〜..HEAD),測試看看'rev -list $ MERGE_BASE〜.. $ OLDEST包含了所有需要的提交a,b和c(儘管如此,也許還有更好的方法來測試它們比rev-list更容易獲得)。

正如twalberg所提到的,像這樣單獨測試提交似乎不是最優和緩慢的,但它是一個開始。這種方法比其合併提交列表方法具有優勢,因爲當所有輸入提交位於同一分支上時,它將提供有效的響應。

性能將主要受合併基礎,頭部X和所需提交集(a,b和c)中最小的之間的距離影響。

+0

這看起來不錯,我沒有機會坐下來,正確地編寫僞代碼,看看會發生什麼。 –

-1

如何:

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

假設你只有1合併。即使你有更多的合併,最舊的一個是包含所有三個提交的變化的那個

+0

這隻適用於非常簡單的情況,如果修訂圖具有嚴重的複雜性(實際上需要這樣的命令),那麼您只需獲取可能是合併的所有可能合併的較小列表。而你所尋求的提交併不一定是合併,但可能是列出的之一。 – Chronial

+1

你不需要給我-1仍然:(我至少部分正確 – ShadyKiller

2

我想這個問題的答案是git不是爲此而做的。 Git真的不喜歡「承諾的孩子」的想法,並且有一個很好的理由:它沒有很好的定義。因爲提交併不知道它的子節點,所以它是一個非常模糊的集合。你可能實際上沒有回購所有的分支,所以錯過了一些孩子。

Gits內部存儲結構也使得找到一個提交的子代是一個相當昂貴的操作,因爲您必須將所有頭的修訂圖移至相應的根或直到您看到所有提交的子對象想要知道關於。

git支持的唯一概念是一個提交包含另一個提交的想法。但是這個功能只支持很少的git命令(其中之一就是git branch)。在git支持它的地方,它不支持任意提交,但只支持分支頭。

這一切都可能看起來像git的一個相當苛刻的限制,但實際上它證明你不需要提交的「子」,但通常只需要知道哪些分支包含特定的提交。


這都說:如果你真的想得到你的問題的答案,你將不得不編寫自己的腳本,找到它。最簡單的方法是從git rev-list --parents --reverse --all的輸出開始。一行一行解析,你會構建一棵樹,併爲每個節點標記它是否是你正在尋找的提交的子代。一旦你遇到了他們,然後把這些財產帶到他們的孩子身上,你就可以做到這一點,等等。

一旦您的提交被標記爲包含所有提交,您將其添加到您的「解決方案列表」並將其所有子項標記爲已死 - 它們不能再包含任何第一次提交。這個屬性也將被傳遞給它的所有後代。

如果您不存儲任何不包含任何您請求的提交的樹的任何部分,則可以在此保存一些內存。


編輯亂砍一些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)