Git轻松入门1:本地仓库篇-LMLPHP

什么是Git

版本控制系统

首先我们要明白,什么是版本控制系统(version control systems)?

版本控制系统,顾名思义,就是能控制文件处于哪个版本的系统。

e.g. 你在博客园里编辑的文章,你可以控制文章是处于某个时间点的版本。

Git轻松入门1:本地仓库篇-LMLPHP

而Git就是一款版本控制系统,而且是分布式的。

与分布式相对应的是集中式版本控制系统,它的版本库是存储在中央服务器的。工作的时候你需要先从中央服务器取得最新的版本,等工作完了,再把自己的工作推送回中央服务器。但这有个问题:万一哪天中央服务器崩溃了甚至数据丢失了,那所有人不就都无法工作了?

于是分布式版本控制系统(Distributed Version Control System,简称 DVCS)就应运而生了。它没有“中央服务器”,每个人的电脑上都有一份完整的版本库,如果某个人的电脑坏了导致版本库丢失了,直接从别人那复制一个版本库过来即可。

Git的基本工作原理

有一类版本控制系统是基于差异(delta-based)来进行版本控制的。

Git轻松入门1:本地仓库篇-LMLPHP

这类版本控制系统存储的是新文件和旧文件间的区别(delta)。这里的delta和高中物理里的delta一样,\(Δv = v_2 - v_1\),都指的是变化量。

而Git和上述这类版本控制系统的工作原理不同:每当你对提交新项目或者对项目进行修改,它会把项目的所有文件都压缩保存起来,这个压缩文件在Git中被称为快照(snapshot)。每次有更新就会每次把所有文件都压缩起来存储一遍,形成一系列快照。

Git轻松入门1:本地仓库篇-LMLPHP

当我们需要处于某个时间点版本的文件时,直接根据索引,找到那个时间点的快照即可。

操作指令

在开始介绍Git相关的指令前,我们先创建三个文件。

Git轻松入门1:本地仓库篇-LMLPHP

创建版本库

所谓“版本库(repository)”就是一个用来存储文件历史版本的仓库。所以要想对文件进行版本控制,我们需要先初始化一个版本库来存储文件的各个历史版本。

$ git init 命令能把当前所在目录变为由Git管理的仓库(或是对该位置已有仓库进行初始化),使得该目录下的文件可被Git进行版本控制。

当执行完该命令后,可以看到该目录下生成了一个 .git 的文件夹。

Git轻松入门1:本地仓库篇-LMLPHP

把文件添加到版本库

创建完版本库后,我们还需要把文件放进去才能对其进行版本控制。

而把文件添加到版本库需要两个步骤:

第一步,用 $ git add 命令把需要跟踪版本的文件添加到暂存区。

Git轻松入门1:本地仓库篇-LMLPHP

第二步,用 $ git commit 命令告诉仓库:我确定要对暂存区里的文件进行版本控制,请把暂存区里的文件正式放入仓库管理。

Git轻松入门1:本地仓库篇-LMLPHP

-m "xxx" 是对本次提交的备注说明;2 files changed 指有两个文件变动:即我们新添加的 README.mddemo1.txt1 insertion 指插入1行内容:即我们在 README.md 文件中插入的“Git is a version control system.”。

在每次提交变动(commit)后,都会产生对应的快照。快照,我们在Git的工作原理那章提到过,它就是被放入版本库中的所有文件的压缩版本。我们可以把快照理解成是游戏的存档。在玩游戏时,我们每通过一关就会保存游戏。如果进行到某一关死了,那就可以通过读取存档来回到前面的某一关。Git也一样,每次commit就是在给文件存档,要是哪一天文件误删了或出错了,那我们就可以读取存档,让文件恢复到存档时的状态。

通过 $ git log 命令,可以查看当前共有几个存档。

Git轻松入门1:本地仓库篇-LMLPHP

同步修改到版本库

当我们在工作区(工作区就是当你打开“我的电脑”看到的目录就叫工作区)里对文件进行修改后,如何把更新同步到版本库中,让版本库也拥有文件最新的版本呢?

我们先添加一行话到 README.md 文件中,使其内容变为如下:

Git is a version control system.
Git is distributed.

现在,运行 $ git status 命令查看版本库当前状态:

Git轻松入门1:本地仓库篇-LMLPHP

从状态中我们可以得到2个信息:

  1. README.md 文件进行了修改,并且这次修改还没有被添加到暂存区。
  2. learn_git 目录下,demo2.txt 文件没被添加到仓库中以进行版本追踪。

如果我们想要进一步得知对 README.md 文件进行了哪些修改,可以用 $ git diff 命令(这步并不是必须的)。

Git轻松入门1:本地仓库篇-LMLPHP

可以看到,新增加的改动是“Git is distributed”这行话。

确定了作出哪些修改后,再把它提交到仓库自然就放心多了。提交修改和之前提交新文件需要同样的两个步骤:$ git add$ git commit

我们先运行 $ git add,然后看看版本库状态。

Git轻松入门1:本地仓库篇-LMLPHP

可以看到,当我们用 $ git add 命令把文件添加到暂存区后,版本库状态就不会再提示我们“尚未暂存已备提交的变更”,而是提示“要提交的变更”,即提示我们要用 $ git commit 命令提交这次变更。

那我们就再运行一下 $ git commit 命令,再来看看版本库状态。

Git轻松入门1:本地仓库篇-LMLPHP

再用git log查看一下当前共有几个存档(快照)。

Git轻松入门1:本地仓库篇-LMLPHP

从以上两张截图可以看出现在已经没有需要提交的修改了,只剩下未跟踪的文件 demo2.txt 了,刚才的那次变动已经被版本库存档了。

把文件从版本库中删除

删除文件也算是对文件的一种修改。同样也需要两步:

  1. $ git rm <file> 把该文件从工作区删除。
  2. commit这次变动,把删除同步到版本库。

注:在执行 $ git rm <file> 指令时,Git会默认执行 $ git add 命令来把这次修改提交到暂存区。

回退到某个版本

我们已知目前共有2个存档:第一个存档的ID是b1a6863...,存档备注是“added README and demo1”;第二个存档的ID是245fcc1...,存档备注是“modified README”。

在Git中,HEAD 是一个指针,它会指向当前所在分支的最新存档,分支的概念我们后边在介绍,但反正目前我们没有创建别的分支。你让 HEAD 指向哪个版本号,Git就把当前版本定位在哪。所以此时 HEAD 代表的就是ID为245fcc...的存档,那么在它前一个的存档就是 HEAD^,前N个的存档就是 HEAD~N

有一天你可能会觉得第二个版本很糟糕,不如第一个版本,于是想坐时光机回到过去,让一切复原到第一个版本时的样子。这时我们可以用 $ git reset 命令来帮助我们穿越时空。

Git轻松入门1:本地仓库篇-LMLPHP

我们再来看一下 README.md 里的内容,后面新添加的那行就不见了,内容回到了最初的时候:

Git is a version control system.

再运行一下 $ git log

Git轻松入门1:本地仓库篇-LMLPHP

一切真的都回到了过去!连存档都变成和当时一样,只有b1a6863...这一个了!

为什么回退的速度这么快呢?

因为我们每个版本的文件都被保存在了快照里,Git仅仅只需要把 HEAD 指针从一个快照指向到另一个快照,再把目录里的文件替换成那个快照里的文件即可。

所以建议:无论是大的变动还是小的变动,都存一下档(即commit)。只有把这个版本存档了,保存进快照里了,日后才能通过读档来返回到该版本。

可万一你又后悔了,觉得一切还是像第二个版本时的那样好,怎么办?通过 $ git log 命令已经找不到第二个版本的存档了,是不是回不去了?

并不是的。在Git中,你的每次操作就会留下记录,我们可以用 $ git reflog 来查看所有的操作。

Git轻松入门1:本地仓库篇-LMLPHP

从中我们可以看到,备注为“modified README”的那次commit的ID是245fcc1。有了commit ID后,我们就又能通过 $ git reset 命令回到未来了!

Git轻松入门1:本地仓库篇-LMLPHP

再运行一下 $ git log 和打开 README.md 文件,可以确认真的回到了未来!

撤销修改

撤销修改有三种情况:

  1. 修改还没添加到暂存区。
  2. 修改已经添加到暂存区,但还没commit。
  3. 修改已经commit。

修改还没添加到暂存区

我们先谈第一种情况。

假设你往 README.md 文件里添加了一行“My boss is stupid. ”,而这最新修改还没有被你添加到暂存区。所以当你运行$ git status 命令时,你看到Git提示你有尚未暂存已备提交的修改。

Git轻松入门1:本地仓库篇-LMLPHP

你觉得这句话可能会让你被炒鱿鱼,尽管你说的是事实。于是你最终还是决定把这句话删掉。

有两种删除方式:

  1. 手动打开 README.md 文件,然后把这句话删掉,让文件回到添加这行话前的样子。
  2. 按照Git提示的,用 $ git restore <file> 命令,来直接撤销改文件在工作区的变动。

你觉得第二种方式看起来显得逼格更高点,于是你输入如下命令。

$ git restore README.md

然后当你再打开README.md文件,发现那句话果然消失了;再运行 $ git status 命令,发现Git也没有任何提示了。

修改已经添加到暂存区但还没commit

再来谈谈第二种情况:你不仅写了那句话,还提交到了暂存区!所以当你运行 $ git status 命令时,你看到Git提示你有要提交的变更。

Git轻松入门1:本地仓库篇-LMLPHP

有了上次撤销的经验,你就放心地按照Git的提示输入如下命令:

$ git restore --staged README.md

再运行 $ git status,你发现现在它提示的和第一种情况一样了,而打开工作区里 README.md 文件,那句话还在。原来这个命令只是帮你撤销一小步,仅仅是从暂存区移除,而不是帮你一夜回到解放前。

当然了,如果你很不幸的已经commit这次提交了,那就只能用 $ git reset 来版本回退了。

总结

Git中文件的所有状态以及状态间的转换可以被概括为下面这张图。

Git轻松入门1:本地仓库篇-LMLPHP

最后,总结一下今天出现过的几个Git命令:

# 创建版本库
$ git init

# 把文件添加到暂存区
$ git add <file>

# 把暂存区里的变动提交到版本库
$ git commit

# 查看当前所有快照
$ git log

# 查看版本库当前状态
$ git status

# 查看某文件的变动
$ git diff

# 删除文件,并把这次变动提交到暂存区
$ git rm <file>

# 版本间的前进和回退
$ git reset

# 撤销修改
$ git restore

参考

  1. https://sp18.datastructur.es/materials/guides/using-git.html
  2. https://www.liaoxuefeng.com/wiki/896043488029600
  3. http://git-scm.com/book/en/v2/Getting-Started-What-is-Git%3F
  4. https://stackoverflow.com/questions/4964099/what-is-a-git-snapshot

 
有问题欢迎大家在评论区留言,转载请注明出处。

02-02 21:14