使用git filter-branch --subdirectory-filter命令保留--no-ff合并提交。

7
我有一个Git仓库,其结构如下:
.git/
    project-root/
        abc/
        xyz/

这就是我想要的:
project-root/
    .git
    abc/
    xyz/

我一直在研究如何实现这个目标,到目前为止,我找到了以下内容:http://git-scm.com/docs/git-filter-branch 根据这个网站上的说明,我应该使用以下命令来达到我的目的:
git filter-branch --subdirectory-filter project-root -- --all

这将重写提交历史,但大部分非快进式合并提交都会丢失(请参见下面的图片)。 有没有办法保留这些提交? enter image description here

这是一个非常好的问题:截图非常美丽,并且真正表达了正在发生的事情。但是,您提供的示例repo布局有点模糊,因为project-root文件夹没有兄弟文件夹。这些兄弟将被subdirectory-filter移除,但不会被您在答案中描述的tree-filter移除。我可以建议您扩展一下示例吗?在大多数使用subdirectory-filter的情况下,您的project-root文件夹将是其中之一。 - stochastic
2个回答

2
如文档所述,这里
--subdirectory-filter <directory>

    Only look at the history which touches the given subdirectory. The result will 
    contain that directory (and only that) as its project root. Implies 
    [Remap_to_ancestor].

我认为问题在于合并提交实际上并没有涉及到给定的子目录,这意味着子目录过滤器甚至不会查看它们,因此无法保留它们。因此,我们必须使用另一个过滤器来检查每个提交。tree-filter非常适合此任务,但我们必须编写一个脚本来执行我们想要对每个提交执行的操作。
此外,我的问题实际上比示例更广泛,因为我希望删除project-root文件夹的同级文件夹。子目录过滤器可以删除它们,但为了使用tree filter进行操作,使用find命令会很有帮助。
将每次运行的命令放在单独的文件中是很方便的。
对于像这样结构化的repo:
.git/ 
someDirectory/
someFile.txt
otherDirectory/
dontDeleteThisOne/
project-root/
    xyz/
    abc/

这是对我有效的方法:
git filter-branch --tree-filter /absolute/path/to/filterScript.sh --tag-name-filter cat -- --all 

其中/absolute/path/to/filterScript.sh是一个可执行脚本,其内容如下:

#!/bin/bash

#save the folder dontDeleteThisOne so it won't get deleted
git mv -fk dontDeleteThisOne project-root && \
#remove everything not under project-root
find . -maxdepth 1 -mindepth 1 '(' -name project-root -o -name '.git' ')' -prune -o -exec git rm -rf --ignore-unmatch '{}' ';' && \
#move the contents of the project-root directory up to root
git mv -fk project-root/{,.[!.],..?}* .;

生成的仓库结构如下所示:
.git/
dontDeleteThisOne/
xyz/
abc/

这个结果等同于使用git filter-branch --subdirectory-filter project-root命令的结果,除了合并提交被保留在历史记录中,正如期望的那样。

当然,这比使用子目录过滤器要慢得多...


我认为问题在于合并提交实际上没有涉及到给定的子目录<...>,因此无法保留它们。但事实并非如此,我有一些git合并是在这个子目录的确切上下文中创建的,但由于某种原因,大多数分支的最终结果被压平了(但不是所有分支)。 - Drachenfels
你能详细说明一下“在确切上下文中创建”的含义吗?我的意思是,我怀疑由于使用--no-ff而创建的合并提交实际上并不包含任何文件的更改,因此不包含要保留的目录中的任何文件更改,并因此被子目录过滤器过滤掉。 - stochastic
我所说的上下文是指我们专门在一个子目录中工作。可以将我们的代码库视为代码库的集合。每个子目录都是另一个项目。因此,我们的分支、合并、挑选等操作都是在该子文件夹中独立进行的。因此,我使用了“上下文”这个词。而且,由于您的陈述暗示合并提交消失是因为分支没有涉及到所提到的子目录,我说这是事实上不正确的。我们的Git合并提交中有90%都消失了。 - Drachenfels
但总的来说,是的,我误解了你的句子。对此我很抱歉。 - Drachenfels
我并不是在说分支没有触及目录中的文件导致问题。我是在说合并提交本身根本没有触及任何文件(我想)。 - stochastic
显示剩余2条评论

0

我尝试使用 --tree-filter 而不是 --subdirectory-filter,它对我来说似乎有效。这是我使用的命令:

git filter-branch --tree-filter 'git mv -k project-root/{,.[!.],..?}* .' -- --all

运行此命令后,提交历史记录将被重写,就好像“project-root”从未被Git跟踪过一样,并且分支拓扑结构得到保留,这正是我想要的。
如果有人在Windows上尝试使用此命令并出现“权限被拒绝”的错误,可以尝试关闭explorer.exe进程(我在这里找到了这个解决方法:http://academe.co.uk/2011/12/git-mv-permission-denied/)。 编辑: 上面的命令不会重写标签。为了重写标签,正确的命令是:

git filter-branch --tree-filter 'git mv -k project-root/{,.[!.],..?}* .' --tag-name-filter cat -- --all


这对我没有用。我不停地收到大量的“WARNING: Ref 'refs/heads/<branchName>' is unchanged”警告。我已经看到其他类似的问题,比如这个这个,这个问题是由于错误路径导致“git mv…”命令无法执行树过滤器所致。我的路径是正确的,我也可以手动运行“git mv…”命令,并且确实会引起变化。 - stochastic

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