Git 版本控制工具概述

Git是目前世界上最先进的分布式版本控制系统。

本地版本控制系统
分布式的版本控制系统

发展历程:

Linus在1991年创建了开源的Linux,从此,Linux系统不断发展,已经成为最大的服务器系统软件了。

Linus虽然创建了Linux,但Linux的壮大是靠全世界热心的志愿者参与的,这么多人在世界各地为Linux编写代码,那Linux的代码就必须要有一个管理工具来管理。

事实是,在2002年以前,世界各地的志愿者把源代码文件通过diff的方式发给Linus,然后由Linus本人通过手工方式合并代码!

到了2002年,Linux系统已经发展了十年了,代码库之大让Linus很难继续通过手工方式管理了,社区的弟兄们也对这种方式表达了强烈不满,于是Linus选择了一个商业的版本控制系统BitKeeper,BitKeeper的东家BitMover公司出于人道主义精神,授权Linux社区免费使用这个版本控制系统。

Linux社区牛人聚集,有一个伙计试图破解BitKeeper的协议,结果被BitMover公司发现了,于是BitMover公司怒了,要收回Linux社区的免费使用权。这个时候Linus向BitMover公司道个歉,保证以后不再发生这样的事情。

道歉归道歉,但实际上linus花了两周时间自己用C写了一个分布式版本控制系统,这就是Git!一个月之内,Linux系统的源码已经由Git管理了!从些Git分布式版本软件就此诞生了,Git迅速成为最流行的分布式版本控制系统。

2008年,GitHub网站上线了,它为开源项目免费提供Git存储,无数开源项目开始迁移至GitHub,包括jQuery,PHP,Ruby等等,很多很多。

整个的事情就是这样,如果不是当年BitMover公司威胁Linux社区要收回使用权,现在我们可能就没有免费而超级好用的Git分布式版本系统了。

最早Git是在Linux上开发的,很长一段时间内,Git也只能在Linux和Unix系统上跑。不过,慢慢地有人把它移植到了Windows上。现在,Git可以在Linux、Unix、Mac和Windows这几大平台上正常运行了

Git的原理

Git的使用流程是
工作区(Working Directory)------->版本库(Repository)暂缓区------>仓库
Git版本工具的部署与使用

工作区(Working Directory)
就是我们在本机的目录,就是我们的工作区,比如/root/myproject

[root@git myproject]# pwd
/root/myproject

版本库(Repository)
工作区有一个隐藏目录.git,这个.git不算工作区,而是Git的版本库。
Git的版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,Git为我们自动创建了一个分支master,以及指向master的一个指针叫HEAD。

我们把文件往Git版本库里添加的时候,要执行两步:
第一步:git add 把文件添加进去,实际上就是把文件添加到暂存区;
第二步:git commit -m "版本描述信息" 提交到版本库,实际上就是把暂存区的所有内容提交到仓库的当前分支

创建Git版本库时,Git自动为我们创建了一个master分支,所以git commit就是往master分支上提交更改。
你可以简单理解为,需要提交的文件修改通通放到暂存区,然后,一次性提交暂存区的所有修改。

当工作区有文件更改,执行git add时,暂缓区就变成以下这样了:
Git版本工具的部署与使用

git add 命令实际上就是把工作区要提交的内容放到暂存区(Stage),然后,执行git commit就可以一次性把暂存区的所有修改提交到分支
一但执行git commit提交后,那么工作区就是“干净”的:
现在版本库变成了这样,暂存区就没有任何内容了:
Git版本工具的部署与使用

以上就是git的流程,没看明白的同学,可以再多看一次。

安装Git

这里我用Centos 7系统来安装Git

# yum -y install git*

因为Git是分布式版本控制系统,所以,上传版本文件时必须提供:你的名字和Email地址。

使用git,创建本地工作区

# git config --global user.name "liuxiang"
# git config --global user.email "409216159@qq.com"

版本库又名仓库,英文名repository,可以理解成一个目录,这个目录里面的所有文件都被Git管理起来,每个文件的修改、删除,Git都能跟踪.

第一步:创建一个版本库,创建一个目录:

# mkdir myproject
# cd myproject
# pwd 
/root/myproject

第二步:git init命令把该目录初始化为Git可以管理的仓库

# git init
Initialized empty Git repository in /root/myproject/.git/

