常用Git命令行总结

forward
众所周知,Git
是程序员们重度使用的工具,以下对开发过程中常用命令做个总结备忘,也会谈一些简单的技巧使Git更好用。
以下所有命令行不区分平台,windows建议使用GitBash,Linux/MacOS建议使用原生Terminal或第三方终端(item2)。
Git不同版本之间,某些指令支持可能会有差异,尽量用新版本吧。本文撰写时使用的版本为 windows-gitbash的 git-2.31.1
,和 mac-terminal 的 git-2.32.0
。
git config
使用git config
命令尽心git的配置,常用命令如下:
命令 | 作用 |
---|---|
git config --list |
打印出当前环境的git配置信息, 接 --global 可以打印全局配置 |
git config user.name benjamin |
git配置username为benjamin,可后接--global |
git config user.email xxx@gxx.com |
git配置email为xxx@gxx.com,可后接--global |
git config --global core.autocrlf false |
windows总提示CR/LF的警告,设置后可去除 |
git config --global credential.helper store |
终端环境保存https的密钥,避免每次都要密码 |
git config --global core.quotepath false |
用来解决git status 显示中文乱码的问题 git-bash需要鼠标右键, 找到 选项->文本->本地Locale ,设置为 zh_CN ,而旁边的字符集选框选为UTF-8 |
当然,除了命令行配置,还可以直接编辑文件~/.gitconfig
文件,但是请清楚修改的具体含义,避免出现git后续使用的不必要的错误。
git init
如果不依赖任何远端仓库,而是自行本地新建仓库并初始化,使用git init
即可。
示例如下,创建一个名为 MyGitRepo的仓库:
|
|
在git init 执行后,执行ls -a
可以看到多了一个 .git 文件夹,这就是git的主干文件了。
接下来就可以新建文件,然后添加了,以及后续上传到远端。
git clone
然而大多数时候,我们并不需要自己新建,而是需要从远端Git仓库获取已经有的仓库。
不论是Github,还是Gitlab,拿到一个远程git仓库地址后,第一件事就是克隆(下载)到本地主机,git clone
命令的作用正在于此,假如现在想克隆Facebook的C++开源库 folly
,仓库地址为:https://github.com/facebook/folly.git,在一个本地目录打开命令行,执行:
|
|
命令执行完成后即可(如果因为网络失败,则可能需要git加速,或者代理加速了)。
如果因为网络的原因,clone的速度太慢,或者报错。有2种方法可以尝试:
-
Github的镜像站进行加速
只需要把
https://github.com
替换成https://hub.fastgit.org
即可。上述命令变为:
1
git clone https://hub.fastgit.org/facebook/folly.git
-
Git加速工具
Dev-SideCar
工具,Gitee地址:Gitee dev-sidecar
git add/restore
Git操作一个新文件的流程为:工作区(也就是本地文件)–> 暂存区(staged) –> 仓库,如下图所示:
|
|
有时候,git add想要排除一些文件,除了add之后restore,还可以在添加的时候就排除。示例:
git add --all ':!filename1' ':!*.txt'
git add . ':!submoduleNamexx'
说了添加,接下来说下几种程度的撤回,以前都是用checkout,但是后来引入了新词 restore,更能准确体现含义。
命令 | 作用 |
---|---|
git restore <file/dir> |
回退掉还没有add的untracked file; 后接文件或目录,一个点表示当前目录所有, 慎用 |
git restore --staged <file/dir> |
回退掉已经add,但还没有commit的tracked file; 后接文件或目录,一个点表示当前目录所有 |
git reset
git reset
承担着节点回退的功能,命令示范如下:
命令 | 命令 |
---|---|
git rest HEAD~n |
回退掉当前n个节点,回到没有add 的untracked状态(红色); 注意,其实是隐藏了 --mixed 的参数了,默认就是它;n一般取1,HEAD~n也可以换成一个具体的commit-ID |
git rest --soft HEAD~n |
和–mixed的区别是,是回到已经add,但是没有commit的状态(绿色) |
git rest --hard HEAD~n |
回退的最彻底,连untracked的本地修改也没了,慎用 ;如果误用,可以使用 git reflog show出历史,然后找回 |
注意:回退的过程最容易丢代码,虽然 reflog 是一大利器,但是不是永久保存,只有90天的记录。
git branch/switch
git branch
,git checkout
,以及git switch
承担着分支管理的一部分作用,命令示范如下:
命令 | 作用 |
---|---|
git branch |
查看本地所有分支,(当前所处分支会绿色高亮) |
git branch -va |
查看仓库所有分支(包括本地和远端) |
git branch -vv |
查看当前分支关联的远程分支 |
git branch --set-upstream=origin/main |
设置当前分支与远端的origin/main分支关联 |
git branch my1.1 V1.1 |
基于名为V1.1的tag创建一个本地分支my1.1 |
git branch -D my1.0 |
删掉名为my1.0的分支 |
git checkout -b tmpMain origin/main |
基于origin/main,创建一个名为tmp的本地分支 origin/main可以不写,则默认基于本地当前分支 |
git switch feature2 |
切换到名为feature2 的分支 |
git checkout
git switch
git fetch/pull
git fetch
为获取,目的为拿到远端的改动,存在仓库,不对本地文件和分支做任何改动。
git pull
为拉取,实际动作为git fetch
+ git merge
,会拉取到远端改动并合并到当前本地分支。如果有冲突,需要解决冲突。
git commit
git commit
即为提交,作用为将已经add到暂存区的文件进行提交,使用方式为git commit -m "commit信息描述",
这里要说的是commit的其他参数:
命令 | 作用 |
---|---|
git commit --amend |
针对最新的这笔commit,追加内容,形成新的commit |
git commit --author="user<email-addr>" |
这个是针对某笔commit,可以修改作者; 多个账号提交错误时可以挽救 |
git commit -am "commit信息描述" |
针对还没add的文件,相当于 add+commit 一步到位 |
git diff
git diff
的命令参数实在是太丰富了,可以用来对比查看本地没暂存(add)的工作区、暂存区的、仓库区的、分支之间的,节点之间的…等等修改.
官方help 信息如下:
|
|
根据个人在日常工作中最常用的命令简单汇总如下:
命令 | 作用 |
---|---|
git diff . |
查看当前本地修改的所有文件diff |
git diff <指定某个路径文件名> |
查看当前本地修改的某个文件diff |
git diff --staged <指定某个路径文件名> |
查看本地修改了且add了的某个文件diff |
git diff SHA1值 SHA2值 --stat |
查看2个节点之间的文件差距,文件列表形式给出 |
git diff SHA1值 SHA2值 <指定某个路径文件名> |
查看2个节点之间的某个文件的具体修改点 |
git diff SHA1值~1 SHA1值 <指定某个路径文件名> |
查看SHA值这个commit节点的某个文件的具体修改 举例 git diff HEAD~1 HEAD CMakeLists.txt |
git diff 分支名a 分支名b --stat |
用法类似2个commit节点,只是对比2个分支的HEAD差异 |
在使用 git diff
这个命令时,通常会有各种教程教配置界面化的 diff 比较工具,但是注意,git diff 本身是 CLI 形式的,所以配置成带UI的是可能会出问题的,带UI的配置命令应该是 git difftool
,而不是 git diff
。如果要看左右 compare 形式的 diff 效果,推荐用 vimdiff
,速度快,而且可以配置主题美化,推荐用 github
的 vim 主题。
git restore
git restore
的作用主要是回撤对文件的修改,有点像git checkout
恢复文件的修改,但区别如下:
|
|
git log
git log
的参数实在是多,这里简单的列出常用的几条:
命令 | 作用 |
---|---|
git log -n <branch_name> |
查看指定分支log,最新n条修改 不接分支名,都是默认当前本地分支 |
git log -n --oneline |
每条commit只打印一行信息,方便查看,查看前n条 |
git log -n --author=<author_name> |
查看指定作者, 查看满足条件的前n条 |
git log -n -- <path_or_file> |
查看某个指定的文件/文件夹, 查看前n条 |
git log -n -i --grep="myKeywords" |
搜索指定的commit关键词(忽略大小写), 满足条件的前n条 |
git log -n --graph --abbrev-commit |
查看最新n条提交,以树形图列出,且commit-id为前几位 |
git log --name-status |
每次修改的文件列表, 显示状态(新增为A,修改为M) |
git log --name-only |
只显示每次修改的文件列表,不显示修改状态 |
git log --stat |
每次修改的文件列表, 及文件修改的统计(修改了多少行) |
git log
还有其他的很多参数,可以将常用的写作系统alias,或者用git的alias,我习惯用前者,在 ~/.bash_profile 最后追加如下内容:
|
|
使用的时候直接执行gitlh -3
即可。
如果使用git的alias,应该是执行git config --global alias.lh 'log --graph --abbrev-commit'
,使用的时候执行git lh -3
(注意alias定义时单引号紧贴命令,而使用时git和lh之间有空格)
git cherry-pick
git cherry-pick
可以摘取其他分支的一个commit,假设commit-ID为 845fead232 ,则执行 git cherry-pick 845fead232
即可,如果有文件冲突,那么需要执行git mergetool
调用解决冲突的工具,比如Meld,BeyondCompare之类的带UI的工具手动解决,解决冲突后git cherry-pick --continue
,至此,已经完成一次成功的commit节点的摘取。
注意 :如果需要cherry-pick多个,建议要按照commit从老到新的顺序摘取,如果commit之间毫无依赖,顺序才可以交换。如果可以压缩,建议压缩后再摘取。
如果要一次cherry-pick多个commit,也是可以的:
|
|
举例某个我的博客仓库的feature分支如上log,现在如果要一次pick上述 中间3个修改节点 到master分支去,执行下面3种方式都可以:
|
|
git push
git push origin
可以推送当前分支的commit修改到同名的远端分支;
git push origin newBranchxx:newBranchxx
可以推送本地一个新建分支到远端,并在远端新建该同名分支origin/newBranchxx;
git push -f origin
可以强行推送本地分支,覆盖远端,一般用于个人随意支配的feature分支,慎用
。
git push origin --delete branchXX
可以删除远程分支origin/branchXX,慎用
。
git rebase
git rebase
的英文描述是 “Reapply commits on top of another base tip”,可以理解为"将分支的基础从一个提交改为另一个提交",和merge相比最大的特点就是可以让分支流更简洁(其实缺点也是这个,丢掉了当初的分支历史);另外,如果团队中有code-review的开发规范,rebase也是必不可少的,减少代码reviewer的负担。
适用场景:从mater分支某个节点A分出了feature分支,然后各自都向前走了几个commit节点,现在feature想要获取master的新修改,这时候可以merge,也可以rebase。
|
|
如果是merge,则是如下效果(其中F是merge节点):
|
|
如果是rebase,则是如下效果:
|
|
上述D',E’是表示rebase后commit-ID会变,如果有冲突还得解决。
可以rebase到别的分支(可以是本地或远端,最常用master),比如执行git rebase origin/master
,或者git rebase commit-ID-xx
选择rebase到某个分支的某个commit节点。
解决冲突时,执行git mergetool
调用工具手动解决,解决后git rebase --continue
,至此,就完成了一次成功的commit。如果中途想放弃rebase,git rebase --abort
即可。
-
常见问题1:rebase 有时候还会带着 submodule 一起,结果出现下述问题
1 2 3 4 5 6 7 8 9 10 11 12
Fast-forwarding submodule moduleAxx Auto-merging moduleAxx error: add_cacheinfo failed to refresh for path 'moduleAxx'; merge aborting. hint: Could not execute the todo command hint: hint: pick 4ec7e2de3b00bfc5a258e002b9e5b0efe8756345 update moduleAxx hint: hint: It has been rescheduled; To edit the command before continuing, please hint: edit the todo list first: hint: hint: git rebase --edit-todo hint: git rebase --continue
正确做法,执行
git rebase --edit-todo
,使用#
号注释掉 子模块 的pick选项,先处理其他代码的 confilict , 最后再来一起看看子模块的更新,一步到位更新。
git rebase -i(compress)
有时候开发一个功能,需要分步骤多次commit临时修改,最后压缩在一起。
git rebase -i HEAD~n
,其中n为数字可以用于压缩最新的n个提交为1个提交,其中HEAD~n
可以替换为那个节点具体的commit值。
下方例子为step1,2,3这3个节点压缩成一个。执行 git log -5 --oneline
,查看最新5条修改,想合并最新3条。
|
|
执行git rebase -i HEAD~3
,则自动跳出vim编辑界面,内容如下(大量#号内容都是注释,不影响commit-msg):
|
|
vim的操作不多说,第一个pick不要改,把后面的2个pick改成字母's'
,表示squash:
|
|
vim保存后:wq
退出,会跳出第二个vim界面,需要编辑commit-msg信息,默认是3条commit-msg都在,#号注释的就不会算在最终commit-msg中。
|
|
注释掉多余的,留下想要的msg,:wq
再次退出后即可,此时3条commit压缩成了一个,commit-msg也只有一条。
|
|
至此,已压缩成功。
git tag
在版本向前开发推进时,除了维护长期分支master(以及develop分支),对一些重要的Release或Feature节点需要打标签,其实就是对特定的这个commit打了一个锚点,方便问题回溯。
git tag
的使用示意如下:
|
|
git stash
日常工作中,临时切换任务是常态了,有时带来的就是分支切换了,但是$\longrightarrow$本地还有修改没有add,切分支是切不过去的,可选的办法有2种:
-
将未add的修改add一下,然后commit保存起来;
-
不执行add,而是
git stash push .
指令表示将未add的修改推入一个cache的堆栈,暂存起来;等事情忙完后,切换回这个分支,再执行
git stash pop
即可拿回之前缓存的修改。Ps:还可以针对单一文件操作,比如只想暂存 file1 和 file2 ,就执行
git stash push file1 file2
,需要注意的是 file1, file2 都必须是已经在git-tracked 状态,git stash的不能是新增的Untracked文件。
git submodule
add submodule
执行git submodule add <remote-URL> <local_path>
可以添加一个子模块到本仓库,local_path 如果省略,则是添加到当前目录。
🎈注意: local_path的结尾最好是和子模块的仓库名同名,不然仍旧会创建一个子仓库同名的目录。
还是针对上述的folly库,给他手动新增一个第三方图像库作为子模块,添加后自动克隆。
|
|
根目录下.gitmodules
文件的内容自动发生改动:
|
|
update submodule
有时候,子模块是远程仓库自带的,第一次克隆下来后需要手动 初始化+同步 ,执行:
|
|
如果不是第一次同步子模块,可以不需要--init
参数。
子模块其实和文件类似,只是在仓库中,它的修改,是它的commit节点来记录和判别的。
如果子模块向前更新了,主仓库要添加这笔改动的话,像普通文件一样,git add --> git commit --> git push
,一套流程带走子模块。
remove submodule
子模块的删除有2种方法,列举如下:
-
删除方法一(规范,推荐):
git submodule deinit path_to_submodule
是官方文档推荐的做法:1 2 3 4 5
# 举例要删除子模块subModAxxx git submodule deinit subModAxxx git rm subModAxxx # 如果deinit命令再加上 --force 参数,就相当于上面2句 git commit -m "delete submodule subModAxxx"
-
删除方法二(野路子,也能做到):
注意按下述顺序操作:
- 回退.gitmodules文件的修改,
git restore
回退之后git add
暂存; - 删除目录树和索引文件,执行:
git rm --cached path_to_submodule
,(path_to_submodule替换成实际路径); - 删除.git目录的文件记录,执行:
rm -rf .git/modules/path_to_submodule
; - 删除本地文件:
rm -rf path_to_submodule
,最后git commit
上述修改即可完全删除子模块 。
- 回退.gitmodules文件的修改,
query submodule
对子模块状态的查询,有2个好用的命令。
-
其中一个是
git submodule status
, 可以用来查看当前本地仓库子模块的实际SHA节点(还没commit); -
另外一个更好用,
git ls-tree <SHA值或tag名或HEAD>:目录 | grep 子模块名
可以用来查看这个指定节点的已经commit的子模块节点。或者可以省去目录指定,默认根目录,执行
git ls-tree <SHA值或tag名或HEAD> | grep 子模块名
。
以我博客仓库为例子,用于发布publish的子模块为 kissingfire123.github.io,放置于仓库根目录,则运行如下命令:
|
|
git grep
git grep
的作用是在仓库指定分支/节点的所有文本中搜索指定的关键字文本,默认是当前节点/分支。
所以可以不用切分支,就直接搜索指定分支下是否包含某个关键文本。
官方用法:
1 2
git grep -h usage: git grep [<options>] [-e] <pattern> [<rev>...] [[--] <path>...
假设我们想看看 ParseInfo 这个接口,在当前仓库分支有没有合入,以及代码状态。
搭配了几个好用且常用的命令参数后,就可以这样搜索:
|
|
other commands
命令 | 作用 |
---|---|
git remote -v |
查看仓库的远端地址 |
git name-rev <commit-id> |
根据一个commit-ID确定该节点来自于哪个分支,ID可取前9位 |
git merge-base --fork-point br1 br2 |
打印出分支br1和分支br2的最新共同祖先节点SHA值(共同节点:形状上类似2条单链表的共同节点) |
git ls-tree <node>:<dir> |
查看某个节点的对应所有子模块的SHA,node可以为主仓库的SHA值或tag名或HEAD,还可以后接 ` |
参考链接: