Git 操作手册
本页目录
这份手册从“我现在要完成什么”进入:先读懂状态,再记录、整合或恢复改动。每节可以单独查阅;同节中的备选操作会明确标出,不要把整篇当成一份连续执行的脚本。
以下命令按 Git 2.53 验证,在目标仓库的根目录执行 Bash / Zsh 命令。旧版本可先用 git --version 确认,特别留意 restore、switch 和 stash 显示选项是否可用。示例约定:origin 是远程名,main 是主分支,feature 是功能分支;file.txt 和 path/to/file 替换为实际路径,COMMIT / KNOWN_GOOD 替换为从日志或标签取得的提交。仓库默认分支不一定叫 main。输出均为示例,哈希和提示语言会因环境不同而变化。
| 当前任务 | 入口 |
|---|---|
| 看懂改了什么、哪些会提交 | 查看改动 |
| 开始分支、挑选内容并提交 | 记录提交 |
| 临时切走,稍后继续 | 暂存工作 |
| 撤销暂存、丢弃修改、撤回提交 | 撤销与恢复 |
| 同步主线、处理冲突、移植修复 | 整合分支 |
| 找回提交、追踪变化、定位回归 | 历史与恢复 |
| 比较远程进度、推送、处理拒绝 | 远程协作 |
| 并行修复、初始化依赖、发布标签 | 工作树、子模块与标签 |
查看改动:先分清比较的两端
Git 中有三份需要区分的内容:HEAD 是当前提交,暂存区(index) 是下一次提交准备记录的快照,工作区(working tree) 是磁盘上正在编辑的文件。add 会把当时的文件内容写入暂存区;之后继续编辑,暂存区不会自动跟着更新。
当前分支与改动概况
# 用两列状态查看暂存与未暂存的改动,并显示分支信息
--short 使用短格式,--branch 增加分支和上游信息。示例输出:
## feature...origin/feature [ahead 1, behind 2]
MM file.txt
M README.md
A new.txt
?? notes.txt
| 标记 | 如何读 |
|---|---|
MM | 第一列:暂存区相对 HEAD 有修改;第二列:工作区相对暂存区还有修改 |
·M | 第一列为空格:没有暂存;工作文件已修改 |
A· | 新文件已进入暂存区,暂存后没有继续修改 |
?? | 未跟踪,不会自动进入提交;普通 diff 也不展示它的内容 |
表中的 · 代表一个空格,用来保留列的位置;实际输出是空格。ahead 1, behind 2 表示相对本地记录的上游分支,当前分支独有 1 个提交,上游独有 2 个;这个查询不会联网,远程进度需先 fetch。冲突时 UU 等标记有专门含义,进入 冲突处理,不要套用普通两列解释。状态格式参考
未暂存与已暂存:按问题选择 diff
# 查看工作区相对暂存区的变化:哪些修改还没有 add
适合继续编辑前或准备 add 时使用。-- 把版本/选项与路径分开;去掉 file.txt 可查看所有已跟踪文件的未暂存差异。
# 查看暂存区相对 HEAD 的变化:这个文件将提交哪些内容
--cached 与 --staged 同义。对前面的 MM 文件,这次与上一次 diff 显示的是不同部分;提交前要检查这一份。只想看总览,可用 git diff --cached --stat,但统计不能替代补丁审阅。
补丁片段示例:
-timeout = 10
+timeout = 30
- 行来自比较起点,+ 行来自比较终点;@@ 给出旧、新文件的行范围。这里是否表示“将提交的修改”,取决于使用的是哪一种 diff。差异比较参考
记录提交:先选内容,再形成快照
为新任务创建分支
从当前提交开始新任务时使用下面的命令。先用 status 确认当前位置;已有修改通常会随工作区保留,不会因为创建分支而自动提交。
# 从当前 HEAD 创建并切换到新的 feature 分支
-c 表示创建;分支已存在时使用 git switch feature,两者是备选。切换被本地修改阻止时,先提交或 stash,再重试。
整文件暂存,或只选部分修改
# 把这个文件当前的内容放入暂存区
适合该文件的所有修改属于同一任务时使用。如果文件已删除,这条命令记录删除。文件里混有两个任务时,改用下面的交互选择,不要先执行整文件 add:
# 逐块选择本次提交要包含的修改
提示中的 y 暂存当前块,n 留在工作区,s 尝试拆小,q 结束选择。无法再拆的块需要先调整文件,或谨慎使用 e 编辑补丁。选错了可 撤销暂存,工作文件保留。新建的未跟踪文件可先按整文件暂存。交互暂存参考
审阅后提交,并核对实际结果
# 审阅所有已暂存内容,确认本次提交的完整范围
核对文件、补丁与项目测试结果后再提交。普通 git commit 记录暂存区;它不会自动包含之后的编辑或未跟踪文件。
# 为审阅过的暂存快照创建提交;替换为描述本次目的的消息
# 核对刚创建的提交及涉及文件
# 检查哪些修改仍留在工作区或暂存区
两条是提交后的配套检查。若仍看到修改,不一定是失败:部分暂存本来就会留下其他内容。只修正最后一次尚未共享提交的说明,可用 git commit --amend --only -m "New message";--only 避免把当前暂存的其他修改一起带入。amend 会替换提交身份。提交与 amend
暂存工作:保存、审阅、恢复、再删除
需要暂时处理别的任务,但当前修改还不适合提交时使用。stash 不是备份服务,只保存在当前仓库;子模块内部的修改需要在子模块中单独处理。
# 保存已跟踪修改和未跟踪文件,附上稍后可识别的说明
-u 包含未跟踪文件,不包含被忽略文件。成功后用 git status --short 确认要带走的改动已退出工作区,再切换分支。需要的被忽略文件仍在原处,不要据此认定整份目录都被保存。
# 根据说明找到要恢复的条目,而非直接假定第一项就是目标
stash@{0}: On feature: before branch switch
下面以查到的 stash@{0} 为例。新建或删除其他 stash 会改变编号,每次删除前重新核对。
# 审阅这份 stash 的补丁,同时显示其中保存的未跟踪文件
确认当前处于要恢复的分支,且 status 没有其他未提交工作后,再应用:
# 恢复保存的内容,同时保留 stash 条目供核对
默认不恢复原来的暂存划分;若需要同时尝试恢复暂存区,选择 git stash apply --index 'stash@{0}' 代替上面的命令。恢复后分别检查 status、未暂存 diff 和已暂存 diff,并运行相关测试。出现冲突时先解决文件,不要重复 apply;stash 没有通用的 --abort,因此应用前保持工作区干净很有意义。
# 仅在恢复内容已核对、条目身份仍正确时删除这份 stash
随后用 git stash list 确认目标条目已移除。pop 把应用和成功后的删除合在一起;冲突时通常保留条目。这里分开使用 apply / drop,让核对发生在删除前。stash 行为参考
撤销与恢复:选中要改变的那一份内容
先用前面的两种 diff 确认要保留的内容,再按目的选择。下表每行是不同任务,不是执行顺序。
| 目的 | 改变什么 | 进入 |
|---|---|---|
| 撤销 add,文件保留 | 暂存区 | 撤销暂存 |
| 丢弃某文件未暂存修改 | 工作区 | 还原文件 |
| 撤回未共享的最后一次提交 | 当前分支指向 | 撤回本地提交 |
| 撤销已经共享的改动 | 新增反向提交 | 反向提交 |
下面的交互固定从同一状态开始:HEAD 为 B,暂存区为 C,工作文件为 D,reset 的目标为较早的 A。点击不同操作,对比哪些内容改变;这些按钮是备选操作,不会累计执行。A / B / C / D 代表示例文件的不同内容。
同一初始状态下,soft 只移动 HEAD,mixed 还重置暂存区,hard 再覆盖工作区;restore --staged 不移动 HEAD。
撤销暂存,保留工作文件
# 将这个文件的暂存版本恢复为 HEAD,保留磁盘上的编辑
--staged 只选择暂存区,默认来源是 HEAD。执行后 git diff --cached -- file.txt 应为空,改动仍可在普通 diff 中看到。新加的文件会退回未跟踪状态,用 status 查看;普通 diff 不显示其内容。这里假定仓库已有首次提交。restore 的来源与目标
丢弃未暂存修改,或取回历史文件
只想丢弃 file.txt 在 add 之后的编辑时,先读 git diff -- file.txt,确认这些内容不再需要,再执行:
# 用暂存区的版本覆盖工作文件,丢弃该文件的未暂存修改
完成后普通 diff 应为空,已暂存 diff 保持原样。没有指定 --source 时,这个操作从暂存区恢复,不一定恢复到最后一次提交。从未提交的编辑通常不能靠 reflog 找回。
如果要取回某个历史提交中的单个文件,这是另一种任务。先确认版本和内容:
# 读取指定提交中的文件内容,尚不覆盖工作区
# 确认当前文件的修改已保存后,用选定历史版本覆盖它
--source 明确恢复来源;这条命令不移动分支,也不暂存。用 git diff -- path/to/file 审阅恢复结果,确认后再 add / commit。若目标提交中没有这个已跟踪路径,restore 会删除它以匹配来源,因此前面的 show 检查不能省略。
撤回最后一次本地提交,保留改动
适用于当前分支最后一次提交尚未共享、且不是根提交的情况。先审阅 git show HEAD,确认撤回对象;已有暂存或未暂存修改也会保留,之后需要一起核对。先为当前提交留一个引用:
# 保留当前提交的位置;此分支不备份未提交的文件修改
使用尚不存在的备份分支名,创建失败先处理再继续。然后执行所需的 soft reset:
# 把当前分支退到父提交,保留暂存区与工作文件
HEAD~1 是沿第一父链的上一提交。用 git log -1 --oneline 核对分支位置,再用两种 diff 检查待重新提交的内容。如果目的是把这些改动全部退回未暂存状态,选择 git reset --mixed HEAD~1 代替 soft;不要在 soft 后再次执行同样的 HEAD~1,否则会再退一条提交。
图中的 hard 还会覆盖工作区已跟踪内容,必要时删除挡住目标路径的未跟踪文件。它适用于明确要舍弃当前状态的任务,不是 soft / mixed 之后的“最后一步”。如果误退了提交,先按 reflog 恢复 找到并固定原提交。reset 模式参考
撤销已共享提交的效果
先保证工作区和暂存区干净,并用 git show COMMIT 审阅要抵消的普通提交。revert 新增一条反向提交,保留已共享历史。
# 在当前分支新增一条抵消目标提交改动的提交
成功后检查 git show HEAD 并运行相关测试:后续代码可能依赖被撤销的内容。冲突时编辑文件、git add -- file.txt 标记解决,再用 git revert --continue;放弃本次操作用 git revert --abort,这是另一条分支。目标若是 merge commit,必须先理解 -m 选择保留哪一侧父线及其对后续合并的影响,不能直接套用普通提交配方。revert 参考
整合分支:先选策略,再处理冲突
以下操作从干净的工作区与暂存区开始。origin/main 是本地的远程跟踪引用,需要先 fetch 才代表最近取得的主线。
# 更新本地远程跟踪引用,不修改当前分支的文件
| 选择 | 结果 | 适用情境 |
|---|---|---|
| merge | 保留已有提交身份;可能快进,也可能产生合并提交 | 把另一条分支历史整合进当前分支 |
| rebase | 把提交重放到新基底,通常产生新身份 | 整理尚未共享的功能分支 |
| cherry-pick | 应用选定提交的改动 | 只移植某项修复,并确认其依赖 |
保留分支历史:把 feature 合入 main
这是 main 接收 feature 的流程。先切换到接收方,再整合;不要仅凭命令中的分支名推断接收方。
# 切换到将接收改动的 main 分支
# 先让本地主线快进到已取得的远程主线;分叉则停止
若提示无法 fast-forward,先查 远程差异,确认本地主线为何有独立提交;不要直接加 force。成功后再合入功能分支:
# 将 feature 的历史整合进当前 main 分支
main 是 feature 的祖先时,默认可以快进,不一定新增合并提交;明确需要保留合并节点时,以 git merge --no-ff feature 代替。完成后用 git log --graph --oneline -10 核对形状,运行测试,再按项目协作流程推送。merge 参考
更新未共享的功能分支:rebase 到主线
这是与上面不同的选择:feature 接收新的主线基底。确认功能分支尚未共享;已共享历史的改写需要与使用者协调。
# 回到需要更新基底的功能分支
# 将当前功能分支的提交重放到已 fetch 的主线上
成功后检查 git log --graph --oneline -10 和测试结果。出现冲突会暂停在某一条提交,按下一节解决;不能把“没有报错”当成语义正确。rebase 参考
冲突暂停后:解决并继续,或放弃
# 确认暂停的是 merge、rebase 还是 cherry-pick,以及 Git 给出的下一步
# 列出仍未解决的路径,作为逐文件处理清单
--diff-filter=U 只选未合并路径,--name-only 只显示文件名。打开文件理解两边修改,编辑成最终想保留的内容;删除 <<<<<<<、=======、>>>>>>> 等冲突标记。不要把 ours / theirs 简单理解为“我的 / 别人的”,尤其在 rebase 中两侧语境会变。
# 将已经手工解决的文件标为已解决;逐个替换为实际路径
如果解决方案是删除文件,使用 git rm -- file.txt 代替 add。再次运行未解决路径查询,确认没有输出,审阅 git diff --cached,再按正在进行的操作只选一条:
| 正在进行的操作 | 解决后继续 | 决定放弃时,改用 |
|---|---|---|
| merge | git merge --continue | git merge --abort |
| rebase | git rebase --continue | git rebase --abort |
| cherry-pick | git cherry-pick --continue | git cherry-pick --abort |
rebase 可能在后面的提交再次暂停,需要重复检查和解决。--skip 会略过当前提交,不是保留修改后忽略错误。完成后 git status 不应再提示操作进行中,并需通过相关测试。abort 用于撤回本次整合,不负责备份操作开始前混入的未提交工作。
只移植一项修复
在需要接收修复的目标分支上执行。先用 git show COMMIT 审阅来源提交与依赖;下面针对普通的单父提交。
# 将选定修复应用到当前分支,并在提交说明中记录来源
-x 便于跨维护分支追溯来源。成功后检查 git show HEAD 并测试;若经过冲突解决,也检查提交说明是否保留了来源信息。冲突处理与放弃入口见上表。cherry-pick 参考
历史与恢复:从现象找到提交
看分支关系与单次改动
# 查看本地各引用可达的最近历史与分支位置
--graph 画连接线,--oneline 每提交一行,--decorate 标注引用名,--all 纳入所有本地 refs(包括远程跟踪 refs),-30 限制条数。它不会主动查询服务器。找到关心的哈希后,再展开那一条:
# 查看选定提交的说明和补丁,判断是否是要找的改动
按代码变化找提交,而不只搜提交说明
# 找出让指定字符串出现次数改变的提交,并展示补丁
-S 适合追踪一个符号何时加入或删除;同一提交中等量移入、移出可能不匹配。若要寻找补丁里匹配某个正则表达式的增删行,改用:
# 搜索补丁中匹配 timeout 赋值的变化行
-p 展示补丁;路径限定范围。两种查询默认沿当前 HEAD 的历史查找,需要其他分支时显式补充引用或 --all。
# 查看当前文件第 40–60 行各自由哪个提交最后修改
从输出中的哈希继续 git show,理解当时的修改原因。blame 标的是行的来源,格式调整也可能成为“最后修改”,不能直接当成故障责任结论。历史搜索选项
误 reset 或删分支后找回提交
# 查看本机 HEAD 的移动记录,寻找操作前的位置
示例片段:
a1b2c3d HEAD@{2026-09-10 12:05:00 +0800}: reset: moving to HEAD~1
b2c3d4e HEAD@{2026-09-10 12:00:00 +0800}: commit: Fix timeout handling
这里要检查的是操作前的 b2c3d4e;复制自己的真实哈希作为下面的 COMMIT。先确认补丁,再固定引用:
# 确认候选提交确实包含丢失的修改
# 创建恢复分支固定该提交,暂不改动当前工作区
recovery 必须是未使用的分支名。用 git log -1 --oneline recovery 验证后,可以在干净工作区 git switch recovery 查看完整状态,或通过 worktree 单独检出。确认要怎样整合,再决定 cherry-pick 或其他操作。
reflog 是本地记录,有过期与垃圾回收边界;其他机器上的移动不在这里,未到期也不等于所有内容一定可恢复。不要承诺固定保留天数,也不要把未提交编辑的恢复寄托于它。reflog 参考
用二分查找定位引入回归的提交
准备同一套可重复的检查,确认当前 HEAD 存在问题,并找到一个不出现问题的 KNOWN_GOOD。从干净工作区开始;bisect 会检出中间版本,构建产物也要避免干扰后续判断。
# 以当前有问题的提交为 bad、已知正常提交为 good,开始二分
Git 选出一个中间提交后,运行同一检查,按实际结果选一个标记:
| 本轮结果 | 命令 | 之后会发生什么 |
|---|---|---|
| 问题不出现 | git bisect good | 缩小范围并选择下一提交 |
| 问题仍出现 | git bisect bad | 缩小范围并选择下一提交 |
| 无法构建或无法判断 | git bisect skip | 跳过此提交;最终可能留下多个候选 |
重复到出现 is the first bad commit,记录哈希并用 git show 审阅。这个结果依赖检查稳定、正常/异常边界适合二分;问题间歇发生或多次引入又修复时需额外验证。结束或主动中止后恢复原位置:
# 退出二分状态,返回开始 bisect 前的位置
用 git status --short --branch 确认已回到原分支。若检查可脚本化,git bisect run /absolute/path/check-regression.sh 可替代手动标记:退出码 0 为 good,1–127 中除 125 外为 bad,125 为 skip。脚本宜放在仓库外,并在自动运行前验证退出码;构建失败不应误报为本次回归。bisect 参考
远程协作:比较进度,再决定如何推送
确认目标远程与双方独有的提交
# 确认 origin 的 fetch 与 push 地址是否指向预期仓库
# 取得远程更新,并清理远程已删除分支对应的跟踪引用
在普通分支 refspec 下,--prune 清理的是远程跟踪引用,不会删除你的本地工作分支。若配置了额外标签或镜像 refspec,先检查其范围。成功后,比较当前分支与要协作的 origin/feature:
# 列出当前分支与远程跟踪分支各自独有的提交
< a1b2c3d Fix timeout handling
> b2c3d4e Update server defaults
三点范围取双方独有的提交,< 属于左侧 HEAD,> 属于右侧 origin/feature。没有输出表示两端没有独有提交;双向都有则已分叉。若远程还没有 feature,这是首次推送场景,跳过不存在的远程分支比较。
明确推送源与目标,并建立上游
以下把当前 HEAD 推到远程 feature;先用 git status --short --branch 确认当前就在要发布的本地 feature 分支,并完成审阅和测试。
# 预览将更新的远程分支,不执行引用更新
冒号左边是源,右边是远程目标;refs/heads/ 明确表示分支。确认目标正确后再执行实际推送:
# 推送当前分支,并将 origin/feature 设置为其上游
-u 用于设置跟踪关系,不是强制更新。成功后用 git branch -vv 核对上游,用 git ls-remote --heads origin refs/heads/feature 查看服务器当前哈希,并与 git rev-parse HEAD 对照;服务器之后仍可能继续变化。dry-run 不锁定远程,也不能证明所有服务端检查都会通过。
被拒绝时,区分正常分叉与有意改写
non-fast-forward 通常说明远程包含这次推送没有保留的历史。重新 fetch,检查双方独有的提交,再选择 merge / rebase 整合;权限、保护分支或 hook 拒绝则按服务端消息处理,force 不能解决这些原因。
只有已经协调好、明确要发布改写历史时,才进入下面的独立流程。普通 --force-with-lease 依赖远程跟踪引用,后台 fetch 可能改变比较基准。可以把审阅过的预期提交固定到变量中:
# 先取得准备覆盖的远程分支状态;失败则停止
# 在这个 shell 中记录预期远程提交,失败或为空则停止
expected_oid=
# 审阅将被替代的历史,确认双方变更都已妥善处理
这个检查点需要实际审阅,不能因为 fetch 成功就认为可以覆盖。确认后,在同一个 shell 中执行:
# 仅当服务器 feature 仍指向已审阅的预期提交时,发布改写结果
${expected_oid:?...} 会在变量未设置或为空时拒绝展开。若 lease 检查失败,说明预期已不成立:重新检查新增远程工作,不要只刷新变量后重试。成功后的验证与普通推送相同。push 与显式 lease
工作树、子模块与标签:不同对象分别操作
保留当前编辑,在另一个目录修复问题
worktree 适合临时修复或二分检查:共用仓库对象与大多数引用,但各有工作目录、HEAD 和暂存区。下面从已经 fetch 的 origin/main 创建新的 hotfix 分支与同级目录 ../hotfix;名称和目录都要尚未使用。
# 为 hotfix 新建独立工作目录,当前编辑保持在原目录
-b 创建分支,路径参数指定工作目录,最后一项指定起点。Git 默认不允许同一分支同时在两个 worktree 检出。用下面的配套检查确认目录与分支:
# 核对所有工作目录及其检出的分支
# 从原目录检查 hotfix 的独立状态
-C 让 Git 在指定目录执行;实际编辑和测试在 ../hotfix 内进行。修复提交并按项目流程整合后,用 git -C ../hotfix status --short --ignored 检查剩余文件;被忽略的 .env 等文件也会随目录删除,需要的内容先移出。再从原目录清理:
# 删除已完成任务的工作目录及登记信息,保留 hotfix 分支
用 git worktree list 确认移除。工作树不干净时拒绝是检查点,先保存内容,不要直接加 --force。移除目录不等于删除分支;确认分支已整合且无需保留后,可单独 git branch -d hotfix。若因未合并被拒绝,先核实整合方式(例如 squash)和目标内容。worktree 参考
取得父仓库记录的子模块版本
以下在含 .gitmodules 的父仓库根目录执行。先查看记录与实际检出状态:
# 查看子模块及嵌套子模块的提交和初始化状态
行首 - 表示未初始化,+ 表示检出提交与父仓库暂存区记录不一致,U 表示冲突;空格表示提交匹配,不证明子模块工作区没有修改。更新前在相关子模块中用 git -C path/to/submodule status --short 检查并保存自己的工作。
# 初始化缺失子模块,并按父仓库记录检出指定提交
--init 完成初始化,--recursive 处理嵌套子模块,--checkout 明确使用检出模式,通常处于 detached HEAD。再次运行 status 查询,应不再有未初始化或提交不匹配的前缀;如有冲突或本地修改阻止更新,先处理原因。
升级依赖是另一项任务:--remote 会依据子模块的远程跟踪分支选版本,而不是复现父仓库记录的版本。升级后要检查子模块内容、测试父项目,并在父仓库提交新的子模块引用;不要把它当成初始化缺失依赖的开关。submodule 参考
为已核对的提交创建并发布标签
示例标签为尚未使用的 v1.0.0。先用 git show --stat HEAD 确认发布提交,用 git status --short 确认需要发布的内容已提交;标签指向提交,不会包含工作区未提交的修改。
# 在明确的 HEAD 提交上创建带说明的本地标签
-a 创建包含说明与标记者信息的标签对象。先用 git show v1.0.0 核对目标与说明,再预览只推送这一标签:
# 预览此标签的远程目标,不批量推送其他本地标签
确认发布对象后实际执行:
# 将已核对的单个标签发布到 origin
# 取得本地带注释标签对象的哈希
# 查询服务器上的同一标签对象,核对哈希
这里对比的是标签对象哈希,不是它指向的提交哈希。若远程同名标签已存在且不同,先查发布记录;不要直接强制覆盖已经被使用的发布标签。标签参考
继续查阅
需要查完整参数时,在终端运行 git help 子命令,例如 git help restore。官方手册适合确认选项语义;本页的任务流程用于把检查、选择、执行与验证串起来。