DanielLaah

廖雪峰Git教程-学习笔记


教程地址:廖雪峰的官方网站
Git Cheat Sheet: Github


创建版本仓库

什么是版本库呢?版本库又名仓库,英文名repository,你可以简单理解成一个目录,这个目录里面的所有文件都可以被Git管理起来,每个文件的修改、删除,Git都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以“还原”。
如何创建一个版本仓库呢?很简单,到目标文件夹下使用命令git init即可。

此时,git会在当前文件夹下面新建一个.git的目录,这个目录就是来跟踪管理整个版本库的,所以轻易不要乱动里面的文件。

把文件添加到版本库

首先,我们创建一个文本文件readme.txt,内容如下:

1
2
Git is a version control system.
Git is free software.


把一个文件提交到仓库需要两步,第一步使用命令git add将文件添加到暂存区

第二步,使用命令git commit将文件提交到仓库。

-m选项后面添加的是对此次提交的一个简单说明,提交成功后,git会告诉你此次提交添加了一个文件并插入了两行内容。
补充:git add可以同时添加多个文件:

1
git add file1.txt, file2.txt

查看仓库当前状态

上次我们已经成功添加并提交了readme.txt文件,现在我们将该文件的内容改为如下:

1
2
Git is a distributed version control system.
Git is free software.

此时,我们使用git status来查看仓库当前状态:

git立刻给我们反馈:当前readme.txt文件已经被修改,但还并没有添加到暂存区。
现在如果我们想要知道该文件具体哪个位置被修改了该怎么办?这个时候我们就需要用到另一个命令。

查看文件被修改的位置

这个命令就是git diff

下面是git的输出结果:

结果一目了然,在文件的第一行添加了一个单词’distributed’。
在确认无误之后,我们就可以放心大胆的进行提交操作了。
首先添加到暂存区,没有任何输出。

这个时候,我们来查看一下仓库状态:

git提示我们将要修改的文件为readme.txt。这个时候在进行commit操作:

再来查看仓库状态,git告诉我们当前没有需要提交的修改。

版本历史记录

首先我们将readme.txt的内容修改为如下:

1
2
Git is a distributed version control system.
Git is free software distributed under the GPL.

然后进行提交:

这样我们不断修改,不断提交,最终我们会获得这个文件的多个版本,现在我们已经有三个版本了。那么如何查看各个版本历史记录呢?这里需要用到git log命令。使用该命令后,输出结果为:

可以加上参数使得输出更简洁:git log --pretty=oneline

图中一大串数字为每个版本的版本号。

版本回退

在git中,HEAD表示当前版本,上一个版本就是HEAD^,上上一个版本就是HEAD^^,再往上就可以用数字表示,如HEAD~100。现在,我们将readme.txt退回到上一个版本。使用git reset命令。(关于–hard参数后面会学习到)

这个时候我来查看一下readme.txt文件的内容:

果然,已经回退到了上一个版本。这个时候,我们再查一下版本历史记录:

我的天呐😲,append GPL那个版本已经找不见了…如果现在想要回去怎么办?没关系,我们可以使用git reset命令配合版本号来实现(版本号只需要写前几位即可):

查看一下readme的内容:

还好,又回到了这个版本,虚惊一场。不过这个时候,又有一个细思极恐的问题,这里的版本号是因为我们在回退版本之前使用了git log命令查看了,如果在回退之前没有查看版本号那该怎么办?😰
这时候可以使用git reflog命令来查看我们的每一次commit和reset命令:

如图,可以看到append GPL版本号(前几位数)为:5119ea4

工作区和暂存区

工作区

就是你在电脑里能看到的目录,比如learngit文件夹就是一个工作区:

版本库

