Git是一款开源的分布式版本控制系统,它的出现和Linux紧密相关。Linux内核项目组为了能更好地管理和维护Linux内核开发,于2002年开始启用商业的分布式版本控制系统BitKeeper。虽然软件开发商授权了Linux社区能免费使用,但是好景不长,到了2005年,BitKeeper的开发商由于某些原因终止了与Linux社区的合作关系。于是Linux的作者Linus Torvalds就决定开发一款能替代BitKeeper的分布式版本控制系统(即Git),在花费十天的时间后发布了Git的第一个版本。

一、版本控制系统

  版本控制系统(Version Control System,VCS)能管理文件内容的变更记录,即可追踪文件的修订历史,确保不同的人在编辑同一文件时能保持同步。该系统不仅能应用于保存源码的文本文件,还能对图像、Word文档等各种类型的文件进行版本控制。有了版本控制系统之后,就能很方便的回退文件到某个状态、比较文件变更前后的区别、查询到修改文件的人等。目前市面上的版本控制系统大致可分为两种:集中式和分布式,下面会对它们做单独的讲解。

1)集中式

  当需要多人协同工作时,就得让集中式版本控制系统(Centralized Version Control Systems,CVCS)登场了。

  这类系统包括CVS、Subversion等都是在一台中央服务器中建立一个仓库,专门用来管理文件的修订版本,客户端可通过网络连接到这台服务器,取出最新文件或提交变更,如图3所示。

图3  集中式版本控制系统

  虽然使用CVCS带来了诸多便利,但是其缺点也很明显,例如中央服务器一旦宕机,那么客户端将无法提交变更和协同工作;或者中央服务器磁盘故障,没有备份仓库,那么将丢失所有的变更记录。

2)分布式

  为了解决CVCS所带来的问题,又有人提出了分布式版本控制系统(Distributed Version Control System,DVCS)。

  这类系统包括Git、BitKeeper等都会将服务器中的仓库完整的克隆到本地,这么一来,即使网络断开,也能继续工作,并且受服务器故障的影响也能降低到最小。在图4中,服务器和两台电脑三者之间都能相互推送各自的修订记录,并且每台电脑上都保存了一个本地仓库。

图4  分布式版本控制系统

  由此可知,DVCS剥夺了服务器的生杀大权,只让它负责中转大家的修订记录,即使缺了服务器,也不影响协同工作。

二、快照

  Git不会像CVS、Subversion等版本控制系统那样存储每个文件所变更的内容以及修订文件版本之间的关系。Git更像是一个文件系统,它存储的是文件快照(Snapshot)。

  Git会先将那些变更的文件拷贝一份,然后把备份文件转换成Blob对象,并对其进行压缩,再把文件各自的内容通过SHA-1哈希运算出对应Blob的名称(即版本号),如下所示,最后由这些哈希值作为索引组成一个快照(即版本信息),而通过快照就能反推出该版本中所有发生变更的文件内容。

fbcceef922ce47253804cf00c72c2e955b8bc1b3

  每次提交都会生成一个新的快照,而对于未更改的文件,则直接链接到上一个版本。

三、工作区域

  Git的工作区域包含三部分:工作目录、暂存区和仓库,它们的说明如下所列:

  (1)工作目录(Working Directory)就是去除项目版本信息后的目录,即磁盘上实际操作的目录。

  (2)暂存区(Stage)也叫索引区(Index),是一个记录了变更信息的文件,即保存着变更文件的当前快照,为提交到仓库中做准备。

  (3)仓库(Repository)也叫版本库,是一个名为.git的隐藏目录,保存着项目的元数据、快照等信息,Git的仓库可分为远程和本地两种。

1)工作流程

  在图5中,通过工作区域的三部分描绘出了Git基本的工作流程,简单的将其分为四步,并给出了相应的Git命令。

图5  Git的工作流程

  首先在工作目录中修改源文件,然后用add命令将变更记录保存到暂存区,再用commit命令将暂存区中的文件提交给本地仓库,最后用push命令将本地仓库的信息推送给远程仓库。图中的云朵表示网络,因为一般远程仓库都会被托管在某台服务器中,需要通过网络将本地信息传送过去。

