Git 实用小抄#
Git - the stupid content tracker.
在项目开发过程中,Git 是非常得力的版本控制管理工具。它使得开发者可以不分时间地点,高效协作。本文简要说明 Git 的基本用法,并介绍提高效率的小 tips,以及 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 fetch
和git merge
git fetch#
使用git fetch
来更新仓库中所有远程仓库的跟踪分支。实际上没有任何更改反映在任何本地工作分支上。这意味着只是你的本地仓库感知到了远端仓库的更新,但还没有做同步。IDEA
有自动fetch
的相关插件,建议安装以及时感知到分支更新。
git merge#
使用git merge target-branch
将目标分支合并到当前分支上。merge
的合并策略有两种:fast-forward 和三方合并:
- Fast-forward 合并 如果在两个分支之间没有任何变更,则 Git 会直接将目标分支指向源分支的提交对象,这就是所谓的 Fast-forward 合并。该操作不会创建新的提交对象,因为早期的提交已经包含了所有的变更。
-
三方合并 如果两个分支之间存在变更冲突,则需要进行三方合并。在三方合并中,Git 会创建一个新的提交对象,该对象包含两个分支之间的共同点和每个分支相对于共同点的变更。Git 还会尝试将冲突解决为一致的变更。
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 作者尤雨溪曾在知乎上提到:
攻击性还是很强啊 2333
git rebase target-branch
的本质是:找到当前分支与目标分支的共同祖先,先将我当前分支的修改 “抛到一边”,使我的当前分支与目标分支的历史提交记录一致,再将 “抛到一边” 的 commit 应用回当前分支。这种合并分支的策略与git merge
的区别是:
git merge
会留下无用的 commit log 信息,污染历史记录。git rebase
为了整理历史记录,理所当然会修改当前分支的 commit 。所以在一条分支上,当你 rebase 后,这条分支的顺序已经和之前不同了,倘若此时别人也在这条分支上,当他 push 时,可能会遇到错误,甚至可能会丢失更改。
所以建议git rebase
只用来修改还未 push 到远端仓库的 commit,或在只有你自己使用的分支上应用。
当然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
此时历史记录已成功合并:
通过git rebase
命令,我们可以在将 commit
push 上远端仓库前,对本地仓库的 commit 进行编辑、重组、合并、编排,来获得干净整洁,合理颗粒度的历史记录。切忌修改已经 push 过远端仓库的 commit !!!
git cherry pick#
使用git cherry pick
命令,你可以明确将某几个 commit 应用到当前分支上。例如现在分支情况为:
现在我想直接将这个 commit 应用到 main 分支上。输入:
git cherry pick C2(这个 commit 的 Hash 值)
这就已经生效了,这会在 main 分支上新建一个 commit
C2'。这两个 commit 内容一样,但 Hash 值是不一样的。
Git 工作流#
最后简要描述下 Git 工作流,Git 工作流工作流展示了一个项目为了隔离不同状态的代码,需要通过多种不同种类的分支配合,达成快速迭代的目的。目前流行的分支模型有特性分支开发模式、主干开发模式等。
特新分支开发模式#
- 分支管理复杂;
- 开发周期相对较长;
- 视时长,与 master 分支的差距会越来越大,最终可能会导致难以解决冲突甚至不可能解决冲突。
主干开发模式#
- 分支管理简单;
- 开发周期可以很短,随时都能签出新版本的分支;
- 代码质量要求高。
最后#
- 掌握 Git 工具,帮助提高开发效率和协作效率;
- 常用的几大命令:
add
,commit
,push
,pull
,status
应该熟练掌握,并可通过[option]
可选项进行功能的增强; - commit log 规范是为了方便追溯历史,以及记录项目变更,更重要的是,可以让人通过查看历史记录快速了解项目;
- 常 pull。经常 pull 保证你是在最新分支上修改,这可以通过频繁 commit 保证。而最后 push 时,应该考虑是否可以通过 rebase 整理历史记录后再 push 上远端仓库;
- 使用 stash 暂存区保证紧急切换代码时忘了 commit 导致可能的代码污染;
- 根据实际使用的分支模型。一旦决定,就应该积极贯彻,否则由于破窗效应,良好的分支管理将被打破,项目质量与迭代速度也将得不到保障。