使用tree命令可以查看 /root/myproject/.git/目录下的树结构

# tree .git  
.git
├── branches
├── COMMIT_EDITMSG
├── config
├── description
├── HEAD
├── hooks
│   ├── applypatch-msg.sample
│   ├── commit-msg.sample
│   ├── post-update.sample
│   ├── pre-applypatch.sample
│   ├── pre-commit.sample
│   ├── prepare-commit-msg.sample
│   ├── pre-push.sample
│   ├── pre-rebase.sample
│   └── update.sample
├── index
├── info
│   └── exclude
├── logs
│   ├── HEAD
│   └── refs
│       ├── heads
│       │   └── master
│       └── remotes
│           └── origin
│               └── master
├── objects
├── ORIG_HEAD
└── refs
    ├── heads
    │   └── master
    ├── remotes
    │   └── origin
    │       └── master
    └── tags

开始编写一个README自述文件

# echo "## README file" > README

使用git status 查看当前工作区的状态

# git status
# On branch master
#
# Initial commit
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       README                   --有一个新的文件,上一名提示可以使用git add <file>添加至暂缓区
nothing added to commit but untracked files present (use "git add" to track)

使用git add 命令 将刚在工作区创建的README添加至暂缓区

# git add README     --将README文件添加至暂缓区
# git status                  --再查看工作区状态
# On branch master
#
# Initial commit
#
# Changes to be committed:
#   (use "git rm --cached <file>..." to unstage)
#
#       new file:   README      --显示暂缓区里有一个新的文件README
#

使用git commit -m "描述信息"将暂缓区的文件提交至本地仓库

# git commit -m "add README"
[master (root-commit) 880705c] add README
 1 file changed, 1 insertion(+)
 create mode 100644 README

# git status
# On branch master
nothing to commit, working directory clean

文件对比git diff

有时候我们修改了本地的一个文件,但临时有事走开了,回来的时候忘记了自己做了哪部分的修改。
此时就要用上git diff命令

来模拟以上环境

# echo "## new add text" >> README     --往README里添加内容

# git status      --查看状态
# On branch master
# 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      --显示README已经被修改过了
#
no changes added to commit (use "git add" and/or "git commit -a")

# git diff README                    --使用git diff 命令对比README文件看看和上次提交到仓库的内容和现在的文件哪些地方被修改过了
diff --git a/README b/README
index 0abae28..2d2eb8f 100644
--- a/README
+++ b/README
@@ -1 +1,2 @@
 ## README file
+## new add text          --这行前面有一个+ 表示是新增的内容

知道自己修改了什么,接下来就可以放心的提交了

# git add README                                   --提交至暂缓区
# git commit -m "mod README"              --提交到本地仓库
[master 56acaae] mod README
 1 file changed, 1 insertion(+)

# git status                                                --再查看状态时显示无内容可提交
# On branch master
nothing to commit, working directory clean

版本回退

在实际工作中,我们提交的版本可以有很多,很难记得每次提交了什么内容,或是什么时候提交的,这里就要用上git log来查看提交的版本

# git log
commit 56acaae3bc05603ac012e1cd7c6604a7e2f7fcc1
Author: uplooking <uplooking@up.com>
Date:   Fri Dec 8 03:39:14 2017 -0500

    mod README

commit 880705c78e9482703e6dfdbc67a16fc7dac133de
Author: uplooking <uplooking@up.com>
Date:   Fri Dec 8 03:25:06 2017 -0500

    add README

这里显示的内容是最近提交的显示在上面;
这里看到有两个版本,时间点也可以看得到,提交人和邮件也可以看得到,第一行的commit 后面的那一连串的字符是commit id是唯一标识符。

在真实环境中我们提交的版本有太多太多,要是这么看的话,不方便看,此时我们可以加上--pretty=oneline参数:这样显示就简洁多了

# git log --pretty=oneline
56acaae3bc05603ac012e1cd7c6604a7e2f7fcc1 mod README
880705c78e9482703e6dfdbc67a16fc7dac133de add README

现在准备回退版本:

在Git中,首先我们要知道现在是什么版本,以前有什么版本,用HEAD表示当前版本,上一个版本就是HEAD^,上上一个版本就是HEAD^^,上几个版本就写多少个^符,如果有很多的话也可以写成HEAD~20。
也可以指定版本的commit id的前4位以上也可以