工作区有一个隐藏的目录.git,这个就是版本库。版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支master,以及指向master的一个指针叫HEAD。
前面讲了我们把文件往Git版本库里添加的时候,是分两步执行的:
第一步是用git add把文件添加进去,实际上就是把文件修改添加到暂存区;
第二步是用git commit提交更改,实际上就是把暂存区的所有内容提交到当前分支。
因为我们创建Git版本库时,Git自动为我们创建了唯一一个master分支,所以,现在,git commit就是往master分支上提交更改。
现在,我们在readme.txt作如下修改:

1
2
3
Git is a distributed version control system.
Git is free software under the GPL.
Git has a mutable index called stage.

然后,在增加一个文件LICENSE

此时查看一下仓库状态:

我们发现readme.txt被修改了,而LICENSE还从来没有被添加过,所以它的状态是Untracked。
这个时候我们再将这两个文件添加到暂存区:

再查看仓库状态:

现在,暂存区的状态就变成这样了:

所以,git add命令实际上就是把要提交的所有修改放到暂存区(Stage),然后,执行git commit就可以一次性把暂存区的所有修改提交到分支。

此时,工作区的内容没有进行任何修改,所以是“干净的”:

管理修改

为什么Git比其他版本控制系统设计得优秀,因为Git跟踪并管理的是修改,而非文件。
我们做如下实验,第一步对readme.txt进行如下修改:

1
2
3
4
Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
Git tracks changes.

然后添加,

再修改readme.txt:

1
2
3
4
Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
Git tracks changes of files.

提交:

这是操作过程:
第一次修改 -> git add -> 第二次修改 -> git commit
Git管理的是修改,当我们用git add命令后,在工作区的第一次修改被放入暂存区,准备提交,但是,在工作区的第二次修改并没有放入暂存区,所以,git commit只负责把暂存区的修改提交了,也就是第一次的修改被提交了,第二次的修改不会被提交。
使用git diff HEAD -- readme.txt命令来查看当前仓库最新版本和工作区中的版本有什么区别:

第二次修改的确没有提交。只需要再一次的add和commit操作即可提交第二次修改。

撤销修改

git add之前撤销修改

假设我们在文件中新添了一句话:

然后我们及时地发现了错误,想要撤销这一修改怎么办?
我们先查看一下仓库状态:

git告诉我们,我们可以使用git checkout命令来丢弃在工作区中的修改,此时我们还没有将修改添加到暂存区,所有这个时候使用这个命令就是回到版本库中最新的状态。使用git checkout -- readme.txt命令之后,文件内容如下:

果然文件复原了,再使用git status命令发现工作区是’干净的’。

git add之后撤销修改

假设刚才我们不仅添加了一句错误的话,并且还添加到了暂存区,那又该如何呢?
首先,我们还是来看一下添加到暂存区之后版本库的状态:

git提示我们可以使用git reset HEAD命令来撤销此次添加到暂存区的操作。

撤销之后,我们再来看一下版本库的状态:

此时,暂存区的内容已经被撤销了。这个时候该如何办?你肯定知道答案啦,当然是使用git checkout -- readme.txt命令来丢弃在工作区中的修改:

整个世界终于清静了!

删除文件

下面来学习一下git中的删除操作。
首先新建一个文件并且提交。

我们现在直接在工作区将该文件删除,然后查看版本库的状态:

1.如果确定要将该文件删除,则使用git rm命令来删除此文件,并提交:

查看版本库的状态:

2.如果是删错了,想要恢复此文件,直接使用命令git checkout -- test.txt即可。

添加远程库

首先在github上新建一个learngit的仓库,然后在本地的learngit仓库下使用命令git remote add origin git@github.com:你的github账户名/learngit.git

然后,将本地库的所有内容推送到远程库上:

由于远程库是空的,我们第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。
推送成功后,可以立刻在GitHub页面中看到远程库的内容已经和本地一模一样:

从现在起,只要本地作了提交,就可以通过命令git push origin master把本地master分支的最新修改推送至GitHub。

从远程仓库克隆

现在,假设我们从零开发,那么最好的方式是先创建远程库,然后,从远程库克隆。
首先,在github上新建一个仓库gitskills(✅Initialize this repository with a README):

