git学习笔记

0. 前言

git在团队协作中有重要作用,是有必要进行系统学习的一门工具。本篇是笔者学习git操作的备忘笔记,主要在linux上进行。

1. git历史

很多人都知道,Linus在1991年创建了开源的Linux,从此,Linux系统不断发展,已经成为最大的服务器系统软件了。
Linus虽然创建了Linux,但Linux的壮大是靠全世界热心的志愿者参与的,这么多人在世界各地为Linux编写代码,那Linux的代码是如何管理的呢?
事实是,在2002年以前,世界各地的志愿者把源代码文件通过diff的方式发给Linus,然后由Linus本人通过手工方式合并代码!
你也许会想,为什么Linus不把Linux代码放到版本控制系统里呢?不是有CVS、SVN这些免费的版本控制系统吗?因为Linus坚定地反对CVS和SVN,这些集中式的版本控制系统不但速度慢,而且必须联网才能使用。有一些商用的版本控制系统,虽然比CVS、SVN好用,但那是付费的,和Linux的开源精神不符。
不过,到了2002年,Linux系统已经发展了十年了,代码库之大让Linus很难继续通过手工方式管理了,社区的弟兄们也对这种方式表达了强烈不满,于是Linus选择了一个商业的版本控制系统BitKeeper,BitKeeper的东家BitMover公司出于人道主义精神,授权Linux社区免费使用这个版本控制系统。
安定团结的大好局面在2005年就被打破了,原因是Linux社区牛人聚集,不免沾染了一些梁山好汉的江湖习气。开发Samba的Andrew试图破解BitKeeper的协议(这么干的其实也不只他一个),被BitMover公司发现了(监控工作做得不错!),于是BitMover公司怒了,要收回Linux社区的免费使用权。
Linus可以向BitMover公司道个歉,保证以后严格管教弟兄们,嗯,这是不可能的。实际情况是这样的:
Linus花了两周时间自己用C写了一个分布式版本控制系统,这就是Git!一个月之内,Linux系统的源码已经由Git管理了!牛是怎么定义的呢?大家可以体会一下。
Git迅速成为最流行的分布式版本控制系统,尤其是2008年,GitHub网站上线了,它为开源项目免费提供Git存储,无数开源项目开始迁移至GitHub,包括jQuery,PHP,Ruby等等。
历史就是这么偶然,如果不是当年BitMover公司威胁Linux社区,可能现在我们就没有免费而超级好用的Git了。

2. git三大区

git三大区域
git三大区域-ProGit

  • 工作区(The working tree):即电脑中git仓库所在的目录,进行工作和修改的目录。
  • 暂存区(The staging area):暂时存放你的修改的区域。 通过git add命令将工作区改动的内容(包括修改的文件、新增的文件)添加到暂存区。 暂存区是你下次要提交的内容,”that stores information about what will go into your next commit”。
  • 版本库(The Git directory):是存放git仓库中各历史版本的区域,在仓库所在根目录的.git/目录下。 当你执行commit提交时,git会将暂存区的内容复制到版本库中,并设为最新版本。 当执行clone从远程克隆仓库当本地时,会将那个远程仓库的版本库也克隆下来。 在版本库中,有一个HEAD指针,它指向当前分支(通常是master)的当前版本。

3. git配置

git config user.name "yourname" 配置当前项目用户名字

git config user.email youremail 配置当前项目用户邮箱

git config --global user.name "yourname" 配置全局用户名字

git config --global user.email youremail 配置全局用户邮箱

--global 对当前用户,--system 对所有用户

git config --list 查看已有的配置信息

vim ~/.gitconfig 编辑当前用户的git配置文件,vim /etc/gitconfig 所有用户的git配置文件

vim .gitignore 仓库根目录下的.gitignore文件中配置了git操作时的忽略文件

4. git基础操作

4.1. 创建本地仓库

git init 在当前文件夹创建一个可git管理的本地仓库

4.2. 将工作区保存到暂存区

git add xxx add命令并非添加一个文件。而是,将修改或新增的文件从工作区添加到当前本地仓库的暂存区中,表明此修改或新增的文件在下次commit提交的内容之中了。常用git add来添加新增的文件。

git add * 将所有修改与新增的文件添加到暂存区。当你在仓库根目录下设置了.gitignore文件并配置了忽略文件时,此命令会报错,如下:

1
2
3
4
5
$ git add *
The following paths are ignored by one of your .gitignore files:
ignore.md
Use -f if you really want to add them.
fatal: no files added

如果你强行使用git add -f *命令,则会添加ignore.md文件。但这样.gitignore文件就没有意义了,所以不建议使用git add *git add -f *

4.3. 将暂存区提交到版本库

git commit 将暂存区提交到本地仓库的版本库。若存在没有被git add添加到暂存区的文件,则不会被提交。

-m "xxxx" 后面跟着的为本次提交的说明。git commit时必须使用此选项。

-a 官方解释:”Tell the command to automatically stage files that have been modified and deleted, but new files you have not told Git about are not affected.” 意思大概是:-a工作区中所有进行修改或删除后的文件都提交到本地仓库,但新增的文件并不提交。

常用commit -m"aaa" -a直接将更改从工作区提交到版本库,而不用经过暂存区。

4.4. 撤销(重置)

git reset --hard 等价于 git reset --hard HEAD 即撤销工作区和暂存区中的所有修改,重置为版本库中HEAD指向的当前版本(未被修改的部分)。

而上一个版本就是 **HEAD^**,上上一个版本就是 **HEAD^^**,当然往上100个版本写100个^比较容易数不过来,所以写成 **HEAD~100**。

或者,你也可以 git reset --hard <commit_id> 直接回溯到某个版本,<commit_id> 指版本号(通过 git log 查看),版本号没必要写全,前几位就可以了,Git会自动去找。

git reset <file>git reset HEAD <file> 撤销git add到暂存区的修改。

git checkout -- <file> 撤销工作区的修改。

4.5. 删除

git rm <file> 如果你已经把某个新增文件提交到版本库了,然后你想删除这个文件,那就用该命令将文件删去,再提交。使用该命令后,git的状态是:

1
2
3
4
5
6
7
8
9
10
$ git rm test.md
rm 'test.md'
$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

deleted: test.md

说明 git rm 会直接更新到暂存区。

删除文件其实还可以,直接删除,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ ls
README.md ignore2.md test2.md
$ rm test2.md
$ ls
README.md ignore2.md
$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)

deleted: test2.md

no changes added to commit (use "git add" and/or "git commit -a")

此时没有直接更新到暂存区,可以通过 git add/rm <file> 来更新。同时,可以通过 git checkout -- <file> 来恢复这个文件。

4.6. 查看状态

git status 显示工作区与暂存区的状态,比如哪些文件被修改了、哪些文件没保存到暂存区、哪些文件没提交等等。若存在没被add的新文件(即未被git跟踪的文件),则会提示Untracked

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# new file: .gitignore
# deleted: ignore.md
#
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: test.md
#
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
#
# ignore2.md

git status -s 更简单地显示状态变化,如:显示 M controllers/article.go
显示内容的每一行开头,M表示被修改、A表示被增加、??表示未受控制

1
2
3
4
5
$ git status -s
A .gitignore
D ignore.md
M test.md
?? ignore2.md

4.7. 查看不同

git diff readme.txt 查看readme.txt这个文件中具体什么内容被修改了。其中:

  • git diff 查看工作区和暂存区的区别
  • git diff --cached 查看暂存区和版本库之间的区别
  • git diff HEAD 查看工作区和版本库之间的区别

4.8. 查看提交日志

git log 查看仓库版本库中(本地仓库和远程仓库的版本库是一样的)从最近到最远的版本信息。换而言之,即每次提交的信息。如下是一次的信息:

1
2
3
4
5
6
commit ee26988de11d36133d180663ddf7b24c4a6233e5
Author: douNine <test@dev3.airdb.io>
Date: Fri Dec 21 00:47:21 2018 +0800

modify task/sync_bbs.go

其中commit一项对应的,是git提交的版本号

git log --graph 显示版本以及分支图,可以说很生动形象了

git reflog 查看历史git命令

4.9. 隐藏工作

当工作区的修改未提交,而我们又想将其隐藏起来时,就需要用到git的stash功能。

git stash 隐藏工作区和暂存区的修改,储存到一个地方。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ git status
On branch master
Your branch is ahead of 'origin/master' by 4 commits.
(use "git push" to publish your local commits)

Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)

modified: test2.md

no changes added to commit (use "git add" and/or "git commit -a")
$ git stash
Saved working directory and index state WIP on master: f0f89d4 --no-ff merge
$ git status
On branch master
Your branch is ahead of 'origin/master' by 4 commits.
(use "git push" to publish your local commits)

nothing to commit, working tree clean

git stash list 查看储存起来的工作现场。

1
2
$ git stash list
stash@{0}: WIP on master: f0f89d4 --no-ff merge

恢复工作现场有两种方法:

一是用git stash apply恢复,但是恢复后,stash内容并不删除,你需要用git stash drop来删除;

