1 Git 使用
1.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了。
1.2 集中式和分布式
1.2.1 集中式版本控制
- 集中式版本控制,版本库是集中存放在中央服务器的,用的都是自己的电脑干活,所以要先从中央服务器取得最新的版本,然后再开始干活,干完活了,再把自己的活推送给中央服务器。需要联网。
1.2.2 分布式版本控制
- 分布式版本控制系统根本没有“中央服务器”,每个人的电脑上都是一个完整的版本库,这样,你工作的时候,就不需要联网了,因为版本库就在你自己的电脑上。为了多人协同开发时,同事之间交换版的方便,可以定一个中央服务器,只是用于多人之间的版本交换和比对。不需要联网
1.3 安装 Git
1.3.1 linux 安装
# yum install git
1.4 初始化版本库
什么是版本库呢?版本库又名仓库,英文名repository,可以简单理解成一个目录,这个目录里面的所有文件都可以被Git管理起来,每个文件的修改、删除,Git都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以“还原”。
1.4.1 创建版本库:
- 首先,选择一个合适的地方,创建一个空目录
$ git init
Initialized empty Git repository in /Users/yanshunjun/Desktop/mygithub/.git/
$ ls -a
. .. .git
可以看到在当前目录下会创建一个隐藏的文件夹 .git
轻易不用动它里面的任何文件,因为这个目录是 Git 来跟踪和管理版本库用的,假如你搞坏了,它就会给你点儿颜色看(Git 仓库就会被破坏)。
- 非空目录其实也是可以创建仓库的,不过不建议这么干。有必要吗! #### 再谈版本库(Repository)
.git 目录就是 Git 的版本库
Git的版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支 master,以及指向 master 的一个指针叫HEAD
- 分支和指针后面再讲
工作区、暂存区和 master 分支
Git 和 SVN 不同之一,就是有 工作区、暂存区的概念
* 工作区: 用来平时的开发、编辑文件之用,在你创建的仓库目录下,就是工作区
* 暂存区: 用来暂时存放准备提交到仓库的文档的地方,在 .git 目录下。
* master 分支: 真正用来存放和发布已经完成的代码文件的地方,在 .git 目录下。
三者的关系位置见下图
-
最初文件在工作区
image.png -
git add readme.txt 后,文件被添加到暂存区,此时工作区的文件和暂存区的文件一致。
image.png -
git commit -m "new file readme.txt" 后,在暂存区的所有文件和目录都将后被提交(移动)到分支 master。
而此时,工作区的文件会和当前分支 master 的文件一致,而暂存区没有任何文件,是清洁的。
总结: 一个文件被提交到版本库的基本流程是:
1. 在你的工作区创建编写你的代码文件 readme.txt (当然也包括目录)
2. 用命令 git add readme.txt 将文件 readme.txt 放到暂存区,这个可以多次执行添加
3. 用命令 git commint -m "new file readme.txt" 将暂存区的所有文件和目录一起提交到 master
可以多次添加,一次提交
实操:
$ pwd
/Users/yanshunjun/Desktop/mygithub
$ mkdir study
$ cd study
$ vi readme.txt
$ cd ..
$ git add study # 我这里是把目录一起提交了
$ git commit -m "crete a readme file"
[master (root-commit) 63e4ecd] crete a readme file
1 file changed, 2 insertions(+)
create mode 100644 study/readme.txt
- 强调一下, git commit 的 -m 参数后面跟的是关于这次提交版本的描述信息。 这个参数不是必须的,但是强烈要求这么干,这样便于以后自己对版本回滚操作,协同开发时,也便于其他人员可以获取到每次提交改动的基本信息
暂存区是Git非常重要的概念,弄明白了暂存区,就弄明白了Git的很多操作到底干了什么。
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 指向你要切换的版本,顺便把工作区的文件更新一下,见下图:
-
处于最新提交后的指针指向:
image.png -
版本回退后的指针指向:
image.png
1.5.4 深入理解 add 和 commit
为什么Git比其他版本控制系统设计得优秀,因为Git跟踪并管理的是修改,而非文件。
- Git 认为的修改是什么?比如:
1\. 你新增了一行,这就是一个修改;
2\. 删除了一行,也是一个修改;
3\. 更改了某些字符,也是一个修改;
4\. 删了一些又加了一些,也是一个修改;
5\. 甚至创建一个新文件,也算一个修改。
下面的实验操作可以很好的说明 Git 管理是修改而不是文件。
- 首先在 readme.txt 新增一行内容 "Git 管理的是修改"
bash-3.2$ echo "Git 管理的是修改" >> study/readme.txt
- 添加到暂存区
bash-3.2$ git add study/readme.txt
- 再次修改 readme.txt 文件,再添加一行新的内容 "Git 管理的不是文件"
bash-3.2$ echo "Git 管理的不是文件" >> study/readme.txt
- 执行 commit 提交
bash-3.2$ git commit -m "Append two lines of content"
- 查看版本库状态
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")
我的天呐!怎么第二次的修改没有被提交?
来回顾一下刚才的操作
* 第一次修改 -> git add -> 第二次修改 -> git commit
这恰恰体现了 Git 管理的是修改,当你用 git add 命令后,在工作区的第一次修改被放入暂存区,准备提交,但是,在工作区的第二次修改并没有放入暂存区,所以,git commit只负责把暂存区的修改提交了,也就是第一次的修改被提交了,第二次的修改不会被提交。
其实当你提交后,可以使用 git diff HEAD readme.txt 命令来查看工作区和版本库里面最新版本的区别
diff --git a/study/readme.txt b/study/readme.txt
index ea4f525..062fcc9 100644
--- a/study/readme.txt
+++ b/study/readme.txt
@@ -1,3 +1,4 @@
Git is a distributed version control system.
Git is free software distributed under the GPL.
Git 管理的是修改
+Git 管理的不是文件
(END)
- 输出中+号绿色显示的就是没有被提交到版本库的修改或新增的内容
正确的方式是:
* 第一次修改 -> git add -> 第二次修改 -> git add -> git commit
正确操作后,再比对 readme.txt 文件,看是否不同,结果返回是空的,证明工作区的文件和版本库里面最新版本的文件是一致的。
总结:再次强调一下, commit 提交的是暂存区的所有文件,而不是工作区的被修改的文件;每次修改,如果不add到暂存区,那就不会加入到commit中。
1.5.5 改变未来(撤销修改)
想像一种场景,你在工作区,对 readme.txt 文件添加了一行新的内容 : "这一行内容我不能添加",当你想要撤销掉这行内容时,你有两种方法:
- 一种是你记得,在这之前你记得你的所有修改。这里我是记得的,因为我只添加了一行内容而已,这样的情况下,你重新编辑这个文件,删除你不要的这一行即可。
- 另一种是,修改的太多了,一片混乱,使你无法继续进行下去。你只想会到没有修改之前,就是恢复到最近一次提交到 master 之后的状态。那就需要用
git checkout -- readme.txt
命令
git checkout -- readme.txt
意思就是,readme.txt
文件在被添加到暂存区之前,对在工作区对此文件的修改全部撤销
这里有两种情况:
- 一种是,在工作区对 readme.txt 进行修改之后,还没有被放到暂存区,暂存区为空;现在,撤销修改就回到和版本库一模一样的状态;
bash-3.2$ echo "这一行内容我不能添加" >> study/readme.txt
bash-3.2$ tail -2 study/readme.txt
Git 管理的不是文件
这一行内容我不能添加
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 checkout -- study/readme.txt
bash-3.2$ git status
On branch master
nothing to commit, working tree clean
bash-3.2$ tail -2 study/readme.txt
Git 管理的是修改
Git 管理的不是文件
-
一种是,在工作区对 readme.txt 进行修改之后,并且已经添加到暂存区了,接着又在工作区对文件作了修改。此时,现在 readme.txt 的状态是:
- 在工作区是一种最新修改后的状态
- 在暂存区是另一种 add 后的状态
- 在 master 是版本库最新的状态
此时,撤销修改,工作区的文件就会和暂存区文件的状态保持一致,master 的文件状态不变。
bash-3.2$ echo "这一行内容我不能添加,但是我把这个文件添加到了暂存区" >>study/readme.txt
bash-3.2$ tail -2 study/readme.txt # 第一次修改后的文件内容
Git 管理的不是文件
这一行内容我不能添加,但是我把这个文件添加到了暂存区
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 study/readme.txt # 添加到暂存区
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$ echo "这一行内这个文件添加到了暂存区,我又添加了此行" >> study/readme.txt
bash-3.2$ tail -2 study/readme.txt # 第二次修改文件后的内容
这一行内容我不能添加,但是我把这个文件添加到了暂存区
这一行内这个文件添加到了暂存区,我又添加了此行
bash-3.2$ git diff study/readme.txt # diff 一下,可以看到工作区和暂存区的文件的不一致
diff --git a/study/readme.txt b/study/readme.txt
index d348259..f303166 100644
--- a/study/readme.txt
+++ b/study/readme.txt
@@ -3,3 +3,4 @@ Git is free software distributed under the GPL.
Git 管理的是修改
Git 管理的不是文件
这一行内容我不能添加,但是我把这个文件添加到了暂存区
+这一行内这个文件添加到了暂存区,我又添加了此行
(END)
- 观察版本库的状态
bash-3.2$ git status study/readme.txt
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: study/readme.txt
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
- 撤销修改,使工作区和暂存区文件的一致
bash-3.2$ git checkout -- study/readme.txt
- 对比一下工作区和暂存区的文件,结果是一致的
bash-3.2$ git diff study/readme.txt
(END)
- 再次观察版本库的状态
bash-3.2$ git status study/readme.txt
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: study/readme.txt
- 验证文件内容
bash-3.2$ tail -2 study/readme.txt
Git 管理的不是文件
这一行内容我不能添加,但是我把这个文件添加到了暂存区
注意:git checkout -- file命令中的--很重要,没有--,就变成了“切换到另一个分支”的命令,我们在后面的分支管理中会再次遇到git checkout命令。
- 此时,你可能会想再执行一次 git checkout -- study/readme.txt,让其回到和版本库一致。
bash-3.2$ git checkout -- study/readme.txt
bash-3.2$ tail -2 study/readme.txt
Git 管理的不是文件
这一行内容我不能添加,但是我把这个文件添加到了暂存区
bash-3.2$ git status study/readme.txt
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: study/readme.txt
那你就错了,说明你还没理解上面黄色的话,再复习一遍:
命令 git checkout -- readme.txt 意思就是,readme.txt文件在被添加到暂存区之前,对 在工作区对此文件的修改全部撤销
意思就是说只能对在工作区的修改进行全部撤销,上面的例子是,第一次修改后,你已经把修改添加到了暂存区, Git 就认为目前工作区的和暂存区的一致,工作区的状态被相对的看做为起始状态,没有什么要撤销的,只剩 commit 了。那么真的没有办法了吗?答案是:当然有办法,请继续看...
此时,想继续恢复到和版本库的一致可以用下面发方法:
你还记得怎么滚吗?
- 先看一下目前版本库的状态
bash-3.2$ git status study/readme.txt
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage) # 这里 Git 已经有了提示,清除暂存区的状态
modified: study/readme.txt
- 回到最新的状态
bash-3.2$ git reset HEAD study/readme.txt
Unstaged changes after reset:
M study/readme.txt
- 此时再看一下状态,暂存区是清洁的,工作区有做了修改
bash-3.2$ git status study/readme.txt
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$ tail -2 study/readme.txt
Git 管理的不是文件
这一行内容我不能添加,但是我把这个文件添加到了暂存区
bash-3.2$ git checkout -- study/readme.txt
bash-3.2$ git status
On branch master
nothing to commit, working tree clean
bash-3.2$
此时整个世界都清净了!
- 客官请留步
现在,假设你不但改错了,还从暂存区提交到了版本库,怎么办呢?还记得版本回退吗?可以回退到上一个版本。不过,这是有条件的,就是你还没有把自己的本地版本库推送到远程。还记得Git是分布式版本控制系统吗?我们后面会讲到远程版本库,一旦你把错误修改的代码提交推送到远程版本库,你就真的惨了……
思路梳理
-
当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令git checkout -- file。
-
当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步,第一步用命令git reset HEAD file,就回到了 1,第二步按 1 操作。
-
已经提交了不合适的修改到版本库时,想要撤销本次提交,参考版本回退一节,不过前提是没有推送到远程库。
1.5.6 自毁前程(删除)
在Git中,删除也是一个修改操作
分两种情况,一种是,在工作区删除的文件,已经被添加到了暂存区,但是没有提交。
当你在工作区删除一个你认为没用的文件时,但是这个文件被已经添加了暂存区,这样 Git 会知道你删除了这个文件,因为此时,工作区和版本库就不一致了,git status命令会立刻告诉你哪些文件被删除了
bash-3.2$ touch useless.txt
bash-3.2$ git add useless.txt
bash-3.2$ rm useless.txt
remove useless.txt? y
bash-3.2$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: useless.txt
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: useless.txt
- 此时,你有两种选择:
- 真的要删除这个文件
可以用 git rm 删除在暂存区的文件
bash-3.2$ git rm useless.txt
rm 'useless.txt'
bash-3.2$ git status
On branch master
nothing to commit, working tree clean
- 删错了,需要把文件恢复到工作区
还记 git checkout -- file 吗?
bash-3.2$ git checkout -- useless.txt
bash-3.2$ ls useless.txt
useless.txt
bash-3.2$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: useless.txt
<mark style="box-sizing: border-box;">git checkout -- file 不单可以撤销工作区的修改,也可以撤销工作区的删除,因为之前提到过,删除在 Git 看了也是修改。</mark>
另一种是,在工作区删除的文件,添加到了暂存区,并且别提交了。
- 此时,你也有两种选择:
- 真的要删除这个文件
bash-3.2$ rm useless.txt
remove useless.txt? y
bash-3.2$ git status
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: useless.txt
no changes added to commit (use "git add" and/or "git commit -a")
从版本库删除这个文件
bash-3.2$ git rm useless.txt
rm 'useless.txt'
并且提交
bash-3.2$ git commit -m "del file useless.txt"
[master 6b0e1ca] del file useless.txt
1 file changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 useless.txt
bash-3.2$ git status
On branch master
nothing to commit, working tree clean
此时,文件就从版本库中被删除了,一般情况下它再也会不到你身边了
- 删错了,需要把文件恢复到工作区
bash-3.2$ ls useless.txt
ls: useless.txt: No such file or directory
bash-3.2$ git checkout -- useless.txt
bash-3.2$ ls useless.txt
useless.txt
bash-3.2$ git status
On branch master
nothing to commit, working tree clean
总结
git checkout其实是用<mark style="box-sizing: border-box;">版本库</mark>里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。
版本库: 包括 暂存区 和 分支
1.6 我的兄弟们(分支管理)
分支就像科幻电影里的平行宇宙,当你正在电脑前努力学习Git的时候,另一个你正在另一个平行宇宙里努力学习SVN。
如果两个平行宇宙互不干扰,那对现在的你也没啥影响。不过,在某个时间点,两个平行宇宙合并了,结果,你既学会了Git又学会了SVN!
image.png分支在实际中有什么用呢?在实际的开发过程中,不是你一个人在可发,都是一个团队,假设你负责一个数据库操作模块的开发,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。如果等代码全部写完再一次提交,在这中间,代码会保存在你自己电脑上的工作区,存在丢失每天进度的巨大风险。
现在有了分支,就不用怕了。你创建了一个属于你自己的分支,别人看不到,还继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全,又不影响别人工作。
- Git的分支是与众不同的,无论创建、切换和删除分支,Git在1秒钟之内就能完成!无论你的版本库是1个文件还是1万个文件。
1.6.1 创建与合并分支
在版本回退里,你已经知道,每次提交,Git 都把它们串成一条时间线,这条时间线就是一个分支。截止到目前,只有一条时间线,在 Git 里,这个分支叫主分支,即master 分支。HEAD 严格来说不是指向提交,而是指向 master, master 才是指向提交的,所以,HEAD 指向的就是当前分支。
一开始的时候,master 分支是一条线,Git 用 master 指向最新的提交点,再用HEAD 指向 master,就能确定当前分支,以及当前分支的提交点:
image.png每次提交,master 分支都会向前移动一步,这样,随着你不断提交,master分支的线也越来越长
当我们创建新的分支,例如 bac 时,Git 会新建一个指针叫 bac,指向 master 相同的提交点,再把HEAD指向 bac,就表示当前分支在 bac 上:
image.png- Git创建一个分支很快,因为此时,只是增加一个 bac 指针,然后改改 HEAD 的指向即可,工作区的文件都没有任何变化!
从现在开始,对工作区的修改和提交就是针对 bac 分支了,比如新提交一次后,bac 指针往前移动一步,而master 指针不变,HEAD 指针同样不变:
image.png假如我们在dev上的工作完成了,就可以把 bac 合并到 master 上。Git 怎么合并呢?很简单,先切换到 master 分支,此时 HEAD 指针就会指向 master 指针,之后就是直接把master 指向 bac 的当前提交点,就完成了合并:
image.png- 所以Git合并分支也很快!就改改指针,工作区内容也不需要变!
合并完分支后,你觉得 bac 分支没什么用了,甚至可以删除 bac 分支。删除 bac 分支就是把 bac 指针给删掉,删掉后,我们就剩下了一条 master 分支:
image.png实战
- 创建分支 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>
网友评论