如何探索分支的历史?

时间:2019-01-20 08:49:05

标签: git

我想知道开发人员对分支所做的确切更改。

考虑到该分支几次合并到master中,是否有办法告诉开发人员整个历史的变化?

我想添加一下,有没有办法看到不包括合并在内的更改,因此我可以专注于开发人员专门工作吗?

2 个答案:

答案 0 :(得分:2)

获取特定开发人员所做的更改列表的最简单方法是使用git log --author,例如:

git log --author=fred@example.com

如果您使用gitk之类的GUI工具或类似工具,它们都具有按作者姓名/电子邮件搜索提交的选项。

答案 1 :(得分:2)

您可以使用Greg Hewgill's answer,即使用--author选择器选择特定的提交。您可能还需要添加--no-merges。这将只选择由该特定作者姓名进行的提交,并且使用--no-merges,也排除合并提交,即使它们也是由该作者进行的,也不完全相同根据您的要求,但可能是您可以实际得到的,这取决于人们对存储库的重视程度。

但是,使用git log --first-parent可能会做得更好。同样,这取决于人们在存储库中进行提交时需要多加注意。

要做的第一件事是放开分支由其名称定义的概念,因为在非常重要的方式下,分支不是由它定义的!

在Git中,提交是历史记录。每个提交都由一个大的丑陋的哈希ID标识,例如0d0ac3826a3bbb9247e39e12623bbcfdd722f24c(这是Git仓库中Git本身的实际提交)。 Git使用该ID来定位内部数据结构,这些结构构成了冻结的快照以及该提交的元数据(作者名称,日志消息等)。在元数据中,Git存储提交的 parent 提交的哈希ID-当您(或提交提交的人)进行提交时,该提交即为活动提交。因此,通过从一个提交开始,例如使用哈希H(代表巨大的丑陋哈希ID)并向后工作,Git可以从H到其父G ,然后从G返回其父对象F ,依此类推:

... <-F <-G <-H

masterdevelop这样的分支名称仅用于标识一个特定的提交,Git将其称为分支的 tip :< / p>

... <-F <-G <-H   <--master

换句话说,分支名称保存您要称为“分支的一部分”的 last 提交的实际哈希ID。这样,Git可以从末尾开始,然后向后工作。

很显然,如果在末尾添加 new 提交,则该“最后提交”指针必须指向 move ,这正是向其中添加提交的含义。分店。 Git将打包源快照,添加您的姓名和电子邮件地址以及时间,添加您的日志消息,保存 current (分支提示)提交的哈希ID,并写出新的提交:

...--F--G--H   <-- master
            \
             I

写出提交会产生新提交的哈希ID,因此Git现在有了一个独特的丑陋字母和数字字符串,可以找到提交I

作为最后的动作,git commit现在将新提交I的哈希ID写入名称master中,现在您拥有:

...--G--H--I   <-- master

