Git:可以通过多个项目使用相同的子模块工作副本吗?

the*_*0ID 25 git git-submodules

我是Git的新手.可以说,我有两个git存储库,它们添加了与子模块相同的库:

/home/projects/project1/library_XYZ
/home/projects/project2/library_XYZ
Run Code Online (Sandbox Code Playgroud)

还可以说,我正在同时处理项目库.当我对库进行更改时,让我们说/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
Run Code Online (Sandbox Code Playgroud)

同时library_XYZ仍然是两个项目的子模块?

我认为这可能与有关,虽然我的设置有些不同,但是没有答案.

jth*_*ill 20

将共享依赖项设置为子模块很容易.该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 .. )
Run Code Online (Sandbox Code Playgroud)

然后,从任何地方,使用子模块克隆项目并将其设置为使用共享模块:

git clone u://r/project1
cd project1
git submodule init
echo gitdir: path/to/shared/libraryXYZ/.git > libraryXYZ/.git
Run Code Online (Sandbox Code Playgroud)

现在project1libraryXYZ子模块将使用共享libraryXYZ仓库和工作树.

设置您的构建系统以使用相同的工作树,您就完成了.你当然可以得到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
Run Code Online (Sandbox Code Playgroud)

(后期编辑:@ twalberg的评论值得记住,这可能会使得git submodule update从一个项目做一个太简单而没有意识到你也改变了共享依赖项的每个其他项目的构建环境.)


Gue*_*ler 8

我遇到了和你一样的问题:作为子模块的大型通用实用程序库,以及许多依赖它的项目.我不想为实用程序库的每个实例创建单独的签出.

jthill建议的解决方案工作正常,但它只解决了问题的前半部分,即如何保持git的快乐.

缺少的是如何保持您的构建系统满意,期望实际文件可以使用并且不关心gitlink引用.

但如果你将他的想法与符号链接结合起来,你就会得到你想要的!

为了实现这一点,让我们从您的示例中的项目开始

/home/projects/project1
/home/projects/project2
/home/projects/library_XYZ
Run Code Online (Sandbox Code Playgroud)

假设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
Run Code Online (Sandbox Code Playgroud)

然后为$ 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/*")
Run Code Online (Sandbox Code Playgroud)

这将保存子模块本地存储库的所有分支的备份,作为/ 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
Run Code Online (Sandbox Code Playgroud)

例如,如果project1是使用Makefile构建的,并且包含以下用于更新子项目的目标规则

library_XYZ/libsharedutils.a:
        cd library_XYZ && $(MAKE) libsharedutils.a
Run Code Online (Sandbox Code Playgroud)

然后我们从上面插入行作为规则动作的第一行:

library_XYZ/libsharedutils.a:
        test ! -e library_XYZ && ln -s .submods/library_XYZ
        cd library_XYZ && $(MAKE) libsharedutils.a
Run Code Online (Sandbox Code Playgroud)

如果您的项目正在使用其他构建系统,您通常可以通过创建用于创建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
Run Code Online (Sandbox Code Playgroud)

这将为.submods中的所有子模块检出创建符号链接,但前提是它们尚不存在或者它们已损坏.

到目前为止,从传统的子模块布局转换到允许共享的新布局.

一旦您已经提交了该布局,请检查某个地方的超级项目,转到其顶级目录,然后执行以下操作以启用共享:

sharedproject="/home/projects/library_XYZ"
submodule="library_XYZ"
ln -sn -- "$sharedproject" "$submodule"
echo "gitdir: $sharedproject.git" > ".submods/$submodule/.git"
Run Code Online (Sandbox Code Playgroud)

我希望你明白这一点: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)模拟符号链接.

  • `echo "gitdir: $sharedproject.git" &gt; ".submods/$submodule/.git"` 行似乎缺少一个“/”。现在的情况是,它会导致 `git status` 返回一条致命消息。不过,行 `echo "gitdir: $sharedproject/.git" &gt; ".submods/$submodule/.git"` 在我对此答案的测试中有效。 (2认同)