4

对于给定的合并提交,我如何找出哪些文件合并了来自两个或多个父级的更改(有或没有冲突)?

而且,这里有一个例子,只是为了很好的衡量:

A -- B -- C -- E-- .. 
      \-- D --/  

我有以下文件

  • B有f1、f2、f3、f5、f6
  • C 修改 f1 和 f3。删除 f2
  • D 修改 f1、f3 和 f6。添加 f4。
  • E 是合并提交,有 f1、f3、f4、f5 和 f6。

我正在寻找在 E 中返回列表“f1 f3”的 git 命令,因为在 E 中,只有这两个文件同时被C 和 D更改。所有其他人要么未触及,要么仅由单亲更新。

用例如下:一家公司有一个 SCM(不是 git),其中开发人员将变更集(文件列表)提交到临时分支。提交需要通过测试和同行评审的审查,然后才能被主要开发分支接受。偶尔(我的意思是经常),开发分支在提交之后继续进行,此时需要合并(和重新合并)一些文件才能被开发分支接受。

在上面的示例中,底线代表临时分支,D 是我正在审查的变更集。第一行是主开发分支,C 是同时进行的提交。在 E 中,我的更改已获批准,并已更新并与新的开发分支合并。现在的任务是列出我需要向上游推送到公司 SCM 的文件列表(请记住,这是我需要提出的手动变更集)。在 E 中更改的文件包括我在 D 中修改或添加的文件,并且已经推送到上游并且没有对应项或在 dev 分支中未触及(在 C 中)。在 E 中还有其他人在 dev 分支中修改的文件,我与此无关。这些是单个文件父母。其余的是合并的文件(由 Git 自动合并,或者在发生冲突时由我自己合并)。这就是我需要推高的清单。

4

4 回答 4

2

(后期编辑:diff-tree-c仅列出与所有父母不同的文件,即正是所要求的:

git diff-tree -r -c $commit  # content that doesn't match any parent version

)
( 稍后编辑:以上内容实际上并不完全正确:请求的内容和下面的脚本打印的是所有具有多个父项的文件,这些文件自合并基础以来发生了更改。根据定义,所有此类文件都需要合并分辨率。差异忽略合并的文件决议是采取一位父母作为结果。)


好的,从编辑看来,您希望生成一个文件列表以检查合并驱动程序可能的错误合并,这些文件结合了至少两个父级的实际更改。这会做你:

(编辑:正确处理不包含来自已更改父项的更改的合并;还包含@torek 的简化。)

substantive-merges-in ()
{
    set -- `git rev-list $1^! --parents`;
    child=$1;
    shift;
    base=$(git merge-base "$@")
    for parent; do
        git diff-tree $base $parent -r --name-only --diff-filter=M
    done \
    | sort \
    | uniq -d
}
substantive-merges-in master

测试:

git init t;cd t
git checkout -b first
# msysgit doesn't install `seq`?
for i in 1 2 3 4 5 6 7 8 9 10; do echo $i >>both; done
cp both justfirst
git add *; git commit -minitial
git branch second
sed -i s/3/3onfirst/ both
sed -i s/3/3onfirst/ justfirst
git commit -amtwochanges
git checkout second
sed -i s/7/7onsecond/ both
git commit -amonechange
git merge first
substantive-merges-in HEAD          # should list 'both'
git checkout -B second second@{1}
git merge --no-commit first
git checkout --ours both
git commit -amstomp
substantive-merges-in HEAD          # should still list 'both'
于 2013-10-02T14:54:20.730 回答
0

假设你的意思是:

  • commitM是与两个(或更多)父级(至少)的M^合并M^2提交
  • 的完整树MT
  • T您想从某些父级中不存在的任何文件中排除

那么执行此操作的一种直接方法是从完整列表开始T,然后删除这些文件。这是一个可以做到这一点的脚本,我认为它没有太多的魔力。轻微测试...

#! /bin/sh

PROG=$(basename $0)

case $# in
1) user_arg="$1";;
*) echo "usage: $PROG <commit>" >&2; exit 1;;
esac

# find full SHA1 of user-specified rev, plus all its parents
args=$(git rev-list --no-walk --parents "$user_arg") || exit 1
set -- $args

# omit this if you want to just list all files in a non-merge commit
case $# in
1|2) echo "$PROG: $user_arg is not a merge commit" >&2; exit 1;;
esac

# make temp file
TF=$(mktemp -t "$PROG") || exit 1
trap "rm -f $TF" 0 1 2 3 15

# save the SHA-1 of the commit, then toss that from arguments
c=$1
shift

# Now look at each parent: if the file was added between that
# parent and commit $c, it was not in that parent, so it's not
# "in common" across all parents to the final commit.  Dump
# such names into a "remove list".
#
# Remove duplicates from "remove" list.  Turn result into series
# of regexp's for "grep -v".  We need to:
#   1) protect any regexp metacharacters: turn . * ^ $ [ \ into
#      backslash-prefixed versions of same
#   2) add ^ at front and $ at end.
for parent do
    git diff-tree -r --name-only --diff-filter=A $parent $c
done | sort -u | sed -e 's/[.*^$[\]/\\&/g' -e 's/.*/^&$/' > $TF

# Now just run grep -v with that list, with input being the
# output of the "master list" of files in commit $c.
git ls-tree -r --name-only $c | grep -v -f $TF

如果你的意思是别的,--diff-filter上面是可调的。

于 2013-09-30T02:29:50.330 回答
0

我想这样就可以了,也许有人知道更优雅的东西

doit ()
{
    set -- `git rev-list $1^! --parents`;
    child=$1;
    shift;
    for parent; do
        git diff-tree $parent $child -r --raw \
        | awk '$1~/:100/ && $5=="M" {sub(/[^\t]*\t/,""); print}';
    done \
    | sort -u
}
doit master
于 2013-09-30T02:09:24.853 回答
0

好的,让我们根据问题编辑解决一个不同的“相当精确”的定义。

鉴于:

  • 合并提交M
  • 最终树T包含文件f1, f2, ...,fn
  • 和直系父母p1, p2, ...,pn

你想要——不管其他可能的祖先1——所有文件,在给定任何两个不同的父母和的情况下,在和中都被“修改” 。fipapbfipapb

这里“修改”的定义是,对于 commitp和 file fp它本身有一个单亲,p^p既不是合并也不是根提交),并且p:ff提交中的文件p)不同于p^:f(可能甚至不存在)中p^)。

这表明使用以下明显(且完全未优化)的算法来查找树中满足此约束的所有文件:fiT

# set M = merge commit ID and P to its complete list of parents
# (see previous scripts for how to achieve that)
for f in $(git ls-tree -r $M); do
    found=false twice=false
    for p in $P; do
        $twice && continue # already announced
        if modified_in $p $f; then
           $found && twice=true || found=true
        fi
        $twice && echo $f  # announce if found twice
    done
done

其中modified_in定义为:

modified_in() {
    local p=$1 p_hat=$1^ path="$2"

    assert_single_parent $p # optional: verify neither root commit nor merge
    # (if you want to do this, it would be more efficient to do it once
    # outside the "for f in ..." loop)
    test ! -z "$(git diff-tree -r --diff-filter=AM $p_hat $p -- "$path")"
}

在这里,该git diff-tree命令将输出如下一行:

:100644 100644 <sha1_in_p^> <sha1_in_p> M   c

$p_hat对于在和之间修改的文件$psha1值是 blob SHA-1),并且:

:000000 100644 <null_sha1> <sha1_in_p> A    fgh

对于在那里添加的文件。--diff-filter=AM确保没有输出用于删除(否则你会在这里得到一个)R,并将-- "$path"检查限制为给定的文件名路径。我很确定(但尚未测试)您不必担心(复制编辑CR重命名),并且由于这些是提交树差异,而不是索引差异,U因此(未合并)不会发生。所以我们只需要git diff-tree使用该过滤器运行,并测试该命令是否打印任何内容。

(为了使这[可能]更有效,git diff-tree在所有“有趣的”父母上运行所有可能的命令一次,不指定路径,保存他们的输出,然后交叉关联列出的所有文件。出现两次或更多的那些是你的候选人. 但这在sh脚本中要困难得多:你需要像awk这里这样的东西。)

awk[编辑:不,你毕竟不需要,sort | uniq -d会成功的。请参阅jthill 的新答案,它实现了对问题略有不同解释的更高效的版本,可能更接近真正的意图,我承认我仍然感到困惑。]


1也就是说,如果提交图看起来像这样,例如:

A -- B -- C -- D -- M -- .. 
      \-- E -- F --/ 

您只关心DF相关的M更改,而不关心C和的更改E

如果您确实关心,您可能希望M针对例如通过分别将 C-and-D 和 E-and-F 挤压在一起制成的临时树进行差异提交;或者一直做成对比较,或者类似的。基本上,您需要列出合并基础(commit B,此处)和合并本身(M)之间的 revs,然后弄清楚您希望如何处理它们。

于 2013-10-01T05:10:13.523 回答