大型Git仓库的强制重构为多个新仓库

6

我发现了几个使用filter-branch和subtree的简单示例,但它们总是只能移动1个目录。我想要处理以下仓库:

/
  Project1.sln
  Project2.sln
  Source/
    CommonLib.Data/
    CommonLib.Web/
    Project1.Data/
    Project1.Web/
    Project1.Other/
    Project2.Data/
    Project2.Web/

将事物移动到自己的存储库中,采用以下结构:

# CommonRepo
/
  CommonLib.Data/
  CommonLib.Web/

# Project1Repo
/
  Project1.sln
  Project1.Data/
  Project1.Web/
  Project1.Other/

# Project2Repo
/
  Project2.sln
  Project2.Data/
  Project2.Web/

在保留整个历史记录的同时,我们需要注意的是,原始库的每个项目对应着一个或多个分支,因此其他项目所引用的CommonLib版本可能会有所不同。

我想使用git subtree add命令将对每个新库正确标记/修订版本的CommonLib添加到其引用中,但首先,我需要一种方式将几个目录同时拆分到它们自己的位置。

看起来git subtree split -P只能拆分一个目录,而我也无法使用filter-branch获取多个目录。我在Windows平台上操作,因此无法设置所有脚本功能以使操作更加简单。

有什么建议吗?

3个回答

5

最后,我建议您在项目中保留常用库,特别是考虑到您所提到的差异性,因此您理想的结构应该是:

# CommonRepo
/
  CommonLib.Data/
  CommonLib.Web/

# Project1Repo
/
  Project1.sln
  Project1.Data/
  Project1.Web/
  Project1.Other/
  CommonLib/         # I recommend that you do whatever restructuring needed to support this in a sub-directory
    CommonLib.Data/
    CommonLib.Web/

# Project2Repo
/
  Project2.sln
  Project2.Data/
  Project2.Web/
  CommonLib/         # I recommend that you do whatever restructuring needed to support this in a sub-directory
    CommonLib.Data/
    CommonLib.Web/

现在来处理分割:

当你进行分割时,只要不使用不同的注释或其他东西,提交ID将兼容,并且应该可以与合并很好地配合。因此,您可以从中提取CommonLib。

  1. I recommend you clone your whole depo before starting just to be sure you don't lose anything.

    git clone <big-repo> <big-repo-clone>
    
  2. Prepare the old repo

    pushd <big-repo-clone>
    # split for the common lib
    git checkout master  # assuming you want your common lib at master
    git subtree split --prefix=Source --branch=temp-commonLib
    
    # split the projects from their respective branches
    git checkout <branch-for-project1>
    git subtree split --prefix=Source --branch=temp-project1
    
    # split the projects from their respective branches
    git checkout <branch-for-project2>
    git subtree split --prefix=Source --branch=temp-project2
    
  3. Now we need to clean out the parts of those projects that we don't want there. Since they're mixed in you can't really use sub-tree but you can filter-branch to rewrite the history without the other parts.

    # strip unrelated parts from the CommonLib
    git checkout temp-commonLib
    git filter-branch --tag-name-filter cat --prune-empty --index-filter 'git rm -rf --cached --ignore-unmatch Project1* Project2*' HEAD
    
    # strip unrelated parts from the Project1
    git checkout temp-project1
    git filter-branch --tag-name-filter cat --prune-empty --index-filter 'git rm -rf --cached --ignore-unmatch CommonLib* Project2*' HEAD
    
    # strip unrelated parts from the Project2
    git checkout temp-project2
    git filter-branch --tag-name-filter cat --prune-empty --index-filter 'git rm -rf --cached --ignore-unmatch CommonLib* Project1*' HEAD
    

    The prune empty will strip the commits that become empty because they only contained changes that were in the folders you removed.

    Note: All of these changes are at the /source level so that it can be the new root for each project. You can later add your solution back in. Or you can use this prune technique with clones instead of subtrees, and when you're all done you can just move all the contents from '/Source' to '/'

    Now your is going to have extra branches and backups in refs/original/refs/heads/<branch-name>. If during the process you get a fatal error with filter-branch, you can re-create the branch and start again, or if you're confident it didn't do anything yet you can delete this backup with: git update-ref -d refs/original/refs/heads/<branch-name>.

  4. Now just create new repos to store the projects created from those branches

    popd # to get out of <big-repo-clone>
    
    mkdir <new-repo>
    pushd <new-repo>
    
    git init
    git pull <big-repo-clone> <name-of-branch> # like temp-project1
    popd # to get out of the <new-repo>
    
  5. One last thing, lets pull the CommonRepo into the projects.

    pushd <new-project-repo>
    git subtree add --prefix=CommonLib <new-commonlib-repo>
    