2)三种状态

  Git中的文件有多种状态,此处列出其中的三种:已修改(modified)、已暂存(staged)和已提交(committed)。

(1)已修改是还未提交的变更文件。

(2)已暂存是记录在当前快照中的变更文件。

(3)已提交是保存在本地仓库中的变更文件。

四、常用命令

  本节将列举出Git常用的命令,包括创建仓库、提交更新、查看信息等。

1)创建仓库

  如果要在当前目录中创建仓库,那么可以用下面的初始化命令。

git init

  在执行该命令后,就会生成名为.git的隐藏目录。

2)克隆仓库

  GitHub是一个提供Git仓库托管服务的网站,如果要在本机获取网站中已经存在的仓库,那么可以使用克隆命令,如下所示。注意,这两条命令的效果是相同的,因为Git支持HTTPS和SSH两种数据传输协议。

git clone https://github.com/pwstrick/demo.git
git clone git@github.com:pwstrick/demo.git

  在执行该命令后,就会创建一个名为demo的目录。默认情况下,远程仓库中的每个文件的每个版本都会被拉取下来。

3)提交更新

  工作目录中的文件可分为两大类:已跟踪(tracked)和未跟踪(untracked)。已跟踪的文件是指已经被纳入版本控制的文件,它们的状态可以是已修改、已暂存或已提交;而未跟踪的文件正好与之相反,并且不会被记录在之前的快照中。当初始化工作目录时,其中的文件都是未跟踪的;而当克隆仓库时,其中的文件都是已跟踪的。

  使用add命令,开始跟踪文件,其后面跟的参数既可以是文件路径,也可以是目录路径,下面命令中的点号表示跟踪当前目录中的所有文件。

git add .

  在执行add命令后,工作目录中新增或变更的文件就会被记录在暂存区,并且确定了下次提交时的快照内容。

  当暂存区已准备完毕可以提交时,就能执行commit命令了,如下所示,其中“-m”参数能为此次提交添加备注说明,注意,得用引号包裹。

git commit -m "add files"

  commit命令能为本地仓库创建一个持久快照,其内容来自于暂存区的临时快照。commit命令还有一个“-a”参数,可以让已经跟踪过的文件直接到暂存区,然后一并提交给本地仓库,从而就能跳过add命令,如果要与“-m”一起使用,可以像下面这样。

git commit -am "add files"

  当然,对于未跟踪的文件,无法省略add命令,仍然得执行。

4)忽略文件

  在工作目录中创建一个名为.gitignore的文件,列出要忽略的匹配模式(如下所示),就能让Git不再管理相关的文件或目录,例如忽略日志、编译生成的临时文件、Node.js的安装目录等。

# system
*.docx
node_modules/

  第一行相当于注释,第二行是告诉Git忽略后缀为“.docx”的文件,第三行是忽略node_modules目录。.gitignore文件支持Glob模式匹配(即简化过的正则表达式),另外还有一些格式规范:

(1)所有空行或以“#”开头的行都会被Git忽略。

(2)匹配模式能以“/”开头防止递归。

(3)匹配模式能以“/”结尾指定目录。

(4)只要在模式前加上“!”取反,就能忽略该模式以外的文件或目录。

5)删除文件

  Git提供了rm命令,可删除工作目录中的指定文件,并且能消除它在暂存区中的记录,如下所示。

git rm demo.txt

  rm命令的后面既可以跟文件或目录的名称,也能跟Glob模式的正则表达式,例如像下面这样删除后缀为“.log”的文件。

git rm *.log

  结合commit命令就能将删除的操作告知本地仓库。

6)撤销操作

  如果要撤销工作目录中的文件变更,可以使用checkout命令,如下所示,撤销了demo.txt文件中的变更。注意,命令中的“--”不能省略,否则就会执行切换分支的操作。

git checkout -- demo.txt

  如果要撤销暂存区中的文件变更,可以使用reset命令,如下所示,其中HEAD是一个指针,指向当前分支,通过它能得到该分支上最后一次提交的快照。

