一、版本控制介绍
版本控制系统之前如何维护文档的版本
1 集中式版本控制
- 集中式版本控制,版本库是集中存放在中央服务器的,用的都是自己的电脑干活,所以要先从中央服务器取得最新的版本,然后再开始干活,干完活了,再把自己的活推送给中央服务器。需要联网。
- 有代表性的软件:cvs、 svn
2 分布式版本控制
- 分布式版本控制系统根本没有“中央服务器”,每个人的电脑上都是一个完整的版本库,这样,你工作的时候,就不需要联网了,因为版本库就在你自己的电脑上。为了多人协同开发时,同事之间交换版的方便,可以定一个中央服务器,只是用于多人之间的版本交换和比对。不需要联网
- 有代表性的软件: git
二 Git
1 简单介绍
很多人都知道,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
YUM 方式安装
yum install git
每个 Git 使用者的电脑上都应该安装 Git
源码方式安装
- 安装依赖包
yum install dh-autoreconf curl-devel expat-devel gettext-devel \
openssl-devel perl-devel zlib-devel asciidoc xmlto docbook2X
- 解压源码包
tar -zxf git-2.8.0.tar.gz
- 编译安装
cd git-2.8.0
make configure
./configure --prefix=/usr
make install
3 初始化版本库
什么是版本库呢?版本库又名仓库,英文名repository,可以简单理解成一个目录,这个目录里面的所有文件都可以被Git管理起来,每个文件的修改、删除,Git都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以“还原”。
- 首先,选择一个合适的地方,创建一个空目录
mkdir mycode
- 进入创建的空目录,把其初始化为 Git 的仓库
cd mycode/
git init # 初始化仓库的命令
- 查看版本库目录
可以看到在当前目录下会创建一个隐藏的文件夹 .git, 这个隐藏 的目录就是版本库(Repository)了。
轻易不用动它里面的任何文件,因为这个目录是 Git 来跟踪和管理版本库用的,假如你搞坏了,它就会给你点儿颜色看(Git 仓库就会被破坏)。
非空目录其实也是可以创建仓库的,不过不建议这么干。
4 版本库(Repository)基本操作
4.1 工作区、暂存区和 master 分支
Git 和 SVN 不同之一,就是有 工作区、暂存区的概念
* 工作区: 用来平时的开发、编辑文件之用,在你创建的仓库目录下,就是工作区
* 暂存区: 用来暂时存放准备提交到 **master** 分支的文档的地方,在 .git 目录下。
* master 分支: 真正用来存放和发布已经完成的代码文件的地方,在 .git 目录下。
4.2 三者的关系位置见下图
创建一个需要被管理的文件
echo "echo v1" > a.sh
最初文件在工作区,如下图:
将文件添加到暂存区
git add a.sh
文件被添加到暂存区,此时工作区的文件和暂存区的文件一致,如下图:
提交文件到 master 分支
默认情况下,初始化一个目录为 Git 仓库时,都会有一个 master 分支,这也是必须的。
master 分支才是代码的最终归宿,一般用来存放处于稳定状态的软件版本。
当然可以创建其他分支,后面会讲到。
提交
git commit -m "new file a.sh"
在第一次使用 Git 的时候,需要设置一下全局的 Git 用户和用户邮箱。
可以随便填写,如有需要,后期可以修改。
git config --global user.email "shark@sharkyun.com"
git config --global user.name "西瓜甜"
设置好后重新执行 commit 即可
提交后,在暂存区的所有文件和目录都将后被移动到 master 分支 。
而此时,工作区的文件会和当前 master 分支的文件一致,而暂存区没有任何文件。
小结
一个文件被提交到版本库的基本流程是:
- 在你的工作区创建编写你的代码文件 a.sh (当然也包括 非空目录)
- 用命令
git add a.sh
将文件 a.sh 放到暂存区,这个可以多次执行添加 - 用命令
git commint -m "new file a.sh"
将暂存区的所有文件和目录一起提交到 master 分支。
可以多次 add,最后执行一次 commit
[root@ansible mycode]# echo "hostname" >> a.sh
[root@ansible mycode]# git add .
[root@ansible mycode]# echo "date" >> a.sh
[root@ansible mycode]# git add .
[root@ansible mycode]# git commit -m "增加了 获取时间的命令" >> a.sh
5 Tag 标签
commit ID 是 16 进制的一串字符,不容易记忆。
标签就是给每次 commit ID 做一个容易记忆的标记。
5.1 查看 tag
git tag
[root@ansible mycode]# git tag
[root@ansible mycode]#
默认没有任何 tag
5.2 给当前的 commit id(版本) 打 tag
git tag 标签名称
git tag v1.0
- v1.0 就是 tag 的名字
实战:
[root@ansible mycode]# git tag v1.0
[root@ansible mycode]# git tag
v1.0
[root@ansible mycode]#
5.3 给其他 commit id 打 tag
git tag 标签名称 commit id
需要先查询出 commit id
git log --graph --oneline --decorate --all
实战:
[root@ansible mycode]# git log --oneline --decorate
427af40 (HEAD, tag: v1.0, master) 增加了 获取时间的命令
78afbcd new file a.sh
再给指定的 commit ID 打标签
git tag v2.0 78afbcd
实战:
[root@ansible mycode]# git tag v2.0 78afbcd
[root@ansible mycode]# git tag
v1.0
v2.0
[root@ansible mycode]#
5.4 删除标签
git tag -d 标签名
git tag -d v2.0
实战:
[root@ansible mycode]# git tag -d v2.0
已删除 tag 'v2.0'(曾为 78afbcd)
[root@ansible mycode]#
5.4 把最早提交的版本打标签为:v1.0
按照提交的时间先后顺序,打标签。
实战:
[root@ansible mycode]# git log --oneline --decorate --all
427af40 (HEAD, tag: v1.0, master) 增加了 获取时间的命令
78afbcd new file a.sh
[root@ansible mycode]# git tag -d v1.0
已删除 tag 'v1.0'(曾为 427af40)
[root@ansible mycode]# git tag v1.0 78afbcd
[root@ansible mycode]# git tag v2.0 427af40
[root@ansible mycode]# git log --oneline --decorate --all
427af40 (HEAD, tag: v2.0, master) 增加了 获取时间的命令
78afbcd (tag: v1.0) new file a.sh
[root@ansible mycode]#
6 版本回退
Git 支持在各个版本直接自由切换
语法:
git reset --hard 版本 ID
git reset --hard tag
查看版本 id
git log --graph --oneline --decorate --all
实战:
[root@ansible mycode]# git log --graph --oneline --decorate --all
* 427af40 (HEAD, tag: v2.0, master) 增加了 获取时间的命令
* 78afbcd (tag: v1.0) new file a.sh
HEAD 所在的版本,就是当前版本(当前示例是: 427af40)
当知道了 commit ID 后,我们就可以通过 commit ID 进行版本切换了。
回退到之前的某个版本
git reset --hard commit ID
或者
git reset --hard 标签名称
git reset --hard v1.0
实战:
切换前:
[root@ansible mycode]# cat a.sh
echo v1
hostname
date
切换后:
[root@ansible mycode]# git reset --hard v1.0
HEAD 现在位于 78afbcd new file a.sh
[root@ansible mycode]# cat a.sh
echo v1
恢复到未来的版本
还是先查看所有的版本
git log --graph --oneline --decorate --all
在切换到对应的版本
git reset v2.0
实战:
[root@ansible mycode]# git log --graph --oneline --decorate --all
* 427af40 (tag: v2.0) 增加了 获取时间的命令
* 78afbcd (HEAD, tag: v1.0, master) new file a.sh
[root@ansible mycode]# git reset --hard v2.0
HEAD 现在位于 427af40 增加了 获取时间的命令
[root@ansible mycode]# git log --graph --oneline --decorate --all
* 427af40 (HEAD, tag: v2.0, master) 增加了 获取时间的命令
* 78afbcd (tag: v1.0) new file a.sh
[root@ansible mycode]# cat a.sh
echo v1
hostname
date
7 分支
7.1 查看分支
git branch
实战:
[root@ansible mycode]# git branch
* master
7.2 创建分支
先创建再切换
git branch 分支名
创建
git checkout 分支名
切换
git branch dev
git checkout dev
切换和创建同步进行
git checkout -b 需要创建的分支名称
git checkout -b shark
7.3 合并分支
假设目前希望把 dev 分支的改变合并到 master 分支
# 先切换到 master 分支
git checkout master
# 再合并 dev 分支到 master 分支
git merge dev
实战:
确认目前所在分支是 dev
[root@ansible mycode]# git branch
* dev
master
shark
在 dev 分支创建一个文件 dev.txt
[root@ansible mycode]# ls
a.sh
[root@ansible mycode]# touch dev.txt
[root@ansible mycode]# ls
a.sh dev.txt
如下所示:可以看到,此时文件还在工作区,并没有提交,所以这个文件不会被 git 跟踪。
[root@ansible mycode]# git status
# 位于分支 dev
# 未跟踪的文件:
# (使用 "git add <file>..." 以包含要提交的内容)
#
# dev.txt
提交为空,但是存在尚未跟踪的文件(使用 "git add" 建立跟踪)
执行提交操作,把改变提交到版本库:
[root@ansible mycode]# git add .
[root@ansible mycode]# git commit -m "add dev.txt"
[dev a189d07] add dev.txt
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 dev.txt
接下来,我们准备合并分支到 master
先切换到 master
[root@ansible mycode]# git checkout master
切换到分支 'master'
可以看到,此时 master 分支并没有 dev.txt 文件
[root@ansible mycode]# ls
a.sh
执行合并的命令:
[root@ansible mycode]# git merge dev
更新 86ea2f4..a189d07
Fast-forward
dev.txt | 0
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 dev.txt
可以看到合并后,master 分支的工作区也有 dev.txt 文件了:
[root@ansible mycode]# ls
a.sh dev.txt
分支策略
在实际开发中,我们应该按照几个基本原则进行分支管理:
首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;
那在哪干活呢?干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;
你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。
所以,团队合作的分支看起来就像这样:
小结
Git分支十分强大,在团队开发中应该充分应用。
合并分支时,加上 --no-ff 参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而fast forward 合并就看不出来曾经做过合并。
Git 命令速查表
Git 流程图
精简内容
# 修改工作区文件
echo 3 > a.txt
# 添加工作区当前目录下所有文件的修改到 暂存区
git add .
# 提交暂存区的文件到 master 分支
git commit -m "add 3 to a.txt"
# 查看目前的提交版本状态
git log --graph --oneline --decorate --all
# 输出如下内容 HEAD 表示当前所在的版本
* e9817e5 (HEAD, master) add 3 to a.txt
* 1df67f6 add 2 to a.txt
* 15f0139 add a.txt
# 回退版本到 1df67f6
git reset --hard 1df67f6
# 验证版本是否切换
git log --graph --oneline --decorate --all
# 输出如下内容
* 1df67f6 (HEAD, master) add 2 to a.txt
* 15f0139 add a.txt
# 查看当前版本库的所有标签
git tag
# 给当前分支所在的提交点 打标签
git tag 标签名称
git tag 1.0
# 给历史提交点打标签
git tag 标签名称 commit id
git tag 2.0 23fe3456
# 利用 标签切换 版本号
git reset --hard 标签名称
git reset --hard 2.0
# 删除标签
git tag -d 标签名
下面的内容不用看
1.5 时空穿越
git 支持版本的回滚操作,并且,你可以在之前任何一个版本和当前最新有效提交后的版本之间来回切换。
1.5.1 提交修改
之前,我们知道了如何创建一个新的文件 readmi.txt ,并提交到仓库;现在让我们来修改这个文件,内容变为这样:
Git is a distributed version control system.
Git is free software.
并且再创建一个新的空文件 test.txt 和空文件夹 newdir
bash-3.2$ pwd
/Users/yanshunjun/Desktop/mygithub
bash-3.2$ vi study/readme.txt
bash-3.2$ touch test.txt;mkidr newdir
- 观察一下仓库的最新变化和状态
bash-3.2$ 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: study/readme.txt # 已经修改的文件
Untracked files: # 没有被添加过的文件
(use "git add <file>..." to include in what will be committed)
test.txt
no changes added to commit (use "git add" and/or "git commit -a")
空的目录是不会被 Git 跟踪的,也没这个必要!
- 可以看看被修改过的文件,到底修改了哪些内容
bash-3.2$ git diff study/readme.txt
diff --git a/study/readme.txt b/study/readme.txt
index 46d49bf..9247db6 100644
--- a/study/readme.txt
+++ b/study/readme.txt
@@ -1,2 +1,2 @@
-Git is a version control system. # 修改前的内容
+Git is a distributed version control system. # 修改后的内容
Git is free software.
(END)
- 之后一起放到暂存区
bash-3.2$ git add .
- . 会把当前目录下需要提交的所有文件和目录放到暂存区
提交前可以再次观察下仓库的状态
bash-3.2$ git status
On branch master
Changes to be committed: # 下面的文件将会被提交
(use "git reset HEAD <file>..." to unstage)
modified: study/readme.txt
new file: test.txt # 第一次被添加后的状态为 new file
- 提交到仓库
bash-3.2$ git commit -m "add distributed"
[master a34d237] add distributed
2 files changed, 1 insertion(+), 1 deletion(-)
create mode 100644 test.txt
- 再次观察仓库的状态
bash-3.2$ git status
On branch master
nothing to commit, working tree clean # 不需要提交,工作区是清洁的
1.5.2 回到过去(版本的回滚)
想象一种场景,你把你代码写好,提交到版本库,准备发布 v1.0。但此时,你的老板说要添加一个新的功能。于是你就在原来的代码文件的基础上修改添加,又再次提交到版本库。可老板这时候,又反悔了,说这个功能不能现在添加。那你是不是又要,再次修改呢?有了git 当然不用,此时你就需要版本回滚,滚到原来最初的版本。
让我们来模拟一下
- 首先把 readme.txt 文件的内容修改成下面的样子
Git is a distributed version control system.
Git is free software distributed under the GPL.
- 然后提交修改后的文件
bash-3.2$ vi study/readme.txt
bash-3.2$ 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: study/readme.txt
no changes added to commit (use "git add" and/or "git commit -a")
bash-3.2$ git add .
bash-3.2$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: study/readme.txt
bash-3.2$ git commit -m "add GLP for readme.txt"
[master da197f4] add GLP for readme.txt
1 file changed, 1 insertion(+), 1 deletion(-)
bash-3.2$ git status
On branch master
nothing to commit, working tree clean
- 验证最新提交后的版本
bash-3.2$ pwd
/Users/yanshunjun/Desktop/mygithub
bash-3.2$ cat study/readme.txt
Git is a distributed version control system.
Git is free software distributed under the GPL.
- 准备版本回退
在回退版本之前,我们先来回想一下 readme.txt 文件共修改了几次, 3 次对吧。但实际的开发中,有很多文件,一个文件里会有上千行代码,修改过的次数远远超出了你的智商能够承受的范围。怎么办? Git 早就为你想好了。
git log 命令可以查看提交的版本历史
commit da197f447342e65bbf37f5b2e609c71d52db7955 (HEAD -> master)
Author: sharkgit1976 <dockerhub@163.com>
Date: Sun Oct 15 10:04:23 2017 +0800
add GLP for readme.txt
commit a34d2370138d520d1fabc1aa2acc2d234047a39a
Author: sharkgit1976 <dockerhub@163.com>
Date: Sat Oct 14 17:31:19 2017 +0800
add distributed
commit 63e4ecd9409ff1679b8367c116a1c68f045af88d
Author: sharkgit1976 <dockerhub@163.com>
Date: Sat Oct 14 16:16:22 2017 +0800
crete a readme file
git log 常用参数**
某一个人的提交记录:
git log --author=name
一个压缩后的每一条提交记录只占一行的输出:
git log --pretty=oneline
或者你想通过 ASCII 艺术的树形结构来展示所有的分支, 每个分支都标示了他的名字和标签:
git log --graph --oneline --decorate --all
看看哪些文件改变了:
git log --name-status
更多的信息,参考:
git log --help
所以加上参数 --pretty=oneline 会显示的更简洁
da197f447342e65bbf37f5b2e609c71d52db7955 (HEAD -> master) add GLP for readme.txt
a34d2370138d520d1fabc1aa2acc2d234047a39a add distributed
63e4ecd9409ff1679b8367c116a1c68f045af88d crete a readme file
(END)
最前面的一串字符 da197f…7955 是commit id(版本号),是一个SHA1计算出来的一个非常大的数字,用十六进制表示。
每提交一个新版本,实际上Git就会把它们自动串成一条时间线。
- 开始版本回退
开回退之前,起码需要知道当前的版本。 在Git中,用HEAD表示当前版本和分支,从上面的信息中我们现在处于分支 master 的 da197f4…7955 版本。下面我用命令 git reset 回退版本到上次修改前的版本,也就是 a34d2370138…a39a 。
命令用法:
git reset --hard 版本号
版本号的表示形式:
1. 可以是十六进制的形式
2. 也可以是 Git 内部变量的形式。 上一个版本就是HEAD^,上上一个版本就是HEAD^^,100 个版本写成 HEAD~100
十六进制的版本号没必要写全,前几位就可以了,Git会自动去找。当然也不能只写前一两位,因为Git可能会找到多个版本号,就无法确定是哪一个了。
bash-3.2$ git reset --hard HEAD^
HEAD is now at a34d237 add distributed
- 验证版本
bash-3.2$ cat study/readme.txt
Git is a distributed version control system.
Git is free software.
- 回到未来
再看看版本库日志
a34d2370138d520d1fabc1aa2acc2d234047a39a (HEAD -> master) add distributed
63e4ecd9409ff1679b8367c116a1c68f045af88d crete a readme file
(END)
可以看到最近一次修改后提交的版本 add GLP for readme.txt 不见了,此时,我们是不是回不到未来了呢?
要想回到未来(最后一次提交的版本),就必须知道那个版本 ID,但是现在没有了,怎么办?
放心 Git 又给想好了, git reflog 命令会记录每一次导致版本变化的命令以及涉及到的版本号
bash-3.2$ git reflog
a34d237 (HEAD -> master) HEAD@{0}: reset: moving to HEAD^
da197f4 HEAD@{1}: commit: add GLP for readme.txt
a34d237 (HEAD -> master) HEAD@{2}: commit: add distributed
63e4ecd HEAD@{3}: commit (initial): crete a readme file
(END)
这样我们现在又可以回到未来了
da197f4 就是我们要回去的版本 ID
bash-3.2$ git reset --hard da197f4
HEAD is now at da197f4 add GLP for readme.txt
bash-3.2$ cat study/readme.txt
Git is a distributed version control system.
Git is free software distributed under the GPL.
bash-3.2$ git log --pretty=oneline
da197f447342e65bbf37f5b2e609c71d52db7955 (HEAD -> master) add GLP for readme.txt
a34d2370138d520d1fabc1aa2acc2d234047a39a add distributed
63e4ecd9409ff1679b8367c116a1c68f045af88d crete a readme file
就这样,在 Git 的帮助下,你滚来滚去…
1.5.3 HEAD 指针
用过其他版本控制的软件的同学,可能会感觉 Git 的版本切换很快,就是因为有指针在起作用。
HEAD 指向哪个版本,当前就是哪个版本;当你来回切换版本的时候,Git 只是把 HEAD 指向你要切换的版本,顺便把工作区的文件更新一下,见下图:
-
处于最新提交后的指针指向:
-
版本回退后的指针指向:
1.6 我的兄弟们(分支管理)
分支就像科幻电影里的平行宇宙,当你正在电脑前努力学习Git的时候,另一个你正在另一个平行宇宙里努力学习SVN。
如果两个平行宇宙互不干扰,那对现在的你也没啥影响。不过,在某个时间点,两个平行宇宙合并了,结果,你既学会了Git又学会了SVN!
分支在实际中有什么用呢?在实际的开发过程中,不是你一个人在开发,都是一个团队,假设你负责一个数据库操作模块的开发,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。如果等代码全部写完再一次提交,在这中间,代码会保存在你自己电脑上的工作区,存在丢失每天进度的巨大风险。
现在有了分支,就不用怕了。你创建了一个属于你自己的分支,别人看不到,还继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全,又不影响别人工作。
- Git的分支是与众不同的,无论创建、切换和删除分支,Git在1秒钟之内就能完成!无论你的版本库是1个文件还是1万个文件。
1.6.1 创建与合并分支
在版本回退里,你已经知道,每次提交,Git 都把它们串成一条时间线,这条时间线就是一个分支。截止到目前,只有一条时间线,在 Git 里,这个分支叫主分支,即master 分支。HEAD 严格来说不是指向提交,而是指向 master, master 才是指向提交的,所以,HEAD 指向的就是当前分支。
一开始的时候,master 分支是一条线,Git 用 master 指向最新的提交点,再用HEAD 指向 master,就能确定当前分支,以及当前分支的提交点:
每次提交,master 分支都会向前移动一步,这样,随着你不断提交,master分支的线也越来越长
当我们创建新的分支,例如 bac 时,Git 会新建一个指针叫 bac,指向 master 相同的提交点,再把HEAD指向 bac,就表示当前分支在 bac 上:
- Git创建一个分支很快,因为此时,只是增加一个 bac 指针,然后改改 HEAD 的指向即可,工作区的文件都没有任何变化!
从现在开始,对工作区的修改和提交就是针对 bac 分支了,比如新提交一次后,bac 指针往前移动一步,而master 指针不变,HEAD 指针同样不变:
假如我们在 bac 上的工作完成了,就可以把 bac 合并到 master 上。Git 怎么合并呢?很简单,先切换到 master 分支,此时 HEAD 指针就会指向 master 指针,之后就是直接把master 指向 bac 的当前提交点,就完成了合并:
- 所以Git合并分支也很快!就改改指针,工作区内容也不需要变!
合并完分支后,你觉得 bac 分支没什么用了,甚至可以删除 bac 分支。删除 bac 分支就是把 bac 指针给删掉,删掉后,我们就剩下了一条 master 分支:
实战
- 创建分支 bac
bash-3.2$ git branch bac
- 切换到分支 bac
bash-3.2$ git checkout bac
Switched to branch 'bac'
- 创建并切换分支
- 上面的两条命令可以合并为一条
bash-3.2$ git checkout -b bac
- 查看分支
bash-3.2$ git branch
* bac
master
- 星号代表当前所在的分支
- 在分支 bac 上修改文件,并创建一个新文件 bac_new.txt,最后正常添加、提交。
bash-3.2$ echo "changes on the branch of bac" >> study/readme.txt
bash-3.2$ touch bac_new.txt
bash-3.2$ git add .
bash-3.2$ git commit -m "added a new line in readme.txt,create a file bac_new.txt"
[bac 096a515] added a new line in readme.txt,create a file bac_new.txt
2 files changed, 1 insertion(+)
create mode 100644 bac_new.txt
bash-3.2$ tail -3 study/readme.txt
Git 管理的是修改
Git 管理的不是文件
changes on the branch of bac
bash-3.2$ ls
bac_new.txt newdir study
- 此时会被提交到 bac 分支,工作区当然也是属于 bac 分支的
- 切换到 master 分支,并观察文件的变化
bash-3.2$ git checkout master
Switched to branch 'master'
bash-3.2$ tail -3 study/readme.txt
Git is free software distributed under the GPL.
Git 管理的是修改
Git 管理的不是文件
bash-3.2$ ls
newdir study
- 切换到 master 分支后, HEAD 指针也就会指向 master 所指向的提交点,工作区也就属于 master,自然,你看不到在 bac 分支对文件做的任何修改
- 把分支 bac 合并到 master分支
bash-3.2$ git branch # 确定一下你现在所在的分支是 mster
bac
* master
bash-3.2$ git merge bac # 把 bac 分支合并到 master
Updating 6b0e1ca..096a515
Fast-forward
bac_new.txt | 0
study/readme.txt | 1 +
2 files changed, 1 insertion(+)
create mode 100644 bac_new.txt
bash-3.2$ ls # 确认工作区的文件
bac_new.txt newdir study
- 把 bac 分支合并到 master 分支后的文件变化:
- 合并完成后,删除分支 bac,并查看分支
bash-3.2$ git branch -d bac
Deleted branch bac (was 096a515).
bash-3.2$ git branch
* master
bash-3.2$
- 删除分支 bac 就变成下图的样子:
Git鼓励大量使用分支:
- 因为创建、合并和删除分支非常快,所以Git鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在master分支上工作效果是一样的,但过程更安全。
* 查看分支:git branch
* 创建分支:git branch <name>
* 切换分支:git checkout <name>
* 创建+切换分支:git checkout -b <name>
* 合并某分支到当前分支:git merge <name>
* 删除分支:git branch -d <name>
1.6.3 分支管理的策略
通常,合并分支时,如果可能,Git会用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息。
如果要强制禁用Fast forward模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。
下面我们实战一下–no-ff方式的git merge:
首先,仍然创建并切换dev分支:
$ git checkout -b dev
Switched to a new branch 'dev'
修改readme.txt文件,并提交一个新的commit:
$ git add readme.txt
$ git commit -m "add merge"
[dev 6224937] add merge
1 file changed, 1 insertion(+)
现在,我们切换回master:
$ git checkout master
Switched to branch 'master'
准备合并dev分支,请注意–no-ff参数,表示禁用Fast forward:
$ git merge --no-ff -m "merge with no-ff" dev
Merge made by the 'recursive' strategy.
readme.txt | 1 +
1 file changed, 1 insertion(+)
因为本次合并要创建一个新的commit,所以加上-m参数,把commit描述写进去。
合并后,我们用git log看看分支历史:
$ git log --graph --pretty=oneline --abbrev-commit
* 7825a50 merge with no-ff
|\
| * 6224937 add merge
|/
* 59bc1cb conflict fixed
...
可以看到,不使用Fast forward模式,merge后就像这样:
1.7 标签管理
发布一个版本时,我们通常先在版本库中打一个标签(tag),这样,就唯一确定了打标签时刻的版本。将来无论什么时候,取某个标签的版本,就是把那个打标签的时刻的历史版本取出来。所以,标签也是版本库的一个快照。
Git的标签虽然是版本库的快照,但其实它就是指向某个commit的指针(跟分支很像对不对?但是分支可以移动,标签不能移动),所以,创建和删除标签都是瞬间完成的。
Git有commit,为什么还要引入tag?
“请把上周一的那个版本打包发布,commit号是6a5819e…”
“一串乱七八糟的数字不好找!”
如果换一个办法:
“请把上周一的那个版本打包发布,版本号是v1.2”
“好的,按照tag v1.2查找commit就行!”
所以,tag就是一个让人容易记住的有意义的名字,它跟某个commit绑在一起。
1.7.1 创建标签
- 在Git中打标签非常简单,首先,切换到需要打标签的分支上
bash-3.2$ git checkout bac
Already on 'bac'
bash-3.2$ git branch
* bac
master
shark
yan
- 敲命令git tag 就可以打一个新标签
默认是把标签打到最新一次的 commit 上
bash-3.2$ git tag v1.0
v1.0 就是标签名
- 可以用命令git tag查看所有标签
bash-3.2$ git tag
v1.0
- 给历史 commit 打标签
先查看历史版本
bash-3.2$ git log --pretty=oneline --abbrev-commit
fa6419a (HEAD -> bac, tag: v1.0) Problem one has been repaired
096a515 added a new line in readme.txt,create a file bac_new.txt
6b0e1ca del file useless.txt
3dc63a6 added a file useless.txt
比方说要对 del file useless.txt 这次提交打标签,它对应的 commit id 是 6b0e1ca,敲入命令
bash-3.2$ git tag v0.8 6b0e1ca
bash-3.2$ git tag
v0.8
v1.0
注意,标签不是按时间顺序列出,而是按字母排序的
- 查看标签信息: git show
bash-3.2$ git show v0.8
commit 6b0e1cafc5a72ef8367e4c83e23558f8f00084c8 (tag: v0.8)
Author: sharkgit1976 <dockerhub@163.com>
Date: Mon Oct 16 14:32:44 2017 +0800
del file useless.txt
diff --git a/useless.txt b/useless.txt
deleted file mode 100644
index e69de29..0000000
(END)
- 通过-s用私钥签名一个标签
bash-3.2$ git tag -s v0.1 -m "signed version 0.1 released" 63e4ecd
fatal: cannot run gpg: No such file or directory
error: gpg failed to sign the data
error: unable to sign the tag
签名采用PGP签名,因此,必须首先安装gpg(GnuPG),如果没有找到gpg,或者没有gpg密钥对,就会报上面的错误信息。如果报错,请参考GnuPG帮助文档配置Key。
正常的话不会输出任何信息,你是知道的,Linux 就是这样。
- 查看 PGP 签名信息
git show
用PGP签名的标签是不可伪造的,因为可以验证PGP签名。验证签名的方法比较复杂,这里就不介绍了。
1.7.2 操作标签
- 删除本地标签
bash-3.2$ git tag -d v0.8
Deleted tag 'v0.8' (was 6b0e1ca)
默认标签只会存储在本地,不会被自动推送到远程。
关于远程仓库,我们会在下一小节介绍。
小结时刻
-
git tag 用于新建一个标签,默认为 HEAD,也可以指定一个 commit id;
-
git tag -a -m “blablabla…” 可以指定标签信息;
-
git tag -s -m “blablabla…” 可以用 PGP 签名标签;
-
git tag 可以查看标签信息及 PGP 签名信息(假如有)
-
git tag 可以查看所有标签。
-
git tag -d 删除标签