Git 完整指南
Git 保存的不是一串“文件修改记录”,而是一组可寻址的对象和指向这些对象的引用。日常使用中的大部分困惑,都能还原成几个具体问题:某次修改现在只在工作区,还是已经进入暂存区?分支名指向哪个 Commit?远端跟踪引用是不是刚刚更新过?准备执行的命令会复制内容、移动引用,还是创建一个新的反向提交?
把这些问题弄清以后,add、commit、merge、rebase、reset 不再是一组靠记忆区分的咒语。初学者可以安全完成第一次提交,日常开发能判断分支和冲突,团队负责人也能解释为什么某些操作只允许出现在个人分支,而另一些规则必须放到服务器端。
安装与配置:先确认你实际执行的是哪个 Git
Git 的安装并不复杂,麻烦往往来自同一台机器上存在多个入口。Windows 终端、Git Bash、WSL、IDE 和图形客户端可能调用不同的可执行文件;升级了其中一个,不代表其他入口一起更新。开始配置前先保存一份只读快照:
git --version
git --exec-path
git config --list --show-origin --show-scopeWindows 再执行:
where.exe git
Get-Command git -All | Format-Table CommandType, Source, VersionmacOS 和 Linux 可以使用:
type -a git
command -v git版本号回答“这个客户端具备哪些能力”,路径回答“究竟调用了哪一份安装”,配置来源则回答“某个行为是谁改的”。三者缺一,重装很容易只是把问题换了位置。
Git 官方安装入口维护各操作系统的发行方式。Windows 企业设备优先采用组织批准的软件中心或包管理源;允许使用 WinGet 时,可以先查看包信息,再安装明确的软件包 ID:
winget show --id Git.Git -e --source winget
winget install --id Git.Git -e --source wingetmacOS 可以使用系统提供的 Command Line Tools,也可以由 Homebrew 管理。决定使用 Homebrew 后仍要检查 PATH 顺序,因为安装成功不等于终端已经选中它:
brew info git
brew install git
type -a git
git --versionLinux 应优先使用发行版仓库或组织维护的软件源。下面的命令分别适用于 Debian/Ubuntu 系和 Fedora 系,真实环境只执行与发行版相符的一组:
sudo apt update
sudo apt install git
sudo dnf install git源码构建适合需要特定补丁、受控编译参数或发行版尚未提供目标能力的场景。它同时把依赖补丁、安全更新、卸载和多版本切换责任留给团队,不能只因为“版本更新”就默认走源码安装。
Git 配置有 system、global、local 和 worktree 等作用域。读取时会按来源叠加,写入时若不说明作用域,通常落到当前仓库。官方 git-config 手册给出了完整作用域和读写语义;实际排查应优先查看最终值及其来源,而不是直接打开某一个猜测中的配置文件。
个人机器最小配置可以从身份、默认分支和拉取策略开始:
git config --global user.name "Your Name"
git config --global user.email "you@example.invalid"
git config --global init.defaultBranch main
git config --global pull.ff onlypull.ff=only 不会替你处理已经分叉的历史。它会在无法 fast-forward 时停下,让人选择 merge 或 rebase。对刚开始建立团队规范的仓库,这种显式失败通常比让每台机器采用不同默认行为更容易排查。
工作账号和个人账号不能靠“提交前记得检查”隔离。可以按目录条件加载不同配置:
# ~/.gitconfig
[includeIf "gitdir:~/work/"]
path = ~/.gitconfig-work
[includeIf "gitdir:~/personal/"]
path = ~/.gitconfig-personal对应文件分别保存 user.name、user.email 和需要的签名设置。进入仓库后用下面的命令确认最终身份:
git config --show-origin --get user.name
git config --show-origin --get user.email
git var GIT_AUTHOR_IDENT
git var GIT_COMMITTER_IDENT换行策略必须与 .gitattributes、编辑器和 CI 一起治理。不要在业务变更中顺手切换全局 core.autocrlf,否则一个看似很小的配置修改可能产生全仓行尾差异。仓库需要统一文本语义时,应把规则提交到 .gitattributes,再用单独变更完成重新规范化和审查。
safe.directory 处理的是仓库所有权信任,不是通用权限修复。遇到 dubious ownership 时先核对目录归属、挂载方式、容器用户和 CI 工作区,不要把 * 写入全局安全目录绕过所有检查。
四个本地层次:工作区、暂存区、对象库和引用
Git 的日常界面可以先简化为四层。工作区是磁盘上正在编辑的文件;暂存区也叫 index,保存下一次 Commit 准备采用的快照;对象库位于 .git/objects,保存 blob、tree、commit 和 annotated tag 等对象;引用位于 refs/*,分支和标签用名字指向对象,HEAD 再说明当前检出位置。
Blob 保存文件内容,不保存原始文件名;tree 把名称、模式和对象 ID 组织成目录快照;commit 指向一个 tree,并记录父 Commit、作者、提交者和说明。分支只是一个会移动的引用。Git 官方的 Git Objects 对这些对象有完整演示。
用一个可删除的实验仓库观察状态变化:
mkdir git-object-lab
cd git-object-lab
git init
git switch -c main
printf "alpha\n" > app.txt
git status --short
git add app.txt
git status --short
git commit -m "初始化实验文件"第一次 status 中的 ?? 表示文件尚未被 index 跟踪;add 之后它进入暂存区;commit 再把 index 写成 tree 和 commit,并推进当前分支。可以继续检查对象关系:
git show --no-patch --format='commit=%H%nparents=%P%ntree=%T' HEAD
git ls-tree HEAD
git cat-file -p HEAD
git cat-file -p HEAD^{tree}这组命令比“Git 保存了一个版本”更准确。Commit ID 不是给文件内容单独计算的编号,而是对象内容的一部分结果;父节点、作者信息或提交说明变化,都会得到新的 Commit。
暂存区不是“待上传文件夹”。git add 会把当时的内容放进 index;之后继续编辑同一文件,工作区和 index 会出现两个版本。此时:
git diff
git diff --staged前者比较工作区与 index,后者比较 index 与 HEAD。提交前同时看两份差异,可以发现“文件已经 add,后来又改了一半”的常见遗漏。
第一次完整工作流:从修改到远端
已有项目通常从 clone 开始,新项目从 init 开始:
git clone <remote-url> project
cd project
# 或在已有目录初始化
git init
git switch -c main进入仓库后,先确认根目录、当前分支和远端,不要在嵌套仓库或错误工作树里直接提交:
git rev-parse --show-toplevel
git status --short --branch
git remote -v
git branch -vv一次普通修改的节奏是查看状态、审阅未暂存差异、选择要进入本次提交的内容、再次审阅暂存差异,然后提交:
git status --short
git diff
git add -p
git diff --staged
git commitgit add -p 允许按 patch hunk 选择内容,适合把混在一个工作区里的独立改动拆开。它不是修复混乱工作的万能工具;若两个功能在同一行交织,先整理代码和测试,再决定提交边界。
提交说明首先服务审查和恢复。标题描述这次变化完成了什么,正文解释为什么这样改、有哪些兼容或回滚信息。提交规范可以由团队约定,但不能为了通过格式检查写出信息量更低的句子。
远端协作时,fetch 只获取对象并更新远端跟踪引用;pull 会在 fetch 之后继续整合当前分支。Git 官方教程也明确把 pull 描述为获取再整合。对重要仓库,先 fetch、检查图,再决定 merge 或 rebase,通常更可控:
git fetch origin --prune
git log --graph --oneline --decorate --boundary HEAD...origin/main
git diff --stat HEAD...origin/main创建主题分支并首次推送:
git switch -c feature/example
git push -u origin feature/example-u 建立 upstream,之后 status、无参数 push 和 ahead/behind 判断才有明确比较对象。推送成功只证明远端接受了对象和引用更新,不等于变更已经评审、检查或进入主分支;这些约束属于代码托管平台和仓库规则。
HTTPS 凭证:让 helper 管理秘密,不把 Token 写进 URL
HTTPS 认证通常经过 Git 的 credential 子系统和 credential helper。Git 向 helper 提供协议、主机等上下文,helper 再从系统凭证库或 OAuth 流程取得凭证。官方 credential helper 列表说明,store 会把凭证明文写入磁盘,cache 只在进程内临时保存;Windows 常见的 Git Credential Manager、macOS Keychain 和 Linux Secret Service 才适合承担桌面凭证存储。
先查看实际 helper 链:
git config --show-origin --get-all credential.helper
git config --show-origin --get credential.useHttpPath同一主机使用多个账号时,凭证是否需要包含 URL path 取决于平台和仓库布局。盲目全局开启 credential.useHttpPath 可能让每个仓库都重复登录,完全忽略 path 又可能串用账号。应在测试仓库中验证读取、写入、轮换和删除,而不是只确认浏览器登录成功。
远端地址保持无秘密形式:
git remote set-url origin https://example.com/owner/repository.git
git remote -v不要使用 https://user:token@example.com/...。这种 URL 会进入 shell 历史、进程参数、诊断输出、CI 日志和仓库配置。需要清除错误凭证时,优先使用平台或 helper 提供的删除入口;随后再次访问,预期重新认证。只删除本地记录但不撤销泄漏 Token,不能算完成处置。
401 通常先检查凭证是否失效或命中了错误账号;403 还要继续区分仓库权限、分支保护和组织策略;407 指向代理认证;浏览器能打开平台而 Git TLS 失败,则应检查 Git 使用的代理、CA 和 TLS 后端。不要把所有认证问题都归结为“Token 不对”。
SSH:客户端身份和服务器身份是两条链
SSH 访问同时验证两件事:客户端拿哪把私钥证明自己,客户端又凭什么相信当前服务器。私钥、agent 和 IdentityFile 处理前者,known_hosts 与可信指纹渠道处理后者。
为工作账号生成独立密钥:
ssh-keygen -t ed25519 -C "work-git" -f ~/.ssh/id_ed25519_work
ssh-add ~/.ssh/id_ed25519_work
ssh-add -l公钥内容可以提交给代码托管平台,私钥不能上传、提交或复制到团队共享目录。需要同一平台多账号时,使用 Host 别名表达身份路由:
Host git-work
HostName git.example.com
User git
IdentityFile ~/.ssh/id_ed25519_work
IdentitiesOnly yes
Host git-personal
HostName git.example.com
User git
IdentityFile ~/.ssh/id_ed25519_personal
IdentitiesOnly yes仓库远端必须使用对应别名:
git remote set-url origin git@git-work:team/repository.git
ssh -G git-work | sed -n '/^hostname /p;/^user /p;/^identityfile /p'
ssh -T git@git-workssh -G 展开最终配置,适合判断 Include、Host 匹配和 IdentityFile 是否生效。Too many authentication failures 往往是 agent 提供了过多密钥;IdentitiesOnly yes 可以收窄尝试集合。Permission denied (publickey) 还要区分密钥没有加载、公钥未登记、命中了错误 Host、账号没有仓库权限,以及服务端不接受当前算法。
出现 REMOTE HOST IDENTIFICATION HAS CHANGED 时先暂停。主机迁移、密钥轮换和中间人攻击都可能产生相同表象。删除旧 known_hosts 记录只会移除旧信任,不会证明新指纹可信;新指纹必须从平台公告、管理员或另一条独立可信渠道核对。
分支、Merge、Rebase 与 Cherry-pick
分支是指向 Commit 的可移动引用。git switch -c feature/a 创建引用并让 HEAD 指向它;后续 commit 推进该引用。分支便宜,是因为它没有复制整份代码。
git switch main
git fetch origin --prune
git merge --ff-only origin/main
git switch -c feature/exampleMerge、Rebase 和 Cherry-pick 都能把变化带到另一条线上,但对象关系不同:
| 操作 | 对提交图的影响 | 适合的主要场景 | 主要代价 |
|---|---|---|---|
| Merge | 保留原 Commit,并在分叉时创建双亲 merge commit | 已共享分支、需要保留拓扑 | 主线图更复杂 |
| Rebase | 在新父节点上重新创建一组 Commit | 个人主题分支整理基线 | Commit ID、签名和旧审查坐标改变 |
| Cherry-pick | 选择提交的补丁,在目标线创建新 Commit | 将独立修复带入维护线 | 容易形成重复补丁和长期分叉 |
| Squash merge | 平台把一次变更压成新的单 Commit | 主线强调 PR 粒度 | 分支内部提交身份和拓扑不进入主线 |
Merge 的官方语义见 git-merge,Rebase 的提交重放与恢复选项见 git-rebase。不要用“线性历史更高级”代替选择依据。需要保留原签名对象、并行上下文或审计拓扑时,merge 有明确价值;希望个人分支在进入评审前去掉无意义的修正提交时,rebase 更合适。
个人分支更新到最新主线:
git fetch origin
git rebase origin/main
git range-diff ORIG_HEAD...HEADrange-diff 用于比较两组提交序列,能帮助审查 rebase 前后的补丁是否发生意外变化。分支已经被别人作为上游后,不要直接改写;先协调冻结窗口,记录旧尖端,并明确所有下游如何恢复。
确需更新已经推送的个人主题分支时,先 fetch,再使用租约保护:
git fetch origin
git push --force-with-lease origin HEAD:feature/example租约保护的前提是你观察到的远端状态仍然可信。后台工具自动 fetch 可能改变远端跟踪引用;高风险操作可以使用包含预期 Commit 的精确 lease。受保护主分支不应依赖个人自觉避免强推,应由服务端规则直接拒绝。
Cherry-pick 适合把一个已经审查的独立修复带入维护分支:
git switch maintenance
git cherry-pick -x <source-commit>-x 在提交说明中记录来源,便于追踪。它没有保留原 Commit 身份;目标线产生的是新对象。重复 cherry-pick 可能得到空提交或冲突,应先检查目标线是否已经包含等价补丁。
冲突:不要只搜索 <<<<<<<
冲突发生时,工作区里的标记只是可见部分,index 还保存共同基线、当前侧和另一侧的条目。可以这样观察:
git status
git ls-files -u
git diff --name-only --diff-filter=U
git show :1:path/to/file
git show :2:path/to/file
git show :3:path/to/fileStage 1 是 merge base,Stage 2 和 Stage 3 分别代表当前操作语境中的两侧。Rebase 会逐个重放提交,ours 与 theirs 的直觉很容易和普通 merge 相反,因此不要仅凭词义选择整文件。先看正在执行的操作和提交:
git status
git branch --show-current
git rev-parse --verify MERGE_HEAD
git rebase --show-current-patch解决过程应围绕业务语义:读共同基线,理解双方各自改变了什么,编辑结果,运行针对性测试,再把结果加入 index。Git 只知道文本和对象关系,不知道某一侧删除校验是重构还是漏洞。
git add path/to/file
git diff --check
git diff --staged
git status
git merge --continue不同操作对应不同继续或中止命令。Merge 使用 git merge --continue 或 git merge --abort;Rebase 使用 git rebase --continue、--skip、--abort;Cherry-pick 和 Revert 也有自己的 --continue、--abort、--skip。不要在不清楚当前序列器状态时混用命令。
rerere 可以记录冲突前后的形状,在相似冲突再次出现时复用解决结果:
git config --local rerere.enabled true
git rerere status
git rerere diff它适合长期维护分支、反复 rebase 或批量回合并,也会稳定复用一次错误决定。团队启用后仍要检查 diff 和测试,发现记录错误时用 git rerere forget <path> 撤销该路径的记忆。
撤销与恢复:先判断要改变哪一层
“回到之前”可能指覆盖工作区、调整暂存区、移动本地分支、在公共历史追加反向提交,或者找回已经失去引用的对象。命令选择应先看共享范围:
| 现场 | 优先入口 | 变化发生在哪里 |
|---|---|---|
| 放弃未暂存编辑 | git restore <path> | 工作区 |
| 取消暂存但保留编辑 | git restore --staged <path> | index |
| 修改尚未共享的最近提交 | git commit --amend 或受控 reset | 本地引用与新 Commit |
| 撤销已共享提交 | git revert <commit> | 公共历史新增反向 Commit |
| Reset/Rebase 后提交不见 | git reflog 后创建救援分支 | 找回本地引用旧位置 |
| 撤销 merge commit | 先确认 mainline,再 git revert -m | 公共历史与未来合并语义 |
执行任何可能覆盖内容或移动引用的命令前,先保存现场:
git status --short --branch
git diff
git diff --staged
git rev-parse HEAD
git log --graph --oneline --decorate -12
git reflog -12未提交内容没有进入对象库,reflog 也救不回来。重要工作区先复制到受控位置,或在救援分支创建清楚标记的临时提交。stash 可以临时收起工作,但它仍需命名、检查和清理,不应成为长期无主备份。
restore 处理路径内容,reset 可以移动当前分支并按模式调整 index、工作区,revert 则创建新 Commit。公共历史默认使用 revert,因为其他协作者看到的是可追踪的新动作,而不是远端尖端突然倒退。
误 reset 或 rebase 后,立即从 reflog 找到旧位置并创建引用:
git reflog --date=iso
git branch rescue/before-rewrite HEAD@{1}
git show rescue/before-rewrite官方 git-reflog 说明 reflog 记录的是本地引用更新。它不是远端备份,也不是永久保留承诺;gc、expire、仓库重新克隆和本地清理都会改变恢复窗口。找回对象后先建立分支,再继续整理,避免后续命令让对象再次失去引用。
回滚 merge commit 时,-m 选择的是 mainline 父节点。选错父节点会反向错误的一侧,并改变未来再次合并这条分支的结果。先检查父节点和树差异:
git show --no-patch --format='%H%nparents: %P' <merge-commit>
git diff <merge-commit>^1 <merge-commit>
git diff <merge-commit>^2 <merge-commit>Git 回滚只改变仓库中的树。已经发布的制品、数据库写入、消息、云资源和泄漏凭证不会自动恢复。服务恢复、数据补偿、凭证撤销和代码历史处理应分开执行,再用共同事件记录关联。
身份、签名与“Verified”标记
Commit 中的 author、committer 和邮箱是元数据,不是强身份认证。HTTPS 或 SSH 登录证明当前请求持有某种访问凭证;Commit/Tag 签名证明某个私钥对对象内容做过签名;平台上的 Verified 标记还依赖平台如何绑定公钥、邮箱、账号和信任状态。三者不能互相替代。
Git 支持 OpenPGP、SSH 和 S/MIME 等签名格式。团队已有 PKI 和密钥生命周期时应沿用受控体系;开发者团队希望降低本地门槛时,SSH 签名通常更容易接入。下面展示 SSH 格式的最小本地配置:
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519_work.pub
git config --global commit.gpgsign true
git commit -S -m "验证签名提交"
git log --show-signature -1本地验证 SSH 签名还需要受控的 allowed signers 文件,把身份和公钥绑定起来:
developer@example.invalid namespaces="git" ssh-ed25519 AAAA...git config --global gpg.ssh.allowedSignersFile ~/.config/git/allowed_signers
git verify-commit HEADallowed signers 文件本身就是信任源,需要版本、审查、撤销和人员生命周期管理。把任何出现过的公钥都加入其中,只是把验证变成“签名数学上成立”,没有证明签名者仍被组织授权。
Annotated Tag 可以带说明和签名,适合表达发布或重要里程碑:
git tag -s vX.Y.Z -m "release vX.Y.Z" <commit>
git verify-tag vX.Y.ZRebase、Cherry-pick 和 Squash 会创建新 Commit,旧签名不会迁移。机器人签名也不等于流水线可信;还要验证运行身份、工作流来源、受保护环境和制品证明。若团队把签名作为强门禁,应先演练密钥过期、撤销、轮换、离职回收和应急发布,避免门禁失效时只能永久开放管理员绕过。
大文件、外部仓库和大工作区
Git LFS、Submodule、Worktree、Sparse Checkout 与 Partial Clone 都会改变仓库体验,但它们解决的层次不同。
| 能力 | 改变的对象 | 主要用途 | 不能解决什么 |
|---|---|---|---|
| Git LFS | Git 保存 pointer,内容进入独立 LFS 存储 | 大型二进制内容 | 不能自动清理已经进入普通历史的大文件 |
| Submodule | 父仓库保存指向子仓库 Commit 的 gitlink | 独立仓库版本组合 | 不能自动解决递归权限和对象可达性 |
| Worktree | 同一对象库附加多个工作区和 index | 并行处理多个分支 | 不能隔离共享对象库故障 |
| Sparse Checkout | 收窄工作树呈现的 tracked paths | 降低遍历和 IDE 索引范围 | 不是权限边界,也不必然减少下载量 |
| Partial Clone | 延迟下载部分可达对象 | 降低首次对象传输 | 增加后续命令对网络和远端的依赖 |
Git LFS
Git LFS把大文件内容保存到独立 LFS 服务,普通 Git 历史中保存包含 OID 和大小的 pointer。启用后先声明仓库策略,再提交 .gitattributes:
git lfs install
git lfs track "*.psd"
git add .gitattributes
git add design.psd
git commit -m "使用 LFS 管理设计源文件"
git lfs ls-files提交 pointer 不等于 LFS 对象已经交付。推送、CI 拉取、镜像仓库、备份和迁移都要验证 LFS endpoint、权限、配额和对象完整性。git lfs fsck 可以检查本地对象;克隆后只看到三行 pointer 时,应检查 LFS 客户端、smudge 过程和对象权限,而不是把 pointer 当作文件损坏。
已有大文件进入普通历史后,新增 track 只影响后续提交。历史迁移会重写 Commit ID,必须协调冻结、重建引用、重新克隆、签名与审查证据。大文件是否应该进入版本控制也要先判断;构建制品、缓存和可再生依赖通常更适合制品库。
Submodule
Submodule 让父仓库用 mode 160000 的 gitlink 固定子仓库 Commit。它保存的是精确对象位置,不是“始终取子仓库最新版”:
git submodule add <child-url> libs/child
git commit -m "引入 child 子仓库"
git ls-tree HEAD libs/child克隆时显式递归初始化:
git clone --recurse-submodules <remote-url>
git submodule status --recursive
git submodule update --init --recursive官方 gitsubmodules说明了 gitlink、.gitmodules 和子模块配置的关系。父仓库更新指针前,目标 Commit 必须已经推到子仓库且所有消费环境可访问。父仓库 clone 成功、子目录为空,通常只是没有初始化;指针前出现 +,说明工作区子仓库与父仓库记录的 Commit 不同;权限错误与对象不存在则要分别验证。
Submodule 适合需要独立权限、独立发布和精确组合的仓库。若团队真正需要原子修改、统一搜索和单次评审,强行用 Submodule 维持物理分仓会增加协调成本。
Worktree、Sparse Checkout 与 Partial Clone
Worktree 适合在保留当前工作区的同时处理热修或评审:
git worktree add -b hotfix/example ../hotfix-example origin/main
git worktree list --porcelain每个 worktree 有自己的 HEAD、index 和工作区,默认共享对象库和多数 refs。同一分支通常不能同时检出到两个 worktree。构建输出、端口、依赖目录和本地数据库仍需按工作树隔离;删除目录后要用 git worktree prune 清理管理记录,不能直接把 .git/worktrees 当普通缓存删除。
Sparse Checkout 收窄当前工作树可见路径,优先使用 cone mode:
git sparse-checkout set --cone services/payment shared
git sparse-checkout list
git sparse-checkout disable文件不在工作区,不代表没有访问权限,也不代表对象没有下载。git-sparse-checkout记录了它与其他命令的交互边界。构建、测试、IDE 和安全扫描都要验证在稀疏范围下是否仍能看到真实依赖。
Partial Clone 通过 filter 延迟部分对象传输:
git clone --filter=blob:none <remote-url> project
git -C project config --get remote.origin.promisor
git -C project rev-list --objects --all --missing=printblob:none 保留 Commit 和 tree,文件内容在需要时补取。官方 Partial Clone 设计把提供缺失对象的远端称为 promisor remote。首次 clone 变快的代价是某些 show、blame、checkout 或构建在之后触发网络;代理不稳定、凭证过期、远端清理和离线开发都要进入验证。
团队规则:本地反馈快,服务器端才负责不可绕过
Git Hook、commitlint 和 IDE 检查适合尽早反馈,但开发者可以跳过本地 Hook,网页编辑、机器人和其他客户端也未必安装它。真正要求所有进入关键分支的提交都满足某项条件,必须在 CI、接收端 Hook 或托管平台规则中重新验证。
提交消息规范、DCO、密码学签名、代码评审和自动检查是不同约束。消息规范改善历史可读性;DCO 表达贡献来源声明;签名绑定对象和密钥;评审证明某个 head SHA 被看过;自动检查证明特定代码和环境执行过测试。把它们压成一个“绿色徽标”,事故时就无法判断缺的是哪种证据。
本地仓库可以提供快速反馈:
git config core.hooksPath .githooks若仓库采用版本化 Hook,脚本需要同时处理跨平台运行、依赖版本和失败信息。最终门禁仍应检查服务端实际收到的完整提交集合,不能只看最后一个 Commit,也不能让检查名称漂移后规则永远等待或错误放行。
分支策略不需要追求某一种漂亮提交图。短生命周期产品团队常用主干加短主题分支;需要维护多个已发布版本的项目会保留维护线;强合规环境可能要求 merge commit、签名和不可变审查对象。选择时要看回滚粒度、并行开发、发布频率、审计要求和团队处理冲突的能力。
托管平台负责 PR/MR、保护分支、CODEOWNERS、状态检查、角色和绕过审计。跨平台方法见 Pull Request、Merge Request、Review 与 CODEOWNERS;具体产品分别见 GitHub、GitLab、Gitee、Gitea 和 Bitbucket Cloud。Git 本身并不知道某个网页审批是否有效,也不会自动阻止管理员绕过平台规则。
凭证和自动化身份应有 owner、最小权限、轮换、撤销和退出路径。机器人不使用个人 PAT;离职回收不能只删除平台成员,还要检查 deploy key、SSH 证书、GitHub App、Project Access Token、CI Secret 和镜像任务。诊断日志在分享前删除仓库 URL、用户名、Token、代理地址和内部路径。
仓库升级或迁移时,Git 对象只是资产的一部分。分支、Tag、LFS 对象、Submodule 可达性、PR/MR 讨论、检查记录、发布关联、规则、团队权限和审计日志需要分别迁移或留档。退出旧平台前先冻结写入口,验证新远端对象数量和关键引用,再轮换凭证;只执行一次 mirror push 不能证明协作系统已经迁完。
排障时先读状态,不要先搜索一条“修复命令”
Git 报错通常已经指出发生在哪一层。先保留原始错误和 status,再按对象、引用、网络或权限分型:
| 现象 | 第一组证据 | 常见方向 |
|---|---|---|
| 终端和 IDE 行为不同 | git --version、执行路径、配置来源 | 多版本或配置作用域不同 |
nothing to commit 但文件明明改了 | git status --ignored、git check-ignore -v | 位于仓库外、被忽略、稀疏范围或 Submodule 内 |
| Pull 拒绝 fast-forward | git log --graph HEAD...@{upstream} | 本地与远端已经分叉,需要明确 merge/rebase |
| Push 被拒绝 non-fast-forward | git fetch 后比较远端跟踪引用 | 远端已有新提交或本地改写历史 |
| 401/403 | helper、账号、仓库权限、分支规则 | 凭证失效、账号错误或授权不足 |
| SSH publickey 失败 | ssh -G、ssh-add -l、ssh -vT | 身份选择、agent、公钥登记或仓库权限 |
| Rebase/Cherry-pick 停住 | git status、当前 patch、unmerged index | 冲突、空补丁或序列器状态 |
| Reset 后提交不见 | git reflog、git fsck | 引用移动,对象可能仍可恢复 |
| clone 很慢 | trace、对象统计、LFS/Submodule、网络 | 历史大文件、递归依赖、代理或服务端能力 |
| 签名本地有效而平台未验证 | 本地 verify、平台公钥与邮箱绑定 | 信任源和身份映射不同 |
需要采集 Git 自身诊断时,可以短时启用 Trace2,并把输出放到受控临时目录:
GIT_TRACE2_EVENT=<temporary-path> git fetch originTrace 可能包含路径、远端、进程参数和性能信息。采集后先脱敏再分享,问题结束后清理。不要把 GIT_CURL_VERBOSE 的完整输出直接贴到公开工单;认证头、代理和内部地址都可能泄漏。
故障恢复的停止条件不是“命令退出码为 0”,而是引用指向预期 Commit、工作区和 index 状态可解释、远端对象可达、CI 与评审绑定当前 SHA、旧凭证或旧入口已经失效。Git 的优势在于多数状态都可以被观察;不先观察就执行破坏性命令,等于主动丢掉它提供的证据。
什么时候不该继续给 Git 加能力
Git 适合保存可审查的文本和适量二进制版本,不是制品仓库、数据库、备份系统或权限隔离系统。构建产物放制品库,运行数据进入数据库和备份体系,真正需要保密隔离的代码拆到服务端独立授权仓库。Sparse Checkout 和分支命名都不能替代访问控制。
团队也不必启用所有高级能力。仓库规模正常时,Partial Clone 会增加隐含网络依赖;没有独立发布边界时,Submodule 只会增加递归管理;提交签名没有密钥生命周期和验证规则时,只会产生真假难辨的徽标;复杂分支模型没有明确发布需求时,会把协调成本转嫁给每次合并。
一套稳健的 Git 基线并不花哨:安装来源可追踪,身份不会串用,提交前能读懂 index,公共历史不随意改写,冲突有测试,误操作能从 reflog 建立救援引用,平台门禁绑定当前 Commit,凭证和机器人可以撤销。做到这些以后,再根据真实瓶颈引入 LFS、Worktree、Sparse Checkout、Partial Clone 或更强的签名与审计规则。