git reset HEAD demo.txt

  注意,上述两种撤销都是在commit命令之前执行的。有些情况下的撤销需要依次执行上面两条命令才能完成,例如demo.txt修改了多次,并且执行了多次“git add .”命令,然后要撤销所有的更改。

7)回退版本

  如果要实现版本回退,那么首先得知道版本号,而通过log命令就能获取到当前分支的历史版本,如下所示,包括版本号、作者、备注等信息。

$ git log
commit 00ef86de2fe382c5e1a9185a182a35bbbd34c0fd
Author: strick <pwstrick@163.com>
Date: Tue Jul 2 14:26:31 2019 +0800
test commit 9a701365eeb47cf876c726cf5e321af5ce9cabbf
Author: strick <pwstrick@163.com>
Date: Tue Jul 2 14:13:19 2019 +0800
add demo.txt

  现在就能通过reset命令,指定版本号,实现回退,如下所示。由于HEAD指针的存在,Git的版本回退其实就是移动指向,因此其速度是非常快的。

git reset --hard 9a701365eeb47cf876c726cf5e321af5ce9cabbf

8)查看信息

  在Git中用于查看信息的命令包括log、status、diff和reflog等,其中log命令已在前文介绍过,用来查看提交的版本历史,按提交时间倒序排列。接下来会先修改demo.txt文件,然后再执行相关的查看命令,其中命令可选择的参数由于篇幅原因都没有给出。

  status命令用来查看工作目录的状态,显示变更信息以及提示可用命令,如下所示。

$ git status
On branch master
Your branch is up-to-date with 'origin/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: demo.txt
no changes added to commit (use "git add" and/or "git commit -a")

  diff命令用来查看工作目录和暂存区之间的差异,如下所示,其中a和b表示的是同一个文件的不同版本。

$ git diff
diff --git a/demo.txt b/demo.txt
index d28d40b..30d74d2 100644
--- a/demo.txt
+++ b/demo.txt
@@ -1 +1 @@
-add
\ No newline at end of file
+test
\ No newline at end of file

  diff命令还能比较暂存区和本地仓库、两次commit提交、文件修改前后等之间的差异。

  reflog命令用来查看当前分支的所有操作记录,包括commit、reset、pull等,如下所示。

e1ce188 HEAD@{0}: pull origin master: Fast-forward
9a70136 HEAD@{1}: reset: moving to 9a701365eeb47cf876c726cf5e321af5ce9cabbf
......
fb8e5df HEAD@{13}: commit: demo
f1f30d6 HEAD@{14}: clone: from https://github.com/pwstrick/demo.git

9)远程仓库

  与远程仓库相关的命令有remote、fetch、pull和push,其中remote可与其它命令(例如add、show、rename等)配合实现扩展功能。

  remote命令可列出所有远程仓库的简称,在执行克隆命令时,Git会默认为其添加一个名为origin的远程仓库。如果为remote命令添加“-v”参数,那么会显示远程仓库的简称和其读写的URL。

$ git remote -v
origin https://github.com/pwstrick/demo.git (fetch)
origin https://github.com/pwstrick/demo.git (push)

  fetch命令可从远程仓库中拉取本地没有的数据,例如缺失的分支。

  pull命令可抓取远程分支的最新变更,并将其自动合并到当前分支中,在pull后面会紧跟远程仓库的简称和分支名称,如下所示。

git pull origin master

  push命令可将本地快照、变更的文件等信息推送到远程仓库中,其格式与pull命令类似,如下所示。

git push origin master

五、分支

  Git之所以能高性能的处理分支,主要得益于其与众不同的存储设计:弃文件变化,取文件快照。每次commit提交,Git都会创建一个提交对象(Commit Object),该对象包含本次快照的指针、父对象的指针(即上一个提交对象)、当前工作目录的结构和相关附属信息(例如作者、备注等),注意,首次提交产生的提交对象没有父对象。

  Git的分支本质上就是指向提交对象的可变指针,其默认分支叫master,如图6所示,用圆代表提交对象,圆内的字符串表示版本号的前5位。

图6  分支和其提交历史

