diff --git a/internal/executor/gitundo.go b/internal/executor/gitundo.go new file mode 100644 index 0000000..101db3f --- /dev/null +++ b/internal/executor/gitundo.go @@ -0,0 +1,94 @@ +package executor + +import ( + "context" + "errors" + "fmt" + "strings" +) + +// gitUndoOptions are the parsed arguments of `kopsc git undo`. +type gitUndoOptions struct { + Hard bool +} + +// runGitUndo undoes the last commit. +func runGitUndo(ctx context.Context, req BuiltinRequest) error { + return runGitUndoWith(ctx, req, execGitRunner{}) +} + +func runGitUndoWith(ctx context.Context, req BuiltinRequest, runner gitRunner) error { + opts, confirmed, err := parseGitUndoArgs(req.Args) + if err != nil { + return err + } + confirmed = confirmed || req.Confirmed + + // Get the last commit info + out, _, err := runner.Run(ctx, "log", "-1", "--format=%h %s") + if err != nil { + return fmt.Errorf("获取提交信息失败: %w", err) + } + + lastCommit := strings.TrimSpace(out) + if lastCommit == "" { + return errors.New("没有提交可以撤销") + } + + // Show preview + fmt.Fprintf(req.Stdout, "撤销最后一次提交\n\n") + fmt.Fprintf(req.Stdout, "提交: %s\n", lastCommit) + + if opts.Hard { + fmt.Fprintf(req.Stdout, "模式: 完全丢弃改动(--hard)\n") + } else { + fmt.Fprintf(req.Stdout, "模式: 保留改动到暂存区(--soft)\n") + } + + if !confirmed { + fmt.Fprintf(req.Stdout, "\n") + if opts.Hard { + fmt.Fprintf(req.Stdout, "⚠️ 这将永久丢弃最后一次提交的改动!加 --yes 确认执行。\n") + } else { + fmt.Fprintf(req.Stdout, "撤销后改动会保留在暂存区,加 --yes 执行。\n") + } + return &ExitError{Code: 2, Err: errors.New("需要确认")} + } + + // Execute undo + resetArg := "--soft" + if opts.Hard { + resetArg = "--hard" + } + + if _, stderr, err := runner.Run(ctx, "reset", resetArg, "HEAD~1"); err != nil { + return fmt.Errorf("撤销失败: %w\n%s", err, stderr) + } + + if opts.Hard { + fmt.Fprintf(req.Stdout, "\n✓ 已撤销提交并丢弃改动\n") + } else { + fmt.Fprintf(req.Stdout, "\n✓ 已撤销提交,改动保留在暂存区\n") + } + return nil +} + +func parseGitUndoArgs(args []string) (gitUndoOptions, bool, error) { + var opts gitUndoOptions + var confirmed bool + + for _, arg := range args { + switch arg { + case "--yes", "-y": + confirmed = true + case "--hard": + opts.Hard = true + default: + if strings.HasPrefix(arg, "-") { + return opts, false, fmt.Errorf("未知选项 %q", arg) + } + } + } + + return opts, confirmed, nil +}