回溯重命名分支和重写提交

时间:2017-05-02 15:48:50

标签: git jira bitbucket-server

我有这个回购,origin/X-123

我有一张JIRA票,X-123。我创建了一个名为X-123的分支,对它进行了一些提交,它已被推送到x-example。然后是从origin / X-123到主分支的pull请求,它已被合并。

这项工作全部完成,但事后才知道,事实证明我所做的工作应该是真的反对Y-789票。理想情况下我想:

  • 将我的提交从“X-123 blah”改为“Y-789 blah”
  • 将我的分支从X-123重命名为Y-789
  • 确保拉取请求链接到JIRA票证Y-789而不是X-123

现在我只能满足于最后一个;有任何想法吗?由于所有的工作已经合并为主人,所以可能是在马用螺栓固定之后关上了门。我很想恢复我对抗X-123所做的所有工作,然后对Y-789重复一遍,但似乎这里应该有一些简单的步骤(这肯定不是那么不寻常吗?)。

1 个答案:

答案 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告诉他们使用的创造它!)。

其次,我们只需注意,您的存储库中的分支名称,如masterX-123,是一个缩写形式的全名, Git调用引用。以refs/heads/开头的任何全名都是分支,因此您的master确实是refs/heads/master而您的X-123确实是refs/heads/X-123

这些所谓的远程跟踪分支,如origin/masterorigin/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时,仍然是mX-123的提示。另一个origin/X-123有一封信,表示在合并m之前它是master的位置,最后一个X-123被标记为显示它是Mmaster指向的合并提交。最后,其中四个标签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-123origin/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)发生了变化,而不是与提交相关的源树。