22.4 Topgit的使用

通过前面的原理部分,可以发现Topgit为管理特性分支所引入的配置文件和基准分支都是和Git兼容的。

在refs/top-bases/命名空间下的引用,用于记录特性分支的基准分支。

在特性分支的工作区根目录下引入两个文件.topdeps和.topmsg,用于记录分支的依赖和说明。

引入新的钩子脚本hooks/pre-commit,用于在提交时检查分支依赖有没有发生循环等。

Topgit的命令行的一般格式为:


tg[global_option]<subcmd>[command_options…][arguments…]


说明:在子命令前的全局选项,目前可用的只有-r<remote>,用于设定远程版本库,默认为origin。在子命令后可以跟子命令相关的参数。

下面就来介绍Topgit常用的子命令。

1.tg help命令

tg help命令显示帮助信息。在tg help后面提供子命令名称,可以获得该子命令详细的帮助信息。

2.tg create命令

tg create命令用于创建新的特性分支。用法如下:


tg[…]create NAME[DEPS…|-r RNAME]


其中:

NAME是新的特性分支的分支名,必须提供。一般约定俗成:NAME以t/前缀开头的表明此分支是一个Topgit特性分支。

DEPS……是可选的一个或多个依赖分支名。如果不提供依赖分支名,则使用当前分支作为新的特性分支的依赖分支。

-r RNAME选项,将远程分支作为依赖分支,不常用。

tg create命令会创建新的特性分支refs/heads/NAME,以及特性分支的基准分支refs/top-bases/NAME,并且在项目根目录下创建文件.topdeps和.topmsg。还会提示用户编辑.topmsg文件,输入详细的特性分支描述信息。

为了试验Topgit命令,找一个示例版本库或干脆创建一个版本库。在示例版本库的master分支下输入如下命令创建一个名为t/feature1的特性分支:


$tg create t/feature1

tg:Automatically marking dependency on master

tg:Creating t/feature1 base from master…

Switched to a new branch 't/feature1'

tg:Topic branch t/feature1 set up.Please fill.topmsg now and make initial commit.

tg:To abort:git rm-f.top*&&git checkout master&&tg delete t/feature1


提示信息中以"tg:"开头的是Topgit产生的说明。其中提示用户编辑,topmsg文件,然后执行一次提交操作完成Topgit特性分支的创建。

如果想撤销此次操作,删除项目根目录下的.top*文件,切换到master分支,然后执行tg delete t/feature1命令删除t/feature1分支及特性分支的基准分支refs/top-bases/t/feature1。

输入git status可以看到当前已经切换到t/feature1分支,并且Topgit已经创建了.topdeps和.topmsg文件,并已将这两个文件加入到暂存区。


$git status

On branch t/feature1

Changes to be committed:

(use "git reset HEAD<file>…"to unstage)

#

new file:.topdeps

new file:.topmsg

#

$cat.topdeps

master


打开.topmsg文件,会看到下面的内容(前面增加了行号):


1 From:Jiang Xin<jiangxin@ossxp.com>

2 Subject:[PATCH]t/feature1

3

4 <patch description>

5

6 Signed-off-by:Jiang Xin<jiangxin@ossxp.com>


其中第2行是关于该特性分支的简短描述,第4行是详细描述,可以写多行。编辑完成,别忘了提交,提交之后才完成Topgit分支的创建。


$git add-u

$git commit-m "create tg branch t/feature1"


如果这时想创建一个新的特性分支t/feature2,并且也是要依赖master,注意需要在命令行中提供master作为第二个参数,以设定依赖分支。因为当前所处的分支为t/feature1,如果不提供指定的依赖分支就会自动依赖当前分支。


$tg create t/feature2 master

$git commit-m "create tg branch t/feature2"


下面的命令将创建t/feature3分支,该分支依赖t/feature1和t/feature2。


$tg create t/feature3 t/feature1 t/feature2

$git commit-m "create tg branch t/feature3"


3.tg info命令

tg info命令用于显示当前分支或指定的Topgit分支的信息。用法如下:


tg[…]info[NAME]


其中NAME是可选的Topgit分支名。例如执行下面的命令会显示分支t/feature3的信息:


$tg info

Topic Branch:t/feature3(1/1 commit)

Subject:[PATCH]t/feature3

Base:0fa79a5

Depends:t/feature1

t/feature2

Up-to-date.


切换到t/feature1分支,做一些修改并提交:


$git checkout t/feature1

$echo Hi>hacks.txt

$git add hacks.txt

$git commit-m "hacks in t/feature1."


然后再来看t/feature3的状态:


$tg info t/feature3

Topic Branch:t/feature3(1/1 commit)

Subject:[PATCH]t/feature3

Base:0fa79a5

Depends:t/feature1

t/feature2

Needs update from:

t/feature1(1/1 commit)


状态信息显示t/feature3不再是最新的状态(Up-to-date),因为依赖的t/feature1分支包含新的提交,而需要从t/feature1获取更新。

4.tg update命令

tg update命令用于更新分支,即从依赖的分支获取最新的提交合并到当前分支。同时在refs/top-bases/命名空间下的特性分支的基准分支也会更新。


tg[…]update[NAME]


其中NAME是可选的Topgit分支名。下面就对需要更新的t/feature3分支执行tg update命令。


$git checkout t/feature3

$tg update

tg:Updating base with t/feature1 changes…

Merge made by recursive.

hacks.txt|1+

1 files changed,1 insertions(+),0 deletions(-)

create mode 100644 feature1

tg:Updating t/feature3 against new base…

Merge made by recursive.

hacks.txt|1+

1 files changed,1 insertions(+),0 deletions(-)

create mode 100644 feature1


从上面的输出信息可以看出执行了两次分支合并操作,一次是针对refs/top-bases/t/feature3引用指向的特性分支的基准分支,另外一次针对的是refs/heads/t/feature3特性分支。

执行tg update命令因为要涉及分支的合并,因此并非每次都会成功。例如在t/feature3和t/feature1中同时对同一个文件(如hacks.txt)进行修改。然后在t/feature3中再执行tg update可能就会报错,进入冲突解决状态。


$tg update t/feature3

tg:Updating base with t/feature1 changes…

Merge made by recursive.

hacks.txt|1+

1 files changed,1 insertions(+),0 deletions(-)

tg:Updating t/feature3 against new base…

Auto-merging hacks.txt

CONFLICT(content):Merge conflict in hacks.txt

Automatic merge failed;fix conflicts and then commit the result.

tg:Please commit merge resolution.No need to do anything else

tg:You can abort this operation usinggit reset—hardnow

tg:and retry this merge later usingtg update.


可以看出第一次对refs/top-bases/t/feature3引用指向的特性分支的基准分支合并成功,但对t/feature3特性分支进行的合并出错了。

执行tg info命令查看一下当前分支t/feature3的状态。


$tg info

Topic Branch:t/feature3(3/2 commits)

Subject:[PATCH]t/feature3

Base:37dcb62

*Base is newer than head!Please run 'tg update'.

Depends:t/feature1

t/feature2

Up-to-date.


从上面tg info命令的输出可以看出当前分支的状态是Up-to-date,但是输出中包含一个提示:分支的基(Base)要比头(Head)新,请执行tg update命令。

这时如果执行tg summary命令的话,可以看到t/feature3处于B(Break)状态。


$tg summary

t/feature1[PATCH]t/feature1

0 t/feature2[PATCH]t/feature2

>B t/feature3[PATCH]t/feature3


执行git status命令,可以看出因为两个分支同时修改了文件hacks.txt而导致冲突。


$git status

On branch t/feature3

Unmerged paths:

(use "git add/rm<file>…" as appropriate to mark resolution)

#

both modified:hacks.txt

#

no changes added to commit(use "git add" and/or "git commit-a")


可以编辑hacks.txt文件,或者调用冲突解决工具解决冲突,之后再提交,这才真正完成了此次tg update。


$git mergetool

$git commit-m "resolved conflict with t/feature1."

$tg info

Topic Branch:t/feature3(4/2 commits)

Subject:[PATCH]t/feature3

Base:37dcb62

Depends:t/feature1

t/feature2

Up-to-date.


5.tg summary命令

tg summary命令用于显示Topgit管理的特性分支的列表及各个分支的状态。用法如下:


tg[…]summary[-t|—sort|—deps|—graphviz]


不带任何参数执行tg summary是最常用的Topgit命令。在介绍不带参数的tg summary命令之前,先看看该命令的其他用法。

使用-t参数显示特性分支列表。

22.4 Topgit的使用 - 图1

22.4 Topgit的使用 - 图2

标记"D":表明该分支处于过时(out-of-date)状态。可能是一个或多个依赖的分支包含了新的提交,尚未合并到此特性分支。可以用tg info命令看出到底是由于哪个依赖分支的改动导致该特性分支处于过时状态。

标记"B":之前演示中出现过,表明该分支处于Break状态,即可能由于冲突未解决或其他原因导致该特性分支的基准分支(base)相对该分支的头(head)不匹配。例如refs/top-bases下的特性分支的基准分支迁移了,但是特性分支未完成迁移。

标记“!”:表明该特性分支所依赖的分支不存在。

标记"l":表明该特性分支只存在于本地,不存在于远程跟踪服务器。

标记"r":表明该特性分支既存在于本地,又存在于远程跟踪服务器,并且两者匹配。

标记"L":表明该特性分支,本地的比远程跟踪服务器的新。

标记"R":表明该特性分支,远程跟踪服务器的比本地的新。

