整理并学习几个git中的工具:

  • rebase - 用于合并提交,重写历史,较merge更复杂
  • submodule - 子模块功能,用于管理项目中包含的独立子模块
  • git-flow - 一个工具,提供flow命令来简化符合git branching model中规范的操作

git rebase

rebase(变基)命令用于合并提交,重写历史。

在git有两个命令可以合并不同分支的变动: mergerebasemerge是比较常用的合并命令,而且在pull命令中也包含mergerebase相对于merge的不同之处:

  • 破坏性的操作,合并操作后会破坏提交历史,重写操作后无法追溯历史
  • 具有交互式编辑方式,可以对多个提交进行不同操作,使用[-i|–interactive]选项开启

使用铁律: 永远不要在公共的分支上执行rebase,因为这会影响其他使用它的人。

合并同一分支的多次提交

将一连串的提交合并为一次提交,简化提交历史。

合并前:

C1 --- C2 --- C3 --- C4

合并后:

C1 --- C2'

两种指定要合并提交的方式:

  1. 指定起始和终止提交

    git rebase -i [START_COMMIT_HASH] [END_COMMIT_HASH]

    START_COMMIT_HASH为起始提交hash,END_COMMIT_HASH为终止提交hash。。

  2. 根据HEAD指针向前推断

    git rebase -i HEAD~[SUM]SUM为最后提交的个数。

若执行git rebase -i HEAD~3命令,git会在文本编辑器上提供一个列表:

pick 6d8b3f7 fix lerna
pick 4e556da add nuxt
pick 41cf252 add git files

在这个列表中需要关注的两个地方:

  1. 该列表中最旧的提交在最上面,最新的在最下面,与使用git log查看的顺序是相反的,该顺序是交互式rebase的操作顺序。

  2. hash前的单词表示对该提交的操作,该操作有如下几种:

    # Commands:
    # p, pick = use commit
    # r, reword = use commit, but edit the commit message
    # e, edit = use commit, but stop for amending
    # s, squash = use commit, but meld into previous commit
    # f, fixup = like "squash", but discard this commit's log message
    # x, exec = run command (the rest of the line) using shell
    # d, drop = remove commit
    

因此若我想将这三次提交都合并到最旧的提交上,需要把后两次提交的操作修改为squash,如下:

pick 6d8b3f7 fix lerna
squash 4e556da add nuxt
squash 41cf252 add git files

保存后即得到一个包含前三次提交所有变更及提交信息的提交。

合并不同分支的多次提交

比如,我们想将A分支的提交合并到B分支的提交上

合并前:

C1 ---- C3 --- C5  Branch-A
   \
    --- C2 --- C4  Branch-B

合并后:

// C3'与C3有可能内容完全一样,但实际上是不一样的提交,有着不同的hash。
C1 --- C2 --- C4(Branch-B) --- C3' --- C5'(Branch-A)

在这个过程中有三步:

  1. 来到A与B分支的共同提交处(C1)
  2. 整合B分支的提交(C2, C4)
  3. 整合A分支的提交(C3’, C5’),这些提交被重新应用到了B分支的提交后,位置发生了改变,被re-base

要实现上述过程只需要执行:

git checkout Branch-A
git rebase Branch-B

若第三步整合的提交与第二步中的存在冲突,则需要先解决冲突,git add提交修改后,再执行如下命令处理剩余的合并操作:

git rebase  --continue

注意事项

  1. 不要在已提交的公共分支上使用rebase操作
  2. 在需要的场合再使用rebase

git submodule

用于管理项目中的submodule(子模块)。

场景

适合用submodule来管理博客项目(从hugo中了解)。在博客中,一般有博客项目和生成的静态站点。可以将博客项目作为主项目,所生成的静态站点作为submodule,这样在修改完文章生成新的静态站点后,可以提交主项目中submodule的改动,而不用切换到另一个项目中。

一个 “子模块” 其实就是一个标准的 Git 仓库。不同的是,它被包含在另一个主项目的仓库中。一般情况下,它包含一些库文件和其它资源文件,你可以简单地把这些库文件作为一个子模块添加到你的主项目中。

添加submodule

git submodule add http://<@repo>/<module>.git <explicitDirectory>

默认会将module clone到以module命名的根目录文件夹下,也可以指定clone的路径。虽然将该项目clone了下来,但是它并不作为主项目版本控制的一部分。

