Git详细教程!

第1章 Git基础
1.1    问题
你的论文,从开题开始,直达答辩通过期间,会被导师不断打回修改,你就会不断地拷贝复制之前的论文,再进行修改,得到新的论文版本,如此往复就会产生各种版本的论文。
 
这就是手动进行论文的版本管理,这种版本管理的方式非常普遍,因为它简单。但是这种方式也是很容易出错的,你也根本记不住哪个版本对应什么样的修改

1.2    版本控制
版本控制是一个软件系统,这个软件专门用来记录对一个或多个文件在什么时间做了什么样的修改,如此我们就可以通过版本控制软件来召出任何一个修改的版本。

版本控制可以分为3类:
1.    本地版本控制
2.    击中版本控制
3.    分布式版本控制

1.2.1    本地版本控制
本地版本控制软件的出现,会让你想要编辑的文件,只会出现一份,而该文件的多个历史版本会有本地版本控制软件帮你保留起来。如此,对于用户而言,就不会看到多个版本的文件了。
 
 
本地版本控制的缺点是:不适合于多人协作编辑一个文件

1.2.2    集中式版本控制
为了满足多人协作编辑一个文件的需求,又出现了集中式版本控制软件。集中式版本控制的特点是,所有文件的所有版本都在一个单独的中央服务器上,客户端会从该服务器上“检出
(check out)”文件。这种方式曾今应用了很久
 
 
集中式版本控制的致命缺点是:如果中央服务器宕机、掉线的话,则所有工作就不能再继续了。

1.3.4 分布式版本控制
在分布式版本控制中,客户端不是仅仅从服务器“检出”文件的最新版本,而是把服务器中的所有文件、所有历史版本,都做了镜像备份。如此一来,就算中央服务数据丢失了,任何一个客户端中都有完整的文件和历史记录,可以随时恢复文件到服务器中,如下图所示:
 
 
分布式版本控制与集中式版本控制的区别是:
1.    集中式版本控制的每个客户端同一时刻只存有一个版本,文件的所有历史版本只存放于中央服务器中,如果中央服务器挂了,则所有工作都不能再继续了
2.    分布式版本控制中,每个客户端都存放了所有文件所有历史版本。如果服务器数据丢失了,可以从任何一个客户端中恢复数据到服务器
3.    在不能联网的情况下,集中式版本控制是无法继续工作的,而分布式版本管理不会受到断网的影响
 
1.4    Git的诞生小历史
1.    Linus创建了开源的linux
2.    世界各地的志愿者把源代码发给Linus,然后由Linus本人手工整合
3.    linux壮大了,于是Linus选择了版本控制系统BitKeeper
4.    BitKeeper授权Linux社区免费使用
5.    Linux社区有人试图破解BitKeeper,BitKeeper怒了,收回使用权
6.    Linus花了两周时间自己用C写了一个分布式版本控制系统,这就是Git!
7.    一个月之内,Linux系统的源码已经由Git管理了!

1.5    Git是什么
Git是一个免费开源的分布式版本控制系统。

1.6    Git的特点
Git与其他版本控制软件的主要区别,是存储各个版本的方式。其他的版本控制软件存放的都是文件和对文件的修改,如下图所示
 

Git并没有这样做。Git中存放的每一个版本,都是一个所有文件的快照。使用Git时,每当你进行版本提交的时,Git都会把提交那一刻的所有文件的一个快照存放到版本库中。为了提高效率,如果一个文件与上一个版本相比没有任何变化的话,Git不会把该文件再存储一个份,而是存放一个指向上一个版本的指针。这就是所谓的快照流(stream of snapshots),如下图所示,虚线框表示存放的是指针:
 
 

正所谓不破不立,Git从各个方面重新定义了版本控制系统,而这一点恰恰是其他版本控制软件没有做到的,它们总是从上一代复制思路。当我们学习到“分支”的时候,会再次感受到Git这种存储数据方式的威力的!

1.7    几乎所有操作都是在本地的
在使用集中式版本控制系统时,你的电脑必须能联网。当你要提交代码时,需要网络把数据提交给中央服务器。当你要浏览历史版本时,需要通过网络向中央服务器发送请求,最终中央服务器再通过网络把历史版本列表发送给你的机器。所以一旦没有了网络,集中式版本控制系统就无法再使用了。

而Git不需要网络也能工作,因为每一个Git客户端都拥有所有文件和所有历史版本!比如,你要浏览项目的历史版本时,Git不需要从外界服务器获取,Git会直接从你本地计算机中获取历史版本。

这也意味着,在没有网络的情况下(当你坐飞机时),你也可以把代码提交到本地,当有网络的时候(下飞机了),再把本地的数据提交到远程服务器。


1.8    安装Git
目前,Git可以在Linux、Unix、Mac和Windows这几大平台上正常运行了。我们主要学习在windows中如何使用git。

下载git
 
https://www.git-scm.com/download/win
 
双击git安装包,看到以下界面,直接点击next即可

选择一个安装目录,然后点击下一步
 
 

后续安装过程,一路下一步即可,直到安装!
 

至此,git安装完毕! 若在桌面上的右键菜单中,可以看到如下选项,则说明git安装成功!
 
 

