在一系列提交中运行filter-branch

65
git filter-branch --env-filter '
export GIT_AUTHOR_EMAIL="foo@example.com"
export GIT_AUTHOR_NAME="foo"' -- commita..commitb

在“你想重写哪个ref?”中获得结果。

因此,似乎filter-branch不允许您在两个任意refs之间使用范围符号。

如果这种方法不可行,那么运行过滤器以覆盖一系列连续提交的最直接方法是什么?


18
说真的,谁发明了如此无用的错误信息?这种信息似乎唯一有用的地方就是把它输入到谷歌里...类似“范围的结束需要是一个引用而不是提交的ID”(感谢@qqx)这样的信息看起来更有帮助。 - oliver
8个回答

52

@kan说过,你不能在历史的中间应用filter-branch。你必须从已知提交开始应用,一直到历史的结束。

git filter-branch --env-filter '...' SHA1..HEAD

Filter-branch可以检查提交者或其他信息,选择更改或不更改提交内容,因此有方法可以实现你想要的目标,请参见https://git-scm.com/book/zh/v2/Git-工具-重写历史,查找“全局更改电子邮件地址”。

请记住:如果你已经把提交推送到公共代码库,就不应该使用filter-branch。


2
注意: 它无法重写历史的中间部分,因为这将使附加到中间部分的尾端的父字段无效。 - cdosborn
要仅在最新提交(head commit)上执行此操作,请使用“HEAD^..HEAD”。 - ADTC

25

我找到的最干净的解决方案是:

  1. refb上创建一个临时分支。
  2. refa..temp上应用分支过滤器。
  3. 将其变基到临时分支上并删除它。

即:

git branch temp refb

git filter-branch --env-filter '
export GIT_AUTHOR_EMAIL="foo@example.com"' refa..temp

git rebase temp
git branch --delete temp

1
+1 - 不错的技巧,但我认为 filter-branch 需要在 refa..temp 上运行,而你的 rebase 命令应该是 git rebase --onto temp refa refb - Chronial

12

git filter-branch 可以 接受范围表示法,但是范围的结尾需要是引用,而不是提交的ID。

git checkout -b tofilter commitb
git filter-branch .... commita..tofilter

如果只提供提交记录,它将不知道使用过滤分支更新哪个引用。

5
在这种情况下,我该如何阻止我的筛选脚本被应用于commita之前和commitb之后的提交? - Acorn

8

在if语句中包含您的过滤命令,以检查该范围。您可以使用以下命令检查提交是否在给定范围内:

git rev-list start..end | grep **fullsha**

当前提交将存储在您的过滤器中的$GIT_COMMIT。因此,您的过滤器变为:
git filter-branch --env-filter '
  if git rev-list commita..commitb | grep $GIT_COMMIT; then
    export GIT_AUTHOR_EMAIL="foo@example.com"
    export GIT_AUTHOR_NAME="foo"
  fi' -- ^commita --all

如果你只想重写当前分支,请将--all替换为HEAD


6

我用以下方式进行操作。

假设你想要过滤名为“branch-you-are-filtering”的分支的内容。

假设该分支有一个祖先提交,其引用名为“ref-for-commit-to-stop-at”。

git filter-branch --commit-filter 'YOUR_FILTER_COMMANDS' branch-you-are-filtering...ref-for-commit-to-stop-at

执行后,ref-for-commit-to-stop-at上的提交不会被更改。分支branch-you-are-filtering中筛选/更改的所有提交都将基于ref-for-commit-to-stop-at。是否使用--commit-filter或其他内容由您决定。

嗨,我知道这是一个旧帖子,但我有一个问题... 这个-commit-filter只会过滤我的分支(我的分支是最新的分支),我不想过滤其他分支或者任何在我的分支之前的历史记录。我打算按照这个链接的方法进行操作:https://stackoverflow.com/a/32511518/3109055 - undefined

5

在历史记录的中间,你不能仅仅覆盖提交(commit),因为提交(commit)的SHA1值取决于其父级。因此,Git不知道在过滤后你想将HEAD引用指向哪里。所以,你需要重写所有直到HEAD。

例如:

A---B---C---D---E---F   master
            \
             \--G---H   branch

如果您想过滤提交 B 和 C,则还应该过滤之后的所有提交:D、E、F、G、H。这就是为什么 Git 告诉您在范围末尾使用引用,以便它不会以分离头结束的原因。
修改 B 和 C 提交后,停止将如下所示:
A---B---C---D---E---F   master
\           \
 \           \--G---H   branch
  \-B'--C'      (HEAD or a temporary TAG?..)      

所以,masterbranch将不会受到影响。我认为这不是你想要的。 这意味着你必须覆盖所有提交。历史记录将如下所示:
A---B---C---D---E---F   (loose end, will be garbage collected one day)
\           \
 \           \--G---H   (loose end, will be garbage collected one day)
  \-B'--C'--D'--E'--F'  master
            \
             \--G'--H'  branch

1
那么,如果我不想将过滤器应用于 C 之后的提交,你认为最好的方法是什么?我可以创建一个临时标签吗? - Acorn
@Acorn 如果你想保留过滤后的 C 代码提交(我称之为 C'),你必须至少过滤所有这些提交,以便将它们的祖先从 C 更改为 C',否则,只需将主分支移动到 C' 并放弃它们。没有其他选择。每个提交都在其 SHA1 ID 中加密了其之前的所有历史记录,因此每个提交都受到密码保护,你不能在不被察觉的情况下更改历史记录。 - kan
1
好的,我得到的印象是我必须在我的bash脚本中决定是否应用作者/电子邮件更改。我如何确定我是否在那个范围内? - Acorn
@Acorn 这是一个新问题。也许更容易的方法是通过 git rev-list commita..commitb 获取您感兴趣的提交列表,然后在脚本中检查当前提交是否在列表中。此外,这可能会有所帮助:https://dev59.com/aXA75IYBdhLWcg3w9-Sx - kan

0

使用 filter-branch 的 --setup 参数和一些 shell 功能:

git filter-branch --setup '
for id in `git rev-list commitA..commitB`; do
         eval filterfor_$id=rewrite
done
rewrite() {
        GIT_AUTHOR_NAME="Frederick. O. Oosball"
        GIT_AUTHOR_EMAIL=foobar@example.org
}
' --env-filter 'eval \$filterfor_$GIT_COMMIT'

0

我认为@Acron的解决方案是错误的。我建议按照以下方式更改refa和refb之间包括两个哈希:

  1. git tag master.bak
  2. git reset --hard refa
  3. git filter-branch --env-filter ' export GIT_AUTHOR_EMAIL="foo@example.com"' refa^..master
  4. git cherry-pick refb..master.bak
  5. git tag -d master.bak

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接