当添加一个submodule时,会生成一个.gitmodules文件,该文件会跟踪并保存所使用的submodule信息。除了 “.gitmodules” 配置文件,Git 也会在你本地的 “.git/config” 文件中保存对子模块的记录。最终它也会在它的 “.git/modules” 目录中保存每一个子模块的 “.git” 仓库。

一般情况下不要手动修改这个文件,可能会产生意想不到的结果。

更新submodule及初始化clone项目中的submodule

当submodule中的代码变更时,需要更新它的签出版本才能正常提交主项目的代码。

git submodule update <someModule>

当该子模块项目中还包含子模块时,需要使用--recrusive来安装它内部的sumodule

git submodule update --recrusive

默认情况下,一个项目并不包含它submodule的文件。若想在clone项目时同时保留submodule的文件,需要在使用命令时加上参数。

  1. --recurse-submodules

    clone命令上使用--recurse-submodules参数,会在clone完成时初始化所有submodule。

  2. git submodule update --init --recursive

    如果项目已经clone了下来,则需要使用submodule命令来初始化所有submodule。

删除submodule

安全起见不要手动直接删除,使用如下两步删除submodule

  1. 删除配置文件中submodule
git submodule deinit <module>
  1. 删除submodule的对应文件
git rm <module> or <explicitDirectory>

修改submodule

假设submodule在根目录的lib文件夹下:

cd lib/some-module
git log

当进入子模块的目录后,git命令会仅对子模块有效,而不会影响父仓库。

当修改完submodule文件未提交时,git会在父仓库中提示:

git status
...
    modified:   lib/<module> (modified content)

改变submodule的签出版本

对于一个git仓库,总应有一个版本会签出到工作副本中,对于submodule也不例外。普通的git仓库可能是签出一个分支,而submodule可以签出一个特定的提交。

当在submodule中切换一个tag: v1.0.1对应的提交时:

cd lib/<module>
git checkout v1.0.1

切换回主仓库查看submodule状态

cd ..
$ git submodule status
+3557a0e0f7280fb3aba18fb9035d204c7de6344f   lib/<module> (v1.0.1)

在hash前的+号表示签出的版本与父仓库中记录的submodule版本不一致。

使用git status会发现git移动了指向该submodule的指针:

$ git status
On branch master
Changes not staged for commit:
    (use "git add ..." to update what will be committed)
    (use "git checkout -- ..." to discard changes in working directory)

    modified:   lib/<module> (new commits)

因此需要提交这个改动:

git commit -am "move module pointer to v1.0.1"

git-flow

使用git-flow工具可以通过一些简单命令来进行符合git branching model中规范的相关操作。通常使用AVH版本的git-flow。

init

在初始化git项目时会有一些交互选项来设置一些预设分支与命名规则。

$ git flow init
Initialized empty Git repository in /Users/abc/desktop/git-test/.git/
Branch name for production releases: [master] 
Branch name for "next release" development: [develop] 

How to name your supporting branch prefixes?
Feature branches? [feature/] 
Release branches? [release/] 
Hotfix branches? [hotfix/]

branch

git-flow中存在两种预设的分支:

  • master
  • develop

feature

使用feature命令来创建和完成新功能。假设我们要开发一个计数器功能:

开始feature开发

git flow feature start counter

它所做的就是签出一个新的分支,使用feature的命名规则(feature/counter)

完成feature开发

git flow feature finish counter

所做的事包含:

  1. 将当前工作整合到develop
  2. 删除feature/counter分支,切换到develop分支

release

当认为develop分支已经是个成熟release版本时,使用release命令来发布新的版本。

创建release

git flow release start 1.1.0
# Switched to a new branch 'release/1.1.0'

会创建一个新的release分支,用于后续可能的文件版本标记与最后的编辑。

完成release

git flow release finish 1.1.0

在这里会执行以下操作:

  1. 拉取remote仓库,保持最新版本
  2. release内容合并到masterdevelop分支上
  3. 使用release名称(1.1.0)标记本次release提交
  4. 删除release/1.1.0分支,回到develop

hotfix

hotfix流程用于修复在release版本的后续测试中所发现的小错误。

创建hotfix

git flow hotfix start missing-link

会基于master分支创建一个名为hotfix/missing-link的分支。

完成hotfix

git flow release finish 1.1.5

该命令所做的事情:

  1. hotfix内容合并到masterdevelop分支上
  2. 对本地hotfix提交添加标记
  3. 删除hotfix分支,回到develop

参考