2012-12-18 58 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)