有没有办法在分支上以增量方式使用filter-branch?
粗略地说这样(但实际上并没有效果):
git checkout -b branchA origin/branchA
git branch headBranchA
# inital rewrite
git filter-branch ... -- branchA
git fetch origin
# incremental rewrite
git filter-branch ... -- headBranchA..origin/branchA
git merge origin/branchA
答案 0 :(得分:11)
我不确定你真正想要实现的目标,所以我在这里说的是#34;是的,但可能不是你在想什么,它可能会不能帮助你实现目标,不管是什么,#34;。
在此理解这不仅仅是 filter-branch
的作用,而且在某种程度上 它是如何做到的。
git存储库包含一些提交图。这些是通过一些起始提交节点找到的,这些节点是通过外部引用找到的 - 主要是分支和标签名称,还有注释标签,我只是对这种情况不太重视,然后使用那些起始节点找到更多节点,直到所有节点都可以到达"已找到节点。
每次提交都有零个或多个"父提交"。大多数普通提交都有一个父母;合并有两个或更多的父母。根提交(例如存储库中的初始提交)没有父级。
分支名称指向一个特定的提交,指向其父级,依此类推。
B-C-D
/ \
A---E---F <-- master
\
G J <-- branch1
\ /
H-I-K <-- branch2
分支名称master
指向提交F
(这是合并提交)。名称branch1
和branch2
分别指向J
和K
。
我们还要注意,因为提交指向他们的父母,所以&#34;可达到的设置&#34;名称master
的{{1}}为A B C D E F
,branch1
的设置为A G H I J
,branch2
的设置为A G H I K
。
&#34;真实姓名&#34;每个提交节点的一个是SHA-1,它是提交内容的加密校验和。内容包括相应工作树内容的SHA-1校验和以及父提交的SHA-1。因此,如果您要复制提交并更改 nothing (而不是单个位),则会获得相同的SHA-1,因此最终会使用相同的提交;但是如果你改变一个比特(包括,例如,改变提交者姓名的拼写,任何时间戳或相关工作树的任何部分),你会得到一个新的,不同的提交。
git rev-parse
和git rev-list
这两个命令对大多数git操作都非常重要。
rev-parse
命令将any valid git revision specifier转换为提交ID。 (它还有很多我们可以称之为&#34;辅助模式&#34;,允许将大多数git命令编写为shell脚本 - 而git filter-branch
实际上是一个shell脚本。)
rev-list
命令将修订版范围(也在gitrevisions中)转换为提交ID列表。鉴于只是一个分支名称,它会找到可从该分支到达的所有修订版本的集合,因此在上面的示例提交图中,给定branch2
,它列出了提交{1}},A
,G
,H
和I
的SHA-1值。 (它默认以反向时间顺序列出它们,但可以告诉它们在地形顺序&#34;中列出它们,这对K
很重要,而不是我打算深入了解细节此处。)
但是,在这种情况下,您将需要使用&#34;提交限制&#34;:给定修订范围,如filter-branch
语法,或给出{ {1}},A..B
将其输出转速限制为可从B ^A
到达的提交,但 可从git rev-list
到达。因此,给定B
- 或者euivalently A
- 它会列出branch2~3..branch2
,branch2 ^branch2~3
和H
的SHA-1值。这是因为I
名称提交K
,因此提交branch2~3
和G
会从可到达的集合中删除。
A
filter-branch脚本相当复杂,但总结了它对命令行中给出的&#34; ref名称的操作&#34;并不太难。
首先,它使用G
来查找要过滤的分支或分支的实际头部修订。它使用它两次,实际上:一次获得SHA-1值,一次获取名称。例如,git filter-branch
,它需要获得&#34;真正的全名&#34; git rev-parse
:
headBranchA..origin/branchA
将打印:
refs/remotes/origin/branchA
filter-branch脚本会丢弃所有git rev-parse --revs-only --symbolic-full-name headBranchA..origin/branchA
- 前缀结果,以获取&#34;肯定引用名称列表&#34 ;;最后,这些是它打算重写的内容。
这些是&#34;肯定参考&#34; git-filter-branch manual。
中描述的内容然后使用refs/remotes/origin/branchA
^refs/heads/headBranchA
获取要应用过滤器的提交SHA-1的完整列表。这就是^
限制语法的用武之地:现在脚本只知道可以从git rev-list
到达的提交,但不能从headBranchA..origin/branchA
到达。
一旦有了提交ID列表,origin/branchA
实际应用了过滤器。这些提交了新的提交。
与往常一样,如果新提交与原始提交完全相同,则commit-ID保持不变。但是,如果filter-branch是有用的,可能在某些时候,某些提交会被更改,从而为它们提供新的SHA-1。这些提交的任何直接子项都必须获取新的父ID,因此这些提交也会更改,并且这些更改会传播到最终的分支提示。
最后,将过滤器应用于所有列出的提交后,headBranchA
脚本会更新&#34;肯定参考&#34;。
下一部分取决于您的实际过滤器。让我们假设您的过滤器在每次提交时更改了作者姓名的拼写,或者更改了每次提交时的时间戳,或者更改了每次提交的时间戳,以便每次提交都会被重写,除非出于某种原因而离开根提交不变,以便新分支和旧分支确实有共同的祖先。
我们从这开始:
git filter-branch
(您现在在filter-branch
,即git checkout -b branchA origin/branchA
包含branchA
)
HEAD
(这会使另一个分支标签指向当前的ref: refs/heads/branchA
提交,但不会改变git branch headBranchA
)
HEAD
&#34;积极参考&#34;在这种情况下是HEAD
。要重写的提交是每个可以从# inital rewrite
git filter-branch ... -- branchA
到达的提交,即下面的所有branchA
节点(这里开始提交图表用于说明),除了根提交branchA
:< / p>
o
复制每个R
提交,并移动R-o-o-x-x-x <-- master
\
o-o-o <-- headBranchA, HEAD=branchA, origin/branchA
以指向最后一个新提交:
o
稍后,您将从远程branchA
获取新内容:
R-o-o-x-x-x <-- master
| \
| o-o-o <-- headBranchA, origin/branchA
\
*-*-*-*-* <-- HEAD=branchA
让我们说这会添加标记为origin
的提交(我只会添加一个):
git fetch origin
这里出了问题:
n
&#34;积极参考&#34;这里是R-o-o-x-x-x <-- master
| \
| o-o-o <-- headBranchA
| \
| n <-- origin/branchA
\
*-*-*-*-* <-- HEAD=branchA
,因此将会移动。 rev-list选择的提交只是标记为git filter-branch ... -- headBranchA..origin/branchA
的提交,这是您想要的。让我们这次拼写重写的提交origin/branchA
(大写):
n
现在您尝试N
,这意味着R-o-o-x-x-x <-- master
| \
| o-o-o <-- headBranchA
| |\
| | n [semi-abandoned - filter-branch writes refs/original/...]
| \
| N <-- origin/branchA
\
*-*-*-*-* <-- HEAD=branchA
提交git merge origin/branchA
,这需要找到git merge
链和提交N
之间的合并基础......那就是*
。
我认为这不是你想要做的事情。
我怀疑你想要做的是,而是将N
提交到R
链上。让我们在:
N
这部分没问题,但未来一团糟。事实证明,你实际上根本不想提交*
,而且你不想移动R-o-o-x-x-x <-- master
| \
| o-o-o <-- headBranchA
| |\
| | n [semi-abandoned - filter-branch writes refs/original/...]
| \
| N <-- origin/branchA
\
*-*-*-*-*-N'<-- HEAD=branchA
,因为(我认为)你想要能够稍后重复N
步骤。所以,让我们撤消&#34;这和尝试不同的东西。让我们完全删除origin/branchA
标签并从此开始:
git fetch origin
让我们为headBranchA
点的提交添加一个临时标记,并运行R-o-o-x-x-x <-- master
| \
| o-o-o <-- origin/branchA
\
*-*-*-*-* <-- HEAD=branchA
,以便我们获得提交origin/branchA
:
git fetch origin
现在让我们将n
复制提交到R-o-o-x-x-x <-- master
| \ .--------temp
| o-o-o-n <-- origin/branchA
\
*-*-*-*-* <-- HEAD=branchA
,当我们复制它时,也要修改它(对n
执行任何修改)要获得提交,我们只需致电branchA
:
git filter-branch
完成此操作后,我们会删除N
,我们已准备好重复此循环。
这留下了几个问题。最明显的是:我们如何复制R-o-o-x-x-x <-- master
| \ .--------temp
| o-o-o-n <-- origin/branchA
\
*-*-*-*-*-N <-- HEAD=branchA
(或几个/多个temp
s)然后修改它们?好吧,假设您的n
已经有效,简单的方法是使用n
复制它们,然后使用filter-branch
过滤它们。
仅当git cherry-pick
步骤不会遇到树差异问题时才有效,因此这取决于您的过滤器的作用:
git filter-branch
如果您的过滤器分支改变了树,那么这种方法总是不起作用怎么办?好吧,我们可以直接将过滤器应用于cherry-pick
提交,提交# all of this to be done while on branchA
git tag temp origin/branchA
git fetch origin # pick up `n` commit(s)
git tag temp2 # mark the point for filtering
git cherry-pick temp..origin/branchA
git filter-branch ... -- temp2..branchA
# remove temporary markers
git tag -d temp temp2
次提交,然后复制n
次提交。那些(n'
)提交将存在于本地(已过滤)n'
上。复制后不需要n''
次提交,因此我们会将其弃置。
branchA
我们已经(在图形图纸中)涵盖了新提交如何被复制和修改到您的&#34;逐步过滤&#34; n'
。但是,当您去咨询# lay down temporary marker as before, and fetch
git tag temp origin/branchA
git fetch origin
# now make a new branch, just for filtering
git checkout -b temp2 origin/branchA
git filter-branch ... -- temp..temp2
# the now-altered new branch, temp..temp2, has filtered commits n'
# copy n' commits to n'' commits on branchA
git checkout branchA
git cherry-pick temp..temp2
# and finally, delete the temporary marker and the temporary branch
git tag -d temp
git branch -D temp2 # temp2 requires a force-delete
时,您发现提交已被删除会发生什么?
也就是说,我们从这开始:
branchA
我们像往常一样放下临时标记并执行origin
。但是他们所做的就是删除最后一次R-o-o-x-x-x <-- master
| \
| o-o-o <-- origin/branchA
\
*-*-*-*-* <-- HEAD=branchA
提交,并强制推送他们。现在我们有:
git fetch origin
这里的含义是我们可能也应该o
支持一个修订版。
您是否想要处理此问题取决于您。我在此注意,R-o-o-x-x-x <-- master
| \
| o-o <-- origin/branchA
| `o.......temp
\
*-*-*-*-* <-- HEAD=branchA
的结果在此特定情况下将为空(修订后的branchA
无法从git rev-list temp..origin/branchA
无法提交),但{ {1}} 不为空:它会列出一个&#34;已删除&#34;承诺。如果删除了两个提交,它将列出两个提交,依此类推。
控制origin/branchA
的任何人都可以移除多个提交并且添加了一些其他新提交(事实上,这正是&#34;上游变种&#34;)。在这种情况下,两个temp
命令都将为非空:origin/branchA..temp
将显示已删除的内容,origin
将显示已添加的内容。
最后,控制git rev-list
的任何人都可以完全破坏你的一切。他们可以:
origin/branchA..temp
或temp..origin/branchA
指向无关的分支。同样,由您来决定是否以及如何处理这些案件。
答案 1 :(得分:1)
Git 2.18(2018年第二季度)确实提出了增量过滤。
&#34; git filter-branch
&#34;学会了使用不同的退出代码来允许
调用者告诉没有新提交的情况
从其他错误案例中重写。
commit 0a0eb2e见Michele Locati (mlocati
)(2018年3月15日)
(Junio C Hamano -- gitster
--合并于commit cb3e97d,2018年4月9日)
filter-branch:当没有要重写的时候返回2
使用
--state-branch
选项可以执行增量过滤。 这可能导致在后续过滤中无需重写,因此我们需要 识别这种情况的方法。
所以,当这个&#34;错误&#34;时,让我们以2而不是1退出。发生。