Git学习笔记 - 基础

什么是Git?

Git是一款免费、开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目。我相信很多人都用过SVN这一个版本控制系统,很多中小型公司都喜欢用这个工具的。在很早以前我就听说过Git,但由于工作需求,也就没深入接触了,现在有空就来学习学习这个强大的工具。版本控制系统可以分为2大类:集中式和分布式,SVN属于前者,Git属于后者。Git比SVN强大,原因在于:速度快、可离线工作、超强的分支管理。

Git的中心思想和基本工作原理

接下来讲述的内容,对其的理解程度决定了我们日后能否真正运用好Git,所以,必须弄懂,现阶段弄不懂那就经过实践后再回过头再看再思考千百遍。
Git官网也这样说道:

请注意,接下来的内容非常重要,若你理解了 Git 的思想和基本工作原理,用起来就会知其所以然,游刃有余。

集中式 Vs 分布式

前面也说到版本控制系统可分为集中式与分布式2大类。那么到底它们之间到底有什么区别呢?

集中式

我们都非常清楚版本控制系统最主要就是让多个开发者协同工作,按照正常人的一般的逻辑思维,都会想到集中式版本控制系统(Centralized Version Control System,简称CVCS),这类系统中都有一个单一用于集中管理的服务器,它保存了所有文件的历史版本(用户并没有备份所有,用户处只有这些历史版本的一部分),多个用户之间协同工作都要通过它作为桥梁:用户可以从它取出最新版本的文件或提交修改好的文件给它。

centralized_version_control_system

这种做法最大的好处就是便于管理,该系统的管理员可以很轻松地掌控所有历史版本以及每个开发者的权限。但相对的,坏处就是一旦桥梁崩塌了,用户之间就无法协同工作了,比如这个服务器断电了,用户们也就无法连上这个中央服务器,这导致导出最新版本或提交文件这些功能无法使用了。更糟糕的是,如果服务器并不是断电,而是因不可抗力因素,服务器损毁了,这样所有历史版本数据都丢失了,剩下有用的数据只有每个用户里所保留的某些版本(说白了,用户只有所有历史版本的很小一部分)。

分布式

分布式版本控制系统(Distributed Version Control System,简称DVCS)里的做法跟前面所说的集中式完全不同,这类系统中没有硬性规定一定要有一个统一的老大哥服务器,而是把每个用户都看作是老大哥服务器,所以实际上每个用户都存有所有历史版本的数据。当然这种版本控制系统其实一般还是有一个真正的集中式服务器,只不过它的地位下降了而已。

distributed_version_control_system

用户每次的导出操作导出的其实是所有历史版本,这样相当于每个用户都有所有历史版本的完成备份,极大地提高了安全性。同时,也因为这样,用户在离线时也可以将改动进行提交,在有网络时便可以再继续协同工作。

快照 Vs 差异

Git与其它版本控制系统的主要差别在于对待数据的方式。

这句话更通俗来讲就是:Git记录文件到版本库所用的方式有别于其他版本控制系统

Git存储全部文件的一组快照(snapshot),而不是存储每个文件与初始版本的差异。

每次你提交更新,或在Git中保存项目状态时,它主要对当时的全部文件制作一个快照并保存这个快照的索引。
为了高效,如果文件没有修改,Git不再重新存储该文件,而只是保留一个链接指向之前存储的文件。Git对待数据更像是快照流

storing_data_as_snapshots_of_the_project_over_time

对于Git快照的理解:
当你给Git提交更新时,Git会把它所管理的所有文件都制作成一个备份(但这并不是单纯的文件复制粘贴,Git还是会作出相应的处理的:压缩之类的处理)并存储它,这就是一个新版本。并且,对于经过修改文件肯定是制作一个新的备份,而对于未修改的文件,就存储一个指向上个版本那个备份的指针,这样能够稍微节省一些空间。

相比之下,其他版本控制系统的做法就不一样了。

其它大部分系统以文件变更列表的方式存储信息。
这类系统(CVS、Subversion、Perforce、Bazaar 等等)将它们保存的信息看作是一组基本文件和每个文件随时间逐步累积的差异。


storing_data_as_changes_to_a_base_version_of each_file

对于SVN文件的差异存储的理解:
初始版本是存储了所有文件数据的最完整版本,对于往后每次更新提交,SVN只存储相较上个版本现在更新的部分,而未修改的文件是不进行存储的。

综上所述,这2种做法通俗来说就是:对于每次文件提交,不管文件是否有更新Git都进行存储,而SVN只存文件变动的部分。

  其实这种区别大致是由历史环境所致的,以前硬件资源比较珍贵的,所以什么都必须节省,特别是存储空间,所以在这种环境下所诞生的版本控制系统SVN首要任务就是在记录改动时必须最大程度地节省存储空间。
  这么看来SVN比Git节省空间咯?(虽然也不一定,我现在也不清楚,或许以后会知道哪个更好。)
  但我只知道一个原则:等价交换。所谓有得必有失,速度换空间还是空间换速度,更倾向于哪个就要看需求还有各种限制因素了。上网随便一查Git与SVN对比就会发现大家基本都会说Git在速度上肯定是远胜SVN的。所以Git这种做法首先带来了速度上的优势,同时这个官方Git教程表示:Git采用这种存储版本数据的方式会对分支管理非常有利

三个重要区域

