摘要
最近正在重新学习Git版本控制系统,以前对Git的了解不太深入,这次基于对张龙老师的Git实战视频课程的学习,对Git又有了进一步的认识。对于Git的深入学习过程,将通过多篇文章来记录。也分享给正在学习的同学。
Git is a free and open source distributed version control system designed to handle everything from small to very large projects with speed and efficiency.
这句话来自于Git的官网,大致意思就是说Git是一个开源免费的分布式的版本控制系统,它可以帮助我们处理不论是小项目还是大项目。最重要的是Git的速度和效率非常优秀。
从官网的介绍我们还可以看到全球知名的互联网公司都在使用Git管理项目,特别是GitHub上,众多的全世界的顶级项目都托管在GitHub上。
作为程序员来说,如果我们不能很好的掌握Git的操作和使用,我们就不能很好的利用开源世界给我们的帮助,做一些重复造轮子的工作,更何况GitHub上的开源项目都经历了实际生产中的考验。拥有全世界的开发者的pull request
。就像张龙老师说的那样。如果你还不会使用Git,那么你就不要写代码了!
其实说了这么多,大多数程序员对Git还是有所了解,日常工作中也会使用sourcetree
等图形化工具来协助代码的拉取和推送。知道一些常用的命令:
git init
git add
git commit
git pull
git push
git branch
但是,对Git不了解的同学只是在使用命令,如果一旦出现问题,并不知道如何去解决。所以这也是我们为什么要深入去学习Git原理的部分,让Git真正成为工作的利器
,而不是畔脚石
。
从现在开始,我将从最基础的命令介绍,一步步触及Git的本质。
创建仓库
在Git中创建一个版本库使用命令
git init
在终端中输入如下命令
mkdir mygit
cd mygit
git init
initialized empty Git repository in /Users/liuhao/Desktop/mygit/.git/
上述命令表示创建一个名为mygit
文件夹并进入,使用命令git init
,这样这个普通的文件夹就被Git来管理了。从Git的反馈来理解也是这个意思,初始化了一个空了Git仓库指向了我们刚才创建的mygit
文件夹。
从提示来看,Git还帮我们创建了一个名为.git
隐藏文件夹。从感觉上讲,我们会很自然的想到这个.git
文件夹就是Git管理的当前这个仓库的版本信息。既然Git告诉了我们有这个文件夹的存在,那么我们索性进去看看到底有些啥东西。
cd .git
ls -al
total 24
drwxr-xr-x 10 liuhao staff 340 12 29 11:25 .
drwxr-xr-x 3 liuhao staff 102 12 29 11:17 ..
-rw-r--r-- 1 liuhao staff 23 12 29 11:17 HEAD
drwxr-xr-x 2 liuhao staff 68 12 29 11:17 branches
-rw-r--r-- 1 liuhao staff 137 12 29 11:17 config
-rw-r--r-- 1 liuhao staff 73 12 29 11:17 description
drwxr-xr-x 11 liuhao staff 374 12 29 11:17 hooks
drwxr-xr-x 3 liuhao staff 102 12 29 11:17 info
drwxr-xr-x 4 liuhao staff 136 12 29 11:17 objects
drwxr-xr-x 4 liuhao staff 136 12 29 11:17 refs
这么多,我们先搂两个瞧瞧,其他的目前还不需要了解。
cat HEAD
ref: refs/heads/master
cat config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
我们可以看到,HEAD文件里面保存的是一个字符串ref: refs/heads/master
对Git稍有了解的同学都知道,在Git中master
指的是主分支
,在Git中,当我们创建版本库之后提交文件至版本控制系统,就天生活动在master
分支,所以HEAD里面的内容也就不难理解。它指的就是当前所处的分支
。
对于config
文件,内容是一些参数键值对,也就是说这些参数开用户是可以配置的,我们暂且不去管这些参数,我们来学习Git中第二条命令,在终端中执行如下命令:
git config --local user.name 'test'
git config --local user.email 'test@test.com'
通过上诉命令,我们就给当前仓库配置了用户名和用户邮箱。我们再回到config
文件中,
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[user]
name = test
email = test@test.com
可以清楚的看到config
文件中多了三行,我们着重来看最后三行
[user]
name = test
email = test@test.com
[user]
表示的是用户相关的配置
name
表示的是当前使用这个仓库的人,一般在开发中我们写上自己的姓名。
email
表示的是这样仓库的人的邮箱,一般在工作项目中,写上自己的企业邮箱。
配置这两个参数的原因我们等会介绍,至少我们现在可以知道这个仓库属于我们自己。
文件状态
说了这么多,现在我们的mygit
文件夹中除了.git
自动生成的文件。还没有其他文件,我们赶快自己创建一个文件来实验吧,毕竟我们都知道Git给我们最直观的体验,就是用来管理文件的
。
cd mygit
echo 'hello 1.txt' > 1.txt
cat 1.txt
hello 1.txt
我们在mygit
中创建了一个1.txt
并查看其内容为hello 1.txt
,至此我们完成了文件的创建,那么我们如何把1.txt
提交到版本库中呢?说道这里,我们就要介绍Git中最为重要的三个概念
了,它们分别是:
工作区
暂存区
版本库
它们又分别对应三种状态:
已修改
已暂存
已提交
以上三种状态可以用来描述文件的生命周期。当我们新增或修改文件之后,这个文件就处于已修改的状态,你可以选择将其添加到暂存区,使其文件变成已暂存的状态,需要特别注意的是,此时虽然处于暂存区,但是还没有被纳入版本库,要想把文件纳入版本库,必须在暂存区的基础上将其提交到版本库,此时,文件才属于已提交状态。获取文件的状态,我们可以使用git status
命令,下面我们来演示这个过程。
git status
On branch master
Untracked files:
(use "git add <file>..." to include in what will be committed)
1.txt
nothing added to commit but untracked files present (use "git add" to track)
git add 1.txt
当我们输入git status
命令之后,Git告诉我们了如下信息:
- 当前处于主分支。
- 还没有被版本控制系统追踪的文件 1.txt。
- 提示我们可以使用
git add
来暂存1.txt。 - 当前暂存区没有文件,所以没有可以被提交的文件。
- 现在出现了一个文件可以被暂存,以致被提交。
此时输入git add
命令,Git并没有反馈,如果没有提示,就说明操作成功,这点Linux的行为。此时我们再次输入git status
:
git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: 1.txt
此时反馈的内容与git add
之前相比,出现了一些变化:
- 目前依然处于主分支。
- 有了一些改变可以被提交。
- 你也可以使用
git reset HEAD <file>
将文件从暂存区撤掉,重置为已修改的状态。
我们按照一条路走到底的思路,利用git commit
命令将其提交。
git commit -m 'init 1.txt'
master 48a1eb6] 1.txt
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 1.txt
提交文件,使用git commit
命令,-m
参数为message
,填写此次提交信息,需要注意的是,Git推荐也是强制要求我们必须填写message,并且是有用有意义的内容。
我们再来摘取提交之后比较重要信息:
master 48a1eb6] 1.txt
其中的一串数字表示的是commit id
,为了区分每一次不同的提交,Git会给我们生产一个不唯一的id。这个commit id
非常有用,我们在版本回退
的时候会使用它。
此时,我们就走通了一个文件的生命周期的流程。我们使用如下命令来查看当前场景:
git status
On branch master
nothing to commit, working tree clean
git log
commit 48a1eb6b7262d68e28b93d51251c750801b76a24
Author: test <test@test.com>
Date: Fri Dec 29 13:18:49 2017 +0800
1.txt
此时,Git首先告诉我们没有可以被提交的内容,也就是说暂存区没有内容,工作区也是干净的,也就是说当前没有修改任何内容。
再往下,我们来认识一个常用的命令git log
,它会告诉我们历史提交记录,这里我们看见了比较熟悉的内容,那就是commid id
,author
,email
。这里的commit id
是完整的,我们之前看到的是它的前面几位。需要了解的是,commit id
非常不唯一,只需要前面几位就能确定是哪一次提交。其它两个信息是我们自己使用git config
命令设置的,它会在git log
中记录下来。这时我们就大概能体会到之前设置user
的好处了。他告诉了Git是谁在提交。
版本回滚
我们知道在Git中,总是有后悔药可以吃的
。版本回滚就是一个方面,使用版本回滚,我们可以回到之前的任意一次提交时的状态。
此时我们做如下修改:
vi 1.txt
add 'text'
cat 1.txt
hello 1.txt
text
git commit -a -f 'second commit'
此时我们重新修改了文件并做了一次提交,文件变成了两行内容。需要注意的是,这里使用了添加和提交合一的命令:
git commit -a -m
使用这条简短命令的前提是,待提交的文件已经被版本控制系统追踪过。
这时我们查看这次提交:
git log
commit 055c5e79b408721a0129d515baa6dd6ad9f8fe27
Author: test <test@test.com>
Date: Fri Dec 29 14:01:33 2017 +0800
secode commit
commit 48a1eb6b7262d68e28b93d51251c750801b76a24
Author: test <test@test.com>
Date: Fri Dec 29 13:18:49 2017 +0800
1.txt
此时如果我们对当前版本修改的内容不满意,就可以切换到之前的版本,使用命令:
git reset --hard HEAD~n
git reset --hard HEAD^
git reset --hard 48a1e
其中后面两种比较常用。但是推荐使用最后一种方式,给定commit id
的一部分,就可以任意的切换到对应的分支。
第一种是将n换成哪一次提交
,第二种一个^代表往前推一个版本
,在实际使用中,都不太友好。
需要注意的是,有时候我们希望又切回最新的版本,使用git log
又看不到较新版本的commit id
,可以使用git reflog
命令。
git reflog
055c5e7 HEAD@{0}: reset: moving to 055c
4f01813 HEAD@{1}: reset: moving to HEAD~4
055c5e7 HEAD@{2}: commit: secode commit
f52fa35 HEAD@{3}: commit: add
48a1eb6 HEAD@{4}: commit: 1.txt
d2cf225 HEAD@{5}: commit: del
4f01813 HEAD@{6}: commit (initial): init commit
这样会得到每一次版本的commit id,无论当前处于哪个版本。
后悔药
介绍两条可以用来恢复操作的命令
-
将暂存区中的文件回退到已修改状态
git rm --cached <file>
-
将已修改的文件恢复成上次提交后的内容,也就是把上一次提交后的修改丢弃
git checkout -- <file>
需要注意的是要将文件的修改丢弃,该文件必须被版本控制系统追踪过。
分支
以上,我们已经学习了很多命令,这些命令非常基础,需要我们信手拈来。要做到烂熟于心,只有去不断练习
,设计场景
。
之所以说基础,因为这些命令只需要死记后就能拿来使用,还没有涉及到太多原理性的内容,但是分支却是Git中最重要且核心的概念
,其重要性怎么强调也不为过,正是因为有了分支,我们才有了工程化中最佳Git实践。这一切,都来源于分支的支持。
分支中的命令
介绍分支中的命令之前,先将分支涉及的命令做一下概括。
- git branch
- git branch *
- git checkout *
- git checkout -b *
- git merge *
- git merge -d *
这些命令涉及到
- 查看分支
- 创建分支
- 切换分支
- 创建并进入该分支
- 合并分支
- 删除分支
介绍分支,我们重新创建一个仓库来实验,终端中输入如下命令:
mkdir mygit2
touch 1.txt
git add .
git commit -m 'first commit'
git branch
* master
git branch new
git branch
* master
new
git checkout new
master
* new
在介绍以上命令含义时,我们需要明确master分支到底是什么时候被创建的?
master
分支的创建时机是当我们在创建版本库之后,完成一次完整的add
,commit
操作之后才创建的,并不是版本库一创建就天生存在。也就是说,master分支的创建其实也是懒加载。
明白了上述这个问题,我们就很清晰理解我们为什么完成一次提交后,再创建其他分支。
这样,上诉代码的含义就清晰可见了
git add
git commit
// 此时master被创建
git branch
* master
//此时有且只有一个分支,并且处于master分支
git branch new
* master
new
//创建了一个分支名称为new,目前依然处于master分支
git checkout new
master
* new
//切换到new分支
从以上注释也能理解命令的含义。
分支的合并操作
命令 git merge
分支的合并操作决定了多分支的作用。我们在开发中,除了master分支以外,还会自定义创建其他分支,比如开发新功能的分支,解决Bug的分支,测试分支,线上发布分支等等。正因为有分支合并,我们可以把主分支的代码轻松copy
一份,也可以在其他分支进行修改后把差异部分合并到master
分支。
ls -al
1.txt
可以发现,在new
分支上也有了1.txt
,这是因为我们创建new
分支时,就是基于master
分支,也就拥有了和master
分支一样的版本库。这时我们增加一行内容:
vi 1.txt
'hello'
git add .
git commit
cat 1.txt
hello
修改文件并提交到new
分支的仓库
git checkout master
cat 1.txt
empty
git merge new
Updating c6fdc17..c542424
Fast-forward
1.txt | 1 +
1 file changed, 1 insertion(+)
cat 1.txt
hello
特别需要注意的是,这里出现了一个概念Fast-forward
。中文翻译叫快速前进,这个概念比较重要,它的背后隐藏着分支合并的原理,这一部分,内容比较多。下次再进行讲解。
至此我们就完成对new
分支修改的内容的合并。