跳转至

Git

Git 原理

Book (git-scm.com) 的笔记。

一个 Git仓库 中的 .git/ 目录下一般有四个关键的文件/目录: - HEAD 文件:用于指向当前被检出的分支 - index 文件(还未被创建):用于保存暂存区信息 - objects 目录:存储所有的 Git Objects - refs 目录:存储指向数据的提交对象的指针

一、Git Objects

Git 是一个 content-addressable 文件系统,其核心其实是一个简单的 键值对数据库。 因此,你可以向 Git 仓库插入任何类型的内容,对应的 Git 会借助哈希返回一个唯一的键值,通过键值可以在任意时刻再次取回内容。

前面提到 Git 的核心其实是一个 键值对数据库,而键通过哈希得到。

如果你随便找一个仓库,逛一逛它的 .git/objects 目录,可以发现里面有很多两个字符的命名的文件夹在其内有一个或多个38个字符命名的文件。将它们连接在一起可以得到一个40个字符的完整的 SHA-1值,这便是数据的 键值,而数据本身就被存储在文件中。

为了存储不同的信息,Git 对象主要有三种:blobtreecommit

  • tree 对象:存储目录结构
  • commit 对象:存储提交信息

下面将依次进行讲解。

1. blob 对象

blob 对象用于存储文件内容数据。

Git 的一个底层命令 git hash-object 可以计算并返回传入的数据,也可以将其写入 .gits/objects 目录(Git Objects 数据库),下面我们将使用这个命令来进行一些尝试。


首先初始化一个新的仓库:

Bash Session
1
2
3
4
$ mkdir GitPlayground
$ cd GitPlayground
$ git init
Initialized empty Git repository in /mnt/d/_Dev/GitPlayground/.git/

在仓库刚被创建的时候 .git/objects 目录会被初始化,其中有两个子目录 infopack,不过目前 .git/objcets 目录中没有任何一个文件:

Bash Session
1
2
3
4
5
$ find .git/objects
.git/objects
.git/objects/info
.git/objects/pack
$ find .git/objects -type f

使用 git hash-object 创建一个新的数据对象并使用 -w 指示 Git 将其存储到数据库中:

Bash Session
$ echo 'test content' | git hash-object -w --stdin
d670460b4b4aece5915caf5c68d12f560a9fe3e4

它返回了一个 40 个字符长度的字符串,这是数据 test content 的 SHA-1 哈希值。

现在再查看一下 .git/objects 中的内容:

Bash Session
$ find .git/objects -type f
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4

Git 将上面的 SHA-1 哈希值的前 2 个字符作为子目录名,后面 38个字符作为文件名将数据存储为文件。


下面介绍另一个命令 git cat-file,这个命令可以用来很方便地查看 Git Objects 的内容:

Bash Session
$ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
test content

下面创建一个新文件并将其写入数据库:

Bash Session
1
2
3
$ echo 'version 1' > test.txt
$ git hash-object -w test.txt
83baae61804e65cc73a7201a7252750c76066a30

然后修改其内容,再写入数据库:

Bash Session
1
2
3
$ echo 'version 2' > test.txt
$ git hash-object -w test.txt
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a

现在 .git/objects 中就会包含三个文件,分别存储了先前的字符串以及 test.txt 的两个版本:

Bash Session
1
2
3
4
$ find .git/objects -type f
.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4

现在就算我们将 test.txt 删除,也可以通过唯一的键值获取到对应版本的内容:

Bash Session
1
2
3
$ git cat-file -p 83baae61804e65cc73a7201a7252750c76066a30 > test.txt
$ cat test.txt
version 1
Bash Session
1
2
3
$ git cat-file -p 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a > test.txt
$ cat test.txt
version 2

这就是 blob 对象:

Bash Session
$ git cat-file -t 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
blob

不过你其实可以注意到,blob 对象只能够存储文件的数据内容,而不能存储目录结构以及文件名等信息。

2. tree 对象

Tree 对象用于存储存储目录结构(文件路径、文件名等)。 快照其实就是存储根目录信息的 tree 对象。


这里先以一个假设的仓库为例解释一下 tree 对象的概念:

假设有一个仓库,其最新的 tree 如下:

