/

【Git 权威指南】读书笔记 - 协同模型

主要内容:【Git 协同模型】

经典 Git 协同模型

集中式协同模型

可以像集中式版本控制系统那样使用 Git,在一个大家都可以访问到的服务器上架设 Git 服务器,每个人从该服务器克隆代码,本地提交推送到服务器上。

金字塔式协同模型

虽然理论上每个开发者的版本库都是平等的,但是会有一个公认的权威的版本库,这个版本库由一个或者多个核心开发者负责维护(具有推送的权限)。

开源社区逐渐发展出金字塔模型,而这也是必然之选。

Topgit 协同模型

笔者注:Topgit 是否已经过时?

卖主分支 Vendor Branch 是在版本库中专门创建一个和上游同步的分支,一旦有上游代码发布就捡入到卖主分支中。

子模组协同模型

创建子模组

$ git submodule add /path/to/repos/libA.git lib/lib_a

.gitmodules 的内容:

$ cat .gitmodules
[submodule "lib/lib_a"]
path = lib/lib_a
url = /path/to/repos/libA.git

克隆带子模组的版本库

$ git clone /path/to/repos/super.git /path/to/my/workspace/super-clone

子模组的版本库并不会默认克隆,如果需要克隆出子模组型式引用的外部库,需要执行:

$ git submodule init
$ git submodule update

在子模组中修改和子模组的更新

修改更新的方式和普通仓库一样。如果修改了子模块,要先推送子模块的修改,再推送主仓库,以防止其他人克隆 super 版本库、更新模组时因为找不到该子模组版本库相应的提交而导致出错。

查看子模组状态:

$ git submodule status

子树合并

引入外部版本库

# 注册外部版本库
$ git remote add util /path/to/repos/util.git

$ git fetch util

# 查看所有分支
$ git branch -a

# 从 util/master 远程分支创建一个本地分支 util-branch
$ git checkout -b util-branch util/master

子目录方式合并外部版本库

# 在主分支,将分支 util-branch 读取到当前分支的一个子目录下
$ git read-tree --prefix=lib util-branch

# 将 lib 目录下的文件更新出来
$ git checkout -- lib

现在还不能忙着提交,因为如果现在进行提交就体现不出来两个分支的合并关系。需要使用 Git 底层的命令进行数据提交。

将暂存区的目录树保存下来

$ git write-tree

要手工创建一个合并提交,即新的提交要有两个父提交。这两个父提交分别是 master 分支和 util-branch 分支的最新提交。

$ echo "subtree merge" | \
git commit-tree 2153518409d218609af40babededec6e8ef51616 \
-p 911b1af2e0c95a2fc1306b8dea707064d5386c2e \
-p 12408a149bfa78a4c2d4011f884aa2adb04f0934
62ae6cc3f9280418bdb0fcf6c1e678905b1fe690

需要把当前的 master 分支重置到此提交 ID:

$ git reset 62ae6cc3f9280418bdb0fcf6c1e678905b1fe690

操作繁琐,可使用下面 subtree 命令进行代替。

利用子树合并跟踪上游改动

$ git checkout util-branch

$ git pull

$ git checkout master

$ git merge -Xsubtree=lib util-branch

Git Subtree

管理子项目

假设 P1 项目P2 项目 共用 S 项目

  • 关联 S 项目
git subtree add --prefix=<S项目的相对路径> <S项目git地址> <分支> --squash

--squash 意思是把 subtree 的改动合并成一次 commit,这样就不用拉取子项目完整的历史记录。--prefix 之后的 = 等号也可以用空格。

  • 更新代码

P1、P2 项目里各种提交 commit,其中有些 commit 会涉及到 S 目录 的更改,但是没关系。

  • 提交更改到子项目
git subtree push --prefix=<S项目的相对路径> <S项目git地址> <分支>

Git 会遍历 步骤 2 中所有的 commit,从中找出针对 S 目录 的更改,然后把这些更改记录提交到 S 项目 的 Git 服务器上,并保留 步骤 2 中的相关 S 的提交 记录到 S仓库 中。

  • 更新子目录
git subtree pull --prefix=<S项目的相对路径> <S 项目 git 地址> <分支> --squash

拆分已有项目

需要从现有项目中抽取公共模块单独进行 Git 管理,假设已有 项目 P 抽取 项目 S

  • 提交日志分离
git subtree split -P <S项目的相对路径> -b <临时branch>

Git 会遍历所有的 commit,分离出与 S 项目的相对路径相关的 commit,并存入临时 branch 中。

  • 创建子 repo
mkdir <S项目新路径>
cd S项目新路径
git init
git pull <P项目的路径> <临时branch>
git remote add origin <S项目的git仓库>
git push origin -u master
  • 清理数据
cd P项目的路径
git rm -rf <S项目的相对路径>
git commit -m '移除相应模块' # 提交删除申请
git branch -D <临时branch> # 删除临时分支
  • 添加 subtree
git subtree add --prefix=<S项目的相对路径> <S项目git地址> <分支> --squash
git push origin master

执行完第 2 步时,对应的目录已经剥离出来形成独立的项目了。第 3,4 步主要是把当前项目的对应的文件给删除,重新在 P 项目建立 Subtree。

tip:
推送到 GitHub Page:

git subtree push --prefix=dist origin gh-pages

Reference: