git subtree和submodule

git subtree 和 submodule

git subtree

git subtree 主要命令:

git subtree add --prefix=<prefix> <commit>
git subtree add --prefix=<prefix> <repository> <ref>
git subtree pull --prefix=<prefix> <repository> <ref>
git subtree push --prefix=<prefix> <repository> <ref>
git subtree merge --prefix=<prefix> <commit>

1、添加 subtree

git subtree add --prefix=sub/AndroidDemos git@gitee.com:hacketzeng/Android_Tools.git master --squash
// --squash参数表示不拉取历史信息,而只生成一条commit信息。

执行 git status 可以看到提示新增两条 commit
git push 到远端,其他开发人员 git clone,会完整拉取代码

2、子工程更新

git subtree pull --prefix=sub/AndroidDemos git@gitee.com:hacketzeng/Android_Tools.git master --squash

3、修改了子工程,如何 push 到远端

git subtree push

git submodule

git submodule 允许你将一个 Git 仓库作为另一个 Git 仓库的子目录。 它能让你将另一个仓库克隆到自己的项目中,同时还保持提交的独立。

添加子模块 add

语法:

git submodule add <git仓库地址> [<本地工程路径名>]

例如,将 AndroidDemos 工程添加到当前工程下。

git submodule add https://github.com/hacket/AppInit hacket/appinit
# 或者,添加以AppInit命名的目录路径
git submodule add https://github.com/hacket/AppInit

添加成功后,工程会增加 .gitmodules 文件,该配置文件保存了项目 URL 与已经拉取的本地目录之间的映射,这个需要提交到主工程版本管理:

[submodule "hacket/appinit"]
	path = hacket/appinit
	url = https://github.com/hacket/AppInit

克隆子模块:clone 一个包含 submodule 的 repo

主工程未 clone 的情况,--recurse-submodules

如果要 clone 一个项目,并且包含其子模块的文件,则需要给 git clone 命令最后加上 --recurse-submodules 或者 --recursive 参数(否则只会下载一个空的子模块文件):

git clone --recurse-submodules https://github.com/grpc/grpc
# 其中--recurse-submodules选项,它会自动初始化并更新仓库中的每一个子模块, 包括可能存在的嵌套子模块。

clone 时未带 submodule 相关参数

克隆一个包含 submodule 的仓库目录,未带 submodule 相关参数,并不会 clone 下子仓库的文件,只是会克隆下.gitmodule 描述文件,需要进一步克隆子仓库文件。

git clone ssh://xxx.git

# 初始化子模块相关配置
git submodule init
## 会自动在 config 中加入下面两行内容,可以用gift config --list查看
## submodule.xxx.url=git@github.com:yyy/xxx.git
## submodule.xxx.active=true

# 然后执行下面,就可以拉取到子模块仓库中的文件了
git submodule update

或者使用组合指令:

# 拉取到子模块仓库中的文件了,也可以将这两步命令合并为一步:
git submodule update --init
# 要拉取所有层层嵌套的子模块,则执行:
git submodule update --init --recursive

此时子目录在一个未命名分支 61f6637,此时子仓库有改动并没有检测到。

* (HEAD detached at 61f6637)
master
remotes/origin/HEAD -> origin/master
remotes/origin/dev
remotes/origin/master

其中 --recursive 选项和 git clone 时候的 --recurse-submodules 的含义是一样的;

修改拉取的 submodule 仓库默认分支

命令默认拉取主分支(master),想要修改这个默认拉取分支可以修改 .gitmodules 文件中子模块对应的 branch 值,或者执行:

git config submodule.xxx.branch dev

或者执行同时将配置写入文件,这样其他人拉取父项目也会获取该配置:

git config -f .gitmodules submodule.xxx.branch dev

更新子模块 update

默认情况下,在主仓库的根目录下,执行 git pull 命令并不会主动更新 submodule 最新的 commit

submodule update 概述

submodule 的更新一般有两个层面的更新,分别是:

  1. 拉取 submodule 子项目最新的提交,如果子项目对应的分支上有更新,那么会拉取下来,并且修改主项目跟踪的改子项目的 commitId;这种情况一般是:明确需要跟踪子项目的特定提交,使用其新特性;
git submodule update --remote
  1. 拉取项目中跟踪的 submodule 的 commitId 对应的数据到本地;这种情况一般都是项目的开发者拉取 submodule 是否有跟踪的变化,更新一下对应的数据;如果 submodule 的子项目有最新的,只会拉取项目跟踪的 commitId,不一定是最新的
git submodule update

git submodule update --remote 命令结果:

hzktg

submodule update 策略

submodule update --rebase 后 submodule 项目的状态变成了 detached:
51na2
命令 git submodule update 更新子项目的策略有三种:

git submodule update --remote
git submodule update --remote --rebase
git submodule update --remote --merge

两种方式设置更新策略:

git config -f .gitmodules .modules/si_basic_flutter.update rebase

向上同步主仓库

如果你是子模块的开发者,当你希望主仓库使用新版本的子模块时,需要向上同步主仓库。
fubcq
在主仓库中,可以使用 git submodule status 和 git status 查看子模块的状态:显示为 + 号表示主仓库和子仓库未同步:
qjztf
如果想要将子模块的内容同步到主仓库,只需要在主仓库中创建一个新的提交即可

