【问题标题】:Retrospectively rename branch and reword commits回顾性地重命名分支并改写提交
【发布时间】:2017-05-02 15:48:49
【问题描述】:

我有这个 repo,origin/master

我有一张 JIRA 票,X-123。我创建了一个名为 X-123 的分支,并对其进行了一些提交,这些提交已被推送到 origin/X-123。然后从 origin/X-123 向主分支发出拉取请求,该请求被合并。

除了现在事后看来,这项工作已经完成,事实证明我所做的工作真的应该反对票 Y-789。理想情况下,我想:

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

现在我只满足于其中的最后一个;有任何想法吗?既然所有的工作都已经合并到master里面了,说不定就是马跑了就关门了。我很想恢复我对 X-123 所做的所有工作,然后对 Y-789 重复它,但似乎这里应该有一些简单的步骤(这肯定不会那么不寻常吗?)。

【问题讨论】:

    标签: git jira bitbucket-server


    【解决方案1】:

    可能有更好的、特定于 Jira 的方法来处理这个问题,但这里是您可以在 Git 中完成这一切的方法。

    重命名分支(本地名称)很简单:

    git branch -m <newname>
    

    当您使用它时,或者:

    git branch -m <oldname> <newname>
    

    但是,对现有提交进行更改是不可能的;重命名分支没有好处。但不要绝望! :-)

    一些重要的背景

    首先,一个小问题:

    我有这个 repo,origin/master

    origin/master 不是存储库。这是你 Git 记忆的典型名称:“这是我在另一个 Git 中看到的,我称之为 origin,这是我最后一次通过互联网电话呼叫另一个 Git 并询问它的分支。它说它有一个 master 是提交 a234b67... 或其他什么,所以我在 my 存储库中写下了 origin/master 以记住 他们的 master = a234b67...。"

    你的 Git 对它在另一个 Git 中看到的 每个 分支执行此操作:如果他们有一个名为 X-123 的分支,你会得到一个名为 origin/X-123 的副本。这包括当您的 Git 要求他们的 Git创建X-123 时。一旦他们说“OK”,你的 Git 就知道他们的 Git 现在有这个 X-123(并且它的哈希 ID 就是你的 Git 告诉他们用来创建它的任何东西!)。 p>

    其次,让我们在这里注意一下,您的存储库中的 branch 名称,例如 masterX-123 是 Git 将其称为 reference。任何以refs/heads/ 开头的全名都是一个分支,所以你的master 真的是refs/heads/master 而你的X-123 真的是refs/heads/X-123

    这些所谓的远程跟踪分支origin/masterorigin/X-123 是全名以refs/remotes/ 开头并继续包含origin/ 部分的引用。您的 Git 只是将外部 Git 的分支名称 (refs/heads/master) 重命名为您的远程跟踪名称 (refs/remotes/origin/master)。

    (就此而言,标签名称只是refs/tags/ 中的引用。)

    稍后,您的 Git 会删除 refs/heads/refs/remotes/ 部分以用于显示目的。这就是为什么您通常看不到这些前缀的原因。有时,无论出于何种原因,Git 只会删除 refs/,而您会看到 remotes/origin/master。在少数情况下,知道全名很重要。他们可能不会出现在这里,但值得知道。

    你不能修改提交,但你可以复制它们

    无论您是否将本地分支从 X-123 (refs/heads/X-123) 重命名为 Y-789 (refs/heads/Y-789),名称本身都只是映射一些提交哈希:ac0ffee4deadcafe... 或其他。更改名称会在新名称中留下相同的提交哈希。您现在必须对除合并之外的所有提交进行副本

    一次复制一个提交的主要工具是git cherry-pick。假设您创建了一个 new 分支 Y-789,其中 没有 个旧提交的副本,也没有合并。 (根据您的描述,我假设您的开发从 master 分支出来,但如果不是,请在此处使用其他名称。)让我们将其绘制为提交图片段:

    ...--o--*-----m------M   <-- master, origin/master
             \          /
              A--B--C--D   <-- X-123, origin/X-123
    

    一轮o 代表一个普通的提交。其中一个,*,被填充以标记它:这是 master 之前的 之间的 合并基础,当它指向提交 m 时,而 X-123origin/X-123 的小费仍然是什么。另一个m 有一个字母表明它是masterX-123 被合并之前所在的位置,最后一个M 被标记以表明它是合并提交,@ 987654369@ 和origin/master 点。最后,其中四个带有标签A-B-C-D,以便我们可以将它们称为我们计划复制的原件。

    我们现在想要的是进行Y-789 系列的提交,这将是A-B-C-D 系列的副本。所以我们创建了一个新标签,Y-789,指向合并基:

    ...--o--*   <-- Y-789 (HEAD)
            |\
            | ----m------M   <-- master, origin/master
             \          /
              A--B--C--D   <-- X-123, origin/X-123
    

    正如(HEAD) 所示,我们现在希望这个新分支上。

    接下来,我们需要复制 A,但是,在复制中,稍微更改它的提交信息,这样X-123 blah 就不是Y-789 blah了。为此:

    git cherry-pick --edit <hash ID of A>
    

    这会更新我们的索引和工作树以复制在 A 中更改的任何内容并设置提交消息模板,但尚未实际进行提交,然后以允许我们编辑消息的方式调用 git commit。 (如果没有--edit,这将构成提交。然后我们可以git commit --amend 它来修复它的消息,但这会导致一次“一次性”提交,因此效率不高——尽管我们可能并不在意;“垃圾”提交是在 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 已经在其中进行了 相同 更改,感谢合并 @ 987654389@;但如果此合并作为严格的“添加合并提交”完成,结果将如下所示:

    ...--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),但所有这些原始提交将保留在 graph,您将永远拥有A-B-C-D 提交。

    摆脱原件更难,因为合并提交M 存在。 可以完成,但是 每个人都有这个合并 M——那就是你,你的 origin 的另一个 Git,以及 任何其他 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之外,它看起来就像原始图。

    如果现在每个人都删除了他们的 names X-123origin/X-123,Git 最终将 garbage collect 提交 A-B-C-D。在那之前,也没有人会看到它们:它们只是僵尸提交,潜伏在每个存储库副本中,以防有人想要复活它们。

    自动化git cherry-pick 复制

    要自动执行精美的复制,您只需使用git rebasegit rebase 所做的是识别一组提交——在我们的例子中,A-B-C-D——并复制它们。当您以git rebase -i 运行它时,它首先会针对一系列pick 命令打开一个编辑器会话,每个命令代表一个git cherry-pick。您可以将每个pick 更改为edit——这是让你运行git commit --amend 的大锤——或reword,它只允许你编辑提交消息,就像我们在上面所做的那样。

    完成所有的樱桃采摘后,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 上的提交,然后将这些提交复制到合并基础 * 之后,同时也让我们将pick 更改为reword。全部完成后,Git 会将名称 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 push -u origin Y-789 在另一个 Git 上创建 Y-789(因此在我们的 Git 中创建 origin/Y-789,并将其设置为我们的新上游)。我们不必删除现有的X-123,也不必删除我们的origin/X-123,我们只需要说服上游存储库删除他的 X-123,然后像往常一样使用git fetch --prune .

    然后我们将发出拉取请求,让控制合并的人处理这一切。

    合并D'

    合并D' 是容易还是困难取决于这些事情:

    • D 合并到master 时是否存在合并冲突?
    • 进行此合并的人是否愿意删除提交M? (请注意,这对“下游”的每个人都有影响,包括你:你们都必须重复这种剥离。这可能很容易,也可能很难。)
    • 做这件事的人知道git merge -s ours吗?
    • 如果剥离 M 并发生冲突,那么执行此操作的人是否知道如何模拟 git merge -s ours

    如果没有合并冲突并且不需要手动调整,那么一切都很容易。只需根据需要保留或剥离M(要剥离它,我们需要git reset --hard 并且我们需要确保在M 之后没有提交之后,然后所有下游都有问题,所以要非常确定这是你想要的)。然后合并,就完成了。

    如果存在 合并冲突,但您可以 git merge -s ours 而不剥离 M,这很简单:只需 git merge -s ours 说“我们已经正确地进行了合并,所以完全忽略所有进行合并时在A'-B'-C'-D' 链上提交result

    如果存在合并冲突并且进行合并的人正在剥离M,那就有点困难了。一般的想法是从master 中删除M,但保留提交及其树(例如,创建一个指向它的名称,或者依赖 reflogs 并只是复制粘贴哈希,或其他)。然后运行git merge,会看到同样的合并冲突;但随后删除合并结果并将其替换为已保存提交中的树。或者,等效地,可以使用 git commit-tree 管道命令来减少麻烦和大惊小怪,但这已经深入到 Git 奥秘中。

    请注意,所有这些都假设您没有对 tree 进行任何更改,即原始 D 提交的 git diff 和新的 D' 提交将为空:只有消息(和哈希 ID)已更改,而不是与提交关联的源代码树。

    【讨论】:

      猜你喜欢
      • 2015-11-27
      • 2014-06-29
      • 1970-01-01
      • 2022-08-09
      • 2018-08-15
      • 2020-02-05
      • 1970-01-01
      • 2021-09-24
      • 1970-01-01
      相关资源
      最近更新 更多