一、Git的概论
什么是版本控制?
版本控制的定义
版本控制系统(Version Control System,VCS) 是一种记录文件内容变化,以便将来查阅特定版本修订情况的系统。它能够:
记录历史:保存文件的每一次修改记录
追溯变更:查看谁在什么时候修改了什么内容
版本回退:随时恢复到之前的任意版本
协作开发:多人同时对同一项目进行修改
例如:从实际场景来说
假设你是一名安全研究员,正在编写一份漏洞分析报告。随着工作的推进,你的文件可能会变成这样:
漏洞分析报告.doc
漏洞分析报告_修改版.doc
漏洞分析报告_最终版.doc
漏洞分析报告_最终版2.doc
漏洞分析报告_打死也不改了.doc
漏洞分析报告_真的最终版.doc这种情况是不是很熟悉?这就是没有版本控制带来的混乱
Git 的核心特点
1. 分布式架构
每个开发者的本地都有一个完整的版本库,包含项目的全部历史记录。这意味着:
几乎所有操作都在本地完成,速度极快
即使没有网络连接也能正常工作
任何一个副本都可以作为备份
2. 数据完整性保证
Git 使用 SHA-1 哈希算法对所有数据进行校验。每一个文件、每一次提交都有一个唯一的 40 位十六进制哈希值:
e83c5163316f89bfbde7d9ab23ca2e25604af290这个特性对于安全工作者来说尤为重要——任何对历史记录的篡改都会导致哈希值变化,从而被发现
3. 只添加数据
Git 的操作几乎只往数据库中添加数据,很难让 Git 执行任何不可逆的操作。这意味着:
已提交的数据很难丢失
可以放心地进行各种实验
历史记录具有可追溯性
4. 三种状态
Git 管理的文件有三种状态:
这三种状态对应 Git 项目的三个工作区域:
工作目录(Working Directory)
|
| git add
v
暂存区域(Staging Area)
|
| git commit
v
Git 仓库(Repository)二、安装Git
windows系统安装
方法一:官方安装包(推荐)
访问 Git 官方网站:https://git-scm.com/
点击 "Download for Windows" 下载安装包
运行安装程序,按照向导完成安装
方法二:使用包管理器(windows)
如果你使用 Chocolatey 包管理器:
choco install git如果你使用 winget:
winget install Git.GitmacOS 系统安装
方法一:Xcode Command Line Tools
打开终端,输入:
git --version如果系统没有安装 Git,会自动提示安装 Xcode Command Line Tools。
方法二:Homebrew(推荐)
如果你已经安装了 Homebrew:
brew install git方法三:官方安装包
访问 https://git-scm.com/download/mac 下载安装包。
Linux 系统安装
Debian/Ubuntu 系列
sudo apt update
sudo apt install gitRHEL/CentOS/Fedora 系列
# CentOS/RHEL 7
sudo yum install git
# CentOS/RHEL 8+ / Fedora
sudo dnf install gitArch Linux
sudo pacman -S git验证安装
安装完成后,打开终端(Windows 用户打开 Git Bash),输入以下命令验证:
git --version如果看到类似以下输出,说明安装成功:
git version 2.43.0三、Git基本操作(实验步骤)
1. Git初使配置
安装完成后,在开始使用 Git 之前,需要进行一些基本配置
配置用户信息
Git 要求每次提交都必须包含用户信息,这对于追踪代码变更非常重要。
# 设置用户名
git config --global user.name "你的名字"
# 设置邮箱
git config --global user.email "your.email@example.com"注意:这里的用户名和邮箱会记录在每一次提交中,是公开可见的。在安全工作中,这些信息可能被用于追踪代码作者
配置级别说明
Git 的配置分为三个级别:
优先级从低到高:系统级 < 用户级 < 仓库级
其他常用配置
配置默认编辑器
# 使用 Vim
git config --global core.editor vim
# 使用 VS Code
git config --global core.editor "code --wait"
# 使用 Nano
git config --global core.editor nano配置默认分支名称
git config --global init.defaultBranch main配置命令别名
git config --global alias.st status
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
git config --global alias.lg "log --oneline --graph --all"配置颜色输出
git config --global color.ui auto查看配置信息
查看所有配置:
git config --list查看特定配置项:
git config user.name
git config user.email查看配置项的来源:
git config --list --show-origin步骤1:初始化新仓库,并找到克隆路径
# 创建项目目录
mkdir -p secscan
cd secscan
# 初始化 Git 仓库
git init执行后,你会看到类似这样的输出:
Initialized empty Git repository in /home/mario/secscan/.git/此时,Git 会在当前目录下创建一个名为 .git 的隐藏目录,这个目录包含了 Git 仓库的所有元数据。后面会详细讲解这个目录的结构
验证仓库创建成功:
# 查看生成的 .git 目录
ls -la输出示例:
total 12
drwxrwxr-x 3 kali kali 4096 Jan 26 10:00 .
drwxrwxr-x 3 kali kali 4096 Jan 26 10:00 ..
drwxrwxr-x 7 kali kali 4096 Jan 26 10:00 .git看到 .git 目录,说明仓库初始化成功。
github上找到克隆项目
打开github找到nmpa复制源代码:https://github.com/nmap/nmap

步骤2:克隆源代码
# 将 nmap 源码库完整克隆到指定的本地目录 /Users/mario/secscan
git clone https://github.com/nmap/nmap.git /Users/mario/secscan结果图

原理解析:
这个操作完成了 “远程开源项目本地化” 的核心步骤,可以脱离网络或

但改不了核心部件
git clone 命令(对应去车企拿全套改装图纸):你拿到所有设计图,能自己改发动机、加配件,甚至看懂这车为啥这么设计
指定 /Users/mario/secscan(对应把图纸放到你车库的 “改装车专区” 抽屉):方便你后续找图纸、和其他改装资料放一起
总结:
这个命令的核心就是:把 nmap 的 “全套设计图纸” 完整搬到你电脑指定的文件夹里,不是只拿个 “成品”
这么做的好处:能自己改 nmap、适配自己的电脑、学习它的设计逻辑,比只用现成的安装包灵活得多
指定路径只是为了让 “图纸” 放得更规整,方便你后续找
步骤3:文件状态与生命周期
文件4种状态
未跟踪(Untracked)
|
| git add
v
已暂存(Staged)
|
| git commit
v
未修改(Unmodified)
|
| 编辑文件
v
已修改(Modified)
|
| git add
v
已暂存(Staged)
...状态说明:
举例:
# 确保在项目目录中
cd secscan
# 创建 README 文件
cat > README.md << 'EOF'
# SecScan - 端口扫描器
一个用于内部安全评估的端口扫描工具。
## 功能
- TCP 端口扫描
- 服务识别
- 结果导出
## 作者
安全工具开发组
EOF
#查看文件状态
git status结果图

