作为主要代码库的子git仓库

12
我想找到一种设置git代码库的方法,使其包含来自较大代码库的子集文件,并继承主代码库的历史记录。我主要的动机是能够通过GitHub共享代码子集。目前,我通过单个git代码库管理我的研究相关(大多数为Matlab)代码。代码本身松散地组织成几个文件夹,代码依赖关系经常跨越文件夹。我不想上传整个仓库的远程副本,因为它包括许多混合项目,其他人不会希望完全拥有它们。我的想法是为每个项目创建一个独立的仓库,仅跟踪该项目的相关文件,但继承所有主仓库的提交。理想情况下,我想能够在这些子仓库中独立标记版本,但这不是必需的。我已经研究了git子模块、子树和gitslave,但所有这些都似乎假定子项目是隔离的文件集,而在我的情况下,许多子项目与其他子项目共享文件。我还尝试创建特定于项目的分支,使用git rm删除不相关的文件,但是当我需要将主分支中的更改合并到项目分支时,它就崩溃了(由于项目删除的文件中的更改而导致冲突的混乱)。我目前通过定期将相关文件复制到每个项目的新文件夹来共享代码。但这意味着新副本没有提交历史记录。有没有更可靠的方法来共享这些各种子代码集,并使它们与我进行的更改保持最新?

你要找的术语是“子模块”和“子树”。它们是解决类似问题的两种不同方案,就像你所描述的那样。研究它们并自行选择。 - Adam
这样做可能效果不佳(我的直觉告诉我)。但是,您可以将当前的存储库复制一份,并重新处理所有提交,以便只保留您想要的文件。然后您可以分享它,或者将您的工作移到那里。 - Thorbjørn Ravn Andersen
关于 git subtree 的一些信息:https://dev59.com/S1QJ5IYBdhLWcg3wKysQ#61273621 - Inigo
3个回答

2

根据我理解您的问题:

  • 您有一个包含多个子项目的大型代码库
  • 您想要将每个子项目提取出来并作为其自己的仓库共享,仍然包含该子项目的历史记录/提交
  • 子项目共享一些文件 => 这意味着由一个子项目使用的文件并不严格包含在单个子目录中,因为一个文件可能在多个子项目中使用,这就是为什么您不能简单地使用git subtreegit submodules的原因

提取只有一部分文件的历史记录到专用分支(然后可以将其推送到专用仓库)的一种方法是使用git filter-branch

# regex to match the files included in this subproject, used below
file_list_regex='^subproject1/|^shared_file1$|^lib/shared_lib2$'

git checkout -b subproject1 # create new branch from current HEAD

git filter-branch --prune-empty \
  --index-filter "git ls-files --cached | grep -v -E '$file_list_regex' | xargs -r git rm --cached" \
  HEAD

这将

  • 首先基于当前的HEAD创建一个新的分支subproject1(git checkout -b subproject1)
  • 遍历它的整个历史(git filter-branch [...] HEAD)
  • 删除所有不属于子项目的文件(git ls-files --cached | grep -v -E '$file_list_regex'),使用(xargs -r git rm --cached)
  • 没有涉及到子项目文件的所有提交将从该分支中删除(--prune-empty).
  • 此操作不会检出每个修订版本,而是仅在索引(--index-filter/--cached)上操作。

虽然这是一次性操作,但根据我理解你的问题,您想要连续更新提取的子项目存储库/分支以获取新的提交。

好消息是您可以简单地重复此命令,因为git filter-branch将始终为您的子项目分支生成相同的提交/历史记录-只要您不手动更改它们或重写主分支。

缺点是这将每次并且对于每个子项目都完全过滤历史记录。鉴于您只想将master分支的最后5个提交添加到现有的subproject1分支的末尾,您可以像这样调整命令:

# get the full commit ids for the commits we consider
# to be equivalent in master and subproject1 branch
common_base_commit="$(git rev-parse master~6)"
subproject_tip="$(git rev-parse subproject1)"

# checkout a detached HEAD so we don't change the master branch
git checkout --detach master

git filter-branch --prune-empty \
  --index-filter "git ls-files --cached | grep -v -E '$file_list_regex' | xargs -r git rm --cached" \
  --parent-filter "sed s/${common_base_commit}/${subproject_tip}/g" \
  ${common_base_commit}..HEAD

# force reset subproject1 branch to current HEAD
git branch -f subproject1

说明:

  • 这将仅重写最后的5个提交(git filter-branch [...] ${common_base_commit}..HEAD),直到 master~6,我们认为它是等效于 subproject1 当前版本的提交。
  • 对于(第一个)这些提交,它将把其父级从 master~6 重写为 subproject1--parent-filter 'sed s/${common_base_commit}/${subproject_tip}/g'),有效地将5个重写的提交重新基于 subproject1
  • 最后,我们只需要更新 subproject1 以包含其上的新提交。

进一步优化/自动化:

  • 实现更好的逻辑来列出您想要包括的文件 ($file_list_regex) 或者实际上要排除的文件 (git ls-files --cached | grep -v -E '$file_list_regex') 来自给定的子项目
  • 使要包括的文件列表依赖于当前提交 ($GIT_COMMIT) 或者在存储库本身中检入列表,以防子项目的要包括的文件可能随时间而变化
  • 找到一种自动化的方法来找到子项目分支顶部的“等效”提交在当前主分支上
  • 将所有内容组合成一个漂亮的 git 别名,以便您可以简单地使用 git update-project subproject1

我猜你在正则表达式的第一部分中想要写的是"^subproject1$"而不是"^subproject1/"。 - Nicolas Form

1

您正在寻找 git子模块:

在一个项目中工作时,经常会需要使用另一个项目。也许这是一个第三方开发的库,或者您正在单独开发并在多个父项目中使用。在这些情况下经常出现一个共同问题:您希望能够将两个项目视为分开的,但仍然能够在其中一个项目中使用另一个项目。

子模块的 TL;DR 是它们是包含在其他存储库中的存储库。

父级存储库所知道的唯一信息是子级告诉它的最后提交的 SHA,因此每个存储库都是独立管理的,但它们彼此引用,允许您将它们组合在一起。

这里有一篇来自 GitHub 的 博客文章,讲解了这个主题。


0

首先,让我总结一下你的问题:

  • 你有一个大型代码库
  • 你想将其拆分为子代码库
  • 你想保持历史记录的完整性

从你的统计数据中,我可以看到你在一个主代码库中存储了14个子项目。这通常是一个非常糟糕的解决方案,因为请记住,每当有人克隆代码库时,它也会获取所有子项目的完整历史记录。例如,如果我想为你的其中一个子项目做出贡献,我不想携带你拥有的8096个文件。

如果这些项目彼此无关,只需将它们拆分为子代码库即可。使用GitHub,您可以创建组织。不要犹豫,创建自己的组织并将所有子项目放入其中。主要优点是每个子项目都将拥有:

  • 自己的wiki
  • 自己的问题跟踪器
  • 自己的首页

如果您有相关项目,每个项目都需要从特定的提交中获取。我建议您使用git子模块。例如,如果您查看ext/文件夹中的TortoiseGit项目,您将注意到其他存储库的链接。

另一个解决方案是使用git子树, 这似乎不是您问题的最佳解决方案。

如果您的主存储库属于以下任何一类,则应重新审视您使用Git的方式:

  • Git存储库超过100 MB
  • Git存储库存储工件(.exe.tmp、二进制文件、生成的文件、.pdf等)

您的存储库是否在GitHub上公开?


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