Bash Session
1
2
3
4
$ git cat-file -p master^{tree}
100644 blob a906cb2a4a904a152e80877d4088654daad0c859      README
100644 blob 8f94139338f9404f26296befa88755fc2598c289      Rakefile
040000 tree 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0      lib

master^{tree} 指定了 master 分支最新的提交所指向的 tree 对象。

可以看到 tree 对象的内容包含一系列 Git 对象的关联模式、类型、哈希值以及文件名。 这与 Unix 的文件系统很相似,不过是经过简化的。

如果进一步查看 lib 对象的内容可以得到:

Bash Session
$ git cat-file -p 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0
100644 blob 47c6340d6459e05787f644c2447d2595f5d3a54b      simplegit.rb

其结构可以用下面这张图来表示:

image-20230324205004043


接下来进行一些尝试:

Git 创建 tree 时会使用 暂存区 或 索引 的状态来创建,所以我们要想创建一个 tree 对象,也需要通过暂存一些文件来创建索引。

以一个单入口 test.txt 文件为例:

Bash Session
$ git update-index --add --cacheinfo \
  100644 83baae61804e65cc73a7201a7252750c76066a30 test.txt

通过 git update-index 命令来更新索引,使用 --add 是因为 test.txt 目前并不在暂存区内(甚至暂存区都还未创建),使用 --cacheinfo 是因为 test.txt 目前不在目录中而是在数据库中。 之后指定模式、哈希值、文件名。

100644 表示是一个普通文件,其他更多的模式比如 100755 表示可执行文件,120000 表示一个符号链接。

现在索引创建完毕,可以使用 git write-tree 来将暂存区写入到 tree 对象中并保存进数据库。

Bash Session
1
2
3
4
$ git write-tree
d8329fc1cc938780ffdd9f94e0d364e0ea74f579
$ git cat-file -p d8329fc1cc938780ffdd9f94e0d364e0ea74f579
100644 blob 83baae61804e65cc73a7201a7252750c76066a30      test.txt

接下来再创建一个由第二个版本的 test.txt 以及一个新文件 new.txt 组成的 tree 对象:

Bash Session
1
2
3
4
$ echo 'new file' > new.txt
$ git update-index --cacheinfo 100644 \
  1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
$ git update-index --add new.txt
Bash Session
1
2
3
4
5
$ git write-tree
0155eb4229851634a0f03eb265b69f5a2d56f341
$ git cat-file -p 0155eb4229851634a0f03eb265b69f5a2d56f341
100644 blob fa49b077972391ad58037050f2a75f74e3671e92      new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a      test.txt

接下来可以通过 git read-tree 来读取 tree 对象的内容并放到暂存区内,我们取出第一个 tree 的内容置于 bak 目录(使用 --prefix 可以指定存储 tree 对象的目录)然后再创建一个 tree 对象:

Bash Session
1
2
3
4
5
6
7
$ git read-tree --prefix=bak d8329fc1cc938780ffdd9f94e0d364e0ea74f579
$ git write-tree
3c4e9cd789d88d8d89c1073707c3585e41b0e614
$ git cat-file -p 3c4e9cd789d88d8d89c1073707c3585e41b0e614
040000 tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579      bak
100644 blob fa49b077972391ad58037050f2a75f74e3671e92      new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a      test.txt

现在整个仓库的状态可以用下图表示:

image-20230324205016509

这便是 tree 对象。

3. commit 对象

到目前为止,blobtree 对象虽然可以存储所有文件及目录的信息,但是仍旧没有保存下来有关谁在何时为何保存了快照的信息,而这些信息就由 commit 对象保存。

可以通过 git commit-tree 并指定一个 tree 对象来创建 commit 对象:

Bash Session
$ echo 'First commit' | git commit-tree d8329f
fdf4fc3344e67ab068f836878b6c4951e3b15f3d
Bash Session
1
2
3
4
5
6
$ git cat-file -p fdf4fc3
tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
author Scott Chacon <schacon@gmail.com> 1243040974 -0700
committer Scott Chacon <schacon@gmail.com> 1243040974 -0700

First commit

