# Git 进阶工具

## 搜索功能（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 并不会跟踪它的内容，而是将它看作子模块仓库中的某个具体的提交。
尽管 Git 会“跟踪”子模块，但它并没有将子模块的具体内容（即其历史和文件）直接包含到父仓库的版本控制中。
父仓库不包含子模块内容，只存储指针（引用）。

修改子模块并提交后，需显式更新父仓库中的引用。
**自动提交子模块状态**：它会将子模块的当前状态（即引用的提交）记录在父仓库中。也就是说，Git 会将子模块引用的提交（即子模块的 HEAD）存储在父仓库的索引（暂存区）中。

**添加到暂存区**：子模块目录会被视作一个单独的 Git 仓库，而父仓库会将其作为一个特定提交的引用添加到暂存区。实际上，父仓库的变化并不是对子模块内容的直接修改，而是对子模块的指针（即某个特定提交的引用）进行记录。

常用命令


|命令|说明|
|---|---|
|`git submodule add <url>`|添加子模块|
|`git submodule status`|查看子模块状态|
|`git submodule update`|更新子模块到父仓库记录的提交|
|`git submodule update --remote`|更新子模块到远程最新版本|

**更改父仓库 main 分支**：只会影响子模块的引用，而不会自动影响子模块内容。如果要更新子模块内容，需要手动更新并同步。

**更改子模块**：如果更改仅限于子模块本身，它不会直接影响父仓库。但如果你提交并更新子模块的引用，父仓库会跟踪这些更改，进而影响 main 分支。

克隆含子模块的项目

```bash
# 方式1：两步操作
$ git clone <主仓库URL>
$ git submodule init
$ git submodule update
```


方式2：一步到位
`$ git clone --recurse-submodules <主仓库URL>`

| 操作      | 对父仓库影响                 | 对子模块影响              |
| ------- | ---------------------- | ------------------- |
| 更改父仓库分支 | 更新子模块引用（指针可能变化）        | 不自动更新内容，子模块仍停留在原提交  |
| 修改子模块内容 | 不自动反映（父仓库仍指向旧提交）       | 独立提交历史，子模块有自己的分支和提交 |
| 提交父仓库   | 记录子模块的新指针（commit hash） | 不影响子模块内容本身          |


## 打包（git bundle）

### 什么是 git bundle？

Git 提供了多种网络传输方式，如 SSH、HTTP 等，但还有一种不太常用却非常有效的方法—— `git bundle`。

`git bundle` 可以将 Git 数据“打包”成一个二进制文件，实际上它会把 `git push` 所传输的所有内容封装到单个文件中。你可以把这个文件通过邮件、U 盘等方式转发给他人，对方再将其解包到自己的仓库中，从而实现数据同步，无需依赖网络。

### 提交范围语法速查表

|语法|说明|
|---|---|
| `main ^5de18d5` |main 分支所有提交，**排除** 5 de 18 d 5 及其之前的提交（即只保留 5 de 18 d 5 之后的提交）|
| `main~1` |main 分支 HEAD 之前的**一个**提交（即父提交）|
| `main..dev` |main 到 dev 之间的所有提交（即 dev 有而 main 没有的提交）|
| `main ^origin/main` |main 有而 origin/main 没有的提交（与 `origin/main..main` 等价）|

> **等价命令示例**：以下两条命令输出相同信息，都用于显示本地 `main` 分支有、但远程 `origin/main` 分支没有的提交。`..` 是范围表示法，`^` 是排除表示法。
> 
> ```bash
> git log --oneline origin/main..main
> git log --oneline main ^origin/main
> ```

### 创建打包文件（打包未推送的提交）

```bash
# 通用格式
$ git bundle create <文件名>.bundle <本地分支> ^<远程分支>
# 示例：打包 main 分支中 origin/main 没有的提交
$ git bundle create updates.bundle main ^origin/main
```

### 应用打包文件

```bash
# 方式一：直接拉取（相当于 git pull）
$ git pull <文件名>.bundle <分支>
# 方式二：仅获取（相当于 git fetch）
$ git fetch <文件名>.bundle <分支>
```

### 完整使用场景示意

```bash
# 1. 在 A 机器上打包未推送的提交
$ git bundle create my-updates.bundle main ^origin/main
# 2. 将 my-updates.bundle 通过 U 盘/邮件 传给 B 机器
# 3. 在 B 机器上应用打包文件
$ git fetch my-updates.bundle main   # 仅获取
# 或直接拉取
$ git pull my-updates.bundle main    # 获取并合并
```
---

[[版本控制系统]] · [[Git学习顺序]] · 上一章：[[Git 忽略、删除与清理]] · 下一章：[[Git 内部原理]]
