Git系列的前几篇文章针对基础知识进行了详细讲解,但是Git还包含很多其他命令,就不每个都展开细讲了,本篇文章整理了一些2.0+版本的常用Git命令,以供备忘。
目录
1. 创建版本库
1.1 git clone <url> <本地路径>
克隆远程版本库到本地所指定的路径中,包括代码和版本的提交记录等;
若后面不加本地路径,则默认克隆到当前目录中,且仓库所在目录名为远程仓库的名称;
可以参考Git系列讲解(一):代码托管平台GitCode及本地Git环境搭建 的使用。
- git clone -n <url> <本地路径>
不做检出操作,也就是只克隆.git
,后续想检出代码时直接使用git checkout HEAD
即可。
1.2 git init <仓库名称>
初始化一个git仓库,执行完后目录下会出现 .git 目录,并且默认当前为master分支。
- git init --bare <仓库名称> 初始化一个git的bare(裸)仓库
(1) 看下图,git裸仓库bareproject.git
目录结构和上面的git仓库里边的.git
目录结构是一样的,就相当于普通git仓库的.git目录,而没有工作区,所以git相关的操作(git add/push/commit等)都是无效的。bare仓库的意义在于作为git的中心仓库而使用在服务器上,存储各个客户端所提交的版本信息等。
(2) 创建bare仓库时,名称一般以.git结尾,这样就解释了从gitcode等代码托管平台进行git clone的项目为什么都是.git结尾。
2. 修改和提交
2.1 git status
查看文件在工作区和暂存区的状态,包含修改,删除,增加的文件及未跟踪的文件
2.2 git diff
查看工作区中已经跟踪的文件对比暂存区记录的变更内容,也就是文件执行git add后,git diff就没有输出了
- git diff <file> 查看具体某个文件的变更内容
2.3 git add .
将工作区所有文件的变动记录添加到暂存区(包含文件更新,删除,新建等记录,与git 1.0+版本的git add -A相同)
- git add <file> 将工作区某文件变动记录添加到暂存区
- git add -u 只添加已经跟踪的文件变更记录到暂存区
2.4 git rm <file>
删除工作区某文件,并将删除记录添加到暂存区,等同于rm <file> + git add <file>
- git rm <file> --cached
在暂存区中添加文件的删除记录,工作区文件不变。然后再进行git commit
这样的操作,这个文件就会从本地版本库或者是远程版本库中删除。这个命令的应用场景一般是发现某文件有问题,想从版本库中删除,但是后续还需要在此文件基础上修改,所以不直接删除本地工作区中此文件。
2.5 git mv <old name> <new name>
更改工作区某文件的名字,并将更改记录添加到暂存区。
2.6 git commit
提交所有暂存区记录的文件(更新,新建,删除)到版本库。执行此命令后会自动打开.git
目录下的COMMIT_EDITMSG文件(默认使用nano编辑器),编辑提交信息后,保存退出,本次提交即可完成。
- git commit -m “提交信息” :与git commit相比,不会自动打开提交记录文件
- git commit --amend :是对上一次提交的补充,新的提交信息及commit id会覆盖上一次提交的
- git commit -am “提交信息” :相当于git add与git commit -m的结合
3. 查看提交历史
这一部分内容在Git系列讲解(四):提交记录之git log与git blame的使用 进行了详细讲解,这里就大概整理一下。
- git log
git log --pretty=
git log --oneline
git log --stat
git log -p
git show commitID
git show --stat commitID
git log --date=
git blame <file>
git blame -L <开始行数>,<结束行数> <file>
4. 撤销
4.1 git reset <commit>
(1) 主要作用就是重置HEAD指针到某笔提交,默认效果等于下面的git reset --mixed <commit>
(2) 这里的<commit>
也可以是HEAD指针,如git reset HEAD
,代表reset到HEAD所指向的那笔提交(即最新的一笔提交)
(3) 注意未跟踪的文件(没有进行git add的文件)不会受影响,这一点适用下面所有选项。
- git reset --soft <commit>
工作区和暂存区不变,重置版本库到某笔提交 - git reset --mixed <commit>
工作区不变,重置暂存区及版本库到某笔提交 - git reset --hard <commit>
重置工作区,暂存区,未进行commit的内容都会被清除。 - git reset --merge <commit>
分两种情况讲解此选项:
(1) 工作区中未改动的文件和已经加入到暂存区的改动文件,这部分的处理就和–hard
一样,这些文件在工作区,暂存区和版本库中全部重置;
(2) 还未加入暂存区的改动文件,这部分的处理就和–mixed
一样,这些文件在工作区中不会被重置,改动依然保留。而这部分文件有两种情况下无法正常进行reset操作而导致终止,如下图所示:
第一种情况:重置的那笔提交中不包含此文件
第二种情况:重置的那笔提交中包括此文件,但是那笔提交并不是最新对此文件进行修改的提交。
- git reset --keep <commit>
这个和–merge
选项类似,不同点在于即使是添加到暂存区的文件,也会受两种异常情况的影响。
4.2 git checkout
此命令除了有切换分支的功能,还有检出文件的作用,以此达到撤销的目的。
注意未跟踪
或者已跟踪未提交过
的文件不受影响。
- git checkout HEAD .
对比工作区,暂存区与版本库中的不同,从本地版本库中(当前分支)检出当前目录下的所有文件,覆盖到工作区与暂存区。 - git checkout .
与git checkout HEAD .
的区别就是,只对比暂存区和工作区的不同,所以说已经执行过git add的文件,用此命令不会进行检出操作,如果想检出已经git add的文件,可以使用git checkout HEAD .
- git checkout HEAD <file>
对比工作区,暂存区与版本库中的不同,从本地版本库中(当前分支)检出某个文件,覆盖到工作区与暂存区。 - git checkout <file>
与git checkout HEAD <file>
的区别就是,只对比暂存区和工作区的不同,所以说已经执行过git add的文件,用此命令不会进行检出操作,如果想检出已经git add的文件,可以使用git checkout HEAD <file>
4.3 git revert <commit>
不同于git reset <commit>
,git revert会生成一个新commit,这个commit的作用是撤消一个已存在提交的所有修改。head指向新生成的这个commit,本质上是撤销或者倒转。
- git revert -n <commit>
加上-n
选项代表not commit,所以如果加上-n,则需要在git revert后再手动执行git commit。
5. 分支与标签
5.1 git branch
显示本地所有分支
- git branch -a
显示本地和远程所有分支名称,一般与-v或者-vv一起使用。 - git branch -v
显示本地分支名称,提交的简写哈希值以及提交注释。如果是-vv
,则可以进一步显示本地分支对应的远程分支名。 - git branch -r
显示所有的远程分支名 - git branch <new branch name>
创建一个新的本地分支 - git branch -d <branch name>
删除一个本地分支,没有合并的分支不能删除 - git branch -D <branch name>
强制删除一个本地分支,小心使用
5.2 git tag
列出所有本地标签
- git tag <tag name>
基于最新提交创建标签 - git tag -a <tag name> <commit id>
基于某笔提交创建标签 - git tag -d <tag name>
删除本地标签
5.3 git checkout <branch / tag>
切换到指定分支或标签,注意直接使用git checkout <tag>
会出现头指针分离的状态,一般情况没有这么使用的。
- git checkout -b <new branch>
创建分支并切换到此分支 - git checkout -b <new branch> <old branch>
基于已有的branch,创建新分支并切换到此分支 - git checkout -b <new branch> <tag>
以tag那笔提交为最新提交,创建一个新分支并切换到此分支。如下面例子所示:
6. 合并与变基
6.1 git merge <branch>
合并指定分支到当前分支
6.2 git rebase <branch>
变基操作。如下图所示,例如当前是处于work分支,执行git rebase master
,首先找到两个分支共同的父分支C2,然后截取work分支上C2以后的分支(即W3),改变W3的基底分支为master分支上最新的分支,这个改变的过程中会有merge的过程而形成W3’。
这里有必要讲一下变基的意义,个人认为变基操作和合并操作虽然都有将两条分支合并到一起的能力,但是变基可以形成一个非常清晰的提交历史,分支图上不会分叉。具体可以看另一篇文章Git系列讲解(三):同一分支下多人协同开发。
虽然变基有着这样的优点,但是它却有一个致命的缺点,就是无法在分支图上体现出它是何时进行合并的,而git merge却可以体现出来,加上git rebase操作确实有一些复杂,导致企业一般情况很少使用它。虽然分支图会比较乱出现很多自动合并的提交信息,但是一般情况无伤大雅。最后到底要不要使用git rebase,请各位结合实际情况进行考虑。
6.3 git cherry-pick <commit>
cherry-pick顾名思义,就是摘取某个提交然后挂在当前的分支上。对比merge和rebase来讲,其优势在于灵活性高,想用哪个提交的内容,直接cherry-pick过来就行。
-
git cherry-pick <commit 1>…<commit n>
cherry-pick支持范围摘取,注意左侧的commit时间早于右侧的commit时间,而且选取的范围是左开右闭的,所以想把最左侧的commit也成功取到,则需要这么写git cherry-pick <commit 1>^…<commit n>
-
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 <num>
有些提交是由两个分支合并而来的,那么这个提交就会有两个parent。而cherry-pick其实是使用目标提交与父提交的差异补丁进行工作的,那么在cherry-pick这样的合并提交时,如果不指定用哪个parent与目标提交做差异补丁,那么cherry-pick就会失败。而-m
选项可以指定使用哪个parent做差异补丁,两个parent分别用1号和2号代表,其中1号parent代表当前分支上的提交,2号parent代表合入过来的分支上的提交,所以这个数值一般指定为1
。
7. 远程操作
7.1 git remote -v
查看远程版本库信息(包含远程版本库名称和仓库地址)
-
git remote show <remote>
查看指定远程版本库信息,例如git remote show origin
-
git remote add <remote> <url>
添加远程仓库。这条命令适用于新建的工程,可以使用这条命令添加一个远程仓库的名称和url地址,再通过git push <remote> <branch>
命令将仓库信息一并提交到服务器,这样远程服务器那边就创建好了此仓库和分支。
7.2 git fetch <remote> <branch>
获取远程仓库更新信息,并将信息更新到本地的远程仓库副本。git fetch使用后,可以根据实际情况确定是否进行与本地仓库进行merge。
7.3 git pull <remote> <branch>
拉取远程分支代码并合并到本地,相当于git fetch + git merge
7.4 git push <remote> <branch>
在使用git commit向本地仓库提交之后,使用此命令向远程仓库推送本地仓库的提交信息(代码,索引,提交记录等)。当然这条命令也可以推送本地新创建的远程分支,远程仓库等。关于如何本地创建远程分支和远程仓库,请看前面的内容。
- git push <remote> :<branch / tag>
删除远程仓库的某个分支或者标签
7.5 git push --tags
上传本地所有标签
8. 缓存修改(git stash)
先讲述两个场景:
场景一: 正在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}即可。
9. 工作树(git worktree)
上面讲的git缓存机制的确是好用,不过对某些场景下却不适用。
场景一: 比如我在A分支上改完一个问题进行编译,恰好编译时间要很久,那我这期间就只能等着编译完才能进行别的分支开发任务。
场景二: 比如想使用Beyond Compare软件进行两个分支代码对比,但是软件只能是对两个目录的文件做对比。
有人提出对这套代码在本地克隆多个代码仓库即可解决,但是问题是多个代码仓库之间不能直接进行代码修改共享,在A仓修改的代码需要提交到服务器,然后B仓再通过git pull等命令拉取服务器内容,才能得到A仓的修改内容,不免有些麻烦。
所以针对这种情况,git提供了工作树的机制,即创建一个新的工作区(目录),这些创建出的工作区使用的都是同一个本地版本库,在创建工作区的时候,要指定一个分支进行管理(也可以创建新分支)。
9.1 git worktree add <工作树路径> -b <new branch> <old branch>
基于原有分支创建一个新分支,并基于新分支创建一个工作树。
Tips:新分支建议起名字加一个temp前缀,强调此分支是临时的。
-
git worktree add <工作树路径>
以工作树目录名称创建一个新分支,而此新分支是基于当前分支(或当前头指针位置)创建。
注:包含子模块的工作树在创建后,新生成的工作树目录子模块为空,需要重新同步子模块。 -
git worktree add <工作树路径> -b <new branch>
指定新分支名创建工作树,新分支基于当前分支(或当前头指针位置)创建。 -
git worktree add <工作树路径> <old branch>
这条命令会直接使用原有的分支进行创建新工作树。
9.2 git worktree list
列出所有的工作树,包括工作树路径及其对应的commit的哈希值和分支名称(或头指针状态)
9.3 git worktree remove <工作树路径>
删除某个工作树目录(包含子模块的工作树无法删除),但是其对应的分支不会被删除,可以后续手动删除。
- git worktree remove <工作树路径> --force
和上面的差不多,只不过是即使包含子模块的工作树目录也可以删除。
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命令可以清除这部分无用信息。