Git 实用技巧
Git
中有很多实用的技巧, 让提交代码变成更愉悦的事, 本文将持续记录作者积累的一些技巧
~
与 ^
的区别
Commit Log
每条线上的星号(*
)右侧是对应的 CommitID
, 开始和结尾交叉代表公共提交。可以使用参数--decorate
和--graph
来简化显示 GIT
提交历史
git log --graph --decorate
上图第一条线拥有 165f5a1->3cb9272->de87e10->b6de943
这几条提交记录:
下面是等价的表示方法:
A = = A^0
B = A^ = A^1 = A~1
C = A^2
D = A^^ = A^1^1 = A~2
E = B^2 = A^^2
F = B^3 = A^^3
G = A^^^ = A^1^1^1 = A~3
H = D^2 = B^^2 = A^^^2 = A~2^2
I = F^ = B^3^ = A^^3^
J = F^2 = B^3^2 = A^^3^2
HEAD^[num]
用于选择第几条为主线,例如使用:
git reset --hard HEAD^2
这个将从左往右选取第二条为主线,且回退一个版本,commit-id 将为:
ae3d768->02c1b1b->b6de943
HEAD~[num]
用于在当前主线『默认 master』,回退版本,例如使用:
git reset --hard HEAD~1
这个将选取当前 master 主线,且回退一个版本,commit-id 将为:
3cb9272->de87e10->b6de943
HEAD^^
这种使用方式 HEAD 后面可以跟多个^,每个表示回退一个版本,效果同 HEAD~[num]
git reset --hard HEAD^^
这个将选取第一条主线,且回退两个版本,commit-id 将为:
de87e10->b6de943
HEAD~~
这种使用方式 HEAD 后面可以跟多个~,每个表示回退一个版本,效果同 HEAD~[num]
git reset --hard HEAD~~
这个将选取第一条主线,且回退两个版本,commit-id 将为:
de87e10->b6de943
如何选用
波浪符号~
在外观上几乎是线性的,并且想沿直线向后走; 而插入符号^
表示一棵有趣的树段或道路上的一个岔路口
- 大部分时间使用~——回溯几代,通常是你想要的
- 用于^合并提交——因为它们有两个或更多(直接)父级
代码统计
如果想了解多人团队中同事或者自己的代码统计情况, git
提供了相关的命令, 方便且直观
# 查看团队每个人的代码提交量
git shortlog -s -n
Fast-Forward 合并
Git
中的 fast-forward(快速前进)
是一种合并(merge)
分支的方式,它通常用于将一个分支的更改合并到另一个分支上。
Fast-forward 合并
的效果是,目标分支(主分支)
的指针直接移动到源分支(开发分支)
的最新提交,因此看起来就像是主分支“快速前进”到了开发分支的状态
Fast-forward 合并
通常是一种简单且干净的合并方式,因为它不会创建合并提交(merge commit)
,但它只适用于特定的情况,即主分支没有新的提交。如果主分支有新的提交,你可能需要执行普通的合并,这将创建一个合并提交以整合来自其他分支的更改
Fast-forward 合并
只会发生在特定条件下
- 合并到主分支
假设你有一个开发分支,比如 feature-branch
,并且你在该分支上做了一些更改。当你想将这些更改合并到主分支(通常是 master
分支)时,如果在合并时没有新的提交到主分支,Git
将执行 fast-forward
合并
- 没有冲突
在 fast-forward 合并
中,没有冲突会发生。这是因为在 fast-forward 合并
时,Git
简单地将目标分支(主分支)
指向源分支(开发分支)
的最新提交,这不会导致冲突
更多相关的内容可以查阅Merging vs Rebasing
变基 Rebase
笔者总结了rebase
的最常用三种用法(注意区间前开后闭):
合并 commit
在开发中, 可能自己的分支进行了多次的提交, 为了保持合入公共开发分支中自己的 commit 的整洁性, 可以使用 commit 合并
注意
一般使用场景是自己的本地分支, 最好不要在远端公共分支上进行这种操作, 是极其危险的
# 区间前开后闭, 一般可以使用 commitid~1来包含该提交
git rebase -i <start-commit> <end-commit>
# 此时, 会进入一个匿名分支, 需要切出一个新分支
git checkout -b <name>
# 推送到自己的远端
git push origin HEAD
修改 commit
如果前几次已经git commit
到本地, 此时想修改他们提交信息, 也可以是用rebase -i
命令
# 例如 git rebase -i HEAD~1 就是修改最近一次提交
git rebase -i <start-commit> <end-commit>
此时在vim
模式下, 与合并commit
所选择使用的pick选项
不同, 而是选择使用reword选项
来修改提交信息。
rebase -i
支持的选项有下图中的几种
特殊情况
如果只修改最新一次的提交信息, 可以使用git commit --amend
命令
此时等同于git rebase -i HEAD~1
复制 commit
可以复制某一个分支的一串 commit 到当前分支, 类似于cherry-pick
- base: 分支名称
- from: 待合并片段的起始 commitId(不包含)
- to: 待合并片段的结束 commitId(包含)
语法:
git rebase --onto base from to
# 被复制的分支上执行
git checkout -b <new-branch> <end-commit>
git rebase --onto <target-branch> <start-commit>^
合并远端分支
拉取远端代码的具体分支时, 拉取后与本地进行fast-forward
合并, 可以使用
git pull --rebase origin <target-branch>
重设分支基点
团队协作中, 主分支经常会更新一些内容, 因此我们提交到主分支前一般都需要先合并主分支的内容到开发分支, 主要使用两种方式合并
如果使用merge
来合并主分支, 那么会产生一条多余的无关合并记录, 从而污染了开发分支, 如图
为了解决上述的问题, 就得使用rebase
重设开发分支的基点, 使得开发分支的提交记录变得干净
git checkout feature
git rebase main
这样就将整个feature
分支从分支的顶端开始,有效地将所有新提交合并到main
。 但是,变基不是使用合并提交,而是通过为原始分支中的每个提交创建全新的提交来重写项目历史记录
合并后的分支图如下
删除本地无效的分支
远端有很多分支已经被删除, 而本地仍然存在, 删除本地的无效的分支可以使用prune
命令
# 列出已经失效的引用分支
git remote prune show origin
# 删除失效的分支
git remote prune origin
恢复被删除的 stash 代码
有时候不小心清空了stash list
中的备用代码, 想找回来怎么办? 可以使用以下命令:
# 撤销git stash clear的操作
git fsck --unreachable | grep commit | cut -d ' ' -f3 | xargs git log --merges --no-walk --grep=WIP
文件换行符
在windows
、unix
等各种系统上面采用了不同的换行符, 换行符的统一在团队合作时尤为关键
查看换行符
如果未统一换行符,那么必然会导致一些问题, 下面的命令可以检索出所有文件的换行符类型
详细的列出当前工作目录中所有文件的换行符类型, 总的来说, 换行符有以下几种常见格式
LF
:Unix
风格的换行符(\n)
CRLF
:Windows
风格的换行符(\r\n)
CR
:Mac OS
风格的换行符(\r)
none
:二进制文件或没有换行符的文件
git ls-files --eol
换行符配置
跨平台协作开发是常有的,不统一的换行符确实对跨平台的文件交换带来了麻烦。最大的问题是,在不同平台上,换行符发生改变时,Git
会认为整个文件被修改,这就造成我们没法 diff
,不能正确反映本次的修改。还好 Git 在设计时就考虑了这一点,其提供了一个 autocrlf
的配置项,用于在提交和检出时自动转换换行符,该配置有三个可选项:
true
: 提交时转换为LF
,检出时转换为CRLF
false
: 提交检出均不转换input
: 提交时转换为LF
,检出时不转换
用如下命令即可切换三种配置
# 提交时转换为LF,检出时转换为CRLF
# 全局配置
git config --global core.autocrlf true
# 局部配置
git config --local core.autocrlf true
# 提交时转换为LF,检出时不转换
# 全局配置
git config --global core.autocrlf input
# 局部配置
git config --local core.autocrlf input
# 提交检出均不转换
# 全局配置
git config --global core.autocrlf false
# 局部配置
git config --local core.autocrlf false
如果把 autocrlf
设置为 false
时,那另一个配置项 safecrlf
最好设置为 ture
。该选项用于检查文件是否包含混合换行符,其有三个可选项:
true
: 拒绝提交包含混合换行符的文件false
: 允许提交包含混合换行符的文件warn
: 提交包含混合换行符的文件时给出警告
用如下命令即可切换三种配置
# 拒绝提交包含混合换行符的文件
# 全局配置
git config --global core.safecrlf true
# 局部配置
git config --local core.safecrlf true
# 允许提交包含混合换行符的文件
# 全局配置
git config --global core.safecrlf false
# 局部配置
git config --local core.safecrlf false
# 提交包含混合换行符的文件时给出警告
# 全局配置
git config --global core.safecrlf warn
# 局部配置
git config --local core.safecrlf warn
统一换行符
如果想要统一换行符, 可以借助Prettier
# 直接通过prettier运行转换操作, 点为要修改的文件路径,可以改成对应的目录
npx prettier --write --end-of-line lf .
在工程应用中, 更推荐的方式是使用Vscode插件 EditorConfig for VS Code , 并在根目录创建配置文件.editorconfig
, 然后搭配Eslint
、Prettier
等工具, 完美解决换行符问题
想了解更多请参考作者的另一篇文章 代码风格工具集成
# .editorconfig
root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
Git Reset 的三个选项
- mixed(默认值)
回退一个版本,且会将暂存区的内容和本地已提交的内容全部恢复到未暂存的状态,不影响原来本地文件及未提交的本地修改
git reset (-–mixed) HEAD~1
- soft
回退一个版本,不清空暂存区,将已提交的内容恢复到暂存区,不影响原来本地的文件(未提交的也不受影响)
git reset -–soft HEAD~1
- hard
回退一个版本,清空暂存区,将已提交的内容的版本恢复到本地,本地的文件也将被恢复的版本替换
git reset -–hard HEAD~1
Git 恢复误删的分支
使用 git log -g 找回之前提交的 commit_id
使用 git branch recover_branch[新分支] commit_id 命令用这个 commit 创建一个分支
切换到 recover_branch_abc 分支,检查文件是否存在
Git 图片提交失败
在某些情况下, Git
提交成功并推送到远端后, 发现远端分支的图片并没有更新, 这种情况下需要使用-- force
或者 -- refresh
选项来进行add
git add --refresh .
# 或者
git add --force .
删除远端库敏感文件
有时候不小心将一些敏感文件提交到远端库, 这时候需要删除远端库该敏感文件所有的记录
第一步、删除本地记录
首先在本地操作历史记录, 用于删除本地库中该文件所有记录
如果是文件夹, 还需要在git rm
中添加-r
参数
git filter-branch --force --index-filter \
"git rm --cached --ignore-unmatch 待删除的文件(相对项目的路径)" \
--prune-empty --tag-name-filter cat \
-- --all
第二步、将记录覆盖到远端
将上一步的操作覆盖到远端, 将远端库中的记录也全部删除
# 覆盖所有的分支
git push origin --force --all
# 覆盖所有的tags
git push origin --force --tags
第三步、解除引用和垃圾回收
最后一步, 用于强制解除对本地存储库中的所有对象的引用和垃圾收集, 删除垃圾节省空间
git for-each-ref --format='delete %(refname)' refs/original | git update-ref --stdin
git reflog expire --expire=now --all
git gc --prune=now
回滚本地分支
git reset (--mixed) [commit id]
回退到指定版本,且会将暂存区的内容和本地已提交的内容全部恢复到未暂存的状态,不影响原来本地文件(未提交的也不受影响) 该参数--mixed
是git reset
的默认参数 例如需要回退一个版本, 执行下面命令:
# 回退一个版本
git reset HEAD~1
需要回退到某个 commitid(提交id通过git log查看
), 如需要回滚到4a50c9f
,则执行
git reset 4a50c9f
git reset --soft [commit id]
回退到指定版本,不清空暂存区,将已提交的内容恢复到暂存区,不影响原来本地的文件(未提交的也不受影响)
git reset --hard [commit id]
回退到指定版本,清空暂存区,将已提交的内容的版本恢复到本地,本地的文件也将被恢复的版本替换
git revert [commit id]
生成一个新的 commit,将指定的 commit 内容从当前分支上撤除
常见用法
- 撤销单个commit
git revert commitId
- 撤销多个不连续commit
git revert commitId1 commitId2 commitId3
- 撤销连续多个commit
# 前开后闭区间, 不包含commitId1, 但包含commitId2
git revert commitId1..commitId2
git revert -m [parent Id][commit id]
当需要 revert 回滚两个分支合并后的一个公共提交, 此时需要加上-m
选项来确认是第几个父 id(也就是确认回滚哪一条分支
); 可以通过git show [提交id]
来查看有几个父 id
例子: 回滚到 cad132423 这个公共提交的第一个父节点 git revert -m 1 cad132423
回滚远端分支
案例: 需要回滚 master 上面的代码到 4a50c9f
第一种方法
主要使用Merge
假合并策略, 以下是步骤:
- 先回滚到需要移除的 comitid 的前一次正确 commitid
git checkout -b remote-v1 4a50c9f
- 合并策略为强行保留现在的分支(假合并) 合并中完全采用 remote-v1 的代码
git merge -s ours master
- 推送到远程分支
# 也可以使用 git push origin HEAD:master
git push origin remote-v1:master
第二种方法
主要使用Revert
撤销, 以下是步骤:
- 先移除有代码错误的 comitid, 撤销一连串的 id 用
(commit1..commit2]
(前开后闭区间, 不包含commit1, 但包含commit2), 参数--no-commit
是用于后面手动提交
# -n 是 --no-commit 的缩写
git revert -n f7742cd..551c408
- 正常提交代码
git commit -a -m 'This reverts commit 7e345c9 and 551c408'
git push origin HEAD:master
第三种方法(极不推荐) 危险
这种方式是强制操作, 忽略所有警告和报错, 强行推送并覆盖远端分支的代码
危险系数高, 稍微不慎, 会导致其他人的代码丢失, 以下是步骤:
- 使用文章开头的方式回滚本地分支
- 强行提交到远端分支
git push origin master -f