如何在git中获取合并文件列表?

时间:2013-09-30 00:31:07

标签: git

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

而且,这是一个例子,只是为了好的衡量标准:

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中,我已批准我的更改,并已更新并与新的dev-branch合并。现在的任务是提出一个我需要向上游推送到公司SCM的文件列表(记住,这是我需要提出的手动变更集)。在E中更改的文件包括我在D中修改或添加的文件,并且已经向上游推送并且没有对应文件或者没有在dev分支中触及(在C中)。并且在E中也是由其他人在dev分支中修改的文件,而我与之无关。这些是具有单个父级的文件。其余的是合并的文件(由Git自动或在发生冲突时由我自己)。这就是我需要推动的清单。

4 个答案:

答案 0 :(得分:2)

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

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


(后面的编辑:以上实际上并不完全正确:请求的内容以及下面打印的脚本是自合并库以来所有具有更改的父项的文件。根据定义,所有这些文件都需要合并解析.diff忽略合并解析的文件是结果一个父母。 )


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

(编辑:正确处理不包含已更改父级的更改的合并;还包含@ 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'

答案 1 :(得分: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

答案 2 :(得分:0)

假设你的意思是:

  • commit M是一个包含两个(或更多)父项的合并提交(至少M^M^2
  • M的完整树T
  • 您要从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是可调的。

答案 3 :(得分:0)

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

假设:

  • 合并提交M
  • 最终树T包含文件f1f2,...,fn
  • 和直接父母p1p2,...,pn

你想要的 - 无论其他可能的祖先 1 -all files fi哪里有两个不同的父母papb,{{1} } fipa中都被“修改”了。

此处“修改”的定义是,对于提交pb和文件pf本身只有一个父,p(所以{{1} }既不是合并也不是根提交),p^(提交p中的文件p:f)与f不同(可能在p中不存在{1}})。

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

fi

其中T定义为:

# 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 值为blob SHA-1),并且:

$p

添加了一个文件。 sha1确保没有删除的输出(否则你会在这里获得:000000 100644 <null_sha1> <sha1_in_p> A fgh )并且--diff-filter=AM将检查限制为给定的文件名路径。我很确定(但尚未测试)您不必担心R-- "$path"(复制 - 编辑和重命名),因为这些是提交树差异,而不是索引差异,{{1} }(未合并)不能发生。所以我们只需要使用该过滤器运行C,并测试该命令是否打印任何内容。

(为了使[可能更多]更有效率,在所有“有趣”父项上运行所有可能的R命令,而不指定路径,保存其输出,然后交叉关联列出的所有文件。出现两次或两次以上的人是你的候选人。但是U脚本要难得多:你需要git diff-tree这样的东西。)

[编辑:不,毕竟你不需要git diff-treesh会做到这一点。请参阅jthill's new answer,它实现了对问题略有不同解释的更高效版本,可能更接近真实意图,我承认我仍然感到困惑。]


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

awk

您只关心awksort | uniq -dA -- B -- C -- D -- M -- .. \-- E -- F --/ 所做的更改,而不关注DF所做的更改。

如果你关心,你可能想要对例如分别通过挤压C-D和E-and-F制成的临时树进行差异提交M ;或者一直进行成对比较,或者其他一些比较。基本上,您需要列出合并库(此处为C)和合并本身(E)之间的转速,然后找出您希望如何处理它们。