我们回退到 add README这个版本中,这个版本当时只有一行内容

# git log --pretty=oneline     --先列出版本号
56acaae3bc05603ac012e1cd7c6604a7e2f7fcc1 mod README
880705c78e9482703e6dfdbc67a16fc7dac133de add README

# git reset --hard 8807        --回退到commit id开头为8807的版本
HEAD is now at 880705c add README

# git log --pretty=oneline     --查看当前的版本
880705c78e9482703e6dfdbc67a16fc7dac133de add README

# cat README                   --查看内容,回退成功
## README file

现在,你回退到了某个版本,回退这之后后悔了,想恢复到新版本怎么办?
好在git提供了这么一个功能,可以看到提交和回退的操作 git reflog命令

# git reflog               --列出了所有的commit的操作和reset回退的作品,还显示了对应的commit id
880705c HEAD@{0}: reset: moving to 8807
56acaae HEAD@{1}: reset: moving to 56acaa
880705c HEAD@{2}: reset: moving to 88070
56acaae HEAD@{3}: commit: mod README       ---该条记录就是最后一次提交到仓库的commit动作
880705c HEAD@{4}: commit (initial): add README

# git reset --hard 56acaae     --输入最后一次提交的commit id 回退
HEAD is now at 56acaae mod README

# git log --pretty=oneline       --查看当前版本,已经恢复到最新版本了
56acaae3bc05603ac012e1cd7c6604a7e2f7fcc1 mod README
880705c78e9482703e6dfdbc67a16fc7dac133de add README

# cat README                     --验证下内容,是新后一次提交的内容
## README file
## new add text

撤消与删除

在真实环境中我们会遇到修改了的文件,提交到了暂存区,想撤销的。或是删除仓库里不要的文件。

--撤消
第一种情况:修改了工作区的文件,还未提交到暂存区,要撤销

# echo "## Git checkout" >>README      --往README里添加内容

# cat README                --查看README内容
## README file.
## new file text
## 学习git软件

## Git checkout

[root@git myproject]# 
[root@git myproject]# git status             --查看状态,显示README文件被修改
# On branch master
# 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
#
no changes added to commit (use "git add" and/or "git commit -a")

[root@git myproject]# 
[root@git myproject]# git checkout -- README          --此时如果要撤消修改,则执行该命令
[root@git myproject]# 
[root@git myproject]# 
[root@git myproject]# git status         --再看状态,显示没有文件修改或添加
# On branch master
nothing to commit, working directory clean

[root@git myproject]# cat README       --再查看README内容时,内容已经撤消成功
## README file.
## new file text
## 学习git软件

git checkout -- file 命令中的--很重要,没有--,就变成了“切换到另一个分支”的命令,后面的分支管理中会讲到git checkout命令切分支。

第二种情况:如果你修改了文件,还执行了git add 到暂存区,这时要撤消的话,要执行多一步,如下:

[root@git myproject]# echo "## Git checkout" >>README 
[root@git myproject]# 
[root@git myproject]# git status
# On branch master
# 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
#
no changes added to commit (use "git add" and/or "git commit -a")

[root@git myproject]#  
[root@git myproject]# git add .
[root@git myproject]# 
[root@git myproject]# 
[root@git myproject]# git status      --当我们修改一个文件执行了git add后,再看状态时,会提示执行git reset HEAD <file> 可以撤消到工作区
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   README
#

[root@git myproject]# 
[root@git myproject]# git reset HEAD README    --把README暂存区修改的内容撤消到工作区
Unstaged changes after reset:
M       README

[root@git myproject]# git status        --再看状态时,显示文件已经到了工作区
# On branch master
# 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
#
no changes added to commit (use "git add" and/or "git commit -a")

[root@git myproject]# 
[root@git myproject]# git checkout -- README      --再执行git checkout -- README把修改的内容从工作区里撤消
[root@git myproject]# 
[root@git myproject]# git status            --再看状态,显示没有可提交的内容
# On branch master
nothing to commit, working directory clean

[root@git myproject]# cat README      --再看内容,已经恢复到了
## README file.
## new file text
## 学习git软件

删除文件

第一种情况:误删了工作区的某个文件;
如果误删了工作区的某个文件,可以从仓库里恢复,实践如下:

