ref^指的是在ref之前的提交,那么在ref之后的提交呢?

例如,如果我签出12345,我如何签出下一次提交?

是的,Git是一个DAG节点指针结构树。我如何找到这个之后的提交?


当前回答

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

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

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

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

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

其他回答

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

“逆箭头移动”是可能的,但如果你以前没有见过,那么这样做的方式是令人惊讶的,然后是显而易见的。假设我们有:

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

使用middle~2跟随箭头两次从C回到a,那么我们如何从C移动到D呢?答案是:我们从E开始,最后使用名字,向后工作直到中间,记录我们一路访问的点。然后我们只需要在最后一个方向上移动我们想要的距离:向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向后工作时,我们只列出那些将commit C作为自己祖先之一的提交。也就是说,如果这个图——或者它的相关部分——实际上是这样的:

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

然后对表单feature2进行简单的请求。master按一定顺序枚举提交J、G和I以及F和H。使用——ancestor -path,我们剔除H和I:它们不是C的后代,只是a的后代。使用——topo-order,我们确保实际的枚举顺序是J,然后是G,然后是F。

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

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

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

为了获得“id2方向的下一个提交”,并且有一个非常长的提交列表,git rev-list命令在试图写入head时可能会得到一个破裂的管道,因为head已经停止读取其输入并退出。由于shell通常会忽略管道破裂错误,因此这通常是可行的。只是要确保在使用时忽略它们。

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

重要的边注

注意带有“菱形”或“苯环”的图形片段:

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

将一次提交从H“向前”移到最后会得到I或k。对此你无能为力:两次提交都向前一步!如果您从结果的提交开始,并执行另一个步骤,那么您现在已经提交到您开始的路径。

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

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

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

《Hudson》(现在的Jenkins)的创造者Kohsuke Kawaguchi(2013年11月)写道: Kohsuke / 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

正如这个线程所说明的,在一个基于DAG(有向无环图)表示的历史的VCS中,没有“一个父”或“一个子”。

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

提交的顺序是通过“拓扑顺序”或“日期顺序”来完成的(参见GitPro书籍)。

但是从Git 1.6.0开始,你可以列出提交的子对象。

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

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

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

我在~/.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
}

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

[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

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