从工作中梳理出来的实用git命令
在网上搜 git 教程,一搜一大把,但是我还是想再写一篇有关 git 的文章,原因有二:一是这类文章很好写,很适合拿来水;二是看到的教程大多都不是很接地气,要么是罗列一遍基本的命令,要么是只对几个命令进行了详细的分析,我想写一篇能够涵盖我日常工作场景的 git 操作指南,哪天忘了命令怎么打只需要打开这篇文章足矣。
git 实验
真的忘了某个命令有什么表现时,除了搜索官网文档或别人的使用经验外,其实还可以实践出真知,Learn Git Branching 是一个 git 在线实验的网站,你可以在上面输入 git 命令,然后可以在页面上很直观地看到分支是怎么变化的,让你更容易理解命令的作用,还有一些闯关练习能够帮助你更迅速的掌握 git 命令,值得推荐。
前置工作
安装 git 的方法不用多说,如果你和我一样是前端工作者的话,可以配置 vscode 作为 git 的文本编辑器。
git config --global core.editor "code --wait"
这个奏效的前提是将vscode添加到环境变量中。
如果你都有vscode了,不妨再下载一个 gitlens 插件,在 git rebase 的时候可以提供一个交互式rebase的界面,还能看到项目代码当前行的最近提交的作者的信息,很方便。
另外,如果你使用了本文中提到的git命令,却提示找不到命令,不妨检查是不是你的git版本是不是太旧了。
还有一件事,第一次使用 git,你还需要配置你的用户名和邮箱,这起名不要求必须和你在 gitlab 或 github 上的账户的名字和邮箱保持一致,它的作用只是为了给你自己的提交记录标记作者名和作者邮箱而已,与账号登录没有关系。
git config --global user.name 名字
git config --global user.email 邮箱地址
拉代码
git clone <仓库url>
一般公司的仓库都是在内网搭建 gitlab 仓库,推送代码是少不了身份认证的,常用的方式就是ssh认证。一般流程是:自己创建一对 ssh 密钥(公钥和私钥),将公钥配置到 gitlab 上,私钥放到.ssh目录下。
创建密钥的命令如下:
ssh-keygen -t rsa -C "密钥注释"
-t
参数指定加密算法,这里使用了 rsa 算法(一种利用大数因式分解的数学难题来保证破解难度的非对称加密算法), -C
指定密钥注释,网上很多人在注释写自己的邮箱,实则没有什么必要加注释。因为没有加 -f
参数指定文件名,所以会交互式提示你输入文件路径名,按默认的来即可,还会提示你输入passphrase,没有特殊需求直接两个回车跳过即可,默认会在用户目录的 .ssh 文件夹下(cd ~/.ssh
可以切换到该目录)创建 id_rsa 和 id_rsa.pub 两个文件。.pub
后缀的就是存储公钥信息的文本文件,没有该后缀的文件就是保存私钥的文本文件。将公钥文件的文本内容复制,粘贴到gitlab上配置ssh公钥的地方(相信你能在设置页中找到的)即可完成配置。使用默认文件路径的好处是 git push/pull 的时候会自动使用~/.ssh/id_rsa
去认证,认证成功就可以推送或拉取代码了。
建分支
master是稳定分支,一般是禁止push的,只能在gitlab上操作。所以我们开发一般要建立一个新分支。工作中用到的有关命令如下:
切换分支
git switch <branch-name>
# 或者
git checkout <branch-name>
基于当前分支创建新分支
git checkout -b <branch-name>
分支重命名
# 不要求提前切到任何分支
git branch -m <old-name> <new-name>
绑定上游分支
# origin 是默认主机名,指向你 git clone 时指定的仓库url,你也可以通过某些操作将origin更名成其他名字,但为了交流上的方便,求你别折腾了
git branch -u origin <remote-branch>
-u
等效于--set-upstream-to=
,别再用后者那么长的名字了。
推送/拉取分支
未绑定上游分支,推送/拉取代码方式:
git push origin <remote-branch-name>
git pull origin <remote-branch-name>
绑定后上游分支后,推送/拉取代码可简写成:
git push
git pull
加 -f
参数可以强制推送,一般用在rebase过(提交历史被整理,提交的hash已经发生改变)的分支上
你也可以在第一次推送/拉取代码的时候加 -u
参数,推送的同时也起到绑定上游分支的效果
git push -u origin <remote-branch-name>
git pull -u origin <remote-branch-name>
临时保存暂存区状态
如果拉取分支或切换分支或变基的时候暂存区有内容,会阻止命令执行,提示你先处理暂存区的内容。如果你想清理柜子底下的垃圾,可以先把柜子挪开,把垃圾清扫后再把柜子恢复原位。stash命令就是做这件事的, 可以先用stash命令将暂存区入栈,操作完了后再出栈。
# 暂存区入栈
git stash
# 暂存区出栈
git stash pop
删除分支
# 删除本地分支
git branch -D <branch-name>
# 删除远程分支
git push origin :<remote-branch-name>
上面删除远程分支的操作可以看成是推送一个空分支到关联的远程分支,导致了远程分支被删。
代码提交
实际的项目代码就是你的工作区,记录和上一个 commit 相比存在的变更,git还有一个暂存区,创建一个commit一般是先从将文件从工作区添加到暂存区,再用暂存区的内容创建一个commit 记录。
添加到暂存区
# 将A.txt B.txt C.txt添加到暂存区
git add A.txt B.txt C.txt
# 当前所有文件添加到暂存区
git add .
如果你不是要一次性将所有变更都添加到暂存区(可能想将变更拆成几个 commit),其实用命令添加到暂存区的方式不是很方便,我更习惯直接在 vscode 上的源代码管理侧边栏上操作。
一般 ide 都会集成 git 管理,使用 git 管理工具操作简单,但是需要在界面上执行点击按钮等交互操作;直接在终端输入 git 命令,可以通过在方向键找到上次执行过的命令,直接按回车执行,也很快,但对 add、rebase 等操作不如 ide 集成的工具方便,只能说各有优劣。
提交
git commit -m "提交注释"
# 查看提交历史
git log
# 将指定hash的commit拷贝到当前分支后面
git cherry-pick <hash>
修改提交历史
# 将暂存区内容合并到上一个提交,如果暂存区没有内容,就是单纯用来修改提交注释的
git commit --amend -m "新提交注释"
# 编辑前<n>个提交的提交历史
git rebase -i HEAD~<n>
rebase命令可以将多个commit合并成一个squash,输入上述rebase命令后会弹出一个文本,将pick
改为squash
,就会将该行的提交合并到上一个提交,改为drop
则删除该提交,保存文本就会开始执行变基(rebase)操作,如果出现冲突就要合并冲突,解决冲突后将文件添加到暂存区,然后输入 git rebase --continue
继续执行变基(千万别执行git commit
操作)。如果想终止变基,可以输入 git rebase --abort
变基操作会导致受影响的提交记录及其后面提交记录的hash值发生变化,变基后再次推送到远程分支,不出意外会提示你代码冲突了,这个时候一般用 -f
强制推送。
如果有人需要拉取你的代码,你推了一批变基后的提交到远程分支,别人重新拉取的时候也会提示代码冲突,如果他只需要拉取代码,不需要对代码做任何改动,可以强制让远程分支覆盖本地分支
git pull -f origin <remote-branch>:<local-branch>
版本回退
版本控制最重要的一个功能就是版本回退
# 撤销暂存区的修改
git reset HEAD
# 回到commit-hash及其以后的提交记录和当前暂存区内容未添加到暂存区的状态
git reset <commit-hash>
# commit-hash及其以后的提交记录撤销,回到暂存区
git reset --soft <commit-hash>
# commit-hash及其以后提交记录撤销,工作区回到这些commit变更发生前的状态
git reset --hard <commit-hash>
# 创建一个新提交,内容是使工作区撤销到commit-hash发生前的状态
git revert <commit-hash>
commit-hash 别名
很多时候要用到 commit记录的 hash 值,而你又不想每次重新敲一遍git log
查看怎么办?对于某些hash有别名,你可以用别名取代该hash。
- 分支名可以表示该分支最新的提交的hash
- HEAD表示当前分支最新提交的hash
HEAD~1
、HEAD~2
、······、HEAD~n
表示当前分支倒数第1、第2、······、第n个提交的hash
合并分支
在自己的分支上开发一段时间后,master分支可能已经有了新的提交,自己的分支就会落后于master分支,一般操作是先切到master分支 git pull一下拉取远程master的最新更新,然后切回自己的分支,执行 git rebase master,作用是比较当前分支和master分支,找到发生分叉的地方,在分叉后面属于当前分支的提交记录拼接在master分支最新提交的后面,这也是rebase(变基)名称的由来。合并分支还有一个 git merge 命令,会创建一个新提交记录,内容是合并分支,可以用在将其他分支合到master分支,但是现在合并到master的操作都是只能在gitlab上点击操作的,没有自己输入git merge的场景,反而 git rebase用的更多一些。
禁止忽略文件名大小写
默认git跟踪文件变化是不区分文件名大小写,直接重命名文件只改变了大小写,git是不会检测到这个变更的,有时候给我带来了困扰, 可以用以下命令取消
git config --global core.ignorecase false
忽略文件
一般我们会在项目根目录添加一个命名为 .gitignore
的文件,声明需要被 git 忽略的文件,你也可以在子目录添加,git 允许有多个忽略文件,每个.gitignore
的作用范围是文件所在目录及其子目录。
以项目根目录的.gitignore
为例,列举以下场景:
忽略项目根目录的 node_modules 文件夹下所有文件
node_modules/*
忽略了 dist 文件夹的所有文件,除了 .json
为后缀的文件
dist/*
# **/ 表示跨任意层目录
!dist/**/*.json
git hooks
在前端项目的代码库中,有时你会发现在提交代码和推送代码的时候执行了一些额外的命令,这可能是有一个叫husky或类似husky的npm库实现的。配置完执行husky命令,详情见官网(Husky - Git hooks (typicode.github.io)),可以生成一些git hook脚本,拷贝到.git/hooks文件夹下,git会在合适的时机执行对应的脚本,如 pre-commit 脚本会在执行git commit之前执行,如果你不希望该脚本被执行,可以输入git命令时加上 --no-verify
参数,如下:
git commit --no-verify -m "feat: 新提交"
你可能会问:为什么要多次一举用 husky,而不是在直接在 .git/hooks
编写脚本呢?因为一般我们希望参与项目的所有开发人员都用同一套 git hook,而你自己的 .git
文件夹的内容不能被 git 视为代码库的一部分,是没法推送到远程仓库的。
那我们一般用 git hook 能干啥呢,场景如下:
- 提交前规范化提交信息,可以搭配 commitlint等 npm 库使用,commitlint 用法
- 提交前规范化代码风格,搭配 eslint 等 npm 库使用,eslint用法
- ...
查找 bug 第一次出现的 commit
使用 git 进行版本管理意味着持续集成,当用户报告你的产品出了一个 bug,你想排查这个 bug是从哪个 commit 开始引入的,你可以尝试一下 git bisect
命令。这个命令的作用是标记当前HEAD是好的还是坏的,通过二分查找帮你切换到下一个要检查的二分中点的 commit
# 排查起点到终点的提交记录
git bisect start 终点commit 起点commit
# 标记当前commit 是好的,自动切换到下一个要检查的commit
git bisect good
# 标记当前commit 是坏的,自动切换到下一个要检查的commit
git bisect bad
# 停止检查,切回最初的HEAD
git bisect reset
自定义 git 命令
git 提供了取别名的功能,比如说我可以自定义 git unstage
来替代 git reset -- HEAD
命令,这是通过以下命令实现的
git config --global alias.unstage "reset -- HEAD"
# 或者
# 如果要执行的别名命令是非 git 指令,则要以感叹号为前缀
git config --global alias.unstage "!git reset -- HEAD"
分享一下我定义的 git 命令——
新建一个功能分支
git config --global alias.new "!git switch master && git pull && git checkout -b "
合并master的更新到功能分支
git config --global alias.sync "!f() { git switch master && git pull && git switch $1 && git rebase master; }; f"
转载自:https://juejin.cn/post/7026031516333375518