在Git中引用提交的子项

48

如果想要将 HEAD 移动到当前 HEAD 的父级,那很容易:

git reset --hard HEAD^

但有没有一种简单的方式来执行与此操作相反的操作,即将头设置为当前头的第一个子提交?

现在,我使用 gitk 作为解决方法(alt-tab、up-arrow、alt-tab、middle-click),但我想要更优雅的解决方案,一个也可以在没有可用gitk时使用的方案。


请参考脚本/命令git children-of! - VonC
1
在 https://dev59.com/XHE95IYBdhLWcg3wi-ie 上有一个好的解决方案。 - William Pursell
可能是如何在Git中找到下一个提交?的重复问题。 - Ciro Santilli OurBigBook.com
11个回答

18

很可能不是最快的解决方案,但它满足我的需求:

#!/bin/bash

REV=$1

if [[ -z "$REV" ]]; then
    echo "Usage: git-get-child <refspec> [<child-number>]"
    exit
fi

HASH=$(git rev-parse $REV)

NUM=$2

if [[ -z "$NUM" ]]; then
    NUM=1
fi

git rev-list --all --parents | grep " $HASH" | sed -n "${NUM}s/\([^ ]*\) .*$/\\1/p"

git rev-list --all --parents 能够完美满足我的需求:它会迭代所有可访问的提交,对于每个提交,它会打印如下所示的一行:

SHA1_commit SHA1_parent1 SHA1_parent2 等。

grep表达式中的空格确保只找到那些涉及到当前SHA1的父节点。然后我们获取第n个子节点的第n行,并得到该子节点的SHA1。


我认为应该是 git rev-list --all --parents | grep -m 1 -B $(($NUM-1)) " $HASH" | head -1 | sed 's/ .*//',否则当 $NUM != 1 时它不太起作用。 - Schwern
2
请参见以下可能显著的性能改进 - Tyler
请注意,这不会找到悬空提交,即不再从任何分支可达的提交。git reset --hard HEAD^通常会使HEAD提交不可访问(除非它也在一个分支上)。因此,这可能无法找到所有提交。要查找所有提交,必须使用git rev-list --walk-reflog和/或git fsck --unreachable - sleske
1
应该使用 git rev-parse 而不是 git-rev-parse,无法编辑帖子。 - ben.snape
它救了我的命。我不小心将测试合并到功能分支中,这是一个巨大的混乱,也不像撤消最新提交那么容易(因为它合并了很多提交)。这个命令帮助我解决了这个问题。 - Artem Novikov

13

使用git rev-list --all上述方法考虑了所有可用的提交,这可能会很多,而且通常是不必要的。如果感兴趣的子提交可以从某个分支到达,则对子提交感兴趣的脚本需要处理的提交数可以减少:

branches=$(git branch --contains $commit| grep -v '[*] ('| sed -e 's+^..++')

将确定 $commit 是祖先的分支集。对于现代 Git,至少是版本 2.21+,这应该可以在不需要 sed 的情况下完成(未经测试):

branches=$(git branch --format='%(refname:short)' --contains $commit| grep -v '[*] (')

使用这个集合,git rev-list --parents ^$commit $branches 应该精确地返回 $commit 和所有其祖先的分支头之间的所有父子关系集合。


1
这对我有用,但是我必须过滤掉“*(无分支)”行,因为我当时处于一个分离的分支上。对于我的存储库来说,这大约快了10倍,0.13秒对比1.4秒。 - Paul Wagland
1
考虑使用 --format='%(refname: short) 来简化 git branch 的输出(以去除前导空格和 *) - Joshua Goldberg
1
zsh(但不是bash)在第二个命令行中使用$branches时会出现问题,因为换行符的解释方式不同。如果将branches存储为数组,则bash和zsh都可以正常工作:branches=($(git branch --contains ... )) - Joshua Goldberg
@Paul Wagland:干得好。添加了grep调用以摆脱这些行,以及像*(HEAD detached at $SHA1)这样的行。@Joshua Goldberg:添加了sed以去除前导的* ,如果有的话。在我的LTS系统上不可用--format,但很好知道。对于那些有幸拥有它的人,我添加了你的建议。关于zsh vs. bash,我更喜欢将我的脚本限制在POSIX sh所提供的内容上,感谢您为zsh用户提供解决方案。感谢您将“上面”的答案链接化。 - Rainer Blome

