likes
comments
collection
share

开发中需掌握的Git操作

作者站长头像
站长
· 阅读数 6

起因

由于最近在开发过程中有切实的需求用 Git,因此最近又把 Git 教程拿出来刷了一遍。

公司目前使用的是 SVN,之前工作也没有复杂的团队协作,但最近开发任务变多,并且同一个项目有多个需求线在走,存在 A 任务开发了一半,需要优先开发 B,或者 B 有 bug 需要立刻修复。这种情况下,显然 Git 的分支管理策略会更适合来处理这个场景。本文就根据这样以一个使用场景来展开讲讲。

GUI 工具会使用 TortoiseGit 和 SourceTree。

Git 的常规操作,例如 pull/commit/push 和一些基本的概念这里就不细展开了,忘记了可以快速刷一遍教程。

分支管理

此处我们仅描述分 回到之前我们描述的场景中,现在我们有多个开发任务同时进行中,而且会有新的 bug 修复工作穿插进来。显然一个 master 分支显得力不从心。

那么我们粗略的划分一下,创建两个 2 个分支,feat1 和 feat2。

开发中需掌握的Git操作

创建完成后,我们可以 checkout 到指定分支进行开发工作。SourceTree 左侧,可双击对应的 branch 进行切换。在分支下 commit 等操作和之前无异。

那么接下来最关键的问题就是分支的合并了。

分支的合并有 2 种操作,merge 和 rebase。这两种操作都能做到分支合并的效果,但是在操作的方式上有一些不同。

Merge

merge 基本的特点:

  • 合并后会新增一条合并的 commit 记录,并且合并后所有合并进来的 commits 会按照时间顺序排列
  • 多个 commits 进行 merge 时只需要处理一次冲突

开发中需掌握的Git操作

其中在操作上有 3 种方式:

  • 普通 merge,合并后会新增一条合并的 commit 记录
  • fast-forward,合并 feat1 分支到 master 分支时,如果 master 分支的状态没有被更改过,此时就会将 master 分支的 HEAD 指针直接移动到 feat1 上,并且不会产生额外的 commit 记录。
  • squash,它就是在 merge 分支的时候把分支上的所有 commits 合并为一个 commit 后再 merge 到目标分支。这种做法的好处在于能够保持主干分支的整洁。对于整个项目而言,它并不 care 某个人完成某个 feature 具体提交了多少次,因此最终 merge 到主干分支后,只保留一个 commit 会更清晰。

GUI 操作

开发中需掌握的Git操作

在具体操作上,我们可以看下 TortoiseGit 和 SourceTree。上图是 2 个分支当前的状态。此时我们需要将 feat1 合并到 master 分支上。

在 SourceTree 中,需要选中 feat1 分支右键,“Merge feat1 into current branch”,此时可以勾选,是否采用 “fast-forward” 策略,如果勾选,则无论目标分支是否有更新,都会产生一个 merge commit。

开发中需掌握的Git操作

开发中需掌握的Git操作

在 TortoiseGit 中,可设置的参数会更多,可以选择之前讲到的 “Squash” 已经是否采用 “fast-forward” 策略。其中,勾选 “Fast Forward Only”,若符合“fast-forward” 策略,会直接合并,反之 aborting;勾选“Squash”,则需要手动在 commit 一次。

开发中需掌握的Git操作

开发中需掌握的Git操作

合并成功可以看到 commit history 图和之前描述的一致,普通 merge 会新增 merge commit,Squash 会将多个 commit 合并成 1 个后 merge 到目标分支,这里保留了自动生成的 log,可以看的更清楚。

Rebase

rebase 这个命令的功能有很多,其中最重要的一个就是用来做分支合并的。

rebase 基本的特点:

  • 变基后不会产生合并的 commit 记录,并且目标分支的更新会被放到最顶端,和 merge 相比,history 不会有分叉,更整洁
  • 目标分支上如果有多个 commit,从其他分支 rebase 后,如果产生冲突需要一个个解决

开发中需掌握的Git操作

GUI 操作

开发中需掌握的Git操作

在之前的记录下,我们在 master 分支上更新了 1 次,此时 feat1 分支已经落后了,在开发的过程中,为了减少冲突的发生需要经常同步主干分支的更新。现在我们需要合并 master 的更新到 feat1 上去,使用 merge 是可以做到的,rebase 同样可以,下面就来演示一下。

在 SourceTree 中可以右键需要 rebase 的更改的分支,比如现在需要将 master 分支的更新 rebase 到 feat1,就在 master 分支上右键选择 “Rebase current changes onto master”(可理解成,将 feat1 的 commit 提取出来,然后将其的 root 变基到 master 上,然后再把 feat1 的 commits 重写到最顶端),然后点 ok,此时如果没有出现冲突,可直接完成 rebase 操作。否则需要不断 solve 冲突文件,然后点菜单上 Actions -> Continue Rebase,直到完成整个 rebase 过程。

开发中需掌握的Git操作

再来看 TortoiseGit 的操作。Branch 为当前分支,Upstream 为需上游分支。下图的操作为 feat1 rebase onto master,也就是说将 master 分支的更新合并到 feat1 中,并将 feat1 的 commits 重写到最顶端。同时在这个界面还可以进行 Squash 操作,可以选择是否将 feat1 中的 commits 进行修改和合并。然后点 Start Rebase 就可以了。如果出现冲突,会卡住,解决完后,点 Continue 就可以继续 Rebase,直到结束。

