普通视图

发现新文章,点击刷新页面。
今天 — 2026年4月26日首页

别再手敲 git checkout -b 了,我把团队分支规范做成了 CLI

作者 ZZJsky123
2026年4月25日 13:28

大家好😁。

最近我在团队里做了一个很小的工具,叫 pumpp

它解决的问题听起来特别不起眼,甚至有点无聊:

每周发布的时候,别再手敲 git checkout -b release/xxx 了。

你可能会说,这也值得做个工具?

说实话,一开始我也觉得不值得。切个分支嘛,谁不会。

git checkout -b release/1.8.0-20260425 main

看着很简单,对吧。

但这个命令,如果你们团队每周都要敲,如果每个人都要敲,如果分支名还必须符合统一规范,那它就不是一个简单命令了。

它变成了一件反复消耗注意力的小事。

而工程里最烦人的,往往就是这种小事。


事情是从每周发版开始的

我们团队有固定的发布节奏,每周都要创建发布分支。

分支名大概长这样:

release/1.8.0-20260425

里面有几个信息:

  • release/ 前缀
  • package.json 里的版本号
  • 当天日期
  • main 切出来

规范写得很清楚。

文档里有,群公告里也发过,甚至有人把示例命令贴在 README 里。

问题是,规范写在文本里,不等于规范真的被执行。

每次到发版的时候,还是会有人问:

这周 release 分支要带日期吗?

release/1.8.0-20260425,还是 release/v1.8.0-20260425

main 切,还是从当前分支切?

hotfix 的格式是不是跟 release 一样?

这些问题都不难,但它们每周都会出现。

更烦的是,哪怕大家都知道规范,也还是会手滑。

有人少写日期,有人把 / 写成 -,有人复制上周命令忘了改版本号,有人本来想切 hotfix,结果前缀写成了 fix

这些错误都不是什么大事故。

但它们会不断打断流程。

你要删掉错分支,重新创建,确认有没有推到远端,确认 CI 有没有被触发,确认别人有没有基于那条分支拉代码。

一来一回,十分钟没了。

我有时候觉得,工程效率很多时候不是被大问题拖垮的,而是被这种小摩擦磨掉的。


文本约束的问题

团队当然可以继续靠文档约束。

比如写一段:

发布分支格式:release/{version}-{date}
功能分支格式:feature/{username}-{desc}
热修分支格式:hotfix/{username}-{desc}

然后希望大家每次都照着来。

但说真的,这种约束很脆。

新同学不知道文档在哪,老同学觉得自己记得,忙的时候没人愿意打开文档确认,CI 报错之后大家才想起来:「哦对,我们规范不是这么写的」。

这就很像早期团队靠口头约定代码格式。

「我们缩进用两个空格」

「import 要排序」

「变量名不要随便缩写」

听起来都对,但只要没有 ESLint / Prettier,最后一定会变成 review 里互相提醒。

分支命名也是一样。

靠人记住,是最不稳定的方案。

如果一条规则真的重要,就应该让工具来执行它。

这就是 pumpp 的出发点。


我想要的不是 git wrapper

我不想做一个复杂的 Git 客户端。

Git 已经够强了,也够复杂了。

pumpp 想解决的是更窄的一件事:

把团队分支命名规范写进配置文件,再用一个统一命令创建分支。

也就是这两步:

  1. 项目里放一份 pumpp.config.ts
  2. 团队成员通过 pnpm branchpumpp 创建分支

先看配置。

import { definePumpConfig } from 'pumpp-cli'

export default definePumpConfig({
  base: 'main',
  types: {
    release: {
      pattern: 'release/{version}-{date}',
    },
    feature: {
      pattern: 'feature/{username}-{desc?}',
      base: 'HEAD',
    },
    hotfix: {
      pattern: 'hotfix/{username}-{desc?}',
    },
  },
})

这个配置的核心是 pattern