1)创建

  创建分支其实就是新建一个新的可移动指针,如果要创建一条develop分支,那么执行的命令可以像下面这样。

git branch develop

  现在就会变成两条分支,如图7所示。

图7  两条分支

  执行“git branch”命令能够查看已有的分支,在当前分支的名称前会加“*”标记,如下所示。

$ git branch
develop
* master

2)切换

  在Git中,有一个特殊的HEAD指针,之前曾提到过它能指向当前分支,相当于当前分支的别名,如图8所示。

图8  HEAD指向当前分支

  切换分支其实就是改变HEAD的指向,例如切换到develop分支,其命令如下所示,内部操作如图9所示。

git checkout develop

图9  切换分支

  有一条快捷命令,可以创建一个分支并自动切换到那个分支,如下所示,给checkout命令添加了“-b”参数。

git checkout -b develop

3)合并

  有了分支后,就能在各自的分支工作,并且互不干扰,例如在develop分支提交了一个版本,如图10所示,develop分支向前移动了,而master分支仍在原地。

图10  HEAD自动向前移动

  现在用“git checkout master”命令将分支切换回master,HEAD会指向master分支,并且工作目录将恢复成master分支中的快照。如果要将develop分支中的变更记录合并(Merge)到master分支中,那么可以使用merge命令,如下所示。

$ git merge develop
Updating 9a9636b..64ea4b2
Fast-forward
demo.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

  注意上面的Fast-forward,说明这次合并使用了快进模式,即直接将指针向前移动。举例来说,当前开发处在develop分支,在提交一系列变更后,切换到master分支,而master分支没有提交新的变更,此时将develop分支合并到master分支就会采用快进模式。

4)冲突

  如果两个分支对同一文件的同一行都做了不同的修改,那么在合并时就会发生冲突(Conflict),如下所示,都修改了demo.txt文件的第一行。

$ git merge develop
Auto-merging demo.txt
CONFLICT (content): Merge conflict in demo.txt
Automatic merge failed; fix conflicts and then commit the result.

  此时,Git就会停止工作,等待冲突的解决。使用status命令可以查看所有冲突的文件,如下所示。

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
You have unmerged paths.
(fix conflicts and run "git commit")
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: demo.txt
no changes added to commit (use "git add" and/or "git commit -a")

  查看demo.txt文件,可以看到下面的特殊区段。

<<<<<<< HEAD
test
=======
minus
>>>>>>> develop

  Git用<<<<<<<、=======和>>>>>>>标记出不同分支的变更,其中上半部分是HEAD所指的master分支,下半部分是develop分支。要解决冲突,就得手动去掉Git生成的多余字符,以及保留其中一个分支的修改,在完成这一系列的操作后,demo.txt文件中的内容就会变成下面这样。

minus

  现在只要将冲突文件的变更保存到暂存区(如下所示),就能将它们标记成已解决,继续下面的工作了。

git add .
git commit -am "conflict"

  当冲突的文件特别多时,一个个的查找难免会费时费力,改用Git客户端工具会便捷很多,例如TortoiseGit,它不仅能列出冲突的文件,还能提供图11中的可视化编辑界面。

图11  TortoiseGit冲突解决

5)远程

  之前通过push命令将本地分支的信息推送到了远程仓库中(如下所示),与此同时,如果没有远程的同名分支,那么Git会自动为其创建一个,并且还会为它们两个建立跟踪关系。

git push origin master

  利用branch命令可在本地查看远程仓库(如下所示),分支会以“远程仓库/分支名称”的形式显示,例如origin/master。

$ git branch -r
origin/develop
origin/master

  如果当前分支与远程分支存在跟踪关系,那么在push或pull时可省略分支名称,如下所示。

git pull origin
git push origin

  Git允许手动建立跟踪关系,只要为branch命令添加“-u”或“--set-upstream-to”参数即可(如下所示),从而就能让一个本地分支跟踪多个远程分支。

$ git branch -u origin/strick
Branch develop set up to track remote branch strick from origin.

  如果当前分支只有一个可跟踪的远程分支,那么在push或pull时连仓库名称都能省略,如下所示。

git pull
git push