Git安装完成后的第一件事,就是为git设置你的用户名和邮箱地址,这一步很重要!因为使用Git每次进行提交的时候都会使用到这两个信息!毕竟Git是分布式版本控制系统,所以,每个机器提交时都必须自报家门
 


设置好之后,可以使用以下命令来查看
 
 
 

加上--show-origin可以看到配置对应的文件:
 


你也许会担心,如果有人故意冒充别人怎么办?这个不必担心,首先我们相信大家都是善良无知的群众,其次,真的有冒充的也是有办法可查的。

帮助命令,通过帮助命令我们可以看到一个命令的用法
 
 
1.9    Hello World
让我们通过一个Hello World示例快速快速体会一下git的作用
 

一个典型的git操作如下:
1.    编写工作区中的文件
2.    把工作区中的修改加入暂存区
3.    把暂存区中的修改提交到版本库

步骤1,创建一个文件夹,名叫:learngit
 

步骤2,进入该文件夹,打开右键菜单,点击“Git Bash ”
 
 

步骤3,键入 git init
 
此时,在learngit文件夹中会出现一个“.git”文件夹,该“.git”文件夹所在的目录就是工作区

步骤4,在文件夹中创建一个文件:hello.txt
 

步骤5,查看文件的状态
 
 
因为 hello.txt 在 Untracked files 之下,所以 hello.txt 这个文件是还没有被 git 追踪的,说白了就是该文件并没有被加入暂存区,也没有在最新一次的提交中出现过

步骤6,为了让 git 能够追踪(管理) hello.txt 文件,我们需要使用 git add 命令
 

步骤7,查看文件的状态
 
因为 hello.txt 的在 Changes to be committed 之下,所以 hello.txt 文件的状态为 Staged,表示该文件已经被加入到暂存区了,处于暂存区中的文件将会在下次提交时,被提交到版本库中。
注意 git add 命令后面也可以跟一个目录的名字,此时 git add 会把该目录下的所有文件都加入到暂存区中,比如 git add . 就是把当前目录中的所有文件都加入到暂存区中
 
步骤,8,将暂存区中的修改提交到版本库
 
注意,提交时,必须编写本次的提交信息,例子中是使用 -m 来指定提交信息的。并且提交之后,暂存区中的修改就被清空了(工作区中的文件还在)。可以想象,在版本库中已经有一个版本了:
 

步骤9,查看文件的状态
 
以上这个信息表示,你的工作区是“干净”的。换句话说,就是工作区中的所有文件与版本库中最新版本中的所有文件内容是一致的。此时工作区中的所有文件的状态都是 Unmodified

步骤10,查看提交日志
 
 
步骤11,再创建一个文件:foo.txt,将foo.txt加入暂存区,然后提交版本
 

可以想象,在版本库中目前有2个版本:
 

步骤12,查看提交历史记录
 
 

步骤13,回退版本
 

步骤14,以此类推,我们可以通过git命令不断地向版本库中提交新的版本:
 

以下是更详细的版本库模型:
 
 


步骤15,修改hello.txt的内容
 

步骤16,查看文件的状态
 
我们发现 hello.txt 在 Changes not staged for commit 之下,这表示一个已经被 git 追踪的文件在工作区中被修改了,并且这个修改还没有被加入到暂存区,此时 hello.txt 的状态为 modified

步骤17,再次运行 git add,把对 hello.txt 文件的修改加入到暂存区
 
 

步骤18,查看文件的状态
 
对 hello.txt 的修改已经被存入暂存区了,并且会在下次提交时,被提交到版本库中

步骤19,更复杂的情况我们在后面在学习,目前先把这个暂存区中的修改提交了,完成我们的 Hello World 例子
 


1.10    复盘 Hello World
1.    Git 基本命令
Git 命令    说明
git add    将工作区中的修改加入暂存区
    
 
git commit    将暂存区中的修改加入到版本库
git status    查看文件的状态
git log    查看历史提交记录
git reset --hard 版本号    回退到指定的版本

2.    文件状态模型图
 
Untracked:文件还未被Git追踪,也就是没有被Git管理,比如刚刚创建出来的文件就处于这个状态
Staged:处于暂存区中的文件,就是Staged状态
Unmodifed:工作区中的文件与版本库中当前版本库中内容一致,就处于该状态 Modified:原本处于Unmodifed状态的文件,被修改了,就进入Modified状态

以上,只有Untracked状态是没有被Git管理的,其他3个状态虽然各不一样,但是都算作被 git管理了

通过这个Hello World,我们知道了工作区、暂存区、版本库、提交、版本回退、文件的状态这些概念,后续章节将详细学习git的方方面面

3.    Git三大区域:
a.    工作区(Working Directory)简单来说,工作区就是“.git”隐藏文件夹所在的目录
b.    暂存区(Staging Area),处于暂存区中的修改,将会在下次提交命令执行时,被加入到版本库中
c.    版本库(Repository),存放用户提交的各个版本
 
 
问题是:
a.    要暂存区干嘛?避免过多的提交。把多个相关的修改绑定在一次提交中。
b.    版本库在哪里? 就在.git中的objects。

