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

时间:2014-12-09 13:14:35

标签: git git-submodules

我是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有关,虽然我的设置有所不同,但仍然没有答案。

5 个答案:

答案 0 :(得分:19)

将子共享依赖项设置为子模块很容易。 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的{​​{1}}子模块将使用共享的libraryXYZ repo和worktree。

设置您的构建系统以使用相同的工作树,您就完成了。当然,您可以通过git告诉您在任何给定的回购中它们的位置:

libraryXYZ

(晚期编辑:@twalberg's remark值得记住,这可能会让您从一个项目中轻松地执行# 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 而不会意识到您还改变了其他所有项目的构建环境共享依赖项的项目。)

答案 1 :(得分:7)

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

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

然后对/ 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。

当然,/ home / projects / library_XYZ中确实没有该名称的远程名称,但保存的分支将显示为" git branch -r"将在那里执行。

作为一种安全措施,上述命令中的refspec不以" +"开头,因此" git push"不能意外覆盖/ home / projects / library_XYZ中已经存在的任何分支。

接下来,将删除.git / modules / library_XYZ以节省空间。我们可以这样做,因为我们不再需要使用" git submodule init"或者" git子模块更新"。这是因为我们将共享/ 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子目录是一个 unversioned 符号链接,而不是对应于&#34; .gitmodules&#34;中定义的子模块路径。 / p>

符号链接将由构建基础结构本身自动创建,然后指向.submods / library_XYZ,但仅限于此,如果符号链接尚不存在,这很重要。

这允许人们手动创建符号链接而不是让构建系统创建符号链接,因此也可以指向单个共享签出而不是.submods / library_XYZ。

如果您愿意,可以在机器上使用共享结账。

但是,如果另一个人没有做任何特别的事情,只是检查出project1并执行正常的&#34; git子模块更新--init library_XYZ&#34;,没有共享的结账,情况会一样。

在任何一种情况下都不需要更改签出的构建文件!

换句话说,project1和project2的签出将像往常一样开箱即用,其他人使用您的仓库时不需要特别说明。

但是,在构建系统有机会创建符号链接之前手动创建gitlink文件和library_XYZ符号链接,您可以在本地&#34;覆盖&#34;符号链接并强制执行库的共享签出。

还有另外一个优点:事实证明,你不需要捣乱&#34; git submodule init&#34;或者&#34; git子模块更新&#34;如果您使用上述解决方案,那就完全没有了!

这是因为&#34; git submodule init&#34;仅作为&#34; git子模块更新&#34;的准备工作。但是你不会需要后者,因为库已经在其他地方检出过了,并且在那里已经有了自己的.git目录。所以&#34; git submodule update&#34;没什么可做的,我们也不需要它。

作为不再使用&#34; git submodule update&#34;的副作用,也不需要.git / module子目录。也不需要为子模块设置替换( - reference选项)。

此外,您不再需要任何遥控器来推送/拉/ home / projects / library_XYZ / home / projects / project1和/ home / projects / project2。因此,您可以从project1和project2中删除用于访问library_XYZ的遥控器。

双赢局面!

此解决方案唯一明显的缺点是它需要符号链接才能工作。

这意味着,在VFAT文件系统上检查project1是不可能的。

但那么,谁做到了?

即使这样做,像http://sourceforge.net/projects/posixovl这样的项目仍然可以解决文件系统的任何符号链接限制。

最后,为Windows用户提供了一些建议:

自VISTA以来,通过mklink命令可以使用符号链接,但它需要特殊权限。

但是当使用来自sysinternals的&#34; junction&#34; -command时,甚至在Windows XP时代就已经创建了目录的符号链接。

此外,您可以选择使用CygWin,即使没有操作系统的支持,也可以(AFAIK)模拟符号链接。

答案 2 :(得分:0)

你可以这样做,因为git 2.5:

  1. 删除 / home / projects / project 2 / library_XYZ
  2. 删除 / home / projects / project 2 / .git / modules / library_XYZ
  3. cd /home/projects/project1/library_XYZ
  4. / home / projects / project 1 / library_XYZ
  5. 中创建分支 project2
  6. 运行git worktree add ../../project2/library_XYZ project2
  7. 现在, /home/projects/project1/.git/modules/library_XYZ 在两个项目之间共享。

答案 3 :(得分:0)

在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

答案 4 :(得分:0)

我选择了一种更简单的方法(恕我直言):

我按如下所示设置共享子模块:

  1. 将应作为共享子模块的项目克隆到某个目录本地
  2. 在共享子模块中,通过在[core]下添加“ worktree = ..”来修改此项目的本地配置。

对于共享此子模块的所有项目:

  1. 添加子模块(这意味着,实际上我现在有该项目的两个签出副本)
  2. 在./git/submodule/
  3. 中删除相应的子模块文件夹
  4. 删除.module文件以外的子模块目录的内容
  5. 修改子模块目录中的.git以指向1)中项目设置的路径

旁注:这似乎不适用于Sourcetree。由于某些原因,Sourcetree没有获得对项目权限的引用,并且始终假定文件已删除(由于步骤5)。但是,它可以使用命令行完美地运行,这使我相信设置是正确的,但是Sourcetree却有问题。