areas

  • 工作目录(working directory)

    工作目录是对项目的某个版本独立提取出来的内容。 这些从 Git 仓库的压缩数据库中提取出来的文件,放在磁盘上供你使用或修改。

    被Git跟踪管理的那个目录就是工作目录,工作目录里的任何风吹草动(添加、修改、删除文件),Git都能跟踪到。

  • 暂存区(staging area)

    暂存区域是一个文件,保存了下次将提交的文件列表信息,一般在 Git 仓库目录中。 有时候也被称作”索引”,不过一般说法还是叫暂存区域。

    官方概念其实也说得挺清楚的了,但我在这里再解释一下:
    暂存区里面存放的是在工作目录中将要提交到Git仓库的文件所对应的快照。通俗理解的话,其实可以把暂存区看作特殊化的购物车

  • Git仓库(git repository / directory)

    Git仓库目录是Git用来保存项目的元数据和对象数据库的地方。这是Git中最重要的部分,从其它计算机克隆仓库时,拷贝的就是这里的数据。

    Git仓库目录就是包含于工作目录中名为.git的目录,这个目录里存储了整个项目的历史变动版本,每次提交的更新都是提交到该仓库里的。

  • 基本的Git工作流程如下:
    1. 在工作目录中修改文件;
    2. 暂存文件,将文件的快照放入暂存区域;
    3. 提交更新,找到暂存区域的文件,将快照文件永久性存储到Git仓库目录;

  • 三个区域就对应了三种状态:
    1. 如果Git目录中保存着的特定版本文件,就属于已提交状态。
    2. 如果作了修改并已放入暂存区域,就属于已暂存状态。
    3. 如果自上次取出后,作了修改但还没有放到暂存区域,就是已修改状态。

如果现在就想要快速了解三个区域的具体细节,那就请看这里

使用Git前的初始工作

安装

  • 首先到以下所说的地方下载最新版Git:地址一地址二下载Git安装包,没速度的那肯定要翻墙了,用XX-Net这个免费的翻墙工具即可。

  • 然后就是运行安装包安装了,其中也没什么重要的东西设置的,基本都是下一步即可。

  • 安装完后,我们可以通过2种方法打开Git命令窗口:
    在开始菜单中就能找到Git -> Git Bash,打开它就会弹出一个命令行窗口,
    当然你也可以在任意目录处点鼠标右键也可以看到Git Bash Here

配置

打开Git命令窗口后就可以进行一些必要的初始设置了:

用户名和邮箱地址

1
2
git config --global user.name "User Name"
git config --global user.email "xxxx@yyy.com"

默认情况下每次提交更新到Git仓库时就要用到这些信息。

代理

通常,我们都会将Git配合GitHub进行使用,所以最好还是配置代理
千万别嫌麻烦,由于国内网络的问题,大部分情况对GitHub进行推送更新版本或者克隆版本库时,那龟速简直就是煎熬,大大地降低了生产力。

1
git config --global http.proxy "[protocol://][user[:password]@]proxyhost[:port]"

配置代理一般来说只配置这一条就够了,配置就按照上面所写的规则配置就行。在这举几个实例:

  • XX-Net
    免费的XX-Net代理,这个是最多人用的了,因为方便简单,只需简单几个步骤就能科学上网。

    这里必须写一篇关于ssh的文章。

  • Shadowsocks
    这个代理也十分多人用,免费方式是有的,但更多是自己花钱买VPS搭建Shadowsocks服务端进行代理。
    要花钱的基本就是更稳定的,而为VPS所要支出的费用并不算太多,所以现在也越来越多人通过这种渠道进行科学上网。

设置完后,如果想要检查你的配置,可以使用以下这条命令进行查看:

1
git config --list

1
2
3
4
5
6
7
8
9
10
11
12
13
core.symlinks=false
core.autocrlf=true
core.fscache=true
color.diff=auto
color.status=auto
color.branch=auto
color.interactive=true
help.format=html
http.sslcainfo=E:/Program/Git/mingw64/ssl/certs/ca-bundle.crt
diff.astextplain.textconv=astextplain
rebase.autosquash=true
user.name=ExtremeGTR
user.email=extrememakwan@gmail.com

而当你想查询Git中某些命令的到底有什么作用的时候,你可以通过如下命令进行查看:

1
git help

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
usage: git [--version] [--help] [-C <path>] [-c name=value]
[--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
[-p | --paginate | --no-pager] [--no-replace-objects] [--bare]
[--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
<command> [<args>]

These are common Git commands used in various situations:

start a working area (see also: git help tutorial)
clone Clone a repository into a new directory
init Create an empty Git repository or reinitialize an existing one

work on the current change (see also: git help everyday)
add Add file contents to the index
mv Move or rename a file, a directory, or a symlink
reset Reset current HEAD to the specified state
rm Remove files from the working tree and from the index

examine the history and state (see also: git help revisions)
bisect Use binary search to find the commit that introduced a bug
grep Print lines matching a pattern
log Show commit logs
show Show various types of objects
status Show the working tree status

grow, mark and tweak your common history
branch List, create, or delete branches
checkout Switch branches or restore working tree files
commit Record changes to the repository
diff Show changes between commits, commit and working tree, etc
merge Join two or more development histories together
rebase Reapply commits on top of another base tip
tag Create, list, delete or verify a tag object signed with GPG

collaborate (see also: git help workflows)
fetch Download objects and refs from another repository
pull Fetch from and integrate with another repository or a local branch
push Update remote refs along with associated objects

'git help -a' and 'git help -g' list available subcommands and some
concept guides. See 'git help <command>' or 'git help <concept>'
to read about a specific subcommand or concept.

注意事项

在我们把文件交给Git仓库管理之前,首先我们要十分明确以下几点:

  • 所有版本控制系统只能跟踪文本文件的改动,比如txt文件,各种程序代码文件。
    Git也是一样的,Git并不能强大到跟踪诸如图片、音乐、视频等二进制文件的改动,比如告诉你第3行删除了一个字,第5行增加了一个数字3

  • 一般文本文件都用UTF-8编码,因为这种编码在所有平台都是通用的。

  • 在Windows下,千万不能用自带的记事本编辑UTF-8编码的文件,因为它默认会在文件开头添加0xEFBBBF这3个字节的数据作为标记。
    这个标记被称为BOM(Byte Order Mark)即字节顺序标记,它用于标识该文件是用哪个Unicode字符集进行编码的。

Wikipedia上有关BOM的介绍:

The byte order mark (BOM) is a Unicode character, U+FEFF BYTE ORDER MARK (BOM),
whose appearance as a magic number at the start of a text stream can signal several things to a program consuming the text:

  • What byte order, or endianness, the text stream is stored in;
  • The fact that the text stream is Unicode, to a high level of confidence;
  • Which of several Unicode encodings that text stream is encoded as.

BOM use is optional, and, if used, should appear at the start of the text stream.

用Windows自带的记事本编辑Unicode编码的文件,保存后默认就会被加上一个BOM头,编辑UTF-8编码文件保存后被简称带BOM的UTF-8
所以,我们最好还是用notepad++sublime text等软件进行文本文件的编辑,因为它们都不会给文件加BOM。

体验Git最简单的功能

  现在,我们可以开始进行一些简单的Git操作了,最开始我们最关心而且实现最简单的就是把文件提交到Git仓库管理起来。我们一般通过Git对名为版本库仓库(repository)进行操作。仓库也正如前面所讲,它就是一个名为.git的目录,包含于工作目录中,并且里面存有所有的历史版本。Git跟踪工作目录内的一切变动,并把用户想要记录的变动存储到版本库中。

创建仓库

  • 新建普通目录
    创建仓库非常简单,首先是定位到一个你认为合适的目录,然后再创建一个普通目录,并进入该目录

    1
    2
    mkdir learngit
    cd learngit

    经过这一个步骤,learngit还只是一个普通目录,它并没有被Git管理起来。

  • 初始化仓库
    在之前所创建的普通目录中执行以下这条命令:

    1
    git init

    执行完这条命令后,控制台会显示这么一条信息:

    Initialized empty Git repository in G:/learngit/.git/
    

  Git会在这个普通目录learngit中创建一个名为.git的子目录,这个.git包含了初始化仓库的所有必须的文件,它也用于存储工作目录所有的历史版本信息。同时,这也说明learngit这个普通目录已经是转变为被Git管理起来的工作目录。如果没看到,那你可以设置一下系统显示被隐藏的内容,这个文件夹默认情况下是隐藏的。

验证工作目录是否真的被Git所管理

前面我们已经创建好一个被Git管理的工作目录learngit,里面有任何变动,Git都可以得知,那么现在就来看看这到底是不是真的。

  • 首先在learngit里随便编写一个文本文件,这里我就新建一个readme.txt,内容如下:

    1
    2
    Git is a version control system for tracking changes in computer files and coordinating work on thos files among multiple people.
    This is my first time to use Git!
  • 执行这个命令

    1
    git status

    这个命令可以查看工作目录的状态。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    On branch master  

    Initial commit

    Untracked files:
    (use "git add <file>..." to include in what will be committed)

    readme.txt

    nothing added to commit but untracked files present (use "git add" to track)

    这信息告诉我们:Git发现了一个未被跟踪的文件readme.txt,并提示我们使用git add <file>...命令。
    这很好地说明了工作目录learngit确实是被Git管理起来的,Git都知道我们给工作目录添加了新文件了。

提交更新

我们想把添加新文件readme.txt到工作目录learngit这种变动提交到仓库其实非常简单,只需2步即可:

接下来我们只要做到以下2步即可把readme.txt真正地交给仓库管理:

  • 第1步,执行git add <file>...命令把它添加到暂存区,也相当于之前所说的让Git跟踪此文件。

    1
    git add readme.txt
  • 第2步,执行git commit将暂存区中的快照提交到仓库。

    1
    git commit -m "wrote a readme.txt file"

    -m后面所输入的内容是本次提交readme.txt文件的一些必要的说明。
    平时,我们每次提交都应该写上一些有意义的说明,这样我们在日后某些时间回顾时就更清楚那次所提交的内容大概有什么变动的。

    执行完后Git会显示这些信息:

    1
    2
    3
    [master (root-commit) a3763e7] wrote a readme.txt file
    1 file changed, 2 insertions(+)
    create mode 100644 readme.txt

    此时,添加新文件readme.txt这改动就被提交到Git仓库中了。

小结

所以一般情况下在最开始使用Git只需要4步:

  1. 创建一个空的目录,在该目录下执行git init初始化仓库。
  2. 修改工作目录中的内容,比如添加文件、修改文件等。
  3. 执行git add <file>...命令跟踪文件(添加文件到暂存区)。
  4. 执行git commit命令将刚才添加到暂存区的文件提交给仓库。

Git工作原理细节

之前所演示的只是最简单的把新增1个文件这种变动提交到仓库,步骤上确实很简单,但不明白其中的原理那就无法很好地继续学习Git了。

三个重要区域的具体状态

经过前面一个小节的介绍,概念上的知识是知道不少了,但细节上具体情况是怎样的是完全不清楚。
所以,现在就来看看具体例子里面的细节。在之前那些简单的操作后,工作目录、暂存区、Git仓库三个重要区域所处的状态如下所示:

当前三个重要区域的状态

或许你立马就觉得非常奇怪并产生疑问:
为何暂存区是非空的?暂存区不是暂时存储文件的地方么?完成提交更新提交后,它应该是空的才对啊?

根据前面一个小节的Git官方图示,并结合我们的常规直觉,大多数人都会认为暂存区是空的,其实在那小节的后面我也进行过简要的补充描述了,在这再说一次:把暂存区看作特殊化的购物车,我们都清楚购物完成后购物车是必须清空的,而Git的暂存区工作方式并没有完美契合生活常识直觉,完成提交后,它是不会被清空的,它仍存储着提交到Git仓库的快照

文件状态概念

先来看这张图:

文件的状态变化周期

这个图非常清晰地展示了工作目录中的文件状态是如何变化的。

在工作目录中的文件可被Git标记为多种状态,大体可分为2种:未被跟踪已被跟踪,后者还能细分为3种状态:

  • 未被跟踪(untracked)
    处于未被跟踪这种状态的文件都是未被Git管理的文件,它们即没有被记录于上个版本,也没有被放入暂存区。说白了即Git对它们一无所知。

  • 已被跟踪(tracked)
    处于被跟踪这种状态的文件都是被Git管理的文件,更具体地说指的是那些被纳入版本控制的文件。但这种状态还可细分为其余3种。

    • 未修改(unmodified)
      处于未修改状态的文件指的是该文件在上个版本有它的记录,但现在它没有被改动过,Git就将它标记为未修改的文件。
    • 已修改(modified)
      处于已修改状态的文件指的是该文件在上个版本有它的记录,但现在对它进行改动了,Git就将它标记为已修改的文件。
    • 已暂存(staged)
      处于已暂存状态的文件指的是无论该文件是否存在于上个版本中或被修改过,它被添加到暂存区中,Git就将它标记为已暂存的文件。

检查文件的状态

要查看工作目录中文件的状态十分简单,之前那个小节也用过那个命令了。

1
git status

当时是说该命令是用于查看工作目录的状态,而更确切地说它是用于查看工作目录中所有文件处于什么状态。

下面我要做的是紧接着之前提交过readme.txt文件后继续进行各式各样Git操作演示。

同时这里会给出3个区域的状态图,而其中Git仓库的状态图只是一个初期的简略版,并不算是最准确的,准确来说这些深层的原理是以后要研究的。


查看上次提交过后的状态

执行git status可以看到下面这种信息。

1
2
On branch master
nothing to commit, working tree clean

这里Git给我们说道:没有任何更新需要提交,工作目录非常干净。

这表示:工作目录中所有文件在上次提交更新后,整个工作目录都没有发生任何变动,这就是所谓的工作目录中所有文件都处于未修改状态。

当前3个区域的简单状态图:

  在仓库中,HEAD是一个指针,它指向正在工作的分支,现在它指向的是名为master的分支,并且该分支中有一个id为a3763e7的版本,版本中记录了上次提交的更新readme.txt的文件快照。现在我们只知道这么多就可以了,而关于HEADmaster、分支、版本id这几个点更深入的内容是以后再关注的。


添加新文件

新增一个文件temp.txt文件,并执行git statustemp.txt文件的内容如下:

1
gg my friend!

执行命令后的信息如下:

1
2
3
4
5
6
7
On branch master
Untracked files:
(use "git add <file>..." to include in what will be committed)

temp.txt

nothing added to commit but untracked files present (use "git add" to track)

这里Git明确告诉我们:这里有未被跟踪的文件temp.txt,并叫我们使用git add命令来让它跟踪此文件。

这表示:给工作目录中添加新文件时,该文件就处于未被跟踪状态,我们可以通过git add命令把它添加到暂存区,那么Git才能跟踪它。

添加新文件后3个区域的简单状态图:


暂存新文件

照着Git的提示继续操作,执行git add命令,并再执行git status

1
2
3
git add temp.txt

git status

1
2
3
4
5
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

new file: temp.txt

在我们把temp.txt添加到暂存区后,Git列出了以下更新变动是将要被提交,这里只有添加新文件temp.txt这一变动。

这表示:执行git add命令将文件添加到暂存区后,该文件就处于已暂存状态,已暂存的文件都是将要被提交到仓库的。

暂存新文件后3个区域的简单状态图:

现在看图就更清楚git add做了什么,正如之前所讲,把要提交的文件添加到暂存区中。


修改被版本库记录的文件

这里把文件中的这部分内容进行修改:

1
2
3
Git is a version control system
改成
Git is a Version Control System

修改readme.txt,并执行git status,执行查看状态命令后显示信息如下:

1
2
3
4
5
6
7
8
9
10
11
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

new file: temp.txt

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: readme.txt

这里后面那块信息才是我们要关注的:
Git告知我们与版本库相比较,工作目录中的readme.txt被修改了,但它还没被暂存,并提示我们可以使用git add将这个更新暂存。

这表示:当我们修改曾经提交给版本库的文件,该文件就会从未修改转变为已修改状态。

从状态图可以很清楚看到:新添加的temp.txt已经添加到暂存区,而被修改过的readme.txt还没有添加到暂存区。


暂存已修改的文件

将修改后的readme.txt也添加到暂存区,并执行git status

1
2
3
git add readme.txt

git status

1
2
3
4
5
6
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

modified: readme.txt
new file: temp.txt

现在,我们非常清楚,新添加的文件temp.txt以及已修改的文件readme.txt都被添加到暂存区。


修改已暂存的文件

先不进行提交,再次修改readme.txt,然后执行git status
或许你会想,如果对已经暂存的文件再进行修改,该文件的状态会如何变化呢?

1
2
3
Version Control System
改成
Version Control System(VCS)

执行git status后有以下信息:

1
2
3
4
5
6
7
8
9
10
11
12
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

modified: readme.txt
new file: temp.txt

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: readme.txt

readme.txt居然同时处于已暂存和已修改2种状态!

实际上,readme.txt在暂存区中有一个版本,工作目录中又是另外一个版本。
暂存区里存储的是之前通过git add添加进去的版本,而在工作目录中最新的变动并没有被暂存;
如果现在使用git commit提交当前更新,提交到版本库的readme.txt是暂存区中那个版本而非工作目录中的版本。
所以在一般情况下,在最后提交到版本库之前,我们都应该执行git add命令把所有最新的变动添加到暂存区。

修改已暂存文件后的3个区域的状态图:

从图可以非常清楚看出之前所得出的结论,readme.txt一个文件同时有2个版本,一个比较新的在暂存区,一个最新的在工作目录中。


将最新的变动暂存

把最新的变动添加到暂存区,查看状态,最后提交到版本库

1
2
3
git add readme.txt

git status

1
2
3
4
5
6
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

modified: readme.txt
new file: temp.txt

现在确保所有最新的文件变动都添加到暂存区了,那就可以执行更新提交命令了。

1
git commit -m "wrote a temp.txt file and modified readme.txt"

1
2
3
[master adc1a5a] wrote a temp.txt file and modified readme.txt
2 files changed, 2 insertions(+), 1 deletion(-)
create mode 100644 temp.txt

提交更新后3个区域的简单状态图:

在执行git commit更新后,暂存区的内容都会被提交到仓库中;在仓库master分支中,也增加了一个id为abc1a5a的版本。


将文件状态从已被跟踪转变为未被跟踪

将已被跟踪转变为未被跟踪,即让Git不再管理该文件,Git跟踪文件有2种情况:

  • 文件被暂存
  • 文件已被记录于仓库中

  其实两种情况都用git rm命令就可以完成了,git rm就是从工作目录以及Git中删除文件;不指定任何参数而单纯指定删除文件的话,这样会连工作目录中的文件都删除掉,而这里我们只想将文件转变为未被跟踪状态并且不删除工作目录中的文件,我们只需要给它加上--cached参数即可。

第1种情况:
给工作目录增加一个temp2.txt,然后将它添加到暂存区,并查看状态。

1
2
3
git add temp2.txt

git status

1
2
3
4
5
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

new file: temp2.txt

那么我们就来试试git reset HEAD <file>这个命令能否将temp2.txt还原为未被跟踪的状态。

1
2
3
git reset HEAD temp2.txt

git status
1
2
3
4
5
6
7
On branch master
Untracked files:
(use "git add <file>..." to include in what will be committed)

temp2.txt

nothing added to commit but untracked files present (use "git add" to track)

3个区域的状态图就不用看了,我们现在肯定知道temp2.txt被放入了暂存区,即它处于被跟踪状态,然后又被移出了暂存区,成未被跟踪状态。

情况1演示完了,那就可以先把temp2.txt删除掉,手工删除或是执行rm temp2.txt删除都可以。

第2种情况:

将版本库中记录的temp.txt它的状态转变成未被跟踪的状态。

同样也是执行这个那么命令git rm --cached,并查看状态。

1
2
3
git rm --cached temp.txt

git status
1
2
3
4
5
6
7
8
9
10
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

deleted: temp.txt

Untracked files:
(use "git add <file>..." to include in what will be committed)

temp.txt

现在,被版本库记录的temp.txt也成了未被跟踪状态,只要现在我们提交,那Git就会将它从版本库中删除,而继续让它保留在工作目录中。

我们不用真正把它从版本库中删除,所以只需执行git add temp.txt便可还原之前的状态。

以上就是文件状态转变的所有流程。

常用的Git基础操作

  之前只是演示文件状态的变化,当然,在介绍状态的时候也进行了一些特定的Git操作,现在把那些已经讲过的以及我们没接触过的Git操作单独拿出来讲能够让我们对这些操作所产生的作用了解得更清晰一些。还有一点就是,这里不再大篇幅地叙述内在原理,而是侧重于命令作用的描述。

添加文件到暂存区

git add &lt;file&gt;

git add <file>
这个命令我们再熟悉不过了,前面用过了很多次,这里不再详细介绍。

提交文件更新到仓库

关于提交更新这块的命令都不用做实际例子演示了,毕竟这是非常简单的。

git commit

git commit
这个最简单的提交命令我们也非常熟悉,用它就可以把暂存区中的内容一并提交到仓库中。
一般在控制台中执行该命令后,都会让你输入提交说明的,这里是VIM模式,所以不熟悉的话操作起来挺麻烦的。

  • 首先输入C再按回车,这样就进入编辑模式了,然后就是输入提交说明。
  • 输入提交说明后,按Ctrl+C退出编辑模式,再按SHIFT+两次Z退出VIM模式,这样就算提交成功了。

git commit -m "comment"

git commit -m "comment"
这个带-m参数的命令就是之前我们用得最多的。
用这个带-m参数的命令后面再写上提交说明,这样就不必进入麻烦的VIM模式了,对于新手来说推荐使用这个。

git commit -a

git commit -a
这个带-a参数的命令可以让我们直接“绕过”暂存区,将所有文件更新提交到仓库,这个可以说是更加方便了。
如果需要提交非常多文件,而你又不想手动添加每一个文件到暂存区,那这个命令就起作用了,
这里所说的绕过暂存区其实不是真的没把所有文件添加到暂存区,只不过是这一项工作由Git来做了,
所以说,这个命令的作用相当于把工作目录中的所有文件都提交到了暂存区,然后再把暂存区中内容提交到仓库。
当然,最后说一句的就是使用这个命令提交还是要进入到VIM模式写提交说明的。

git commit -a -m "comment"

git commit -a -m "comment"
既然git commit命令可以带-m-a参数,那么我们就可以大胆尝试让它同时带上-m-a两个参数。
这也是可行的,这样我们就能跳过暂存区提交更新并且不用再进入VIM模式写提交说明了。

查看文件状态

git status

git status
这个命令的作用我们是再熟悉不过了,前一个小节我们用了太多次了。
它用于查看工作目录中所有文件各自的状态。

git status -s或–short

git status -s或–short
这2个命令的作用是一样的,它们也是用于查看工作目录中所有文件各自的状态,但它们输出的状态信息是git status的简览版。

使用这条命令所输出的文件状态信息格式都像这样:

1
状态标识符 文件名

这非常好理解:文件名对应的文件处于状态标识符所对应的状态。

Git使用2位字符来描述状态标识符,下面是所有状态标识符以及它们各自所代表的状态:

1
2
3
4
5
?? - 新文件且未被跟踪
A - 新文件且已暂存,A在左边
M - 已修改且已暂存,M在左边
M - 已修改但未暂存,M在右边
MM - 同时处于上面这2种状态,修改已暂存的文件时就会有这种状态

比如:有新添加的文件temp.txt,并且它被添加到暂存区了,同时有readme.txt被修改但还未被添加到暂存区。
简览版状态信息:

1
2
A  temp.txt
M readme.txt

这就可以非常快速地看到temp.txtreadme.txt所处的状态。

查看文件具体差异

我们随时都可以通过git status查看文件处于什么状态从而得知它进行了哪种修改,但这不能让我们得知文件具体修改了什么。
git diff命令就能立功了,顾名思义,diffdifference的缩写,因此使用它能够让我们非常清楚地知道那些被修改过的文件的具体变动是啥。

git diff

git diff
比较工作目录与暂存区这两处所有文件一一对应的差异,然后列出有差异的文件以及其差异部分。

在这里就继续用之前那个名为learngit的工作目录进行演示。

  • 修改readme.txt文件
    修改其中的内容:

    1
    2
    3
    4
    5
    6
    7
    Version Control System(VCS)
    改成
    Version Control System

    work on thos files
    改成
    work on those files

    并且在最后增加一行新内容:

    1
    Git development began in April 2005,
  • 修改temp.txt文件

    1
    2
    3
    gg my friend!
    改成
    gg my friends!
  • 此时我们再运行git diff就会看到

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    diff --git a/readme.txt b/readme.txt
    index aa2e41f..71c60c4 100644
    --- a/readme.txt
    +++ b/readme.txt
    @@ -1,2 +1,3 @@
    -Git is a Version Control System(VCS) for tracking changes in computer files and coordinating work on thos files
    among multiple people.
    -This is my first time to use Git!
    \ No newline at end of file
    +Git is a Version Control System for tracking changes in computer files and coordinating work on those files
    among multiple people.
    +This is my first time to use Git!
    +Git development began in April 2005,
    \ No newline at end of file
    diff --git a/temp.txt b/temp.txt
    index fd7c469..daa5eed 100644
    --- a/temp.txt
    +++ b/temp.txt
    @@ -1 +1 @@
    -gg my friend!
    \ No newline at end of file
    +gg my friends!
    \ No newline at end of file

    这些就是文件差异的详细信息,只要我们修改过文件,执行该命令后Git都会列出来。

    其中-前缀的是旧版本的内容,+前缀的是我们修改后的内容,一进行对比哪里改了哪里没动就一目了然了。

    1
    \ No newline at end of file

    这是Git的提示信息,告诉我们在文件末尾没有换行。只要给文件加上换行,这个提示信息就不会再出现了,所以都给它们加上换行。

git diff −−cached或staged

git diff −−cached或staged
同样是比较所有文件的差异,但它所比较的是暂存区与仓库中当前分支的最新版本之间的差异

当我们将已修改的temp.txtreadme.txt都添加到暂存区,再运行git diff,Git不会显示任何信息,
这很正常,因为这个命令是对比工作目录与暂存区,现在已修改的文件都被添加到暂存区了,它们所存储的内容都是一致的。
但如果我们想要继续对比暂存区的内容与上次所提交到仓库的内容两者之间的差别,
这就要给这个无参命令加上--cached--staged了(后者是Git 1.6.1及更高版本才允许使用的)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
diff --git a/readme.txt b/readme.txt
index aa2e41f..518e3bb 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,2 +1,3 @@
-Git is a Version Control System(VCS) for tracking changes in computer files and coordinating work on thos files
among multiple people.
-This is my first time to use Git!
\ No newline at end of file
+Git is a Version Control System for tracking changes in computer files and coordinating work on those
files among multiple people.
+This is my first time to use Git!
+Git development began in April 2005,
diff --git a/temp.txt b/temp.txt
index fd7c469..64db368 100644
--- a/temp.txt
+++ b/temp.txt
@@ -1 +1 @@
-gg my friend!
\ No newline at end of file
+gg my friends!

这里所显示的信息还是一样的,之前我也是给这2文件增加了换行,所以新版本也没有了\ No new line at end of file的提示信息。

git diff [−−cached或staged] &lt;file&gt

git diff [−−cached或staged] <file>
比较差异的命令,不仅可以一次性比较所有文件两两对应的异同,而且可以针对单个文件两两进行比较,用法很简单,在后头指定文件名即可。

现在我们就指定比较暂存区与上个提交到仓库的temp.txt文件。

1
git diff --staged temp.txt

1
2
3
4
5
6
7
8
diff --git a/temp.txt b/temp.txt
index fd7c469..64db368 100644
--- a/temp.txt
+++ b/temp.txt
@@ -1 +1 @@
-gg my friend!
\ No newline at end of file
+gg my friends!


撤销操作

git diff HEAD &lt;file&gt

git diff HEAD <file>
这个命令的作用是:撤销指定的暂存文件。更通俗地说就是将之前所添加到暂存区中文件内容用最新版本(即上一个版本所记录)的内容覆盖掉。

上一个操作,我们修改了readme.txt以及temp.txt并把它们添加到暂存区,现在我们可以用这个命令来撤销暂存这2个文件。

1
git reset HEAD readme.txt
1
2
Unstaged changes after reset:
M readme.txt
1
git reset HEAD temp.txt
1
2
3
Unstaged changes after reset:
M readme.txt
M temp.txt

现在我们再执行git status查看一下所有文件的状态:

1
2
3
4
5
6
7
8
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: readme.txt
modified: temp.txt

no changes added to commit (use "git add" and/or "git commit -a")

从输出信息来看,现在readme.txttemp.txt2个文件都被Git撤销暂存了,其实是Git把上个版本记录的内容覆盖到暂存区中。
所以我们之前修改完并添加到暂存区的内容被覆盖掉了,所以那些被修改过的文件都会再次处于未暂存的状态。

  • git checkout -- <file>
    checkout是检出的意思,所以我们可以直接推断这个命令就是检出指定的文件。
    其实这个命令的作用Git在输出一些提示信息的时候就已经告诉我们了:

    1
    (use "git checkout -- <file>..." to discard changes in working directory)

    直接翻译过来的意思就是用这个命令来丢弃在工作目录中的修改,更深入来说就是从暂存区检出文件覆盖掉工作目录中对应的文件

    所以我们可以通过使用这个命令来直接丢弃工作目录中未暂存的修改(把文件恢复到修改前的状态)。

    这里我想把temp.txt恢复到之前的状态,那么就可以执行以下命令:

    1
    git checkout -- temp.txt

    执行完后没有任何输出信息,但再查看状态时,就会发现现在只有readme.txt处于已修改状态,并且temp.txt已经恢复到修改前的内容。

    1
    2
    3
    4
    5
    6
    7
    8
    On branch 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: readme.txt

    no changes added to commit (use "git add" and/or "git commit -a")

git checkout HEAD −− &lt;file&gt

git checkout HEAD −− <file>
这个命令的作用与之前那个差不多,但这个命令是从最新版本检出文件覆盖掉暂存区以及工作目录中对应的文件
这个命令更加暴力,如果在查看文件的时发现改错了,但你又把它添加到暂存区,这时想彻彻底底地恢复到上个版本的内容,那直接执行它便可。

小结

将已修改文件许恢复到未修改前的状态有2种情况:

  • 已修改文件未暂存:
    那就直接用git reset HEAD <file>就行了。
  • 已修改文件已暂存:
    首先用git reset HEAD <file>,然后再用git checkout -- <file>或者更直接的执行git checkout HEAD -- <file>

这2个命令用起来你或许会觉得很迷糊,现阶段你只要知道它就这么用的就行了,但要搞清楚其根本原理性,那就一定要完全理解暂存区才行。

删除文件

rm &lt;file&gt

rm <file>
这个命令的作用:删除工作目录中的指定文件。这么简单的命令就不作过多演示了,直接跳过。

git rm &lt;file&gt

git rm <file>
其实用git help就可以查看这个命令的作用:

Remove files from the working tree and from the index

翻译过来就是从工作目录以及索引(其实指的是暂存区)中移除指定文件。这么说如果真正想不再把文件纳入版本库的管理,那只要提交便可。

现在,版本库还在管理着temp.txt,我们就是用这个命令对它进行删除。

1
git rm temp.txt
1
rm 'temp.txt'
1
git status
1
2
3
4
5
6
7
8
9
10
11
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

deleted: temp.txt

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: readme.txt

现在我们可以看到temp.txt被我们删除这一改动被添加到暂存区了,同时我们打开learngit目录,也发现temp.txt文件已被删除了。

其实执行这个命令就相当于执行了2条命令:

1
2
3
4
rm temp.txt
git rm temp.txt

git add temp.txt

执行第1条命令进行本地文件的删除,执行第二条命令是将删除文件这种变动添加到暂存区。

现在我们再把暂存区中的内容提交,那么temp.txt就被真正删除掉,不会再纳入到最新版本的管理,当然我们还是可以从以前的版本找到它。

1
git commit -a -m "modified readme.txt and deleted temp.txt"
1
2
3
[master 5ab2a6b] modified readme.txt and deleted temp.txt
2 files changed, 3 insertions(+), 3 deletions(-)
delete mode 100644 temp.txt

git rm −−cached &lt;file&gt

git rm −−cached <file>
这个命令的作用其实和之前所说那条差不多,但最终效果有一点点区别而已。
使指定文件不再纳入版本库的管理,但本地文件仍然保留在硬盘中,可以说这是软删除。

移动文件

git mv &lt;file_from&gt &lt;file_to&gt

git mv <file_from> <file_to>
这个命令的作用查看git帮助即可:

Move or rename a file, a directory, or a symlink


哦,现在知道了,它是用于移动或重命名一个文件、目录或符号链接,在此就不再详细演示。

查看版本库提交历史

git log

git log
查看版本库提交历史再简单不过了,直接执行git log即可大功告成,当然它还能附加很多参数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
commit 5ab2a6b585e776c01365c5b6172bb67f1627a786
Author: ExtremeGTR <extrememakwan@gmail.com>
Date: Wed Mar 8 15:05:20 2017 +0800

modified readme.txt and deleted temp.txt

commit adc1a5a0f5e4fb9a0da0f95d3c22576cc788dfdb
Author: ExtremeGTR <extrememakwan@gmail.com>
Date: Sat Mar 4 15:42:35 2017 +0800

wrote a temp.txt file and modified readme.txt

commit a3763e7d95ace909b1f90d35e1c36c0627f03f3d
Author: ExtremeGTR <extrememakwan@gmail.com>
Date: Sat Mar 4 09:40:59 2017 +0800

wrote a readme.txt file

直接执行我们就可以看到这些提交历史信息,我们之前总共提交过3次,所以这里也显示的信息没错。

而这个命令最常用的参数就是-p--stat,前者用于查看每次提交内容的详细差异,后者是显示媒体提交内容的总结性统计信息。

git log −−stat

git log −−stat
1
git log --stat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
commit 5ab2a6b585e776c01365c5b6172bb67f1627a786
Author: ExtremeGTR <extrememakwan@gmail.com>
Date: Wed Mar 8 15:05:20 2017 +0800

modified readme.txt and deleted temp.txt

readme.txt | 5 +++--
temp.txt | 1 -
2 files changed, 3 insertions(+), 3 deletions(-)

commit adc1a5a0f5e4fb9a0da0f95d3c22576cc788dfdb
Author: ExtremeGTR <extrememakwan@gmail.com>
Date: Sat Mar 4 15:42:35 2017 +0800

wrote a temp.txt file and modified readme.txt

readme.txt | 2 +-
temp.txt | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)

commit a3763e7d95ace909b1f90d35e1c36c0627f03f3d
Author: ExtremeGTR <extrememakwan@gmail.com>
Date: Sat Mar 4 09:40:59 2017 +0800

wrote a readme.txt file

readme.txt | 2 ++
1 file changed, 2 insertions(+)

git loggit log --stat这两个命令所显示的日志信息必有的4点:版本号作者以及其邮箱提交时间提交信息

git log −−pretty=oneline

git log −−pretty=oneline
这个就是显示简略版日志信息。
1
2
3
5ab2a6b585e776c01365c5b6172bb67f1627a786 modified readme.txt and deleted temp.txt
adc1a5a0f5e4fb9a0da0f95d3c22576cc788dfdb wrote a temp.txt file and modified readme.txt
a3763e7d95ace909b1f90d35e1c36c0627f03f3d wrote a readme.txt file

其中--pretty后面指定的值不一定是oneline,这些值可以是:oneline,short,full,fuller 和 format(后跟指定格式)

总结

好了,现在就来一下子总结归纳全部Git命令吧。只要我们熟练这些命令就能够顺利操作Git的基本功能了。

git add <file>               将指定文件添加到暂存区。
git commit                   将更新记录到版本库。
git status                   查看工作目录中所有文件的状态。
git diff                     查看工作目录与暂存区两者的文件详细差异。
git reset HEAD <file>        用最新版本的文件覆盖掉暂存区中对应的文件。
git checkout -- <file>       从暂存区中检出文件覆盖掉工作目录中对应的文件。
git checkout HEAD -- <file>  从最新版本检出文件覆盖掉暂存区以及工作目录中对应的文件。
git rm <file>                从工作目录以及暂存区中移除指定文件。
git rm --cached <file>       使指定文件不再纳入版本库的管理,但本地文件仍然保留在硬盘中。
git log                      查看版本库提交历史信息。    

mkdir dir                    创建目录
rm <file>                    删除指定的本地文件
mv <file_from> <file_to>     移动或重命名文件、目录或符号链接。

参考资料