在 TFS 上编辑另一个开发人员 GIT 提交消息

Edit another developers GIT commit message on TFS

有人问我能否修改 git 项目历史记录中的一组签入评论,其中包含其他开发人员的不当措辞。

谁能告诉我如何实现这个(或确认这是否不可能)? 我是 TFS 的管理员,因此访问权限不是问题。 我们的 TFS 在内部使用 SQL Express & GIT 作为源代码控制。 TFS 版本为 15.117.26714.0

我们在带有团队资源管理器插件的本地计算机上使用 Visual Studio 2017。

从命令行,您可以使用

git rebase -i <sha-hash of the first commit to edit>~

(注意末尾的波浪号。您必须按字面输入。)

键入此内容后,将打开一个文本编辑器,其中包含所有提交的列表以及如何处理它们的命令。该命令默认为 pick。对于要编辑的那些,选择 reword。然后保存文件并关闭文本编辑器。现在对于您选择改写的每个提交,编辑器将再次打开并允许您编辑提交消息。编辑完成后保存关闭即可。

请注意,这将从您开始的那一刻起重写整个 git 历史记录。之前将此历史记录拉取到本地存储库的任何人都需要 git fetch 然后 git reset --hard <branch name>。如果他们不这样做,那么有问题的提交将在它们继续工作时保留在回购协议中。

可以做到,但很可能会很麻烦。根据审计规则的性质,您可能会考虑 git 注释是否提供了更好的解决方案。 (如果您只是缺少信息,并且您的审计员同意,添加注释可以让您以最小的干扰添加额外的信息。如果您需要 删除 信息 - 如果有人在消息中输入了密码、组织上不可接受的语言或其他任何内容 - 那将无济于事。)

您可能会注意到其他一些源代码控制系统可以让您更自由地编辑提交消息。我不想在此基础上为我的团队做出源代码控制工具的决定,但如果这对您的团队来说是一个频繁的需求,那么这是一个考虑因素。或者,更好的办法是弄清楚是否需要调整任何内容,以便不会成为未来的频繁需求。

(在某种程度上,这可能是关于在提交消息中禁止 and/or 要求什么的培训问题。您可以使用挂钩设置某种护栏 - 预接收挂钩最重要的是,如果您的托管环境允许您配置一个,并且预提交挂钩是开发人员确保任何错误的一个很好的便利"fail fast"。)

问题是提交消息是提交的组成部分。当您修改提交的消息时(我们称之为 P),您实际上是在创建一个新的提交(P')。此外,如果您有一个提交 C 并且您想将其父项从 P 更改为 P',那么您必须将其替换为新的提交 C'.

修改提交与用新提交替换它们之间的区别听起来并不多,但实际上这意味着您要从已发布分支的历史记录中删除原始提交,这就是它成为问题。

要进行清理,请从任何克隆开始。假设我们有以下内容,其中提交 not named 'x' 有错误的提交消息。

x -- x -- x -- A <--(master)(origin/master)
 \
  x -- x - x -- B -- x <--(branch1)(origin/branch1)
   \      /
    x -- C -- x <--(branch2)(origin/branch2)

修复 A 并不难。

git checkout master
git commit --amend

将提供一个编辑器,您可以重写提交消息。 (或者您可以包括 git commit 选项,例如 -m 来指定提交消息。)那么您有

            A' <--(master)
           /
x -- x -- x -- A <--(origin/master)
 \
  x -- x - x -- B -- x <--(branch1)(origin/branch1)
   \      /
    x -- C -- x <--(branch2)(origin/branch2)

请注意 A 仍然存在并且 origin/master 仍然指向它。这个很重要。这意味着当你想更新 origin 时,你必须说

git push -f

这将 "break" 克隆其他人。有关问题和典型清理程序的更多信息,请参阅 "Recovering from Upstream Rebase" 下的 git rebase 文档 (https://git-scm.com/docs/git-rebase);但也请注意,如果这最终重写了大部分回购协议,您可能希望遵循这样的切换过程:

  • 每个人都推送他们所有的代码;它不需要完全合并,但必须在 origin 中,因为
  • 每个人都丢弃他们的克隆
  • 你做你的清理工作
  • 每个人都制作一个新的克隆

无论如何,您已经清理了一个提交。到 B。这次不是分支提示,所以 amend 不是要使用的东西。而是做一个 rebase.

git rebase -i branch1~2 branch1

您将获得一个 "todo" 列表,其中显示了 branch1 上的最后一对提交。找到 B 的条目并将行的开头从 pick 更改为 reword。然后让 rebase 继续,当它到达 B 时,它会给你一个提交信息的编辑器。最后你有

            A' <--(master)
           /
x -- x -- x -- A <--(origin/master)
 \
  x --- x --- x -- B -- x <--(origin/branch1)
   \         / \
    \       /   B' -- x' <--(branch1)
     \     /
      x - C -- x <--(branch2)(origin/branch2)

不比A差多少;你重写了一个 x commit 由于 reparenting,但最后它仍然只是强制推送的一个参考。

现在 C 呢?好吧,C 可以从多个引用访问,并且对于其中一个提交,它们之间有一个合并。这些因素使 rebase 更难正确使用。在这种情况下,您可能希望使用 git filter-branch。 (如果你有任何情况让你使用 filter-branch,那么你可能会考虑 只是 做一个 filter-branch 来完成你所有的重写,而不是搞乱个人 rebaseamend 操作。)

麻烦的是如何写msg-filter。您可以编写一个脚本来检查提交 ID,对于每个已知的 "bad" 提交输出相应的新提交消息,而对于其他所有内容只需 cat 返回原始消息。或者你可以为每次提交启动一个编辑器,如果没有太多的提交不实用的话。或者介于两者之间(为每个 ID 在特定列表中的提交启动一个编辑器)。有太多的方法,太多的原因会影响使用哪种方法,无法在这个答案中详细说明。

假设你整理了一个过滤器脚本,你会做类似的事情

git filter-branch --msg-filter=my-filter-script -- --all

这个结果的完整图表变得混乱,但基本上对于本地分支 C 被替换为 C',所有可以到达 C 的东西都被类似地替换,并且每个分支C 需要强制推送。