然后用git clone命令拷贝到本地:

此时,本地已经有一个和远程一样的库了。
打开查看:

创建与合并分支

在之前学的内容里,我们只涉及到一个分支及master分支。HEAD指向master分支,master指向提交。

当我们创建新的分支,例如dev时,Git新建了一个指针叫dev,指向master相同的提交,再把HEAD指向dev,就表示当前分支在dev上。使用命令git checkout -b dev来创建并切换分支:


使用git branch命令来查看分支:

现在我们已经在dev分支上,此时对工作区修改和提交就是针对dev分支的了,对master分支不会有影响,我们对dev分支下的README.md文件添加一行,并提交。

1
Creating a new branch is quick.



然后,我们来查看一下master分支上的README.md文件有没有变化:

果然,对dev分支的修改并不会影响到master分支。
现在我们在master分支上,假如我们在dev上的工作完成了,就可以把dev合并到master上。Git怎么合并呢?最简单的方法,就是直接把master指向dev的当前提交,就完成了合并。使用命令git merge dev

注意到上面的Fast-forward信息,Git告诉我们,这次合并是“快进模式”,也就是直接把master指向dev的当前提交,所以合并速度非常快。
当然,也不是每次合并都能Fast-forward,后面会讲其他方式的合并。

然后我们再查看master分支的README.md文件:

可以看到,此时master分之下的README.md文件和dev分支最新提交是完全一样的了。
如果不在需要dev分支,可以将其删掉,删掉之后又只剩下一个分支了。
使用命令git branch -d dev删除dev分支。

解决冲突

现在我们回到learngit仓库,新建分支feature1:

在readme.txt添加一行:

1
Creating a new branch is quick AND simple.

提交:

切换到master分支:

在master分支上把readme.txt的添加一行:

1
Creating a new branch is quick & simple.

提交:

现在,master分支和feature1分支各自都分别有新的提交,变成了这样:

这个时候我们在进行合并:

git此时提示我们合并存在冲突,必须先解决冲突。git status也可以告诉我们冲突的文件:

我们现在查看readme.txt的内容:

Git用<<<<<<<,=======,>>>>>>>标记出不同分支的内容,我们修改如下后保存:

1
Creating a new branch is quick and simple.

再提交:

现在,master分支和feature1分支变成了下图所示:

用带参数的·git log --graph --pretty=oneline --abbrev-commit也可以看到分支的合并情况:

最后,删除feature1分支。

分支管理策略

通常,合并分支时,如果可能,Git会用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息。
如果要强制禁用Fast forward模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。
首先创建并切换dev分支:

修改readme.txt文件,并提交一个新的commit;切换回master分支。
使用--no-ff参数合并分支,表示禁止fast forward模式。

因为本次合并要创建一个新的commit,所以加上-m参数,把commit描述写进去。
git log --graph --pretty=oneline --abbrev-commit

在实际开发中,我们应该按照几个基本原则进行分支管理:
首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;
那在哪干活呢?干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;
你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。
所以,团队合作的分支看起来就像这样:

Bug 分支

在Git中,每个bug都可以通过一个新的临时分支来修复,修复后,合并分支,然后将临时分支删除。
当你接到一个修复一个代号101的bug的任务时,很自然地,你想创建一个分支issue-101来修复它,但是,等等,当前正在dev上进行的工作还没有提交:

并不是你不想提交,而是工作只进行到一半,还没法提交,预计完成还需1天时间。但是,必须在两个小时内修复该bug,怎么办?
幸好,Git还提供了一个stash功能,可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作,使用git stash命令:

现在,用git status查看工作区,就是干净的(除非有没有被Git管理的文件),因此可以放心地创建分支来修复bug。

首先确定要在哪个分支上修复bug,假定需要在master分支上修复,就从master创建临时分支:

现在修复bug,需要把“Git is free software …”改为“Git is a free software …”,然后提交:

