Add the Cobra-based command surface and the flag/env/config-file resolution layer that all commands share. Configuration (internal/config): - Load() resolves settings with the documented precedence flags > env > config file > defaults, via viper: GITFLOW_-prefixed env vars with dot-to-underscore mapping, plus ~/.gitflow.yaml (or $GITFLOW_CONFIG) - RegisterFlags/NewFlagSet own the flag definitions so every command and the tests share a single source of truth - Config/Validate/Dump cover dir, interval, format (table/json/compact), exclude globs, max depth, worker count, and the AI block (enabled, provider, model, api_key_env, base_url); ConfigFile records the loaded path; Dump renders the effective config as human-readable YAML with the interval as a duration string App orchestration (internal/app): - New() validates the configuration at the boundary (fail fast) - ScanOnce() runs discovery then a concurrent status scan, warning on stderr and continuing when discovery is only partially successful (e.g. permission-denied subtrees), and bundles everything into a ScanResult CLI (cmd/gitflow): - root command with scan / config / version subcommands - scan: resolves config, prompts for the parent directory when stdin is a TTY and --dir was not given (per the README), runs a single pass, and renders the result — interim plain/JSON output until phase 3 lands the presenter package - config: prints the effective configuration - version: prints the build version (ldflags-injectable) - signalContext() wires SIGINT/SIGTERM into a cancellable context for graceful shutdown Testing: - config: defaults, flag overrides, env overrides, flag-beats-env precedence, config file loading (including duration and slice values), GITFLOW_CONFIG path override, validation failures, and Dump output - app: config validation on New, end-to-end ScanOnce over a real temp repo, and missing-directory errors Verified: go build, go vet, go test -race, gofmt clean; manual smoke of `gitflow version`, `gitflow config`, and `gitflow scan -d <dir>` against a scratch directory with a dirty repo.
53 lines
1.3 KiB
Go
53 lines
1.3 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"gitea.oblak.solutions/dimitar/gitFlow/internal/version"
|
|
)
|
|
|
|
// newRootCmd builds the top-level gitflow command.
|
|
func newRootCmd() *cobra.Command {
|
|
root := &cobra.Command{
|
|
Use: "gitflow",
|
|
Short: "Scan Git repositories and get suggested next steps",
|
|
Long: `gitflow discovers every Git repository under a parent directory,
|
|
scans each one's status, and presents the findings. It can rescan on a
|
|
schedule and, when enabled, uses an AI agent to suggest next actions for
|
|
repositories that need attention.`,
|
|
SilenceUsage: true,
|
|
SilenceErrors: true,
|
|
}
|
|
root.AddCommand(
|
|
newScanCmd(),
|
|
newConfigCmd(),
|
|
newVersionCmd(),
|
|
)
|
|
return root
|
|
}
|
|
|
|
// signalContext returns a context that is cancelled on SIGINT/SIGTERM so
|
|
// scans and watch loops shut down gracefully.
|
|
func signalContext() (context.Context, context.CancelFunc) {
|
|
return signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
|
}
|
|
|
|
// newVersionCmd prints the build version.
|
|
func newVersionCmd() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "version",
|
|
Short: "Print version information",
|
|
Args: cobra.NoArgs,
|
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
|
fmt.Printf("gitflow %s\n", version.Version)
|
|
return nil
|
|
},
|
|
}
|
|
}
|