release/{version}-{date} 的意思就是:

  • {version} 从 manifest 里读,默认是 package.jsonversion
  • {date} 用当天日期,格式是 YYYYMMDD
  • 最后拼出一条合法分支名

但这里还有一个字段,我觉得跟 pattern 一样重要。

base

分支名写对了,但从错的地方切出来,后面一样会很痛。

比如 release 分支应该永远从 main 切,这样它代表的是主干某个明确状态。如果有人在自己的 feature 分支上手滑敲了一句:

git checkout -b release/1.8.0-20260425

那这条 release 分支里可能混进一堆还没合并的实验代码。

名字看起来完全正确。

但内容已经错了。

这比名字错还麻烦,因为它不一定第一时间暴露。

所以 pumpp 里 base: 'main' 的意思不是装饰字段,而是在说:

这个类型的分支,只能从 main 切。

你也可以在具体类型上覆盖它。

比如 release / hotfix 都从 main 切,但 feature 想从当前所在分支切,就写:

types: {
  release: { pattern: 'release/{version}-{date}', base: 'main' },
  hotfix: { pattern: 'hotfix/{username}-{desc?}', base: 'main' },
  feature: { pattern: 'feature/{username}-{desc?}', base: 'HEAD' },
}

HEAD,或者简写 '.',表示「我当前 checkout 的分支」。

这在一些团队里很有用。比如你正在 dev 上做一组相关改动,想从当前上下文里切一条 feature,而不是每次都从 main 开始。

但 release 这种分支,我个人建议还是写死 main

发布分支从哪来,是流程边界,不要靠手感。

顺手说一句,pumpp 会在真正创建分支前校验 base,它必须是本地存在的分支。origin/main、tag、commit SHA 这种都不会被当成合法 base。这个限制看起来有点严格,但目的很简单,别让大家以为自己从远端主干切了,结果实际用的是一个含糊的引用。

然后在 package.json 里加一个团队入口。

{
  "scripts": {
    "branch": "pumpp",
    "branch:release": "pumpp release",
    "branch:feature": "pumpp feature",
    "branch:hotfix": "pumpp hotfix"
  }
}

之后团队成员不需要记 git checkout -b 那一长串,也不需要记 pattern。

直接:

pnpm branch

它会进入交互模式,让你选择分支类型。

或者更直接一点:

pnpm branch:release

创建发布分支。

pnpm branch:feature --desc login

创建功能分支。

这才是我更推荐的用法。

不是每个人全局安装一个命令,然后自己凭印象敲;而是项目把 branch 入口写在 scripts 里,团队只认这个入口。

像跑测试一样。

你不会要求新人记住底层测试命令到底是 vitest run 还是 jest --runInBand,你会告诉他:

pnpm test

切分支也应该一样。


创建分支时,它会先停下来问你

执行 pnpm branch:release 后,pumpp 会根据配置算出分支名。

比如:

release/1.8.0-20260425

然后它不是直接冲上去创建,而是先给你一个确认菜单:

? Branch name: release/1.8.0-20260425
❯ ✔ Accept    Create this branch as-is
  ✎ Edit      Modify before creating
  ✖ Cancel    Abort, do not touch the repo

这个菜单是我特意做的。

因为本地开发不是 CI。

本地会有很多临时情况,比如这次 release 想带一个 rc1 后缀,或者 feature 描述想临时改一下。

所以 pumpp 给了三个选择:

  • Accept,按当前分支名创建
  • Edit,在已经预填好的分支名上直接修改
  • Cancel,干净退出,不动仓库

Edit 之后还会重新跑 git check-ref-format 和重名检查。

这点很重要。

工具不能一边说自己约束规范,一边在用户手动改名后就放飞自我。


交互不是花活,是为了少记东西

再看 feature 分支。

默认 pattern 是:

feature/{username}-{desc?}

{username} 会从 Git 用户名、环境变量、系统用户名里取一个,然后 slug 成适合分支名的形式。

{desc?} 是可选描述。

你可以直接传:

pnpm branch:feature --desc oauth-refresh

得到:

feature/alice-oauth-refresh

如果你不传 --desc,在支持交互的终端里,pumpp 会问你要不要填描述,并且显示实时预览。

这个预览不是炫技。

它解决的是一个很小但真实的问题:

我在输入描述的时候,想知道最终分支名长什么样。

尤其是描述里有空格、大写、下划线的时候,比如你输入:

Fix Login Bug

pumpp 会把它 slug 成:

fix-login-bug

最后得到:

feature/alice-fix-login-bug

你不用在脑子里手动模拟这一遍。


CLI 当然也能直接用

scripts 是我推荐的团队用法,但 pumpp 本身也是一个普通 CLI。

你可以直接:

pumpp release
pumpp feature --desc login
pumpp hotfix --desc urgent-fix --push -y

也可以只预览,不动仓库:

pumpp release --dry-run

CI 里一般会配 -y 跳过确认。

- name: Create release branch
  run: pnpm dlx pumpp-cli release -y --push

本地有人参与,保留确认;CI 没人参与,跳过交互。

这就是 pumpp 的基本策略。


团队自己的字段怎么办

很多团队的分支名不止 version、date、username。

可能还会有模块名、Jira ticket、需求 ID、环境名。

这种东西 pumpp 不可能内置完。

所以它有 tokenProviders

比如你们希望:

feature/PROJ-1234-login

配置可以这么写:

import { definePumpConfig } from 'pumpp-cli'

export default definePumpConfig({
  types: {
    feature: {
      pattern: 'feature/{ticket}-{desc}',
    },
  },
  tokenProviders: [
    {
      name: 'ticket',
      resolve: () => process.env.JIRA_TICKET?.toLowerCase(),
    },
  ],
})

如果某个字段不适合自动解析,比如模块名想让用户现场输入,也可以标记成 interactive

tokenProviders: [
  { name: 'module', interactive: true },
]

然后 pattern 写:

style: {
  pattern: 'style({module})/{username}-{desc?}',
}

本地会问,CI 不会傻等。

如果必填 token 没值,pumpp 会直接失败,而不是偷偷生成一条半残分支。

这点我挺坚持。

因为 CI 里最可怕的不是失败,是带着错误信息成功。


后来我们怎么用

现在我更倾向于把 pumpp 当成「项目规范的一部分」。

不是让每个人记命令,而是让仓库告诉大家怎么创建分支。

比如新同学进项目,只需要看 package.json

{
  "scripts": {
    "dev": "vite",
    "test": "vitest",
    "branch": "pumpp",
    "branch:release": "pumpp release",
    "branch:feature": "pumpp feature",
    "branch:hotfix": "pumpp hotfix"
  }
}

他会很自然地知道:

pnpm branch

就像他知道:

pnpm test

一样自然。

这就是我想要的效果。

不是让工具显得很厉害,而是让团队少记一件事。


最后想说的

分支名这个东西,看起来很小。

但它连接着很多后续流程:CI、发版、回滚、review、changelog、分支清理。

只要它是手敲的,就会飘。

只要它靠文本约束,就会被忘。

所以我的结论很简单:

团队规范不要只写在文档里,要尽量写进工具里。

文档适合解释为什么,工具负责保证怎么做。

pumpp 只是把这件事落到了「创建分支」这个很小的场景里。

如果你的团队也每周都要切发布分支,也在群里反复确认「这次 release 分支怎么命名」,或者你已经厌倦了每次手敲 git checkout -b,可以试一下。

pnpm add -D pumpp-cli
pumpp init

然后在 package.json 里加:

{
  "scripts": {
    "branch": "pumpp"
  }
}

之后让团队从这一句开始:

pnpm branch

让机器记规范,让人少背一点。

谢谢大家👏,如果对你有帮助欢迎不吝点赞👍,也可安装 pumpp 使用下。

❌
❌