这个问题的答案是“有点,但也许你不必”。
要复制提交,请使用git cherry-pick <em>commit</em>。这实质上是告诉 git:“去看看我要求你挑选的提交。无论它做了什么更改,在当前分支上再次进行相同的更改。然后进行新的提交1具有相同的提交消息。”
因此,如果您已经在 master 上进行了提交,而您希望在 newbranch 上进行提交:
$ git checkout newbranch
$ git cherry-pick master
此时,您将在newbranch 上,并且提交将被复制。您现在需要对 master 上的旧提交做一些事情。
如果您尚未在任何地方发布(推送或让其他人拉取)此提交,则只需“回滚”:
$ git checkout master
$ git reset --hard HEAD^ # be careful here! I always run "git status" first
请注意,git reset --hard 将清除所有正在进行的工作。
如果您已经发布了提交,您通常应该将其还原(参见user2699113's answer)。 “还原”很像挑选樱桃,复制提交,不同之处在于它反转更改:当时添加的任何内容现在都将被删除,而删除的任何内容都会被添加回来。 (而且,与cherry-pick 一样,revert 会自动提交,除非你告诉它不要这样做。)
(请注意,您可以通过 SHA-1 ID 指定提交,但如果有名称(分支或标签名称,或其他参考名称),您可以只写名称。)
1除非您使用-n 或--no-commit 来抑制它,否则git 无法进行相同的更改并告诉您存在冲突并让您自己修复.
“也许你不必”部分是怎么回事?
这是关于 git 及其提交和分支的事情。 Git 与大多数其他版本控制系统不同,当您在“分支上”进行提交时,分支 name 实际上并不是提交的一部分。2您分配分支名称(或标签,无论您想如何称呼它们),但实际的分支结构——“结构分支”——是由每个提交的历史形成。
提交时,该提交记录其父提交。分支名称只是标签,它为您(和 git)提供了一个起点,然后 git 遵循提交父 ID。这意味着我们可以手动绘制提交图(有向无环图或提交的 DAG):
A <-- B <-- C <-- master
^
\
D <-- branch
这里commit D是分支branch的尖端,它指向commit C作为它的父级。提交C 指向B,它指向A,这是初始(根,无父)提交。
当你“在分支主”上并创建一个新分支时,只会创建一个新的 标签,它指向你现在所在的同一个地方。所以假设你之前有A 到C(这次我不会用那么多箭头来画这些;假设箭头通常指向左):
A - B - C <-- HEAD=master
这次我添加了HEAD= 来展示 git 如何跟踪你“在”哪个分支。 (.git 目录中的 HEAD 文件只包含分支的名称:cat .git/HEAD,您通常会看到 ref: refs/heads/master 或其他任何内容。)如果您现在执行 git checkout -b branch,您会得到:
A - B - C <-- master, HEAD=branch
(现在.git/HEAD 包含branch 而不是master)。
当您进行新提交(任何新提交,包括来自cherry-pick 和revert 的提交)时,git 会查看HEAD 以查看您所在的分支,以及该分支的SHA-1 ID。这就是“树枝的尖端”。然后 Git 进行一个新的提交,其父是旧的分支提示,并将新的提交 ID 写入分支文件,以便分支名称指向新的提交。 (当然HEAD 仍然只有分支名称。)
所以,如果你“在分支 branch”上并进行新的提交 D,这就是你得到的:
A - B - C <-- master
\
D <-- HEAD=branch
如果你搞砸了一点,并且“在分支master”上,你会得到这个:
A - B - C <-- branch
\
D <-- HEAD=master
但是等等,这正是你想要的,除了分支标签。您需要做的就是将名称 master 指向 branch 所在的位置,并将 branch 指向 master 所在的位置,并切换 HEAD 使其显示为 branch 而不是 master。
事实证明,这很容易……嗯,相当容易。 :-) 你需要一个临时名称,因为 git 不会让你有两个同名的分支。首先,将分支重命名为branch,让您更改master。然后将master重命名为branch,并将branch的新名称重命名为master:
$ git branch -m branch temp
$ git branch -m master branch
$ git branch -m temp master
第一步将标签branch 更改为temp。第二个将master 更改为branch(你还在上面,所以也重写HEAD)。最后一个将temp 更改为master,为您提供最初想要的提交 DAG 和标签设置。
与其他部分一样,您应该只在未发布(未推送)提交上尝试这种“标签交换”。 (这样做的原因很简单:当您发布提交时,您通过 name-to-commit-ID 映射来完成。如果您将标签打乱并“重新发布”,则提交 ID 会没有改变,但名称到 ID 的映射会改变,所以任何拿起你之前广告的人,例如 master 意味着 bb71dde...,现在得到一个与旧映射不“快进相关”的新映射. 人们和 git 期望分支映射会改变,但通常只是“快进风格”。)
2例如,在 Mercurial 中,分支名称实际上记录在 commit-data 中。