Git 实用小抄

Git - the stupid content tracker.

在项目开发过程中,Git 是非常得力的版本控制管理工具。它使得开发者可以不分时间地点,高效协作。本文简要说明 Git 的基本用法,并介绍提高效率的小 tips,以及 Git 工作流分支管理。

一图了解 Git 分区

常用 Git 命令

git status

使用git status查看缓冲区(index)和工作目录(workspace)的状态,你可以在这里看到:

  • 当前分支
  • 未跟踪的文件
  • 已修改的文件
  • 已暂存的文件
  • 附加信息

勤用git status能帮助你随时掌握仓库状态。

git add

使用git add命令将指定文件提交到缓冲区(index)。建议搭配git status,谨慎查看需要添加至缓冲区的修改,以防将不在 commit log 描述范围内,或未被.gitignore文件忽略的文件提交上去了。

git commit

使用git commit将修改提交到本地仓库(repository)。Git 强制需要给每个 commit 添加描述说明。如果当你提交后,猛然一拍大腿,惊呼“我提交错了!”,这时还可以使用git reset来恢复,或使用git commit --amend来修改最新的提交。如果你不在乎新提交一个记录,那么也可以使用git revert生成一个新的 commit 来撤销你的更改。git commit的信息应该规范填写,最好一看 commit log 便知道这个 commit 做了何种修改。

git commit 规范

规范要求 commit log 的意义在于可追溯项目历史记录,以及更新代码时用最少的成本获取提交信息——这个更新做了哪些更改。在一个 commit log 里尽量通过三个维度描述一个 commit:type(类型)、scope(范围)、subject(简介)。

type(必选项)

type 代表一个 commit 的提交类型。后续追溯历史记录时,先通过 type 进行筛选,可以节约不少时间。

type(必须) 英文 说明
feat feature 新增功能
fix fix 修复缺陷
docs documents 文档更新
style style 代码格式
refactor refactor 代码重构
perf performance 性能提升
test test 测试相关
build build 构建相关
ci continuous integration 持续集成
revert revert 回退代码
chore chore 其他修改
scope(可选项)

scope 代表一个 commit 修改的范围,比如视图层、控制层,模型层等等。

subject(必选项)

subject 代表一个 commit 的说明,最好不超过 50 字。说明应该准确描述这次修改,方便追溯。为了达成目的,提交的颗粒度可以小一点,后续可以通过git rebase合并多次提交,整理提交记录。

好的历史记录,看着赏心悦目。其实更建议中国人写 subject 用中文。。。

令人眼前一亮的历史记录

坏的历史记录,让人眼前一黑。但人家是大佬,他这样写一定是有他的理由的(确信)

令人眼前一黑的历史记录

git pull

使用git pull将远端更改同步到本地仓库。建议时常pull一下,保证自己正在修改的是最新的分支。如果你有未commit的更改,则 git pull 命令的合并部分将失败,而你的本地分支将保持不变。因此,在从远程仓库中提取新提交之前,你应该始终在分支中提交你的更改。

要完全理解git pull的工作模式,我们需要了解两个命令:git fetchgit merge

git fetch

使用git fetch来更新仓库中所有远程仓库的跟踪分支。实际上没有任何更改反映在任何本地工作分支上。这意味着只是你的本地仓库感知到了远端仓库的更新,但还没有做同步。IDEA有自动fetch的相关插件,建议安装以及时感知到分支更新。

git merge

使用git merge target-branch将目标分支合并到当前分支上。merge的合并策略有两种:fast-forward 和三方合并

  • Fast-forward 合并 如果在两个分支之间没有任何变更,则 Git 会直接将目标分支指向源分支的提交对象,这就是所谓的 Fast-forward 合并。该操作不会创建新的提交对象,因为早期的提交已经包含了所有的变更。

iShot_2023-03-25_09.09.51

  • 三方合并 如果两个分支之间存在变更冲突,则需要进行三方合并。在三方合并中,Git 会创建一个新的提交对象,该对象包含两个分支之间的共同点和每个分支相对于共同点的变更。Git 还会尝试将冲突解决为一致的变更。

    iShot_2023-03-25_09.14.25