4.    典型的Git操作如下:
a.    你修改了工作区中的文件
b.    你把修改加入暂存区
c.    你进行了提交,把当前项目中所有文件的快照存入版本库中
 
第2章 Git基础命令
本章的要学习Git基础命令都非常实用,项目开发中使用的绝大多数Git命令就是本章要学习的

2.1    忽略文件
通常在项目中,你会有一类不希望被git显示为“untracked”的文件,比如日志文件、项目构建文件、针对于Java的字节码文件等等,在这种情况下,你可以创建一个叫
做“.gitignore”的文件来列出所有需要忽略的文件名,如下:
 
建议在项目一开始就使用 .gitignore 文件,把 .gitignore 先 编辑好,再开展其他开发工
作!

2.2    git diff命令
git stauts只能显示我们对哪些文件做了修改,如果你还想知道,修改的确切内容是什么,就需要使用以下命令了:
 
该命令可以回答以下2个问题:
A.    你对哪个文件做了什么修改,并且还没有将修改加入暂存区
B.    你已经把哪些修改存入了暂存区,并打算提交到版本库中,这需要在git diff后加上-- staged,如下
 

比如,有以下的示例使用 git diff 来查看有哪些修改还没有被加入暂存区
 
 

查看两个版本之间的文件列表的不同
 

查看指定文件在两个版本之间的不同
 

如果感觉这种显示不够直观,可以使用 vimdiff 查看
 


2.3 git rm删除文件
 


2.4 untracked
如果你忘了将一些不需要被git管理的文件加入到“.gitingore”中,且已经把它们加入到了暂存区,又提交到了仓库中时,就可以使用以下命令,把暂存区中的某个文件移除掉,而且再次提交的话,就会从仓库中删除掉abc.txt文件:
 
注意,abc.txt仍然在工作区中,只是不再被git管理了,所以下次使用 git status 命令时,
会发现abc.txt的状态成为了:Untracked
 
2.6    Undoing things (撤销操作)
2.6.1    git log
像这样,你不断对文件进行修改,然后不断提交修改到版本库里,就会产生越来越多的提交,我们可以通过git提供的“版本回退”来加载任意一次提交时的代码。(就好像玩游戏时的存档一样)如下,使用git log命令来查看历史提交记录:

注意:
a.    这个列表是按照时间降序排列的,也就是说最上面的一行就是最近一次的提交信息。
b.    提交信息中有作者的名字、邮箱和日期
c.    你看到的一大串类似91a17c9...的是commit id(版本号)。同时也可以看到,目前有2次提交,如果觉得输出信息太多,可以加上--pretty=oneline参数:

2.6.2    git commit --amend
当你对于最近一次代码的修改,提交过早,就可能会忘记add某些文件,或者你最近一次提交信息非常混乱,如果你想重做这最近一次的提交,把你遗忘的步骤补上,然后将修改加入暂存区,使用以下命令提交即可:
 
注意,该命令只适用于最近的一次提交。且会改变最后一次提交的“commit id”
 
比如,如果你刚刚提交完成,却发现自己忘记将某一个文件的修改加入暂存区了,你可以这样做:
 
如此,第二个提交就代替了一次的提交,而在git log中不会产生一个新的提交

2.6.3    Unstaging a Staged File
你修改了2个文件,并且想分别提交两次,但是你不小心执行了“git add *”一次性把这两个文件都加入到了暂存区,此时你如何从暂存区中移除其中一个文件呢?其实git status命令本身就有提示:
 

我们就按照提示,进行操作即可:


2.6.4    Unmodifying a Modified File
 
你刚刚完成了一次提交,现在工作区和暂存区都是干净的。然后你又修改了工作区中的某个文件,突然你意识到这些修改是没有必要的,此时如何撤销工作区中的这些修改呢?


git restore <file>会把文件在工作区的修改全部撤销,这里有两种情况:
一种是1.txt自修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态;
一种是1.txt已经添加到暂存区后,又作了修改,现在撤销修改就回到添加到暂存区后的状态。
总之,就是让这个文件回到最近一次git commit或git add时的状态。

2.7.    版本回退
2.7.1    git reset
 
好了,现在我们启动时光穿梭机,准备进行版本回退!

在Git中,用HEAD表示当前版本:
a.    上一个版本就是HEAD^
b.    上上一个版本就是HEAD^^
c.    当然往上100个版本写100个^ ,可以简写成HEAD~100
 
 

此时我们用git log再看看现在版本库的状态:

最新的那个版本”edit hello.txt”已经看不到了!好比你从21世纪坐时光穿梭机来到了19世纪,然而不能再穿梭回21世纪了!?

其实,只要能找到那个“edit hello.txt”的commit id,就可以指定回到未来的某个版本


那么如果再回退到某个版本后,再想恢复到新版本的时候,找不到新版本的commit id时,该怎么办?
在Git中,总是有后悔药可以吃的!使用git reflog命令即可:

 
Git的版本回退速度非常快,因为Git在内部有个指向当前版本的HEAD指针,如下图所示:

     

     
 

传统的VCS是这样管理代码的:每次提交时,保存的都是修改的内容。(SVN)
 

