如何签出到 Git 分支的某个时间点(即提交)?
我想将我的 Git 分支回滚到特定的提交。因此,我运行 git log 并找到提交 SHA 哈希值,然后运行 git checkout
这通常工作得很好,但这次有些可疑。当我再次查看 git log 时,我发现最新的提交是正确的,但我错过了很多下面的提交。我突然意识到:此提交是另一个分支(另一个
)合并的一部分,并且我看到了该分支的历史记录(预合并)。
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
的提交,post-合并:
5-4-B'-B-A-3-2-F
我已经制作了 一个存储库,您可以在其中测试它:
$ 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
,这使得很难选择正确的提交。
I want to roll my Git branch back to a specific commit. So I run git log
and find the commit SHA hash, and run git checkout <myhash>
.
This usually works just fine, but this time something was fishy. As I looked at git log
again, I see that the latest commit is correct, but I miss a lot of commits further down. It dawns on me: this commit is part of a merge from another branch (another
), and I see the history from that branch, pre-merge.
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
I checkout commit 5
and get the commits in the history from another
:
5-4-3-2-F
But I'd like the commits in the history from master
, post-merge:
5-4-B'-B-A-3-2-F
I've made a repo where you can test this:
$ 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
I can almost use git rebase -i
to do what I want:
$ git branch back-in-time
$ git checkout back-in-time
$ git rebase -i 18b1a648bc ## the SHA1 of 'test 4', the commit before 'test 5'
An editor spawns: Remove the unwanted commits. Save and exit, and git log
now shows the wanted commits:
5-4-B'-B-A-3-2-F
The problem is that git rebase -i
doesn't show the commits in the same order as git log
, which makes it difficult to pick the right commits.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
合并不会混合提交,它只是加入 @Jefromi 的答案中描述的分支。因此,当然,您可以在任何给定时间恢复树的状态,但如果时间是在合并之前,您将需要小心选择正确的分支。所以,我想你应该使用 git log --oneline --graph ,这样你就可以在给定时间选择相应分支上的最新提交。当您检查该提交时,它显然只会有给定时间该分支的提交历史记录。
如果您想真正混合来自两个分支的提交,则需要一个使用 gitcherry-pick 或 git rebase -i 来对两个分支的提交进行排序的工作流程按时间顺序分支成直分支。这会带来各种各样的问题,至少是大量的合并冲突。此外,中间状态很可能在语义上不一致。
Merge does not intermingle commits, it just joins the branches as depicted in @Jefromi 's answer. So, of course, you can restore the state of the tree at any given time but if the time is prior to the merge, you will need to take care to pick the right branch. So, I guess you should use
git log --oneline --graph
so you can pick the latest commit on the respective branch at the given time. When you check that commit out, it will obviously only have the commit history of that branch at the given time.If you wanted to really intermingle commits from both branches, you'd need a work-flow that uses
git cherry-pick
orgit rebase -i
to order the commits from both branches chronologically into a straight branch. This calls for all kinds of problems, the least being a lot of merge conflicts. Also, the intermediate states will be most likely semantically inconsistent.我不能说太多次:你所要求的历史从未存在过。如果您想手动创建它,但根本没有类似的东西可供您查看。合并不会以任何方式将提交混合在一起形成某种合并的历史。如果您的问题是“我如何手动创建这样的历史记录”,那么就问这个问题。但你似乎已经知道如何使用 rebase 了。
我认为您的主要误解可能是 git-log 的输出。 git log 显示的提交顺序本质上是任意选择。历史不是线性的,但它必须将它们一一列出,所以它确实如此。该顺序并不意味着一个提交是前一个提交的父级,因此这并不意味着如果您检查其中一个提交,您将在其下方打印所有内容的并集。它可能会打印提交列表,就像您在问题中放入的那样,但在您的示例存储库中,这就是历史记录的样子。您可以使用 gitk --all 或 git log --graph 来查看它。
就是这样。合并不会神奇地将这些分支混在一起。它创建一个单个提交,其父级是合并提交,包含合并提交内容的组合。所以你可以检查提交2,或者提交B,但是没有“master分支中的提交2”。如果您愿意,您可以查看提交 B 并合并提交 2,看看会得到什么。
据我所知,你所说的你想要的提交列表就是通过合并提交 5 和 B 并以任意方式将其展平而得到的。如果您只想要合并的结果:
如果您实际上想要这样的平坦历史记录:
但这些都是用户决定。您正在创建一个从未存在过的存储库状态。因为它从未存在过,所以你必须创建它。
至于评论中关于哈希的讨论,再次强调,SHA1 唯一标识一次提交。提交 2 的 SHA1 仅适用于该提交。你没有证明我的测试仓库是错的,你已经证明我是对的:那里只有一个提交 2,并且它只有一个哈希值。
提交是一个包含以下内容的对象:元数据(作者/提交者姓名、电子邮件、日期;提交消息)、对其父级的引用以及它所代表的树(将其视为内容的快照) 。因此提交 2 代表该特定快照。不多不少。它的父级是提交 1。这不是时间戳问题;而是提交 1。提交通过 SHA1 知道其父项。无论如何,提交 2 的父级都是提交 1。只有一个提交 2,无论你是从 master 分支还是其他分支到达它 - 看图,你可以看到它在 master 分支中的情况,通过 7、6、5、4、3。
I can't say this enough times: the history you're asking for never existed. If you want to manually create it, but there is nothing at all like that for you to check out. A merge does not in any way mash together commits into some kind of amalgamated history. If your question is "how do I manually create history like this" then ask that. But you seem to already know how to use rebase.
I think your primary misunderstanding may be of git-log's output. The order commits are shown by git log is essentially an arbitrary choice. History isn't linear, but it has to list them one by one, so it does. That order does not mean one commit is the previous one's parent, and therefore it does not mean that if you check out one of those commits, you'll have the union of everything printed below it. It may print lists of commits like you've put in your question, but in your example repository, this is what the history looks like. You can see it with
gitk --all
orgit log --graph
.That's it. A merge doesn't magically mash those branches together. It creates a single commit, whose parents are the merged commits, containing the combination of the content of the merged commits. So you can check out commit 2, or commit B, but there is no "commit 2 in the master branch". You could, if you like, check out commit B and merge commit 2, and see what you get.
The list of commits you say you want, as far as I can tell, is what you'd get by merging commits 5 and B, and flattening it in an arbitrary way. If you just want the result of the merge:
If you actually want flat history like that:
But these are all user decisions. You're creating a state of the repository that never existed. Since it never existed, you have to create it.
As for the discussion about hashes in the comments, again, an SHA1 uniquely identifies a commit. The SHA1 for commit 2 is the SHA1 for that commit only. You have not proven me wrong with your test repo, you've proven me right: there is only one commit 2 there, and it only has one hash.
A commit is an object containing a few things: metadata (author/committer name, email, date; commit message), a reference to its parent(s), and the tree it represents (think of it as a snapshot of the content). So commit 2 represents that particular snapshot. Nothing more, nothing less. Its parent is commit 1. This is not a matter of timestamps; a commit knows its parents by their SHA1s. Commit 2's parent is commit 1, no matter what. There is only one commit 2, whether you reach it from the master branch or another branch - look at the picture, and you can see how it's in the master branch, via 7, 6, 5, 4, 3.
我只回答我自己的问题:为了让这个工作流程正常工作(并具有某种意义),我必须使用 git log 的拓扑排序:
git log --topo-order
。这样,日志看起来像这样:现在 git rebase -i 中的顺序是有意义的,因为它也是拓扑顺序的。只需剪掉底部的提交,直到到达有问题的提交并保存,瞧!
(顺便说一句,我首先需要这个工作流程,因为我试图查明哪个提交引入了错误。后来我意识到 git bisect 在这方面比我做得更好。)
I'll just answer my own question: In order for this workflow to work (and make some kind of sense), I have to use a topological ordering of the git log:
git log --topo-order
. This way the logs look like this:Now the order in
git rebase -i
makes sense, since it's in topological order as well. Just snip away the bottom commits until you reach the commit in question and save, and voila!(As a side note, I needed this workflow in the first place because I tried to pinpoint which commit introduced a bug. Afterwards I realized
git bisect
does a better job on this than me.)