我有和你类似的问题:一个大型通用工具库作为子模块,许多项目都依赖它。我不想为每个实例创建单独的检出。
jthill建议的解决办法很好,但仅解决了问题的前半部分,即如何让git满意。
缺失的是如何让您的构建系统满意,因为它需要实际的文件来处理,而不关心gitlink引用。
但是,如果将他的想法与符号链接结合起来,您就能得到想要的结果!
为了实现这一点,让我们从您的示例项目开始。
/home/projects/project1
/home/projects/project2
/home/projects/library_XYZ
假设项目1和项目2都已将library_XYZ添加为子模块,并且当前这三个项目都包含library_XYZ的完整检出。为了通过共享符号链接到库的检出来替换库子模块的完整检出,请执行以下操作:
sharedproject="/home/projects/library_XYZ"
superproject="/home/projects/project1"
submodule="library_XYZ"
cd "$superproject"
(cd -- "$submodule" && git status)
(cd -- "$submodule" && git push -- "$sharedproject")
git submodule deinit -- "$submodule"
rm -rf .git/modules/"$submodule"
mkdir -p .submods
git mv -- "$submodule" .submods/
echo "gitdir: $sharedproject/.git" > ".submods/$submodule/.git"
ln -s -- "$sharedproject" "$submodule"
echo "/$submodule" >> .gitignore
然后,针对/home/projects/project2作为$superproject,重复相同的步骤。
这里是已完成的操作的说明:
首先,使用“git submodule deinit”删除子模块检出,将library_XYZ留下为空目录。请务必在执行此操作之前提交任何更改,因为它将删除检出!
接下来,我们使用“git push”将尚未推送到共享项目的本地检出中保存的任何提交保存到/home/projects/library_XYZ。
如果由于未设置远程或refspec而无法工作,则可以执行以下操作:
(saved_from=$(basename -- "$superproject"); \
cd -- "$submodule" \
&& git push -- "$sharedproject" \
"refs/heads/*:refs/remotes/$saved_from/*")
这将在/home/projects/library_XYZ中将子模块本地仓库的所有分支保存为远程分支。$superproject目录的基本名称将用作远程的名称,例如在我们的示例中是project1或project2。
当在那里执行"git branch -r"时,保存的分支将显示出来,当然,在/home/projects/library_XYZ中实际上不存在该名称的远程分支。
作为安全保障,上述命令中的refspec不以"+"开头,因此“git push”不会意外覆盖任何已经存在于/home/projects/library_XYZ中的分支。
接下来,.git/modules/library_XYZ将被删除以节省空间。我们可以这样做是因为我们不再需要使用“git submodule init”或“git submodule update”。这是因为我们将共享/home/projects/library_XYZ的检出和.git目录,避免了本地副本的使用。
然后,我们让git将空的子模块目录重命名为“.submods/library_XYZ”,这是一个(隐藏的)目录,项目中的文件永远不会直接使用。
接下来,我们应用jthill的部分解决方案,并在.submods/library_XYZ中创建一个gitlink文件,使git将/home/projects/library_XYZ视为子模块的工作树和git仓库。
现在来了新东西:我们创建了一个符号链接,相对名称为“library_XYZ”,指向/home/projects/library_XYZ。这个符号链接将不会被纳入版本控制,因此我们将其添加到.gitignore文件中。
项目1和项目2中的所有构建文件都将使用library_XYZ符号链接,就像它是一个普通的子目录一样,但实际上在/home/projects/library_XYZ的工作树中查找文件。
除了Git之外,没有人会真正使用.submods/library_XYZ!
然而,由于符号链接./library_XYZ没有被版本控制,因此在检出项目1或项目2时不会被创建。因此,我们需要确保在缺失时会自动创建它。
这应该通过项目1/项目2的构建基础设施来完成,其等效于以下shell命令:
$ test ! -e library_XYZ && ln -s .submods/library_XYZ
例如,如果使用Makefile构建project1,并包含以下目标规则以更新子项目:
library_XYZ/libsharedutils.a:
cd library_XYZ && $(MAKE) libsharedutils.a
然后我们将上面的那一行作为规则动作的第一行插入:
library_XYZ/libsharedutils.a:
test ! -e library_XYZ && ln -s .submods/library_XYZ
cd library_XYZ && $(MAKE) libsharedutils.a
如果你的项目使用其他构建系统,通常可以通过创建用于创建library_XYZ子目录的自定义规则来完成相同的操作。
如果你的项目仅包含脚本或文档,并且根本不使用任何构建系统,你可以添加一个脚本,用户可以运行该脚本来创建“缺失的目录”(实际上是符号链接),如下所示:
(n=create_missing_dirs.sh && cat > "$n" << 'EOF' && chmod +x -- "$n")
for dir in .submods/*
do
sym=${dir#*/}
if test -d "$dir" && test ! -e "$sym"
then
echo "Creating $sym"
ln -snf -- "$dir" "$sym"
fi
done
EOF
这将在.submods中创建所有子模块检出的符号链接,但仅在它们不存在或损坏时才会创建。
到目前为止,传统子模块布局已转换为允许共享的新布局。
一旦您已经提交了该布局,请在某个地方检出超级项目,进入其顶级目录,并按以下顺序执行以启用共享:
sharedproject="/home/projects/library_XYZ"
submodule="library_XYZ"
ln -sn -- "$sharedproject" "$submodule"
echo "gitdir: $sharedproject.git" > ".submods/$submodule/.git"
我希望你能明白这个意思:project1和project2使用的library_XYZ子目录是一个未版本化的符号链接,而不是与“.gitmodules”中定义的子模块路径相对应。
构建基础设施将自动创建符号链接并将其指向.submods/library_XYZ,但是仅当符号链接不存在时才会这样做,这一点非常重要。
这使得可以手动创建符号链接,而不是让构建系统创建它,因此也可以将其指向单个共享的checkout,而不是.submods/library_XYZ。
这样,如果您想要在自己的机器上使用共享的checkout,就可以手动创建符号链接。
但是,如果另一个人什么都不做,只是检出project1并执行正常的“git submodule update --init library_XYZ”,那么事情仍然可以像没有共享checkout一样工作。
两种情况下都不需要更改已检出的构建文件!
换句话说,在不需要其他人遵循特殊说明的情况下,project1和project2的检出将像往常一样工作。
但是,通过在构建系统有机会创建符号链接之前手动创建gitlink文件和library_XYZ符号链接,您可以在本地“覆盖”符号链接并强制执行库的共享checkout。
甚至还有另一个好处:事实证明,如果使用上述解决方案,则根本不需要处理“git submodule init”或“git submodule update”:它可以自动工作!
这是因为"git submodule init"只是为了准备"git submodule update"而必要的。但是你不需要后者,因为库已经被检出到其他地方,并且已经有了自己的.git目录。所以"git submodule update"无需操作,我们也不需要它。
由于不再使用"git submodule update",也不需要.git/module子目录。对于子模块,也没有必要设置alternates(--reference选项)。
此外,在/home/projects/project1和/home/projects/project2中推送/拉取/home/projects/library_XYZ时,你也不需要任何远程内容。因此,可以从project1和project2中删除访问library_XYZ的远程内容。
这是一个双赢的局面!
这种解决方案唯一明显的缺点是它需要符号链接才能工作。
这意味着,在VFAT文件系统上无法检出project1等项目。
但是,即使这样做,像
http://sourceforge.net/projects/posixovl这样的项目仍然可以解决文件系统中任何符号链接限制的问题。
最后,给Windows用户一些建议:
自VISTA以来,符号链接可通过mklink命令使用,但它需要特殊权限。
但是,当使用sysinternals中的“junction”命令时,甚至在Windows XP时期就已经可以创建对目录的符号链接。
此外,你还可以选择使用CygWin,它可以(据我所知)即使在没有操作系统支持的情况下模拟符号链接。