本文篇幅很长,通过详细浏览官方文档,带你了解git基础操作。
除基于学习为目的合理引用外,本文未经授权不得以商业目的进行任何搬运或转载,本文作者依法享有所有权益。

发现知识盲区

事情是这样的,我本地的某个项目在当初创建目录并进行Git初始化后,由于没有在.gitignore文件内添加正确的内容,导致现在编辑了一段时间才发现VSCode显示有4K+挂起的更改。

2022-156 (1)

仔细一看,好家伙,果然是把依赖包文件夹node_modules这个庞然大物给算进去了,此外还有一些该项目运行时生成的临时目录和缓存目录等。这些文件都是不需要进行版本控制和备份的,所以现在需要解决这个问题。

执行 git status 查看工作区文件状态,node_modules/ 赫然在清单中,但由于我的这个项目一次都没有git add过,所以全部都是未跟踪状态(Untracked)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ git status
On branch master

No commits yet

Untracked files:
(use "git add <file>..." to include in what will be committed)
.gitignore
docs/
node_modules/
package.json
yarn.lock

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

我直接分情况说解决办法,想知道原理可以浏览Git官方文档。

情况一

如果这些无关文件还从来没有被执行过 git add,也就是还没有添加进暂存区,像我的这个项目一样是属于刚创建的,所有文件还是未跟踪(Untracked)状态,还没有被纳入版本控制,那么这个解决方式适合你。

怎么做呢?现在你要做的是赶紧去项目根目录创建 .gitignore 文件,并把要忽略的文件正确地添加上。添加完成后再执行 git status 查看工作区文件状态,就会发现 node_modules/ 等文件已经不在“未跟踪”清单中了:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ git status
On branch master

No commits yet

Untracked files:
(use "git add <file>..." to include in what will be committed)
.gitignore
docs/
package.json
yarn.lock

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

接下来就可以放心地进行 git addgit commitgit push 等操作了。

情况二

如果这些无关文件已经被执行过 git add,也就是添加进了暂存区,目前是已跟踪(Tracked)状态,纳入了版本控制,甚至都已经提交了,那么这个解决方式适合你。

解决这个问题涉及到了git的移除文件命令。我们想把文件从 Git 仓库中删除(亦即从暂存区域移除),但仍然希望保留在当前工作目录中。 换句话说,你想让文件保留在磁盘,但是并不想让 Git 继续跟踪。 当你忘记添加 .gitignore 文件,不小心把一个很大的日志文件或一堆 .a 这样的编译生成文件添加到暂存区时,这一做法尤其有用。 为达到这一目的,使用 git rm 命令的 --cached 选项。这个命令把这个文件从已追踪里踢出来。

上面这段话你可能看不懂,先别管那么多,先说解决办法,再来了解为什么。解决办法就是执行 git rm --cached 文件或目录名,后面那个参数是你之前忘记添加到 .gitignore 文件中的文件名或目录名,比如:

1
2
3
4
5
6
# 忽略任何目录下名为 node_modules 的文件夹
# 其中参数-r表示递归,递归删除整个目录中的所有子目录和文件
git rm -r --cached node_modules/

# 忽略 README.md 文件
git rm --cached README.md

必要时可直接执行 git rm -rf --cached . 命令,移除当前暂存区所有文件。

无论如何,执行完后别忘了赶紧去 .gitignore 文件把要忽略的文件正确地添加上,接下来就可以放心地进行 git addgit commitgit push 等操作了。

情况三(奇怪的方块)

这种情况是我这次真实遇到的莫名bug,浪费我一晚上时间,神经病!!!你们当成个笑话看吧,或者当成一个离奇的故事听听,估计全球就我遇到了,并且我也没办法复现出来……

按理说我这次本应该是情况一,也就是直接添加完 .gitignore 的内容就能生效了。我的这个项目需要添加这仨:

1
2
3
node_modules/
.temp
.cache