简洁模式:
git status -s输出:
?? README.md?? 表示未跟踪的文件。简洁模式的常见标记:
M file.txt # 已修改,未暂存
M file.txt # 已修改,已暂存
MM file.txt # 已暂存后又被修改
A file.txt # 新添加到暂存区
?? file.txt # 未跟踪步骤4:把文件提交到缓存状态
源代码:常用的 git add 用法
# 添加单个文件
git add filename.txt
# 添加多个文件
git add file1.txt file2.txt
# 添加当前目录下的所有文件
git add .
# 添加所有已跟踪文件的修改(不包括新文件)
git add -u
# 添加所有变更(包括新文件、修改、删除)
git add -A
# 提交(还是提交在本地,-m是为了方便了解提交的内容,类似注释)
git commit -m "mario添加本项目描述"结果图

常用的 git commit 用法:
# 直接在命令行输入提交信息
git commit -m "提交信息"
# 打开编辑器输入详细的提交信息
git commit
# 跳过暂存区,直接提交所有已跟踪文件的修改
git commit -am "提交信息"修改最后一次提交:
# 修改提交信息
git commit --amend -m "新的提交信息"
# 添加遗漏的文件到上次提交
git add forgotten-file.txt
git commit --amend --no-edi注意:--amend 会改变提交的哈希值,如果已经推送到远程仓库,不要使用这个命令
步骤5:使用VS Code查看git
结果图:

源代码:创建主程序文件
#!/usr/bin/env python3
"""
SecScan - 端口扫描器
"""
import socket
import argparse
from datetime import datetime
def scan_port(host, port, timeout=1):
"""扫描单个端口"""
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
result = sock.connect_ex((host, port))
sock.close()
return result == 0
except:
return False
def scan_range(host, start_port, end_port):
"""扫描端口范围"""
open_ports = []
for port in range(start_port, end_port + 1):
if scan_port(host, port):
open_ports.append(port)
print(f"[+] Port {port} is open")
return open_ports
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="SecScan - 端口扫描器")
parser.add_argument("host", help="目标主机")
parser.add_argument("-p", "--ports", default="1-1000", help="端口范围")
args = parser.parse_args()
print(f"[*] Scanning {args.host}...")
print(f"[*] Time: {datetime.now()}")创建敏感信息配置文件
"""
SecScan 配置文件
"""
# 扫描代理服务器配置
PROXY_HOST = "192.168.1.100"
PROXY_PORT = 8080
PROXY_USER = "scanner"
PROXY_PASSWORD = "Sc@nner2024!Secret" # 内部代理密码
# API 密钥(用于结果上报)
API_KEY = "sk-secscan-a]1b2c3d4e5f6g7h8i9j0"
API_SECRET = "secret-xxxxxxxxxxxxxxxxxxxx"
# 数据库配置(存储扫描结果)
DB_HOST = "192.168.1.50"
DB_USER = "secscan"
DB_PASSWORD = "DB@Pass2024!"
DB_NAME = "scan_results"结果图:

步骤7:修改文件并查看差异
源代码
#修改文件,使用环境变量
cat > config.py << 'EOF'
"""
SecScan 配置文件
警告:请勿将敏感信息写入此文件!
"""
import os
# 扫描代理服务器配置(从环境变量读取)
PROXY_HOST = os.environ.get("PROXY_HOST", "localhost")
PROXY_PORT = int(os.environ.get("PROXY_PORT", "8080"))
PROXY_USER = os.environ.get("PROXY_USER", "")
PROXY_PASSWORD = os.environ.get("PROXY_PASSWORD", "")
# API 密钥
API_KEY = os.environ.get("API_KEY", "")
API_SECRET = os.environ.get("API_SECRET", "")
# 数据库配置
DB_HOST = os.environ.get("DB_HOST", "localhost")
DB_USER = os.environ.get("DB_USER", "")
DB_PASSWORD = os.environ.get("DB_PASSWORD", "")
DB_NAME = os.environ.get("DB_NAME", "scan_results")
EOF
# 查看差异
git diff config.py结果图:


提交修复
# 添加修改
git add config.py
# 提交
git commit -m "安全修复:移除硬编码密码,改用环境变量"查看提交历史
git log --oneline
# 输出
xxxxxxx 安全修复:移除硬编码密码,改用环境变量
xxxxxxx 添加扫描器核心代码和配置文件
xxxxxxx 初始化项目:添加 README
## 重要警告:虽然最新版本的配置文件不再包含密码,但密码仍然存在于 Git 历史中!
# 验证:从历史中查看旧版本的配置文件
git show HEAD~1:config.py
## 这将显示包含密码的旧版本!这就是为什么单纯删除敏感信息并不能真正解决问题
步骤8:撤销内容
源代码
# 模拟误操作
echo "jjdhjkcnkjadchdsccjdhcksj" >> config.py
# 查看状态
git status -s
# 撤销修改(恢复到上次提交的状态)
git restore config.py
# 验证
git status -s结果图

步骤9:撤销暂存区的文件
源代码
# 假设添加了不该添加的文件
echo "test" > test.txt
# 缓存
git add test.txt
# 查看状态
git status -s
# 把文件指定从暂存区拿出来
git restore --staged test.txt
# 再次查看状态
git status -s
# 删掉文件
rm test.txt结果图

