小类随手记

一个简单使用的git提交工具,基于fish自定义函数

用 NixOS + home-manager 在 Fish Shell 中定义一个交互式 gcp 函数,结合 Conventional Commits 规范实现带中文提示的 Git 提交与推送。

背景

日常开发中,Git 提交是高频操作。虽然 git commit -m "xxx" 足够简单,但团队协作中遵循 Conventional Commits 规范能显著提升提交信息的可读性和自动化能力(自动生成 CHANGELOG、语义化版本 bump 等)。

然而每次提交都要手动输入 feat:fix: 等前缀,偶尔还会拼错。如果能有一个交互式工具,先显示当前变更概览,再通过菜单选择提交类型,最后自动拼装并推送,显然会更高效。

本项目基于 NixOS + home-manager 管理整个系统配置,Fish Shell 作为默认交互式 Shell,自然希望将这样的工具函数纳入 Nix 声明式管理,一次部署、全局生效。

设计思路

  • 用 Fish Shell 函数实现:不引入外部依赖,利用 Fish 内置的 readswitchgit status --short 等能力
  • Nix 化:通过 home-manager 的 programs.fish.functions 以声明式方式定义函数,随系统 rebuild 自动部署
  • 交互流程
    1. 先调用 git status --short 显示变更概览(新增/修改/删除的文件)
    2. 列出 Conventional Commits 10 种类型及中文说明
    3. 用户选择序号,输入提交描述
    4. 自动执行 git add . && git commit -m "type: desc" && git push

完整代码

文件路径:modules/apps/git_fiash_fun.nix

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
{
  pkgs,
  username,
  ...
}:
{
  home-manager = {
    users.${username} = { ... }: {
      programs.fish.functions = {
        gcp = {
          description = "交互式 Git 提交与推送(Conventional Commits)";
          body = ''
            set -l types \
                "feat     — 新功能,包含实质性逻辑的新文件" \
                "fix      — 修复 bug" \
                "docs     — 仅文档变更(README、注释、文档字符串)" \
                "style    — 格式调整、空白、缺少分号(无代码逻辑变化)" \
                "refactor — 代码重构,非新增功能或修复 bug" \
                "perf     — 性能改进" \
                "test     — 添加或修改测试" \
                "build    — 构建系统、依赖、CI 配置" \
                "chore    — 维护、工具、配置文件、gitignore" \
                "revert   — 回退之前的提交"

            echo ""
            echo "========== 变更概览 =========="
            git status --short
            echo "=============================="
            echo ""
            echo "请选择提交类型 (Conventional Commits):"
            echo ""
            for i in (seq (count $types))
                if test $i -lt 10
                    echo "  $i)  "$types[$i]
                else
                    echo "  $i) "$types[$i]
                end
            end

            echo ""
            read -l -P "输入序号 (1-10) [默认 1]: " type_idx
            if test -z "$type_idx"
                set type_idx 1
            end

            set -l prefix
            switch $type_idx
                case 1;  set prefix "feat"
                case 2;  set prefix "fix"
                case 3;  set prefix "docs"
                case 4;  set prefix "style"
                case 5;  set prefix "refactor"
                case 6;  set prefix "perf"
                case 7;  set prefix "test"
                case 8;  set prefix "build"
                case 9;  set prefix "chore"
                case 10; set prefix "revert"
                case '*'
                    echo "无效输入,已取消。"
                    return 1
            end

            read -l -P "输入提交描述: " commit_msg
            if test -z "$commit_msg"
                echo "描述不能为空,已取消。"
                return 1
            end

            echo ""
            echo "即将执行: git add . && git commit -m \"$prefix: $commit_msg\" && git push"
            echo ""

            git add .
            git commit -m "$prefix: $commit_msg"
            and git push
            or echo "提交或推送失败,请检查错误信息。"
          '';
        };
      };
    };
  };
}

在模块调度中心注册

app_and_modules_list.nix 中按条件引入(此处对所有机器生效):

1
++ importIf IsAll ./modules/apps/git_fiash_fun.nix

因为该函数依赖 Fish Shell,而 Fish 已通过 IsAll 条件全局启用,所以此模块也在 IsAll 下引入。

关键细节

1. 如何让 Fish 函数被 Nix 管理

home-manager 提供 programs.fish.functions 属性集,每个 key 是一个函数名,value 包含 descriptionbody。rebuild 后函数文件自动生成在 ~/.config/fish/functions/gcp.fish,Fish 启动时自动 source。

2. 为什么不用 fzf

最初考虑过用 fzf 做模糊搜索选择器,体验更流畅。但为了保持零额外依赖、纯 Fish 内置能力实现,选择了编号菜单——这在 Fish 的任何安装环境下都能工作,无需额外安装 fzf

3. 为何是 git add . 而非选择性暂存

gcp 的设计场景是"我想把当前所有变更一次性提交并推送",类似一个快捷发布命令。如果需要精细控制暂存哪些文件,应使用 lazygitgitui 或原生 git add -p

4. 提交信息语言

函数本身不限制提交描述语言——交互提示是中文,但实际 commit message 内容由用户自由输入。建议团队项目中保持英文描述以保持一致性。

5. Nix 特性:字符串路径 vs Nix path literal

注意 body = ''...'' 使用的是 Nix 多行字符串。函数体作为字符串嵌入 Nix 表达式中,最终由 home-manager 写入 Fish 函数文件。这是 Nix 声明式管理配置文件的典型模式——配置内容以字符串形式存在于 Nix 模块中,而非独立的配置文件。

使用方式

在任意 Git 仓库中 直接运行 gcp ,输出示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
========== 变更概览 ==========
 M modules/apps/git_fiash_fun.nix
?? modules/apps/git_fiash_fun.nix
==============================

请选择提交类型 (Conventional Commits):

  1)  feat     — 新功能,包含实质性逻辑的新文件
  2)  fix      — 修复 bug
  3)  docs     — 仅文档变更(README、注释、文档字符串)
  4)  style    — 格式调整、空白、缺少分号(无代码逻辑变化)
  5)  refactor — 代码重构,非新增功能或修复 bug
  6)  perf     — 性能改进
  7)  test     — 添加或修改测试
  8)  build    — 构建系统、依赖、CI 配置
  9)  chore    — 维护、工具、配置文件、gitignore
  10) revert   — 回退之前的提交

输入序号 (1-10) [默认 1]: 9
输入提交描述: 整理配置

最终执行:git commit -m "chore: 整理配置" && git push

总结

这个 gcp 函数虽然只有几十行,但体现了 NixOS 配置管理的一个核心理念:一切皆声明。工具函数不再是散落在各台机器 ~/.config/fish/config.fish 中的手写片段,而是作为 Nix 模块的一部分,版本化、可复用、按条件加载。

配合 Nix Flakes 和 home-manager,新增一个函数 = 写一个 .nix 文件 → 在调度中心注册 → rebuild,整个过程声明式、可追溯、可复现。

扩展:可以集成到nvim/ecmacs

comments powered by Disqus
Theme Stack