我是Git的新手。可以说,我有两个git存储库,它们添加了与子模块相同的库:
/home/projects/project1/library_XYZ
/home/projects/project2/library_XYZ
还可以说,我正在同时处理项目和库。当我对库进行更改时,让我们说在/home/projects/project1/library_XYZ
中,我必须推动这些更改然后将它们拉入/home/projects/project2/library_XYZ
以使它们可用于project2
,对吧?我认为这是不方便的,原因有两个:
library_XYZ
。有没有办法让Git克隆子模块library_XYZ
到同一本地目录,即使文件组织如下
/home/projects/project1
/home/projects/project2
/home/projects/library_XYZ
而library_XYZ
仍然是两个项目的子模块?
我认为这可能与this有关,虽然我的设置有些不同,但是没有答案。
将共享依赖项设置为子模块很容易。 git submodule
命令不会自动执行,但子模块只不过是一个嵌套的存储库 - 而git不需要任何实际的存储库或其工作树在任何特定的位置。
设置libraryXYZ repo以用作共享子模块
# a submodule is just a repository. We're going to share this one.
git clone u://r/libraryXYZ
# and keep its worktree right here:
( cd libraryXYZ; git config core.worktree .. )
然后,从任何地方,使用子模块克隆项目并将其设置为使用共享模块:
git clone u://r/project1
cd project1
git submodule init
echo gitdir: path/to/shared/libraryXYZ/.git > libraryXYZ/.git
现在project1
的libraryXYZ
子模块将使用共享的libraryXYZ
repo和worktree。
设置您的构建系统以使用相同的工作树,您就完成了。你当然可以得到git来告诉你那些在任何给定的回购中:
# for example to see where all a project's submodules' parts are kept
git submodule foreach git rev-parse --git-dir --show-toplevel
# or for just one:
git --git-dir=$project1/libraryXYZ/.git rev-parse --git-dir --show-toplevel
(后期编辑:@twalberg's remark值得记住,这可能会使得从一个项目中执行git submodule update
变得有点太容易,而没有意识到您还改变了共享依赖项的每个其他项目的构建环境。)
我遇到了和你一样的问题:作为子模块的大型通用实用程序库,以及许多依赖它的项目。我不想为实用程序库的每个实例创建单独的签出。
jthill建议的解决方案工作正常,但它只解决了问题的前半部分,即如何让git满意。
缺少的是如何保持您的构建系统满意,期望实际文件可以使用并且不关心gitlink引用。
但如果你将他的想法与符号链接结合起来,你就会得到你想要的!
为了实现这一点,让我们从您的示例中的项目开始
/home/projects/project1
/home/projects/project2
/home/projects/library_XYZ
假设project1和project2都已将library_XYZ添加为子模块,并且当前所有三个项目都包含library_XYZ的完整检出。
要通过共享符号链接替换库子结构的完整签出,请执行以下操作:
sharedproject="/home/projects/library_XYZ"
superproject="/home/projects/project1"
submodule="library_XYZ"
cd "$superproject"
(cd -- "$submodule" && git status) # Verify that no uncommited changes exist!
(cd -- "$submodule" && git push -- "$sharedproject") # Save any local-only commits
git submodule deinit -- "$submodule" # Get rid of submodule's check-out
rm -rf .git/modules/"$submodule" # as well as of its local repository
mkdir -p .submods
git mv -- "$submodule" .submods/
echo "gitdir: $sharedproject.git" > ".submods/$submodule/.git"
ln -s -- "$sharedproject" "$submodule"
echo "/$submodule" >> .gitignore
然后为$ superproject重复/ home / projects / project2的相同步骤。
以下是对已完成的解释:
首先使用“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。
当然,在/ home / projects / library_XYZ中并不存在该名称的远程名称,但保存的分支将显示为就像在那里执行“git branch -r”时那样。
作为安全措施,上述命令中的refspec不以“+”开头,因此“git push”不会意外覆盖/ home / projects / library_XYZ中已经存在的任何分支。
接下来,将删除.git / modules / library_XYZ以节省空间。我们可以这样做,因为我们不再需要使用“git submodule init”或“git submodule update”。这是因为我们将与子模块共享check-out和/ home / projects / library_XYZ的.git目录,避免两者的本地副本。
然后我们让git将空的子模块目录重命名为“.submods / library_XYZ”,这是一个(隐藏的)目录,项目中的文件永远不会直接使用。
接下来,我们将jthill的部分解决方案应用于问题,并在.submods / library_XYZ中创建一个gitlink文件,这使得git将/ home / projects / library_XYZ视为工作树和子模块的git repo。
现在出现了新的事情:我们创建了一个符号链接,其相对名称为“library_XYZ”,指向/ home / projects / library_XYZ。此符号链接不会置于版本控制之下,因此我们将其添加到.gitignore文件中。
project1和project2中的所有构建文件都将使用library_XYZ符号链接,就像它是一个普通的子目录一样,但实际上是从/ home / projects / library_XYZ中的工作树中找到文件。
没有人除了git实际上使用.submods / library_XYZ!
但是,由于符号链接./library_XYZ未进行版本控制,因此在签出project1或project2时不会创建它。因此,我们需要注意它将在缺失时自动创建。
这应该由project1 / project2的构建基础结构完成,其命令等效于以下shell命令:
$ test ! -e library_XYZ && ln -s .submods/library_XYZ
例如,如果project1是使用Makefile构建的,并且包含以下用于更新子项目的目标规则
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")
#! /bin/sh
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,但仅限于此,如果符号链接尚不存在,这一点很重要。
这允许人们手动创建符号链接而不是让构建系统创建符号链接,因此也可以指向单个共享签出而不是.submods / library_XYZ。
这样,您可以根据需要在计算机上使用共享签出。
但是如果另一个人没有做任何特别的事情并且只检查project1并执行正常的“git子模块更新--init library_XYZ”,那么在没有共享签出的情况下,情况会相同。
在任何一种情况下都不需要更改签出的构建文件!
换句话说,project1和project2的签出将像往常一样开箱即用,其他使用您的仓库的人不需要遵循特殊说明。
但是,在构建系统有机会创建符号链接之前手动创建gitlink文件和library_XYZ符号链接,您可以在本地“覆盖”符号链接并强制执行库的共享签出。
还有另外一个优点:事实证明,如果您使用上述解决方案,您根本不需要使用“git submodule init”或“git submodule update”:它只是没有!
这是因为“git submodule init”仅作为“git子模块更新”的准备工作。但是你不需要后者,因为库已经在其他地方检出,并且在那里已经有了自己的.git目录。所以“git submodule update”无关,我们不需要它。
作为不再使用“git submodule update”的副作用,也不需要.git / module子目录。也不需要为子模块设置替换( - 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,即使没有操作系统的支持,它也可以(AFAIK)模拟符号链接。
你可以从git 2.5开始做这些:
cd /home/projects/project1/library_XYZ
git worktree add ../../project2/library_XYZ project2
现在,/ home /projects / project1 /.git / modules / library_XYZ在两个项目之间共享。
在Linux上,简单地说
sudo mount -o bind /home/projects/library_XYZ /home/projects/project1/library_XYZ
如果你已经初始化了子模块,也许你也应该做一下Guenther Brunthaler之前的建议。
cd /home/projects/project1/
git submodule deinit library_XYZ
rm -rf .git/modules/library_XYZ
我选择了一种更简单的方法(imho):
我按如下方式设置了共享子模块:
对于共享此子模块的所有项目:
旁注:这似乎与Sourcetree无关。由于某种原因,Sourcetree没有获得对项目的引用,并且始终假定文件被删除(由于步骤5)。虽然它使用命令行完美无瑕地工作,这使我相信设置是正确的,但Sourcetree被窃听。