[root@git test]# rm README     --删除文件
rm: remove regular file ‘README’? y
[root@git test]# 
[root@git test]# ls    --文件已经被删除
[root@git test]# 
[root@git test]# git status     --查看状态,显示工作区文件README文件被标记为删除
# On branch 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:    README
#
no changes added to commit (use "git add" and/or "git commit -a")

[root@git test]# 
[root@git test]# 
[root@git test]# git checkout -- README    --此时执行git checkout -- <file> 可以将删除的文件撤消
[root@git test]# 
[root@git test]# git status
# On branch master
nothing to commit, working directory clean

[root@git test]# 
[root@git test]# ls     --再查看时文件已经恢复回来
README
[root@git test]# 

第二种情况:确认要删除仓库的某个文件,git rm命令
实践如下:

[root@git test]# git rm README     --删除工作区的文件README
rm 'README'

[root@git test]# 
[root@git test]# git status     --查看状态,README已经被标识为delete
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       deleted:    README
#

[root@git test]# git commit -m "delete README"     --执行提交到仓库
[master 3cd5280] delete README
 1 file changed, 2 deletions(-)
 delete mode 100644 README

[root@git test]# 
[root@git test]# ls     --文件已经被彻底删除
[root@git test]#

当执行git rm 动作后,突然想撤消怎么办? 使用 git stash 恢复
示例:

[root@git Git]# git reset --hard 5e4e356     --恢复README文件回来
HEAD is now at 5e4e356 mod README

[root@git Git]# ls
README

[root@git Git]# 
[root@git Git]# git rm README
rm 'README'

[root@git Git]# git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       deleted:    README
#
[root@git Git]# 
[root@git Git]# git stash     --执行还原到最后一次
Saved working directory and index state WIP on master: 5e4e356 mod README
HEAD is now at 5e4e356 mod README

[root@git Git]# 
[root@git Git]# 
[root@git Git]# git status    --状态已经变成没有可提交的动作
# On branch master
nothing to commit, working directory clean

[root@git Git]# ls      --文件已经回来
README

[root@git Git]# 

远程仓库GitHub

讲到远程仓库,那就有必要提下另外一个版本控制系统SVN,SVN就是集中式版本控制系统,且只就有集中式版本控制系统。但Git比SVN强大百倍,Git又可以实现集中式,分布式,本地仓库,是一个多元化版本控制系统 。后面还更多的Git杀手级的功能。

Git分布式版本控制系统,同一个Git仓库,可以分布到不同的机器上。最开始只有一台机器有一个原始版本库,此后,别的机器可以“克隆”这个原始版本库,而且每台机器的版本库其实都是一样的,并没有主次之分。
实际工作环境中,是找一台电脑充当服务器的角色,7 * 24小时开机,个人电脑都从这个“服务器”仓库克隆一份到自己的电脑上,并且各自把各自的提交推送到服务器仓库里,也从服务器仓库中拉取别人的提交。

当然这里不讲本地环境中的仓库,我们讲官方GitHub代码托管网站,这是一个免费代码托管服务,所以,只要注册一个GitHub账号,就可以免费获得Git远程仓库。
GitHub官方网站:https://github.com 大家自行注册。

GitHub注册成功后,我们还需要进行一些设置,创建ssh-key才可以将个人电脑中的内容提交到GitHub中来,GitHub为什么要ssh-key呢?因为GitHub需要识别出你推送提交确实是你推送的,而不是别人冒充的,而Git支持SSH协议,所以,GitHub只要知道了你的公钥,就可以确认只有你自己才能推送。

GitHub允许你添加多个Key。假定你有若干电脑,你一会儿在公司提交,一会儿在家里提交,只要把每台电脑的Key都添加到GitHub,就可以在每台电脑上往GitHub推送了。

友情提示下:在GitHub上免费托管的Git仓库,任何人都可以看到,且还可以评论,但只有你自己才可以修改,所以,敏感信息就要慎重了。

第一步:创建ssh-key

[root@git ~]# ssh-keygen -t rsa -C "409216159@qq.com"    --把邮箱换成你自己的邮箱,然后一直回车到结束
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa): 
Created directory '/root/.ssh'.
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:8XkbCTSVxBqZgs3rq7NtHVva+H2tf+HW5Rk4RwmVptk 409216159@qq.com
The key's randomart image is:
+---[RSA 2048]----+
|       +  o*o....|
|      . +.+.o. o |
|        .o.o  * .|
|        .o.o + E |
|       .S o + o  |
|        . ...= +.|
|         o B. +.B|
|      ..o = .. +*|
|      o=.  .. ++o|
+----[SHA256]-----+
[root@git ~]# 
[root@git ~]# ll ~/.ssh/     --查看家目录下的.ssh目录下有两个文件id_rsa私钥,不可泄露, id_rsa.pub公钥,对外提供的,可以放心提供给任何人。
total 8
-rw------- 1 root root 1679 Dec  8 08:05 id_rsa
-rw-r--r-- 1 root root  398 Dec  8 08:05 id_rsa.pub

[root@git ~]# cat ~/.ssh/id_rsa.pub   --查看公钥内容,复制,一会有用
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDt6OBCybnxDBlTIASrEkWWhdq3Eo4umeEJ6LWOdfUmGbu5EqmcB0mtxfRaiXMdB1DP4LFT7oNwGJwWSk5h/zGpXPzc8ONO698dvIJE/MU3buXF2uj28VWZBAMyyuuOQmgzIcZAntE2mCBjhIoE/UH+ourLHP+r1KJiuNVOTADo9MTSTVY9aq/0xm6tAXamtLV+cbOEKO66FYZnp0z0McgTbC9LSPakuUW8px1h4jRGi1dhWqfNJVNZonjfnSwWCbem+a313vVK9NU2cehOblv/KR1LVhhAIqi4EPnhV+ff2TVpxzI025NaHvr9a7pi04q6bLNFAEg4RHk8v2xJdWKp 409216159@qq.com

第二步:登陆GitHub网站:

点击头像 --> 点击settings ---> 点向SSH and GPG keys --->New SSH key ---> Title 随意填 ---> Key 就是上面的id_rsa.pub的内容复制上来 ---> Add SSH key
Git版本工具的部署与使用

添加完成后的样子:
Git版本工具的部署与使用

第三步:接下创建远程仓库
点击头像旁边的 + 号 ---> New repository ---> Repository name 处填写仓库名称(自己命名,我这写的是learngit) ---> 点击Create repository 创建仓库
Git版本工具的部署与使用

创建仓库完成的样子:
Git版本工具的部署与使用

GitHub上刚创建的仓库learngit仓库是空的,GitHub在界面上提示,可以从这个仓库克隆出新的仓库,也可以把一个已有的本地仓库与之关联,然后,把本地仓库的内容推送到GitHub仓库。

根据GitHub的提示,在本地的learngit仓库下运行以下两条命令:

第一条:git remote add origin https://github.com/liuyjishg/learngit.git
注意:把上面的liuyjishg替换成你自己的GitHub账户名,learngit替换为你自己在GitHub上创建的仓库名,否则,你在本地关联的就是我的远程库,关联没有问题,但是你以后推送是推不上去的,因为你的SSH Key公钥不在我的账户列表中。

[root@git myproject]# git remote add origin https://github.com/liuyjishg/learngit.git
[root@git myproject]#

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

第二条:git push -u origin master
把本地库的内容推送到远程GitHub仓库,用git push命令,实际上是把当前分支master推送到远程。

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

[root@git myproject]# git push -u origin master
Username for 'https://github.com': liuyjishg         ---要求你输入GitHub官方的帐号
Password for 'https://liuyjishg@github.com':       ---要求你输入GitHub官方帐号对应的密码
Counting objects: 11, done.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (11/11), 843 bytes | 0 bytes/s, done.
Total 11 (delta 0), reused 0 (delta 0)
To https://github.com/liuyjishg/learngit.git
 * [new branch]      master -> master
Branch master set up to track remote branch master from origin.

推送成功后,可以在GitHub页面中看到远程库的内容已经和本地是一样的:
Git版本工具的部署与使用

前面讲到的是在GitHub创建仓库,且把本地的仓库关联到GitHub中的公共仓库。

接下来讲从远程仓库中克隆到本地仓库中来

现在,假设我们从零开发,那么最好的方式是先创建远程库,然后从远程库克隆。
登陆GitHub,创建一个新的仓库,名字叫project,
勾选Initialize this repository with a README,
这样GitHub会自动为我们创建一个README.md文件。
Git版本工具的部署与使用

创建完毕后,可以看到README.md文件:
Git版本工具的部署与使用

接下来用命令git clone将GitHub的远程仓库克隆到本地来

[root@git ~]# git clone https://github.com/liuyjishg/project.git      
Cloning into 'project'...
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), done.
[root@git ~]# 
[root@git ~]# 
[root@git ~]# ls
anaconda-ks.cfg  project
[root@git ~]# cd project/
[root@git project]# ls
README.md
[root@git project]# 

注意把Git库的用户名liuyjishg换成你自己的,然后进入project目录看看,已经有README.md文件了。

Git的版本分支

Git的分支是与众不同的,无论创建、切换和删除分支,Git在1秒钟之内就能完成!无论你的版本库有多少文件。
Git里默认是有一个主分支叫master。

建议大家使用分支:
常用命令如下:
查看分支:git branch

创建分支:git branch <name>

切换分支:git checkout <name>

创建+切换分支:git checkout -b <name>

合并某分支到当前分支:git merge <name>

删除分支:git branch -d <name>


[root@git Git]# git checkout -b aa      --创建aa分支,然后切换到dev分支:
Switched to a new branch 'aa'

[root@git Git]# git branch                   --查看当前分支,*号代表当前所在的分支
* aa
  master
[root@git Git]# 

[root@git Git]# echo aa >> README    --往README文件里添加内容
[root@git Git]# 
[root@git Git]# 
[root@git Git]# git add .
[root@git Git]# git commit -m "aa mod README"
[aa e86a117] aa mod README
 1 file changed, 4 deletions(-)

[root@git Git]# 
[root@git Git]# git checkout master       --切换到主分支
Switched to branch 'master'

现在,我们把aa分支的工作内容合并到master主分支上:
[root@git Git]# git merge aa
Updating 936e9d0..70373b6
Fast-forward
 README | 1 +
 1 file changed, 1 insertion(+)

[root@git Git]# cat README     --再查看内容,此时aa分支上的内容已经合并到master主分支了
test
aa
test,test
aa

注意到上面的Fast-forward信息,Git告诉我们,这次合并是“快进模式”,也就是直接把master指向aa的当前提交,所以合并速度非常快。

当然,也不是每次合并都能Fast-forward,我们后面会讲其他方式的合并。

合并完成后,就可以放心地删除dev分支了:

[root@git Git]# git branch -d aa
Deleted branch aa (was 70373b6).

[root@git Git]# 
[root@git Git]# git branch 
* master

创建、合并和删除分支非常快,所以Git鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在master分支上工作效果是一样的,但过程更安全

合并时提示冲突
我们经常会遇到这种情况,主分支修改了配置文件,其它分支也修改配置文件,合并时会提示冲突。
示例:

[root@git Git]# git checkout -b feature1      --创建一个新的分支feature1
Switched to a new branch 'feature1'

往README文件里追加内容

[root@git Git]# echo "test simple" >> README
[root@git Git]# 
[root@git Git]# git add ./README
[root@git Git]# 
[root@git Git]# git commit -m "test simple"
[feature1 594482c] test simple
 1 file changed, 1 insertion(+)

切换到master主分支,并也往README文件里追加内容

[root@git Git]# git checkout master
Switched to branch 'master'
[root@git Git]# 
[root@git Git]# echo "aa simple" >> README 
[root@git Git]# 
[root@git Git]# git add ./README
[root@git Git]# 
[root@git Git]# git commit -m "aa simple"
[master 34db356] aa simple
 1 file changed, 1 insertion(+)

现在master主分支和feature1分支各自都分别有新的提交,变成了这样:
Git版本工具的部署与使用

这种情况,Git合并时会提示README文件冲突,文件内容也会有相应提示

[root@git Git]# git merge feature1      
Auto-merging README
CONFLICT (content): Merge conflict in README        --这里已经提示我们README文件合并时内容冲突
Automatic merge failed; fix conflicts and then commit the result.   

这里我们来看README文件

[root@git Git]# cat README 
test
aa
test,test
aa
<<<<<<< HEAD
aa simple
=======
test simple
>>>>>>> feature1

Git用<<<<<<<HEAD来表示是主分支修改的内容,=======表示分隔符,>>>>>>>表示是feature1分支修改的内容。

这种情况我们需要人工重新修正README文件,然后再提交才能解决冲突