如果没有出现"l/r/L/R":表明该版本库尚未设置远程版本库。

一般带有标记"r"的是最常见的,也是最正常的。

下面要介绍的tg remote命令为测试版本库建立一个对应的远程版本库,然后就能在tg summary的输出中看到"l/r/L/R"等标识符了。

6.tg remote命令

tg remote命令用于为远程版本库增加Topgit的相关设置,以便在和该远程版本库进行git fetch、git pull等操作时能够同步Topgit的相关分支。命令用法如下:


tg[…]remote[—populate][REMOTE]


其中REMOTE为远程版本库的名称,默认为origin。执行tg remote命令会自动在版本的配置文件中增加的refs/top-bases下引用同步表达式。下面的示例中的最后一行就是执行tg remote origin后增加的设置。


[remote "origin"]

url=/path/to/repos/tgtest.git

fetch=+refs/heads/:refs/remotes/origin/

fetch=+refs/top-bases/:refs/remotes/origin/top-bases/


如果使用—populate参数,除了会向上面那样设置默认的Topgit远程版本库外,还会自动执行git fetch命令,然后在本地建立Topgit特性分支和对应的基准分支。

当执行tg命令时,如果不用-r remote全局参数,则默认使用origin远程版本库。

下面为前面测试的Topgit版本库设置一个远程版本库,具体操作过程如下。

(1)先创建一个裸版本库tgtest.git。


$git init—bare/path/to/repos/tgtest.git

Initialized empty Git repository in/path/to/repos/tgtest.git/


(2)然后执行git remote命令,将刚刚创建的版本库以origin为名注册为远程版本库。


$git remote add origin/path/to/repos/tgtest.git


(3)执行git push,将当前版本库的master分支推送到刚刚创建的远程版本库。


$git push origin master

Counting objects:7,done.

Delta compression using up to 2 threads.

Compressing objects:100%(3/3),done.

Writing objects:100%(7/7),585 bytes,done.

Total 7(delta 0),reused 0(delta 0)

Unpacking objects:100%(7/7),done.

To/path/to/repos/tgtest.git

*[new branch]master->master


(4)之后运行tg remote命令为远程版本库添加额外的配置,以便该远程版本库能够跟踪Topgit分支。


$tg remote—populate origin


(5)执行了上面的tg remote命令后,会在当前版本库的.git/config文件中添加设置(以加号开头的行):


[remote "origin"]

url=/path/to/repos/tgtest.git

fetch=+refs/heads/:refs/remotes/origin/

+

fetch=+refs/top-bases/:refs/remotes/origin/top-bases/

+[topgit]

+remote=origin


(6)这时再执行tg summary会看到分支前面都有标记"l",即本地提交比远程版本库的新。


$tg summary

l t/feature1[PATCH]t/feature1

0l t/feature2[PATCH]t/feature2

>l t/feature3[PATCH]t/feature3


(7)执行tg push命令将特性分支t/feature2及其基准分支推送到远程版本库。


$tg push t/feature2

Counting objects:5,done.

Delta compression using up to 2 threads.

Compressing objects:100%(3/3),done.

Writing objects:100%(4/4),457 bytes,done.

Total 4(delta 0),reused 0(delta 0)

Unpacking objects:100%(4/4),done.

To/path/to/repos/tgtest.git

*[new branch]t/feature2->t/feature2

*[new branch]refs/top-bases/t/feature2->refs/top-bases/t/feature2


(8)再来看看tg summary的输出,会看到t/feature2的标识变为"r",即远程和本地同步。


$tg summary

l t/feature1[PATCH]t/feature1

0r t/feature2[PATCH]t/feature2

>l t/feature3[PATCH]t/feature3


(9)运行tg push—all[1],会将所有的Topgit分支推送到远程版本库。之后再来看tg summary的输出,会看到所有分支都带上了"r"的标识。


$tg push—all

$tg summary

r t/feature1[PATCH]t/feature1

0r t/feature2[PATCH]t/feature2

>r t/feature3[PATCH]t/feature3


如果版本库设置了多个远程版本库,要针对每一个远程版本库执行tg remote<REMOTE>,但只能有一个远程的源用—populate参数调用tg remote将其设置为默认的远程版本库。

7.tg push命令

在前面tg remote的介绍中,已经看到过tg push命令。tg push命令用于将Topgit特性分支及对应的基准分支推送到远程版本库。用法如下:


tg[…]push[—dry-run][—no-deps][—tgish-only][—all|branch*]


tg push命令后面的参数指定要推送给远程服务器的分支列表,如果省略则推送当前分支。改进的tg push命令支持通过—all参数将所有Topgit特性分支推送到远程版本库。

参数—dry-run用于测试推送的执行效果,而不是真正执行。参数—no-deps的含义是不推送依赖的分支,默认推送。参数—tgish-only的含义是只推送Topgit特性分支,默认推送指定的所有分支。