您只需要导入 .sln 文件(我将把最后一步留给您处理)。


我认为你不需要一次指定多个目录来实现你的目标。 - johnb003
在您的示例和编辑中,我认为您重新组织了我的层次结构。每个项目的 Web 和非 Web 部分是同级的,Web 目录不存在于需要移动的其他目录下。 - Chip Paul
非常抱歉,我无法理解您所写的目录结构。这是您的意思吗(请参见我帖子中的编辑)? - johnb003
1
我根据更新后的初始结构更新了我的答案,并进行了测试。 - johnb003
今天我会尝试一下。上次我执行 filter-branch 命令花了超过 24 小时,最后我甚至无法确定它是否有任何作用,但我还是会开始执行并看看情况如何。 - Chip Paul
显示剩余4条评论

1

我的程序 git_filter 可以为您完成分割。我写这个程序是因为我们大型仓库中的所有其他解决方案速度非常慢。它在这里:

https://github.com/slobobaby/git_filter

它为原始仓库的每个提取创建多个分支。目前我在这里有一个测试分支:

https://github.com/slobobaby/git_filter/tree/subdir

该命令将创建一个新的分支,其中包含原始存储库的子目录重命名为新存储库的根目录。
与基于git-core的解决方案相比,运行时间只需要几分钟,而不是几小时或几天。
脚本中包含一个功能,可以将这些新分支推送到新的干净存储库。

哎呀,我没看到你在使用Windows。你可以在其中启动带有Linux的虚拟机...或将我的代码移植到Windows上。 - slobobaby
我昨天在搜索时发现了你的项目,但我不确定如何配置它以实现我的目标。如果原始仓库上有多个分支,这些分支是否会被工具转移到新的仓库中? - Chip Paul
不,每个分支都作为流程的第二阶段推送为新的单独存储库。重复使用原始对象而不是创建新对象真的有助于加快处理速度。push_clean_repos脚本将创建干净的存储库。 - slobobaby
如果我读了自己的代码,我就会发现子目录分支做的是与你想要的相反的事情,它将根目录移动到一个子目录中。这个新的分支在 https://github.com/slobobaby/git_filter/tree/flatten 上实现了你想要的功能。我甚至添加了配置,重写了你上面描述的示例仓库。只需检出并运行“./git_filter test.cfg”,并将你的仓库放在当前目录下的测试文件夹中即可 :) - slobobaby
对于关于多个分支的问题,答案是默认情况下只会选择一个分支并进行过滤。您需要运行它多次以过滤多个分支。我可能可以将其更改为在所有提交上执行,而不仅仅是某个特定分支上执行。 - slobobaby
目前情况真的很混乱。每个项目至少有一个分支,每个分支都有整个代码库的某些部分的副本,随着分支之间的合并,主要是为了共享 Common 代码的新添加。历史记录中有十几对目录和数十个分支,大多数情况下每个项目只有一个活动分支。 - Chip Paul

1
对我来说,与其试图分离出仓库的许多部分,我更喜欢克隆原始仓库,然后通过切掉不需要的内容来精简它。然后在原始(或另一个克隆)中,我将剥离已分离出的部分。我发现这是一种更容易、迭代的方法,具有更高的可见性,以便了解正在发生什么。
请参阅我关于此主题的最近答案: https://stackoverflow.com/a/22210682/955926

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