Git 进阶操作和工作流

进阶操作不包含基础,进阶分为两部分:使用分支和修改提交。主要整理自 猴子都能懂的 GIT 入门。关于工作流,罗列了几个链接,值得反复阅读学习。

其实我们在 Git 官网 就能找到最好的学习材料:《Pro Git》。知识点讲得很全,也讲得很好。无论是进行系统的学习,还是作为参考手册都是不二之选!但如果你真的只有 5 分钟时间,还要快速上手使用,拿这么一本书显然是不合适的,所以就推荐了上面的入门贴。实际工作中,碰到什么问题,我都是来翻书的。

使用分支

先确定运用规则才可以有效地利用分支。

Merge 分支

Merge 分支是为了可以随时发布 release 而创建的分支,它还能作为 Topic 分支的源分支使用。保持分支稳定的状态是很重要的。如果要进行更改,通常先创建 Topic 分支,而针对该分支,可以使用Jenkins 之类的 CI 工具进行自动化编译以及测试

通常,大家会将 master 分支当作 Merge 分支使用。

Topic 分支

Topic 分支是为了开发新功能或修复 Bug 等任务而建立的分支。若要同时进行多个的任务,请创建多个的 Topic 分支。

Topic 分支是从稳定的 Merge 分支创建的。完成作业后,要把 Topic 分支合并回 Merge 分支。

分支的合并

完成作业后的 topic 分支,最后要合并回 merge 分支。合并分支有2种方法:使用 merge 或 rebase。使用这2种方法,合并后分支的历史记录会有很大的差别。

merge

进行 merge 的话,分为两种情况:

  1. fast-forward(快进)合并

    合并 bugfix 分支到 master 分支时,如果 master 分支的状态没有被更改过,那么这个合并是非常简单的。 bugfix 分支的历史记录包含 master 分支所有的历史记录,所以只要把 bugfix 移动到 master 分支就可以导入 bugfix 分支的内容了(即将 master 的 HEAD 移动到 bugfix 的 HEAD 这里)。这样的合并被称为 fast-forward(快进)合并。

    这种场景不会产生新的提交。

  2. non fast-forward

    但是,master 分支的历史记录有可能在 bugfix 分支分叉出去后有新的更新。这种情况下,要把 master 分支的修改内容和 bugfix 分支的修改内容汇合起来。

    因此,合并两个修改会生成一个提交。这时,master 分支的 HEAD 会移动到该提交上。

Note

执行合并时,如果设定了 non fast-forward (--no-ff)选项,即使在能够 fast-forward 合并的情况下也会生成新的提交并合并。

执行 non fast-forward 后,分支会维持原状。那么要查明在这个分支里的操作就很容易了。

阮一峰老师写到:

使用 --no-ff 参数后,会执行正常合并,在 Master 分支上生成一个新节点。为了保证版本演进的清晰,我们希望采用这种做法。

rebase

这个没有亲自实践过,要认真学学。

  1. 首先,rebase bugfix 分支到 master 分支, bugfix 分支的历史记录会添加在 master 分支的后面。

    这时移动提交 X 和 Y 有可能会发生冲突,所以需要修改各自的提交时发生冲突的部分。

  2. rebase 之后,master 的 HEAD 位置不变(此时就是典型的 fast-forward merge 场景了)。因此,要合并 master 分支和 bugfix 分支,即是将 master 的 HEAD 移动到 bugfix 的 HEAD 这里。

rebase 最大的优点是:历史记录成一条线,相当整洁。在《Pro Git》书中提到:

变基使得提交历史更加整洁。 你在查看一个经过变基的分支的历史记录时会发现,尽管实际的开发工作是并行的,但它们看上去就像是先后串行的一样,提交历史是一条直线没有分叉。

一般我们这样做的目的是为了确保在向远程分支推送时能保持提交历史的整洁——例如向某个别人维护的项目贡献代码时。在这种情况下,你首先在自己的分支里进行开发,当开发完成时你需要先将你的代码变基到 origin/master 上,然后再向主项目提交修改。 这样的话,该项目的维护者就不再需要进行整合工作,只需要快进合并便可。

同样,在书中也提到了变基的风险,作为新手我们并不需要做过多的学习。

具体练习见 用 rebase 合并

改写提交

参考自 改写提交。改写提交非常容易出现冲突,需要人工合并。

“改写提交”,我觉得《Pro Git》的表达更好一些——“重写历史”。因为有关的操作大多比较敏感,都有后遗症,操纵不当更会变成灾难,所以建议 认真读书、反复练习之后再在实际项目中操作。

在进入正文之前,我们必须清楚 ^~ 的区别:What’s the difference between HEAD^ and HEAD~ in Git?

强调~ 是对 ^ 的复合运算,而不是 ^n 的复合运算。

修改最近的提交

git commit –amend

取消过去的提交(内容)

git revert 可以取消指定的提交内容。

虽然使用后面要提到的 rebase -i 或 reset 可以直接删除提交,但是有时候并不合适(甚至不能)直接删除提交,比如不能随便删除已经发布的提交。这时就可以通过 revert 创建(否定某一次提交的)提交。

遗弃提交

git reset 除了默认的 mixed 模式,还有 soft 和 hard 模式。

模式名称 HEAD 的位置 索引 工作树
soft 修改 不修改 不修改
mixed 修改 修改 不修改
hard 修改 修改 修改

主要使用的场合:

  • 复原修改过的索引的状态(mixed)
  • 彻底取消最近的提交(hard)
  • 只取消提交(soft)

提取提交

cherry 樱桃

git cherry-pick 可以从其他分支复制指定的提交,然后导入到现在的分支。

例如,假设我们有个稳定版本的分支,叫 v2.0,另外还有个开发版本的分支 v3.0,我们不能直接把两个分支合并,这样会导致稳定版本混乱,但是又想增加一个 v3.0 中的功能到 v2.0 中,这里就可以使用 cherry-pick 了。

改写提交的历史记录

git rebase -i

这个命令很强大,在 rebase 指定 -i(interactive,交互式的) 选项,可以改写、替换、删除或合并提交。但也有一些副作用……

请翻书,重写历史

副作用?

场景描述:今天进行 rebase -i 操作后发现 merge –no-ff 的分支合并记录都没了

![merge –no-ff](https://raw.githubusercontent.com/nielong0610/MarkdownPhotos/master/merge –no-ff.png)

ps:借着这张图,复习一下 ^~ 的知识。

  • git reset head~,head 变更为 7c8d8a;
  • git reset head~2,head 变更为 6af94;
  • git reset head~^2,head 变更为 78663f;
  • git reset head4,报错!因为 head4 不存在
  • 强调~ 是对 ^ 的复合运算,而不是 ^n 的复合运算。(7c8d8a)^2~ = 6af94d = (7c8d8a)~

返回来继续说,执行 git rebase -i head~3 后结果如下:

![merge –no-ff](https://raw.githubusercontent.com/nielong0610/MarkdownPhotos/master/merge –no-ff 2.png)

在执行 git rebase -i head~3 时对话框如下:

1
2
3
4
5
6
pick 6af94d1 modify name.txt
pick 78663ff new age.txt (笔者注:7c8d8a2 透明的)
pick 915b8b1 re modify name.txt

# Rebase d0cd09d..915b8b1 onto d0cd09d (3 command(s))
#

可以看到,是不存在任何合并节点的。这也是 rebase 的目的!

疑问:如果我只是想改写 7c8d8a 之前某个 commit 的备注信息,且不想丢失分支演进,改怎么做呢?

结论:其实这算不得 rebase -i 的副作用,git rebase 默认 ignore merges。我自己以期上述目的,是用错了命令,虽然我至今不知道怎么实现上述目的 查看 --preserve-merges 参数。再往深的追究,其实我至今都不知道该怎么使用 git 去实现一个完善的工作流程,我了解一些命令的用法,但去不知道该怎么组织一个工程,怎么对一个项目进行细致的、完整的版本管理?

参见 git rebase --preserve-merges 手册。此参数的某些使用情景很乱,见 What exactly does git’s “rebase –preserve-merges” do (and why?)

汇合分支上的提交,然后一同合并到分支

git merge –squash

TortoiseGit 的 Show RefLog

TODO:感觉很有用哎,今天进行 rebase -i 操作后发现 merge –no-ff 的分支合并记录都没了,而且进行了两次 reabse -i,所以也没办法通过 git reset ORIG_HEAD 恢复。幸运的是发现了 RefLog 的存在,不知道这是 Git 本身的功能,还是 TortoiseGit 的功能扩展。抽空查探一下。

是 Git 自身的机制,很强大!官方手册,真心没有探索的欲望,怎么破?

其他

Tips

不同类别的修改 (如:Bug修复和功能添加) 要尽量分开提交,以方便以后从历史记录里查找特定的修改内容。

Tips

查看其他人提交的修改内容或自己的历史记录的时候,提交信息是需要用到的重要资料。所以请用心填写修改内容的提交信息,以方便别人理解。

以下是Git的标准注解:

1
2
3
第1行:提交修改内容的摘要
第2行:空行
第3行以后:修改的理由

请以这种格式填写提交信息。

Get

切换分支使用 checkout,检出文件也使用 checkout……不是因为作者懒得起名字,而是因为两者本质是同一种操作,都是从版本库(或索引)检出文件到工作区。分支本质上是某一个提交(的引用),切换分支就是检出对应的提交。

Get

如果在log命令添加 --decorate 选项执行,可以显示包含标签资料的历史记录。

在 windows 下搭建 git 服务器

2016/12/15 12:49:18 前两天将写的代码误删了,没有用版本管理工具。代码在一台连不上互联网的服务器上,因为系统老旧的原因,使用上有好多限制,负责人担心在新系统上写的代码拿到旧系统(最终必须部署在旧系统上)上用还得再次修改兼容性之类的问题,所以一般都是在旧系统上直接一个字母一个字母的敲代码。误删上周的工作内容后,后悔之余,也得考虑以后怎么避免这种情况。

在旧系统上凑活能装上 git,很原始的版本了。但它只能访问到我的笔记本(局域网),而不能访问互联网,所以就需要在 E431 windows7 的电脑上部署 Git 仓库。

依据上一篇搭建完成之后,操作中报错:(看着好像是不能是 --bare Repo 啊)

工作流

工作流 flow,对于大团队和个人肯定是不一样的,不能一概而论。对于个人开发,其重点最终要落在对分支 branch 的利用上,只要合理安排分支基本就可以了。对于人员很多,强调合作开发的团队,在上述基础上还要找准各节点(每个开发人员)的定位,会变得复杂很多。

一个完整的工作流包括对分支的利用、对节点的安排。好在大多时候我们只需关注前面一部分就能达到目的。

分支的安排

3.4 Git 分支 - 分支开发工作流 中描述了“长期分支”和“特性分支”两个概念。

Git分支管理策略 - 阮一峰 中使用了两个常设分支(长期分支)和三个临时分支(特性分支)。master 用于正式发布,develop 用于日常开发;feature 功能分支,Release 预发布分支,fixbug 修补分支。此策略即 Git flow

合并分支使用 --no-ff 选项。

Git flow 并不完美,Git 工作流程 - 阮一峰 中又讲解了另外两种分支策略:Github flow、Gitlab flow。推荐 Gitlab flow。在摸索中学习,在学习后实践。

ps:2017/2/23 15:23:53 我现在认为基于“版本发布”的,还是使用 git flow 更好一些,更符合直觉,更何况有那么多的前辈们(非 web 开发)都是如此呢。

gitlab flow

如何使用gitlab的flow以及代码review

GitLab Flow的使用

分布式节点的安排

5.1 分布式 Git - 分布式工作流程 中提到三种

集中式工作流、集成管理者工作流、司令官与副官工作流。

个人开发或者小团队开发,使用前者足够了。这种单点协作模型完全符合人们的直觉,好多人虽然不知道“集中式工作流”这个名词,但已经在这么用了。

其他