什么是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.mddemo1.txt1 insertion 指插入1行内容:即我们在 README.md 文件中插入的“Git is a version control system.”。

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

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

同步修改到版本库

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

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

  1. Git is a version control system.
  2. Git is distributed.

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

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

  1. README.md 文件进行了修改,并且这次修改还没有被添加到暂存区。
  2. 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 了,刚才的那次变动已经被版本库存档了。

把文件从版本库中删除

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

  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 命令来帮助我们穿越时空。

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

  1. 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 文件,可以确认真的回到了未来!

撤销修改

撤销修改有三种情况:

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

修改还没添加到暂存区

我们先谈第一种情况。

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

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

有两种删除方式:

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

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

  1. $ git restore README.md

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

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

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

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

  1. $ git restore --staged README.md

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

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

总结

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

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

  1. # 创建版本库
  2. $ git init
  3. # 把文件添加到暂存区
  4. $ git add <file>
  5. # 把暂存区里的变动提交到版本库
  6. $ git commit
  7. # 查看当前所有快照
  8. $ git log
  9. # 查看版本库当前状态
  10. $ git status
  11. # 查看某文件的变动
  12. $ git diff
  13. # 删除文件,并把这次变动提交到暂存区
  14. $ git rm <file>
  15. # 版本间的前进和回退
  16. $ git reset
  17. # 撤销修改
  18. $ 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

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

Git轻松入门1:本地仓库篇的更多相关文章

  1. Git轻松入门2:分支篇

    什么是分支 在玩剧情类游戏时,不同的选择会触发不同的剧情路线,每条剧情路线都会独立发展,最终走向不同的结局. Git中所谓的"分支(branch)"就如同游戏中的剧情路线,用户可以 ...

  2. Git轻松入门3:远程仓库篇

    在第一讲中,我们有介绍过:Git是分布式版本控制系统.每个人的电脑上都有一份完整的版本库.当对项目作出了修改后,只要把修改推送给对方即可.但很有可能的情况是:两台电脑不在一个局域网内,无法互相访问:或 ...

  3. Git【入门】这一篇就够了

    前言 欢迎关注公众号,白嫖原创PDF,也可以催更,微信搜:JavaPub,回复:[666] Git 在生产工作中是使用频率很高的工具,但我发现很多文章只是对它做了简单的提交命令说明,真正遇到 版本冲突 ...

  4. git学习笔记04-将本地仓库添加到GitHub远程仓库-git比svn先进的地方

    第1步:创建SSH Key.在用户主目录下,看看有没有.ssh目录,如果有,再看看这个目录下有没有id_rsa和id_rsa.pub这两个文件,如果已经有了,可直接跳到下一步. 如果没有,打开Shel ...

  5. git 快速使用(本地仓库同步到远程仓库)

    学git一段时间,可惜公司用的是svn,平时少用,又忘了,总结一下,免得下次又得重新学习.得多多用才是正道! 一.  将本地的提交到网上git仓库 1.在git创建仓库                ...

  6. Git总结笔记3-把本地仓库推送到github

    说明:此笔记在centos 7 上完成 1.配置公钥 [root@kangvcar ~]# ssh-keygen -t rsa -C "kangvcar@126.com" [roo ...

  7. 如何在git上创建的本地仓库

    一.安装git(在git) 二. 三.输入个人信息(代码提交者) git config --global user.name "xxxx" git config --global ...

  8. Android Studio使用GIt提交代码到本地仓库后没有Push,如何回退保存

    当在AS中使用Git来提交代码时,有时候我们不注意的情况下会把不想提交的文件或文件夹(比如\build下的)提交到本地仓库,如果此时并没有Push到远程仓库的话.如果让本地仓库的提交回退并且保存之前的 ...

  9. git笔记十:本地仓库同步到gitlab

    本地仓库同步到gitlab 帮助文档 git remote --help 操作场景: 本地创建git仓库(含有readme.md文件), commit了三次 gitlab网站创建了一个项目 添加了re ...

  10. git 使用案例(本地仓库无缝迁移远程仓库)

    之前都是直接从gitlab上clone代码,然后把本地代码copy过去,然后push.有点麻烦,查询了一下如何无缝从本地仓库迁移到远程仓库.记录一波... 下面的例子采用github来做例子. 1. ...

随机推荐

  1. Android驱动入门-在Android系统上运行JAVA程序

    在linux上运行java程序,直接用javac编译,再用java运行就行了.但是在Android上,由于虚拟机和pc端的不同,所以操作方法也是不一样的. 如果想在Android上运行Hello wo ...

  2. MongoDb学习(五)--Gridfs--上传下载

    版本 <dependency> <groupId>org.springframework.data</groupId> <artifactId>spri ...

  3. VoltDB成功入选CNCF Landscape云原生数据库全景图

    近日,VoltDB正式入选 CNCF Landscape(可能是目前其中唯一的关系型分布式内存数据库).此次VoltDB 进入 CNCF Landscape,意味着 VoltDB 正式成为了 CNCF ...

  4. String被final修饰

    源码:

  5. flume基本概念及相关参数详解

    1.flume是分布式的日志收集系统,把手机来的数据传送到目的地去 2.flume传输的数据的基本单位是 event,如果是文本文件,通常是一行记录.       event代表着一个数据流的最小完整 ...

  6. Redis学习之路(一)Redis简介

    一.Redis简介 是以key-value形式存储,和传统的关系型数据库不一样,不一定遵循传统数据库的一些基本要求.(区别于MYSQL的二维表格的形式存储).和Memcache类似,但是很大程度上拟补 ...

  7. 容器编排系统K8s之APIService资源

    前文我们聊到了k8s上crd资源的使用和相关说明,回顾请参考:https://www.cnblogs.com/qiuhom-1874/p/14267400.html:今天我们来了解下k8s的第二种扩展 ...

  8. SpringBoot入门 简单搭建和使用

    前言 差不多两年前,那个时候我准备要做毕业设计了,才第一次知道java有框架这种东西,在网上找了好多SSM的教程,那会儿真的是Spring+SpringMVC+MyBatis搭建的,印象极深的是还要写 ...

  9. 基于 MPI 的快速排序算法的实现

    完整代码: #include <iostream> #include <cstdlib> #include <ctime> #include <algorit ...

  10. TCP/IP五层模型-传输层-UDP协议

    ​1.定义:UDP:是非面向连接.不可靠的用户数据包协议. 2.应用场景:适合对数据完整性要求不高,但对延迟很敏感,比如即时通信(语音视频聊天等). 3.UDP报文格式: 4.用UDP传输数据的应用层 ...