另一种方式是用git stash pop,恢复的同时把stash内容也删了。

5. git分支

5.1. 分支的作用:

分支在实际中有什么用呢?假设你准备开发一个新功能,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。如果等代码全部写完再一次提交,又存在丢失每天进度的巨大风险。
现在有了分支,就不用怕了。你创建了一个属于你自己的分支,别人看不到,还继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全,又不影响别人工作。

总而言之,就是你可以在另外的git分支上干自己的事,而不影响主分支。做完还可以更新到主分支上。

注:git中主分支只有一条,叫 **master**。一条分支就是一条时间线,每个时间节点代表着每个版本。master是主分支的名字,同时也是指向主分支最新节点的指针,而HEAD一开始指向的就是 master 指针。

5.2. 创建、切换分支

git branch dev 创建dev分支。dev分支会继承当前分支的所有内容。

git checkout dev 切换到dev分支上。

git checkout -b dev 创建dev分支,然后切换到dev分支上。git checkout命令加上-b参数表示创建并切换分支,相当于以下两条命令:

1
2
3
$ git branch dev
$ git checkout dev
Switched to branch 'dev'

5.3. 查看分支

git branch 查看分支,* 后面的是当前所处分支。

1
2
3
$ git branch
* dev
master

git branch -a 查看远程和本地所有的分支信息。

git log --graph --pretty=oneline --abbrev-commit 该命令常用于生动形象地显示简略的分支图。显示如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
$ git log --graph --pretty=oneline --abbrev-commit
* 07bb2d8 (HEAD -> master, origin/master, origin/HEAD, dev) merge
|\
| * 5a0a7da commit in dev
* | 48a4216 commit in master
|/
* 6aa880e a
* 91147e8 解决分支冲突
|\
| * ea5cf82 commit in dev
* | 28809bb commit in master
|/
* 82016a7 commit in dev
* 6bf1a5d git rm
* 09f2106 bb
* 4524372 aabb
* 058e02c aaa
* f5cc038 git add -f * can add all file including the ignore file
* b5f8f30 aa
* 92c65b1 test add
* d0fe8dc test
* 3a870e8 true demo
* 09d694b find why show falsely
* d6b46e1 also test
* a757718 test.md
* 69483da Update README.md
* cfb9ede Update README.md
* 9bdf268 Merge pull request #1 from 99MyCql/readme-edits
|\
| * 3cef569 Update README.md
|/
* 677f493 Initial commit

5.4. 合并分支

在dev分支上添加一些内容,然后提交到该分支上,如下:

1
2
3
4
5
6
7
8
$ git add branch.md
$ git commit -m"commit in dev"
[dev 82016a7] commit in dev
1 file changed, 1 insertion(+)
create mode 100644 branch.md
$ git status
On branch dev
nothing to commit, working tree clean

回到主分支上,意料之中,并没有出现刚刚修改的内容(新增了branch.md文件)。

1
2
3
4
5
6
7
8
9
$ git checkout dev
Switched to branch 'dev'
$ ls
README.md branch.md ignore2.md test2.md
$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
$ ls
README.md ignore2.md test2.md

此时,需要将dev分支上合并到master分支上,就能将修改从dev分支更新到master分支上了。

git merge dev 将dev分支合并到master分支。结果如下:

1
2
3
4
5
6
7
8
$ git merge dev
Updating 6bf1a5d..82016a7
Fast-forward
branch.md | 1 +
1 file changed, 1 insertion(+)
create mode 100644 branch.md
$ ls
README.md branch.md ignore2.md test2.md

5.5. 合并分支(分支管理篇)

使用 git merge dev 将dev分支合并到master分支时,会将dev中的提交都变为master中的提交。即将两个分支融合在一起,dev中的提交将不在显示。这种合并方式称为:Fast-forward

但在实际使用中,我们希望保留dev的提交信息,这样能清晰地了解项目每一步的进行。

$ git merge --no-ff -m "xxx" dev 该命令的作用是不采用Fast-forward模式,保留dev分支中每次提交的信息。合并到master分支时,建立一个新的提交,即将dev中的新内容提交到master,而不是粗暴地融合。因此,该命令相当于一个提交,需要附加提交信息-m "xxx"该命令是项目中的合并常用命令

--no-ffFast forward 区别:

未合并时:

1
2
3
  A---B---C dev
/
D------------ master

git merge dev 合并:

1
D-A---B---C master

git merge --no-ff -m "xxx" dev 合并:

1
2
3
  A---B---C dev
/ \
D-----------E master

5.6. 删除分支

git branch -d dev 删除dev分支

如果分支未合并,则需要强制删除 git branch -D dev

5.7. 解决分支合并冲突

在dev分支上修改branch.md文件并提交。然后,切换到master分支上修改branch.md文件同样位置,但修改内容不同,接着在master上也进行提交。

此时,在master分支上合并dev分支。显然,两个分支上都对同一文件同一位置做了不同修改,这样的合并必然会产生冲突的。合并结果如下:

1
2
3
4
$ git merge dev
Auto-merging branch.md
CONFLICT (content): Merge conflict in branch.md
Automatic merge failed; fix conflicts and then commit the result.

查看branch.md文件,文件中内容如下:

1
2
3
4
5
6
7
<<<<<<< HEAD
in master branch:
change in master
=======
in dev branch:
add this change
>>>>>>> dev

<<<<<<< HEAD======= 之间的是master分支中的内容。

=======>>>>>>> dev 之间是dev分支中的内容。

对于两部分冲突内容,我们需要 手动解决冲突 。选择需要的留下,而将剩下的删除即可。此处,我们选择master中的内容。解决后文件内容如下:

1
2
in master branch:
change in master

此时再查看git状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ git status
On branch master
Your branch is ahead of 'origin/master' by 2 commits.
(use "git push" to publish your local commits)

You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)

Unmerged paths:
(use "git add <file>..." to mark resolution)

both modified: branch.md

no changes added to commit (use "git add" and/or "git commit -a")

按照git的建议,再 git commit -a 提交即可。

1)虽然在master分支中合并了冲突,但是,在dev分支中的内容并没有被修改。不过,合并之后,dev分支已经不重要了。

2)在冲突未解决时,很多git操作都不能使用,比如:撤销。

5.8. 分支管理策略

在实际开发中,我们应该按照几个基本原则进行分支管理:

首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;

那在哪干活呢?dev分支。也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;

你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。

软件开发中,bug就像家常便饭一样。有了bug就需要修复,在Git中,由于分支是如此的强大,所以,每个bug都可以通过一个新的临时分支来修复,修复后,合并分支,然后将临时分支删除。

而合并时,注意要采用--no-ff模式。

6. git远程仓库

6.1. 克隆到本地

git clone https://github.com/xxx/xxx 将github上xxx远程仓库的代码克隆到本地,形成本地仓库。

假如,远程有两个分支master和dev,本地只有一个master分支,克隆或者拉取后并不会在本地创建dev分支。

我们需要用git checkout -b dev origin/dev命令,将远程dev分支创建到本地。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ git clone https://github.com/99MyCql/TestOfGit.git
Cloning into 'TestOfGit'...
remote: Enumerating objects: 53, done.
remote: Counting objects: 100% (53/53), done.
remote: Compressing objects: 100% (28/28), done.
remote: Total 84 (delta 20), reused 50 (delta 17), pack-reused 31
Unpacking objects: 100% (84/84), done.
$ cd TestOfGit/
$ ls
branch.md ignore2.md README.md test2.md
$ git branch -a
* master
remotes/origin/HEAD -> origin/master
remotes/origin/dev
remotes/origin/master
$ git checkout -b dev origin/dev
Branch dev set up to track remote branch dev from origin.
Switched to a new branch 'dev'

6.2. 查看远程仓库信息

git remote 查看远程仓库信息。

git remote -v 查看详细信息。

1
2
3
4
5
$ git remote
origin
$ git remote -v
origin https://github.com/99MyCql/TestOfGit.git (fetch)
origin https://github.com/99MyCql/TestOfGit.git (push)

-v 显示了抓取和推送的地址,如果不可推送则没有第二个地址。

git branch -a 查看远程和本地所有的分支信息。

1
2
3
4
5
6
$ git branch -a
* dev
master
remotes/origin/HEAD -> origin/master
remotes/origin/dev
remotes/origin/master

6.3. 添加远程仓库

git remote add <name> <url>,添加一个远程仓库,shortname为自定义的远程主机名,url为远程仓库的url

6.4. 删除远程仓库

git remote remove <name>

6.5. 本地与远程分支追踪关系

在某些场合,Git会自动在本地分支与远程分支之间,建立一种追踪关系(tracking)。比如,在git clone的时候,所有本地分支默认与远程主机的同名分支,建立追踪关系,也就是说,本地的master分支自动”追踪”origin/master分支。Git也允许手动建立追踪关系。

$ git branch --set-upstream-to origin/dev dev 该命令指定dev分支追踪origin/dev分支,即将本地dev分支与远程dev分支建立追踪关系。

当没有建立追踪关系时,如果在dev分支进行远程操作又没有指定远程分支时,则会出错:

1
2
3
4
5
6
7
8
9
$ git branch
* dev
master
$ git push
fatal: The current branch dev has no upstream branch.
To push the current branch and set the remote as upstream, use

git push --set-upstream origin dev

因此,得先建立本地分支与远程分支的追踪关系:

1
2
3
4
5
$ git branch --set-upstream-to origin/dev dev
Branch 'dev' set up to track remote branch 'dev' from 'origin'.
$ git push
Username for 'https://github.com': 99MyCql
Password for 'https://99MyCql@github.com':

6.6. 拉取

$ git pull <远程主机名> <远程分支名>:<本地分支名> 取回远程主机某个分支的更新,再与本地的指定分支合并。

如:
$ git pull origin next:master 取回origin主机的next分支,与本地的master分支合并

$ git pull origin next 如果远程分支是与当前分支合并,则冒号后面的部分可以省略。

$ git pull origin 本地的当前分支自动与对应的origin主机”追踪分支”进行合并。

$ git pull 该命令表示,当前分支自动与唯一追踪分支进行合并。

如果远程主机删除了某个分支,默认情况下,git pull 不会在拉取远程分支的时候,删除对应的本地分支。这是为了防止,由于其他人操作了远程主机,导致git pull不知不觉删除了本地分支。

但是,你可以改变这个行为,加上参数 -p 就会在本地删除远程已经删除的分支。$ git pull -p

6.7. 推送

$ git push <远程主机名> <本地分支名>:<远程分支名> 注意,分支推送顺序的写法是<来源地>:<目的地>,所以git pull是<远程分支>:<本地分支>,而git push是<本地分支>:<远程分支>。

$ git push origin master 该命令表示,将本地的master分支推送到origin主机的master分支。如果后者不存在,则会被新建。

$ git push origin :master 如果省略本地分支名,则表示删除指定的远程分支,因为这等同于推送一个空的本地分支到远程分支。
等同于:
$ git push origin --delete master 删除origin主机的master分支。

$ git push origin 将当前分支推送到origin主机的对应存在追踪关系的分支。

如果当前分支只有一个追踪分支,那么主机名都可以省略
$ git push

6.6. 解决冲突

当多人协作时,经常会出现冲突。
比如,A和B同时拉取了远程最新版到本地。A修改了项目的origin.md文件,同时提交并推送到了远程。B也修改了该文件,但当B要推送时,便出现冲突报错了:

1
2
3
4
5
6
7
8
9
10
11
$ git push
Username for 'https://github.com': 99MyCql
Password for 'https://99MyCql@github.com':
To https://github.com/99MyCql/TestOfGit.git
! [rejected] dev -> dev (fetch first)
error: failed to push some refs to 'https://github.com/99MyCql/TestOfGit.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

显然,远程版本库(A推送后更新了远程版本库)和本地版本库不相同,必然会出现冲突。
此时,我们需按照提示,先拉取最新的内容。

1
2
3
4
5
6
7
8
9
10
11
$ git pull
remote: Enumerating objects: 4, done.
remote: Counting objects: 100% (4/4), done.
remote: Compressing objects: 100% (1/1), done.
remote: Total 3 (delta 1), reused 3 (delta 1), pack-reused 0
Unpacking objects: 100% (3/3), done.
From https://github.com/99MyCql/TestOfGit
f7a736f..69d4b68 dev -> origin/dev
Auto-merging origin.md
CONFLICT (add/add): Merge conflict in origin.md
Automatic merge failed; fix conflicts and then commit the result.

然后,跟解决合并分支冲突一样,进行手动解决。再将解决后内容提交并推送即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
$ vim origin.md
$ git status
On branch dev
Your branch and 'origin/dev' have diverged,
and have 1 and 1 different commits each, respectively.
(use "git pull" to merge the remote branch into yours)

You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)

Unmerged paths:
(use "git add <file>..." to mark resolution)

both added: origin.md

no changes added to commit (use "git add" and/or "git commit -a")
$ git commit -a -m"solve the conflict"
[dev 6cc66a2] solve the conflict
$ git push
Username for 'https://github.com': 99MyCql
Password for 'https://99MyCql@github.com':
Counting objects: 6, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (6/6), 567 bytes | 51.00 KiB/s, done.
Total 6 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), completed with 1 local object.
To https://github.com/99MyCql/TestOfGit.git
69d4b68..6cc66a2 dev -> dev

7. 参考教程:

git官网
廖雪峰git教程
阮一峰的网络日志