[root@git Git]# vi README 
test
aa
test,test
aa
test simple

[root@git Git]# 
[root@git Git]# git add ./README
[root@git Git]# 
[root@git Git]# git commit -m "conflict fixed"
[master ecea0d4] conflict fixed       --提交过后,Git会提示你冲突解决

当冲突解决后,master主分支和feature1分支就变成了下面这样:
Git版本工具的部署与使用

我们可以用 git log 也可以看到分支的合并情况:

[root@git Git]# git log --graph --pretty=oneline --abbrev-commit
*   ecea0d4 conflict fixed
|\  
| * 594482c test simple
* | 34db356 aa simple
|/  
*   2452222 add test

合并成功,解决冲突后,就可以将feature1分支删除了

[root@git Git]# git branch 
  feature1
* master
[root@git Git]# git branch -d feature1
Deleted branch feature1 (was 594482c).
[root@git Git]# 
[root@git Git]# 
[root@git Git]# git branch 
* master
[root@git Git]# 

禁用Fast forward模式管理

合并分支时,Git默认使用用Fast forward模式,删除分支后,分支信息会永久删除。

如果要强制禁用Fast forward模式,Git就会在merge时生成一个新的commit,从分支历史上就可以看出分支信息。

示例:
创建并切换dev分支,往README文件里追加新的内容,并提交一个新的commit

[root@git Git]# git checkout -b dev
Switched to a new branch 'dev'
[root@git Git]# 
[root@git Git]# echo "test branch" >> README 
[root@git Git]# 
[root@git Git]# git add ./README
[root@git Git]# 
[root@git Git]# git commit -m "add merge"
[dev 93df64b] add merge
 1 file changed, 1 insertion(+)

现在,我们切回master主分支,合并dev分支,使用--no-ff 参数来禁用Fast forward模式

[root@git Git]# git checkout master
Switched to branch 'master'
[root@git Git]# 
[root@git Git]# git merge --no-ff -m "merge with no-ff" dev     --当禁用Fast forward模式时,合并就是一个新的commit,所以加上-m参数
Merge made by the 'recursive' strategy.
 README | 1 +
 1 file changed, 1 insertion(+)

合并后,我们用git log看看分支历史:

[root@git Git]# git log --graph --pretty=oneline --abbrev-commit 
*   7668b3d merge with no-ff
|\  
| * 93df64b add merge
|/  
*   ecea0d4 conflict fixed

可以看到,不使用Fast forward模式,merge后就像这样
Git版本工具的部署与使用

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

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

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

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

所以,团队合作的分支看起来就像这样:

Git版本工具的部署与使用

Git分支十分强大,在团队开发中应该充分应用。

合并分支时,加上--no-ff参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而fast forward合并就看不出来曾经做过合并

Bug临时分支
例如:你正在开发中,突然收到一个问题工单,说要解决主线上的一个BUG,这时我们就可以先保存当前正在写的代码,再使用Git创建一个临时的分支来修复这个BUG,然后再合并,恢复原来正在写的代码工作。

示例:修复一个代号bug-issue-50的bug的任务,这里我们创建一个分支bug-issue-50来修复它,但是,当前正在dev上进行的工作还没有提交,此时需要将当前的工作保存,然后才能修复:

先模拟正在工作的场景:

[root@git Git]# git branch 
* dev
  master

[root@git Git]# echo "测试临时分支" >> README 
[root@git Git]# echo "work Hello" > hello.py
[root@git Git]# git add ./*
[root@git Git]# git status
# On branch dev
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   README
#       new file:   hello.py
#
[root@git Git]# 

上面模拟正在工作的场景,我们修改了README文件,添加了一个新的hello.py文件,因未完成,还不能提交,所以只做了add。此时你收到问题工单必须马上解决Bug,怎么办?
这时Git的stash功能可以派上用场了,stash是把当前工作内容 "保存" 起来,等以后恢复现场后继续工作:

[root@git Git]# git stash       
Saved working directory and index state WIP on dev: f879521 分支管理
HEAD is now at f879521 分支管理

[root@git Git]# git stash list                               --使用git stash list 可以看到刚刚保存的工作区名称
stash@{0}: WIP on dev: f879521 分支管理       

[root@git Git]# git status                                   --
# On branch dev
nothing to commit, working directory clean