6

基于Paul Wagland的答案他的来源,我正在使用以下内容:

git log --ancestry-path --format=%H ${commit}..master | tail -1

我发现Paul的答案给我旧版本提交记录的错误输出(可能是由于合并导致的),其中主要差异在于--ancestry-path标志。


2
使用 --reverse 并使用 head -1 进行轻微加速。 - Tom Hale
请查看我的答案,以获取更通用的解决方案。 - Tom Hale

4
您可以使用gitk...因为可能有多个子级,所以可能没有像HEAD^这样的简单方法。
如果您想撤消整个操作,也可以使用reflog。使用git reflog查找提交的指针,您可以将其用于reset命令。请参见此处

4

根据How do I find the next commit in git?中的答案,我有一个适合自己的解决方案。

假设您想要在“主”分支上找到下一个修订版,则可以执行以下操作:

git log --reverse ${commit}..master | sed 's/commit //; q'

这也假设只有一个下一个版本,但这在问题中已经被默认了。

4

如果只是想移动 HEAD(不更新索引或工作树),可以使用以下命令:

git reset --soft $(git child)

您需要使用下面的配置。

说明

基于@Michael的答案,我在我的.gitconfig中修改了child别名。

它在默认情况下按预期工作,并且也很灵活。

# Get the child commit of the current commit.
# Use $1 instead of 'HEAD' if given. Use $2 instead of curent branch if given.
child = "!bash -c 'git log --format=%H --reverse --ancestry-path ${1:-HEAD}..${2:\"$(git rev-parse --abbrev-ref HEAD)\"} | head -1' -"

如果没有其他提交参数,它默认按照向当前分支的尖端追溯祖先的方向,将HEAD的子代作为结果(除非指定另一个提交参数)。

如果要使用短哈希表单,请使用%h而不是%H

在分离头部的情况下,没有分支,但可以通过以下别名获得第一个子代:

# For the current (or specified) commit-ish, get the all children, print the first child 
children = "!bash -c 'c=${1:-HEAD}; set -- $(git rev-list --all --not \"$c\"^@ --children | grep $(git rev-parse \"$c\") ); shift; echo $1' -"

$1替换为$*以打印所有子元素。

如果你在错误的分支上,这个不起作用。有没有办法概括一下? - TamaMcGlinn
奇怪的是,如果您不在同一分支上,则children别名可以正常工作,但对于具有多个子提交的提交,它无法打印出更多的提交。 - TamaMcGlinn

2

这取决于你所问的内容。在无数个分支中,有当前引用的无限数量的子节点,有些是本地的,有些是远程的,还有许多已经被重新定义并在你的仓库中,但不是你打算发布的历史的一部分。

对于一个简单的情况,如果你刚刚执行了重置到HEAD^,你可以通过HEAD@{1}获取回你刚才丢掉的子节点。


2

您可以使用Hudson的创建者(现在是Jenkins)Kohsuke Kawaguchi(2013年11月)的要点:
Kohsuke Kawaguchikohsuke / git-children-of:

给定一个提交,找到该提交的直接子级。

#!/bin/bash -e
# given a commit, find immediate children of that commit.
for arg in "$@"; do
  for commit in $(git rev-parse $arg^0); do
    for child in $(git log --format='%H %P' --all | grep -F " $commit" | cut -f1 -d' '); do
      git describe $child
    done
  done
done

将该脚本放在一个被$PATH引用的文件夹中,然后只需输入以下命令即可:
git children-of <a-commit>

2

这个答案不是一个绝对的答案,但它对于快速研究非常有用。

  • git log 从一开始
  • 每一行都是一个缩写提交,带有--oneline
  • 可选地添加 --graph 的选项,以获得多个祖先。
  • 使用管道符 |
  • 通过--before-context=n(也可以使用-B n)向后追溯n(此处为3)个祖先,并在其中搜索缩写提交(7个字符)grep
git log --oneline HEAD | grep -B 3 <an-abbrev-commit>

1
这也是我想到的。它的一个好处是:它非常简单易懂。 - user98761

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接