一个 commit 对象包含以下内容: - 用于表示当前快照的顶级的 tree 对象 - 前一个 commit 对象(如果有) - 作者和提交者的相关信息(用户名称以及邮箱还有时间戳) - 提交信息

下面再创建两个 commit 对象,并使用 -p 来指定前一个提交:

Bash Session
1
2
3
4
$ echo 'Second commit' | git commit-tree 0155eb -p fdf4fc3
cac0cab538b970a37ea1e769cbbde608743bc96d
$ echo 'Third commit'  | git commit-tree 3c4e9c -p cac0cab
1a410efbd13591db07496601ebc7a059dd55cfe9

其实目前,我们几乎通过手动操作得到了一个实际的 Git 仓库,可以使用 git log 来查看历史记录:

Bash Session
$ git log --stat 1a410e
commit 1a410efbd13591db07496601ebc7a059dd55cfe9
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri May 22 18:15:24 2009 -0700

    Third commit

 bak/test.txt | 1 +
 1 file changed, 1 insertion(+)

commit cac0cab538b970a37ea1e769cbbde608743bc96d
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri May 22 18:14:29 2009 -0700

    Second commit

 new.txt  | 1 +
 test.txt | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

commit fdf4fc3344e67ab068f836878b6c4951e3b15f3d
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri May 22 18:09:34 2009 -0700

    First commit

 test.txt | 1 +
 1 file changed, 1 insertion(+)

现在再查看一下 .git/objects(注释表示存储的内容):

Bash Session
$ find .git/objects -type f
.git/objects/01/55eb4229851634a0f03eb265b69f5a2d56f341 # tree 2
.git/objects/1a/410efbd13591db07496601ebc7a059dd55cfe9 # commit 3
.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a # test.txt v2
.git/objects/3c/4e9cd789d88d8d89c1073707c3585e41b0e614 # tree 3
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30 # test.txt v1
.git/objects/ca/c0cab538b970a37ea1e769cbbde608743bc96d # commit 2
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4 # 'test content'
.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579 # tree 1
.git/objects/fa/49b077972391ad58037050f2a75f74e3671e92 # new.txt
.git/objects/fd/f4fc3344e67ab068f836878b6c4951e3b15f3d # commit 1

整个仓库的内容可以表示为下图:

image-20230324205031736

二、Git References

到目前为止,我们从 Git仓库 取东西都需要一个对应对象的哈希值,Git引用 就是一个特殊的文件,通过保存不同的哈希值来动态地指向不同的 Git对象,他们被存储在 .git/refs 目录下。

对于我们刚才手动创建的“仓库”,目前并没有任何引用:

Bash Session
1
2
3
4
5
$ find .git/refs
.git/refs
.git/refs/heads
.git/refs/tags
$ find .git/refs -type f

若要创建一个新引用来帮助记忆最新提交所在的位置,从技术上讲我们只需简单地做如下操作:

Bash Session
$ echo 1a410efbd13591db07496601ebc7a059dd55cfe9 > .git/refs/heads/master

现在,你就可以在 Git 命令中使用这个刚创建的新引用来代替 SHA-1 值了:

Bash Session
1
2
3
4
$ git log --pretty=oneline master
1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit

不过并不建议直接手动修改文件, 如果想更新某个引用,Git 提供了一个更加安全的命令 update-ref 来完成此事:

Bash Session
$ git update-ref refs/heads/master 1a410efbd13591db07496601ebc7a059dd55cfe9

这基本就是 Git 分支的本质:一个指向某一系列提交之首的指针或引用。 若想在第二个提交上创建一个分支,可以这么做:

Bash Session
$ git update-ref refs/heads/test cac0ca

这个分支将只包含从第二个提交开始往前追溯的记录:

Bash Session
1
2
3
$ git log --pretty=oneline test
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit

现在,仓库看起来会像是这样:

image-20230324205042844

1. HEAD 引用

HEAD 文件通常是一个符号引用(symbolic reference),指向目前所在的分支。 所谓符号引用,表示它是一个指向其他引用的指针。

然而在某些罕见的情况下,HEAD 文件可能会包含一个 git 对象的 SHA-1 值。 当你在检出一个标签、提交或远程分支,让你的仓库变成 “分离 HEAD”状态时,就会出现这种情况。