步骤10:重置
撤销提交
方式一:git reset(改变历史)
# 软重置(但会保留工作路径,缓存、仓库都保留)
git reset --soft HEAD~1
# 工作目录缓存都会清空的重置
git reset --hard HEAD~1
# 删文件夹
git rm -r 文件夹名
# 从工作目录和暂存区删除
git rm 文件名
# 只从暂存区删除(保留工作目录中的文件)
git rm --cached 文件名
# 强制删除已修改的文件
git rm -f 文件名
# 删除目录
git rm -r 目录名/方式二:git revert(不改变历史)
# 创建一个新提交来撤销指定提交
git revert HEADgit revert 不会删除历史记录,更安全
步骤11:重命名
# 改名
git mv 旧文件名 新文件名步骤12:忽略文件:防止敏感信息提交
为了防止以后再次误提交敏感文件,我们需要创建
.gitignore文件
为 SecScan 项目创建 .gitignore
cd secscan
# 创建 .gitignore 文件
cat > .gitignore << 'EOF'
# 敏感配置文件
.env
.env.*
secrets.yml
credentials.json
# 本地配置(包含密码)
config.local.py
# 扫描结果(可能包含敏感信息)
results/
*.json
*.csv
# Python
__pycache__/
*.py[cod]
*.egg-info/
venv/
# 日志
*.log
logs/
# IDE
.idea/
.vscode/
*.swp
# 操作系统
.DS_Store
Thumbs.db
EOF
# 查看状态
git status -s输出:
?? .gitignore# 提交 .gitignore
git add .gitignore
git commit -m "添加 .gitignore:防止敏感文件提交".gitignore 语法规则
全局忽略文件
# 创建全局忽略文件
git config --global core.excludesfile ~/.gitignore_global已跟踪文件的忽略
如果文件已经被跟踪,添加到 .gitignore 不会生效。需要先从暂存区移除:
git rm --cached filename.txt安全提示:在安全审计中,检查 .gitignore 文件可以发现项目中可能存在的敏感文件类型
四、git分支管理(实验步骤)
什么是分支
在 Git 中,分支本质上是指向某个提交对象的可移动指针。
想象一下,你正在写一本书。主线剧情已经写好了,但你突然有了一个新想法,想尝试一个不同的结局。你不想破坏已有的内容,于是你复制了一份手稿,在副本上进行修改。这个副本就相当于一个分支。
在 Git 中,这个过程更加轻量级——创建分支不需要复制任何文件,只是创建一个新的指针
分支的内部原理
当你执行 git commit 时,Git 会创建一个提交对象,这个对象包含:
指向暂存内容快照的指针
作者信息和提交信息
指向父提交的指针(首次提交没有父提交,合并提交有多个父提交)
提交对象结构:
commit 1a2b3c4d
├── tree(指向目录树)
├── parent(指向父提交)
├── author
├── committer
└── message分支就是一个指向这些提交对象的指针。默认分支名为 main(或旧版本的 master)
HEAD 指针
HEAD 是一个特殊的指针,指向当前所在的分支。可以把它理解为"你现在在哪里"的标记
用 “看书” 比喻 HEAD 指针
把 Git 仓库想象成一本厚厚的书(每个提交版本是书的一页):
书的目录(refs/heads/):记录了各章节(分支)的起始页码,比如「main 章节」从第 100 页开始,「feat/login 章节」从第 120 页开始;
HEAD 指针:就是你看书时夹的书签 ——
正常看书(在分支上):书签夹在「main 章节」的目录页,意思是 “我现在在 main 分支,看的是这个分支最新的内容”;
翻到某一页(分离头指针):书签直接夹在第 110 页,意思是 “我现在直接看第 110 页的内容,和任何章节(分支)都无关”
代码块
# 查看 HEAD 指向:
cat .git/HEAD
# 输出示例:
ref: refs/heads/main创建分支开发新功能
现在让我们为 SecScan 添加 UDP 扫描功能
1.查看当前分支
# 进入项目目录
cd ~/git-security-labs/user-management-system
# 查看当前分支
git branch结果图

2.创建并切换到新分支
# 创建并切换到 feature-udp 分支
git switch -c feature-udp
# 查看分支
git branch结果图

3.在新分支上开发
# 添加 UDP 扫描功能
cat >> secscan.py << 'EOF'
def scan_udp_port(host, port, timeout=1):
"""扫描 UDP 端口"""
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(timeout)
sock.sendto(b'', (host, port))
try:
data, addr = sock.recvfrom(1024)
return True
except socket.timeout:
return True # UDP 无响应可能表示端口开放
except:
return False
finally:
sock.close()
EOF
# 查看修改
git diff
## [feature-udp xxxxxxx] feature: 添加 UDP 端口扫描功能
## 1 file changed, 15 insertions(+)
# 提交
git add secscan.py
git commit -m "feature: 添加 UDP 端口扫描功能"查看分支历史
# 查看所有分支的提交历史
git log --oneline --graph --all结果图

4切换分支与并行开发
换回 main 分支
# 切换回 main
git switch main
# 查看当前分支
git branch创建另一个功能分支
git switch -c feature-service-detect
# 添加服务识别功能
cat >> secscan.py << 'EOF'
# 常见服务端口映射
SERVICE_PORTS = {
21: 'FTP',
22: 'SSH',
23: 'Telnet',
25: 'SMTP',
53: 'DNS',
80: 'HTTP',
443: 'HTTPS',
3306: 'MySQL',
3389: 'RDP',
6379: 'Redis',
}
def identify_service(port):
"""根据端口识别服务"""
return SERVICE_PORTS.get(port, 'Unknown')
EOF
# 提交
git add secscan.py
git commit -m "feature: 添加服务识别功能"结果图

