在上面的示例中,我们向现有的Git repository添加了一个新的submodule。但是,反过来,当我们需要克隆一个已经包含submodule的仓库时,又会怎么样呢?
如果我们执行普通的git clone <remote-URL>,将会下载主项目,但任何submodule文件夹都是空的!这再次生动的证明了submodle文件是独立的,不包含在它们的父仓库中。
在这种情况下,要在克隆了父仓库之后填充submodule,可以简单地执行git submodule update --init --recursive。不过更好的方法是在调用git clone时直接添加--recurse-submodules选项。
在普通的Git仓库中,我们通过使用git checkout <branchname>或者在Git 2.23引入的git switch <branchname>,告诉git当前活动的分支是什么。当在这个分支上进行新的提交时,HEAD指针会自动移动到最近的提交。理解这一点很重要——因为Git submodule的工作方式不太一样!
在submodule中,我们总是签出一个特定的版本——而不是一个分支!即使在submodule中执行git checkout main这样的命令,在后台,也只会把该分支上当前最新的提交记录下来,而不会改变分支本身的内容。
这并不是系统错误,而是有意设计的。考虑一下:当我们引用第三方库时,希望完全控制在主项目中使用的确切代码。当库的维护者发布一个新版本(这当然很好),我们不一定希望这个新版本立马被应用到项目中,因为我们还不知道这些新的更改是否会破坏我们的项目!
如果想知道项目中的子模块使用的是什么版本,可以在主项目中查看以下信息:
$ git submodule status ea703a7d557efd90ccae894db96368d750be93b6 lib/spacetime (6.16.3)
上面显示了lib/spacetime子模块当前签出的版本,它还告诉我们这个版本是基于一个名为“6.16.3”的tag。在Git中处理submodule时,经常会使用tag。
要是我们希望submodule使用tag为“6.14.0”的旧版本。首先,我们必须更改目录,以便在子模块的上下文中执行Git命令。然后,我们可以基于tag执行git checkout:
$ cd lib/spacetime/ $ git checkout 6.14.0 Previous HEAD position was ea703a7 Merge pull request #301 from spencermountain/dev HEAD is now at 7f78d50 Merge pull request #268 from spencermountain/dev
如果我们回到主项目,再次执行git submodule status,我们会看到:
$ cd ../.. $ git submodule status +7f78d50156ae1205aa50675ddede81a61a45fade lib/spacetime (6.14.0)
仔细看一下输出:SHA-1哈希值前面的小+符号告诉我们,submodule的版本与当前存储在父仓库中的版本不同。由于我们刚刚更改了已签出的版本,因此这是正常的。
在主项目中调用git status也会告诉我们这个事实:
$ git status On branch master Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: lib/spacetime (new commits)
可以看到,Git认为移动submodule的指针和其他变化一样:如果我们保存这个改动,就必须提交到仓库里:
$ git commit -m "Changed checked out revision in submodule" $ git push
在上面的步骤中,是我们移动了submodule指针:我们选择签出一个不同的修订,提交它,并将它推送到团队的远程仓库中。但如果我们的一个同事更改了submodule的修订——可能是因为子模块发布了一个有趣的新版本,而我们决定在项目中使用它(当然是在彻底测试之后……)。
我们在主项目中做一个简单的git pull——我们可能会经常这么做——从共享远程仓库中获得新的更改:
$ git pull From https://github.com/gntr/git-crash-course d86f6e0..055333e main -> origin/main Updating d86f6e0..055333e Fast-forward lib/spacetime | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
倒数第二行表示子模块中的某些内容已被更改,让我们仔细看看:
$ git submodule status +7f78d50156ae1205aa50675ddede81a61a45fade lib/spacetime (6.14.0)
我相信你还记得那个小+号:它表示submodule指针被移动了!要将本地签出的版本更新为我们的团队成员选择的“官方”版本,可以运行update命令:
$ git submodule update lib/spacetime Submodule path 'lib/spacetime': checked out '5e3d70a88180879ae0222b6929551c41c3e5309e'
好了!我们签出了主项目仓库中记录的submodule版本!
我们已经介绍了使用Git submodule的基本概念,其他工作流程是相当标准的。
例如,检查子模块中新的更改的工作方式与其他任何Git repository类似:在submodule repository中运行git fetch命令,如果你确实想要使用这些更新,可能会接着执行类似git pull origin main的命令。
如果是自己管理的内部代码库,那也可能需要在子模块中进行更改。可以像处理任何其他Git仓库一样处理submodule:可以进行更改、提交更改、推送更改等等。
在表面简单的命令下面,Git提供了很强大的功能。但是它的许多高级工具——比如Git submodule——并不为人所知。这么多开发人员错过了很多强大的东西,这真是太遗憾了!
如果你想深入了解其他一些先进的Git技术,强烈推荐一个免费短视频合集:“Advanced Git Kit”[3],你可以学到Reflog、Interactive Rebase、Cherry-Picking,甚至分支策略等主题。
祝你成为一个更好的开发者!
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!