1. 这些技巧不是“锦上添花”,而是日常 Git 生产力的底层支点
你有没有过这样的时刻:刚合完一个分支,一转头发现主干上少了一次关键提交,但git log里已经翻不到它了?或者在调试时临时改了三四个配置文件,既不想提交,又怕git checkout -- .误删自己真正想保留的改动?又或者,每天敲git status -s、git diff --no-index、git log --oneline -n 20这些命令,手指都快形成肌肉记忆了,却从没想过——为什么非得打这么长?
这不是“高级玩法”,而是我带过的十几个团队、参与过的二十多个中大型协作项目里,最常被忽视、却最直接影响开发节奏的六类高频操作场景。它们不涉及 rebase 的哲学思辨,也不挑战 merge 策略的顶层设计,但每一条都卡在“写完代码 → 安全提交 → 快速验证 → 灵活回溯”这个最短路径上。标题里列出的六项:引用日志(reflog)、忽略临时性本地修改、检查文本文件修改、命令别名、为临时提交建分支、移动提交到另一分支——它们共同指向一个事实:Git 的默认交互层,是为“严谨版本控制”设计的,不是为“人类日常手速”优化的。
我见过太多开发者把git stash当万能胶水,结果 stash list 堆到 27 条,git stash pop stash@{23}时冲突报错才想起自己三个月前藏了个数据库密码;也见过同事为比对两个未跟踪的 JSON 配置文件,硬生生先git add再git diff最后git reset,全程屏住呼吸生怕一步错就污染暂存区。这些都不是“不会用 Git”,而是没摸清 Git 在“已知状态”和“未知状态”之间那条模糊边界的处理逻辑。而 reflog 就是那条边界上的探照灯,git update-index --skip-worktree是给文件贴的“勿动”便签,git diff --no-index是专治“非 Git 管理文件”的手术刀。它们不改变 Git 的核心模型,只是把模型里早已存在的能力,拧开盖子,递到你手边。
这六项技巧,我按使用频率和救急价值重新排了序:reflog 是你的“后悔药记录仪”,排第一;忽略临时修改和检查文本修改是每日高频的“状态净化”动作,排二、三;别名是长期复利型投资,排四;临时分支和移动提交则是“突发状况响应包”,排五、六。接下来每一节,我都不会只告诉你命令怎么敲,而是先说清:这个命令在 Git 的对象模型里动了哪根神经,为什么不用它你就得绕三公里,以及我在某跨平台系统重构项目里,因为漏掉其中一步,多花了 47 分钟定位一个本该 30 秒解决的环境差异问题。
2. reflog:不是日志,是 Git 操作的“行车记录仪”,且永不覆盖
很多人把git reflog当成git log的备胎,觉得“不就是多显示几行 hash 吗”。这是最大的误解。git log展示的是分支指针的线性历史——它讲的是“这个分支上发生过什么”。而git reflog记录的是HEAD 和各分支引用自身的移动轨迹——它讲的是“这个指针自己走过哪些地方”。前者是故事书,后者是 GPS 轨迹图。
2.1 reflog 的存储机制与不可删除性
reflog 数据存在.git/logs/目录下,每个引用(如HEAD、refs/heads/main)都有独立日志文件。关键点在于:reflog 不受git gc影响,且默认保留 90 天(可通过gc.reflogExpire修改)。这意味着,即使你执行了git reset --hard HEAD~3把三个提交从分支历史里“抹掉”,只要 reflog 还在,那些提交对象就仍在 Git 对象库中,未被回收。
我曾在一个模拟项目 X 中遇到典型场景:某导师要求将 feature/login 模块合并进 main,但合并后发现登录态校验逻辑有严重竞态 bug。团队决定紧急回退,执行git reset --hard HEAD~1。问题来了——回退后,git log里再也看不到那个 merge 提交,更找不到它之前 feature/login 分支的最终状态。此时git reflog show main输出如下:
a1b2c3d (main) HEAD@{0}: reset: moving to HEAD~1 e4f5g6h (main) HEAD@{1}: merge feature/login: Merge made by the 'ort' strategy. i7j8k9l (feature/login) HEAD@{2}: checkout: moving from feature/login to main注意HEAD@{1}这一行——它明确记录了“merge feature/login”这个操作,且给出了该 merge 提交的完整 hashe4f5g6h。而HEAD@{2}显示切换前在feature/login分支,其最新提交是i7j8k9l。这意味着,即使feature/login分支已被删除,我们仍能通过git checkout e4f5g6h或git branch temp-fix e4f5g6h精准复活那个 merge 状态,甚至用git checkout i7j8k9l回到 feature 分支的原始终点。
提示:
git reflog默认只显示 HEAD 的日志。要查看特定分支,必须加参数,如git reflog show main或git reflog show refs/heads/develop。漏掉这个参数是新手最常犯的错误,导致以为 reflog “失效”。
2.2 reflog 的真实战场:三类必救场景
场景一:误删分支后的精准复活
某高校课程项目中,A同学执行git branch -D feature/payment后发现支付模块的 PR 还没提。此时git branch列表里已无该分支。但git reflog show feature/payment(如果该分支近期被 checkout 过)会显示其最后提交 hash。直接git branch feature/payment <hash>即可恢复。
场景二:git reset --hard后的“时间倒流”git reset --hard后,git log只剩当前 HEAD。但git reflog里HEAD@{1}就是 reset 前的位置。执行git reset --hard HEAD@{1}即可秒级回滚 reset 操作。这比翻聊天记录找 commit hash 快十倍。
场景三:追踪“幽灵提交”的来源
当git fsck报告 dangling commit(游离提交)时,这些提交往往来自git cherry-pick或git rebase的中间状态。git reflog --all | grep <dangling-hash>能快速定位是哪个操作、在哪个分支上产生的,避免盲目git show海捞。
2.3 reflog 的安全边界与实操红线
reflog 虽强,但有硬性限制:它只记录引用的移动,不记录工作区或暂存区的变更。例如,你git checkout -- file.txt丢弃了修改,reflog 不会记这笔操作。另外,git gc --prune=now会强制清理过期 reflog,生产环境切勿随意执行。
我踩过的坑:在某公司 CI 流水线脚本中,为“清理干净环境”加入了git gc --prune=now。结果某次部署失败后,运维同学想用 reflog 找回误删的配置提交,发现日志全空。后来改为git gc --prune=now --aggressive并禁用 reflog 清理,问题解决。教训:reflog 是生产环境的“事故黑匣子”,宁可多占几MB磁盘,绝不主动清除。
3. 忽略临时性本地修改:skip-worktree与assume-unchanged的本质区别与选型逻辑
“忽略本地修改”常被简单等同于.gitignore,这是危险的误读。.gitignore解决的是“从未被 Git 跟踪的文件”如何不被纳入暂存区;而skip-worktree和assume-unchanged解决的是“已被 Git 跟踪,但当前工作区需临时豁免检查”的难题。它们是 Git 为“本地开发配置”量身定制的两把锁,但锁芯结构完全不同。
3.1skip-worktree:给文件打上“永久静音”标签
git update-index --skip-worktree <file>的核心逻辑是:告诉 Git,“此文件的当前工作区状态,我自行负责,请彻底忽略其所有变更,无论内容、权限、符号链接”。Git 在执行git status、git diff、git add时,会跳过对该文件的任何比对。
它的适用场景极其明确:本地开发专属配置文件。例如,项目根目录下的config.local.json,它在 Git 中存的是模板(config.local.json.example),每个开发者 clone 后复制并填写自己的 API 密钥、数据库地址。若不加保护,git status总会显示modified: config.local.json,git add .会误提交敏感信息。
实操步骤:
# 1. 确保文件已在 Git 中跟踪(即已 commit 过) git add config.local.json.example git commit -m "add config template" # 2. 复制模板并标记为 skip-worktree cp config.local.json.example config.local.json git update-index --skip-worktree config.local.json # 3. 验证:status 不再显示该文件 git status # 输出中无 config.local.json注意:
--skip-worktree标记会随git clone一起复制到新仓库!这意味着,如果你在团队共享的.gitattributes里全局设置,新成员 clone 后无需额外操作。这是它相比assume-unchanged的最大优势。
3.2assume-unchanged:给 Git 一个“性能优化承诺”
git update-index --assume-unchanged <file>的设计初衷是性能优化:当工作区有超大文件(如 2GB 日志),Git 每次status都要计算其 SHA-1 校验和,耗时极长。此命令告诉 Git:“我保证这个文件没变,请跳过校验”。
但它有致命缺陷:该标记不随 clone 传播,且一旦工作区真被修改,Git 会完全“失明”。例如,你对large-dataset.csv执行--assume-unchanged,然后用脚本悄悄更新了它。git status依然显示“nothing to commit”,但git commit -a会把这次修改无声提交!这在数据科学项目中极易引发灾难。
因此,我的经验法则是:永远优先用skip-worktree,仅在单机超大文件调试场景下,且确认无误提交风险时,才用assume-unchanged。
3.3 混合策略:.git/info/exclude作为第三道防线
当skip-worktree无法覆盖(如需忽略整个node_modules/下的.DS_Store),.git/info/exclude是最佳选择。它类似.gitignore,但只作用于当前仓库,不提交,不传播。创建方式:
echo "node_modules/**/*.DS_Store" >> .git/info/exclude echo "build/*.log" >> .git/info/exclude它与.gitignore的关系是:Git 先查.gitignore(全局/项目级),再查.git/info/exclude(仓库级)。两者互补,而非替代。
我踩过的坑:在某图像处理 Demo 中,团队约定用skip-worktree保护local.env。但某成员误用assume-unchanged,并在一次git commit -a中意外提交了测试环境的数据库密码。事后复盘发现,assume-unchanged的“静默提交”特性,让它成为最危险的 Git 功能之一。现在我的所有项目初始化脚本里,第一行就是git update-index --skip-worktree local.env,第二行是git update-index --skip-worktree .env.local,雷打不动。
4. 检查对文本文件的修改:超越git diff的七种精准比对术
git diff是瑞士军刀,但当你面对“非 Git 跟踪文件”、“跨仓库文件”、“压缩包内文本”或“编码混乱的旧文件”时,它立刻变成钝刀。真正的文本比对高手,手里至少有七把不同齿距的锉刀。
4.1git diff --no-index:专治“野生文件”的外科手术刀
这是标题中“检查对文本文件的修改”的核心答案。--no-index参数让 Git完全脱离版本库上下文,纯粹做两个文件的逐行文本比对,且支持 Git 的所有 diff 算法(如--histogram、--patience)。
典型场景:你有两个未被 Git 跟踪的配置文件prod.config.yaml和staging.config.yaml,需要快速定位差异。
git diff --no-index --color-words prod.config.yaml staging.config.yaml--color-words会高亮到单词级差异(如timeout: 3000vstimeout: 5000),比默认的整行高亮精准十倍。
更进一步:结合git config --global diff.tool vimdiff,可一键调用 vimdiff 进行可视化三路比对(左:prod,中:差异,右:staging)。
4.2git diff --word-diff:代码评审的“显微镜”
当 PR 评审需要聚焦“到底改了哪几个词”,--word-diff比--color-words更激进。它把 diff 单元从“行”压缩到“单词”,甚至能识别出user_id→userId这样的命名风格变更。
实测对比:
# 普通 diff(整行) - const user_id = data.id; + const userId = data.id; # git diff --word-diff(单词级) const {+userId+} = data.{+id+};这对前端框架升级(如 React Class → Hook)或后端 ORM 字段映射变更的评审,效率提升显著。
4.3git diff --ignore-space-at-eol:终结“空格战争”
团队协作中,IDE 自动清理行尾空格常引发无意义 diff。--ignore-space-at-eol让 Git忽略行尾空白字符的差异,使git diff真正聚焦逻辑变更。
但注意:它不解决根本问题。更好的实践是,在项目根目录添加.editorconfig文件,统一规范:
[*] end_of_line = lf insert_final_newline = true trim_trailing_whitespace = trueGit 本身不读取.editorconfig,但主流 IDE(VS Code, WebStorm)会自动遵循,从源头消灭空格争议。
4.4git diff --submodule=log:子模块的“版本快照比对”
当项目使用 Git Submodule 时,git diff默认只显示子模块 commit hash 的变更。加上--submodule=log,会输出子模块内部的详细提交日志:
git diff --submodule=log HEAD~1 # 输出示例: # Submodule utils updated from abc123 to def456 # > feat: add retry logic (def456) # > fix: timeout handling (c7d8e9)这让你一眼看出子模块升级带来了哪些具体功能,而非仅看到一串 hash。
4.5git diff --stat:PR 描述的“黄金摘要生成器”
--stat不显示代码,只输出修改文件列表及增删行数统计。它是撰写 PR 描述的神器:
git diff --stat HEAD~3 # 输出: # src/api/auth.js | 12 ++++++------ # src/components/UI.jsx | 8 ++++---- # test/auth.spec.js | 20 ++++++++++++++++++++ # 3 files changed, 30 insertions(+), 10 deletions(-)直接复制粘贴到 PR 正文,比“修复了登录问题”有力百倍。
4.6git diff --name-only:批量操作的“文件清单引擎”
当需要对所有修改文件执行同一操作(如格式化、静态检查),--name-only提供纯净的文件路径列表:
# 对所有修改的 JS 文件运行 Prettier git diff --name-only --diff-filter=AM HEAD~1 | grep '\.js$' | xargs -r prettier --write # --diff-filter=AM 表示只取 Added 和 Modified 文件,排除 Deleted4.7git diff --binary:二进制文件的“文本化解码器”
对.png、.pdf等二进制文件,git diff默认只显示Binary files a/file.png and b/file.png differ。加上--binary,Git 会尝试将其转换为 base64 编码进行 diff(需配合git config --global core.autocrlf false避免换行符干扰)。虽不能看像素差异,但能发现“文件是否被完全替换”或“元数据是否变更”。
我踩过的坑:在某跨平台系统重构中,设计师提交了新版icon.svg,但开发收到的却是旧版。git diff --binary icon.svg显示 base64 内容完全不同,立刻锁定是设计稿同步环节出错,而非代码问题。没有--binary,我们可能花半天排查构建流程,而不是 30 秒定位根源。
5. Git 命令别名:不是偷懒,是重构你的命令行认知模型
git co代替git checkout,git ci代替git commit——这看似是键盘侠的炫技。但当我把git lg(自定义的树状 log)和git undo(封装了reset+reflog查询)加入日常,才明白别名的本质:它把 Git 的“命令集合”,重构成符合你思维习惯的“动作词汇表”。
5.1 别名的三层架构:alias → shell function → script
Git 别名支持三种实现层级,复杂度递增,能力递增:
| 层级 | 示例 | 适用场景 | 关键能力 |
|---|---|---|---|
| Alias(字符串替换) | co = checkout | 简单命令缩写 | 无参数传递,无逻辑判断 |
| Shell Function(函数封装) | lg = "!f() { git log --graph --all --format='%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative; }; f" | 复杂 log 格式、条件判断 | 支持$1,$2参数,if/else |
| External Script(外部脚本) | undo = "!f() { ... }; f" | 跨命令协调、IO 操作 | 可调用任意 shell 命令,读写文件 |
5.2 我的生产环境核心别名库(附原理注释)
以下是我所有项目.gitconfig中的必备别名,每一条都经过三年以上实战检验:
[alias] # 1. lg: 树状历史视图(带颜色、分支、相对时间) lg = "!f() { git log --graph --all --format='%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative; }; f" # 2. undo: 智能回退(自动检测上一操作类型,推荐 reset/hard/reset) undo = "!f() { \ if git reflog | head -1 | grep -q 'checkout:'; then \ echo 'Last action was checkout. Undo with: git checkout -'; \ git checkout -; \ elif git reflog | head -1 | grep -q 'commit:'; then \ echo 'Last action was commit. Undo with: git reset --soft HEAD~1'; \ git reset --soft HEAD~1; \ else \ echo 'Last action was reset/merge. Use git reflog to inspect.'; \ fi \ }; f" # 3. ds: 查看暂存区变更(带文件大小、编码提示) ds = "!f() { git diff --cached --stat; git diff --cached --numstat | awk '{sum += $1 + $2} END {print \"Total lines changed:\", sum}'; }; f" # 4. ignore: 一键 skip-worktree(带确认) ignore = "!f() { echo \"Marking $1 as skip-worktree...\"; git update-index --skip-worktree \"$1\"; git status -s \"$1\"; }; f"原理深挖:undo别名的智能逻辑
它并非简单执行git reset,而是解析 reflog 的首行操作类型:checkout:触发git checkout -(切回上一分支);commit:触发git reset --soft HEAD~1(保留工作区和暂存区,仅撤回 commit);其他情况则引导用户自查 reflog。这避免了git reset --hard误删工作区的风险,是“防御性编程”在 CLI 层的体现。
5.3 别名的团队落地:.gitconfig与git template
个人别名易管理,团队统一难落地。我的方案是:将核心别名写入项目根目录的.gitconfig,并通过git config --local include.path ../.gitconfig引入。这样,新成员 clone 后执行git config --local include.path .gitconfig,即可启用全部别名。
更进一步:在公司级 Git 模板仓库中,预置template/hooks/pre-commit和template/config,新仓库git init --template=<path>时自动继承。这比口头要求“请配置别名”有效百倍。
我踩过的坑:曾为某高校开源课程编写教学材料,要求学生配置lg别名。结果 60% 的学生因引号转义错误(Windows CMD vs PowerShell)导致别名失效,课上大量时间消耗在环境调试。后来改为提供一键脚本setup-aliases.sh,内含git config --global alias.lg '...'的完整命令,并自动检测 shell 类型,问题彻底解决。别名的价值不在命令多短,而在能否零门槛、零错误地抵达每个使用者。
6. 为临时指向的提交创建分支:git branch <name> <commit>的原子性与安全边界
标题中“为临时指向的提交创建分支”,直指git branch <name> <commit>这一被严重低估的原子操作。它不是git checkout -b的替代品,而是在 Git 对象图上,为任意 commit(无论是否被分支引用)钉下一个永久锚点的精确手术。
6.1 为什么不用git checkout -b?—— 时间戳陷阱
git checkout -b new-branch的本质是:以当前 HEAD 为起点,创建新分支并立即切换。但“当前 HEAD”是一个瞬时状态。设想场景:你在main分支,刚git pull更新到最新,正准备基于此创建feature/new-ui。但就在你输入git checkout -b的 0.5 秒内,CI 流水线推送了一个 hotfix 提交。此时git checkout -b创建的分支,起点是那个 hotfix,而非你预期的pull后状态。
而git branch feature/new-ui a1b2c3d(a1b2c3d是你git log确认的精确 commit hash)则完全规避此风险。它不依赖 HEAD 的瞬时值,而是直接在对象图上,将分支指针指向指定 commit,绝对精准。
6.2 三类不可替代的实战场景
场景一:基于 reflog 中的“幽灵提交”建分支
如前文 reflog 所示,git reflog show main | head -1得到e4f5g6h,执行git branch debug-merge e4f5g6h,即可为那个被 reset 掉的 merge 提交创建独立分支,用于深度调试,不影响当前工作区。
场景二:跨仓库复现问题
当用户报告“在 commitx7y8z9a上出现崩溃”,而你本地仓库无此提交(因未 fetch 远程分支),可先git fetch origin x7y8z9a(Git 2.29+ 支持 fetch 单个 commit),再git branch reproduce-bug x7y8z9a,完美复现用户环境。
场景三:构建“快照分支”用于审计
在某金融系统合规审计中,要求对每月 1 号的生产环境状态打快照。自动化脚本执行:
SNAPSHOT_HASH=$(git ls-remote origin main | cut -f1) git branch snapshot/$(date +%Y-%m-01) $SNAPSHOT_HASH git push origin snapshot/$(date +%Y-%m-01)snapshot/2024-01-01分支永远指向当日生产环境的确切状态,审计员可随时git checkout验证。
6.3 安全红线:git branch不切换,git switch才切换
git branch <name> <commit>仅创建分支,不改变当前工作区状态。这是它安全的根本原因——你创建分支的动作,与当前正在编辑的文件完全隔离。若需立即切换,应显式执行git switch <name>或git checkout <name>。
我踩过的坑:在某图像处理 Demo 的压力测试中,需为不同算法版本创建快照分支。我误用git checkout -b perf-test-v2,结果因网络延迟,新分支起点是旧 commit,导致测试数据污染。改用git branch perf-test-v2 $(git rev-parse HEAD)后,问题消失。记住:branch是建锚点,switch是坐火箭——二者必须分步、显式执行。
7. 将提交移动到另一分支:cherry-pick的精准外科与rebase --onto的宏观调度
“将提交移动到另一分支”是标题中最易引发争议的操作。git cherry-pick和git rebase --onto都能做到,但它们如同“镊子”与“起重机”:一个处理单点,一个调度区块。选错工具,轻则白忙,重则数据错乱。
7.1cherry-pick:单提交迁移的黄金标准
git cherry-pick <commit>的语义是:将指定 commit 的“补丁内容”(即 diff)应用到当前分支,并生成一个新 commit。它不移动原 commit,而是复制其变更。
关键优势:完全保留原 commit 的 author 信息(作者、邮箱、时间),仅更新 committer 信息(为你自己、当前时间)。这在合规场景(如审计要求记录谁在何时将某修复引入生产)中至关重要。
实操流程(将feature/login的修复提交a1b2c3d移至main):
# 1. 切换到目标分支 git switch main # 2. 应用补丁(生成新 commit) git cherry-pick a1b2c3d # 3. 验证:新 commit 的 author 仍是原作者,committer 是你 git show --pretty=fuller a1b2c3d # 原 commit git show --pretty=fuller HEAD # 新 commit,author 相同,committer 不同注意:
cherry-pick可能产生冲突。解决后git add . && git cherry-pick --continue。切勿git commit,否则会中断 cherry-pick 流程。
7.2rebase --onto:区块迁移的精密仪器
当需要迁移连续多个提交(如feature/login分支上从c1到c3的三个提交),cherry-pick需三次操作,而rebase --onto一次完成:
# 将 feature/login 分支上 c1..c3 的提交(不包括 c1), # 移动到 main 分支的最新状态之后 git rebase --onto main c1 feature/login其参数逻辑是:--onto <new-base> <old-base> <branch-to-move>。c1是旧基线(迁移区块的起始点前一个 commit),feature/login是待迁移分支。
为什么不用git rebase main feature/login?
后者会将feature/login的全部历史(包括其分叉点之前的 commits)都重放一遍,可能导致重复提交、冲突爆炸。--onto则精准限定迁移范围,是专业团队的标配操作。
7.3 绝对禁止的“伪移动”:git reset+git checkout
网上常见错误方案:“先git reset --hard <target>,再git checkout <source>”。这本质是丢弃当前分支历史,再切换过去,而非“移动提交”。它破坏了分支的线性历史,且无法追溯原提交来源。
正确姿势永远是:cherry-pick处理单点,rebase --onto处理区块,git merge处理长期协作。三者边界清晰,混用即事故。
我踩过的坑:在某跨平台系统重构中,为将develop分支的五个性能优化提交迁移到release/2.1,我误用git reset --hard release/2.1 && git checkout develop,导致develop分支的后续开发失去基础,团队三人花两小时重建历史。后来严格制定《提交迁移 SOP》:单提交用cherry-pick,多提交用rebase --onto,并加入 pre-receive hook 拦截reset --hard到主干分支的操作。Git 的力量越大,操作的仪式感越强——每一次cherry-pick,都该是一次签名确认。
8. 这六项技巧的终极价值:把 Git 从“版本控制工具”升维为“开发认知加速器”
写完这六节,我重新打开终端,敲下git lg,看着那棵色彩分明、分支交织的提交树,突然意识到:这些技巧的终极目的,从来不是“让命令敲得更快”,而是重塑我们与代码变更之间的认知关系。
reflog让我们敢于reset --hard,因为它消除了“不可逆”的心理枷锁;skip-worktree让我们坦然面对本地配置,因为它划清了“共享逻辑”与“私有状态”的楚河汉界;git diff --no-index让我们跳出 Git 的藩篱,把任意文本文件纳入可比对、可审计的范畴;别名则把 Git 的命令行界面,重构成符合你大脑工作流的“第二语言”;git branch <name> <commit>赋予我们以任意 commit 为支点,撬动整个开发流程的杠杆;而cherry-pick与rebase --onto,则让我们在分支的迷宫中,始终握有精准调度变更的罗盘。
它们共同指向一个事实:Git 的强大,不在于它有多复杂,而在于它给了你足够的自由,去定义什么是“值得被记住的变更”,以及“如何被记住”。reflog 记住的是操作轨迹,skip-worktree 记住的是你的意图,diff 记住的是文本的细微差别,别名记住的是你的思维习惯,分支记住的是你的决策节点,cherry-pick 记住的是你的协作脉络。
所以,下次当你犹豫要不要git reset --hard,不妨先git reflog;当你被config.local.json的红色状态困扰,试试git update-index --skip-worktree;当你需要比对两个未跟踪的文件,敲git diff --no-index;当你厌倦了长命令,花十分钟配置lg和undo;当你想为某个关键提交建快照,用git branch snapshot-x <hash>;当你需要把修复引入主干,坚定地git cherry-pick。
这些不是技巧,是你与 Git 达成的契约:它提供原子能力,你赋予业务意义。而这份契约的签署,不需要仪式,只需要你今天,在终端里,敲下第一个git reflog。