git add的作用是将新增或已更改文件的内容添加到index中,作为下次commit的内容
在一次commit之前,可以执行多次add,一次add就是一次拍照,内容是当前的新增或更改内容,如果后续对内容进行修改且希望提交,需要再次add
默认情况下,add不会添加忽略文件,使用--force可强制添加。如果不使用--force,命令会失败并返回忽略的文件列表
语义及设计理念
git add的作用不是“添加文件”,而是把工作区中的文件内容,写入Git对象数据库,并更新index中对应路径指向的对象
Working Tree(工作区)
↓ git add
Index / Staging Area(暂存区)
↓ git commit
Repository(对象库 + 提交历史)
git add只负责从工作区读取文件内容,生成(或复用)blob对象,更新index,它不产生commit,也不更改HEAD
底层行为
Git不关心文件,只关心内容
当执行
git add a.txt
Git做的事情是
- 读取a.txt内容
- 对内容计算SHA-1/SHA-256(新仓库)
- 生成一个
blob对象(如果这个内容以前不存在) - 把这个blob的hash写进index
- index中记录
路径名 -> blob hash + 文件模式
如果文件内容没变,git add不会生成新对象,这就是Git去重能力极强的根本原因
index
index存储下一次commit的完整快照草稿,index中的每一条记录包含
- 文件路径
- 文件权限(100644/100755等)
- blob对象hash
设计哲学
git add是Git的核心设计之一
支持部分提交
假设改了一个文件
// main.c
修了 bug
加了日志
写了一半新功能(未完成)
可以
git add -p main.c
只把bug修复,日志加入index,而把未完成的新功能留在工作区
Git的单位不是文件,是”内容块(hunk)“
index的设计
index是缓冲层,如果没有git add,每次commit只能提交工作区的全部状态,无法构建干净、可回溯的历史
OPTIONS
<pathspec>要添加的文件内容
可以使用文件通配符(例如,*.c)来添加所有匹配的文件
也可以指定一个前导目录名(例如dir/来添加dir/下所有文件).也是<pathspec>,是指从当前目录开始,递归匹配所有路径,git add .,在不同Git版本/配置下,会add新文件,会add修改文件,可能不会stage删除<pathspec>是一个匹配规则系统,支持:目录,glob,排除,魔法前缀-n/--dry-run模拟执行-v/--verbose尽可能多的显示信息-f/--force允许添加已忽略的文件--sparse允许更新sparse-checkout范围之外的索引条目。通常情况下,git add命令会拒绝更新路径不再sparse-checkout范围内的索引条目,因为这些文件可能会在未发出警告的情况下从工作树中移除-i/--interactive以交互方式将工作树中修改后的内容添加到索引中。可以提供可选的路径参数,以将操作限制在工作树的子集中p/--patch以交互方式选择索引和工作树之间的补丁块,并将其添加到索引中。这使用户有机会在将修改后的内容添加到索引之前查看差异。这实际上运行了add--interactive命令,但绕过了初始命令菜单,直接跳转到补丁子命令-U<n>/--unified=<n>生成包含行上下问的而差异。默认值为diff.context,如果未设置配置选项,则默认值为3 --inter-hunk-context=<n>显示差异代码块之间的上下文,最多显示指定的行数,从而合并彼此接近 的代码块。默认值为diff.interHunkContext,如果为设置配置选项,则默认值为0-e/--edit在编辑器中打开差异文件与索引的对比,并允许用户进行编辑。编辑器关闭后,调整代码块标题并将补丁应用到索引。此选项的目的是选择要应用的补丁行,甚至可以修改要暂存的行的内容。这比使用交互式代码块选择器更快、更灵活。但是很容易出错,导致创建了一个不适用于索引的补丁-u/--update仅更新索引中已存在与<pathspec>匹配的条目。此操作会删除并修改索引条目以匹配工作树,但不会添加新文件。如果在使用-u选项时未指定<pathspec>,则会更新整个工作树中所有跟踪的文件(旧版本的Git会将更新限制在当前目录及其子目录中)-A/--all/--no-ignore-removal不仅更新工作树中存在与<pathspec>匹配的文件的索引,还会更新索引中已存在条目的文件。此操作会根据工作树添加、修改和删除索引条目。如果在使用-A选项时未执行<pathspec>,则会更新整个工作树中的所有文件(旧版本的Git会将更新限制在当前目录及其子目录中),简单来说,该操作会复刻工作区中的所有行为(添加,修改,删除)到index--no-all/--ignore-removal更新index,只处理新增和修改,忽略“已删除”的文件。新文件: 加入index, 修改文件: 更新index, 删除文件: index中仍保留(不stage删除)。当不提供<pathspec>时,这个选项是no-op(无效)-N/--intent-to-add在index中记录“我打算将来add这个文件”,但不记录内容。index中有路径,但没有blob内容,这是一个空条目。在Git内部,index有该文件的entry, blob hash是“未定义状态”,文件被视为tracked(已跟踪),但内容仍完全来自工作区,还未进入对象数据库。-N是一个逻辑跟踪标记,不是内容操作。这个功能的作用:1.让git diff显示“未add的新文件内容”,默认情况下git diff不会显示新文件内容,因为diff的基准是index,新文件index不存在,-N选项让diff可以显示出新文件内容。2.配合git commit -a,git commit -a只会提交tracked文件。如果想用commit -a但又有新文件,可以使用-N--refresh不添加内容,只更新index中的stat信息。index里除了blob hash,还有文件大小,mtime,inode(部分系统)。Git用这些来做快速是否变更判断。使用场景:文件内容没变,但时间戳/元数据异常,Git误判为modified,使用git add --refresh修正index的文件状态缓存--ignore-errors部分文件add失败,不中断整个add操作。行为:成功的:继续add,失败的:跳过,最终返回非0.Git默认是要么全部成功,要么失败。这个选项是尽量成功,但告诉你有问题--ignore-missing检查pathspec中哪些文件会被忽略,不关心它们是否存在。该命令只能和--dry-run一起用,不修改index--no-warn-embedded-repo当git add some_dir/,而some_dir/.git存在时,Git会警告:你正在添加一个嵌套仓库。因为这通常意味着:子模块没有正确处理,历史会混乱。这个选项的作用是关闭该警告,你需要明确知道自己在干什么--renormalize对所有已跟踪文件,重新执行官clean过滤器,然后重新写入index. 典型clean行为:CRLF->LF, text属性生效。触发场景:修改了core.autocrlf,.gitattributes的text/eol、历史index中内容脏了。该命令只会影响index,不修改工作区文件,隐含-u--chmod=(+|-)x只修改index中的可执行位,不修改磁盘文件,只改Git记录--pathspec-from-file/--pathspec-file-nulpathspec不从命令行传,而是从文件/stdin读。适用于路径太多,路径包含空格、换行、特殊字符、自动化工具生成路径列表。--pathspec-file-nul用\0分隔,所有字符字面量处理,最安全的批量路径方式--选项终止符,明确告诉Git:后面全是路径,不是选项
交互式add
# 启动交互式暂存界面
git add -i
# 或使用完整命令
git add --interactive
# 简写模式(更新版本)
git add -p # 补丁模式,最常用
启动后显示菜单
staged unstaged path
1: unchanged +12/-0 README.md
2: unchanged +20/-4 src/main.py
3: unchanged +3/-0 newfile.txt
*** Commands ***
1: status 2: update 3: revert 4: add untracked
5: patch 6: diff 7: quit 8: help
What now>
- status(状态)
显示当前更改的摘要信息
What now> 1
# 显示哪些文件已暂存、未暂存
- update(更新)
将整个文件的更改添加到暂存区
What now> 2
staged unstaged path
1: unchanged +12/-0 README.md
2: unchanged +20/-4 src/main.py
Update>>
# 输入文件编号(如 1,2 或 1-3)
Update>> 1,2
# 暂存选中文件的全部更改
- revert(恢复)
从暂存区移除已暂存的文件
What now> 3
staged unstaged path
1: +12/-0 unchanged README.md
Revert>>
# 输入要取消暂存的文件编号
- add untracked(添加未跟踪文件)
添加新文件到暂存区
What now> 4
staged unstaged path
1: unchanged +3/-0 newfile.txt
Add untracked>>
- patch(补丁模式)
逐块暂存更改-这是交互式add的核心功能
What now> 5
# 或直接使用补丁模式
git add -p
git add --patch
- diff(查看差异)
显示暂存区与工作区的差异
What now> 6
staged unstaged path
1: +12/-0 +4/-2 README.md
Diff>>
# 输入文件编号查看具体差异
- quit(退出)
退出交互式界面
- help(帮助)
显示命令帮助信息
补丁模式
启动补丁模式
# 对特定文件使用补丁模式
git add -p filename.py
# 对所有更改文件使用
git add -p
# 等价于
git add --patch
补丁模式的工作流程
- Git逐块显示更改
- 每个块前显示选项提示
- 用户选择如何操作该块
- 继续处理下一个块
选项
当Git显示一个代码块时,会提示
Stage this hunk [y,n,q,a,d,j,J,g,/,s,e,?]?
常用选项
- y(yes):暂存此块
- n(no):不暂存此块
- q(quit):退出,不暂存当前块及后续所有块
- a(all):暂存当前文件的所有块
- d(done):不暂存当前文件的所有块
- j(jump):跳过当前块,暂不决定
- J(Join):跳到下一个块,但把当前块合并到下一个
- g(goto):选择特定块处理
- /(search):用正则表达式搜索块
- s(split):将当前大块分割成更小的块
- e(edit):手动编辑当前块
- ?(help):显示帮助
s(split) 分割大块
当Git显示的块太大,包含多个逻辑更改时
@@ -10,7 +10,10 @@ def calculate_total(items)
total = 0
for item in items:
total += item.price
- return total
+
+ # 添加折扣计算
+ # discount = total * 0.1
+ return total - discount
# 另一个不相关的函数
def validate_input(data):
输入s后,Git会尝试将大块分割成更小的逻辑块
e(edit) 手动编辑
- 选择特定暂存行
- 修改要暂存的内容
- 完全自定义暂存内容
输入e后进入编辑界面
# Manual hunk edit mode -- see bottom for a quick guide.
@@ -10,7 +10,10 @@ def calculate_total(items):
total = 0
for item in items:
total += item.price
- return total
+
+ # 添加折扣计算
+ discount = total * 0.1
+ return total - discount
# ---
# To remove '-' lines, make them ' ' lines (context).
# To remove '+' lines, delete them.
# Lines starting with # will be removed.
编辑规则
- 保留
-开头的行:从暂存区删除 - 删除
+开头的行:不添加这些更改 - 将
-改为空格:保留原行 - 添加新行:使用
+开头
Hunk
在Git中,Hunk是diff输出的一个基本单位,代表文件中一组连续的更改行。一个hunk包含
- 上下文行(未更改)
- 删除的行(以
-开头) - 添加的行(以
+开头)