但是我发现添加完 .gitignore 后,VSCode 侧边栏依旧提示有 4K+未跟踪文件(Untracked) 的变更。使用 git status 查看文件状态,未跟踪列表依旧有 node_modules 等无关文件。并且执行 git add . 命令,依然会把 node_modules 等无关文件添加到缓存区,也就是说我的 .gitignore 文件根本未生效!!!而且这真是一个漫长的过程,我盯着终端面板迅速飘过的一坨坨进度日志陷入了沉思……

不过还好,按照情况二来执行 git rm -rf --cached . 命令,可以将暂存区所有文件移除,让它们重新变成未跟踪状态,也就是变回情况一,问题再次回到原点。

所以究竟是为什么?为什么明明是情况一的状况,但添加 .gitignore 文件却无法生效?问题到底是出在 .gitignore 吗?难道是我文件名称拼写错了吗,并没有错呀?里边内容除了那三行,什么都没有呀?所以究竟是为什么?我盯着终端面板,再次陷入了沉思……

在我交替执行了多次 git add .git rm -rf --cached . 命令的过程中,偶然间我发现使用命令 cat .gitignore 列出要忽略的文件时,它打印出了如下内容:

1
2
3
4
$ cat .gitignore
▒▒node_modules/
.temp
.cache

那个白色方块是什么?默认就有的吗?为什么只在 node_modules/ 前面有?

我突然警惕了起来,打开了另一个包含 node_modules/ 的项目,使用命令 cat .gitignore 列出的文件列表,看到打印结果我惊呼,人家别的项目并没有那个奇怪的方块啊!

难道是 .gitignore 文件里不小心复制进去了不可见字符?VSCode看不出,但是终端却能打印出,那肯定是混进字符了啊!于是我直接清空了 .gitignore 文件,再次使用命令 cat .gitignore

1
2
$ cat .gitignore
▒▒

卧槽不带这样玩的啊!我傻眼了,我都把 .gitignore 清空了兄嘚!你那个白色方块生命力这么顽强的吗?

好吧!你无情,那就休怪我无义!我他喵的反手把 .gitignore 给删了,然后重新创建,重新添加下面这仨:

1
2
3
node_modules/
.temp
.cache

再使用命令 cat .gitignore 试了试,这次打印竟然正常了,小白块已经死无葬身之地了:

1
2
3
4
$ cat .gitignore
node_modules/
.temp
.cache

并且我注意到,VSCode 侧边栏未跟踪文件(Untracked)的变更数量从 4K+ 直接将为了 22。执行 git status 查看文件状态,未跟踪列表已经没有了 node_modules 等无关文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ git status
On branch master

No commits yet

Untracked files:
(use "git add <file>..." to include in what will be committed)
.gitignore
docs/
package.json
yarn.lock

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

执行命令 git add . 也变得正常了,要忽略的文件都没有添加到暂存区。事已至此,终于解决。

所以,究竟是为什么呢?我尝试把该白块再复制到 .gitignore 文件,但它是能被看到的,并非不可见字符。刚刚遇到的那种情况我竟然无法重新复现出来……

