Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions .version/changelog/Unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,33 @@

## 新增

暂无
- 新增顶层 git 动词子命令:`fastgit status|status short`、`fastgit add`、`fastgit log|log graph`、`fastgit diff [--staged|--unstaged]`、`fastgit branch current|list|checkout|checkout-remote|create|delete`、`fastgit fetch [--prune]`、`fastgit rebase <upstream>|--continue|--abort|--skip`、`fastgit remote|remote list`
- `fastgit pull` 新增 `--rebase` flag(rebase 方式拉取,与 `--all`/`--hard` 互斥)
- `fastgit tag show <tag>`:查看 tag 详情(`git show <tag>`)
- `fastgit ui`:原 `ggc` 交互命令面(fuzzy 搜索 + workflow + alias),配置文件仍为 `ggc.yaml`,路径不变

## 修复

暂无

## 变更

暂无
- 移除 `fastgit ggc` 统一入口,原有命令摊平为顶层子命令;`ggc pull current|pull rebase`、`ggc push current|push force`、`ggc tag list`、`ggc commit <message>` 分别由 `pull`、`push`、`tag`、`git commit -m` / `fastgit commit` 承接

## 文档

暂无
- README、docs/features、docs/architecture、docs/roadmap 同步新命令面

## 影响范围

暂无
- CLI 命令面:`fastgit ggc` 用户需改用顶层子命令或 `fastgit ui`
- `ggc.yaml`(workflow/alias)路径与格式不变,无需迁移

## 验证建议

暂无
- `go build ./... && go test ./...`
- `fastgit status`、`fastgit branch current`、`fastgit remote`、`fastgit pull --help`(确认 `--rebase`)、`fastgit ui list`、`fastgit ui path`

## 回滚建议