如果查看 HEAD 文件的内容,通常我们看到类似这样的内容:

Bash Session
$ cat .git/HEAD
ref: refs/heads/master

如果执行 git checkout test,Git 会像这样更新 HEAD 文件:

Bash Session
$ cat .git/HEAD
ref: refs/heads/test

当我们执行 git commit 时,该命令会创建一个提交对象,并用 HEAD 文件中那个引用所指向的 SHA-1 值设置其父提交字段。

你也可以手动编辑该文件,然而同样存在一个更安全的命令来完成此事:git symbolic-ref。 可以借助此命令来查看 HEAD 引用对应的值:

Bash Session
$ git symbolic-ref HEAD
refs/heads/master

同样可以设置 HEAD 引用的值:

Bash Session
1
2
3
$ git symbolic-ref HEAD refs/heads/test
$ cat .git/HEAD
ref: refs/heads/test

不能把符号引用设置为一个不符合引用规范的值:

Bash Session
$ git symbolic-ref HEAD test
fatal: Refusing to point HEAD outside of refs/

2. Tags 引用

前面我们刚讨论过 Git 的三种主要的对象类型(数据对象树对象提交对象 ),然而实际上还有第四种。 标签对象(tag object) 非常类似于一个提交对象——它包含一个标签创建者信息、一个日期、一段注释信息,以及一个指针。 主要的区别在于,标签对象通常指向一个提交对象,而不是一个树对象。 它像是一个永不移动的分支引用——永远指向同一个提交对象,只不过给这个提交对象加上一个更友好的名字罢了。

『Git』0. 概述

T21:46:00+08:00 什么是 git?是一款免费、开源、分布式 版本控制系统。那么版本管理系统又是什么?

一、什么是版本管理

版本管理系统Version Control System or VCS)即记录一个或一组文件随着时间的推移所发生的变化,以便你以后可以找回特定的版本。日常计算机使用中对一个文件的最常见最简单的的版本管理就是 撤销/重做 系统,然而版本管理并不局限于单个文件以及文件的类型,它完全可以扩展到对一个目录内所有内容的管理。

一个被用于版本管理的最关键的工具就是 修订管理系统Revision Control System or RCS),它的工作原理是在每一次文件更新时记录文件不同版本之间的差异(也叫做补丁集),并将其以一种特殊的格式保存在磁盘上;然后它可以通过将所有的补丁相加来重新创建任何文件在任何时间点的样子。

1. 中心化 VCS

Pasted image 20221130204212

也就是将版本文件存储在一个中央服务器上,所有其他人从这个服务器中获取最新版本的文件,修改后再传回服务器。 然而这有一些坏处,如果服务器寄了,那就寄了。在本地管理也是如此,硬盘寄了,那就寄了。

2. 分布式 VCS

Pasted image 20221130204218

各个客户不再只是检查文件的最新快照;相反,他们将所有版本信息,包括其完整的历史,完全保存。因此,如果有任何服务器寄了,任何一个客户端的仓库都可以被复制到服务器上,以恢复它。每个克隆实际上是所有数据的完整备份。

而 git,就是一个分布式的版本管理系统。

二、什么是 git

Git 与其他版本管理系统相比,存储和思考信息的方式非常不同,因此如果你理解了什么是 Git 以及它如何工作的基本原理,那么有效地使用 Git 对你来说可能就会容易得多。

Git 存储快照 而非差异

首先,Git 与其他任何 VCS(CVS、Subversion、Perforce、Bazaar等等)的主要区别在于 Git 对其数据的思考方式。

git并不记录随着时间推移产生的不同文件版本之间的差异,而是记录一个完整的文件快照。每次提交或保存项目状态时,Git 基本上都会拍下所有文件在那一刻的样子,并存储对该快照的引用。为了提高效率,如果文件没有变化,Git不会再次存储该文件,而只是存储一个与之前相同的文件的链接。

『Git』1. 仓库

T21:47:00+08:00 本节内容:

  • git init 初始化空仓库
  • git clone 从远端克隆下来一个仓库

仓库 是受 git 管理的项目根目录及其内部的所有内容,它包含被管理的文件以及一个 .git 目录(其中存放着 git 用于管理版本而产生的数据文件)。

