目前版本控制系统比较流行的就是SVN和Git了,相比较而言,Git有分布式的优势,对网络依赖性更低,但SVN简单,这一条就有很强的生存能力了。用Git已经好几年了,不过很长一段时间只是在用clone pull add commit push
这些类Ctrl+C/V
的命令(Office中),连操作Head指针实现Ctrl+Z/Y
都没怎么用,想起去年收到了Leancloud的10X
程序员笔记本,里面附页还写着几行Git命令,突然觉得有些陌生了。
也只是突然想到,回忆一下,当是补上多年前未肯作的笔记了。
基本文件操作
检查文件状态
Git检查文件状态可以使用git status
,可以看到已经提交的修改和未提交的修改:
使用git diff
可以查看尚未暂存的文件的修改:
|
|
另外加上--cached
或者--staged
(新版支持)参数,可以直接查看已暂存的和上次提交时的差异。
基本文件操作
除去系统自带的mv
或者rm
命令,Git也有自己的git mv
和git rm
命令,在Git仓库中,后者不仅仅是对文件做了前者的操作,也在工作目录中做了前者的操作。
如git rm
在删除文件后,也从跟踪文件清单中删除了该文件(使用--cached
只是从暂存区中删除,使用-f
同时也删除文件),以后不会再跟踪该文件,而rm
命令的操作记录依然会被记录在跟踪文件清单中。
一个简单的例子,先创建一个文件:
此时未放入暂存区,直接删除就可以,Git也不会记录,但是如果Git已经跟踪了该文件,则直接删除状态为:
如果使用git rm test
,可以看到:
可以看到,test
文件的记录已经被删除了。
同样,git mv
也是一样的类型,git mv file1 file2
相当于:
查看提交历史
查看每次的提交历史可以直接使用git log
,可以看到每次的提交记录。另外,加上-p
参数可以展开每次提交的内容差异,加上-{d}
可以指定显示最近次数的差异,如-2
显示最近两次提交的差异。加上--since
或者--until
可以限制时间查询,如可以用git long --since=2.weeks
显示最近两周的修改。加上--word-diff
可以进行单词层面的对比,加上--graph
以ASCII
图形表示的分支合并历史。如果只想看每次提交的简略信息,可以加上-stat
参数。另外,可以使用--pretty
指定展示提交历史的格式,如用oneline
将每个提交放在一行显示(--pretty
常用参数有oneline,short,full,fuller和format(后跟指定格式)
)。
撤销操作
仅修改提交信息
如果提交信息写错了,或者有些文件漏掉了未添加到暂存区,可以使用amend
指令重新提交:
这样就完成了提交信息的修改。
取消暂存区文件
如果想取消暂存区的某个文件的暂存,有两种方法。一是上面的git rm --cached
直接将文件从暂存区中删除,实际文件不受影响。另外一个是HEAD
指针的操作。HEAD
可以理解为指向当前分支的指针,指向该分支最近一次的调用,操作HEAD
指针即可实现版本回退等操作。
这里直接使用reset
命令,将某个文件重置到最近一次提交时的状态:
因为上次test
未暂存,所以相当于从暂存区中取消该文件。
撤销对文件的修改
使用git checkout -- file
可以撤销上次提交以来,对某个文件的所有修改,本质上是拷贝了上次提交时的该文件来覆盖它,因此对该文件做的任何修改都会消失。该命令需要谨慎使用,最好的方式是通过分支的保存进度来恢复。
Git中所有已经提交的东西基本上都是可以恢复的,但未暂存的就不属于Git恢复的范畴了。
远程仓库
Git主要是在本地修改好了再推送到远程仓库,实际上对远程仓库的操作比较少,就一些基本的推拉行为。
- 查看远程仓库。
直接使用git remote
即可查看当前的远程仓库,加上-v
选项可以以详细模式查看。 - 添加远程仓库。
直接使用git remote add <shortname> <url>
,将仓库名和地址添加即可。 - 从远程仓库抓取数据。
有两种需求,一种是只从远程仓库拉取数据,但并不合并到当前分支,可以使用git fetch <remotename>
命令。
另外,使用git clone
获取的远程仓库会自动归于origin
名下。
另一种,需求是自动抓取并合并到当前分支,可以使用git pull
命令。 - 推送数据到远程仓库。
基本操作,git push <remotename> <branch>
。 查看远程仓库信息。
1git remote show <remote-name>远程仓库的删除和重命名。
- 删除远程仓库:
git remote rm <remotename>
- 重命名远程仓库:
git remote rename <orignname> <newname>
标签
Git可以给历史中的某个提交打上标签,以示其重要性,如v1.0
等。
列出标签
列出已有标签,可以直接使用git tag
命令,加上-l
参数可以过滤选项。如
|
|
创建标签
标签分为轻量标签和附注标签,轻量标签如其名轻量,只是一个特定提交的引用,本质上是将提交校验和存储到一个文件中,没有保存其他任何信息,因此创建也比较简单。附注标签则是Git数据库中的一个完整对象,是可以被校验的。附注标签通常包含打标签者的姓名、邮件地址、日期、标签信息等,并可以使用GPG(GNU Privacy Guard)签名及验证。
- 创建附注标签: 最简单的方式是使用
tag
的-a
选项:
|
|
查看标签:
其中,-m
是存储在标签中的信息,是必填内容。使用git show
也可以看到标签信息与对应的提交信息。
- 创建轻量标签: 轻量标签的创建不需要任何选项,直接提供标签名字即可。
|
|
查看标签:
此时用git show
只能看到标签的提交信息,没有额外信息。
后期上标签
也可以对过去的提交上标签,使用git log --pretty=oneline
时可以看到每次提交的校验和,如某次校验和是e0c29751bf13be3df3b5030cc589685752bd9fb6
,则可以通过该校验和给该次提交打上标签:
实际只需要部分校验和即可。
分享标签
通常情况,git push
并不会将标签推送到服务器上,需要通过显示命令才能分享标签到远程仓库。
如果要一次性推送所有本地新增标签到服务器上,则可以使用--tags
参数:
删除标签
删除本地仓库的标签,可以使用:
如果要同时删除远程标签,则需要使用git push <remotename> :refs/tags/<tagname>
来更新远程仓库标签。
标签检出
可以使用git checkout
命令查看某个标签指向的文件版本。但会使仓库处于头指针分离(“detacthed HEAD”)的状态:在”头指针分离“状态下,如果做了某些更改然后提交他们,标签不会发生变化,但新的提交不属于任何分支,也无法访问,除非确切的提交哈希。所以如果要进行更改,通常需要创建一个新分支:
如果继续对newversion
分支做改动,该分支的提交指针会继续向前移动,就不是原来的v1.12
标签了。
分支
Git好用很大原因是其极具优势的分支模型,使得分支处理方式更为轻量。
在使用git commit
新建一个提交对象前,Git会先计算每一个子目录的校验和,然后在Git仓库将这些目录保存为一个Tree对象,然后就可以创造一个提交对象,并包含了指向这个Tree对象的指针。Git使用blob类型的对象存储此次保存的快照。
关于Git的树结构,可以用Git官方仓库中的一张图说明:
这是首次提交后的结构图,此时Git仓库中有五个对象(五个校验和),最右侧的是三个存储文件快照的blob对象,中间是记录目录结构和blob对象索引的树对象,最左侧是包含指向书对象的指针和所有提交信息的提交对象。
此时因为是第一次提交,相当于祖先提交,提交对象中没有父对象,但之后的所有提交对象中,都会多一个父对象指针,指向上次提交。
Git分支在本质上是一个指向最新提交对象的指针,每次提交操作之后,指针都会更新到最新提交。
分支就是某个提交对象往回看的历史。
使用git branch
可以列出所有的分支,加上--merged
或--no-merged
可以显示已合并或未合并的分支。
分支创建
Git使用master
作为默认的分支名,如果要创建分支,可以使用branch
选项。
但此时只是新建了一个分支,并未将当前工作分支切换过去。Git确定当前工作的分支是使用HEAD
指针,HEAD指针指向哪个分支,当前就在哪个分支工作。
也可以使用git log -decorate
命令查看各个分支当前所指的对象。
分支切换
切换分支即修改HEAD
指针指向,可以使用chenkout
命令实现。
在每次提交后,HEAD
指针会随着当前分支一起向前移动以保证以后分支能正确切换回来。
或者直接使用命令:
可以在新建分支的同时切换到该分支,-b
可以理解为branch
,相当于:
分支合并
在某个分支上进行操作,使得该分支指针向前移动后,如果要将该分支合并到其他分支,则可以切换到其他分支进行merge
操作:
当两个分支没有需要解决的分歧时,可以直接合并。
删除分支
当分支不再使用时,可以删除:
对于未合并的分支,直接删除会失败,可以使用-D
强制删除。
冲突合并
如果合并的两个分支,并不是直接祖先关系,两个分支在其共同祖先分支上都做了修改,如果修改没有冲突,如修改的都是不同的文件,则Git会自动新建一个提交,将共同祖先分支以及两个要合并的分支共同合并建立一个新的提交。此时Git会自行决定选取哪个提交作为最优的共同祖先。
但是如果两个不同分支都对同一个文件做了修改,在合并时就会引起冲突,因为Git不知道到底该对这个文件做如何操作。此时Git会先暂停下来,等待用户解决冲突。这种情况在平时也经常会遇到,如在本地对某个远程仓库做了修改,但是远程仓库在此之前已经在另一台电脑上做了push
操作,这时使用pull
操作就会自动抓取并合并到当前分支,如果存在冲突,pull
时就会提示哪个文件修改冲突,并等待用户解决。此时,可以使用git status
查看状态。
解决冲突后可以重新使用git add
将其标记为冲突已解决。
远程分支
远程引用是指向远程仓库的指针,包括分支、标签等,可以通过git ls-remote <remotename>
查看远程引用的完整列表,或者通过git remote show <remote>
查看远程分支的更多信息。
远程跟踪则是指向远程分支状态的引用,只有当与远程仓库通信时,它们会自动移动。用户无法手动修改其状态。
可以使用git fetch
命令将远程仓库中的内容拉取到本地,同事远程跟踪会更新到新的远程分支状态。当本地与远程的工作出现分叉之后,合并到本地分支时,依然会考虑是否有冲突的问题,解决方式和其他冲突分支合并一样。
推送本地分支
使用git push
将本地分支推送到远端:
等价于
Git会自动将test
名字展开为refs/heads/test:refs/heads/test
。
跟踪分支
使用checkout
可以实现对分支的跟踪:
通常可以新建一个本地分支来跟踪拉取的远程分支:
也可以使用-u
或--set-upstream-to
选项来直接设置已有的本地分支来跟踪拉取的远程分支:
另外,可以使用git branch -vv
命令查看设置的所有跟踪分支。
合并分支
可以使用git fetch
拉取分支后再使用git merge
合并到本地分支,也可以直接使用git pull
拉取并合并到本地分支。但是有时候git pull
会显得有些佛性,难以理解,最简单的方式是fetch
与merge
的组合。
删除分支
删除远程分支可以使用:
或者直接将空分支推送到远端覆盖远端分支即可:
变基
这个是个有趣的用法,自从有了变基,Github
就变成了Gayhub
(逃 )。
啊呸!当然不是这个原因。
变基是一种整合分支的方法,通常整合分支有两种方法:合并和变基。
合并(merge
)之前已经经常用到了,主要就是将一个分支合并到另一个上。而变基(rebase
)则是将一个分支里提交的修改在另一个分支上重放一边,也就是走别人的路,让别人说去吧。
一个基本的例子如下:
此时,Git会先找到这两个分支的分叉点(即最近共同祖先),然后从分叉点开始,将branch1
所经历的操作,给branch2
也体验一下。然后回到branch2
,进行一次快进合并:
其实就这个例子来看,变基和合并没有任何区别,但这样可以保证在向远程分支推送时保持提交历史的简洁。
另外,变基可以放到其他分支进行,并不一定非得依据分化之前的分支。可以从一个特性分支里再分出一个特性分支,然后跳过前面的特性分支,将后者与主分支进行变基,可以使用--onto
选项。
即取出branch2
分支,找到branch1
和branch2
的分离点,然后在master
分支上重放其共同祖先之后的修改。
然后就可以将变基后的分支快进合并到master
分支上:
剩下的也可以将branch1
合并到master
中:
然后快进合并master
分支:
之后就可以删除无用的分支了。
变基风险
因为人人都可以编辑,所以一旦分支中的对象提交发布到公共仓库,就千万不要对该分支进行变基,不然其他人不得不重新将手里的工作和你的提交进行整合,接下来你也要重新拉取他们的提交进行整合,引入太多不必要的麻烦。
总之用官方一句加粗的话说:
不要对在你的仓库外有副本的分支执行变基。
其他操作
别名
和Linux的alias
命令一样的意思,也是方便在git中快速操作。
设置别名后,通过 git co
即可实现git checkout
命令。
储藏
当不想提交现在的工作状态,又想切换到别的分支进行工作,可以先将当前状态出藏起来。储藏(Stash)可以获取工作目录的中间状态——也就是修改过的被追踪的文件和暂存的变更——并将它保存到一个未完结变更的堆栈中,随时可以重新应用。
使用git stash list
可以查看当前储藏的列表。
如果之后要恢复储藏的状态,可以使用:
Git则会默认恢复最近一次的储藏,如果想应用更早的储藏,则可以通过名字指定,如:
此时对文件的变更被重新应用,但是被暂存的文件没有重新被暂存。可以通过运行git stash apply
命令时带上一个--index
的选项来告诉命令重新应用被暂存的变更。apply
选项只尝试应用储藏的工作,但储藏的栈上仍然有该储藏。可以通过运行git stash drop
,加上希望移除的储藏的名字来移除该储藏,或者直接通过git stash pop
来重新应用储藏并在此之后快速删除栈上的储藏。
取消储藏
如果要取消之前所应用的储藏的修改,可以通过取消该储藏的补丁达到该效果:
如果没有指定储藏名称,则会自动选择最近的储藏:
从储藏中创建分支
在储藏一个工作状态后,继续在该分支上工作,最后还原储藏的时候可能会引起合并冲突,此时可以新建一个储藏分支简化工作。
此时Git会创建一个新的分支,检出储藏工作时的所处的提交,重新应用,如果成功,则丢弃储藏。