我给自己写了一个本地 Git 提交助手:提交前先把改动讲清楚
目录
- 缘起:一次提交为什么总是混进无关文件
- 需求拆解:提交助手不能替人盲目操作
- 技术选型:围绕 Git 命令构建可解释界面
- 核心实现一:读取状态与差异信息
- 核心实现二:选择性暂存与提交预览
- UI 定制:让提交前的最后检查变简单
- 三个 Git 助手最容易越界的地方
- 结尾:好的提交工具应该增加确认,而不是减少判断
1. 缘起:一次提交为什么总是混进无关文件
我很喜欢 Git 的可追踪性,却不喜欢“临近提交时整理改动”这一步。一个下午可能同时改了功能、补了注释、调整了格式,还生成了几个本地测试文件。到了提交时,工作区里已经不是一个清晰的改动,而是一堆需要重新理解的文件。
常见做法各有问题:
git add .很快,却可能把密钥、日志和临时文件一起加入;- 一个文件里同时有两类改动,整文件暂存会让提交难以 review;
- 终端查看长 diff 时,容易错过文件末尾的小变化;
- 提交说明写成“修改代码”,过几天几乎无法帮助回忆;
- 一些 Git 图形客户端功能太多,反而让一次简单提交变重。
我想要的是一个提交前整理台:显示状态和 diff,允许按文件或区块选择暂存,执行提交前再次展示将进入 commit 的内容,但不自动 push,也不替我解决冲突。
2. 需求拆解:提交助手不能替人盲目操作
功能清单围绕“看清楚”和“可撤销”:
| 诉求(人话) | 拆解后(可验收) |
|---|---|
| 知道改了哪些文件 | 展示新增、修改、删除和未跟踪状态 |
| 不把临时文件提交 | 支持忽略规则和显式文件选择 |
| 只提交部分改动 | 支持按 diff 区块选择暂存 |
| 提交前再看一次 | 显示暂存区 diff 和文件统计 |
| 提交说明别太空 | 提供类型、范围和摘要模板 |
| 操作失败能恢复 | 默认不删除文件、不自动 push |
第一版不提供自动生成“完美提交信息”的 AI 功能。提交说明可以由模板辅助,但具体意图必须由作者确认;工具不能根据文件名擅自猜测业务含义。
心得:Git 工具最重要的安全线,是不把“方便”变成未经确认的仓库变更。
3. 技术选型:围绕 Git 命令构建可解释界面
程序使用 .NET 8 和 WinForms,通过 Git CLI 获取状态和执行有限操作:
| 能力 | 方案 |
|---|---|
| 仓库检测 | git rev-parse --show-toplevel |
| 文件状态 | git status --porcelain=v1 |
| 未暂存差异 | git diff |
| 暂存差异 | git diff --cached |
| 选择性暂存 | git apply --cached |
| 提交执行 | git commit |
我没有直接操作 .git/index。Git 命令虽然需要解析输出,但能保持和用户现有 Git 版本一致,也能让用户在工具关闭后继续使用命令行检查状态。
所有命令都以仓库根目录为工作目录,参数通过进程参数列表传入,不拼接一整段 shell 字符串。这样可以减少路径空格、特殊字符和命令注入带来的问题。
4. 核心实现一:读取状态与差异信息
--porcelain=v1 提供了适合程序解析的稳定状态格式。每行前两个字符分别表示暂存区和工作区状态:
private static GitStatus ParseStatus(string line)
{
var indexState = line[0];
var worktreeState = line[1];
var path = line.Length > 3 ? line[3..] : string.Empty;
return new GitStatus(
path,
indexState,
worktreeState,
IsUntracked: indexState == '?' && worktreeState == '?');
}
重命名和带空格的路径不能简单按空格分割,工具会保留 Git 输出的路径编码信息,并在展示层再进行转义。无法安全解析的行会原样显示为警告,而不是被静默丢弃。
文件列表点击后加载对应 diff。未跟踪文件默认只显示“尚未被 Git 跟踪”,用户明确选择后才读取文件内容生成新文件 diff,并限制单文件预览大小,避免把构建产物一次性加载进界面。
状态刷新不会覆盖用户当前勾选。刷新后若某文件发生变化,工具标记“内容已变化,需要重新确认”,不继续沿用旧的区块选择。
5. 核心实现二:选择性暂存与提交预览
选择性暂存的核心是把用户勾选的 diff 区块生成补丁,再交给 Git 应用到 index:
private async Task StagePatchAsync(
string repository,
string patch,
CancellationToken token)
{
var result = await RunGitAsync(
repository,
["apply", "--cached", "--whitespace=nowarn", "-"],
patch,
token);
if (result.ExitCode != 0)
throw new GitCommandException(result.Error);
}
暂存成功后,工具重新读取 git diff --cached,不假设 Git 一定按原样接受补丁。若上下文已经变化,Git 拒绝应用,用户需要重新加载 diff,而不是让工具强行覆盖。
提交前预览页只显示将进入本次 commit 的内容:文件列表、增删行数量、完整 diff 和提交说明。提交说明使用可选模板:
<type>(<scope>): <summary>
<details>
标题长度、空摘要和疑似调试文本会给出提示,但不会阻止用户使用自己的格式。提交按钮旁边明确写着当前分支和暂存文件数量,避免在错误仓库中操作。
6. UI 定制:让提交前的最后检查变简单
界面采用三栏布局:左侧是文件状态,中间是 diff,右侧是提交说明和暂存区摘要。
每个文件显示两组状态:工作区和暂存区。一个文件同时有两种状态时,使用双标签提醒用户“这次提交只包含其中一部分改动”。
顶部有三个明确按钮:刷新、暂存所选、撤销暂存。撤销暂存只执行 git restore --staged,不会删除工作区改动。提交按钮在没有暂存内容时禁用,push 永远不在这个工具里出现。
敏感文件名可以加入本地保护规则,例如 .env、密钥目录和证书文件。规则只影响界面默认选择,不修改仓库的 .gitignore;用户仍可显式查看和操作这些文件。
7. 三个 Git 助手最容易越界的地方
坑一:状态刷新可能和用户编辑同时发生。 工具读取 diff 后,编辑器可能立刻保存了新内容。执行选择性暂存前必须重新获取并验证补丁上下文,不能把旧快照直接应用到新文件。
坑二:处理冲突不能靠隐藏错误。 git apply、git commit 和 hook 都可能失败。工具要显示 Git 的原始错误、当前分支和工作区状态,不要把所有失败都改写成“操作未成功”。
坑三:提交不等于备份。 用户点击提交后,工具不能暗示代码已经安全上传。提交只改变本地仓库历史,是否 push、是否创建远程备份仍然是独立动作。
此外,Git hook 可能修改文件或阻止提交。提交结束后工具要重新刷新状态,并提示 hook 是否产生了新的未暂存改动。
8. 结尾:好的提交工具应该增加确认,而不是减少判断
做完这个 Git 提交助手后,我提交代码时不再先执行一把梭的 git add .。先看状态,再选择区块,最后只对暂存区内容写提交说明,提交历史明显更容易阅读。
一个可靠的提交助手应该做到:
- 区分工作区和暂存区;
- 执行前展示最终 diff;
- 选择性操作失败时保持原状;
- 默认不删除、不 push、不修改忽略规则;
- 提交后重新读取真实仓库状态。
如果你也想做类似工具,可以先实现状态列表、diff 预览和安全暂存,再考虑提交模板。Git 的价值来自可追踪和可复核,辅助工具应该让这两点更清晰,而不是把判断藏在自动化按钮后面。
好的提交不是“按下按钮就完成”,而是你知道这次提交为什么存在,以及它准确包含了什么。
- 点赞
- 收藏
- 关注作者
评论(0)