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

问题描述 投票:25回答:5

我是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 git-submodules
5个回答
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

现在project1libraryXYZ子模块将使用共享的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变得有点太容易,而没有意识到您还改变了共享依赖项的每个其他项目的构建环境。)


8
投票

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

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)模拟符号链接。


1
投票

你可以从git 2.5开始做这些:

  1. 删除/ home / projects / project2 / library_XYZ
  2. 删除/home/projects/project2/.git/modules/library_XYZ
  3. cd /home/projects/project1/library_XYZ
  4. 在/ home / projects / project1 / library_XYZ中创建一个分支project2
  5. 运行git worktree add ../../project2/library_XYZ project2

现在,/ home /projects / project1 /.git / modules / library_XYZ在两个项目之间共享。


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

0
投票

我选择了一种更简单的方法(imho):

我按如下方式设置了共享子模块:

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

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

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

旁注:这似乎与Sourcetree无关。由于某种原因,Sourcetree没有获得对项目的引用,并且始终假定文件被删除(由于步骤5)。虽然它使用命令行完美无瑕地工作,这使我相信设置是正确的,但Sourcetree被窃听。

© www.soinside.com 2019 - 2024. All rights reserved.