diff --git a/README.md b/README.md index 6ebcd7d..5f7c5e0 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,30 @@ git add . lazycommit commit | fzf --prompt='Pick commit> ' | xargs -r -I {} git commit -m "{}" ``` +Use gc-style parameters: + +```bash +# stage all tracked, deleted, and untracked files first, then generate 3 suggestions in Spanish with emoji +lazycommit commit --stage-all -g 3 -l Spanish -e + +# emoji output is normalized even when the upstream model response is inconsistent + +# force opencode provider/model for one run +lazycommit commit -p opencode \ + -m opencode/minimax-m2.5-free + +# print only first generated message (for scripting) +lazycommit commit -o + +# debug mode (prints provider/model/diff diagnostics) +lazycommit commit -d + +# stage all files first, then generate 3 suggestions with emoji +lazycommit commit --stage-all -g 3 -e + + +``` + Generate PR titles against `main` branch: ```bash diff --git a/cmd/commit.go b/cmd/commit.go index 8b13c4f..0a38c15 100644 --- a/cmd/commit.go +++ b/cmd/commit.go @@ -4,6 +4,8 @@ import ( "context" "fmt" "os" + "regexp" + "strings" "github.com/m7medvision/lazycommit/internal/config" "github.com/m7medvision/lazycommit/internal/git" @@ -19,13 +21,63 @@ type CommitProvider interface { func init() { RootCmd.AddCommand(commitCmd) + + commitCmd.Flags().StringVarP(&commitProviderFlag, "provider", "p", "", "Provider override: opencode, openai, copilot, anthropic, gemini") + commitCmd.Flags().StringVarP(&commitModelFlag, "model", "m", "", "Model override for selected provider") + commitCmd.Flags().IntVarP(&commitGenerateFlag, "generate", "g", 0, "Number of commit message suggestions to generate") + commitCmd.Flags().StringVarP(&commitLanguageFlag, "lang", "l", "", "Language override for generated commit messages") + commitCmd.Flags().BoolVarP(&commitEmojiFlag, "emoji", "e", false, "Prefix generated commit messages with gitmoji") + commitCmd.Flags().BoolVarP(&commitMessageOnlyFlag, "message-only", "o", false, "Print only the first generated message") + commitCmd.Flags().BoolVar(&commitStageAllFlag, "stage-all", false, "Stage all tracked, deleted, and untracked changes before generating commit messages") + commitCmd.Flags().BoolVarP(&commitSilentEmptyFlag, "silent-empty", "n", false, "Stay silent when there are no staged changes") + commitCmd.Flags().BoolVarP(&commitDebugFlag, "debug", "d", false, "Show debug diagnostics") } +var ( + commitProviderFlag string + commitModelFlag string + commitGenerateFlag int + commitLanguageFlag string + commitEmojiFlag bool + commitMessageOnlyFlag bool + commitStageAllFlag bool + commitSilentEmptyFlag bool + commitDebugFlag bool +) + +var conventionalTypePattern = regexp.MustCompile(`^([a-z]+)(\([^)]+\))?:\s+`) + var commitCmd = &cobra.Command{ Use: "commit", Short: "Generate commit message suggestions", - Long: `Analyzes your staged changes and generates a list of 10 conventional commit message suggestions.`, + Long: `Analyzes your staged changes and generates conventional commit message suggestions.`, + Example: ` lazycommit commit + lazycommit commit --stage-all + lazycommit commit -p opencode -m opencode/minimax-m2.5-free + lazycommit commit -g 3 -l Spanish + lazycommit commit -o`, Run: func(cmd *cobra.Command, args []string) { + if commitStageAllFlag { + hasChanges, err := git.HasChanges() + if err != nil { + fmt.Fprintf(os.Stderr, "Error checking git status: %v\n", err) + os.Exit(1) + } + if !hasChanges { + if !commitSilentEmptyFlag { + fmt.Println("No changes to stage.") + } + return + } + if err := git.StageAll(); err != nil { + fmt.Fprintf(os.Stderr, "Error staging changes: %v\n", err) + os.Exit(1) + } + if commitDebugFlag { + fmt.Fprintln(os.Stderr, "debug: staged all changes with git add --all") + } + } + diff, err := git.GetStagedDiff() if err != nil { fmt.Fprintf(os.Stderr, "Error getting staged diff: %v\n", err) @@ -33,19 +85,44 @@ var commitCmd = &cobra.Command{ } if diff == "" { - fmt.Println("No staged changes to commit.") + if !commitSilentEmptyFlag { + fmt.Println("No staged changes to commit.") + } + if commitDebugFlag { + fmt.Fprintln(os.Stderr, "debug: staged diff is empty") + } return } + providerName := strings.TrimSpace(config.GetProvider()) + if strings.TrimSpace(commitProviderFlag) != "" { + providerName = strings.TrimSpace(commitProviderFlag) + } + + if providerName == "" { + fmt.Fprintln(os.Stderr, "Provider is empty. Set one with 'lazycommit config set' or use --provider.") + os.Exit(1) + } + if !isSupportedCommitProvider(providerName) { + fmt.Fprintf(os.Stderr, "Unsupported provider: %s\n", providerName) + os.Exit(1) + } + var aiProvider CommitProvider - providerName := config.GetProvider() + generateCount := commitGenerateFlag + if generateCount <= 0 { + generateCount = config.GetNumSuggestionsForProvider(providerName) + } + if generateCount <= 0 { + generateCount = 10 + } // API keys are not needed for CLI-backed providers. var apiKey string if providerName != "anthropic" && providerName != "gemini" && providerName != "opencode" { var err error - apiKey, err = config.GetAPIKey() + apiKey, err = config.GetAPIKeyForProvider(providerName) if err != nil { fmt.Fprintf(os.Stderr, "Error getting API key: %v\n", err) os.Exit(1) @@ -53,48 +130,48 @@ var commitCmd = &cobra.Command{ } var model string - if providerName == "copilot" || providerName == "openai" || providerName == "anthropic" || providerName == "gemini" || providerName == "opencode" { + if strings.TrimSpace(commitModelFlag) != "" { + model = strings.TrimSpace(commitModelFlag) + } else if providerName == "copilot" || providerName == "openai" || providerName == "anthropic" || providerName == "gemini" || providerName == "opencode" { var err error - model, err = config.GetModel() + model, err = config.GetModelForProvider(providerName) if err != nil { fmt.Fprintf(os.Stderr, "Error getting model: %v\n", err) os.Exit(1) } } - endpoint, err := config.GetEndpoint() + endpoint, err := config.GetEndpointForProvider(providerName) if err != nil { fmt.Fprintf(os.Stderr, "Error getting endpoint: %v\n", err) os.Exit(1) } + if commitDebugFlag { + fmt.Fprintf(os.Stderr, "debug: provider=%s model=%s generate=%d lang=%q emoji=%t message_only=%t silent_empty=%t\n", + providerName, model, generateCount, commitLanguageFlag, commitEmojiFlag, commitMessageOnlyFlag, commitSilentEmptyFlag) + fmt.Fprintf(os.Stderr, "debug: stage_all=%t\n", commitStageAllFlag) + fmt.Fprintf(os.Stderr, "debug: diff_bytes=%d endpoint=%q\n", len(diff), endpoint) + } + + provider.SetRuntimeCommitPromptOptions(provider.CommitPromptOptions{ + Generate: generateCount, + Language: strings.TrimSpace(commitLanguageFlag), + Emoji: commitEmojiFlag, + }) + defer provider.ResetRuntimeCommitPromptOptions() + switch providerName { case "copilot": aiProvider = provider.NewCopilotProviderWithModel(apiKey, model, endpoint) case "openai": aiProvider = provider.NewOpenAIProvider(apiKey, model, endpoint) case "anthropic": - // Get num_suggestions from config (default to 10) - numSuggestions := config.GetNumSuggestions() - if numSuggestions <= 0 { - numSuggestions = 10 - } - aiProvider = provider.NewAnthropicProvider(model, numSuggestions) + aiProvider = provider.NewAnthropicProvider(model, generateCount) case "gemini": - numSuggestions := config.GetNumSuggestions() - if numSuggestions <= 0 { - numSuggestions = 10 - } - aiProvider = provider.NewGeminiProvider(model, numSuggestions) + aiProvider = provider.NewGeminiProvider(model, generateCount) case "opencode": - numSuggestions := config.GetNumSuggestions() - if numSuggestions <= 0 { - numSuggestions = 10 - } - aiProvider = provider.NewOpencodeProvider(model, config.GetFallbackModels(), numSuggestions) - default: - // Default to copilot if provider is not set or unknown - aiProvider = provider.NewCopilotProvider(apiKey, endpoint) + aiProvider = provider.NewOpencodeProvider(model, config.GetFallbackModelsForProvider(providerName), generateCount) } commitMessages, err := aiProvider.GenerateCommitMessages(context.Background(), diff) @@ -108,8 +185,96 @@ var commitCmd = &cobra.Command{ return } + if generateCount > 0 && len(commitMessages) > generateCount { + commitMessages = commitMessages[:generateCount] + } + + commitMessages = applyOutputOverrides(commitMessages, commitEmojiFlag) + + if commitDebugFlag { + fmt.Fprintf(os.Stderr, "debug: generated_messages=%d\n", len(commitMessages)) + } + + if commitMessageOnlyFlag { + fmt.Println(commitMessages[0]) + return + } + for _, msg := range commitMessages { fmt.Println(msg) } }, } + +func isSupportedCommitProvider(providerName string) bool { + switch providerName { + case "copilot", "openai", "anthropic", "gemini", "opencode": + return true + default: + return false + } +} + +func applyOutputOverrides(messages []string, addEmoji bool) []string { + out := make([]string, 0, len(messages)) + for _, msg := range messages { + updated := msg + if addEmoji { + updated = ensureGitmojiPrefix(updated) + } + out = append(out, updated) + } + return out +} + +func ensureGitmojiPrefix(msg string) string { + trimmed := strings.TrimSpace(msg) + if trimmed == "" { + return msg + } + if hasLeadingEmoji(trimmed) { + return msg + } + matches := conventionalTypePattern.FindStringSubmatch(trimmed) + if len(matches) < 2 { + return msg + } + emojiByType := map[string]string{ + "feat": "✨", + "fix": "🐛", + "refactor": "♻️", + "perf": "⚡️", + "docs": "📝", + "style": "🎨", + "test": "🧪", + "chore": "🔧", + "ci": "👷", + "build": "📦", + "revert": "⏪️", + "security": "🔒️", + } + if emoji, ok := emojiByType[matches[1]]; ok { + return emoji + " " + trimmed + } + return msg +} + +func hasLeadingEmoji(s string) bool { + for _, r := range strings.TrimSpace(s) { + return isEmojiRune(r) + } + return false +} + +func isEmojiRune(r rune) bool { + switch { + case r >= 0x1F000 && r <= 0x1FAFF: + return true + case r >= 0x2600 && r <= 0x27BF: + return true + case r == 0x00A9 || r == 0x00AE || r == 0x3030: + return true + default: + return false + } +} diff --git a/cmd/commit_test.go b/cmd/commit_test.go new file mode 100644 index 0000000..87c970c --- /dev/null +++ b/cmd/commit_test.go @@ -0,0 +1,38 @@ +package cmd + +import "testing" + +func TestApplyOutputOverrides(t *testing.T) { + in := []string{"feat: add provider and model flags"} + out := applyOutputOverrides(in, true) + + if len(out) != 1 { + t.Fatalf("expected 1 message, got %d", len(out)) + } + if out[0] != "✨ feat: add provider and model flags" { + t.Fatalf("unexpected output: %q", out[0]) + } +} + +func TestEnsureGitmojiPrefix(t *testing.T) { + got := ensureGitmojiPrefix("fix: handle empty staged diff") + want := "🐛 fix: handle empty staged diff" + if got != want { + t.Fatalf("got %q, want %q", got, want) + } +} + +func TestEnsureGitmojiPrefix_NoDoubleEmoji(t *testing.T) { + msg := "✨ feat: add provider override" + if got := ensureGitmojiPrefix(msg); got != msg { + t.Fatalf("emoji should not be duplicated, got %q", got) + } +} + +func TestEnsureGitmojiPrefix_NonASCIIDescription(t *testing.T) { + got := ensureGitmojiPrefix("feat: añade validación") + want := "✨ feat: añade validación" + if got != want { + t.Fatalf("got %q, want %q", got, want) + } +} diff --git a/internal/config/config.go b/internal/config/config.go index a5c2fd5..4fc6d2d 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -92,7 +92,17 @@ func GetActiveProviderConfig() (*ProviderConfig, error) { if cfg == nil { InitConfig() } - providerName := cfg.ActiveProvider + return GetProviderConfig(cfg.ActiveProvider) +} + +func GetProviderConfig(providerName string) (*ProviderConfig, error) { + if cfg == nil { + InitConfig() + } + providerName = strings.TrimSpace(providerName) + if providerName == "" { + return nil, fmt.Errorf("provider is empty") + } providerConfig, ok := cfg.Providers[providerName] if !ok { return nil, fmt.Errorf("provider '%s' not configured", providerName) @@ -104,14 +114,18 @@ func GetAPIKey() (string, error) { if cfg == nil { InitConfig() } + return GetAPIKeyForProvider(cfg.ActiveProvider) +} - providerConfig, err := GetActiveProviderConfig() +func GetAPIKeyForProvider(providerName string) (string, error) { + providerConfig, err := GetProviderConfig(providerName) if err != nil { return "", err } + providerName = strings.TrimSpace(providerName) if providerConfig.APIKey == "" { - return "", fmt.Errorf("API key for provider '%s' is not set", cfg.ActiveProvider) + return "", fmt.Errorf("API key for provider '%s' is not set", providerName) } apiKey := providerConfig.APIKey @@ -121,7 +135,7 @@ func GetAPIKey() (string, error) { envVarName := strings.TrimPrefix(apiKey, "$") envValue := os.Getenv(envVarName) if envValue == "" { - return "", fmt.Errorf("environment variable '%s' for provider '%s' is not set or empty", envVarName, cfg.ActiveProvider) + return "", fmt.Errorf("environment variable '%s' for provider '%s' is not set or empty", envVarName, providerName) } return envValue, nil } @@ -130,21 +144,37 @@ func GetAPIKey() (string, error) { } func GetModel() (string, error) { - providerConfig, err := GetActiveProviderConfig() + if cfg == nil { + InitConfig() + } + return GetModelForProvider(cfg.ActiveProvider) +} + +func GetModelForProvider(providerName string) (string, error) { + providerConfig, err := GetProviderConfig(providerName) if err != nil { return "", err } + providerName = strings.TrimSpace(providerName) if providerConfig.Model == "" { - return "", fmt.Errorf("model for provider '%s' is not set", cfg.ActiveProvider) + return "", fmt.Errorf("model for provider '%s' is not set", providerName) } return providerConfig.Model, nil } func GetEndpoint() (string, error) { - providerConfig, err := GetActiveProviderConfig() + if cfg == nil { + InitConfig() + } + return GetEndpointForProvider(cfg.ActiveProvider) +} + +func GetEndpointForProvider(providerName string) (string, error) { + providerConfig, err := GetProviderConfig(providerName) if err != nil { return "", err } + providerName = strings.TrimSpace(providerName) // If custom endpoint is configured, use it if providerConfig.EndpointURL != "" { @@ -152,7 +182,7 @@ func GetEndpoint() (string, error) { } // Return default endpoints based on provider - switch cfg.ActiveProvider { + switch providerName { case "openai": return "https://api.openai.com/v1", nil case "copilot": @@ -164,7 +194,7 @@ func GetEndpoint() (string, error) { case "opencode": return "", nil // opencode uses CLI, no endpoint needed default: - return "", fmt.Errorf("no default endpoint available for provider '%s'", cfg.ActiveProvider) + return "", fmt.Errorf("no default endpoint available for provider '%s'", providerName) } } @@ -280,7 +310,11 @@ func GetNumSuggestions() int { if cfg == nil { InitConfig() } - providerConfig, err := GetActiveProviderConfig() + return GetNumSuggestionsForProvider(cfg.ActiveProvider) +} + +func GetNumSuggestionsForProvider(providerName string) int { + providerConfig, err := GetProviderConfig(providerName) if err != nil { return 10 // Default to 10 if error } @@ -294,7 +328,11 @@ func GetFallbackModels() []string { if cfg == nil { InitConfig() } - providerConfig, err := GetActiveProviderConfig() + return GetFallbackModelsForProvider(cfg.ActiveProvider) +} + +func GetFallbackModelsForProvider(providerName string) []string { + providerConfig, err := GetProviderConfig(providerName) if err != nil { return nil } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 600a14f..9c818db 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -105,6 +105,75 @@ func TestGetAPIKey_RegularAPIKey(t *testing.T) { } } +func TestProviderSpecificConfigAccessors(t *testing.T) { + cfg = &Config{ + ActiveProvider: "openai", + Providers: map[string]ProviderConfig{ + "openai": { + APIKey: "openai-key", + Model: "openai-model", + EndpointURL: "https://openai.example/v1", + NumSuggestions: 3, + }, + "copilot": { + APIKey: "copilot-key", + Model: "copilot-model", + EndpointURL: "https://copilot.example", + NumSuggestions: 7, + }, + "opencode": { + Model: "opencode-model", + FallbackModels: []string{"fallback-a", "fallback-b"}, + }, + }, + } + t.Cleanup(func() { + cfg = nil + viper.Reset() + }) + + activeKey, err := GetAPIKey() + if err != nil { + t.Fatalf("GetAPIKey returned error: %v", err) + } + if activeKey != "openai-key" { + t.Fatalf("active provider key = %q, want openai-key", activeKey) + } + + copilotKey, err := GetAPIKeyForProvider("copilot") + if err != nil { + t.Fatalf("GetAPIKeyForProvider returned error: %v", err) + } + if copilotKey != "copilot-key" { + t.Fatalf("copilot key = %q, want copilot-key", copilotKey) + } + + copilotModel, err := GetModelForProvider("copilot") + if err != nil { + t.Fatalf("GetModelForProvider returned error: %v", err) + } + if copilotModel != "copilot-model" { + t.Fatalf("copilot model = %q, want copilot-model", copilotModel) + } + + copilotEndpoint, err := GetEndpointForProvider("copilot") + if err != nil { + t.Fatalf("GetEndpointForProvider returned error: %v", err) + } + if copilotEndpoint != "https://copilot.example" { + t.Fatalf("copilot endpoint = %q, want https://copilot.example", copilotEndpoint) + } + + if got := GetNumSuggestionsForProvider("copilot"); got != 7 { + t.Fatalf("copilot suggestions = %d, want 7", got) + } + + fallbacks := GetFallbackModelsForProvider("opencode") + if len(fallbacks) != 2 || fallbacks[0] != "fallback-a" || fallbacks[1] != "fallback-b" { + t.Fatalf("opencode fallbacks = %#v, want fallback-a/fallback-b", fallbacks) + } +} + func TestGetEndpoint_DefaultEndpoints(t *testing.T) { // Reset configuration for clean test cfg = nil diff --git a/internal/git/git.go b/internal/git/git.go index 0778a05..cf2d410 100644 --- a/internal/git/git.go +++ b/internal/git/git.go @@ -27,6 +27,24 @@ func GetStagedDiff() (string, error) { return out.String(), nil } +func HasChanges() (bool, error) { + cmd := exec.Command("git", "status", "--short") + var out bytes.Buffer + cmd.Stdout = &out + if err := cmd.Run(); err != nil { + return false, fmt.Errorf("error running git status --short: %w", err) + } + return strings.TrimSpace(out.String()) != "", nil +} + +func StageAll() error { + cmd := exec.Command("git", "add", "--all") + if err := cmd.Run(); err != nil { + return fmt.Errorf("error running git add --all: %w", err) + } + return nil +} + // GetDiffAgainstBranch returns the diff against the specified branch. For example "main" when creating a PR. func GetDiffAgainstBranch(branch string) (string, error) { // Check if the branch exists diff --git a/internal/git/git_test.go b/internal/git/git_test.go new file mode 100644 index 0000000..1ef7bfd --- /dev/null +++ b/internal/git/git_test.go @@ -0,0 +1,72 @@ +package git + +import ( + "os" + "os/exec" + "path/filepath" + "testing" +) + +func runGit(t *testing.T, args ...string) { + t.Helper() + cmd := exec.Command("git", args...) + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("git %v failed: %v\n%s", args, err, string(out)) + } +} + +func TestHasChanges(t *testing.T) { + tmp := t.TempDir() + t.Chdir(tmp) + + runGit(t, "init") + runGit(t, "config", "user.email", "test@example.com") + runGit(t, "config", "user.name", "Test User") + + clean, err := HasChanges() + if err != nil { + t.Fatalf("HasChanges returned error: %v", err) + } + if clean { + t.Fatalf("expected clean repo to report no changes") + } + + if err := os.WriteFile(filepath.Join(tmp, "file.txt"), []byte("hello\n"), 0o644); err != nil { + t.Fatalf("write file: %v", err) + } + + dirty, err := HasChanges() + if err != nil { + t.Fatalf("HasChanges returned error: %v", err) + } + if !dirty { + t.Fatalf("expected repo with untracked file to report changes") + } +} + +func TestStageAll(t *testing.T) { + tmp := t.TempDir() + t.Chdir(tmp) + + runGit(t, "init") + runGit(t, "config", "user.email", "test@example.com") + runGit(t, "config", "user.name", "Test User") + + if err := os.WriteFile(filepath.Join(tmp, "new.txt"), []byte("new\n"), 0o644); err != nil { + t.Fatalf("write new file: %v", err) + } + + if err := StageAll(); err != nil { + t.Fatalf("StageAll returned error: %v", err) + } + + cmd := exec.Command("git", "diff", "--cached", "--name-only") + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("git diff --cached --name-only failed: %v\n%s", err, string(out)) + } + if string(out) != "new.txt\n" { + t.Fatalf("expected staged file new.txt, got %q", string(out)) + } +} diff --git a/internal/provider/prompts.go b/internal/provider/prompts.go index e8f267a..7992639 100644 --- a/internal/provider/prompts.go +++ b/internal/provider/prompts.go @@ -1,10 +1,43 @@ package provider -import "github.com/m7medvision/lazycommit/internal/config" +import ( + "fmt" + "regexp" + "strings" + + "github.com/m7medvision/lazycommit/internal/config" +) + +var languageInstructionPattern = regexp.MustCompile(`\n\nIMPORTANT: Generate all content in .*\.`) // GetCommitMessagePrompt returns the standardized prompt for generating commit messages func GetCommitMessagePrompt(diff string) string { - return config.GetCommitMessagePromptFromConfig(diff) + basePrompt := config.GetCommitMessagePromptFromConfig(diff) + opts := GetRuntimeCommitPromptOptions() + if strings.TrimSpace(opts.Language) != "" { + basePrompt = languageInstructionPattern.ReplaceAllString(basePrompt, "") + } + + var extraRules []string + if opts.Generate > 0 { + extraRules = append(extraRules, fmt.Sprintf("IMPORTANT: Generate exactly %d commit messages. Put each message on its own line.", opts.Generate)) + } + if strings.TrimSpace(opts.Language) != "" { + extraRules = append(extraRules, fmt.Sprintf("IMPORTANT: Generate all content in %s.", strings.TrimSpace(opts.Language))) + } + if opts.Emoji { + extraRules = append(extraRules, `Gitmoji rules: +- Prefix exactly one emoji before commit type. +- Mapping: feat=✨ fix=🐛 refactor=♻️ perf=⚡️ docs=📝 style=🎨 test=🧪 chore=🔧 ci=👷 build=📦 revert=⏪️ security=🔒️. +- Format: (): +- Every output line must start with an emoji and a space.`) + } + + if len(extraRules) == 0 { + return basePrompt + } + + return fmt.Sprintf("%s\n\n%s", basePrompt, strings.Join(extraRules, "\n")) } // GetPRTitlePrompt returns the standardized prompt for generating pull request titles diff --git a/internal/provider/runtime_options.go b/internal/provider/runtime_options.go new file mode 100644 index 0000000..0d2185d --- /dev/null +++ b/internal/provider/runtime_options.go @@ -0,0 +1,30 @@ +package provider + +import "sync" + +type CommitPromptOptions struct { + Generate int + Language string + Emoji bool +} + +var ( + runtimeOptionsMu sync.RWMutex + runtimeOptions CommitPromptOptions +) + +func SetRuntimeCommitPromptOptions(opts CommitPromptOptions) { + runtimeOptionsMu.Lock() + defer runtimeOptionsMu.Unlock() + runtimeOptions = opts +} + +func GetRuntimeCommitPromptOptions() CommitPromptOptions { + runtimeOptionsMu.RLock() + defer runtimeOptionsMu.RUnlock() + return runtimeOptions +} + +func ResetRuntimeCommitPromptOptions() { + SetRuntimeCommitPromptOptions(CommitPromptOptions{}) +} diff --git a/package-lock.json b/package-lock.json index 3a323f0..d790b2e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2165,9 +2165,19 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "peer": true, "dependencies": {