submodule uodate 相关命令

只拉取主工程跟踪的子模块的 commitId 对应的代码,子模块有最新的提交不更新
拉取子模块最新的代码,并更新主工程跟踪的 commitId

跟踪 submodule 特定分支

想换 submodule 的分支或 tag,进入到 submodule 目录,然后 git checkout xxx 指定分支即可
GIt Fork 直接点开对应的 submodule 操作即可。
e1qa1

查看子模块状态

git submodule

git submodule status

6iw7v

push 带修改的 submodule 的主工程

存在的一个问题:主工程跟踪的 commit 修改提交了,但子模块修改未提交

如果 A 修改了子模块的内容并提交了记录,父项目也提交并推送了远程仓库(更新了跟踪的 commitId),但是子模块没有推送其对应的远程仓库, A 运行起来没有什么问题,那么 B 拉取父项目代码变更时没有问题,但是更新子模块时就会遇到下面的问题:

fatal: remote error: upload-pack: not our ref 16d1b6b94e3245f3a7fb4f43e5b6f44b14027fbb
Fetched in submodule path 'xxx', but it did not contain 16d1b6b94e3245f3a7fb4f43e5b6f44b14027fbb.
Direct fetching of that commit failed.

**原因:**即由于其他人没有及时将子模块的提交 push 的子模块的远程仓库,我们本地父项目有了关于子模块最新的变更,但是在子模块的仓库中却找不到,就报错了
**解决:**让对方在子模块下 push 一下这边再重新更新就行了;

主工程和子模块同时推送

目标:修改了 submoudle 的内容,在推送主项目的时候,也希望推送跟踪的子 submodule 项目

如果子模块(与父项目记录的对应分支)存在未 push 的提交,就会报错,并且子模块有推送失败的,父项目也会推送失败;

git push --recurse-submodules=check
# 配置到config,避免每次手动输入
git config push.recurseSubmodules check
git push --recurse-submodules --on-demand
git config --global push.recurseSubmodules=on-demand

与 --on-demand 含义一样;不过这里为什么不使用 git config --global submodule.recurse true 的设置呢?submodule.recurse 的配置不仅不对 clone 生效,在 push 的时候也不生效

游离的 HEAD(git submodule head detached)

在使用 git submodule 的时候,常常会遇到 执行完以下操作后发现 子仓库的 head 指针处于游离状态
在主工程修改子模块的代码后提交,出现这个弹窗提示
ybe4k
解决:

  1. 首先删除已有的 submodule
  2. 重新建立 submodule,加入时使用 -b 参数,使得母项目追踪子项目的指定 branch(否则默认不追踪):
git submodule add -b <branch> <repository> [<submodule-path>]
# 如
git submodule add -b master git@github.com:hacket/DebugTools.git submodules/debugtools
  1. 更新
git submodule update --remote

简单的一行命令递归修复所有子项目的 detached head(其中默认都追踪子项目的 master branch):

git submodule foreach -q --recursive 'git checkout $(git config -f $toplevel/.gitmodules submodule.$name.branch || echo master)'

修改子模块

如果在本地修改 submodule 工程,在主仓库 git status 会显示 submodule 仓库有修改。但是在主工程进行 git push 没用,现在需要进去子仓库进行提交

$ git add .
$ git commit -m "commit"
$ git push origin HEAD:master

删除子模块

git submodule deinit -f 子模块名 删除了子模块,则再次查看时输出会是这样的,删除的子模块前面加了个 -
jvp4y

git submodule deinit -f AndroidDemos
git rm --cached AndroidDemos
# git rm --cached <本地路径>
rm -rf AndroidDemos

如果是带了目录的,需要加上目录
0yjgl

批量修改子模块 foreach

git submodule foreach [git 操作]

git submodule foreach 'git stash'
git submodule foreach 'git checkout -b new'

submodule 主要问题

主工程一个分支有 submodule,一个分支没有 submodule,分支切换问题

父项目从含有子模块的分支切换到没有子模块的分支时,默认会保留子模块对应的目录,所以这使得切换过去时本地会保留关于子模块的修改记录,显然这不太合理,所以从包含子模块的分支切换到 xxx 时,需要这样执行:

git checkout xxx --recurse-submodules

submodule 的 url 被修改了

如果父项目中子模块的仓库地址(submodule.xxx.url)被其他协作者修改了,那么我们再更新子模块时就可能遇到问题,需要执行:

git submodule sync --recursive

同步完 url,然后再重新初始化更新:

git submodule update --int --recursive

忽略 submodule 中的修改或新增文件

.gitmodules 添加 ignore 字段,ignore 意义:

untracked :忽略 在子模块B(子模块目录)新添加的,未受版本控制内容
dirty : 忽略对子模块目录下受版本控制的内容进行了修改
all : 同时忽略untracked和dirty

添加了 ignore 后,子模块的修改,在主工程用 git status 就看不到了

detached head does not point any branch

在项目哪个拉去

submodule 冲突

  1. 将子仓库的冲突解决后,push
  2. 然后同步主仓库的 commit,主仓库对子仓库的引用可能也会导致冲突,将主仓库对准最新的子仓库的的最新 commit 即可

9sj4e