通常获取 Git 仓库有两种方式: 1. 将尚未进行版本控制的本地目录转换为 Git 仓库 2. 从其它服务器 克隆 一个已存在的 Git 仓库。

一、在尚未进行版本控制的目录中初始化仓库

比如我们有这样一个目录:

Text Only
1
2
3
 my_project/
 |- main.cpp
 |- main.exe

首先,你需要进入到想要管理的项目根目录中,然后执行初始化命令:

Bash
cd my_project/
git init

执行完毕后,你将看到多了一个名为 .git 的子目录,这个子目录含有你初始化的 Git 仓库中所有的必须文件,这些文件是 Git 仓库的骨干,不过通常你不必管它。

Diff
1
2
3
4
 my_project/
+|- .git/
 |- main.cpp
 |- main.exe

如果你所在的目录中已经存在一些文件,初始化仓库并不会自动让他们被 Git 跟踪,你还需要 [向 git 中添加文件]。

Bash
git add *
git commit -m "initial commit"

二、克隆现有的仓库

克隆仓库的命令是 git clone <url> 。 比如,要克隆 Git 的链接库 libgit2,可以用下面的命令:

Bash
$ git clone https://github.com/libgit2/libgit2

这会在当前目录下创建一个名为 “libgit2” 的目录,并在这个目录下初始化一个 .git 文件夹, 从远程仓库拉取下所有数据放入 .git 文件夹,然后从中读取最新版本的文件的拷贝放在目录下。

当然你也可以指定存储的目录名:

Bash
$ git clone https://github.com/libgit2/libgit2 mylibgit

『Git』2. 记录更新

T21:48:00+08:00 本节内容:

  • git status 查看仓库状态
  • git add 开始跟踪文件
  • git add 暂存文件
  • git rm 移除文件(取消跟踪)
  • git commit 提交更新
  • git log 显示所有的提交日志

一、Git 中的工作流程

首先,在 Git 仓库中,任何文件都不外乎这两种状态:

  • 已跟踪:被纳入了版本管理,在上一次快照中有它们的记录

    对于这类文件有三种状态:已提交、已修改、已暂存

    • 已提交:文件当前版本的快照已被保存

    • 已修改:修改了文件,但没有被保存

    • 已暂存:标记了文件的当前版本做了标记,会使之包含在下次提交的快照中

  • 未跟踪:没有被纳入版本管理

对应着这些文件被存储在不同的地方:

  • 工作区:也就是展现在外面的文件,这里是对项目的某个版本独立提取出来的内容,供你使用或更改。

  • 暂存区.git/ 目录中的一个文件,保存了下次将要提交的文件列表信息。

  • .git 目录:Git 用来保存项目的元数据和对象数据库的地方。

基本的 Git 工作流程如下: 1. 在工作区中修改文件。 2. 将你想要下次提交的更改选择性地暂存,这样只会将更改的部分添加到暂存区。 3. 提交更新,找到暂存区的文件,将快照永久性存储到 Git 目录。

二、跟踪文件

还是上一个例子,比如有这样一个目录:

Diff
1
2
3
 my_project/
 |- main.cpp
 |- main.exe

我们来对仓库进行初始化:

Bash
git init
Diff
1
2
3
4
 my_project/
+|- .git/
 |- main.cpp
 |- main.exe

此时,我们可以通过下面的命令来查看当前仓库的状态:

Bash
git status

其输出如下:

Text Only
On branch master

No commits yet

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

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

它告诉我们 main.cppmain.exe 没有被跟踪,要想跟踪一个文件,要执行如下命令:

Bash
git add main.cpp main.exe

[!tip] 当然它支持通配符,所以也可以这么写来添加所有文件名为 main 后缀不限的文件:git add main.*,也可以这样子添加所有文件:git add * 当然也可以用 git add .

我们再执行 git status 查看一下仓库状态:

Text Only
1
2
3
4
5
6
7
8
On branch master

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
        new file:   main.cpp
        new file:   main.exe

现在文件就被跟踪了。

如果我们此时修改一下 main.cpp,再查看状态:

Text Only
On branch master

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
        new file:   main.cpp
        new file:   main.exe

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   main.cpp