可能这就是玄学吧,可能这就是生活的玩笑吧,也可能是三体人派出的智子阻碍我推动地球人类的科技进步吧。(笑


其实对于我遇到的这个问题,最简单的解决方法是直接删掉 .git,然后重新 git init 初始化。哈哈确实,这是最简单的办法了,毕竟是新创建项目。

OK,现在已经可以解决问题了,你已经可以止步于此,退出去忙你的事情吧。

但是,你真的不想知道为什么要这么做吗?求知欲是学习进步的重要动力之一。下面是我浏览Git官网后吸收消化的内容,可选择性浏览,我还是建议自己去看原文档。


Git起步

git中文文档:https://git-scm.com/book/zh/v2

关于Git

许多人习惯用复制整个项目目录的方式来保存不同的版本,或许还会改名加上备份时间以示区别。 这么做唯一的好处就是简单,但是特别容易犯错。 有时候会混淆所在的工作目录,一不小心会写错文件或者覆盖意想外的文件。

版本控制是一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统。有了它你就可以将选定的文件回溯到之前的状态,甚至将整个项目都回退到过去某个时间点的状态,你可以比较文件的变化细节,查出最后是谁修改了哪个地方,从而找出导致怪异问题出现的原因,又是谁在何时报告了某个功能缺陷等等。 使用版本控制系统通常还意味着,就算你乱来一气把整个项目中的文件改的改删的删,你也照样可以轻松恢复到原先的样子,但额外增加的工作量却微乎其微。

像 Git、Mercurial、Bazaar 以及 Darcs 等,都属于分布式版本控制系统(Distributed Version Control System,简称 DVCS),它们使得开发者可以协同工作。客户端并不只提取最新版本的文件快照, 而是把代码仓库完整地镜像下来,包括完整的历史记录。 这么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库恢复。 因为每一次的克隆操作,实际上都是一次对代码仓库的完整备份。

Git的三种状态

  • Git 有三种状态,你的文件可能处于其中之一:

    1. 已修改(modified):已修改表示修改了文件,但还没保存到数据库中。
    2. 已暂存(staged):已暂存表示对一个已修改文件的当前版本做了标记,使之包含在下次提交的快照中。
    3. 已提交(committed):已提交表示数据已经安全地保存在本地数据库中。
  • 这会让我们的 Git 项目拥有三个阶段:

    1. 【工作区】Working Directory,工作区是对项目的某个版本独立提取出来的内容。 这些从 Git 仓库的压缩数据库中提取出来的文件,放在磁盘上供你使用或修改。就是你在电脑里能看到的目录。
    2. 【暂存区】Staging Area,暂存区是一个文件,保存了下次将要提交的文件列表信息,一般在 Git 仓库目录中。 按照 Git 的术语叫做“索引”,英文叫 stage 或 index,不过一般说法还是叫“暂存区”。
    3. 【Git目录(版本库)】.git directory(Repository),Git 仓库目录是 Git 用来保存项目的元数据和对象数据库的地方。 这是 Git 中最重要的部分,从其它计算机克隆仓库时,复制的就是这里的数据。工作区能看到的那个 .git 隐藏目录,就是 Git 的版本库,它不算工作区。

2022-156 (2)

  • 基本的 Git 工作流程如下:

    1. 在工作区中修改文件。
    2. 将你想要下次提交的更改选择性地暂存,这样只会将更改的部分添加到暂存区。
    3. 提交更新,找到暂存区的文件,将快照永久性存储到 Git 目录。
  • 所以可以用以下三条来描述文件状态:

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

Git基础

1. 获取Git仓库

关于Git的安装和配置这里不再赘述,请前往官网或其他文档了解。

  • 通常有两种获取 Git 项目仓库的方式,两种方式都会在你的本地机器上得到一个工作就绪的 Git 仓库:
    1. 将尚未进行版本控制的本地目录转换为 Git 仓库,即在已存在的目录中初始化一个git仓库,属于无中生有。
    2. 从其它服务器克隆一个已存在的 Git 仓库,即拷贝其他地方的git仓库,属于直接搬运过来。

(1)初始化本地目录为git仓库

如果想新建的话可以使用命令 mkdir 名称 创建空文件夹,然后使用 cd 命令进入项目目录。

不新建的话,也要进入项目目录才能执行下方初始化命令:

1
git init

该命令将创建一个名为 .git 的子目录,这个子目录含有你初始化的 Git 仓库中所有的必须文件,这些文件是 Git 仓库的骨干。 但是,在这个时候,我们仅仅是做了一个初始化的操作,你的项目里的文件还没有被跟踪。现在,你已经得到了一个存在被追踪文件与初始提交的 Git 仓库。(参见 Git 内部原理 来了解更多关于到底 .git 文件夹中包含了哪些文件的信息。)

(2)克隆现有的仓库

如果你想获得一份已经存在了的 Git 仓库的拷贝,比如说,你想为某个开源项目贡献自己的一份力,这时就要用到 git clone 命令。Git 克隆的是该 Git 仓库服务器上的几乎所有数据,而不是仅仅复制完成你的工作所需要文件。 当你执行 git clone 命令的时候,默认配置下远程 Git 仓库中的每一个文件的每一个版本都将被拉取下来。 事实上,如果你的服务器的磁盘坏掉了,你通常可以使用任何一个克隆下来的用户端来重建服务器上的仓库 (虽然可能会丢失某些服务器端的钩子(hook)设置,但是所有版本的数据仍在,详见 在服务器上搭建 Git )。

克隆仓库的命令是 git clone <repo> 。 比如,要克隆 Git 的链接库为 BFUI-docs,可以用下面的命令:

1
git clone https://github.com/Barry-Flynn/BFUI-docs.git

这会在当前目录下创建一个名为 BFUI-docs 的目录,并在这个目录下初始化一个 .git 文件夹, 从远程仓库拉取下所有数据放入 .git 文件夹,然后从中读取最新版本的文件的拷贝。 如果你进入到这个新建的 BFUI-docs 文件夹,你会发现所有的项目文件已经在里面了(咱们看到的是该git项目的工作区),准备就绪等待后续的开发和使用。

Git 支持多种数据传输协议。 上面的例子使用的是 https:// 协议,不过你也可以使用 git:// 协议或者使用 SSH 传输协议,比如 git@github.com:Barry-Flynn/BFUI-docs.git

2. 记录每次更新到仓库

这部分内容比较抽象,需要结合命令来理解,建议仔细看官网来消化:2.2 Git 基础 - 记录每次更新到仓库

现在我们的机器上有了一个 真实项目 的 Git 仓库,并从这个仓库中检出了所有文件的 工作副本。 通常,你会对这些文件做些修改,每当完成了一个阶段的目标,想要将记录下它时,就将它提交到仓库。

  • 请记住,你工作目录下的每一个文件都不外乎这两种状态:
    1. 已跟踪(tracked):已跟踪的文件是指那些被纳入了版本控制的文件,在上一次快照中有它们的记录,在工作一段时间后, 它们的状态可能是未修改已修改已放入暂存区。简而言之,已跟踪的文件就是 Git 已经知道的文件。
    2. 未跟踪(untracked):工作目录中除已跟踪文件外的其它所有文件都属于未跟踪文件,它们既不存在于上次快照的记录中,也没有被放入暂存区。 初次克隆某个仓库的时候,工作目录中的所有文件都属于已跟踪文件,并处于未修改状态,因为 Git 刚刚检出了它们, 而你尚未编辑过它们。

编辑过某些文件之后,由于自上次提交后你对它们做了修改,Git 将它们标记为已修改文件(modified)。 在工作时,你可以选择性地将这些修改过的文件放入暂存区,然后提交所有已暂存的修改,如此反复。

git status 命令

  • 可以用 git status 命令查看哪些文件处于什么状态。
    1. nothing to commit, working directory clean:这说明你现在的工作目录相当干净。换句话说,所有已跟踪文件在上次提交后都未被更改过。 此外,上面的信息还表明,当前目录下没有出现任何处于未跟踪状态的新文件,否则 Git 会在这里列出来。
    2. Untracked files::在状态报告中如果看到有文件出现在 Untracked files 下面,说明他们是未跟踪的文件。意味着 Git 在之前的快照(提交)中没有这些文件;Git 不会自动将之纳入跟踪范围,除非你明明白白地告诉它“我需要跟踪该文件”。 这样的处理让你不必担心将生成的二进制文件或其它不想被跟踪的文件包含进来。使用命令 git add 可以开始跟踪一个文件。
    3. Changes to be committed::只要在 Changes to be committed 这行下面的,就说明是已暂存状态,这意味着你跟踪了这个文件,且距离上次 git add 后还没有进行任何修改。 如果此时提交,那么该文件在你运行 git add 时的版本将被留存在后续的历史记录中。 git add 命令后面跟的参数可以使用文件或目录的路径;如果参数是目录的路径,该命令将递归地跟踪该目录下的所有文件。
    4. Changes not staged for commit::文件出现在 Changes not staged for commit 这行下面,说明已跟踪文件的内容发生了变化,但还没有放到暂存区,你需要暂存这些已修改的文件。 要暂存这次更新,也需要运行 git add 命令。 这是个多功能命令:可以用它开始跟踪新文件,或者把已跟踪并进行了修改的文件放到暂存区,还能用于合并时把有冲突的文件标记为已解决状态等。 将这个命令理解为“精确地将内容添加到下一次提交中”而不是“将一个文件添加到项目中”要更加合适。
    5. Changes to be committed:Changes not staged for commit::也就是某个文件同时出现在了暂存区非暂存区,什么意思?好吧,实际上 Git 暂存的是你上次运行 git add 命令时文件的版本。 如果你现在运行 git commit 提交,提交的会是之前那个版本,而不是你目前在工作目录中看到的当前版本。 所以,运行了 git add 之后又作了修订的文件,需要重新运行 git add 把当前的最新版本重新暂存起来。

git status –short 命令

  • 如果觉得 git status 命令输出的内容太多了,可以用 git status -s 命令或 git status --short 命令,你将得到一种格式更为紧凑的输出。输出中有两栏,左栏指明了暂存区的状态,右栏指明了工作区的状态。(下面第一行是命令)
    1
    2
    3
    4
    5
    6
    $ git status -s
    M README
    MM Rakefile
    A lib/git.rb
    M lib/simplegit.rb
    ?? LICENSE.txt
    1. M表示:已修改过的文件,但尚未暂存
    2. MM表示:已修改,暂存后又作了修改,但新修改的尚未暂存
    3. A表示:新添加到暂存区中的文件
    4. ??表示:新添加的未跟踪文件

gitignore 忽略文件

一般我们总会有些文件无需纳入 Git 的管理,也不希望它们总出现在未跟踪文件列表。 通常都是些自动生成的文件,比如日志文件,或者编译过程中创建的临时文件等。 在这种情况下,我们可以创建一个名为 .gitignore 的文件,列出要忽略的文件的模式。 来看一个实际的 .gitignore (下面带$符号的那一行是命令,命令下面才是文件内容):

1
2
3
$ cat .gitignore
*.[oa]
*~

文件内容第一行告诉 Git 忽略所有以 .o.a 结尾的文件。一般这类对象文件和存档文件都是编译过程中出现的。 第二行告诉 Git 忽略所有名字以波浪符(~)结尾的文件,许多文本编辑软件(比如 Emacs)都用这样的文件名保存副本。 此外,你可能还需要忽略 log,tmp 或者 pid 目录,以及自动生成的文档等等。

要养成一开始就为新仓库设置好 .gitignore 文件的习惯,以免将来误提交这类无用的文件!(呜呜这说的不就是我嘛)

  • 文件 .gitignore 的格式规范如下:
    1. 所有空行或者以 # 开头的行都会被 Git 忽略。(也就是说你可以在#后面写注释)
    2. 可以使用标准的 glob 模式匹配,它会递归地应用在整个工作区中。
    3. 匹配模式可以以(/)开头防止递归。
    4. 匹配模式可以以(/)结尾指定目录。
    5. 要忽略指定模式以外的文件或目录,可以在模式前加上叹号(!)取反。

所谓的 glob 模式是指 shell 所使用的简化了的正则表达式。 星号(*)匹配零个或多个任意字符;[abc] 匹配任何一个列在方括号中的字符 (这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c); 问号(?)只匹配一个任意字符;如果在方括号中使用短划线分隔两个字符, 表示所有在这两个字符范围内的都可以匹配(比如 [0-9] 表示匹配所有 0 到 9 的数字)。 使用两个星号(**)表示匹配任意中间目录,比如 a/**/z 可以匹配 a/za/b/za/b/c/z 等。

我们再看一个 .gitignore 文件的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 忽略所有的 .a 文件
*.a

# 但跟踪所有的 lib.a,即便你在前面忽略了 .a 文件
!lib.a

# 只忽略当前目录下的 TODO 文件,而不忽略 subdir/TODO
/TODO

# 忽略任何目录下名为 build 的文件夹
build/

# 忽略 doc/notes.txt,但不忽略 doc/server/arch.txt
doc/*.txt

# 忽略 doc/ 目录及其所有子目录下的 .pdf 文件
doc/**/*.pdf

提示:GitHub 有一个十分详细的针对数十种项目及语言的 .gitignore 文件列表, 你可以在 https://github.com/github/gitignore 找到它。
注意:在最简单的情况下,一个仓库可能只根目录下有一个 .gitignore 文件,它递归地应用到整个仓库中。 然而,子目录下也可以有额外的 .gitignore 文件。子目录中的 .gitignore 文件中的规则只作用于它所在的目录中。 (Linux 内核的源码库拥有 206 个 .gitignore 文件。)多个 .gitignore 文件的具体细节超出了本文的范围,更多详情见 man gitignore

git diff 命令

此命令用于查看已暂存和未暂存的修改。

如果 git status 命令的输出对于你来说过于简略,而你想知道具体修改了什么地方,可以用 git diff 命令。 稍后我们会详细介绍 git diff,你通常可能会用它来回答这两个问题:当前做的哪些更新尚未暂存? 有哪些更新已暂存并准备好下次提交? 虽然 git status 已经通过在相应栏下列出文件名的方式回答了这个问题,但 git diff 能通过文件补丁的格式更加具体地显示哪些行发生了改变。

要查看尚未暂存的文件更新了哪些部分,不加参数直接输入 git diff。此命令比较的是工作目录中当前文件暂存区域快照之间的差异。 也就是修改之后还没有暂存起来的变化内容

请注意,git diff 本身只显示尚未暂存的改动,而不是自上次提交以来所做的所有改动。 所以有时候你一下子git add暂存了所有更新过的文件,运行 git diff 后却什么也没有,就是这个原因。

git diff –staged 命令

若要查看已暂存的将要添加到下次提交里的内容,可以用 git diff --staged 命令。 这条命令将比对已暂存文件最后一次提交的文件差异:

git diff –cached 命令

若要查看已经暂存起来的变化,可以用 git diff --cached 命令。

--staged--cached 是同义词,两个效果是一样的。

注意:在本书中,我们使用 git diff 来分析文件差异。 但是你也可以使用图形化的工具或外部 diff 工具来比较差异。 可以使用 git difftool 命令来调用 emerge 或 vimdiff 等软件(包括商业软件)输出 diff 的分析结果。 使用 git difftool --tool-help 命令来看你的系统支持哪些 Git Diff 插件。

提交更新

现在的暂存区已经准备就绪,可以提交了。 在此之前,请务必确认还有什么已修改或新建的文件还没有 git add 过, 否则提交的时候不会记录这些尚未暂存的变化。 这些已修改但未暂存的文件只会保留在本地磁盘。 所以,每次准备提交前,先用 git status 看下,你所需要的文件是不是都已暂存起来了, 然后再运行提交命令 git commit

1
$ git commit

这样会启动你选择的文本编辑器来输入提交说明。

注意:启动的编辑器是通过 Shell 的环境变量 EDITOR 指定的,一般为 vim 或 emacs。 当然也可以按照 Git文档入门部分 介绍的方式, 使用 git config --global core.editor 命令设置你喜欢的编辑器。

编辑器会显示类似下面的文本信息(本例选用 Vim 的屏显方式展示):

1
2
3
4
5
6
7
8
9
10
11
12
13
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# Your branch is up-to-date with 'origin/master'.
#
# Changes to be committed:
# new file: README
# modified: CONTRIBUTING.md
#
~
~
~
".git/COMMIT_EDITMSG" 9L, 283C

可以看到,默认的提交消息包含最后一次运行 git status 的输出,放在注释行里,另外开头还有一个空行,供你输入提交说明。 你完全可以去掉这些注释行,不过留着也没关系,多少能帮你回想起这次更新的内容有哪些。

注意:更详细的内容修改提示可以用 -v 选项查看,这会将你所作的更改的 diff 输出呈现在编辑器中,以便让你知道本次提交具体作出哪些修改。

在开头的空行输入提交说明,然后就可以退出编辑器了。Git 会丢弃注释行,用你输入的提交说明生成一次提交。

退出Vim编辑器的方法是按下Esc键,按完之后光标会跑到最下面,这样Vim退出了write模式,进入了命令模式。一般是输入:wq然会回车就可以保存并退出了,以下命令仅作了解即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
:q  
//退出

:q!
//退出且不保存(:quit!的缩写)

:wq
//保存并退出

:wq!
//保存并退出即使文件没有写入权限(强制保存退出)

:x
//保存并退出(类似:wq,但是只有在有更改的情况下才保存)

:exit
//保存并退出(和:x相同)

:qa
//退出所有(:quitall的缩写)

:cq
//退出且不保存(即便有错误)

另外,如果你觉得每次 git commit 时还得在Vim等编辑器中输入说明,还得退出,很麻烦。那么你可以在 commit 命令后添加 -m 选项,将提交信息与命令放在同一行,就不用进入编辑器内了,如下所示(其中Story 182: Fix benchmarks for speed是提交信息):

1
2
3
4
$ git commit -m "Story 182: Fix benchmarks for speed"
[master 463dc4f] Story 182: Fix benchmarks for speed
2 files changed, 2 insertions(+)
create mode 100644 README

好,现在你已经创建了第一个提交! 可以看到,提交后它会告诉你,当前是在哪个分支(master)提交的,本次提交的完整 SHA-1 校验和 是什么(463dc4f),以及在本次提交中,有多少文件修订过,多少行添加和删改过。

请记住,提交时记录的是放在暂存区域的快照。 任何还未暂存文件的仍然保持已修改状态,可以在下次提交时纳入版本管理。 每一次运行提交操作,都是对你项目作一次快照,以后可以回到这个状态,或者进行比较。

跳过使用暂存区域

尽管使用暂存区域的方式可以精心准备要提交的细节,但有时候这么做略显繁琐。 Git 提供了一个跳过使用暂存区域的方式, 只要在提交的时候,给 git commit 加上 -a 选项,Git 就会自动把所有已经跟踪过的文件暂存起来一并提交,从而跳过 git add 步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ 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: CONTRIBUTING.md

no changes added to commit (use "git add" and/or "git commit -a")
$ git commit -a -m 'added new benchmarks'
[master 83e38c7] added new benchmarks
1 file changed, 5 insertions(+), 0 deletions(-)

看到了吗?提交之前不再需要 git add 文件“CONTRIBUTING.md”了。 这是因为 -a 选项使本次提交包含了所有修改过的文件。 这很方便,但是要小心,有时这个选项会将不需要的文件添加到提交中。

移除文件

要从 Git 中移除某个文件,就必须要从已跟踪文件清单中移除(确切地说,是从暂存区域移除),然后提交。

情况1-连带从工作目录删除

可以用 git rm 命令完成此项工作,并连带从工作目录中删除指定的文件,这样以后就不会出现在未跟踪文件清单中了。

如果只是简单地从工作目录中手工删除文件,运行 git status 时就会在 “Changes not staged for commit” 部分(也就是 未暂存清单)看到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 删除某文件
$ rm PROJECTS.md

# 查看Git工作目录文件状态
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)

deleted: PROJECTS.md

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

然后再运行 git rm 记录此次移除文件的操作:

1
2
3
4
5
6
7
8
9
10
# 从Git工作目录中删除指定的文件
$ git rm PROJECTS.md
rm 'PROJECTS.md'
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

deleted: PROJECTS.md

其中 git rm 命令后面可以列出文件或者目录的名字,也可以使用 glob 模式。比如下方命令会删除所有名字以 ~ 结尾的文件。:

1
$ git rm \*~

下一次提交时,该文件就不再纳入版本管理了。 如果要删除之前修改过已经放到暂存区的文件,则必须使用强制删除选项 -f(译注:即 force 的首字母)。 这是一种安全特性,用于防止误删尚未添加到快照的数据,这样的数据不能被 Git 恢复。

情况2-保留工作目录文件

另外一种情况是,我们想把文件从 Git 仓库中删除(亦即从暂存区域移除),但仍然希望保留在当前工作目录中。 换句话说,你想让文件保留在磁盘,但是并不想让 Git 继续跟踪。 当你忘记添加 .gitignore 文件,不小心把一个很大的日志文件或一堆 .a 这样的编译生成文件添加到暂存区时,这一做法尤其有用。 为达到这一目的,使用 --cached 选项,这个命令把这个文件从已追踪里踢出来。:

1
$ git rm --cached README

如果想删除文件夹,可以加上参数 -r表示递归。即 git rm -r --cache 文件夹名/ 表示递归删除整个目录中的所有子目录和文件,比如:

1
2
# 忽略任何目录下名为 node_modules 的文件夹
git rm -r --cached node_modules/

移动文件

不像其它的 VCS 系统,Git 并不显式跟踪文件移动操作。 如果在 Git 中重命名了某个文件,仓库中存储的元数据并不会体现出这是一次改名操作。 不过 Git 非常聪明,它会推断出究竟发生了什么,至于具体是如何做到的,我们稍后再谈。

既然如此,当你看到 Git 的 mv 命令时一定会困惑不已。 要在 Git 中对文件改名,可以这么做:

1
$ git mv file_from file_to

它会恰如预期般正常工作。 实际上,即便此时查看状态信息,也会明白无误地看到关于重命名操作的说明:

1
2
3
4
5
6
7
8
$ git mv README.md README
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

renamed: README.md -> README

其实,运行 git mv 就相当于运行了下面三条命令:

1
2
3
4
5
6
7
8
# 将README.md重命名为README
$ mv README.md README

# 从Git中删除README.md
$ git rm README.md

# 将README添加到暂存区
$ git add README

如此分开操作,Git 也会意识到这是一次重命名,所以不管何种方式结果都一样。 两者唯一的区别在于,git mv 是一条命令而非三条命令,直接使用 git mv 方便得多。 不过在使用其他工具重命名文件时,记得在提交前 git rm 删除旧文件名,再 git add 添加新文件名。

3. 查看提交历史

请前往官方文档:2.3 Git 基础 - 查看提交历史

4. 撤消操作

请前往官方文档:2.4 Git 基础 - 撤消操作

5. 远程仓库的使用

请前往官方文档:2.5 Git 基础 - 远程仓库的使用

6. 打标签

请前往官方文档:2.6 Git 基础 - 打标签

7. Git 别名

请前往官方文档:2.7 Git 基础 - Git 别名

8. 总结

现在,你应该能完成所有 Git 基本的本地操作了:创建或克隆一个仓库、进行更改、暂存并提交这些更改、浏览仓库从创建到现在的所有更改历史。

接下来,你就可以学习 Git 的杀手级特性:分支模型


参考内容: