作者:下里巴人

来自南京审计大学

远程仓库Github

Git是分布式版本控制系统,同一个Git仓库,可以分布到不同的机器上。怎么分布呢?最早,肯定只有一台机器有一个原始版库,此后,别的机器可以“克隆”这个原始版本库,而且每台机器的版本库其实都是一样的,并没有主次之分。
你肯定会想,至少需要两台机器才能玩远程库不是?但是我只有一台电脑,怎么玩?
其实一台电脑上也是可以克隆多个版本库的,只要不在同一个目录下。不过,现实生活中是不会有人这么傻的在一台电脑上搞几个远程库玩,因为一台电脑上搞几个远程库完全没有意义,而且硬盘挂了会导致所有库都挂掉,所以我也不告诉你在一台电脑上怎么克隆多个仓库。
实际情况往往是这样,找一台电脑充当服务器的角色,每天24小时开机,其他每个人都从这个“服务器”仓库克隆一份到自己的电脑上,并且各自把各自的提交推送到服务器仓库里,也从服务器仓库中拉取别人的提交。
(引自廖雪峰)

这一部分的操作需要我们拥有一个Github账号。从名字就可以看出,这个网站就是提供Git仓库托管服务的,所以,只要注册一个GitHub账号,就可以免费获得Git远程仓库。具体可以参考我们之前的文章(点这里直达哦),在这里已经较为详细的叙述了如何将自己的ssh密钥部署在Github上,请先完成这些步骤才能继续操作喔(*^▽^*)

添加远程库

在完成上述设置后,我们需要在Github中新建一个库(repo),库的名称随意,我就先建立一个名为learngit的远程库。

在我们建立好仓库后,GitHub告诉我们,可以从这个仓库克隆出新的仓库,也可以把一个已有的本地仓库与之关联,然后,把本地仓库的内容推送到GitHub仓库。现在,我们根据GitHub的提示,在本地的learngit仓库下运行命令:

1
$ git remote add origin https://github.com/Yourname/Yourreponame.git

添加后,远程库的名字就是origin,这是Git默认的叫法,也可以改成别的,但是origin这个名字一看就知道是远程库。

注意,这里的“Yourename”和“Yourreponame”分别是你Github账号的用户名和你新建仓库的名字。

下一步,就可以把本地库的所有内容推送到远程仓库上:

1
2
3
4
5
6
7
8
9
10
11
12
$ git push -u origin master

Enumerating objects: 25, done.
Counting objects: 100% (25/25), done.
Delta compression using up to 16 threads
Compressing objects: 100% (21/21), done.
Writing objects: 100% (25/25), 2.02 KiB | 1.01 MiB/s, done.
Total 25 (delta 9), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (9/9), done.
To https://github.com/Motherlesssss/learngit.git
* [new branch] master -> master
branch 'master' set up to track 'origin/master'.

把本地库的内容推送到远程,用git push命令,实际上是把当前分支master推送到远程。

由于远程库是空的,我们第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。

这时候你登录查看该远程库,就会发现已经和自己本地的版本库一模一样啦!!从现在开始,我们只要本地提交了,就可以通过一下命令将本地的master分支的最新修改推送至远程库:

1
$ git push origin master

删除远程库

有了添加远程库,那我们肯定也会有删除远程库的需要,一共分两步,演示如下

解除本地与远程仓库的绑定关系

首先,我们先使用如下指令查看远程库信息:

1
2
3
4
$ git remote -v

origin https://github.com/Motherlesssss/learngit.git (fetch)
origin https://github.com/Motherlesssss/learngit.git (push)

然后根据名字删除:

1
$ git remote rm origin

删除远程库

要真正删除远程库,需要登录到GitHub,在后台页面找到删除按钮再删除。

从远程库克隆

我们之前是在现有本地库的情况下建立远程库,假设我们从零开始,那么最好的方式是先创建远程库,然后从远程克隆,我们先建立一个新的远程仓库gitskills,并且勾选Initialize this repository with a README,这样GitHub会自动为我们创建一个README.md文件。

这时,一个远程库我们已经建立好了,输入以下指令来克隆一个本地库:

1
$ git clone git@github.com:Yourname/Yourreponame.git
1
2
3
4
5
6
7
$ git clone git@github.com:Motherlesssss/gitskills.git

Cloning into 'gitskills'...
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (3/3), done.

注意,这里的“Yourename”和“Yourreponame”分别是你Github账号的用户名和你已经建立好的仓库的名字。

这时你就会发现本地的gitskills目录中已经出现README.md啦!!

分支管理

关于分支管理的作用以及解释,在廖雪峰老师的网站已经有详细解释了,可以自行前往观看,这里便不再赘述,直接进入正题(*^▽^*)

创建并切换分支

创建并切换至dev分支:

1
2
3
$ git switch -c dev

Switched to a new branch 'dev'

git switch命令加上-c参数表示创建并切换,相当于以下两条命令:

1
2
3
4
$ git branch dev
$ git switch dev

Switched to branch 'dev'

查看当前分支

1
2
3
4
$ git branch