二、暂存与提交文件

暂存的目的是标记有哪些文件需要被提交,如果一个文件是刚被添加到 git 版本管理中的,那么它便是被暂存的。

也就是说刚才我们通过 git addmain.cppmain.exe 跟踪后,他们便是暂存的,你也可以看到在 git status 中也显示他们是 Changes to be committed,而我们又对 main.cpp 做了修改,这时候这个修改便没有被暂存,git status 显示其为 Changes not staged for commit

我们可以先提交一下更改:

Bash
git commit -m "Initial commit"

通过 -m 参数指定提交的信息。

输出:

Text Only
1
2
3
 2 files changed, 8 insertions(+)
 create mode 100644 main.cpp
 create mode 100644 main.exe

再来看看状态:

Text Only
1
2
3
4
5
6
7
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   main.cpp

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

可以看到只有暂存的更改被提交了。

我们来将刚才修改过的 main.cpp 暂存一下,这个命令依旧是 git add

Text Only
git add main.cpp

再来看一下状态:

Text Only
1
2
3
4
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   main.cpp

再进行提交,输出:

Text Only
[master e0afb9c] updated main.cpp
 1 file changed, 1 insertion(+), 1 deletion(-)

最终的状态:

Text Only
On branch master
nothing to commit, working tree clean

三、.gitignore文件 以及 文件的移除

有时候我们不希望一些文件被 git管理,比如本例子中的 main.exe 这类的构建文件,它是由源代码文件 main.cpp 编译出来的,因此我们常常不对这类文件进行管理。git 提供了一个方法来忽略一些特定的文件、目录,那就是 .gitignore 文件。

在项目根目录创建一个 .gitignore 文件:

Diff
1
2
3
4
5
 my_project/
 |- .git/
+|- .gitignore
 |- main.cpp
 |- main.exe

在里面写入以下内容:

Text Only
*.exe

这时我们查看状态会显示:

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

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

我们再重新编译刚才我们修改后的 main.cppmain.exea.exe

Text Only
g++ main.cpp -o main
g++ main.cpp -o a

这时我们再查看状态我们会发现,main.exe 的修改,以及 a.exe 的新增都没有出现:

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

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

我们对 .gitignore 加入跟踪,并且提交:

Bash
git add .
git commit -m "Added .gitignore"

但是其实,再文件的快照版本中,main.exe 依旧是存在的,只是这一次提交的快照中并没有包括对其的修改,而 a.exe 是不存在的,因为它还没有被跟踪。

从 git 中移除一个文件(取消跟踪的同时也会将其从工作区删除)的方式如下:

Bash
git rm main.exe

[!info] 正常情况下删除了某个文件后的暂存操作也是使用 git rm。 也就是说 git rm 命令的作用是暂存对文件的移除,如果文件存在也会实际的将文件移除。

Diff
1
2
3
4
5
 my_project/
 |- .git/
 |- .gitignore
 |- main.cpp
-|- main.exe

我们再查看状态:

Text Only
1
2
3
4
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        deleted:    main.exe

这时,main.exe 便被停止跟踪了,同时也被从本地目录删除。

再将其提交:

Bash
git commit -m "Removed main.exe"

到此为止我们做了很多的修改,我们可以通过 git log 来查看所有的提交(最新的提交会在最上面):

Text Only
commit 901aff72d7be178eb8626096bbab2a1cacb6ede6 (HEAD -> master)
Author: Azur冰弦 <973562770@qq.com>
Date:   Thu Dec 1 18:17:39 2022 +0800

    Removed main.exe

commit bff6c0e2f04628b372c715f6a04f07b34752a47f
Author: Azur冰弦 <973562770@qq.com>
Date:   Thu Dec 1 18:12:47 2022 +0800

    Added .gitignore

commit e0afb9cb05b556c0934ff059ea1aa234bac8e620
Author: Azur冰弦 <973562770@qq.com>
Date:   Thu Dec 1 10:13:45 2022 +0800

    updated main.cpp

commit 34ef417c28ab016015d36f70ceafc6cb9ad4f9a3
Author: Azur冰弦 <973562770@qq.com>
Date:   Thu Dec 1 10:12:27 2022 +0800

    Initial commit