8.tg depend命令

tg depend命令目前仅实现了为当前的Topgit特性分支增加新的依赖。用法如下:


tg[…]depend add NAME


将NAME加入到文件.topdeps中,并将NAME分支向该特性分支及特性分支的基准分支进行合并操作。虽然Topgit可以检查到分支的循环依赖,但还是要注意合理地设置分支的依赖,合并重复的依赖。

9.tg base命令

tg base命令用于显示特性分支的基准分支的提交ID(精简格式)。

10.tg delete命令

tg delete命令用于删除Topgit特性分支及其对应的基准分支。用法如下:


tg[…]delete[-f]NAME


默认只删除没有改动的分支,即标记为“0”的分支,除非使用-f参数。

目前此命令尚不能自动清除其分支中对删除分支的依赖,还需要手工调整.topdeps文件,删除对不存在的分支的依赖。

11.tg patch命令

tg patch命令通过比较特性分支及其基准分支的差异,显示该特性分支的补丁。用法如下:


tg[…]patch[-i|-w][NAME]


其中-i参数显示暂存区和基准分支的差异。-w参数显示工作区和基准分支的差异。

tg patch命令存在的一个问题是只能正确显示工作区中的根执行。这个缺陷已经在我改进的Topgit中被改正[2]

12.tg export命令

tg export命令用于导出特性分支及其依赖,便于向上游贡献。可以导出Quilt格式的补丁列表,或者顺序提交到另外的分支中。用法如下:


tg[…]export([—collapse]NEWBRANCH|[—all|-b BRANCH1,BRANCH2…]—quilt DIRECTORY|—linearize NEWBRANCH)


这个命令有三种导出方法。

将所有的Topgit特性分支压缩为一个,提交到新的分支。


tg[…]export—collapse NEWBRAQNCH


将所有的Topgit特性分支按照线性顺序提交到一个新的分支中。


tg[…]export—linearize NEWBRANCH


将指定的Topgit分支(一个或多个)及其依赖分支转换为Quilt格式的补丁,保存到指定目录中。


tg[…]export-b BRANCH1,BRANCH2…—quilt DIRECTORY


在导出为Quilt格式补丁的时候,如果想将所有的分支导出,必须用-b参数将分支全部罗列(或者分支的依赖关系将所有分支囊括),这对于需要导出所有分支的操作非常不方便。我改进的Topgit通过—all参数实现了所有分支的导出。

13.tg import命令

tg import命令将分支的提交转换为Topgit特性分支,指定范围的每个提交都转换为一个特性分支,各个特性分支之间线性依赖。用法如下:


tg[…]import[-d BASE_BRANCH]{[-p PREFIX]RANGE…|-s NAME COMMIT}


如果不使用-d参数,特性分支则以当前分支为依赖。特性分支名称自动生成,使用约定俗成的t/作为前缀,也可以通过-p参数指定其他前缀。可以通过-s参数设定特性分支的名称。

14.tg log命令

tg log命令显示特性分支的提交历史,并忽略合并引入的提交。


tg[…]log[NAME][—GIT LOG OPTIONS…]


tg log命令实际是对git log命令的封装。这个命令通过—no-merges和—frst-parent参数调用git log,虽然屏蔽了大量因和依赖分支合并而引入的依赖分支的提交日志,但是同时也屏蔽了合并到该特性分支的其他贡献者的提交。

15.tg mail命令

tg mail命令将当前分支或指定特性分支的补丁以邮件的形式外发。用法如下:


tg[…]mail[-s SEND_EMAIL_ARGS][-r REFERENCE_MSGID][NAME]


tg mail调用git send-email发送邮件,参数-s用于向该命令传递参数(需要用双引号括起来)。邮件中的目的地址从补丁文件头中的To、Cc和Bcc等字段获取。参数-r引用回复邮件的ID以便正确地生成in-reply-to邮件头。

注意:此命令可能会发送多封邮件,可以通过如下设置在调用git send-email命令发送邮件时进行确认。


$git config sendemail.confrm always


16.tg files命令

tg files命令用于显示当前或指定的特性分支改动了哪些文件。

17.tg prev命令

tg prev命令用于显示当前或指定的特性分支所依赖的分支名。

18.tg next命令

tg next命令用于显示当前或指定的特性分支被其他哪些特性分支所依赖。

19.tg graph命令

tg graph命令并非官方提供的命令,而是源自一个补丁[3],实现文本方式的Topgit分支图。当然这个文本分支图没有tg summary—graphviz生成的那么漂亮。

[1]改进后的Topgit才提供同步全部特性分支的功能。

[2]最新的Topgit重构了tg-push的代码,改正了这个缺陷,所以会看到最新改进版的Topgit该特性分支为空。

[3]http://kerneltrap.org/mailarchive/git/2009/5/20/2922