git push

使用git push将修改上传至远程仓库(remote)。建议时不时pull一下代码。

其他实用 Git 命令

git diff

使用git diff查看工作空间还未被添加至缓冲区的修改;使用git diff --cached查看缓冲区尚未被提交的修改;使用git diff branch1 branch2查看两个分支间的差异。

git stash

git stash系列命令带来了一个新的区域——暂存区(stash entry)。使用git stash push将你尚未提交的修改压进暂存区,你的工作空间和缓冲区将恢复成最新 commit 的修改。使用git stash pop弹出暂存区栈顶的修改。使用git stash list查看暂存区保存的修改列表。

git stash经常用来提示不能 pull 的情况时,这通常代表工作空间或缓冲区还有修改没有提交。这时可以先将修改压进暂存区,恢复工作空间和缓冲区,待 pull 操作完成后,再弹出修改。

git stash也很适合切换分支。例如当我们在自己负责的 feature 分支开发时,线上突然有 bug 需要你搞定,你需要立即切换分支到 master 新建 hotfix 分支修复。可你当前的修改还未提交,因为功能尚未完成,也不能提交。这时就可以使用git stash命令将修改压进暂存区,切换分支进行开发,完事再回到压入暂存区时的分支弹出修改即可。

git rebase

git rebase系列命令使我们有机会重新整理我们的提交,使历史记录干净清爽,非常易于追溯历史。对此,Vue 作者尤雨溪曾在知乎上提到:

image-20230325095828583

攻击性还是很强啊 2333

git rebase target-branch的本质是:找到当前分支与目标分支的共同祖先,先将我当前分支的修改“抛到一边”,使我的当前分支与目标分支的历史提交记录一致,再将“抛到一边”的 commit 应用回当前分支。这种合并分支的策略与git merge的区别是:

  • git merge会留下无用的 commit log 信息,污染历史记录。
  • git rebase为了整理历史记录,理所当然会修改当前分支的 commit 。所以在一条分支上,当你 rebase 后,这条分支的顺序已经和之前不同了,倘若此时别人也在这条分支上,当他 push 时,可能会遇到错误,甚至可能会丢失更改。

所以建议git rebase只用来修改还未 push 到远端仓库的 commit,或在只有你自己使用的分支上应用。

iShot_2023-03-25_10.24.13

当然git rebase除了拿来合并分支,还可以合并多个 commit 。我们使用git rebase -i [startpoint] [endpoint]通过选项-i——即--interactive开启交互式编辑界面。这里[startpoint] [endpoint]是一个左开右闭的区间,必须指明[startpoint],默认[endpoint]HEAD指向的 commit 。例如我们通过git rebase -i HEAD~3来编辑最新的 3 次提交。

pick 54f88ff 第一次提交
pick 41346f3 第二次提交
pick 3b9307e 第三次提交

