

## Git 快照的本质

Git 并不会像一些版本控制系统那样每次保存文件的完整副本（比如 Subversion）；相反，Git 会对文件的状态进行索引，利用**快照（snapshot）** 来记录文件的内容。每次执行 `git commit` 时，Git 会将**暂存区（staging area）** 中的文件状态保存为一个新的快照。

- 这个快照并不是直接存储文件本身，而是存储文件内容的哈希值。
- 具体来说，Git 对每个文件生成一个唯一的 **SHA-1 哈希值**，并通过这些哈希值来追踪每个文件的变化。


![](https://git-scm.com/book/zh/v2/images/snapshots.png)
 
 **`git commit`**  是保存快照的主要时机。当你执行 commit 时，Git 会：
 扫描暂存区中的所有文件，检查哪些文件被修改或新增。
生成一个新的快照来记录这些文件的内容（每个文件的哈希值）。
这时，Git 并不会记录所有文件的实际内容，而是通过哈希值来引用这些内容。这使得 Git 存储的版本信息相当高效 。


> Git 更像是把数据看作是对小型文件系统的一系列快照。如果没有修改，Git 不再重新存储该文件，而是只保留一个链接指向之前存储的文件。Git 对待数据更像是一个 **快照流**。



Git 中所有的数据在存储前都计算校验和，然后以校验和来引用。 这意味着不可能在 Git 不知情时更改任何文件内容或目录内容。 这个功能建构在 Git 底层，是构成 Git 哲学不可或缺的部分。 若你在传送过程中丢失信息或损坏文件，Git 就能发现。

Git 用以计算校验和的机制叫做 SHA-1 散列（hash，哈希）。 这是一个由 40 个十六进制字符（0-9 和 a-f）组成的字符串，基于 Git 中文件的内容或目录结构计算出来。 SHA-1 哈希看起来是这样：

`24b9da6552252987aa493b52f8696cd6d3b00373`

Git 中使用这种哈希值的情况很多，你将经常看到这种哈希值。 实际上，Git 数据库中保存的信息都是以文件内容的哈希值来索引，而不是文件名。

Git 默认保存文件快照，即保存每个文件每个版本的完整内容。

## 初始配置
当我们安装好 Git 后，还需要在 Git bash 或者 terminal 进行一些相关设置，以下设置仅需设置一次即可。

```bash
git config --global user.name "Your Name"
git config --global user.email "email@example.com"

除此之外，Git还有很多设置，包括常用编辑器等，大家可以键入以下命令查看自己的设置并进行修改。
git config --list
```

## 一、Git 仓库的获取方式

### 1. 在已存在目录中初始化仓库


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

Windows 路径示例
`$ cd /c/user/my_project`
初始化 Git 仓库
`$ git init`

执行后会创建 `.git` 子目录，包含所有必需的仓库文件。此时项目文件尚未被跟踪。


 ### 对已存在文件进行版本控制

跟踪指定文件
`$ git add <文件名>`

提交初始版本
`$ git commit -m "初始提交"`

### 克隆现有仓库
这时就要用到 git clone 命令。Git 克隆的是该 Git 仓库服务器上的几乎所有数据，而不是仅仅复制完成你的工作所需要文件。 当你执行 git clone 命令的时候，默认配置下远程 Git 仓库中的每一个文件的每一个版本都将被拉取下来。 事实上，如果你的服务器的磁盘坏掉了，你通常可以使用任何一个克隆下来的用户端来重建服务器上的仓库 （虽然可能会丢失某些服务器端的钩子（hook）设置，但是所有版本的数据仍在，详见 在服务器上搭建 Git ）。

`$ git clone <远程仓库地址>`
克隆会拉取远程仓库的所有版本数据，自动设置master分支。




### 文件状态管理

|状态|描述|
|---|---|
|未跟踪（Untracked）|新文件，尚未被 Git 纳入版本控制|
|已跟踪（Tracked）|已纳入版本控制，又分为：|
|→ 未修改（Unmodified）|文件内容与上次提交一致|
|→ 已修改（Modified）|文件内容已变更，未放入暂存区|
|→ 已暂存（Staged）|修改已加入暂存区，等待提交|

### 检查文件状态


`$ git status
`
输出示例：

- `On branch master` — 当前在 master 分支
    
- `Your branch is up-to-date with 'origin/master'` — 与远程同步
    该命令还显示了当前所在分支，并告诉你这个分支同远程服务器上对应的分支没有偏离。 意味着没有任何未推送的更改。通过这条信息可以确认当前分支的状态没有任何新变化。
- `nothing to commit, working directory clean` — 工作目录干净

这说明你现在的工作目录相当干净。换句话说，所有已跟踪文件在上次提交后都未被更改过。 此外，上面的信息还表明，当前目录下没有出现任何处于未跟踪状态的新文件，否则 Git 会在这里列出来。这里的“工作目录干净”意味着所有已跟踪的文件都是未修改状态，也没有新增文件或更改内容。

 - Changes to be committed	已暂存，等待提交


- Changes not staged for commit	已修改，但未暂存


- Untracked files	新文件，未跟踪



### 基本工作流程



|区域|说明|
|---|---|
|工作区（Working Directory）|你实际修改文件的地方|
|暂存区（Staging Area / Index）|临时保存即将提交的文件快照|
|本地仓库（Local Repository）|提交后数据永久存储的地方|
|远程仓库（Remote Repository）|共享与备份的 Git 服务器（如 GitHub）|


基本流程:


```text

工作区 → git add → 暂存区 → git commit → 本地仓库 → git push → 远程仓库
		                                              ↑
		                                           	git pull            		                                           
```
   

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


## 常用命令速查表

| 命令                          | 功能                     |
| --------------------------- | ---------------------- |
| `git init`                  | 在当前目录初始化 Git 仓库        |
| `git clone <url>`           | 克隆远程仓库到本地              |
| `git status`                | 查看当前文件状态               |
| `git add <file>`            | 将文件添加到暂存区（或开始跟踪）       |
| `git commit -m "msg"`       | 将暂存区内容提交到本地仓库          |
| `git diff`                  | 查看工作区与暂存区的差异           |
| `git diff --staged`         | 查看暂存区与上次提交的差异          |
| `git log`                   | 查看提交历史                 |
| `git log --oneline --graph` | 简洁图形化日志                |
| `git show <commit>`         | 查看某次提交的具体内容            |
| `git rm <file>`             | 从 Git 中移除文件（同时删除工作区文件） |
| `git rm --cached <file>`    | 从跟踪清单中移除但仍保留工作区文件      |
| `git mv <old> <new>`        | 重命名/移动文件               |
| `git checkout -- <file>`    | 撤销工作区的修改（危险）           |
| `git reset HEAD <file>`     | 取消暂存（从暂存区移回工作区）        |
| `git commit --amend`        | 修改最近一次提交（合并到上次提交中）     |



2. git add — 添加到暂存区
```bash
# 跟踪新文件 或 暂存修改
$ git add <文件名>

# 递归添加整个目录
$ git add <目录名>
```
⚠️ 关键理解：

git add暂存的是执行命令时的文件版本

修改后需重新git add才能更新暂存区

理解为"精确添加内容到下次提交"而非"添加文件到项目"

3. git commit — 提交到本地仓库

`$ git commit -m "提交说明"`
提交后，暂存区清空

文件状态变为未修改

快照永久保存在.git目录

4. git diff — 查看差异
```bash
# 查看未暂存的改动（工作区 vs 暂存区）
$ git diff

# 查看已暂存的改动（暂存区 vs 上次提交）
``$ git diff --staged
# 或
$ git diff --cached
`````
五、完整工作流示例
场景：在已有项目中工作

```bash
# 1. 克隆或初始化仓库
$ git clone <url>
# 或
$ git init

# 2. 查看当前状态（工作目录干净）
$ git status
> nothing to commit, working directory clean

# 3. 修改文件（如 CONTRIBUTING.md）
# 编辑文件...

# 4. 查看改动的具体内容
$ git diff

# 5. 暂存修改
$ git add CONTRIBUTING.md

# 6. 再次修改同一文件
# 再次编辑...

# 7. 此时暂存区是第一次修改的版本
# 需要重新暂存最新版本
$ git add CONTRIBUTING.md

# 8. 查看已暂存的改动
$ git diff --staged

# 9. 提交
$ git commit -m "更新贡献指南"

# 10. 推送到远程（如需要）
$ git push
```



### 忽略文件（.gitignore）

创建一个 `.gitignore` 文件，列出要忽略的文件模式，例如：

```

*.log          # 忽略所有日志文件
/build/        # 忽略build目录
*.tmp          # 忽略临时文件
!important.log # 例外：不忽略important.log
```

关于更多细节，可参考 [[远程仓库与标签]] 和 [[Git 分支管理]]

 ### 提交更新
提交前的检查流程

```bash
# 1. 查看当前状态
$ git status

# 2. 确认所有需要提交的文件都已暂存
$ git add <文件>

# 3. 提交
$ git commit -m "提交说明"
```
⚠️ 注意：未git add的文件不会包含在本次提交中，只保留在本地磁盘。

三、移除文件（git rm）
场景1：删除文件（从仓库和工作区）

```bash
# 从暂存区移除，并删除工作区文件
$ git rm <文件名>

# 删除目录
$ git rm -r <目录名>

```

场景 2：仅从仓库移除（保留工作区文件）
```bash
# 从Git追踪中移除，但保留磁盘文件
$ git rm --cached README
适用场景：忘记添加.gitignore，误将日志文件或编译生成文件加入暂存区。
```
支持通配符（glob 模式）

```bash

$ git rm *.log      # 删除所有日志文件
$ git rm build/*.a  # 删除build目录下的.a文件
```
四、查看提交历史（git log）
基本使用

``$ git log``
输出示例：
```text
commit 5de18d5... (SHA-1 40位哈希值)
Author: ...
Date: ...
    提交说明
```
SHA-1哈希值：对内容和头信息的校验和，保证数据完整性，确保任何时候checkout都能得到完全一致的状态。

常用选项


|选项|说明|
|---|---|
| `--pretty=oneline` |简化输出，每行显示一个提交|
| `--pretty=format:"%h - %s"` |自定义输出格式|
| `--graph` |用 ASCII 图展示分支/合并历史|
| `--no-merges` |不显示合并提交|
| `-S <字符串>` |查找内容新增/删除的提交|
| `-p` |显示每个提交的差异|
组合使用

```bash
# 美化显示分支结构
$ git log --pretty=oneline --graph

# 查看特定内容的提交历史
$ git log -S "function_name"
```

git reflog
记录所有HEAD的变更历史，可用于恢复丢失的提交：
`$ git reflog`


五、查看提交内容（git show）

```bash
# 查看某次提交的详细信息
$ git show <commit-hash>

# 查看最近一次提交
$ git show HEAD
```
六、清理文件（git clean）
基本用法

```bash
# 查看会清理哪些文件（试运行）
$ git clean -n

# 删除未跟踪的文件
$ git clean -f

# 删除未跟踪的目录
$ git clean -fd
```
⚠️ 警告：git clean会永久删除未跟踪文件。如需保留，可使用：

```bash
# 将所有未跟踪文件保存到栈上
$ git stash --all
```
七、搜索功能（git grep）

```bash
# 在工作目录中搜索字符串
$ git grep "search_string"

# 在历史提交中搜索
$ git grep "search_string" <commit-hash>

# 使用正则表达式
$ git grep -e "regex_pattern"
```

八、子模块（Submodule）

添加子模块

`$ git submodule add <仓库URL> <路径>`

核心概念：

子模块是独立Git仓库，父仓库只记录其特定提交ID

父仓库不包含子模块内容，只存储指针（引用）

修改子模块并提交后，需显式更新父仓库中的引用

常用命令
命令	说明
git submodule add <url>	添加子模块
git submodule status	查看子模块状态
git submodule update	更新子模块到父仓库记录的提交
git submodule update --remote	更新到远程最新版本
克隆含子模块的项目
bash
# 方式1：两步操作
$ git clone <主仓库URL>
$ git submodule init
$ git submodule update

# 方式2：一步到位
$ git clone --recurse-submodules <主仓库URL>
子模块与分支的关系
操作	对父仓库影响	对子模块影响
更改父仓库分支	更新子模块引用	不自动更新内容
修改子模块内容	不自动反映	独立提交历史
提交父仓库	记录子模块新指针	不影响子模块内容


九、打包（git bundle）
创建打包文件
bash
# 打包本地分支的未推送提交
$ git bundle create <文件名>.bundle <本地分支> ^<远程分支>

# 示例：打包main分支中origin/main没有的提交
$ git bundle create updates.bundle main ^origin/main
应用打包文件
bash
# 从打包文件拉取内容
$ git pull <文件名>.bundle <分支>

# 或
$ git fetch <文件名>.bundle <分支>
提交范围语法
语法	说明
main ^5de18d5	main分支所有提交，排除5de18d5之前的提交
main~1	main分支HEAD之前的一个提交
main..dev	main到dev之间的所有提交
main ^origin/main	main有而origin/main没有的提交
十、撤销操作
1. 修正最后一次提交（git commit --amend）
bash
# 修改提交信息或补充遗漏文件
$ git commit --amend -m "新的提交信息"
工作原理：用新提交替换旧提交，而非修复旧提交。

示例场景：

bash
# 忘记添加文件
$ git add 遗漏的文件
$ git commit --amend
2. 取消暂存（git reset HEAD）
bash
# 将文件从暂存区移出
$ git reset HEAD <文件名>
场景：误用git add *暂存了所有文件，想取消部分文件的暂存。

3. 撤销工作区修改（git checkout --）
bash
# 丢弃工作区的修改，恢复到最后提交的版本
$ git checkout -- <文件名>
⚠️ 危险警告：此操作会永久丢失本地修改，无法恢复！除非确定不需要这些修改，否则不要使用。

安全替代方案
bash
# 保留修改，稍后再处理
$ git stash save "临时保存"
$ git stash pop  # 恢复
完整撤销操作速查表
场景	命令	安全性
修改上一次提交信息	git commit --amend -m "新信息"	✅ 安全
补充文件到上一次提交	git add <文件> → git commit --amend	✅ 安全
取消暂存	git reset HEAD <文件>	✅ 安全
撤销工作区修改	git checkout -- <文件>	❌ 危险
安全保存工作区修改	git stash save "备注"	✅ 安全
## 查看提交历史与回溯

```bash
git log --oneline -5                     # 最近5条提交
git log --since="2 weeks ago"            # 两周内的提交
git log --author="someone"               # 某作者的提交
git log -S "function_name"               # 包含特定字符串增删的提交
git reflog                               # 本地所有 HEAD 移动历史
git reset --hard HEAD^                   # 回退到上一个版本（丢弃工作区修改）
git reset --soft HEAD^                   # 回退版本但保留工作区和暂存区修改
git reset --mixed HEAD^                  # 回退版本并取消暂存（保留工作区修改）
```

> 谨慎使用 `git reset --hard`，它会丢弃所有未提交的更改。

## 子模块

通过 `git submodule add <url>` 添加子模块。子模块是独立的 Git 仓库，父仓库只记录其当前提交的 SHA-1。

```bash
git submodule update --remote   # 拉取子模块最新版本
git submodule status            # 查看子模块状态
```

## 打包（git bundle）

Git 可以将数据打包成一个二进制文件（`git bundle`），方便离线传输：

```bash
git bundle create repo.bundle HEAD master
```

> 关于远程操作和标签，请参阅 [[远程仓库与标签]]