* dev
master

这个命令会列出所有分支,当前分支前会加一个 * 号

合并分支(快合并)

当我们在dev分支上做出修改后,我们此时想合并两个分支,可以如下操作:

注意,在进行合并操作前,该分支的修改必须已经提交添加至版本库!

切换至master分支:

1
2
3
$ git switch master

Switched to branch 'master'

我们会发现在这个分支下我们刚刚修改的文件并没有改变,是因为刚刚的修改在dev分支上,而master分支的提交点并没有变,所以接下来合并:

1
2
3
4
5
6
$ git merge dev

Updating d46f35e..b17d20e
Fast-forward
readme.txt | 1 +
1 file changed, 1 insertion(+)

git merge命令用于合并指定分支到当前分支

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

删除分支

当你完成合并后,你就可以删除这个分支了,指令如下:

1
2
3
$ git branch -d dev

Deleted branch dev (was b17d20e).

冲突情况下的合并

在前面我们已经演示过的合并中,dev分支提交的修改内容与master分支完全不同,修改内容不起冲突。因此Git会默认快合并,就像下面这张图所描述的一样:

而如果我们此时新建并切换到一个feature1的分支,在这个分支下我们修改readme.txt添加最后一行并提交:

1
its a test

紧接着,我们切换回master分支,并且将最后一行添加如下,并且提交:

1
it's a test

此时,master分支与feature1分支都分别各自有了新的提交,变成了如下图:


这时候我们若执行合并,会出现以下情况:

1
2
3
4
5
$ git merge feature1

Auto-merging readme.txt
CONFLICT (content): Merge conflict in readme.txt
Automatic merge failed; fix conflicts and then commit the result.

很显然,此时出现了冲突,必须手动解决,当然,使用git status指令查看状态,也会告诉我们哪里起冲突了:

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 1 commit.
(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: readme.txt

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

当然,此时我们直接直接查看readme.txt内容:

Git用<<<<<<<,=======,>>>>>>>标记出不同分支的内容,我们直接在该文件中修改如下后保存并且提交:

1
its a test
1
2
3
4
$ git add readme.txt
$ git commit -m"test"

[master 9772dac] test

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

也可使用该指令查看分支合并情况:

1
git log --graph --pretty=oneline --abbrev-commit

或者该指令查看分支合并图:

1
git log --graph

最后别忘了删除feature1分支哦!!

当然,这里的情况是修改量较小的情况下,如果修改量太大,想要放弃合并,可以使用以下指令来让你的代码恢复到合并之前的状态:

1
git merge --abort

禁用快合并,保留分支历史

大部分情况下,我们的分支不会起冲突,因此绝大多数的合并都是快合并,但是在这种模式下,删除分支后,会丢掉分支信息。因此,我们可以选择强制禁用快分支,这样,我们就能保留分支信息了:

1
$ git merge --no-ff -m "commit描述" 分支名

这时就会如下图,分支信息就得以保留:

分支策略

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

(引自廖雪峰)

Bug分支

我们设想如下场景:我们正在dev分支上进行工作,此时我们突然发现我们此前的版本库中有一个文件存在问题,我们想要创建分支去修改它,但是我们手头的dev分支工作还没有提交,我们该怎么办?不用慌,我们可以输入以下指令来“储藏”当前的工作现场:

1
2
3
$ git stash

Saved working directory and index state WIP on dev: f52c633 add merge

接着,我们确定从哪个分支来修复,假设在master分支上创建临时分支:

1
2
3
4
5
6
7
$ git switch master

Switched to branch 'master'

$ git switch -c bug

Switched to a new branch 'bug'

此时可以用下图描述:

在此分支上完成修改后,切换至master分支,并完成合并,最后记得删除bug分支:

1
2
3
4
$ git add readme.txt 
$ git commit -m "fix bug 101"
[bug 4c805e2] fix bug 101
1 file changed, 1 insertion(+), 1 deletion(-)

请注意这里的4c805e2 !!后续非常重要。

这是我们切换回dev分支,查看工作区却发现是干净的:

1
2
3
4
5
6
7
8
$ git switch dev

Switched to branch 'dev'

$ git status
On branch dev

nothing to commit, working tree clean

用如下命令查看我们刚刚存好的工作区:

1
2
3
$ git stash list

stash@{0}: WIP on dev: f52c633 add merge

接着,用如下命令恢复并删除stash内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ git stash pop

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

new file: hello.py

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: readme.txt

Dropped refs/stash@{0} (5d677e2ee266f39ea296182fb2354265b91b3b2a)

当然可以stash多次,恢复时先用git stash list查看,然后恢复指定stash

1
$ git stash apply stash@{0}

由于我们的dev分支是早期master分支分出来的,所以我们需要将这个改动重放一遍:

1
2
3
4
5
6
7
8
9
$ git branch

* dev
master

$ git cherry-pick 4c805e2

[master 1d4b803] fix bug 101
1 file changed, 1 insertion(+), 1 deletion(-)

这里的4c805e2 ,便是上文标红的编号啦!!