6)删除

  如果要删除分支,那么有两条命令可供选择,下面的第一条可删除本地分支,第二条可删除远程分支,本质上它们做的只是移除相应的指针。

git branch -d develop
git push origin --delete develop

7)变基

  变基(Rebase)能将一条分支上的变更转移到另一条分支上,以图12中的master和develop两条分支为例。

图12  变基前的两条分支

  假设当前分支是develop,变基的目标分支是master,执行rebase命令后的分支情况如图13所示。

图13  变基后的两条分支

  接下来会通过一个例子来讲解变基的用法,首先通过log命令查看master分支的提交历史,如下所示,其中“number”是修改demo.txt文件时所提交的备注。

$ git log
commit 7c27be31904221f3bb4556ca39dd7c8dea071178
Author: strick <pwstrick@163.com>
Date: Wed Jul 3 17:48:47 2019 +0800
number

  然后切换到develop分支,并执行rebase命令,如下所示。注意,变基目标是master而不是develop。

$ git checkout develop
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: digit
Using index info to reconstruct a base tree...
M demo.txt
Falling back to patching base and 3-way merge...
Auto-merging demo.txt
CONFLICT (content): Merge conflict in demo.txt
Failed to merge in the changes.
Patch failed at 0001 digit
The copy of the patch that failed is found in:
D:/demo/.git/rebase-apply/patch When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".

  由于两条分支同时修改了demo.txt文件中的内容,因此产生了冲突,必须先将其解决,才能继续后面的操作。注意,develop分支的修改记录先于master分支提交。

  当解决了所有冲突之后,先执行add命令,注意,此时不需要commit命令。然后再为rebase命令添加“--continue”(如下所示),就能完成变基,其中“digit”也是修改demo.txt文件时所提交的备注。

$ git add .
$ git rebase --continue
Applying: digit

  接着切换回master分支,并将develop分支中的修改合并进来,注意,此时开启了快进模式。

$ git checkout master
$ git merge develop
Updating 7c27be3..24c4b5a
Fast-forward
demo.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

  最后执行log命令,就能看到develop分支所提交的版本添加到了master分支的后面,注意,它没有根据提交时间按顺序插入。

$ git log
commit 24c4b5adb332f8c4f2e5ec39a7c77e0fc224b065
Author: strick <pwstrick@163.com>
Date: Wed Jul 3 17:48:00 2019 +0800
digit commit 7c27be31904221f3bb4556ca39dd7c8dea071178
Author: strick <pwstrick@163.com>
Date: Wed Jul 3 17:48:47 2019 +0800
number

  变基的一大特点就是能将分叉的提交历史梳理成一条直线,另一个特点是它会改变提交历史,这与合并完全不同。变基还有一条基本原则,即只对尚未推送和分享给他人的本地修改允许执行变基操作。

前端利器躬行记(5)——Git的更多相关文章

  1. 前端利器躬行记(8)——VSCode插件研发

    VSCode提供了丰富的 API,可以借助编辑器扩展许多定制功能. 本次研发了一款名为 Search Method 的插件,在此记录整个研发过程. 一.准备工作 1)安装环境 首先是全局安装 yo 和 ...

  2. 前端利器躬行记(1)——npm

    npm(Node Package Manager)是Node.js的包管理工具,相当于一个在线仓库.它提供了一个公共的平台,将分散在世界各地的包集中起来,能轻松的安装.分享和管理相关的包,不用再为搜索 ...

  3. 前端利器躬行记(2)——Babel

    Babel是一个JavaScript编译器,不仅能将当前运行环境不支持的JavaScript语法(例如ES6.ES7等)编译成向下兼容的可用语法(例如ES3或ES5),这其中会涉及新语法的转换和缺失特 ...

  4. 前端利器躬行记(3)——webpack基础

    webpack是一个静态模块打包器,此处的模块可以是任意文件,包括Sass.TypeScript.模板和图像等.webpack可根据输入文件的依赖关系,打包输出浏览器可识别的JavaScript.CS ...

  5. 前端利器躬行记(4)——webpack进阶

    webpack是一个非常强大的工具,除了前文所介绍的基础概念之外,还有各种进阶应用,例如Source Map.模块热替换.集成等,本文会对这些内容做依次讲解. 一. runtime和manifest ...

  6. 前端利器躬行记(6)——Fiddler

    Fiddler是一款免费的.基于Windows系统的代理服务器软件(即Web调试抓包工具),由Eric Lawrence用C#语言在2003年10月发布了第一个版本.注意,由于Fiddler依赖Mic ...

  7. ES6躬行记(1)——let和const

    古语云:“纸上得来终觉浅,绝知此事要躬行”.的确,不管看了多少本书,如果自己不实践,那么就很难领会其中的精髓.自己研读过许多ES6相关的书籍和资料,平时工作中也会用到,但在用到时经常需要上搜索引擎中查 ...

  8. ES6躬行记 笔记

    ES6躬行记(18)--迭代器 要实现以下接口## next() ,return,throw 可以用for-of保证迭代对象的正确性 例如 var str = "向

  9. Node.js躬行记(4)——自建前端监控系统

    这套前端监控系统用到的技术栈是:React+MongoDB+Node.js+Koa2.将性能和错误量化.因为自己平时喜欢吃菠萝,所以就取名叫菠萝系统.其实在很早以前就有这个想法,当时已经实现了前端的参 ...

随机推荐

  1. sublime text 3 15个常用插件介绍

    1.ColorPicker 功能:调色板(需要输入颜色时,可直接选取颜色) 使用:快捷键Windows: ctrl+shift+c 2.Emmet 功能:编码快捷键,前端必备 使用:在输入代码段后,按 ...

  2. 【科研民工笔记2】Ubuntu 16.04 安装nvidia驱动

    我的主机是2060的显卡,用的是安装在U盘中的Ubuntu,开机进入后,因为没有安装驱动,所以界面看以来比较大. 通过手动方式,成功安装驱动,最终成功的方案使用的是run文件安装的方式. 1.手动下载 ...

  3. 对Java中HashCode方法的深入思考

    前言 最近在学习 Go 语言,Go 语言中有指针对象,一个指针变量指向了一个值的内存地址.学习过 C 语言的猿友应该都知道指针的概念.Go 语言语法与 C 相近,可以说是类 C 的编程语言,所以 Go ...

  4. Linux常用命令之ftp

    FTP是Internet用户使用最频繁的文件上传.下载的命令之一.linux ftp用命令的方式来控制在本机和远程ftp服务器之间传送文件.ftp中的命令包括上传文件(单个.多个),下载文件(单个.多 ...

  5. GoAccess 分析 Nginx 日志

    0x00 事件 帮助朋友搭建了博客,运行过了一段时间,准备发个网站分析报告给他. 有效的数据只有 Nginx 的访问日志,于是使用决定 GoAccess 工具对这个日志进行分析, 0x01 安装 吾使 ...

  6. coo ceo cfo cto cio 区别

    常见的CEO(Chief executive officer)首席执行官类似总经理.总裁,是企业的法人代表. COO(Chief operating officer)首席运营官 类似常务总经理CFO( ...

  7. idea打开eclipse项目并部署至tomcat

    前言:因为遇到了用idea打开eclipse项目并配置外置tomact的场景,经过实验也成功了,所以特地来记录一下. 导入eclipse项目 这里我们导入的eclipse下开发的maven项目,直接点 ...

  8. Facebook的早期历史

    Facemash:谁更有吸引力?Facebook的起源   2003年,当时扎克伯格还是一名哈佛大学的二年级学生,他编写了一个名为Facemash的网站.他利用黑客技术入侵了学校管理部门的网站,并从中 ...

  9. $('div','li') 和 $('div , li') 和 $('div li') 区别

    $('div','li')是$(子,父),是从父节点里找子,而不是找li外面的div $('div , li')才是找所有的div和li,之间不存在父子关系 $('div li') 是找div里面所有 ...

  10. 二阶段js 入门知识点 自我总结复习

    二阶段自我总复习   1.javascript基础 :  客户端   安全性   跨平台   脚本语言 三大结构:  顺序 .选择.循环                    顺序:运算符和表达式  ...