# 变基 edb3a72..3b9307e 到 edb3a72(3 个提交)
#
# 命令:
# p, pick <提交> = 使用提交
# r, reword <提交> = 使用提交,但编辑提交说明
# e, edit <提交> = 使用提交,但停止以便修补提交
# s, squash <提交> = 使用提交,但挤压到前一个提交
# f, fixup [-C | -c] <提交> = 类似于 "squash",但只保留前一个提交
#                    的提交说明,除非使用了 -C 参数,此情况下则只
#                    保留本提交说明。使用 -c 和 -C 类似,但会打开
#                    编辑器修改提交说明
# x, exec <命令> = 使用 shell 运行命令(此行剩余部分)
# b, break = 在此处停止(使用 'git rebase --continue' 继续变基)
# d, drop <提交> = 删除提交
# l, label <label> = 为当前 HEAD 打上标记
# t, reset <label> = 重置 HEAD 到该标记
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       创建一个合并提交,并使用原始的合并提交说明(如果没有指定
# .       原始提交,使用注释部分的 oneline 作为提交说明)。使用
# .       -c <提交> 可以编辑提交说明。
# u, update-ref <引用> = 为引用 <ref> 设置一个占位符,以将该引用更新为此处的新提交。
#                       此 <引用> 在变基结束后更新。
#
# 可以对这些行重新排序,将从上至下执行。
#
# 如果您在这里删除一行,对应的提交将会丢失。
#
# 然而,如果您删除全部内容,变基操作将会终止。

注释中详细解释了可用的命令。在此场景中,假设我们希望合并这三个 commit,我们如此修改:

r 54f88ff 第一次提交
f 41346f3 第二次提交
f 3b9307e 第三次提交

接下来我们修改 commit log 信息:

重复向控制台输出“HelloWorld”

# 请为您的变更输入提交说明。以 '#' 开始的行将被忽略,而一个空的提交
# 说明将会终止提交。
#
# 日期:  Sat Mar 25 11:28:26 2023 +0800
#
# 交互式变基操作正在进行中;至 edb3a72
# 最后完成的命令(1 条命令被执行):
#    reword 54f88ff 第一次提交
# 接下来要执行的命令(剩余 2 条命令):
#    fixup 41346f3 第二次提交
#    fixup 3b9307e 第三次提交
# 您在执行将分支 'master' 变基到 'edb3a72' 的操作时编辑提交。
#
# 要提交的变更:
#       修改:     HelloWorld.java

完成修改后查看git log此时历史记录已成功合并:

image-20230325113207939

通过git rebase命令,我们可以在将 commit push 上远端仓库前,对本地仓库的 commit 进行编辑、重组、合并、编排,来获得干净整洁,合理颗粒度的历史记录。切忌修改已经 push 过远端仓库的 commit !!!

git cherry pick

使用git cherry pick命令,你可以明确将某几个 commit 应用到当前分支上。例如现在分支情况为:

image-20230325132314042

现在我想直接将这个 commit 应用到 main 分支上。输入:

git cherry pick C2(这个 commit 的 Hash 值)

image-20230325132449907

这就已经生效了,这会在 main 分支上新建一个 commitC2’。这两个 commit 内容一样,但 Hash 值是不一样的。

Git 工作流

最后简要描述下 Git 工作流,Git 工作流工作流展示了一个项目为了隔离不同状态的代码,需要通过多种不同种类的分支配合,达成快速迭代的目的。目前流行的分支模型有特性分支开发模式、主干开发模式等。

特新分支开发模式

img

  • 分支管理复杂;
  • 开发周期相对较长;
  • 视时长,与 master 分支的差距会越来越大,最终可能会导致难以解决冲突甚至不可能解决冲突。

主干开发模式

img

  • 分支管理简单;
  • 开发周期可以很短,随时都能签出新版本的分支;
  • 代码质量要求高。

最后

  1. 掌握 Git 工具,帮助提高开发效率和协作效率;
  2. 常用的几大命令:addcommitpushpullstatus应该熟练掌握,并可通过[option]可选项进行功能的增强;
  3. commit log 规范是为了方便追溯历史,以及记录项目变更,更重要的是,可以让人通过查看历史记录快速了解项目;
  4. 常 pull。经常 pull 保证你是在最新分支上修改,这可以通过频繁 commit 保证。而最后 push 时,应该考虑是否可以通过 rebase 整理历史记录后再 push 上远端仓库;
  5. 使用 stash 暂存区保证紧急切换代码时忘了 commit 导致可能的代码污染;
  6. 根据实际使用的分支模型。一旦决定,就应该积极贯彻,否则由于破窗效应,良好的分支管理将被打破,项目质量与迭代速度也将得不到保障。
相关链接

只会 git log ? 其实 git 还有两个好用的 log 命令

三行代码让你的 git 记录保持整洁

如何规范你的 Git commit?

Google 和腾讯都采用什么样的开发模式?

Git 沙盒练习