我如何结账到Git分支的某个时间点(即提交)?

时间:2012-01-30 15:43:44

标签: git version-control git-branch git-checkout

我想将我的Git分支回滚到特定的提交。所以我运行git log并找到提交SHA哈希,然后运行git checkout <myhash>

这通常效果很好,但这次有点可疑。当我再次查看git log时,我发现最新的提交是正确的,但是我错过了许多进一步的提交。它告诉我:这个提交是来自另一个分支(another)的合并的一部分,我在合并之前看到了该分支的历史。

master's log, pre-merge:        D-C-----B'-B-A-----F
another's log:              7-6-----5-4--------3-2-F
master's log, post-merge: M-7-6-D-C-5-4-B'-B-A-3-2-F

我结帐提交5并从another获取历史记录中的提交:

5-4-3-2-F

但是我想在master合并后的历史记录中提交:

5-4-B'-B-A-3-2-F

我已经a repo在哪里测试了这个:

$ git clone git://github.com/henrik242/Git-Branch-Test.git
$ cd Git-Branch-Test
$ git checkout -t origin/another
$ git log  ## The commits are named "test [2-7]"
$ git checkout master
$ git log  ## master's original commits are named "test [A-D]".  
$ git checkout 68c1226a0c  ## test 5 
$ git log  ## We now have the commits in the history from the "another" branch,
           ## even though this commit exists in the "master" branch as well

我几乎可以使用git rebase -i来做我想要的事情:

$ git branch back-in-time
$ git checkout back-in-time
$ git rebase -i 18b1a648bc  ## the SHA1 of 'test 4', the commit before 'test 5'

编辑器产生:删除不需要的提交。保存并退出,git log现在显示所需的提交:

5-4-B'-B-A-3-2-F

问题是git rebase -i没有以与git log相同的顺序显示提交,这使得很难选择正确的提交。

3 个答案:

答案 0 :(得分:1)

Merge不会混合提交,它只是加入分支,如@Jefromi的回答所示。因此,当然,您可以在任何给定时间恢复树的状态,但如果时间在合并之前,则需要注意选择正确的分支。所以,我猜你应该使用git log --oneline --graph,这样你就可以在给定的时间在相应的分支上选择最新的提交。当您检查提交时,显然只会在给定时间具有该分支的提交历史记录。

如果你想真正混合来自两个分支的提交,你需要一个使用git cherry-pickgit rebase -i的工作流来按时间顺序将两个分支的提交命令为直接分支。这需要各种各样的问题,最少的是很多合并冲突。此外,中间状态很可能在语义上不一致。

答案 1 :(得分:0)

我不能说这么多次:你所要求的历史从未存在过。如果你想手动创建它,但没有任何东西可供你查看。合并不会以任何方式混合在一起形成某种合并的历史。如果您的问题是“我如何手动创建这样的历史记录”,那么请问。但你似乎已经知道如何使用rebase。

我认为你的主要误解可能是git-log的输出。 git log显示的命令提交本质上是一个任意选择。历史不是线性的,但它必须逐个列出它们,所以它确实如此。该顺序并不意味着一个提交是前一个提交的父级,因此并不意味着如果您签出其中一个提交,那么您将拥有在其下方打印的所有内容的并集。它可能会打印您提交的提交列表,但在您的示例存储库中,这就是历史记录的样子。您可以使用gitk --allgit log --graph来查看。

1 - 2 - 3 - 4 - 5 - 6 - 7 (another-branch)
 \                       \
  A - B - C - D - --------M (master)

就是这样。合并并不会将这些分支混合在一起。它创建一个提交,其父项是合并提交,包含合并提交内容的组合。所以你可以检查提交2,或提交B,但没有“主分支中的提交2”。如果你愿意,你可以查看提交B并合并提交2,看看你得到了什么。

据我所知,你想要的提交列表是你通过合并提交5和B以及以任意方式展平它而获得的。如果您只想要合并的结果:

git checkout -b foo <commit-5>
git merge <commit-B>

如果你真的想要这样平坦的历史:

git checkout -b foo <commit-5>
git rebase -i <commit-B>
# reorder them however it is you wanted them

但这些都是用户决定。您正在创建从未存在的存储库状态。由于它从未存在过,你必须创建它。


对于评论中关于哈希的讨论,SHA1再次唯一地标识了提交。提交2的SHA1仅为该提交的SHA1。你没有证明你的测试报告是错的,你已经证明我是正确的:那里只有一个提交2,它只有一个哈希。

提交是一个包含以下内容的对象:元数据(作者/提交者名称,电子邮件,日期;提交消息),对其父项的引用以及它所代表的树(将其视为快照内容)。因此,commit 2表示该特定快照。没有更多,没有更少。其父母是提交1.这不是时间戳问题;提交通过SHA1知道它的父母。无论如何,提交2的父级都是提交1。只有一个提交2,无论是从主分支还是其他分支到达 - 查看图片,您可以通过7,6,5,4,3看到它在主分支中的位置。

答案 2 :(得分:0)

我将回答我自己的问题:为了让这个工作流程起作用(并且有点意义),我必须使用git log的拓扑排序:git log --topo-order。这样日志看起来像这样:

master's log, pre-merge:                D-C-B'-B-A-F
another's log:              7-6-5-4-3-2------------F
master's log, post-merge: M-7-6-5-4-3-2-D-C-B'-B-A-F

现在git rebase -i中的顺序是有意义的,因为它也是拓扑顺序。只需剪掉底部提交,直到你遇到有问题的提交并保存,然后瞧!

(作为旁注,我首先需要这个工作流程,因为我试图确定哪个提交引入了一个错误。之后我意识到git bisect在这方面比我做得更好。)