修复完成后,切换到master分支,并完成合并,最后删除issue-101分支:

现在我们可以回到dev恢复之前保存的现场了。使用命令git stash list查看:

有两种方法恢复,一是用git stash apply恢复,但是恢复后,stash内容并不删除,你需要用git stash drop来删除;另一种方式是用git stash pop,恢复的同时把stash内容也删了:

在使用git stash list就查看不到任何内容了。
如果有多次保存的内容, 可以使用命令git stash apply stash@{0}恢复指定的stash。

多人协作

当你从远程仓库克隆时,实际上Git自动把本地的master分支和远程的master分支对应起来了,并且,远程仓库的默认名称是origin。
要查看远程库的信息,用git remote

git remote -v命令显示详细信息:

上面显示了可以抓取和推送的origin的地址。如果没有推送权限,就看不到push的地址。

推送分支

推送分支,就是把该分支上的所有本地提交推送到远程库。推送时,要指定本地分支,这样,Git就会把该分支推送到远程库对应的远程分支上:

如果要推送其他分支,比如dev,就改成:

抓取分支

多人协作时,大家都会往master和dev分支上推送各自的修改。
现在,模拟一个你的小伙伴,可以在另一台电脑(注意要把SSH Key添加到GitHub)或者同一台电脑的另一个目录下克隆

当你的小伙伴从远程库clone时,默认情况下,你的小伙伴只能看到本地的master分支。不信可以用git branch命令看看:

现在,你的小伙伴要在dev分支上开发,就必须创建远程origin的dev分支到本地,于是他用这个命令创建本地dev分支:

现在,他就可以在dev上继续修改,然后,时不时地把dev分支push到远程:

你的小伙伴已经向origin/dev分支推送了他的提交,而碰巧你也对同样的文件作了修改,并试图推送:

推送失败,因为你的小伙伴的最新提交和你试图推送的提交有冲突,解决办法也很简单,Git已经提示我们,先用git pull把最新的提交从origin/dev抓下来,然后,在本地合并,解决冲突,再推送:

git pull也失败了,原因是没有指定本地dev分支与远程origin/dev分支的链接,根据提示,设置dev和origin/dev的链接:

再pull:

这回git pull成功,但是合并有冲突,需要手动解决,解决的方法和分支管理中的解决冲突完全一样。解决后,提交,再push:

因此,多人协作的工作模式通常是这样:
首先,可以试图用git push origin branch-name推送自己的修改;
如果推送失败,则因为远程分支比你的本地更新,需要先用git pull试图合并;
如果合并有冲突,则解决冲突,并在本地提交;
没有冲突或者解决掉冲突后,再用git push origin branch-name推送就能成功!
如果git pull提示“no tracking information”,则说明本地分支和远程分支的链接关系没有创建,用命令git branch –set-upstream branch-name origin/branch-name。
这就是多人协作的工作模式,一旦熟悉了,就非常简单。

创建标签

首先,切换到需要打标签的分支上:

然后,敲命令git tag <name>就可以打一个新标签,使用git tag命令可查看当前所有标签。

默认标签是打在最新提交的commit上的。有时候,如果忘了打标签该怎么办?很简单,找到历史提交的commit id,然后打上就可以了:

比如说要给”fix bug 101”打上标签,可以这样:

再查看一下当前标签:

使用命令git show <tagname>查看标签信息:

还可以创建带有说明的标签,用-a指定标签名,-m指定说明文字:

操作标签

删除标签:git tag -d <tagname>

因为创建的标签都只存储在本地,不会自动推送到远程。所以,打错的标签可以在本地安全删除。
如果要推送某个标签到远程,使用命令git push origin

或者,一次性推送全部尚未推送到远程的本地标签:

在Github上查看标签:

如果标签已经推送到远程,要删除远程标签就麻烦一点,先从本地删除:

然后,从远程删除。删除命令也是push,但是格式如下:

git push origin :refs/tags/v0.9

在Github上查看是否删除: