什么是Git
版本控制系统
首先我们要明白,什么是版本控制系统(version control systems)?
版本控制系统,顾名思义,就是能控制文件处于哪个版本的系统。
e.g. 你在博客园里编辑的文章,你可以控制文章是处于某个时间点的版本。
而Git就是一款版本控制系统,而且是分布式的。
与分布式相对应的是集中式版本控制系统,它的版本库是存储在中央服务器的。工作的时候你需要先从中央服务器取得最新的版本,等工作完了,再把自己的工作推送回中央服务器。但这有个问题:万一哪天中央服务器崩溃了甚至数据丢失了,那所有人不就都无法工作了?
于是分布式版本控制系统(Distributed Version Control System,简称 DVCS)就应运而生了。它没有“中央服务器”,每个人的电脑上都有一份完整的版本库,如果某个人的电脑坏了导致版本库丢失了,直接从别人那复制一个版本库过来即可。
Git的基本工作原理
有一类版本控制系统是基于差异(delta-based)来进行版本控制的。
这类版本控制系统存储的是新文件和旧文件间的区别(delta)。这里的delta和高中物理里的delta一样,(Δv = v_2 - v_1),都指的是变化量。
而Git和上述这类版本控制系统的工作原理不同:每当你对提交新项目或者对项目进行修改,它会把项目的所有文件都压缩保存起来,这个压缩文件在Git中被称为快照(snapshot)。每次有更新就会每次把所有文件都压缩起来存储一遍,形成一系列快照。
当我们需要处于某个时间点版本的文件时,直接根据索引,找到那个时间点的快照即可。
操作指令
在开始介绍Git相关的指令前,我们先创建三个文件。
创建版本库
所谓“版本库(repository)”就是一个用来存储文件历史版本的仓库。所以要想对文件进行版本控制,我们需要先初始化一个版本库来存储文件的各个历史版本。
$ git init
命令能把当前所在目录变为由Git管理的仓库(或是对该位置已有仓库进行初始化),使得该目录下的文件可被Git进行版本控制。
当执行完该命令后,可以看到该目录下生成了一个 .git
的文件夹。
把文件添加到版本库
创建完版本库后,我们还需要把文件放进去才能对其进行版本控制。
而把文件添加到版本库需要两个步骤:
第一步,用 $ git add
命令把需要跟踪版本的文件添加到暂存区。
第二步,用 $ git commit
命令告诉仓库:我确定要对暂存区里的文件进行版本控制,请把暂存区里的文件正式放入仓库管理。
-m "xxx"
是对本次提交的备注说明;2 files changed
指有两个文件变动:即我们新添加的 README.md
和 demo1.txt
;1 insertion
指插入1行内容:即我们在 README.md
文件中插入的“Git is a version control system.”。
在每次提交变动(commit)后,都会产生对应的快照。快照,我们在Git的工作原理那章提到过,它就是被放入版本库中的所有文件的压缩版本。我们可以把快照理解成是游戏的存档。在玩游戏时,我们每通过一关就会保存游戏。如果进行到某一关死了,那就可以通过读取存档来回到前面的某一关。Git也一样,每次commit就是在给文件存档,要是哪一天文件误删了或出错了,那我们就可以读取存档,让文件恢复到存档时的状态。
通过 $ git log
命令,可以查看当前共有几个存档。
同步修改到版本库
当我们在工作区(工作区就是当你打开“我的电脑”看到的目录就叫工作区)里对文件进行修改后,如何把更新同步到版本库中,让版本库也拥有文件最新的版本呢?
我们先添加一行话到 README.md
文件中,使其内容变为如下:
Git is a version control system.
Git is distributed.
现在,运行 $ git status
命令查看版本库当前状态:
从状态中我们可以得到2个信息:
- 对
README.md
文件进行了修改,并且这次修改还没有被添加到暂存区。 - 在
learn_git
目录下,demo2.txt
文件没被添加到仓库中以进行版本追踪。
如果我们想要进一步得知对 README.md
文件进行了哪些修改,可以用 $ git diff
命令(这步并不是必须的)。
可以看到,新增加的改动是“Git is distributed”这行话。
确定了作出哪些修改后,再把它提交到仓库自然就放心多了。提交修改和之前提交新文件需要同样的两个步骤:$ git add
和 $ git commit
。
我们先运行 $ git add
,然后看看版本库状态。
可以看到,当我们用 $ git add
命令把文件添加到暂存区后,版本库状态就不会再提示我们“尚未暂存已备提交的变更”,而是提示“要提交的变更”,即提示我们要用 $ git commit
命令提交这次变更。
那我们就再运行一下 $ git commit
命令,再来看看版本库状态。
再用git log
查看一下当前共有几个存档(快照)。
从以上两张截图可以看出现在已经没有需要提交的修改了,只剩下未跟踪的文件 demo2.txt
了,刚才的那次变动已经被版本库存档了。
把文件从版本库中删除
删除文件也算是对文件的一种修改。同样也需要两步:
- 用
$ git rm <file>
把该文件从工作区删除。 - 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
命令来帮助我们穿越时空。
我们再来看一下 README.md
里的内容,后面新添加的那行就不见了,内容回到了最初的时候:
Git is a version control system.
再运行一下 $ git log
。
一切真的都回到了过去!连存档都变成和当时一样,只有b1a6863...
这一个了!
为什么回退的速度这么快呢?
因为我们每个版本的文件都被保存在了快照里,Git仅仅只需要把 HEAD
指针从一个快照指向到另一个快照,再把目录里的文件替换成那个快照里的文件即可。
所以建议:无论是大的变动还是小的变动,都存一下档(即commit)。只有把这个版本存档了,保存进快照里了,日后才能通过读档来返回到该版本。
可万一你又后悔了,觉得一切还是像第二个版本时的那样好,怎么办?通过 $ git log
命令已经找不到第二个版本的存档了,是不是回不去了?
并不是的。在Git中,你的每次操作就会留下记录,我们可以用 $ git reflog
来查看所有的操作。
从中我们可以看到,备注为“modified README”的那次commit的ID是245fcc1
。有了commit ID后,我们就又能通过 $ git reset
命令回到未来了!
再运行一下 $ git log
和打开 README.md
文件,可以确认真的回到了未来!
撤销修改
撤销修改有三种情况:
- 修改还没添加到暂存区。
- 修改已经添加到暂存区,但还没commit。
- 修改已经commit。
修改还没添加到暂存区
我们先谈第一种情况。
假设你往 README.md
文件里添加了一行“My boss is stupid. ”,而这最新修改还没有被你添加到暂存区。所以当你运行$ git status
命令时,你看到Git提示你有尚未暂存已备提交的修改。
你觉得这句话可能会让你被炒鱿鱼,尽管你说的是事实。于是你最终还是决定把这句话删掉。
有两种删除方式:
- 手动打开
README.md
文件,然后把这句话删掉,让文件回到添加这行话前的样子。 - 按照Git提示的,用
$ git restore <file>
命令,来直接撤销改文件在工作区的变动。
你觉得第二种方式看起来显得逼格更高点,于是你输入如下命令。
$ git restore README.md
然后当你再打开README.md文件,发现那句话果然消失了;再运行 $ git status
命令,发现Git也没有任何提示了。
修改已经添加到暂存区但还没commit
再来谈谈第二种情况:你不仅写了那句话,还提交到了暂存区!所以当你运行 $ git status
命令时,你看到Git提示你有要提交的变更。
有了上次撤销的经验,你就放心地按照Git的提示输入如下命令:
$ git restore --staged README.md
再运行 $ git status
,你发现现在它提示的和第一种情况一样了,而打开工作区里 README.md
文件,那句话还在。原来这个命令只是帮你撤销一小步,仅仅是从暂存区移除,而不是帮你一夜回到解放前。
当然了,如果你很不幸的已经commit这次提交了,那就只能用 $ git reset
来版本回退了。
总结
Git中文件的所有状态以及状态间的转换可以被概括为下面这张图。
最后,总结一下今天出现过的几个Git命令:
# 创建版本库
$ git init
# 把文件添加到暂存区
$ git add <file>
# 把暂存区里的变动提交到版本库
$ git commit
# 查看当前所有快照
$ git log
# 查看版本库当前状态
$ git status
# 查看某文件的变动
$ git diff
# 删除文件,并把这次变动提交到暂存区
$ git rm <file>
# 版本间的前进和回退
$ git reset
# 撤销修改
$ git restore
参考
- https://sp18.datastructur.es/materials/guides/using-git.html
- https://www.liaoxuefeng.com/wiki/896043488029600
- http://git-scm.com/book/en/v2/Getting-Started-What-is-Git%3F
- https://stackoverflow.com/questions/4964099/what-is-a-git-snapshot
有问题欢迎大家在评论区留言,转载请注明出处。