我有这个回购,origin/X-123
我有一张JIRA票,X-123。我创建了一个名为X-123的分支,对它进行了一些提交,它已被推送到x-example
。然后是从origin / X-123到主分支的pull请求,它已被合并。
这项工作全部完成,但事后才知道,事实证明我所做的工作应该是真的反对Y-789票。理想情况下我想:
现在我只能满足于最后一个;有任何想法吗?由于所有的工作已经合并为主人,所以可能是在马用螺栓固定之后关上了门。我很想恢复我对抗X-123所做的所有工作,然后对Y-789重复一遍,但似乎这里应该有一些简单的步骤(这肯定不是那么不寻常吗?)。
答案 0 :(得分:6)
可能有更好的,特定于Jira的方式来解决这个问题,但是在Git中你可以做到这一点。
重命名分支 - 本地名称 - 很简单:
git branch -m <newname>
当你在它上面时,或者:
git branch -m <oldname> <newname>
然而,对现有提交进行更改是不可能的;并重命名分支没有好处。但绝望不是! : - )
首先,小一点:
我有这个回购,
origin/master
origin/master
不是存储库。它是Git记忆的典型名称:&#34;这是我在另一个Git中看到的,我打电话给origin
,这是我最后一次打电话给其他人Git在互联网电话上询问了它的分支机构。它说它有一个master
提交a234b67...
或者其他什么,所以我在我的存储库中记下origin/master
来记住他们的 master = a234b67...
。&#34;
你的Git使用在其他Git中看到的每个分支执行此操作:如果他们有一个名为X-123
的分支,则会得到一个名为origin/X-123
的副本。这包括当你的Git要求他们的Git 创建 X-123
时。一旦他们说&#34; OK&#34;,你的Git知道他们的 Git现在有这个X-123
(并且它的哈希ID就是你的Git告诉他们使用的创造它!)。
其次,我们只需注意,您的存储库中的分支名称,如master
或X-123
,是一个缩写形式的全名, Git调用引用。以refs/heads/
开头的任何全名都是分支,因此您的master
确实是refs/heads/master
而您的X-123
确实是refs/heads/X-123
这些所谓的远程跟踪分支,如origin/master
和origin/X-123
,是全名以refs/remotes/
开头的引用,并继续包含{{ 1}}部分。您的Git只需重命名外国Git的分支名称(origin/
)即可创建远程跟踪名称(refs/heads/master
)。
(就此而言,标签名称只是refs/remotes/origin/master
中的引用。)
稍后,您的Git会删除refs/tags/
或refs/heads/
部分以进行显示。这就是为什么你通常不会看到这些前缀的原因。有时候,无论出于何种原因,Git只会剥离refs/remotes/
而你会看到refs/
。在少数情况下,知道全名很重要。他们可能不会来这里,但值得了解。
是否将本地分支从remotes/origin/master
(refs / heads / X-123)重命名为X-123
(refs / heads / Y-789),名称本身只是映射到一些提交哈希:Y-789
或其他什么。更改名称会在新名称中保留相同的提交哈希。您现在必须进行除合并之外的所有提交的副本。
复制提交的主要工具是ac0ffee4deadcafe...
。我们假设您创建了一个 new 分支git cherry-pick
,其中没有旧提交的副本,也没有合并。 (我假设您的开发根据您的描述从Y-789
分支出来,但如果没有,请在此处使用其他名称。)让我们将其绘制为提交图片段:
master
圆...--o--*-----m------M <-- master, origin/master
\ /
A--B--C--D <-- X-123, origin/X-123
表示普通提交。其中一个o
已填写以标记它:合并基础之间的*
之间的回复当它指向提交master
时,仍然是m
和X-123
的提示。另一个origin/X-123
有一封信,表示在合并m
之前它是master
的位置,最后一个X-123
被标记为显示它是M
和master
指向的合并提交。最后,其中四个标签origin/master
,以便我们可以将它们称为我们计划复制的原件。
我们现在想要的是进行A-B-C-D
系列提交,这些提交将是Y-789
系列的副本。所以我们制作了一个新标签Y-789,指向合并基础:
A-B-C-D
正如...--o--* <-- Y-789 (HEAD)
|\
| ----m------M <-- master, origin/master
\ /
A--B--C--D <-- X-123, origin/X-123
所示,我们希望现在 on 这个新分支。
接下来,我们需要复制 (HEAD)
但是,在副本中,稍微更改其提交消息,以便A
代替X-123 blah
Y-789 blah
。要做到这一点:
git cherry-pick --edit <hash ID of A>
这会更新我们的索引和工作树,以复制A
中已更改的内容并设置提交消息模板,而不实际进行提交,然后以允许我们的方式调用git commit
编辑邮件。 (没有--edit
,这会进行提交。然后我们可以git commit --amend
来修复它的消息,但这会导致一个&#34;一次性&#34;提交,因此效率不高 - 虽然我们可能并不在乎;&#34;垃圾&#34;提交在Git中很便宜;请继续阅读以了解自动化的方法。
然后我们重复其余的提交。这构建了一个新的链:
...--o--*--A'-B'-C'-D' <-- Y-789 (HEAD)
|\
| ----m------M <-- master, origin/master
\ /
A--B--C--D <-- X-123, origin/X-123
我们现在可以制作一个新的拉取请求,要求合并D'
(Y-789,我们推送,以便现在有一个origin/Y-789
)到master
。 对于知道如何的人(请参阅下面的详细信息),合并将非常简单,因为master
已经有相同的更改,礼貌合并D
;但如果这个合并是作为严格的&#34;添加合并提交&#34;完成的,结果将如下所示:
...--o--*--A'-B'-C'---D' <-- Y-789, origin/Y-789
|\ \
| ----m------M--N <-- master, origin/master
\ /
A--B--C--D <-- X-123, origin/X-123
其中N
是新的合并提交。
您现在可以安全地删除X-123
标签(并在其他存储库中执行此操作,以便git fetch --prune
删除您的refs/remotes/origin/X-123
),但所有这些原始提交都将保留在图,您将永久地提交A-B-C-D
次。
要摆脱原件更难,因为合并提交M
存在。 可以完成,但所有人谁拥有此合并M
- 那是你,而另一个Git在你的origin
,并且已经选择合并提交的任何其他Git用户M
- 已经从它们的每个存储库中删除了这个提交。
如果您这样做,然后将D'
合并到master
,则会获得此图表:
...--o--*--A'-B'-C'-D' <-- Y-789, origin/Y-789
|\ \
| ----m-------N <-- master, origin/master
\
A--B--C--D <-- X-123, origin/X-123
这张图可以重新绘制得更不凌乱,事实上,除了使用Y-789
之外,它看起来就像原始图。
如果现在每个人都删除了名称 X-123
和origin/X-123
,Git最终将垃圾收集提交A-B-C-D
。在此之前,没有人会正常看到它们:它们只是僵尸提交,潜伏在每个存储库副本中以防有人想要复活它们。
git cherry-pick
复制要自动执行花哨的复制,您只需使用git rebase
即可。 git rebase
所做的是识别一组提交 - 在我们的案例中,A-B-C-D
- 以及复制它们。当您将其作为git rebase -i
运行时,它首先会针对一系列pick
命令启动编辑器会话,每个命令代表一个git cherry-pick
。您可以将每个pick
更改为edit
- 这是允许您运行git commit --amend
- 或reword
的Big Hammer,它可以让您编辑提交消息,就像我们一样在上面做了。
完成所有樱桃采摘后,git rebase
移动分支标签。也就是说,Git不是启动新的分支Y-789
,而是在临时(未命名)分支上进行所有新提交,然后将现有的当前分支名称移动到指向副本。
换句话说,我们只会运行:
git checkout X-123
git rebase -i --onto $(git merge-base X-123 master) master
告诉Git从X-123
开始,在X-123
上查找(用于复制目的)提交不在master
上的提交,然后在merge base {{}之后复制这些提交。 1}},同时也允许我们将*
更改为pick
。一旦完成,Git将移动名称reword
以指向最终副本:
X-123
请注意...--o--*--A'-B'-C'-D' <-- X-123 (HEAD)
|\
| ----m------M <-- master, origin/master
\ /
A--B--C--D <-- origin/X-123
已移动。我们现在要将其重命名为X-123
,然后将Y-789
重命名为另一个Git上的git push -u origin Y-789
(以及我们的Y-789
,并将其设置为我们的新上游)。我们不必删除现有的origin/Y-789
,也不会删除我们的X-123
,我们只需要说服上游存储库删除他的 {{1然后像往常一样使用origin/X-123
。
然后我们将提出拉取请求,并让控制合并的任何人处理它。
X-123
合并git fetch --prune
是否容易或困难取决于以下事项:
D'
合并到D'
?D
? (请注意,这对每个人都有影响&#34;下游&#34;,包括你:你必须重复这个剥离。这可能很容易,或者可能很难。)master
?M
并且存在冲突,那么执行此操作的人是否知道如何模拟 git merge -s ours
?如果没有合并冲突并且不需要手动调整,一切都非常简单。只需根据需要保留或删除M
(要删除它,我们需要git merge -s ours
,我们需要确保 M
之后没有提交,并且然后有所有下游问题,所以要非常肯定这是你想要的)。然后合并,你就完成了。
如果有 合并冲突,但你可以git reset --hard
而不剥离M
,那很简单:只需要git merge -s ours
说&#34 ;我们已经正确地进行了合并,因此在进行合并结果时完全忽略M
链上的所有提交。
如果合并冲突和,合并的人正在剥离git merge -s ours
,那就更难了。一般的想法是从A'-B'-C'-D'
中删除M
但保留提交及其树(例如,使名称指向它,或依赖于reflogs,只需复制粘贴哈希,或其他) 。然后运行M
,这将看到相同的合并冲突;但随后删除合并结果并将其替换为保存的提交中的树。或者,等效地,可以使用master
管道命令以更少的麻烦来做这件事,但这对Git arcana非常深入。
请注意,所有这些都假定您不对树进行任何更改,即原始git merge
提交的git commit-tree
和新的git diff
提交将为空:只有消息(和散列ID)发生了变化,而不是与提交相关的源树。