而Git则是这样管理的:每次提交时,git都会保存提交那一刻的暂存区中所有文件的快照
(只读的副本),同时创建一个指针指向那个快照。为了提高效率,如果某一个文件没有被修改过,Git就不会再把这个文件保存一次,而仅仅是保存好这个文件在上次提交版本中的连接。如下图所示,标有虚线边框的就是一个指向上次提交的连接。
 
现在再考虑一下,为什么我们说Git的版本回退速度非常快了。

2.7.2    git revert
git revert 用于“反做”某一个版本,以达到撤销该版本的修改的目的。比如,我们 commit了三个版本(版本一、版本二、版本三),突然发现版本二的修改有bug,想要撤
 
销版本二,但又不想影响版本三的提交,就可以用 git revert 命令来反做版本二,生成新的版本四,这个版本四中会保留版本三的修改,但撤销了版本二的修改,如下图所示;
如果我们想撤销之前的某一个提交,但是又想保留该提及后面的提交,就可以用这种方法。

2.8 Git提交的是修改,而非文件
Git跟踪并提交的是修改,而非文件!针对于hello.txt,完成以下操作:
a.    第一次修改
b.    git add
c.    第二次修改
d.    git commit

此时使用git status,会发现第二次的修改没有被提交!再使用git diff命令来查看版本库里面最新版本与工作区中版本的区别,我们发现第二次的修改没有被提交!

因为Git提交的是修改,而非文件!当你用git add命令后,在工作区的第一次修改被放入暂存区,准备提交,但是,在工作区的第二次修改并没有放入暂存区(尽管文件的内容中保存了第二次的修改),所以,git commit只负责把暂存区的修改提交了,也就是第一次的修改被提交了,第二次的修改不会被提交。

总之,如果不用git add到暂存区,那就不会加入到commit中。
 

 
第3章 远程仓库
3.1    Git远程仓库是什么
Git远程仓库是位于网络上的仓库,该仓库中存放了你项目的各个版本。 (其实远程仓库也未必非得在网络上,所谓的远程仓库,不如直接理解为“另外一个仓库”而已”,测试的时候,甚至可以把所谓的远程仓库创建在你本地电脑上。)

3.2    Git远程仓库的作用
Git远程仓库的作用就是为了协作开发。同一个Git仓库,可以分布到不同的机器上。怎么分布呢?最早,肯定只有一台机器有一个原始版本库,此后,别的机器可以“克隆”这个原始版本库,而且每台机器的版本库其实都是一样的,并没有主次之分。

这些机器之间要经常把自己的修改推送给对方,这样比较麻烦,所以实际情况是这样找一台电脑充当服务器的角色,每天24小时开机,其他每个人都从这个“服务器”仓库克隆一份到自己的电脑上,并且各自把各自的提交推送到服务器仓库里,也从服务器仓库中拉取别人的提交。不过要知道这个服务器在Git中不是必须的,它只是为了方便交换数据。

我们没有必要自己搭建一个git服务器,进而在其上创建一个远程仓库。因为这个世界上有个叫Gitee的神奇的网站,这个网站就是提供Git仓库托管服务的,所以,只要注册一个 Gitee账号,就可以免费获得Git远程仓库。(如果还没有Gitee的账户,现在就申请一个吧)

3.3    克隆Git远程仓库
当远程仓库已经存在,且本地仓库不存在时,我们可以直接把远程仓库克隆下来,克隆之后,就出现了一个与远程仓库关联好的本地仓库。

3.3.1    创建远程仓库
 
 

Gitee已经告诉我们,没有本地仓库和已有本地仓库分别要如何做,如下:


3.3.2    克隆远程仓库
创建git workspace文件夹,在其中再创建andy和eason两个子文件夹模拟俩个机器
 
 

在andy文件夹中克隆远程仓库:
 

在eason文件夹中克隆远程仓库:


3.3.3    查看远程仓库
键入以下命令:
 

可以看到:

以上输出展示了与本地仓库关联的远程仓库的名字:origin,以及从origin这个远程仓库拉取数据的url,还有向origin这个远程仓库推送数据的url。注意,通过克隆方式关联的远程仓库名字,总是默认为origin。

3.3.4    推送提交
先在andy的本地仓库完成一个提交
 
 

然后推送到远程仓库origin:
 


刷新远程仓库:


3.3.5    拉取修改
此时在eason文件夹中,还没有index.html
 
 

拉取修改


注意,eason拉取的是修改而不是文件。意思是,eason把远程仓库中的修改拉取完毕以  后,在本地对index.html又做了一些修改,结果eason又不想要这些修改了,如果希望通过再次执行“git pull”来拉取远程仓库的index.html文件,是无济于事的,因为“git
pull”拉取的是修改而不是文件!如下:
 
 

要放弃工作区中的修改,执行以下命令即可:
 

 
3.4    关联远程仓库
当远程仓库已经存在,本地仓库也已经存在,在该本地仓库没有关联远程仓库时,我们可以通过git命令让该本地仓库关联上远程仓库。

3.4.1    创建本地仓库
创建G.E.M文件夹,并将其初始化为一个git仓库