查看分支状态
git log --oneline --graph --all
# 输出:
## * xxxxxxx (HEAD -> feature-service-detect) feature: 添加服务识别功能
## | * xxxxxxx (feature-udp) feature: 添加 UDP 端口扫描功能
## |/
## * xxxxxxx (main) 添加 .gitignore:防止敏感文件提交
## ...现在有两个并行的功能分支!
常用分支命令速查:
合并分支
服务识别功能开发完成,现在要合并到主分支。
1.合并 feature-service-detect 分支
# 切换到 main 分支
git switch main
# 合并 feature-service-detect
git merge feature-service-detect -m "合并服务识别功能"输出:
Updating xxxxxxx..xxxxxxx
Fast-forward
secscan.py | 17 +++++++++++++++++
1 file changed, 17 insertions(+)这是一个「快进合并」(Fast-forward),因为 main 分支没有新的提交。
2.合并 feature-udp 分支
# 合并 feature-udp
git merge feature-udp -m "合并 UDP 扫描功能"输出:
Auto-merging secscan.py
CONFLICT (content): Merge conflict in secscan.py
Automatic merge failed; fix conflicts and then commit the result.冲突了! 因为两个分支都修改了 secscan.py 文件。
3.解决合并冲突
# 查看冲突文件
cat secscan.py | grep -A 10 "<<<<<<"冲突标记示例:
<<<<<<< HEAD
# 常见服务端口映射
SERVICE_PORTS = {
...
=======
def scan_udp_port(host, port, timeout=1):
...
>>>>>>> feature-udp解决冲突:我们需要保留两边的代码,删除冲突标记。
# 手动编辑解决冲突(保留两边的功能)
# 这里我们用脚本模拟解决冲突
cat > secscan.py << 'EOF'
#!/usr/bin/env python3
"""
SecScan - 端口扫描器
作者: 安全工具开发组
"""
import socket
import argparse
from datetime import datetime
def scan_port(host, port, timeout=1):
"""扫描 TCP 端口"""
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
result = sock.connect_ex((host, port))
sock.close()
return result == 0
except:
return False
def scan_udp_port(host, port, timeout=1):
"""扫描 UDP 端口"""
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(timeout)
sock.sendto(b'', (host, port))
try:
data, addr = sock.recvfrom(1024)
return True
except socket.timeout:
return True
except:
return False
finally:
sock.close()
# 常见服务端口映射
SERVICE_PORTS = {
21: 'FTP', 22: 'SSH', 23: 'Telnet', 25: 'SMTP',
53: 'DNS', 80: 'HTTP', 443: 'HTTPS', 3306: 'MySQL',
3389: 'RDP', 6379: 'Redis',
}
def identify_service(port):
"""根据端口识别服务"""
return SERVICE_PORTS.get(port, 'Unknown')
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="SecScan - 端口扫描器")
parser.add_argument("host", help="目标主机")
args = parser.parse_args()
print(f"[*] Scanning {args.host}...")
EOF
# 标记冲突已解决
git add secscan.py
# 提交合并
git commit -m "合并 UDP 扫描功能,解决冲突"4.查看合并后的历史
git log --oneline --graph --all输出:
* xxxxxxx (HEAD -> main) 合并 UDP 扫描功能,解决冲突
|\
| * xxxxxxx (feature-udp) feature: 添加 UDP 端口扫描功能
* | xxxxxxx 合并服务识别功能
|/
* xxxxxxx (feature-service-detect) feature: 添加服务识别功能
...5.删除已合并的分支
# 删除已合并的功能分支
git branch -d feature-udp
git branch -d feature-service-detect
# 查看分支
git branch输出:
* main功能分支已清理完毕。
储藏工作(Stash)
组长突然说:「有个紧急 bug 需要修复,你先放下手头的工作。」
但是你正在开发新功能,代码还没写完,不想提交。这时可以用 git stash 保存工作进度。
1 模拟场景
cd ~/security-tools/secscan
# 开始开发新功能(未完成)
echo '
# TODO: 添加结果导出功能
def export_results(results, filename):
pass # 未完成
' >> secscan.py
# 查看状态
git status -s输出:
M secscan.py2 储藏当前工作
# 储藏工作进度
git stash push -m "未完成的结果导出功能"
# 查看状态
git status -s输出为空,工作目录已清空。
# 查看储藏列表
git stash list输出:
stash@{0}: On main: 未完成的结果导出功能3 修复紧急 bug
# 现在可以安全地修复 bug
git switch -c hotfix-timeout
# 修复超时问题
cat > secscan.py << 'EOF'
#!/usr/bin/env python3
"""
SecScan - 端口扫描器
作者: 安全工具开发组
"""
import socket
import argparse
from datetime import datetime
DEFAULT_TIMEOUT = 2 # 修复:增加默认超时时间
def scan_port(host, port, timeout=DEFAULT_TIMEOUT):
"""扫描 TCP 端口"""
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
result = sock.connect_ex((host, port))
sock.close()
return result == 0
except:
return False
def scan_udp_port(host, port, timeout=DEFAULT_TIMEOUT):
"""扫描 UDP 端口"""
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(timeout)
sock.sendto(b'', (host, port))
try:
data, addr = sock.recvfrom(1024)
return True
except socket.timeout:
return True
except:
return False
finally:
sock.close()
SERVICE_PORTS = {
21: 'FTP', 22: 'SSH', 23: 'Telnet', 25: 'SMTP',
53: 'DNS', 80: 'HTTP', 443: 'HTTPS', 3306: 'MySQL',
3389: 'RDP', 6379: 'Redis',
}
def identify_service(port):
return SERVICE_PORTS.get(port, 'Unknown')
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="SecScan")
parser.add_argument("host", help="目标主机")
parser.add_argument("-t", "--timeout", type=int, default=DEFAULT_TIMEOUT)
args = parser.parse_args()
print(f"[*] Scanning {args.host}...")
EOF
git add secscan.py
git commit -m "hotfix: 修复超时时间问题"
# 合并到 main
git switch main
git merge hotfix-timeout -m "合并超时修复"
git branch -d hotfix-timeout4 恢复储藏的工作
# 查看储藏
git stash list
# 恢复储藏并删除记录
git stash pop
# 查看状态
git status -s输出:
M secscan.py之前未完成的工作已恢复!
常用 stash 命令:
变基操作(高级)
1 什么是变基
变基(Rebase)是另一种整合分支的方式。它会将一个分支的提交"移植"到另一个分支上
git checkout feature
git rebase main2 变基 vs 合并
3 交互式变基
交互式变基允许你修改、合并、删除或重新排序提交:
git rebase -i HEAD~3这会打开编辑器,显示最近 3 次提交:
pick abc1234 第一次提交
pick def5678 第二次提交
pick ghi9012 第三次提交
# Commands:
# p, pick = 使用提交
# r, reword = 使用提交,但修改提交信息
# e, edit = 使用提交,但停下来修改
# s, squash = 使用提交,但合并到前一个提交
# f, fixup = 类似 squash,但丢弃提交信息
# d, drop = 删除提交常见用途:
合并多个小提交为一个
修改历史提交信息
删除错误的提交
重新排序提交
警告:不要对已推送到远程的提交进行变基,这会导致历史记录不一致。
分支管理策略
1 Git Flow 工作流
Git Flow 是一种广泛使用的分支管理模型:
main(生产分支)
|
+-- hotfix(紧急修复)
|
develop(开发分支)
|
+-- feature(功能分支)
|
+-- release(发布分支)分支说明:
2 GitHub Flow
GitHub Flow 是一种更简单的工作流:
从 main 创建分支
在分支上进行开发
提交 Pull Request
代码审查
合并到 main
部署
3 安全团队的分支策略建议
对于安全团队,建议采用以下策略:
main(稳定版本)
|
+-- audit/项目名(代码审计分支)
|
+-- poc/漏洞编号(PoC 开发分支)
|
+-- report/日期(报告编写分支)Git分支管理小结
在本章中,我们通过继续开发 SecScan 项目,学习了 Git 分支管理:
创建分支:为 UDP 扫描和服务识别功能创建独立分支
并行开发:同时开发多个功能
合并分支:快进合并和三方合并
解决冲突:处理合并时的代码冲突
储藏工作:临时保存未完成的工作
分支策略:了解安全团队的分支管理模型
当前项目状态:
SecScan 现在具备:
TCP 端口扫描
UDP 端口扫描
服务识别
可配置超时时间
五、Git内部文件结构
Git 的核心是一个内容寻址文件系统,所有仓库的元数据和对象都存储在 .git 目录中,我们平时执行的 git add、git commit 等操作本质上都是在修改这个目录里的内容
1 核心目录结构
.git/
├── HEAD # 指向当前分支的指针
├── config # 仓库级配置文件
├── description # GitWeb 使用的描述文件
├── hooks/ # 钩子脚本目录
│ ├── pre-commit.sample
│ ├── pre-push.sample
│ └── ...
├── index # 暂存区(二进制文件)
├── info/ # 额外信息
│ └── exclude # 本地忽略规则
├── logs/ # 引用日志
│ ├── HEAD
│ └── refs/
├── objects/ # 对象数据库(核心)
│ ├── info/
│ ├── pack/
│ └── [0-9a-f][0-9a-f]/
├── refs/ # 引用(分支、标签等)
│ ├── heads/ # 本地分支
│ ├── remotes/ # 远程分支
│ └── tags/ # 标签
├── COMMIT_EDITMSG # 最后一次提交的信息
├── FETCH_HEAD # 最后一次 fetch 的信息
├── ORIG_HEAD # 危险操作前的 HEAD 备份
└── packed-refs # 打包的引用.git/objects/— 原始档案柜
这是 Git 的对象数据库,存储着所有文件内容、提交信息、目录结构等核心数据
存储的对象类型:
Blob 对象:存储单个文件的内容,不包含文件名等元信息。
Tree 对象:存储目录结构,记录文件和子目录的名称、权限,以及对应的 Blob/Tree 对象哈希值。
Commit 对象:存储一次提交的元数据,包括作者、提交者、提交信息、指向的 Tree 对象哈希值,以及父提交的哈希值。
Tag 对象:存储标签信息,通常指向一个 Commit 对象,用于标记重要版本。
存储方式:对象以 SHA-1 哈希值命名,前两位作为子目录名,后 38 位作为文件名,避免单目录文件过多导致性能问题
举例:
🎯 比喻:公司档案柜,所有代码版本都是带唯一编号的档案袋
✨ 作用:存所有历史版本的原始文件、提交记录,永不删除旧档案
💡 记忆点:你能回滚版本、看历史记录,全靠它存着所有旧档案
🔹.git/refs/— 快捷方式台账
这个目录存储引用(References),也就是指向 Commit 对象的指针,用来简化对长哈希值的记忆
refs/heads/:存储所有本地分支,每个分支文件的内容就是该分支当前指向的 Commit 哈希值refs/tags/:存储所有标签,指向对应的 Commit 或 Tag 对象refs/remotes/:存储远程仓库的分支引用,记录本地最后一次同步时远程分支的状态
举例:
🎯 比喻:档案柜编号记不住?用台账把 “好记的名字” 和 “档案编号” 绑定
✨ 作用:
refs/heads/:分支台账,每个分支名对应一个档案编号refs/tags/:版本标签台账,v1.0对应正式版档案编号
💡 记忆点:分支不是复制代码,只是台账里的一行编号
🔹 .git/HEAD — 当前位置便利贴
这是一个符号引用文件,内容是当前工作区所在的分支或提交
如果在分支上(如
main),内容是ref: refs/heads/main如果处于 “分离头指针” 状态(直接指向某个 Commit),内容就是该 Commit 的哈希值
举例:
🎯 比喻:你现在在哪操作,就把便利贴贴在哪
✨ 作用:
正常状态:贴在分支台账页(比如
ref: refs/heads/main)分离头指针:直接贴在档案袋上(一串哈希值)
💡 记忆点:切换分支就是 “撕旧便利贴,贴新的”
🔹 .git/index — 提交待办清单
这是 Git 的暂存区(Stage/Index),是一个二进制文件,记录了下一次提交时要包含的文件信息(文件名、哈希值、时间戳等)
git add命令就是将文件的当前状态写入这个索引文件git commit则是根据这个索引文件生成新的 Tree 和 Commit 对象
举例:
🎯 比喻:提交前先写清单,要存档的文件都列上
✨ 作用:临时记录下一次提交要包含的文件
💡 记忆点:
git add是写清单,git commit是按清单打包存档
🔹 .git/config — 仓库设置登记本
存储当前仓库的配置信息,包括用户信息、远程仓库地址、分支跟踪关系等
可以通过
git config命令修改,优先级高于全局配置(~/.gitconfig)
举例:
🎯 比喻:办公间的登记本,记着这个仓库的专属配置
✨ 作用:存远程地址、仓库专属的用户名 / 邮箱等
💡 记忆点:
git config命令就是改这个登记本
2 安全视角的重要性
从安全角度来看,.git 目录包含了:
完整的代码历史:包括所有曾经提交过的文件
敏感信息:可能包含被删除但仍存在于历史中的密码、密钥
开发者信息:用户名、邮箱等
服务器信息:远程仓库地址
常见安全问题:
Web 服务器暴露
.git目录敏感信息被提交到版本历史
通过
.git目录重建完整源代码
3为什么Git采用内容寻址存储
Git 采用内容寻址存储,主要是为了实现高效、可靠、去重的版本管理
🛡️ 确保内容不可篡改
Git 对每个文件或提交的内容计算唯一的 SHA-1 哈希值,作为它的 “身份证号”
如果文件内容哪怕只改一个字符,哈希值就会完全不同,Git 立刻就能发现内容被篡改
这相当于给所有历史版本加了 “防伪标签”,保证代码历史的完整性
🧹 自动去重,节省空间
多个分支或版本里的相同文件,Git 只会存一份,因为它们的哈希值相同
比如你在不同分支都用到同一个配置文件,Git 不会重复存储,而是用同一个哈希值指向它
这让仓库体积比复制整个项目的传统方式小得多
⚡ 快速对比与合并
内容寻址让 Git 能通过哈希值快速判断两个文件是否相同,不用逐行比较
合并分支时,Git 只需对比哈希值就能定位差异,大幅提升合并效率
🔗 天然支持分布式协作
每个开发者的本地仓库都有完整的内容寻址数据库,不需要依赖中央服务器
多人协作时,通过哈希值就能确认双方的内容是否一致,避免冲突和数据丢失
即使离线也能正常提交、分支、合并,联网后再同步即可
📜 完整的可追溯性
每个提交的哈希值都包含父提交的哈希值,形成一条完整的 “哈希链”
你可以顺着这条链追溯任何版本的来源,轻松定位问题引入的时间点
这对排查 Bug、审计代码变更至关重要
4.四种基础对象类型
Git 所有版本内容都靠Blob、Tree、Commit、Tag 四大基础对象存储,全是内容寻址(用内容算唯一哈希当 ID),存在.git/objects/里,记清「谁存什么、怎么关联」就够了!
🔹 1. Blob 对象 - 只存「文件内容本身」的小文件
全称:Binary Large Object(二进制大对象)
比如:你有个
test.py文件,内容是print(123),Git 生成的 Blob 只存print(123)这行内容,不知道它叫test.py,也不知道它在src文件夹里关键特点:内容相同,Blob 就相同(比如两个文件夹里的
config.py内容一样,Git 只存 1 个 Blob,省空间)哈希特点:内容相同的文件,不管名字 / 路径在哪,哈希值完全一样(Git 去重的核心,相同内容只存一个 Blob)
关联操作:执行
git add 文件名,Git 就会把文件内容转成 Blob 对象存起来
Blod对象代码及结果
创建 blob 对象:
# 将内容写入对象数据库
echo "Hello, Git" | git hash-object -w --stdin输出:
b7aec520dec0a7516c18eb4c68b64ae1eb9b5a5e查看 blob 对象:
git cat-file -p b7aec520dec0a7516c18eb4c68b64ae1eb9b5a5e输出:
Hello, Git查看对象类型:
git cat-file -t b7aec520dec0a7516c18eb4c68b64ae1eb9b5a5e输出:
blobBlob 对象的存储位置:
.git/objects/b7/aec520dec0a7516c18eb4c68b64ae1eb9b5a5e对象以哈希值的前两位作为目录名,后 38 位作为文件名
🔹 2. Tree 对象 - 给 Blob 贴「名字 + 位置」的标签,还管文件夹结构
存什么:目录结构信息,包含「当前目录下的文件名 / 子目录名 + 对应 Blob / 子 Tree 的哈希 ID + 文件权限」
核心作用:补全 Blob 的缺陷,专门记录 **“哪个 Blob 对应哪个文件名”“哪个文件在哪个文件夹”,还会记录文件权限,最终拼成整个项目的目录树
层级关系:根目录有一个根 Tree,子目录对应子 Tree,最终所有 Tree 串联成完整的项目目录树
Tree对象代码及结果
查看 tree 对象:
git cat-file -p main^{tree}输出示例:
100644 blob a906cb2a4a904a152e80877d4088654daad0c859 README.md
100644 blob 8f94139338f9404f26296befa88755fc2598c289 config.js
040000 tree 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0 src字段说明:
🔹 3. Commit 对象 - 给整个项目拍「带说明的快照」
核心作用:当你执行
git commit时,Git 会先给当前所有文件生成 Tree(根目录的总 Tree),然后 Commit 对象就像快照的 “说明书”,把这次快照的所有信息打包Commit 里存的核心信息(全是关键):
(1)指向根 Tree 的哈希(通过这个 Tree,能还原这次提交的完整项目目录 + 所有文件);
(2)指向父 Commit 的哈希(上一次快照的说明书,串起所有历史版本,能回滚、看git log);
(3)提交人、时间、提交备注(就是你-m写的话,比如 “新增登录功能”)
你用git log看到的每一行记录,就是一个 Commit 对象!
关联操作:执行
git commit,Git 会先生成当前工作区的根 Tree,再基于根 Tree 创建 Commit 对象
Commit对象代码及结果
查看 commit 对象:
git cat-file -p HEAD输出示例:
tree 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0
parent 1a2b3c4d5e6f7890abcdef1234567890abcdef12
author mario <mario@123.top> 1706234567 +0800
committer mario <mario@123.top> 1706234567 +0800
修复安全漏洞字段说明:
🔹 4. Tag 对象 - 版本的 “永久书签”
核心作用:Commit 对象是哈希值(一串乱码,比如 a1b2c3),记不住,Tag 就是给某个重要的 Commit起个好记的名字,比如
v1.0、v2.1-beta,方便快速找到两种类型:
(1)轻量标签:仅存 “标签名 + 目标 Commit 哈希”(简单快捷,git tag v1.0);
(2)附注标签:存完整信息(含说明,git tag -a v1.0 -m "v1.0正式版",推荐打正式版本用)
关联操作:
git tag 标签名 提交哈希(给指定版本打标签),git checkout 标签名(切换到标签版本)
Tag对象代码及结果
附注标签(Annotated Tag)会创建一个 tag 对象:
git cat-file -p v1.0输出示例:
object 1a2b3c4d5e6f7890abcdef1234567890abcdef12
type commit
tag v1.0
tagger evalEvil <evalevil@example.com> 1706234567 +0800
版本 1.0 发布5.对象存储机制
Git 把四大基础对象(Blob/Tree/Commit/Tag)存到 .git/objects/ 里,全程围绕内容寻址+分块压缩+哈希命名,既保证唯一性、又省空间,还能快速查找
1.完整的存储流程:

2.手动验证对象哈希
# 计算文件的 Git 哈希值
git hash-object filename.txt
# 不写入数据库,只计算哈希
echo "Hello, Git" | git hash-object --stdin使用 Python 验证:
import hashlib
import zlib
content = b"Hello, Git\n"
header = f"blob {len(content)}\0".encode()
store = header + content
sha1 = hashlib.sha1(store).hexdigest()
print(sha1) # b7aec520dec0a7516c18eb4c68b64ae1eb9b5a5e3.松散对象与打包对象
松散对象(Loose Objects):
每个对象单独存储为一个文件,位于 .git/objects/[前2位]/[后38位]
打包对象(Packed Objects):
为了节省空间,Git 会定期将松散对象打包成 .pack 文件:
.git/objects/pack/
├── pack-abc123...def.idx # 索引文件
└── pack-abc123...def.pack # 打包文件手动打包:
git gc查看打包内容:
git verify-pack -v .git/objects/pack/pack-*.idx6.引用系统
Git 引用系统想象成你手机里的「联系人通讯录」—— 哈希值是一串难记的手机号(比如 138xxxx1234),引用就是联系人名字(比如「张三」),核心就是用 “好记的名字” 代替 “难记的哈希号”,所有操作都是在 “查通讯录、改通讯录、标记当前要联系的人”,所有引用相关的文件,都在项目 .git 文件夹里
类核心引用:就是通讯录的 3 个「分组」
Git 把不同用途的 “名字 - 哈希映射” 分成 3 个分组,全在 .git/refs/ 文件夹下,就像通讯录里的「家人、同事、客户」分组,分工明确,找起来方便
1. 分支引用:.git/refs/heads/ → 通讯录「常用好友组」
作用:给分支名(比如 main、dev)绑定「该分支最新版本的哈希值」
类比:常用好友组里,「张三」对应手机号 138xxxx1234,「李四」对应 139xxxx5678
实际样子:
(1)有个 dev 分支,就会有个文件 .git/refs/heads/dev;
(2)这个文件里只有一行字:比如a823f80(dev 分支最新版本的哈希值)
关键操作:你在 dev 分支执行
git commit提交代码,Git 会自动更新这个文件 —— 把旧哈希删掉,换成新生成的版本哈希,就像 “张三换手机号了,通讯录里直接更新号码”
分支引用代码块
分支存储在 .git/refs/heads/ 目录下:
cat .git/refs/heads/main输出:
1a2b3c4d5e6f7890abcdef1234567890abcdef12每个分支文件只包含一个 40 位的 SHA-1 哈希值,指向该分支的最新提交
2. 标签引用:.git/refs/tags/ → 通讯录「重要联系人组」
作用:给标签名(比如 v1.0、v2.1)绑定「重要版本的哈希值」(一般是正式版)
类比:重要联系人组里,「客户王总」对应 136xxxx9999,一旦存好就不随便改
实际样子:
(1)打了个 v1.0 标签,就会有个文件 .git/refs/tags/v1.0;
(2)文件里只写一行:正式版的哈希值,比如b945g71。
关键特点:静态不变化—— 创建后不会自动改哈希,除非你手动删了重打,就像重要联系人的手机号一般不会变,适合标记固定的正式版本
标签引用代码块
标签存储在 .git/refs/tags/ 目录下:
cat .git/refs/tags/v1.03. 远程引用:.git/refs/remotes/ → 通讯录「微信好友组(缓存版)」
作用:记录「你最后一次和远程仓库(比如 GitHub 的 origin)同步时,远程分支的哈希值」
类比:你把微信好友的手机号存到本地通讯录,这个通讯录就是 “缓存版”—— 好友换手机号,你不手动更新,本地就还是旧号码
实际样子:
(1)远程仓库叫 origin,有个 main 分支,就会有文件 .git/refs/remotes/origin/main;
(2)文件里写着:你上次git pull/fetch时,远程 origin/main 分支的哈希值
关键特点:不会自动更新, 只有执行
git fetch/pull/push,Git 才会去远程仓库查最新哈希,然后更新这个文件,就像 “手动刷新微信好友资料,更新本地通讯录”
远程引用代码块
远程分支存储在 .git/refs/remotes/ 目录下:
.git/refs/remotes/
└── origin/
├── HEAD
└── mainHEAD 指针:就是通讯录里「星标当前联系人」
HEAD 不是上面的 “分组文件”,但却是引用系统的核心关键—— 它的作用就是标记 “你现在正在操作哪个引用(哪个名字)”,就像你在通讯录里给「张三」标星,告诉自己 “现在要和张三聊天,所有操作都针对他”
HEAD代码块:
HEAD 文件指向当前分支:
cat .git/HEAD输出:
ref: refs/heads/dev如果处于分离 HEAD 状态(直接指向某个提交):
1a2b3c4d5e6f7890abcdef1234567890abcdef121. HEAD 的核心样子:永远只写一句话
日常 99% 的情况,HEAD 文件(.git/HEAD)里的内容都是:ref: refs/heads/分支名比如:ref: refs/heads/dev → 意思是「我现在标星的是 dev 分支,所有操作都基于 dev」
2. 切换分支的本质:就是「改星标」
执行git checkout dev(切换到 dev 分支),Git 干的唯一事就是修改 HEAD 文件:把里面的内容从原来的ref: refs/heads/main,改成ref: refs/heads/dev—— 就像把星标从「张三」移到「李四」,告诉 Git “我现在要操作李四了”
3. Git 怎么找到最终版本?(星标→查分组→拿号码)
当你标星 dev 分支后,Git 会按这个步骤找对应版本,一步都不会错:HEAD(标星dev) → 去refs/heads/找 dev 文件 → 拿到里面的哈希值 → 用哈希找到对应的版本内容。类比:星标张三 → 去常用好友组找张三 → 拿到他的手机号 → 用手机号打电话
refs/logs/ 日志:就是通讯录的「修改记录」
你每次修改通讯录(比如更新张三手机号、把星标从张三移到李四、刷新远程好友号码),都会自动记一笔记录 —— 这就是 .git/refs/logs/ 的作用,记录所有引用和 HEAD 的每一次变化
类比:通讯录里的「修改日志」:2026.1.28 把张三手机号从 138xxxx1234 改成 138xxxx4321;2026.1.28 星标从张三切换到李四
关键命令:
git reflog→ 就是直接查看这份修改日志,哪怕你误删了分支、误切换了版本,只要看这份日志,就能找到之前的 “名字” 和 “哈希号”,把版本恢复回来 —— 相当于 “通讯录删了张三,看修改日志找到他原来的手机号,重新加回来”
你每次修改通讯录(比如更新张三手机号、把星标从张三移到李四、刷新远程好友号码),都会自动记一笔记录 —— 这就是 .git/refs/logs/ 的作用,记录所有引用和 HEAD 的每一次变化。
类比:通讯录里的「修改日志」:2026.1.28 把张三手机号从 138xxxx1234 改成 138xxxx4321;2026.1.28 星标从张三切换到李四
关键命令:
git reflog→ 就是直接查看这份修改日志,哪怕你误删了分支、误切换了版本,只要看这份日志,就能找到之前的 “名字” 和 “哈希号”,把版本恢复回来 —— 相当于 “通讯录删了张三,看修改日志找到他原来的手机号,重新加回来”
引用日志代码块(Reflog)
Git 会记录引用的变更历史,存储在 .git/logs/ 目录下:
cat .git/logs/HEAD输出示例:
0000000... 1a2b3c4... evalEvil <email> 1706234567 +0800 commit (initial): 初始提交
1a2b3c4... 2b3c4d5... evalEvil <email> 1706234600 +0800 commit: 添加功能查看引用日志:
git reflog安全提示:即使使用 git reset --hard 删除了提交,通过 reflog 仍然可以找回。这对于数据取证非常有用。
7 暂存区(Index)
1 index 文件
暂存区的数据存储在 .git/index 文件中,这是一个二进制文件。
查看暂存区内容:
git ls-files --stage输出示例:
100644 a906cb2a4a904a152e80877d4088654daad0c859 0 README.md
100644 8f94139338f9404f26296befa88755fc2598c289 0 config.js字段说明:
2 暂存槽位
在合并冲突时,index 会使用多个槽位:
8 配置文件
1 仓库配置
.git/config 文件存储仓库级配置:
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
[remote "origin"]
url = https://github.com/username/repo.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"]
remote = origin
merge = refs/heads/main
[user]
name = evalEvil
email = evalevil@example.com安全提示:配置文件可能包含敏感信息,如:
远程仓库地址(可能暴露内部系统)
用户身份信息
代理配置
2 info/exclude
.git/info/exclude 文件用于本地忽略规则,不会被提交到仓库:
# 本地忽略规则
my-local-notes.txt
*.local9 钩子脚本
1 钩子系统的设计原理
为什么 Git 需要钩子系统?
Git 的钩子系统是一种事件驱动的扩展机制,允许用户在 Git 操作的关键节点插入自定义逻辑。这种设计体现了 Unix 哲学:
钩子的执行机制:

如果钩子不存在:直接完成提交
如果退出码为 0:继续提交流程
如果退出码非 0:中止提交,显示错误
为什么钩子不会被提交到仓库?
.git/hooks/ 目录位于 .git/ 内部,而 .git/ 目录本身不会被版本控制。这是一个安全设计:
防止恶意仓库通过钩子执行任意代码
允许不同开发者使用不同的本地钩子
服务器端钩子和客户端钩子可以独立配置
团队共享钩子的最佳实践:
# 将钩子脚本放在项目目录中
mkdir -p .githooks
cp pre-commit .githooks/
# 配置 Git 使用自定义钩子目录
git config core.hooksPath .githooks2 hooks 目录
.git/hooks/ 目录包含 Git 钩子脚本:
.git/hooks/
├── applypatch-msg.sample
├── commit-msg.sample
├── post-update.sample
├── pre-applypatch.sample
├── pre-commit.sample
├── pre-push.sample
├── pre-rebase.sample
├── prepare-commit-msg.sample
└── update.sample启用钩子:去掉 .sample 后缀并添加执行权限。
3 常用钩子
4 安全相关的钩子应用
pre-commit 钩子示例(检查敏感信息):
#!/bin/bash
# .git/hooks/pre-commit
# 检查是否包含敏感信息
if git diff --cached | grep -iE "(password|secret|api_key|private_key)" > /dev/null; then
echo "警告:检测到可能的敏感信息!"
echo "请确认是否要提交这些内容。"
exit 1
fi
exit 010 从 .git 目录恢复源代码
这是安全工作中的重要技能——当发现暴露的 .git 目录时,如何恢复完整源代码。
1 基本恢复方法
如果有完整的 .git 目录:
# 进入包含 .git 的目录
cd /path/to/exposed/.git/..
# 恢复工作目录
git checkout .2 从对象数据库恢复
如果只有 objects 目录:
# 查看所有对象
find .git/objects -type f | while read file; do
hash=$(echo $file | sed 's/.*objects\///' | tr -d '/')
echo "=== $hash ==="
git cat-file -t $hash 2>/dev/null
git cat-file -p $hash 2>/dev/null
done3 常用取证命令
# 列出所有提交
git log --all --oneline
# 列出所有分支(包括远程)
git branch -a
# 列出所有标签
git tag
# 查看所有引用
git show-ref
# 查看 reflog
git reflog
# 搜索历史中的敏感信息
git log -p -S "password"
git log -p -S "api_key"
git log -p --all -- "*.env"
# 查看被删除的文件
git log --all --full-history -- "**/deleted-file.txt"
# 恢复被删除的文件
git checkout <commit-hash> -- path/to/file4 使用工具自动化
GitTools(用于从暴露的 .git 目录下载和恢复):
GitTools(用于从暴露的 .git 目录下载和恢复):
# 下载暴露的 .git 目录
./gitdumper.sh https://target.com/.git/ /tmp/git-dump
# 恢复源代码
./extractor.sh /tmp/git-dump /tmp/sourcetruffleHog(搜索敏感信息):
trufflehog git file://./repo11 Git内部文件结构小结
本章我们深入学习了:
.git 目录结构:了解每个文件和目录的作用
Git 对象模型:blob、tree、commit、tag 四种对象
对象存储机制:哈希计算、压缩存储、打包
引用系统:HEAD、分支、标签、reflog
暂存区:index 文件的结构
配置与钩子:配置文件和钩子脚本
源代码恢复:从 .git 目录恢复完整源代码
这些知识是进行 Git 相关安全工作的基础