进行任何提交后,有关该提交的所有内容都将被永久冻结,包括其向后指向的内部箭头:I永久指向H。 (没有什么可以改变的:至多,您可以制作经过改进的I2I'指向H,然后切换您的master指向新的改进版本,只要您在其他人开始使用I之前就这样做,那是相当安全的,例如git commit --amend就是这样做的。这也意味着我们不需要将内部箭头绘制为箭头,我们只需要记住它们只是让Git向后移动。)

现在,合并提交有一些特别之处。我们来看一个只有三个提交的存储库:

A--B--C   <-- master

现在让我们建立一个新分支develop。为此,Git只需添加一个新的名称,指向一些现有提交。通常,这就是我们现在要解决的问题,即提交C

A--B--C   <-- master, develop

现在我们有一个明显的问题:当我们创建一个新的提交时,如何知道对 update 的提交?答案是我们将特殊名称HEAD附加到以下两个分支之一:

A--B--C   <-- master, develop (HEAD)

这告诉Git(和我们)我们在develop而不是master上。我们仍然以任何一种方式使用提交C,但是现在当我们进行新提交D时,它将仅移动名称develop

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

即使分支本身移动,名称HEAD仍附加在分支上。

我们现在可以向develop添加一些提交,也许(也可以不)添加git checkout master(返回到提交C),并在那里也添加一些提交。让我们都做:

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

现在让我们运行git merge develop。合并的目的是合并工作:采用我们在master上所做的工作-无论CH之间有什么不同-不论它们是什么(实际上是“我们”)在develop上执行过,即,从CF的任何不同。不用过多担心如何 Git将这两组更改组合在一起,很显然这是一种方法:我们从C开始,然后将其更改为{{1} },他们从H开始,然后将其更改为C,所以我们所做的任何更改以及他们所做的更改,都与这个共同的起点有所不同。

(共同的起点称为合并基础,这对于理解如何合并的工作至关重要。我们跳过了如何现在,但是您一定要记住寻找合并基础的想法,如果您绘制像这样的简单图形,那么用于合并基础的提交就很明显了。在实际的存储库中,图形变得复杂并且通常不是那么容易可以看到,但是Git会为您找到的。)

无论如何,假设合并工作正常,Git最终自己进行新的提交,合并后的代码作为新提交的快照。此合并提交没有一个父母,但有两个父母:

F

重要的是(不是我可以绘制的东西),新合并提交A--B--C--G--H---I <-- master (HEAD) \ / D--E--F <-- develop 第一个父级是我们之前的提交,即提交I。新合并提交H第二父级是我们告诉Git进行合并的提交,即提交I

假设F的开发者现在继续在develop中进行更多提交,而我们在develop上的I上坐在这里:

master

我们现在可以使用A--B--C--G--H---I <-- master (HEAD) \ / D--E--F--J--K--L <-- develop 再次合并。这次,git merge develop返回到I H的事实意味着共同的起点是提交F,所以我们只是去接他们已经完成的 new 工作:

F

无论他们在A--B--C--G--H---I--------M <-- master (HEAD) \ / / D--E--F--J--K--L <-- develop 上进行了多少工作,无论他们进行了多少工作,只要我们确保合并如此进行,此模式就会重复。我们只用develop进行了合并,而没有采取任何特殊的措施,但是我们在git merge的提交中着手master的工作,而他们从来没有做过G--H放入他们的 git merge master

但是,假设此时develop上的任何人都这样做:

develop

您可能希望这会在git checkout develop git merge master 上进行新的合并提交,但不会!相反,Git会注意到develop(即,提交develop)紧随提交L之后,即M上的合并提交。

因此,

Git将执行所谓的快进合并,这根本不是合并。实际上,它只是一个master,将分支名称向前拖动。在这种情况下,它使git checkoutdevelop都指向提交master

M

如果正在A--B--C--G--H---I--------M <-- master, develop (HEAD) \ / / D--E--F--J--K--L 上工作的任何人 now 进行了一些新的提交,他们都会像这样,第一个指向develop作为其(第一个也是唯一的)父对象:

M

如果从事A--B--C--G--H---I--------M <-- master \ / / \ D--E--F--J--K--L N--O <-- develop (HEAD) 工作的人都特别注意,他们会运行develop而不是git merge --no-ff master,这会做到这一点:

git merge master

在这里,A--B--C--G--H---I--------M <-- master \ / / \ D--E--F--J--K--L---N <-- develop (HEAD) 是一个双父级 merge 提交,其第一个父级为N,第二个父级为merge { L上的{1}}。然后,对M的新提交将按照我们想要的模式继续进行:

master

请注意,至关重要的是,管理develop的人也必须使用A--B--C--G--H---I--------M <-- master \ / / \ D--E--F--J--K--L---N--O--P <-- develop (HEAD) 将提交master合并回git merge --no-ffP真正合并的提交将导致:

master

,其中--no-ff第一父级是A--B--C--G--H---I--------M---------Q <-- master (HEAD) \ / / \ / D--E--F--J--K--L---N--O--P <-- develop ,而 second 的父级是Q。如果没有M,Git只会签出 P并使两个分支名称都指向--no-ff,而不是进行真正的合并以保持所需的“第一”。父”属性。

无论如何,如果在存储库中进行合并的任何人都采取了适当的措施以确保遵守该第一父级属性,则只需找到 last 提交,曾经在P上进行过使用,并使用P,也许还与develop一起使用,以跳过合并提交。

请注意,所有这些git log --first-parent命令共享的是它们仅向您显示一些提交。 --no-merges标志告诉git log向我展示其作者与我所说的相符的提交。 --author标志告诉git log Don'完全不显示合并提交。(默认是显示它们,但即使使用--no-merges,也不显示任何差异。)Git仍然查看所有提交可以通过向后工作以及合并提交来查看 all 的父母。 (它仍然一次仅一次显示一次提交,并且在两次或多次提交时同时显示“想要”,这也有点棘手。)

git log标志有些不同。它告诉-p每当您向后执行合并时,仅跟随第一个父级链接。也就是说,如果我们让Git从提交--first-parent开始并向后工作,它将将显示git log,然后显示P。由于P是合并提交,因此如果我们使用O,Git将不会显示,但是它将退回到 both {{1 }} N。从--no-merges开始,Git将退回到 M L。但是,如果我们添加M,则Git 不会带双亲。相反,它将仅将L first 父级添加到要显示的提交列表中。因此它将从I--first-parent —仅跟随原始N的第一父级。然后它将显示N,然后退回到LdevelopL,依此类推。

(Git最终将立即退回到K,我们需要知道停止关注这一点,但是无论我们选择如何查看这些提交,这都会发生。使用{{1 }}可能会使Git不显示J,只要它有不同的作者,但是如果他们选择了作者,我们仍然会看到F和/或C。)

请注意,即使名称--author被删除,该技术也有效。我们只需要找到我们希望Git从其开始的 last 提交,并使用C使B跟随第一个父级链接从那里向后走。但这取决于谁完成了我们所希望的合并。