摘要
最近正在重新学习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上。
- jquery
- spring
- apache
作为程序员来说,如果我们不能很好的掌握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 '[email protected]'
通过上诉命令,我们就给当前仓库配置了用户名和用户邮箱。我们再回到config
文件中,
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[user]
name = test
email = [email protected]
可以清楚的看到config
文件中多了三行,我们着重来看最后三行
[user]
name = test
email = [email protected]
[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 ..." 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 ..." to unstage)
new file: 1.txt
此时反馈的内容与git add
之前相比,出现了一些变化:
- 目前依然处于主分支。
- 有了一些改变可以被提交。
- 你也可以使用
git reset HEAD
将文件从暂存区撤掉,重置为已修改的状态。
我们按照一条路走到底的思路,利用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
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
Date: Fri Dec 29 14:01:33 2017 +0800
secode commit
commit 48a1eb6b7262d68e28b93d51251c750801b76a24
Author: test
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
-
将已修改的文件恢复成上次提交后的内容,也就是把上一次提交后的修改丢弃
git checkout --
需要注意的是要将文件的修改丢弃,该文件必须被版本控制系统追踪过。
分支
以上,我们已经学习了很多命令,这些命令非常基础,需要我们信手拈来。要做到烂熟于心,只有去不断练习
,设计场景
。
之所以说基础,因为这些命令只需要死记后就能拿来使用,还没有涉及到太多原理性的内容,但是分支却是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
分支修改的内容的合并。