git命令总结
使用前配置
为当前用户配置用户名和邮箱:
git config --global user.name "Sam Chow"
git config --global user.email "samchowgo@163.com"
为系统用户配置用户名和邮箱:
sudo git config --system user.name "Sam Chow"
sudo git config --system user.name "samchowgo@163.com"
获取仓库
初始化一个仓库:
git init example
这个命令会在当前路径下初始化一个git仓库
克隆一个远程仓库到本地:
git clone git://github.com/samchow/someexample.git
远程仓库
查看当前所有的远程仓库:git remote
这个命令至少会显示一个远程仓库,一般是origin
git remote -v
在上述命令的基础上,会显示远程仓库的url
git remote show origin
查看某个特定的远程仓库信息,此处是origin,同时还会显示push
和pull
这两个操作关于分支的一些信息。
从远端仓库中获取数据:
git fetch [remote]
会从远程仓库拉取本地仓库不存在的数据和分支到本地仓库中,但是并不合并和处理与本地有冲突的分支和数据。
添加一个远程仓库:
git remote add sc [url]
,这样就创建了一个名叫sc
的远程仓库.如果执行git fetch sc
,会在本地添加sc的分支,比如sc/master
.也可以切换到这个分支上来。
远程仓库的重命名和删除:
git remote rename sc samchow
,这条命令会将远程仓库sc
改名为samchow
.
git remote rm samchow
,这条命令会把仓库samchow
的远程仓库删除。
分支数据的推送和获取
git push
和git pull
命令,远程分支部分总结
git版本库提交相关
查看工作区和暂存区文件差别:git diff
查看工作区和版本库HEAD上文件的的差别:git diff HEAD
查看暂存区和版本库HEAD上文件的差别:git diff --cached
或者git diff --staged
查看文件状态:
git status
或git status -s
,后者会精简显示。事实上status命令很强大,它会根据上次文件状态和当前文件状态来给出进一步操作的建议,比如如何返回上次文件状态,如何提交等等。
git status -s
会用前两列来分别显示相当于git diff
和git diff --cached
所对应的差距。
将文件从工作区添加到暂存区(此后文件会被追踪):
git add [filename]
注意一点:git diff
如果显示有差异,则无法提交
删除文件或移动文件,并保存到暂存区以便提交:
git rm [filename]
git mv [filename]
提交形成新节点:
git commit -m "some comments"
,注意这里的-m
是必须的,后面要跟comment。
注意:要避免使用git commit -a
来强制提交。
修改最后一次提交(反悔一次)
git commit -amend
查看历史提交
git log
该命令会按照时间顺序显示所有提交,并且包含了提交sha-1校验、作者信息、提交时间和提交说明。
git log [-数字]
,该命令会显示数字个提交。
git log -p
,该命令会在原有基础上添加diff信息。
git log --pretty=oneline
,该命令会使用一行精简显示历史,只有sha-1和提交说明。另外=后面还可以跟short
、full
、fuller
、format
等,其中format
可以个性化设置显示。
git log --graph
,用ASCII图形显示提交分支的合并历史。
git log --stat
,可以显示每次更新的文件修改统计信息。
Git重置——reset
用法1:git reset [--soft | --mixed | --hard] [<commit>]
该用法会改变commit的指向,从而改变提交链。
--soft
参数:只会更改提交的指向,但是暂存区和工作区的内容不变。--mixed
参数:默认情况下会使用该参数,这个参数下,改变提交的指向、清空暂存区差异,但是不改变工作区的内容。--hard
参数:该参数下,提交、暂存区、工作区的内容全部更改为修改后的提交所对应的内容。
用法2:git reset [<commit>] [--] [path/filename]
这种用法不会改变提交的引用指向,只会使用某个版本库的文件来覆盖当前暂存区里面的文件.如果不指定commit,则会默认为HEAD。这种情况下,相当于取消git add filename
的操作。另外中间的--
是可选的。
Git检出——checkout
用法1:git checkout [<commit>] [--] [path/filename]
这种用法是将暂存区和工作区内容变为一致。不同之处在于如果指定了commit,那么会用该commit对应的内容替代当前工作区和暂存区内容;如果不指定的话,那么用当前暂存区覆盖当前工作区。
注意:此处有一个特殊的用法git checkout .
或者git checkout -- .
,该命令直接用当前暂存区所有文件覆盖工作区所有文件,危险!
用法2:git checkout [<branch>]
该用法如果不指定branch,则对当前HEAD进行检查。
如果指定了branch,那么则将HEAD切换到该分支。
用法3:git checkout [[-b] new_branch] [start_point]
该用法会创建新的分支,如果指定-b
,会切换到新的分支上面。start_point
代表分支从哪一个特定的提交上面开始创建。另见分支部分的总结。
分支创建和合并
branch
用法1:git branch
,显示本地分支列表,并且当前分支会用特殊颜色和星号标记出来。
用法2:git branch <branchname> <strat_point>
,创建一个分支,如果指定start_point
,那么就从该提交上创建分支。
用法3:git branch -d <branchname>
和git branch -D <branchname>
,删除分支。前者检查分支是否已经合并,如果未合并,那么无法删除。后者则会强制删除。
用法4:git branch -m <branchname>
和git branch -M <branchname>
,重命名分支。前置会检查修改后的名称是否已经存在,后者则会强制重命名。
注意:git checkout -b <newbranch>
会创建分支并切换到该分支,而用法2则不会切换到该分支。
分支合并
git merge <branchname>
,将branchname合并到当前分支上。
合并之后,会出现一个新的提交。
遇到冲突之后,git会合并失败,此时会停下来,将有冲突的文件标记为unmerged
,等待用户自行解决。之后再用git commit
系列命令进行手动提交。查找冲突文件时,可以运行git status
命令来提示。
分支衍合
git rebase branch1 <branch2>
:该命令将branch2或者目前分支的修改作为补丁打到branch1上面(改变了branch2的分支,使之放到branch1后面而不是原有位置)。这样就可以再次git checkout branch1
和git merge branch2
来合并branch1和branch2。
分支衍合适用于打补丁的情况。
注意:一旦分支中的提交对象发布到公共仓库,就千万不要对该分支进行衍合操作。
如果你遵循这条金科玉律,就不会出差错。否则,人民群众会仇恨你,你的朋友和家人也会嘲笑你,唾弃你。
不解释为什么,需要借助图示太麻烦。
远程分支
先重新总结一下上面的几个命令
git clone
命令,克隆获取远程仓库(假设为origin),并将远程仓库的master作为本地的master,但是origin/master不会随着本地master变化而变化。
git fetch [remote]
命令,拉取远程仓库中的分支,但是并不与本地分支对比和合并。假设拉取远程仓库origin,并且origin/master和本地的master出现了分歧,则origin/master和master两个分支同时存在。
推送本地分支push:
git push [remote] [localbranch] [<:remotebranch>]
: 向远程仓库推送一个本地分支。注意,这里要推送分支,必须本人有对远程写的权限,而且没有必要把本地所有分支都推送到远程仓库。
推送完自己的本地分支后,其他人再fetch就会获取这个本地分支。
跟踪分支:
- 这里不是命令,而是一个概念。在从远程仓库获取的分支上面新建本地分支,本地分支就会自动跟踪这个远程仓库的分支。因此
git push
这个命令没有仓库名和分支名时,会自动找出目前分支对应的远程分支,并推送到远程分支上面。 - 如果远程仓库存在master分支,那么fetch和clone命令会自动在本地创建一个master分支来跟踪远程分支。
git checkout -b <branchname> <remote/branchname>
拉取了远程仓库的其他分支之后,可以使用该命令来创建一个新的跟踪分支,并且本地的branchname和远程仓库的branchname不一定同名。- 本地分支和远程分支默认是重名的,所有push命令的
:remotebranch
可以默认不写
获取远程分支且与本地分支合并pull:
git pull
,将目前分支跟踪的远程分支拉取并在本地合并,如果不成功则需要用户自己解决冲突。
最后是如何推送本地分支的正确做法:
每次推送之前,先运行git pull
命令确认不会和其他成员的推送有冲突(有冲突先解决冲突),再运行git push
命令来推送。
切忌使用git push -f
来强制推送!
删除远程分支:
git push [remotename] [:remotebranch]
:这个写法有点无厘头,用空的本地分支代替远程分支,即将远程分支删除。
暂存
由于分支切换可能比较频繁,导致有些工作尚未达到提交的程度,便需要紧急切换分支。这部分不能提交但是又不能丢失的工作就可以暂存。
注意:暂存只是保存了暂存区和工作区的文件,对提交和分支不会造成影响。
git stash
:保存当前工作到暂存栈中。
git stash list
:列举暂存栈中的内容。
git stash apply <stash@{数字}>
:恢复指定的暂存,如果不指定则恢复stash@{0}
。该命令不删除栈中的暂存。注意数字是从0开始的。
git stash pop
:恢复栈中第一个暂存并将其pop出栈。
git stash drop stash@{数字}
:丢弃栈中指定的暂存。
里程碑(打标签)
用于版本的发布使用的标签
查看标签:
git tag
:该命令会显示项目中所有的标签。
git tag -l 'v1.2.*'
:该命令会显示以“v1.2.”打头的标签。
git show v1.2
:该命令会显示v1.2的详细信息。
空提交:
git commit --allow-empty -m "empty commit"
:在新建标签之前,一般需要一个空提交.
新建标签:
用法1: git tag <tagname> [<commit>]
,创建一个轻量级标签。
用法2: git tag -a -m "msg" <tagname> [<commit>]
,创建一个带说明的标签。
用法3: git tag -s -m "msg" <tagname> [<commit>]
,创建一个带GnuPG签名的标签。如果将-s
改为-u
,则需要选择指定的私钥进行签名。
注意:如果不指定commit,则会将标签打到当前HEAD上面。
推送本地标签:
git push origin v1.2
,本地标签不会自动推送,需要手动推送。
git push origin --tags
,该命令会将本地所有标签推送。
父提交、祖先提交和分支指针移动表达式
一个^
表示一个父提交,如果^
后面跟上数字,则表示第几个父提交(用于分支合并时有多个父提交的情况,比如^^2
,表示父提交的第二个父提交,因为父提交有分支合并的情况)
而~
表示祖先提交,后面跟数字表示第n个祖先(~1
表示父提交,跟^
意思一样)
上述说的是提交历史中的父提交和祖先提交,而<refname>@{n}
则表示分支前面第n次改变,比如master@{2}
表示master指针两次移动之前所在的提交。这个适合于历史中找不到该提交指针的情况(比如可能是被reset掉了)。
git reflog show <branch> -n
:该命令显示某个分支或者HEAD的指针改变情况,显示分支前面的n次改变,注意n是数字。
改变历史
主要是git cherry-pick [commit]
和git rebase
这两个命令。这一部分不涉及交互式命令。
假设有提交
A-B-C-D-E-F
,master现在指向F,现在我们要去掉提交D。
命令1:git cherry-pick [commit]
:该命令将一个提交挑选出来,并且将其以补丁形式“放到”当前HEAD上面,形成一个内容和提交说明都一样的新提交,并将HEAD指向这个提交。
- 思路1 将HEAD checkout到C上面,使用
git cherry-pick master^
和git cherry-pick master
将E-F放到C上面,最后使用git checkout master
和git reset --hard HEAD@{1}
将master的指针放到新的F上面。注意:这里的F提交和原来的F提交的sha-1校验值不一样,是不同的提交 - 思路2 将HEAD checkout到D上面,并且使用
git reset --soft HEAD~2
,在保存C的暂存区和工作区的情况下,将HEAD指向B,此时提交则会形成一个新的跟D的内容一致的提交。然后利用git cherry-pick
命令等做跟思路1相同的操作。
命令2:git rebase --onto <newbase> <since> <till>
,先将分支since到till之间的分支(不包括since)作为补丁打到newbase上面,然后将HEAD重新放置到新的till上面,注意这里的till是不同的提交。
- 思路1: 使用rebase将
D HEAD
rebase到C上面,注意这里是D之后从E开始的分支。然后使用git checkout master
和git reset --hard HEAD@{1}
将master指向新生成的F上。 - 思路2: 使用跟命令1的思路2一样的操作,从B上面提交一个跟D内容一样的提交,然后将E-F rebase到这个提交上面
注意:使用上面这个命令可以丢弃历史提交
反转提交
主要用于远程分支,远程的分支由于多人协同,不能简单地删除提交,正确的作法应该是提交反转。
git revert <commit>
,将该commit的父提交再提交一次。
总结rebase命令
上面的分支合并与改变历史中都用到了git rebase
这个命令,现在总结一下rebase的用法。
git rebase --onto <newbase> <since> <till>
,如果缺省till的话,会默认为HEAD。其他的同改变历史 命令2
git rebase <since> <till>
,参考分支衍合
交互式衍合:
git rebase -i <since>
,运行该命令,会进入一个编辑文件,按照交互式提示来操作。
交互式操作合并提交遇到冲突时:
git reabse --continue
,该命令用于衍合遇到冲突而暂停的时候,解决完冲突之后继续衍合。
git reabse --skip
,该命令用于衍合遇到冲突而暂停的时候,不解决冲突跳过提交的衍合。
git reabse --abort
,该命令用于衍合遇到冲突而暂停的时候,停止衍合回到原来分支的情况。