暂无
- 回退本次提交即可恢复 `fastgit ggc` 入口
60 changes: 36 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,25 @@ wails3 dev
- `fastgit changelog draft`: 使用 Copilot 根据当前改动更新 `Unreleased.md`
- `fastgit changelog release`: 将 `Unreleased.md` 落版为版本文件,并可同步推进 `.version/VERSION`
- `fastgit docs init`: 初始化文档维护用的 prompt / instruction 模板
- `fastgit pull`: 拉取当前分支(支持 `--all`)
- `fastgit pull`: 拉取当前分支(支持 `--all` / `--rebase`
- `fastgit pull --hard`: 强制与远端同步(`fetch + reset --hard`)
- `fastgit push`: 推送当前分支(支持 `--all` / `--force`)
- `fastgit worktree`: 列出当前仓库 worktree
- `fastgit worktree create <issue|branch> [--base <branch>]`: 创建 worktree
- `fastgit worktree remove <issue|branch>`: 删除 worktree
- `fastgit worktree remove --path <worktree-path>`: 按路径删除 worktree
- `fastgit ggc list`: 查看统一命令面(ggc 风格)
- `fastgit ggc <command ...>`: 执行统一命令,例如 `fastgit ggc status short`
- `fastgit ggc` / `fastgit ggc interactive`: 进入交互模式(增量搜索 + workflow)
- `fastgit ggc path`: 查看当前 `ggc.yaml` 的实际路径(按 OS/XDG 规则)
- `fastgit status` / `fastgit status short`: 查看工作区状态
- `fastgit add <file|.>`: 暂存文件
- `fastgit log` / `fastgit log graph`: 查看提交记录
- `fastgit diff [--staged|--unstaged]`: 查看改动
- `fastgit branch current|list|checkout <name>|checkout-remote <name>|create <name>|delete <name>`: 分支操作
- `fastgit fetch [--prune]`: 拉取远端更新
- `fastgit rebase <upstream>` / `fastgit rebase --continue|--abort|--skip`: 变基操作
- `fastgit remote` / `fastgit remote list`: 查看远程仓库
- `fastgit tag show <tag>`: 查看 tag 详情
- `fastgit ui`: 进入交互模式(fuzzy 搜索 + workflow + alias)
- `fastgit ui list`: 查看交互命令面(含 alias)
- `fastgit ui path`: 查看当前 `ggc.yaml` 的实际路径(按 OS/XDG 规则)

## Repo Prompt Templates

Expand Down Expand Up @@ -99,23 +107,27 @@ wails3 dev

> 建议:把“生成建议”和“执行提交”区分成不同 prompt,便于在不同风险场景下选择更稳妥的工作流。

## New ggc-style command surface (phase 1)
## Git command surface

- `fastgit ggc status|status short`
- `fastgit ggc add <file|.>`
- `fastgit ggc commit <message>`
- `fastgit ggc log simple|graph`
- `fastgit ggc diff|diff staged|diff unstaged`
- `fastgit ggc branch current|list local|list remote|checkout <name>|checkout remote <name>|create <name>|delete <name>`
- `fastgit ggc fetch|fetch prune`
- `fastgit ggc pull current|pull rebase`
- `fastgit ggc push current|push force`
- `fastgit ggc rebase <upstream>|continue|abort|skip`
- `fastgit ggc tag list|show <tag>`
- `fastgit ggc remote list`
常用 git 操作已摊平为顶层子命令(原 `ggc` 统一入口已拆分):

## Interactive Mode (phase 2 - MVP)
- `fastgit status|status short`
- `fastgit add <file|.>`
- `fastgit log|log graph`
- `fastgit diff [--staged|--unstaged]`
- `fastgit branch current|list [--remote]|checkout <name>|checkout-remote <name>|create <name>|delete <name>`
- `fastgit fetch [--prune]`
- `fastgit pull [--all|--rebase|--hard]`
- `fastgit push [--all|--force]`
- `fastgit rebase <upstream>|--continue|--abort|--skip`
- `fastgit tag list|show <tag>`
- `fastgit remote|remote list`

> 原 `fastgit ggc commit <message>` 已移除:带 message 的提交直接使用 `git commit -m`,AI 提交使用 `fastgit commit`。

## Interactive Mode

- 入口:`fastgit ui` 或 `fastgit ui interactive`
- 搜索模式(默认)
- 输入字符:实时 fuzzy 过滤命令
- `↑/↓` 或 `Ctrl+N/P`:移动选中
Expand All @@ -133,12 +145,12 @@ wails3 dev

> 对于带占位参数的命令(如 `<name>`),执行时会自动提示输入参数。

## Phase 3: Workflow 持久化 + Alias
## Workflow 持久化 + Alias

- workflow 会持久化到:`<XDG 配置目录>/fastgit/ggc.yaml`
- macOS 常见为:`~/Library/Application Support/fastgit/ggc.yaml`
- Linux 常见为:`~/.config/fastgit/ggc.yaml`
- 每次进入 `fastgit ggc` 交互模式会自动加载上次 workflow
- 每次进入 `fastgit ui` 交互模式会自动加载上次 workflow
- 在交互模式里对 workflow 的新增/删除/清空会自动保存

### Alias 配置
Expand All @@ -163,9 +175,9 @@ aliases:
说明:

- `{0}`、`{1}`... 表示位置参数
- 例如:`fastgit ggc ci "fix typo"`
- 例如:`fastgit ggc quick "chore: update"`
- `fastgit ggc list` 会同时显示内置命令与 alias
- 例如:`fastgit ui ci "fix typo"`
- 例如:`fastgit ui quick "chore: update"`
- `fastgit ui list` 会同时显示内置命令与 alias

## Refer
- https://github.com/Nutlope/aicommits
Expand Down
20 changes: 18 additions & 2 deletions bootstrap/boot.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,32 @@ import (
"github.com/charmbracelet/x/term"
"github.com/pubgo/dix/v2"
"github.com/pubgo/dix/v2/dixcontext"
"github.com/pubgo/fastgit/cmds/addcmd"
"github.com/pubgo/fastgit/cmds/branchcmd"
"github.com/pubgo/fastgit/cmds/checkcmd"
"github.com/pubgo/fastgit/cmds/chglogcmd"
"github.com/pubgo/fastgit/cmds/conflictcmd"
"github.com/pubgo/fastgit/cmds/teamcmd"
"github.com/pubgo/fastgit/cmds/configcmd"
"github.com/pubgo/fastgit/cmds/copilotcmd"
"github.com/pubgo/fastgit/cmds/diffcmd"
"github.com/pubgo/fastgit/cmds/docscmd"
"github.com/pubgo/fastgit/cmds/fastcommitcmd"
"github.com/pubgo/fastgit/cmds/ggccmd"
"github.com/pubgo/fastgit/cmds/fetchcmd"
"github.com/pubgo/fastgit/cmds/historycmd"
"github.com/pubgo/fastgit/cmds/initcmd"
"github.com/pubgo/fastgit/cmds/logcmd"
"github.com/pubgo/fastgit/cmds/prcmd"
"github.com/pubgo/fastgit/cmds/pullcmd"
"github.com/pubgo/fastgit/cmds/pushcmd"
"github.com/pubgo/fastgit/cmds/rebasecmd"
"github.com/pubgo/fastgit/cmds/remotecmd"
"github.com/pubgo/fastgit/cmds/reviewcmd"
"github.com/pubgo/fastgit/cmds/sshcmd"
"github.com/pubgo/fastgit/cmds/statuscmd"
"github.com/pubgo/fastgit/cmds/tagcmd"
"github.com/pubgo/fastgit/cmds/upgradecmd"
"github.com/pubgo/fastgit/cmds/uicmd"
"github.com/pubgo/fastgit/cmds/versioncmd"
"github.com/pubgo/fastgit/cmds/worktreecmd"
"github.com/pubgo/fastgit/pkg/aiprovider"
Expand All @@ -46,10 +54,18 @@ func Main() {
versioncmd.New(),
initcmd.New(),
upgradecmd.New(),
statuscmd.New(),
addcmd.New(),
logcmd.New(),
diffcmd.New(),
branchcmd.New(),
fetchcmd.New(),
rebasecmd.New(),
remotecmd.New(),
tagcmd.New(),
sshcmd.New(),
historycmd.New(),
ggccmd.New(),
uicmd.New(),
fastcommitcmd.New(),
checkcmd.New(),
conflictcmd.New(),
Expand Down
32 changes: 32 additions & 0 deletions cmds/addcmd/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package addcmd

import (
"context"
"fmt"

"github.com/pubgo/fastgit/utils"
"github.com/pubgo/redant"
)

func New() *redant.Command {
return &redant.Command{
Use: "add <file|.>",
Short: "Stage files",
Handler: func(ctx context.Context, i *redant.Invocation) error {
args, err := addArgs(i.Args...)
if err != nil {
return err
}

return utils.RunGit(ctx, args...)
},
}
}

func addArgs(files ...string) ([]string, error) {
if len(files) == 0 {
return nil, fmt.Errorf("usage: add <file|.>")
}

return append([]string{"add"}, files...), nil
}
26 changes: 26 additions & 0 deletions cmds/addcmd/cmd_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package addcmd

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestAddArgs(t *testing.T) {
t.Run("stage all", func(t *testing.T) {
args, err := addArgs(".")
require.NoError(t, err)
require.Equal(t, []string{"add", "."}, args)
})

t.Run("stage multiple files", func(t *testing.T) {
args, err := addArgs("a.go", "b.go")
require.NoError(t, err)
require.Equal(t, []string{"add", "a.go", "b.go"}, args)
})

t.Run("no files is an error", func(t *testing.T) {
_, err := addArgs()
require.ErrorContains(t, err, "usage")
})
}
120 changes: 120 additions & 0 deletions cmds/branchcmd/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package branchcmd

import (
"context"
"fmt"
"strings"

"github.com/pubgo/fastgit/utils"
"github.com/pubgo/redant"
)

func New() *redant.Command {
var flags = new(struct {
listRemote bool
})

return &redant.Command{
Use: "branch",
Short: "Branch inspect and switch shortcuts",
Children: []*redant.Command{
{
Use: "current",
Short: "Show current branch",
Handler: func(ctx context.Context, i *redant.Invocation) error {
return utils.RunGit(ctx, "branch", "--show-current")
},
},
{
Use: "list",
Short: "List branches (--remote for remote branches)",
Options: []redant.Option{
{
Flag: "remote",
Description: "List remote branches",
Value: redant.BoolOf(&flags.listRemote),
},
},
Handler: func(ctx context.Context, i *redant.Invocation) error {
if flags.listRemote {
return utils.RunGit(ctx, "branch", "-r")
}
return utils.RunGit(ctx, "branch")
},
},
{
Use: "checkout",
Short: "Checkout branch",
Handler: func(ctx context.Context, i *redant.Invocation) error {
name, err := requireOneArg(i.Args, "branch checkout <name>")
if err != nil {
return err
}
return utils.RunGit(ctx, "checkout", name)
},
},
{
Use: "checkout-remote",
Short: "Checkout remote branch to a tracking local branch",
Handler: func(ctx context.Context, i *redant.Invocation) error {
name, err := requireOneArg(i.Args, "branch checkout-remote <name>")
if err != nil {
return err
}
args, err := checkoutRemoteArgs(name)
if err != nil {
return err
}
return utils.RunGit(ctx, args...)
},
},
{
Use: "create",
Short: "Create and checkout new branch",
Handler: func(ctx context.Context, i *redant.Invocation) error {
name, err := requireOneArg(i.Args, "branch create <name>")
if err != nil {
return err
}
return utils.RunGit(ctx, "checkout", "-b", name)
},
},
{
Use: "delete",
Short: "Delete local branch",
Handler: func(ctx context.Context, i *redant.Invocation) error {
name, err := requireOneArg(i.Args, "branch delete <name>")
if err != nil {
return err
}
return utils.RunGit(ctx, "branch", "-d", name)
},
},
},
Handler: func(ctx context.Context, i *redant.Invocation) error {
return redant.DefaultHelpFn()(ctx, i)
},
}
}

func requireOneArg(args []string, usage string) (string, error) {
if len(args) != 1 {
return "", fmt.Errorf("usage: %s", usage)
}
return strings.TrimSpace(args[0]), nil
}

func checkoutRemoteArgs(name string) ([]string, error) {
name = strings.TrimSpace(name)
if name == "" {
return nil, fmt.Errorf("usage: branch checkout-remote <name>")
}

remote := name
if !strings.HasPrefix(remote, "origin/") {
remote = "origin/" + remote
}

local := strings.TrimPrefix(remote, "origin/")
return []string{"checkout", "-b", local, "--track", remote}, nil
}
Loading
Loading