如何在 Git 中找到下一个提交

ref^指的是 ref之前的提交。那么如何获得提交 之后 ref呢?

例如,如果我是 git checkout 12345,我如何签出下一个提交?

是的,Git 是一个 DAG节点指针结构树

77620 次浏览
哈德逊(现在是詹金斯)的创建者,Kohsuke川口发布(2013年11月): Kohsuke / git-children-of: < / p >

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

#!/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

正如这个线程所示,在基于由有向无环图(DAG)表示的历史的VCS中,没有“one parent”;或者“一个孩子”。

        C1 -> C2 -> C3
/               \
A -> B                  E -> F
\               /
D1 -> D2 ----/

提交的顺序是由“topo-order”完成的;或“;date-order"(参见GitPro书)。

但是由于Git 1.6.0,你可以列出提交的子类。

git rev-list --children
git log --children

注意:对于家长提交,你也有同样的问题,后缀^的修订参数表示该提交对象的第一个父对象。^<n>表示__abc2父类(即rev^等价于rev^1)。

如果你在分支foo上并发出"git merge bar"那么foo将是第一个父对象。
例如:第一个父节点是你合并时所在的分支,第二个父节点是你合并时所在的分支上的提交

每次提交都存储一个指向其父(在merge(标准)提交的情况下为父)的指针。

因此,没有任何方法可以从父对象指向子提交(如果有的话)。

如果子提交都在某个分支上,你可以使用gitk --all commit^..,其中"commit"是标识提交的东西。例如,如果提交的缩写SHA-1哈希值为c6661c5,则键入gitk --all c6661c5^..

您可能需要将完整的SHA-1哈希值输入到gitk的“;SHA1 ID:"细胞。你需要完整的SHA-1哈希值,在本例中可以通过git rev-parse c6661c5获得。

或者,git rev-list --all --children | grep '^c6661c5883bb53d400ce160a5897610ecedbdc9d'将生成一行包含此提交的所有子对象的行,不管是否涉及分支。

我明白你的意思。让人沮丧的是,有足够多的语法可以转到以前的提交,但没有一个语法可以转到下一个提交。在复杂的历史中,“下一个任务是什么”的问题;变得相当困难,但在复杂的合并中,同样的困难也出现在“以前的”提交中。在简单的情况下,在具有线性历史记录的单个分支中(即使只是局部地针对一些有限数量的提交),向前和向后移动将是很好的和有意义的。

然而,这样做的真正问题是,子提交没有被引用;它只是一个反向链表。找到子提交需要进行搜索,这并不太糟糕,但Git可能不想把它放入refspec逻辑中。

无论如何,我遇到了这个问题,因为我只是想在历史上一步一步地向前迈进,做测试,有时你必须向前迈进,而不是后退。嗯,经过深思熟虑,我想出了这个解决方案:

在你所处的位置之前选择一个承诺。这可能是一个分支。如果你在分支~10,“git结帐分支~9”,“git结帐分支~8”;得到下一个之后,“git结帐分支~7”;等等。

如果需要的话,在脚本中递减这个数字应该非常容易。比解析Git版本列表简单得多。

要列出所有的提交,从当前提交开始,然后是它的子提交,等等-基本上是标准的git日志,但在时间上相反,使用类似于

git log --reverse --ancestry-path 894e8b4e93d8f3^..master

其中894e8b4e93d8f3是您想要显示的第一个提交。

git rev-list --ancestry-path commit1..commit2

我将commit1设置为当前提交,并将commit2设置为当前头,这将返回所有在commit1commit2之间构建路径的提交列表。

输出的最后一行是commit1的子元素(在commit2的路径上)。

我用下面的方法找到了下一个孩子:

git log --reverse --children -n1 HEAD (where 'n' is the number of children to show)

Tomas Lycken在使用Git提交驱动实时编码会话中的响应展示了一种简洁的方法,如果你可以在提交堆栈的末尾创建一个定义良好的标记。本质上

git config --global alias.next '!git checkout `git rev-list HEAD..demo-end | tail -1`'

在“demo-end"是最后一个标签。

我在~/.gitconfig中有这个别名

first-child = "!f() { git log  --reverse --ancestry-path --pretty=%H $1..${2:-HEAD} | head -1; }; f"

我尝试过许多不同的解决方案,但没有一个对我有效。我得自己想办法。

查找下一个提交

function n() {
git log --reverse --pretty=%H master | grep -A 1 $(git rev-parse HEAD) | tail -n1 | xargs git checkout
}

查找之前的提交

function p() {
git checkout HEAD^1
}

如果你心中没有特定的“目的地”提交,而是想看到可能在< em > < / em >分支上的子提交,你可以使用这个命令:

git rev-list --children --all | grep ^${COMMIT}

如果你想看到所有的子节点< em >和孙子< / em >,你必须递归地使用rev-list --children,如下所示:

git rev-list --children --all | \
egrep ^\($(git rev-list --children --all | \
grep ^${COMMIT} | \
sed 's/ /|/g')\)

(给出只有孙子的版本将使用更复杂的sed和/或cut。)

最后,你可以将它输入log --graph命令来查看树结构,如下所示:

git log --graph --oneline --decorate \
\^${COMMIT}^@ \
$(git rev-list --children --all | \
egrep ^\($(git rev-list --children --all | \
grep ^${COMMIT} | \
sed 's/ /|/g')\))

请注意:上面的命令都假设你已经将shell变量${COMMIT}设置为你感兴趣的子提交的某个引用(分支,标签,sha1)。

两个实用的答案:

一个孩子

基于@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

多个孩子

使用分离的HEAD(没有分支)或获取所有子结点,而不考虑分支:

# 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更改为$*以打印所有子对象。

你也可以将--all更改为一个commit-ish,以只显示作为该commit的祖先的子对象——换句话说,只显示给定提交“方向”的子对象。这可以帮助您将输出从多个子节点缩小到一个子节点。

现有的答案假设您有一个包含您正在寻找的提交的分支。

在我的例子中,我正在寻找的提交不在git rev-list --all上,因为没有分支包含它。

我最终手动查看了gitk --reflog

如果你在reflog中也找不到你的commit,试试:

Git的所有内部箭头都是单向的,向后指向。因此,没有简单方便的语法来向前推进:这是不可能的。

可能“反箭头移动”,但它的方式是令人惊讶的,如果你以前没有见过它,然后明显之后。假设我们有:

A <-B <-C <-D <-E   <-- last
^
|
\--------- middle

使用middle~2跟随箭头两次,从C回到A。那么我们如何从C移动到D呢?答案是:我们从E开始,使用last的名称,并向后工作,直到我们得到middleC1。然后我们只需要在last的方向上移动我们想要的距离:移动一步到D,或者移动两步到E

当我们有分支时,这一点尤其重要:

          D--E   <-- feature1
/
...--B--C   <-- master
\
F--G   <-- feature2

哪个提交是C之后的一个步骤?没有正确答案,除非你在问题中加上:在特征的方向上(填空)。

为了枚举C(不包括C)自身和G之间的提交,我们使用:

git rev-list --topo-order --ancestry-path master..feature2

--topo-order确保即使存在复杂的分支和合并,提交也会按拓扑排序。只有当链不是线性的时候才需要这样做。--ancestry-path约束意味着当我们从feature2向后工作时,我们只列出那些将提交C作为自己的祖先之一的提交。也就是说,如果这个图——或者它的相关部分——实际上是这样的:

A--B--C   <-- master
\     \
\     F--G--J   <-- feature2
\         /
H-------I   <-- feature3

然后,一个形式为feature2..master enumates的简单请求将按某种顺序提交JGI,以及FH。在--ancestry-path中,我们去掉了HI:它们不是C的后代,而是J0的后代。使用J1,我们确保实际的枚举顺序是J,然后是G,然后是F

git rev-list命令在其标准输出中溢出这些哈希id,每行一个。为了向feature2的方向前进一步,我们只需要最后的行。

可以添加--reverse,以便git rev-list在生成提交后按相反的顺序打印提交(这很诱人,也很有用)。这确实有用,但如果你在这样的管道中使用它:

git rev-list --topo-order --ancestry-path --reverse <id1>...<id2> | head -1

而有一个非常长的提交列表,当git rev-list命令试图写入已经停止读取其输入和退出的head时,它会得到一个破裂的管道。由于shell通常会忽略管道破裂错误,因此这通常是可行的。只要确保它们在你的的使用中被忽略。

-n 1--reverse一起添加到git rev-list命令中也很诱人。不要这样做!这使得git rev-list在走过一步回来后停止,然后反转所访问的(一项)提交列表。所以每次都会产生<id2>

重要的边注

注意,“钻石”;或者“苯环”;图的片段:

       I--J
/    \
...--H      M--...  <-- last
\    /
K--L

推动一个承诺“前进”;从Hlast将得到要么 I K。对此您无能为力:两次提交都是向前迈进了一步!如果您从结果的提交开始,并执行另一个步骤,那么您现在已经提交到您开始的路径。

解决这个问题的方法是避免一次只移动一步,从而被锁定在路径依赖链中。相反,如果计划访问整个祖先路径链在做其他事情之前,则创建链中所有提交的完整列表:

git rev-list --topo-order --reverse --ancestry-path A..B > /tmp/list-of-commits

然后,访问这个列表中的每个提交,一次一个,您将得到整个链。--topo-order将确保您按此顺序命中__abc1 -和-J,以及__abc3 -和-L(尽管没有简单的方法来预测您将在K-L对之前还是之后执行I-J对)。

终端:

$ git log --format='%H %P' --all --reflog | grep -F " [commit-hash]" | cut -f1 -d' '

或者在.gitconfig中,section [alias]:

children = "!f() { git log --format='%H %P' --all --reflog | grep -F \" $1\" | cut -f1 -d' '; }; f"

这显示了当前HEAD的所有子元素的列表。

git rev-list --parents --all | awk -v h="$(git rev-parse HEAD)" 'index($0,h)>1{print$1}'

它只打印所有以HEAD为父文件的提交。

你可以稍微加快速度,将^HEAD放在|之前,那么HEAD的祖先将不会被搜索。

如果你想打印另一个提交或分支的子文件,只需把它放在HEAD的位置(在更快的版本中,在两个HEAD的位置)。

我需要一个命令来快速签出下一次提交。
我最终得到了这个别名:

[alias]
next = "!f() { CMIT=$(git log --ancestry-path --format=%H ${commit}..${1} | tail -1) && git checkout $CMIT; }; f"

你需要指定你想要遍历的分支,例如:

git next master

你可以直接编辑它,把它添加到你的.gitconfig文件中。

输出示例:

~/home/myRepo | dd9e66ee  git next master
Previous HEAD position was dd9e66e Update README
HEAD is now at d71c74b Rename project; update to JUnit5

通过运行这个命令,你进入分离头模式,当然:)