此时键入以下命令:
 

会发现该本地仓库并没有关联任何远程仓库


3.4.2    关联远程仓库
关联远程仓库的命令如下:
 
 
 

注意,手动关联远程仓库的时候,可以自己指定远程仓库的名字,比如上面的foo就是自定义的远程仓库的名称。

3.4.3    拉取修改
此时G.E.M已经关联了learn-git远程仓库,就可以开始拉取其中的修改了:
 

结果,我们发现,无法拉取,这是因为,通过手动关联远程仓库的方式,并没有关联本地分支与远程分支!

可以通过以下命令查看远程仓库的详细信息:
 
 
 

有两种方式可以解决这个问题:
1.    在进行"git pull"的同时,临时指定本次分支和远程分支的关系
 

2.    仅仅只关联本地分支和远程分支,并不会进行"git pull"操作
 

这里采用第一种方式:


3.    3.4 解除关联
 
 
 

3.4.5 对比:
    手动关联    git clone
远程仓库在本地的名字    自己定义    默认为orign
关联分支    手动关联    自动关联了本地仓库的master分支与
远程仓库的master分支
git pull    手动执行git pull    自动执行git pull

3.5    冲突
3.5.1    冲突发生的条件
我们已经知道,将本地仓库的修改推送给远程仓库的命令是:
 

当andy和eason这两个小伙伴同时执行了"git pull",且先后进行了“git push”,则后一个执行了"git push"的小伙伴就是倒霉蛋了!他的"git push"将会被拒绝。假设是eason是后一个执行"git push"的,此时eason的"git push"就会被拒绝!这是为了防止eason的修改覆盖了andy的修改。

如下:
 
 

3.5.2    解决冲突
当冲突在eason这一方发生后,eason需要先执行“git pull”,把andy的修改拉取下来,此时andy的修改、eason的修改全部都存在于index.html中了:
 
 

经过andy和eason两个人的商讨后,最终共同决定修改index.html内容如下:
 
 
此时,冲突已经解决。也就是在冲突发生后,先执行git pull,再git add,再git push就可以成功push了。

3.5.3    建议
我在此建议,每次要进行git push之前,一定要先进行一次git pull的执行。让git pull先把远程仓库中最新的修改拉取到本地,如果有冲突就解决,然后再进行push。

有些同学可能会担心,在git push之前,先进行git pull的话,远程仓库会把本地的新修改给覆盖了,这是不会发生的,此时git会提示我们合并代码的。

注意,以上的这种处理冲突的方式,其实这并不是实际开发中的工作流程,更加真实的开发流程,在下一章。

3.6    Git Aliases(自学)
https://git-scm.com/book/en/v2/Git-Basics-Git-Aliases
 
第4章 分支
几乎每一个VCS(Version Control System 版本控制软件)都以这样或那样的形式支
持“分支”。这意味着你可以从开发的主线克隆出一个与开发进度与主线完全一样的分支,进而在这个分支上继续做一些开发,而不会影响到原来的主线。
 

在众多的VCS中,分支这样的操作会有较大的代价,它们通常是通过复制代码来完成分支的创建的,这对于大型项目而言必然是十分耗时的操作。

有人指出Git中的分支是一个“杀手级特性”(killer feature)。的确,Git中的“分支”特性让Git从众多的VCS中脱颖而出。Git中的分支操作是十分轻量的,几乎是瞬间完成的
(nearly instantaneous),在Git中来回切换分支是很快速的。不像很多其他的VCS,Git鼓励我们大量地使用分支!理解并且掌握分支操作是学习Git不可避免的一个技术关卡

4.1    铺垫
为了充分理解Git的分支,我们需要先学习Git是如何存储数据的。当你提交的时候,Git会存储一个“提交对象”,该提交对象包含一个指针,指向一个快照,该快照就是提交那一刻的所有文件的快照(副本)。这个提交对象同时也包含作者的名字和邮箱、提交信息、指向前一次提交的指针。

为了更加形象地说明,让我们假设你有一个包含3个文件的目录,3个文件的名字分别是: README、LICENSE、test.rb。当你把它们加入到暂存区,再提交,git就会为每一个文件生成一个id,同时git还会生成一颗树,树的根节点列出了哪个文件名对应哪个id,git还会生成一个提交对象,该对象包含一个指向该树根节点的指针,如下图所示:
 
 
98ca9就是提交对象,92ec2就是树的根节点,通过该根节点可以找到提交那一刻的所有文件的内容,也就是所谓的快照。

在Git中的分支本质上就是一个指向某个提交对象的指针。也就是说,分支就是指针!分支就是指针!分支就是指针!Git中默认的分支名叫做master,记住分支就是指针,当你做提交操作时,git实际上是让这个master指针指向你最后一次的提交,每当你提交时,master指针都会自动向前移动以指向最新一次的提交
 


4.2    创建分支
当你创建一个新的分支时,仅仅是创建了一个新的指针,该指针指向当前的提交。比如,你想创建一个名为testing的分支,你可以键入以下命令:
 
 

这个命令将会创建一个新的指针,指向你当前所处的提交,此时两个分支都指向同一个提交对象:
 

那么git如何确定你究竟处在哪个分支上呢?这就要借助于另外一个叫做HEAD的指针了,该指针是一个二级指针(指向指针的指针)。如下图所示:
 
HEAD指针指向哪个分支,就表示你处在哪个分支上。你现在仍然处在master分支
上,“git branch”命令仅仅是创建一个新的分支,该命令并不会让你切换到刚刚创建的分支上。

你可以利用 git log --decorate 命令来查看你目前处于哪个分支
 
 


4.3    切换分支
切换分支的命令如下:
 

让我们切换到testing分支:
 

这个命令本质上是让HEAD指向了testing:
 
 
git这样设计分支的意义何在?好吧,让我们此时做一个提交:
 

此时的分支模型看起来是这个样子:
 

有趣的是,testing分支向前移动了,而master分支并没有向前移动。让我们再次切回 master分支
 
 
 

这个命令做了两件事情:它让HEAD指向了master,同时将你工作区中的文件恢复到了 master所指向的文件快照的状态。

注意,git log 命令不会显示所有的分支提交,git log默认只会显示当前分支的提交。如果想要查看所有分支的提交信息,则需要为 git log 命令添加 --all 参数。

此时如果我们再次提交会发生什么?如下:
 

现在你项目的提交历史出现了分叉。一开始,你只有一个叫做master的分支,然后你创建了一个testing分支,并在testing上做了一些提交,然后你又切换回master分支,并又在 master上做了一些提交。此时两个分支中的提交是被隔离开的:
 
 

你也可以通过以下命令来查看此时的提交历史:
 


该命令有3个作用
1.    会打印出你的提交历史
2.    显示出各个分支的指向哪个提交
3.    显示出你的提交历史是如何分叉的

可以看出,在Git中,创建分支和切换分支是十分轻量的,这与其他CVS形成了鲜明的对
比,其他的CVS中的分支会复制整个项目文件到第二个文件夹中,当项目变得越来越大时,代价就会越来越高。而在Git中,分支操作几乎是瞬时完成的,所以Git鼓励程序员大量使用分支!

小技巧:你可以利用以下命令在创建新分支的同时,切换到该新分支:
 
 

小技巧:以下命令可以在最近两个分支之间来回切换


注意: 如果工作区中的工作没有完成,比如修改了一个被git管理的文件,还没有提交,此时则无法切换分支。(如果是新建的文件,还没有提交,是可以切换分支的)
 

当前例子只是为了演示分支的创建和提交的分叉,并不设计分支的合并。为了学习以下的合并分支,可以删除当前仓库,从头开始提交。

4.4    合并分支
分支就是科幻电影里面的平行宇宙,当你正在电脑前努力学习Git的时候,另一个你正在另一个平行宇宙里努力学习SVN
 
如果两个平行宇宙互不干扰,那对现在的你也没啥影响。不过,在某个时间点,两个平行宇宙合并了,结果,你既学会了Git又学会了SVN!


快速合并
我们已经知道,每次提交,master分支都会向前移动一步,这样,随着你不断提交, master分支的线也越来越长,这条线就是一个分支,即master分支。Git用master指向最新的提交,再用HEAD指向master


注意:
1.    head用于确定目前处于哪个分支
2.    master用于指向当前工作区对应master分支的哪个提交

当我们创建新的分支,例如dev时,Git新建了一个指针叫dev,指向master相同的提交,再把HEAD指向dev,就表示当前在dev分支上:
 
 

不过从现在开始,对工作区的修改和提交就是针对dev分支了,每提交一次后,dev指针都会往前移动一步,而master指针不变:


假如我们完成了在dev分支上的工作并且通过了测试,就可以把dev合并到master上。Git怎么合并呢?为了让master把dev分支合并进来,需要先切换到master分支,再让master去合并dev:
 

目前这种情况,当mater合并dev时,就是直接让master指向dev的当前提交,就完成了合并。这种情况我们称之为快速合并(Fast-forward),如下图所示:
 
 


合并完分支后,甚至可以删除dev分支。删除dev分支就是把dev指针给删掉,删掉后,我们就剩下了一条master分支:


没有冲突的分叉合并
当你在master从做了一个feature1分支,并做了一次提交,然后又切换回master分支,并做了一次提交,此时的git分支模型开起来就是这个样子:

 
此时让master分支合并feature1分支的话(没有冲突),就会产生一个新的提交,这个新的提交融合了master分支的最新提交和feature1分支的最新提交,如下:


根据以下git log的提示,完成所有步骤
 

有冲突的分叉合并
 
 

查看3.html的内容:
 
=====是一个分隔符,上部分内容是当前master分支对3.html做的修改,下部分内容是 feature1对3.html做的修改。

你可以根据实际情况来解决这个冲突,这里把两部分修改都保留下来:
 
 

再次提交,即可完成合并,查看git提交日志
 
冲突解决的本质是,在冲突发生后,只要再次执行“git add . ”,"git commit"命令,冲突就算解决了。

4.5    综合例子
让我们通过一个简单的例子再来串一遍合并分支。在实际的开发中,你按以下方式使用git
1.    在一个项目中做一些开发工作
2.    要为项目添加新的功能时,创建一个新的分支
 
3.    在该新分支上进行新功能的开发

此时你接到一个电话,生产分支(master分支)需要立即修复一个bug,你按以下步骤来处理:
1.    切换到生产分支
2.    从生产分支创建创建一个hotfix分支来修复bug
3.    修复完成后,成功通过测试,将hotfix分支合并到master分支
4.    切换回你原来的分支继续工作


Basic Branching
首先,在master分支上已经有一些提交了:
 
 

然后,你决定做一个 issue#53 分支来做一些开发工作:

然后,你在iss53这个分支上做了一些修改,并进行了一次提交:
 

 
此时,你接到一个电话,说在产品分支中(master)发现了一个问题,你必须停止手头的任何工作,要立即解决这个bug。你要做的是,切换到产品分支。注意在切换分支时,工作目录必须是干净的,而此时手头的工作并没有完成,提交也不是,撤销工作区中的所有修改也不是,怎么办?此时可以使用先把工作区中的修改先存起来,再切换到产品分支

此时,你工作区中的文件内容,就是你开始issue#53分支之前的状态,这是非常重要的一点:当你切换到某个分支的时候,git会重置你工作区中的文件内容到该分支最后一次提交时的状态。接下来你创建了一个hotfix分支以修复产品分支的bug,并且在hotfix分支上做了一次提交:


在经过测试后,确定产品分支中的bug在hotfix分支已经解决了,你就可以让master分支把 hotfix分支合并了:

注意在结果中出现了 Fast-forward,因为maseter指向的C4提交和hotfix指向的C2提交之间没有分叉,所以git仅仅是移动master指针到hotfix所指向的提交,也就是前面说过
 
的“快速合并”:
 

在这个超级重要的bug被修复并部署到服务器之后,你准备切换回原来的iss53分支继续原先的工作了。不过你应该先删除 hotfix 这个分支,因为它没有必要再存在了,master分支已经包含了 hotfix分支的所有提交了,你可以执行以下的命令来删除一个分支:
 

现在,你可以切原来的iss53分支继续原先的工作了,但是原先工作区中的修改已经被存起来了,如何恢复呢?如下
 

然后你又在iss53上进行了一次提交,以完成 iss53 分支上的开发任务:
 
 
注意在C5提交中,并没有包含当初在 hotfix 分支中做的任何修改。所以你应该让master分支把iss53分支合并起来,或者在未来任何一个你觉得合适的时刻再做这个合并也不迟。

Basic Merging
假设你决定让master分支去合并iss53分支中的修改了,你会按如下命令操作:
 

此时的 master 合并 iss53 与上面的 master 合并 hotfix 不同。在当前这种情况下,你的提交历史中出现了分叉。因为 master 中的 C4 提交不是 iss53 中 C5 提交的“直系祖先”,所以git会用3个提交来完成这次合并:C4、C5,以及它们共同的祖先C2:
 

git会自动创建一个新的提交来完成这次合并,这样的提交叫做“合并提交”,该提交的特殊之处在于它有多个父提交:
 

现在你可以删除 iss53 分支了
 
 
注意,有时候这个合并过程并不顺利。比如在 iss53 和 master 分支中修改了同一                                                             个文件,那么在合并 master 和 iss53 时,你会遇到合并冲突的问题:

此时git就不会自动创建一个新的提交了。你可以通过 git status 命令来查看哪个文件再合并时冲突了:
 

冲突文件的内容大概是这个样子:
 

最终,你决定这样修改冲突文件:
 

然后就可以提交本次的修改,以完成合并了,提交信息默认看起来像这个样子:
 
 
 


4.6    分支管理
创建分支:
 

切换分支
 

创建分支的同时切换分支
 

在最近的两个分支之间切换
 

查看分支:
 

查看分支的同时,也查看每个分支的最新提交
 
 

删除分支
 

强制删除还没有被合并的分支
 

合并分支
 

查看已合并到当前分支的其它分支
 

查看还没有合并到当前分支的其他分支
 
 
第5章 分支工作流
现在你已经学会了分支的基本操作。本章节将学习分支工作流。

5.1    典型的分支模型
 

maseter:主分支只存放最稳定的代码,通常是一个软件的发行版(release)。 develop:开发分支,并不要求一直稳定,但当开发分支比较稳定的时候,就可以合并到主分支。
topic:主题分支,又叫特性分支,是为了开发某一个功能或者特性而做的分支(比如登录功能、注册功能等等),在特性开发完毕并测试成功之后,就合并到开发分支。并且被合并之后通常会删除掉。

注意,越下层的分支越不稳定,提交的次数越多;越上层的分支越稳定,提交的次数越少--仅仅在下层分支稳定的时候才将下层分支合并到上层分支。很多公司按照这个套路发展出了更多层的分支。

5.2    主题分支
主题分支在任何规模的项目中都非常有用。主题分支就是为了开发某一个功能或者某一个特性而临时存在的分支,当功能开发完毕且通过测试后,主题分支会被合并到上层分支中,然后主题分支就会被删除掉。这样的操作对于其他的VCS软件而言是十分奢侈的,但是在Git中,却是鼓励程序员大量地使用分支!

让我们来看一个例子:
 
 
你从master分支创建了另一个分支以完成一个功能:iss91, 而在 iss91 分支开发的过程
中,你又另一个想法,于是你在iss91分支上有创建了一个分支:iss91v2以用另一种想法开发同一个功能,然后你又切换回master分支继续做了一些master分支上的开发,开发到某个节点时,你发现你接下来要开发的功能所用的方法你不确定是否是一个好办法,所以你有创建了一个分支:dumbidea分支,并在该分支上继续实现你的想法,此时你的提交历史看起来就是上面的样子。

假设你更喜欢 iss91v2 分支的解决方案,并且你把你的dumbidea分支给你的团队成员都看了看,并且大伙一直认为dumbidea分支的代码简直是惊为天人!于是你合并了 iss91v2 到 master 分支中,并强制删除了 iss91 分支(也就是舍弃了C5和C6提交),然后也将 dumbidea 合并到了 master 分支中,你的提交历史看起来是这个样子:
 
 

注意,以上的操作都是操作本地的分支。 当你新建和合并分支的时候,所有这一切都只发                                                                 生在你本地的 Git 版本库中 —— 此时并没有设计到远程服务器中的分支。

5.3    远程分支
通过以下命令,可以查看远程仓库的信息
 
 
在使用远程仓库时,“远程跟踪分支”是一个很常用的技术。首先不要被远程跟踪分支这个名字迷惑了,远程跟踪分支并不存在于远程仓库,它只存在于本地仓库中。另外因为任何分支都是指针,所以远程跟踪分支也是一个指针,但是你并不能移动这个指针。你应该把远程跟踪分支当做一个“书签”
这个“书签”记录了你最后一次与一个远程分支连接时,那个远程分支所处于的提交。

远程跟踪分支的名字的形式为:<remote>/<branch>。比如如果你想看看你本地的 master分支对应的远程仓库中的master,在你上次连接它的时候,处于哪一次的提交,你应该查看origin/master这个远程跟踪分支。又比如你和另外一个小伙伴共同协作开发 iss53 分支,此时在你的本地仓库中有一个名字 iss53 的分支,同时本地的 origin/iss53 这个远程跟踪分支代表了上一次你与远程仓库中的 iss53 分支连接的状态。

这听起来有点混乱,让我们来看一个例子。假设你有一个git远程仓库,如果你从该远程仓库克隆到本地仓库,则在该本地仓库中git会自动命名该远程仓库为 origin ,并且拉取该仓库中的所有数据,并且在本地仓库中创建一个远程跟踪分支:origin/master以跟踪远程仓库中master分支的状态,git同时也会为你创建一个与远程分支完全一致的本地分支 master:
 
注意以上的origin/master分支是一个远程跟踪分支,它存在于本地仓库中,真正的远程分支master 是处在远程仓库中的!且 origin/master 是一个只读的分支,你无法直接移动
 
它,除非你再次与远程分支连接上,才会更新 origin/master 分支。你可以通过以下命令看到远程跟踪分支

如果你在本地分支master上做了一些提交,与此同时,其他人推送了一些新的提交到远程仓库的master分支中,此时本地仓库和远程仓库的提交历史看起来是这个样子:
 
只要你的本地master分支一直不与远程仓库中的master分支连接,则你本地的 origin/master 远程跟踪分支就会一直处在那个固定的位置。

为了与远程仓库中的master分支保持同步,你可以运行以下命令:
 

在本例子中,就是运行 git fetch origin,该命令会把 origin 远程仓库中的任何你本地没有的数据拉取下来,同时更新你的本地库,移动 origin/master 到最新的位置:
 
 

这种情况下,你可以使用 git merge 来让 master 分支合并 origin/master 分支,虽然我们不能直接移动 origin/master 分支,但是却可以合并它。实际上我们之前使用的git pull就是git fetch和 git merge的结合。

Pushing Branch
当你想公开一个你本地的某一个分支时,只需要将该分支推送到一个你拥有写权限的远程仓库上。同样,如果你本地有一些你不想公开的私有分支,你也可以藏着玩,然后把私有分支合并到公开的分支上再推送也行。

如果你有一个名叫serverfix的分支,你想把该分支公开以便与其他小伙伴在其上协作开发,你可以键入以下命令:
 

此后在其他小伙伴执行 git fetch origin 命令时,他们就会在他们的本地仓库中得到一个远程跟踪分支:origin/serverfix
 
 
注意以上命令并不会在本地仓库生成serverfix分支,而仅仅只生成了orgin/serverfix这个远程跟踪分支。此时对于这个远程跟踪分支origin/serverfix,你有两种处理方式,一种是将其合并到其他分支上,另一种是根据origin/serverfix这个远程跟踪分支直接生成本地分支 serverfix,命令如下:
 

此后你就可以在本地的 serverfix 分支上进行开发工作了。

你可以使用以下命令查看本地仓库中的分支与远程仓库中的分支的对应关系
 

你可以使用以下命令来让一个本地分支与一个远程分支发生“追踪”关系
 


5.4    Git分支模型
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值