在git分支中构建增量修订列表

时间:2017-12-06 21:20:29

标签: git

我想构建一个分支中所有修订的列表(由于必须定期检查它们)。所以基本上,这是分支具有的修订缓存。由于分支的大小,因此自上次更新缓存以来,仅使用新提交来逐步更新缓存是理想的。这很有效,因为我偶然有办法知道分支什么时候是"脏"。

我可以使用git rev-list --reverse my-branch按时间顺序(最早的第一个)获取所有修订版本。它给我一个很好的简单的修订列表,我可以填入我的缓存。然后,我似乎能够使用git rev-list --reverse my-branch ^<revision>找到新的提交。

事情是,我注意到如果我再次运行我的第一个命令(git rev-list --reverse my-branch),我会得到不同的结果。似乎存在相同的提交,但顺序不同。这让我想知道上面段落中描述的方法是否足够。我实际上并不关心秩序;我只想在该分支中进行一整套修订。我唯一需要订购的是知道最后一个提交是什么(所以我可以在我的第二个命令中填写<revision>)。我假设前一个列表中的最后一次提交是最新的。

(我实际上问的部分是因为我已经使用了这样一个系统一段时间了,但是现在我在缓存中缺少修订版,并且想知道我构建这样的缓存的方法是否不足。)

1 个答案:

答案 0 :(得分:1)

主要问题是在该分支中定义短语修订

根据分支的增长方式,使用git rev-list --topo-order --reverse ^stop start获取可从名称或哈希ID或其他起点获取的提交列表就足够了start ,但可从名称或哈希ID或其他起点 stop 中获取。然后,完成此操作后,您可以将保存的哈希ID更新为您提供的哈希ID,或者从 start 获取。

详细说明

很多人都喜欢想象Git分支的工作方式如下:

master:  A--B--C
                \
develop:         D--E

这里存储库中有五个提交,它们考虑前三个提交 - 我们将提交A到Z标记,而不是通过大的,丑陋的,难以理解的哈希ID标记 - 作为&#34;属于&#34;分支master,提交D和E&#34;属于&#34;分支develop

但这并不是Git分支实际工作的方式。提交有内部箭头连接它们,但这些箭头都是向后。他们从右边开始,向左边工作。这些内部箭头来自每次提交,并指向提交的父级(或者用于合并提交,两个或更多父级)。实际上,每个提交都存储其父级(或者父级,如果是合并提交)的原始哈希ID,而不是箭头。因此指针附着在孩子身上 - 或更准确地说,嵌入其中并且是他们身份的永久性和不可改变的部分。

(每个提交的实际原始哈希ID是通过计算提交内容的加密哈希来确定的,包括拼写出来的父哈希或哈希值。这使得无法改变任何提交的内容,无论如何:如果你改变了一个比特,那么结果就是一个新的,不同的哈希,用于一个新的,不同的提交。)

与此同时,masterdevelop等名称可用作可移动箭头,指向一个特定提交。所以绘图真的应该是这样的:

A--B--C   <-- master
       \
        D--E   <-- develop

名称master指向提交C,名称develop指向提交E.提交E指回D; D指回C; C指向B;由于提交A是有史以来第一次提交,因此无法指出 - 所以它没有,这使得它成为 root commit

最后,这意味着所有提交(在这个五提交存储库中)都在develop上;其中三个<{>> master

分支如何成长,第1部分

现在,将提交添加到分支的典型过程是:

git checkout <name>
... do some work ...
git add -u   # or similar, to copy new versions back into Git's index
git commit

第一步git checkout name提取给定分支名称​​ name 指向的提交内容。这些内容进入Git的索引,也进入你的工作树。然后,Git将名称HEAD设置为记录名称​​ name 。 (让我们说 name develop,我们就在这个五次提交的存储库中。)

您现在照常开展工作,然后使用git add将更新的文件复制回索引。许多人认为指数在git add之前是空的,但情况并非如此。 (Git&#39; --allow-empty标志相当误导。不是索引本身是否为空,而是来自HEAD diff 索引为空。)

索引是一个复杂的野兽,很难直接,但最好的简短描述是它构建下一个提交的地方。它首先使用与工作树相同的所有文件(但在Git化的内部形式中),匹配您刚签出的HEAD提交。您可以使用git add从工作树中复制新版本来更改这些内部表单副本。然后,git commit命令将索引内容打包为新的源快照,从中收集提交消息,并写出具有以下内容的新提交:

  • 从索引创建的快照(技术上是tree行);
  • 您的姓名和电子邮件地址作为作者和提交者,以及&#34;现在&#34;作为这两个项目的时间戳;
  • 当前提交的哈希ID作为新提交的父级;
  • 您的提交消息,作为新提交的消息。

您可以通过运行git cat-file -p HEAD(试一试!)来查看这些内容。

写完之后,现在有第六次提交:

A--B--C   <-- master
       \
        D--E   <-- develop (HEAD)
            \
             F

新提交指向当前提交。使提交出现在分支上的最后一步是移动分支指针,方法是将新提交的哈希ID写入名称存储在HEAD中的分支。自那以后develop,结果就是:

A--B--C   <-- master
       \
        D--E
            \
             F   <-- develop (HEAD)

(现在没有理由将F放在与E分开的一行;我只是保持这种方式,以便更明显地发生了什么)。

分支如何成长,第2部分

现在,分支机构不需要如此简单地增长。例如,假设到目前为止我们有六个提交A到F.然后我们运行git checkout master并创建一个新提交:

A--B--C------G   <-- master (HEAD)
       \
        D--E--F   <-- develop

然后,完成后,我们运行git merge develop

Git现在将提交C(两个分支的合并基础)与两个提示提交进行比较 - HEAD名称提交G和develop名称提交F,因此Git运行{{ 1}}查看我们的内容,git diff --find-renames C G查看他们(无论他们是谁)在git diff --find-renames C F上做了什么。

Git现在结合这两组更改,并将合并后的更改应用于提交C.如果一切顺利 - 如果更改似乎没有冲突,至少就Git而言很聪明,这根本不是很远--Git会从结果中做出新的提交。这个新提交只有两个父项,我们可以像这样绘制它:

develop

此时,突然提交A--B--C------G--H <-- master (HEAD) \ / D--E--F <-- develop 全部都在D-E-F。它们可从提交master访问,名称H指向该地址。

这是mastergit log

的第一个秘密

git rev-listgit log都可以找到一些起点 - 一些是第一次(或最后一次,真正的)提交,通常是某个分支的提示。您可以通过提供分支名称或其原始哈希ID,或通过大量其他特殊语法 1 中的任何一个来指定任何一个特定提交(这些都在the gitrevisions documentation中列出),作为一个起点,命令将使用该提交来查找父提交,并使用父提交来查找另一个父提交,依此类推。

git rev-list命令默认查看git log,而针对脚本的HEAD没有默认值:如果是{39},则必须明确命名git rev-list你想要的是什么。在这种情况下,如果我们使用提交H启动命令,他们将查看H(打印出其哈希ID以及可能的其他信息),然后查看其父项。

但是提交H有两个父母,而不仅仅是一个。因此,HEADgit log现在会查看两个提交,G和F,&#34;同时&#34;。

它们实际上无法同时显示两者,因此它们会使列表线性化。确切的线性化方法取决于您指定的排序选项。默认情况下,只要队列中有多个提交显示,就会显示哪个提交具有最新提交者日期,但是如果指定git rev-list,则命令将确保不交错两个不同的子提交分支:如果它在提交F旁边,它将在显示G之前一直向下移动到D.

(你可能想知道Git怎么可以选择F而不是G.好吧,我们暂时假设G是后来制作的,所以它不会 - 但是如果计算机时钟错了怎么办?当我们制作其中一个?或者如果G首先制作,我们只是奇怪地贴上了它?)

由于每个提交都可以从H到达(通过从那里开始并沿着两个分支向后工作),默认情况下--topo-order将显示每个提交。为了使其尽早停止,您可以指定一个停止点:它将以相同的方式避免显示提交从提交提交的任何提交。因此,如果我们告诉它不显示提交E,它将不会显示E,也不显示D,也不显示C或B或A.这不会阻止它显示G:G 不能从E到达。可达性要求向后,通过Git存储的向后链接。

添加git log只是告诉命令以相反的顺序输出最终列表(由于自然顺序已经向后,因此将向后反转为向前 - )。不过,Git仍然必须生成列表,但是:从提交到其子项没有简单的方法。提交知道所有他们的父母,但没有提交知道其子女的任何

1 不是"syntagma",虽然我喜欢这个词,which is a real word

有时,分支会剧烈/有力地更新

我们可以让所有这些完全正常,自然,一次提交,或者甚至这个突然的名称获得了许多新的可达提交以及它之前的所有提交#34;从合并(或Git称之为快进),但我们也可能有痛苦的变化。

例如,假设在将--reverse合并到develop后,我们会完全删除master

develop

提交都没有消失,因为它们都可以从A--B--C------G--H <-- master (HEAD) \ / D--E--F 找到(可到达)。但是现在我们可以创建一个 new master,与旧版本无关。让我们在提交develop时随意启动它:

G

并添加新提交:

              G   <-- develop
             / \
A--B--C-----'   H   <-- master (HEAD)
       \       /
        D--E--F

并且可能会放松我们的绘图:

              G--I   <-- develop
             / \
A--B--C-----'   H   <-- master (HEAD)
       \       /
        D--E--F

如果我们从这个新的 I <-- develop / A--B--C------G--H <-- master (HEAD) \ / D--E--F 开始并向后工作,然后反转列表以进行转发,我们会收到提交develop。提交A--B--C--G--I根本不在列表中!

更常见的是,但仍然相当痛苦,我们可以强行推动&#34;故意 discard 的事件通过从一个存储库到另一个存储库的推送提交,或者在存储库中丢弃提交的D--E--F事件。在这些情况下,旧的停止点可能变得无效,或者至少不是非常有用。它取决于谁来定义选择在分支上的提交意味着什么?&#34;确定在这里做什么。

第一父母

在所有情况下,值得考虑合并,这会使许多提交一次性达到可达性,这对您的任务意味着什么。然而,git reset的一个非常重要的功能是有用的,提供运行git merge的每个人都以恰当的纪律方式这样做。这是第一个父概念。

当我们在上面创建了提交H的合并时,我们在名为git merge的分支上(名称master包含HEAD,而ref: refs/heads/master表示{{ 1}})。因此,Git确保提交git status第一个父提交on branch master,并且提交H第二个父提交G - 当时名称H指向的提交。

如果我们使用第一个父概念,我们可以从提交F返回到提交develop 而不用让Git跟H回{{1}也是。然后G返回HF干净利落地前往G;所以我们的反向列表将是C,完全不包括合并的A

要获得此行为,只需将A--B--C--G--H添加到D--E--F--first-parent命令即可​​。但请注意,这取决于:谁进行了合并,引入了提交F,因此整个git rev-list链,必须正确完成。如果用户不小心使用git log 2 ,他们将创建一些调用foxtrot merges,将主线提交作为第二个父级而不是第一个。

2 (在Zathras的声音中)git pull ... is wrong tool. ... Never use this.