Git系列的前几篇文章针对基础知识进行了详细讲解,但是Git还包含很多其他命令,就不每个都展开细讲了,本篇文章整理了一些2.0+版本的常用Git命令,以供备忘。
1.1 git clone
克隆远程版本库到本地所指定的路径中,包括代码,分支和版本的提交记录等;
若后面不加本地路径,则默认克隆到当前目录中,且仓库所在目录名为远程仓库的名称;
可以参考Git系列讲解(一):代码托管平台GitCode及本地Git环境搭建 的使用。
其它用法:
git clone -n
不做检出操作,也就是只克隆.git
,后续想检出代码时直接使用git checkout HEAD
即可。
git clone --branch
(1) 参数解释:
–branch 指定了要克隆的分支或标签。
–depth=
(2) 恢复完整的提交记录
由于指定了–depth,所以提交记录不完整。如果想要恢复完整的提交记录,此时只需要使用命令git pull --unshallow
即可。
(3) 恢复所有的分支
指定了–depth之后,只会clone默认分支。如果想要所有的远程分支,则可以将remote.origin.fetch里的默认分支(master)改为星号(*)。
有两种方法修改remote.origin.fetch,一是使用 git config 命令 git config remote.origin.fetch “+refs/heads/*:refs/remotes/origin/*”。二是修改.git/config文件中的remote.origin.fetch。
(4) 总结
有时候项目很大,若想完整的clone整个项目确实要花费不少时间,在比较着急的时候我们可以使用–branch或–depth参数只clone我们想要的那个分支或者某(些)笔提交,这样可以极大的缩短clone项目花费的时间。如果后期想要恢复所有提交和所有分支,则可以参看上面(2)(3)提到的方法。
1.2 git init <仓库名称>
初始化一个git仓库,执行完后目录下会出现 .git 目录,并且默认当前为master分支。
其它用法:
bareproject.git
目录结构和上面的git仓库里边的.git
目录结构是一样的,就相当于普通git仓库的.git目录,而没有工作区,所以git相关的操作(git add/push/commit等)都是无效的。bare仓库的意义在于作为git的中心仓库而使用在服务器上,存储各个客户端所提交的版本信息等。2.1 git status
查看文件在工作区和暂存区的状态,包含修改,删除,增加的文件及未跟踪的文件
2.2 git diff
查看工作区中已经跟踪的文件对比暂存区记录的变更内容,也就是文件执行git add后,git diff就没有输出了
其它用法:
2.3 git add .
将工作区所有文件的变动记录添加到暂存区(包含文件更新,删除,新建等记录,与git 1.0+版本的git add -A相同)
其它用法:
2.4 git rm
删除工作区某文件,并将删除记录添加到暂存区,等同于rm
其它用法:
git commit
这样的操作,这个文件就会从本地版本库或者是远程版本库中删除。这个命令的应用场景一般是发现某文件有问题,想从版本库中删除,但是后续还需要在此文件基础上修改,所以不直接删除本地工作区中此文件。2.5 git mv
更改工作区某文件的名字,并将更改记录添加到暂存区。
2.6 git commit
提交所有暂存区记录的文件(更新,新建,删除)到版本库。执行此命令后会自动打开.git
目录下的COMMIT_EDITMSG文件(默认使用nano编辑器),编辑提交信息后,保存退出,本次提交即可完成。
其它用法:
3.1 git log
这一部分内容在Git系列讲解(四):提交记录之git log与git blame的使用 进行了详细讲解,这里就大概整理一下。
其它用法:
3.2 git reflog
reflog 可以显示本地仓库变更动作的历史记录,比如切换分支,仓库回退,仓库提交 ( 包括amend的提交 ) 等,下面介绍两个使用场景。
案例一:回退amend的提交
有的时候使用git commit --amend
提交代码后,突然想使用git reset
回退到上一笔提交,但是我们知道git log
中amend提交记录会覆盖上一笔提交,没有办法拿到上一笔提交的commitId,这时候可以使用git reflog
来查询上一笔提交的commitId,从而进行回退提交。
案例二:恢复删除的分支
此时如果想恢复删除的dev分支,则可以使用下面命令。
git branch dev dbe6784
4.1 git reset
(1) 主要作用就是重置HEAD指针到某笔提交,默认效果等于下面的git reset --mixed
(2) 这里的
也可以是HEAD指针,如git reset HEAD
,代表reset到HEAD所指向的那笔提交(即最新的一笔提交)
(3) 注意未跟踪的文件(没有进行git add的文件)不会受影响,这一点适用下面所有选项。
其它用法:
–hard
一样,这些文件在工作区,暂存区和版本库中全部重置;–mixed
一样,这些文件在工作区中不会被重置,改动依然保留。而这部分文件有两种情况下无法正常进行reset操作而导致终止,如下图所示:–merge
选项类似,不同点在于即使是添加到暂存区的文件,也会受两种异常情况的影响。4.2 git checkout
此命令除了有切换分支的功能,还有检出文件的作用,以此达到撤销的目的。
注意未跟踪
或者已跟踪未提交过
的文件不受影响。
其它用法:
git checkout HEAD .
的区别就是,只对比暂存区和工作区的不同,所以说已经执行过git add的文件,用此命令不会进行检出操作,如果想检出已经git add的文件,可以使用git checkout HEAD .
git checkout HEAD
的区别就是,只对比暂存区和工作区的不同,所以说已经执行过git add的文件,用此命令不会进行检出操作,如果想检出已经git add的文件,可以使用git checkout HEAD
4.3 git revert
不同于git reset
,git revert会生成一个新commit,这个commit的作用是撤消一个已存在提交的所有修改。head指向新生成的这个commit,本质上是撤销或者倒转。
其它用法:
-n
选项代表not commit,所以如果加上-n,则需要在git revert后再手动执行git commit。5.1 git branch
显示本地所有分支
其它用法:
-vv
,则可以进一步显示本地分支对应的远程分支名。5.2 git tag
列出所有本地标签
其它用法:
5.3 git checkout
切换到指定分支或标签,注意直接使用git checkout
会出现头指针分离的状态,一般情况没有这么使用的。
其它用法:
6.1 git merge
合并指定分支到当前分支
6.2 git rebase
变基操作。如下图所示,例如当前是处于work分支,执行git rebase master
,首先找到两个分支共同的父分支C2,然后截取work分支上C2以后的分支(即W3),改变W3的基底分支为master分支上最新的分支,这个改变的过程中会有merge的过程而形成W3’。
这里有必要讲一下变基的意义,个人认为变基操作和合并操作虽然都有将两条分支合并到一起的能力,但是变基可以形成一个非常清晰的提交历史,分支图上不会分叉。具体可以看另一篇文章Git系列讲解(三):同一分支下多人协同开发。
虽然变基有着这样的优点,但是它却有一个致命的缺点,就是无法在分支图上体现出它是何时进行合并的,而git merge却可以体现出来,加上git rebase操作确实有一些复杂,导致企业一般情况很少使用它。虽然分支图会比较乱出现很多自动合并的提交信息,但是一般情况无伤大雅。最后到底要不要使用git rebase,请各位结合实际情况进行考虑。
6.3 git cherry-pick
cherry-pick顾名思义,就是摘取某个提交然后挂在当前的分支上。对比merge和rebase来讲,其优势在于灵活性高,想用哪个提交的内容,直接cherry-pick过来就行。
其它用法:
git cherry-pick
cherry-pick支持范围摘取,注意左侧的commit时间早于右侧的commit时间,而且选取的范围是左开右闭的,所以想把最左侧的commit也成功取到,则需要这么写git cherry-pick
git cherry-pick -e
可以编辑提交过程中信息
git cherry-pick -n
不自动进行commit操作,只是将内容更新到工作区和暂存区,后续可以手动使用git commit
将变更提交到版本库中。
git cherry-pick -x
在commit的信息后面自动添加一行“(cherry picked from commit xxx)”,强调这条commit是cherry-pick过来的。
git cherry-pick -s
在commit的信息后面自动添加一行“(Signed-off-by: xxx)”,强调这条commit是谁cherry-pick过来的。
git cherry-pick -m
有些提交是由两个分支合并而来的,那么这个提交就会有两个parent。而cherry-pick其实是使用目标提交与父提交的差异补丁进行工作的,那么在cherry-pick这样的合并提交时,如果不指定用哪个parent与目标提交做差异补丁,那么cherry-pick就会失败。而-m
选项可以指定使用哪个parent做差异补丁,两个parent分别用1号和2号代表,其中1号parent代表当前分支上的提交,2号parent代表合入过来的分支上的提交,所以这个数值一般指定为1
。
7.1 git remote -v
查看远程版本库信息(包含远程版本库名称和仓库地址)
其它用法:
git remote show
查看指定远程版本库信息,例如git remote show origin
git remote add
添加远程仓库。这条命令适用于新建的工程,可以使用这条命令添加一个远程仓库的名称和url地址,再通过git push
命令将仓库信息一并提交到服务器,这样远程服务器那边就创建好了此仓库和分支。
7.2 git fetch
获取远程仓库更新信息,并将信息更新到本地的远程仓库副本。git fetch使用后,可以根据实际情况确定是否进行与本地仓库进行merge。
7.3 git pull
拉取远程分支代码并合并到本地,相当于git fetch + git merge
7.4 git push
在使用git commit向本地仓库提交之后,使用此命令向远程仓库推送本地仓库的提交信息(代码,索引,提交记录等)。当然这条命令也可以推送本地新创建的远程分支,远程仓库等。关于如何本地创建远程分支和远程仓库,请看前面的内容。
其它用法:
7.5 git push --tags
上传本地所有标签
先讲述两个场景:
场景一: 正在dev分支下在开发某个功能,突然来了一个bug需要解决,这个时候如果切换到bugfix分支进行修改bug,那dev分支下的修改就没有了,但是现在的代码没写完,所以我还不想提交。怎么办?
场景二: 代码写的正高兴呢,突然发现写错分支了,写在了bugfix分支,怎么将代码修改更改到dev分支下?
上面两个场景都是在不适合提交的基础上,但是又想将现在的更改保留,有没有不用备份文件的方法?不用担心,git给我们提供了git stash缓存机制。
git stash
将工作区的修改内容(注意是git所管理的文件,即之前已经git add过的)添加到缓存中。执行完成后,缓存中会多出一条记录,并清除当前工作区修改内容。每执行一次git stash,缓存中都会多出一条记录,这个缓存类似数据结构的栈,保持后进先出的原则,这个我们下面讲git stash pop的时候会明白。
git stash save “注释”
和git stash一样,不过多了一个注释记录,方便后期查看理解
git stash list
查看缓存列表
git stash pop
执行这条命令,会将缓存中最后(最新)一条修改覆盖到工作区文件,并且这条缓存也会在缓存中清除。上面我们说缓存类似于栈,这条命令就能体现出来。
git stash apply stash@{index}
如果想将缓存中的某条记录覆盖到工作区,可以使用这条命令,并且执行后,也不会自动清除该条缓存。例如git stash apply stash@{1}
git stash show stash@{index}
查看当前工作区文件和某条缓存的差异,不过只能显示差异的数量,不能显示具体差异内容。
git stash drop stash@{index}
丢弃/清除某条缓存,清除该条缓存后,后面的缓存index会自动减一。
git stash clear
清除所有缓存
所以,上面两个场景的问题也就解决了。
场景一: 在切换bugfix分支之前,先将工作区的修改使用git stash缓存,然后切换到bugfix修改好问题后,再切换回dev分支将缓存覆盖到工作区继续开发工作
场景二: 发现没有在正确的分支进行编写代码,只需要将当前工作区修改缓存,然后切换到正确分支,再进行git stash pop或者git stash apply stash@{index}即可。
上面讲的git缓存机制的确是好用,不过对某些场景下却不适用。
场景一: 比如我在A分支上改完一个问题进行编译,恰好编译时间要很久,那我这期间就只能等着编译完才能进行别的分支开发任务。
场景二: 比如想使用Beyond Compare软件进行两个分支代码对比,但是软件只能是对两个目录的文件做对比。
有人提出对这套代码在本地克隆多个代码仓库即可解决,但是问题是多个代码仓库之间不能直接进行代码修改共享,在A仓修改的代码需要提交到服务器,然后B仓再通过git pull等命令拉取服务器内容,才能得到A仓的修改内容,不免有些麻烦。
所以针对这种情况,git提供了工作树的机制,即创建一个新的工作区(目录),这些创建出的工作区使用的都是同一个本地版本库,在创建工作区的时候,要指定一个分支进行管理(也可以创建新分支)。
9.1 git worktree add <工作树路径> -b
基于原有分支创建一个新分支,并基于新分支创建一个工作树。
Tips:新分支建议起名字加一个temp前缀,强调此分支是临时的。
其它用法:
git worktree add <工作树路径>
以工作树目录名称创建一个新分支,而此新分支是基于当前分支(或当前头指针位置)创建。
注:包含子模块的工作树在创建后,新生成的工作树目录子模块为空,需要重新同步子模块。
git worktree add <工作树路径> -b
指定新分支名创建工作树,新分支基于当前分支(或当前头指针位置)创建。
git worktree add <工作树路径>
这条命令会直接使用原有的分支进行创建新工作树。
9.2 git worktree list
列出所有的工作树,包括工作树路径及其对应的commit的哈希值和分支名称(或头指针状态)
9.3 git worktree remove <工作树路径>
删除某个工作树目录(包含子模块的工作树无法删除),但是其对应的分支不会被删除,可以后续手动删除。
其它用法:
9.4 git worktree move <工作树路径> <新工作树路径>
将某个工作树目录移动到另一个位置(或改名),包含子模块的工作树无法执行此操作。
9.5 git worktree lock <工作树路径>
给某个工作树上锁,上锁后不能对此工作树进行git worktree move
,git worktree remove
,git worktree prune
等操作。
9.6 git worktree unlock <工作树路径>
解锁某个工作树
9.7 git worktree prune
有时候没有通过git worktree remove进行删除工作树,而是使用rm等命令直接删除了工作树目录,这样在.git/worktrees
目录下还会存在此工作树信息,使用prune命令可以清除这部分无用信息。
基本命令:
git grep <字符串>
命令介绍:
和grep一样,都是用来搜索字符串的,不过既然是git命令,那要被搜索的文件必须是git仓库内的文件。
※只搜索已添加到暂存区或已经提交过的文件,未添加到暂存区的文件不会被git grep搜索。
常用选项:
10.1 git grep -e
可以看到这里使用了小括号来声明了运算优先级,括号内的先运算,所以最后的结果就是匹配 #define LOG_TAG 或 #define LOG_NDEBUG
10.2 git grep -e
上面介绍参数的时候说了,在不指定逻辑运算选项时,默认-e之间的参数为或的关系。所以最后结果就是匹配 LOG_TAG 或 LOG_NDEBUG。
git grep -e
等同于 git grep -E ‘
10.3 git grep --all-match -e
该例子得到的结果与10.2例子相比,可以看出来少了Bitmap.cpp等文件,这是因为指定 --all-match 选项后,只有同时满足所有筛选条件的文件才会被输出。很明显,少的Bitmap.cpp等文件只有 LOG_TAG 而没有 LOG_NDEBUG。
10.4 git grep
--选项上面已经介绍过了,这个没什么好说的,需要注意的是
(1) pathspec可以是要被搜索文件名的后缀。
git grep -w startPreview -- '*.cpp' '*.h'
(2) pathspec可以是要搜索的路径。
git grep -w startPreview -- 'services/camera/libcameraservice/api1'
(3) pathspec可以是要排除的搜索路径。
git grep -w startPreview -- :^'services/camera/libcameraservice/api1'