开发中需掌握的Git操作

rebase 的结果如下图。History 图和之前描述的一致,不存在分叉。并且 feat1 的 commits 被顶到了最前面。

开发中需掌握的Git操作

rebase 的另一个重要的功能是可以改写 history,合并多次提交纪录。刚刚在 rebase 分支时已经提到了。

比如完成一个 feature,提交了很多次,但有些 commit 可能粒度太细,或者是没必要的,可以进行合并。此时可以 rebase -i 来 squash。

在 SourceTree 和 TortoiseGit 的 History 上,选中一个 commit 右键,都能看到 rebase 操作(Rebase children of xxx interactively or Rebase onto …)。

假设我们想把 feat1 的 update[1] 和 update[2] 合并在一起。

开发中需掌握的Git操作

TortoiseGit 的操作类似,可参考下图。

开发中需掌握的Git操作

Merge vs Rebase

上面两个子章节我们描述了,merge 和 rebase 的用法和异同,那么在实际操作上应该怎么去用这两个命令呢?

由于笔者实践经验不足,故不做分析。在大公司一般 Leader 都会有相应的规范的。

或者可以看看知乎大神们的讨论:在开发过程中使用 git rebase 还是 git merge,优缺点分别是什么?

代码暂存(Stash)

stash 命令就是用来暂时保存工作进度的。例如,开发了一半,中途需要紧急修复一个 bug,本地更改仍未 commit,此时无法直接切换分支。这个时候就可以把本地的更改存在暂存区,等 bug 修复完,checkout 到当前分支再将暂存区的文件释放出来。

GUI 操作

SourceTree 操作

开发中需掌握的Git操作

开发中需掌握的Git操作

TortoiseGit 操作,右键可选择 Stash Changes,操作类似。

版本回退(Reset、Revert)

版本回退分为两个粒度。

  • 级联回退,回退到历史某个版本,换言之撤销目标版本后所的所有提交(reset)
  • 撤销历史某次提交的更改(revert)

Reset

reset 命令的结果上面已经讲了。这里再举个例子,比如当前需要 reset 到 9f72a8c init,当执行完 reset 命令后,9f72a8c 之后的 4 次提交会全部撤销。

开发中需掌握的Git操作

同时,reset 命令有 3 种不同的模式。

  • Soft - keep all local changes
  • Mixed - keep working copy but reset index
  • Hard - discard all working copy changes

如果你对 Git 工作区和暂存区有了解的话,看上图应该很好理解 这 3 种模式了。简单的来讲:

开发中需掌握的Git操作

  • Soft - History 记录回退,但本地保留回退的更改并存在暂存区
  • Mixed - History 记录回退,但本地保留回退的更改并存在工作区
  • Hard - History 记录回退,回退的更改也完全不保留

SourceTree 和 TortoiseGit 默认的模式为 Mixed。

Revert

Reset 命令会直接级联回滚,而 Revert 则是撤销历史某次提交的更改,并且会保留当前撤销的 commit 记录。一般来说撤销历史的某次更改会产生冲突,需要 review 历史更改的地方,然后解决冲突后重新 commit。

GUI 操作

SourceTree 和 TortoiseGit 的操作都是在 History 记录的某个 commit 右键选择相应的命令。

  • SourceTree 是 Reverse commit..
  • TortoiseGit 是 revert change by this commit'

Reflog

上文提到的 Reset 和 Revert 都是针对自己提交的代码进行回退或撤销更改。那如果是 Git 相关的操作错了,可以回滚吗?答案是肯定的。

Reflog 记录了用户所有的 git 操作,例如 linux 下的 history。

在 TortoiseGit 中,右键 show reflog 可以查看到所有操作的记录。SourceTree 暂未有此功能。

开发中需掌握的Git操作

例如 merge 错分支,或者 rebase 的时候把本地更改冲掉了,就可以通过回退 reflog 的方式解决。操作很简单,右键选择具体的操作 reset 即可。

Cherry Pick

cherry-pick 可以将一个分支的某几个 commits 移动到另一个分支,并且会放到顶端。

SourceTree 和 TortoiseGit 的操作都是相同的:

  1. 选择目标分支为当前分支
  2. 在 History 记录的某个 commit 右键选择 Cherry Pick

关于 GUI 工具

笔者个人是在 windows 平台开发的,目前 SourceTree 和 TortoiseGit 是一起使用的。

TortoiseGit 功能上会丰富一些,个人觉得一些复杂的操作提示性做的更完善不容易出错,但是它是在 windows 资源管理器外壳上的,因此经常需要右键去操作,比较麻烦。

SourceTree 是一个软件,查看 log 会更直观,在分支切换和日常 commit/push/pull 上会比较方便。但是缺失了部分功能,比如 relog 就没有。一些命令的可配置性也不强,比如 merge 只能控制是否 ff,无法选择 squash 等。

总结

本文主要描述了 Git 的一些常用的命令以及在 GUI 工具下的使用。这些命令基本是属于开发必备了吧,特别是分支管理那块尤为重要。随着对版本控制需求的不断提高,对于 Git 掌握的要求也会越来越高。翻阅《Pro Git》的时候发现还有很多命令至今没有涉猎到,希望在日后能有进一步的了解。

参考文献