penguin/flatgit

flatgit showcase

commit 78dc2586b9103f6ef0cf522cfa784fb75940aa75

author斟酌 鵬兄 <tgckpg@gmail.com>
date2026-06-18T23:03:48Z
subjectdaemon to support --v=2
commit 78dc2586b9103f6ef0cf522cfa784fb75940aa75
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date:   2026-06-18T23:03:48Z

    daemon to support --v=2
---
 Makefile                            |  2 +-
 cmd/flatgit/main.go                 | 35 ++++++++++++++++++++-----
 internal/buildinfo/buildinfo_gen.go |  4 +--
 internal/gitcmd/gitcmd.go           | 51 ++++++++++++++++++++++++++++++++++---
 4 files changed, 78 insertions(+), 14 deletions(-)

diff --git a/Makefile b/Makefile
index 213aaf3..ebcb615 100644
--- a/Makefile
+++ b/Makefile
@@ -60,7 +60,7 @@ fmt:
 	gofmt -w ./cmd ./internal
 
 run: build
-	$(BIN_DIR)/flatgit daemon -c examples/tinyproxy.json
+	$(BIN_DIR)/flatgit daemon -c examples/tinyproxy.json --v=2
 
 vet:
 	go vet ./...
diff --git a/cmd/flatgit/main.go b/cmd/flatgit/main.go
index d0b141d..b290789 100644
--- a/cmd/flatgit/main.go
+++ b/cmd/flatgit/main.go
@@ -22,14 +22,33 @@ import (
 )
 
 func main() {
-	log := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{}))
-	if err := run(log, os.Args); err != nil {
+	var level slog.LevelVar
+	level.Set(slog.LevelInfo)
+
+	log := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
+		Level: &level,
+	}))
+
+	if err := run(log, &level, os.Args); err != nil {
 		log.Error("flatgit failed", "err", err)
 		os.Exit(1)
 	}
 }
 
-func run(log *slog.Logger, args []string) error {
+func applyVerbosity(level *slog.LevelVar, v int) {
+	if v >= 1 {
+		level.Set(slog.LevelDebug)
+		return
+	}
+
+	level.Set(slog.LevelInfo)
+}
+
+func addCommonFlags(fs *flag.FlagSet) *int {
+	return fs.Int("v", 0, "verbosity level")
+}
+
+func run(log *slog.Logger, level *slog.LevelVar, args []string) error {
 	if len(args) < 2 {
 		usage(args[0])
 		return errors.New("missing command")
@@ -41,7 +60,7 @@ func run(log *slog.Logger, args []string) error {
 	case "serve":
 		return serveCmd(log, args[2:])
 	case "daemon":
-		return daemonCmd(log, args[2:])
+		return daemonCmd(log, level, args[2:])
 	case "version", "-v", "--version":
 		fmt.Printf("%s (%s)\n", buildinfo.Version, buildinfo.Timestamp)
 		return nil
@@ -99,13 +118,15 @@ func serveCmd(log *slog.Logger, args []string) error {
 	return server.ListenAndServe(ctx, server.Options{Addr: *addr, Root: *root, Logger: log})
 }
 
-func daemonCmd(log *slog.Logger, args []string) error {
+func daemonCmd(log *slog.Logger, level *slog.LevelVar, args []string) error {
 	fs := flag.NewFlagSet("daemon", flag.ExitOnError)
 	cfgPath := fs.String("c", "flatgit.json", "config file")
+	v := addCommonFlags(fs)
 	renderOnStart := fs.Bool("render-on-start", true, "render all repos on startup")
 	if err := fs.Parse(args); err != nil {
 		return err
 	}
+	applyVerbosity(level, *v)
 	cfg, err := config.Load(*cfgPath)
 	if err != nil {
 		return err
@@ -114,7 +135,7 @@ func daemonCmd(log *slog.Logger, args []string) error {
 	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
 	defer stop()
 
-	git := gitcmd.New(cfg.Git.Command)
+	git := gitcmd.New(cfg.Git.Command, log)
 	r := render.New(git, cfg.PublicURL, cfg.Render.MaxCommits)
 	q := jobqueue.New(1024, log)
 	q.Start(ctx, cfg.Render.Workers, func(ctx context.Context, name string) error {
@@ -162,7 +183,7 @@ func repoMatches(repo config.Repo, name string) bool {
 }
 
 func renderConfigured(ctx context.Context, log *slog.Logger, cfg *config.Config, repoName string, fetch bool) error {
-	git := gitcmd.New(cfg.Git.Command)
+	git := gitcmd.New(cfg.Git.Command, log)
 	r := render.New(git, cfg.PublicURL, cfg.Render.MaxCommits)
 
 	matched := false
diff --git a/internal/buildinfo/buildinfo_gen.go b/internal/buildinfo/buildinfo_gen.go
index 89801d4..d079abc 100644
--- a/internal/buildinfo/buildinfo_gen.go
+++ b/internal/buildinfo/buildinfo_gen.go
@@ -2,6 +2,6 @@ package buildinfo
 
 const (
     Version     = "dev"
-    GitRevision = "ae2ca700755df37c186d45857ad98648ad940781"
-    Timestamp   = "20260618.222654"
+    GitRevision = "34bbedf22eec0a64eafd23241d4225cbfe8fc707"
+    Timestamp   = "20260618.230137"
 )
diff --git a/internal/gitcmd/gitcmd.go b/internal/gitcmd/gitcmd.go
index c1beb75..34606d4 100644
--- a/internal/gitcmd/gitcmd.go
+++ b/internal/gitcmd/gitcmd.go
@@ -5,6 +5,7 @@ import (
 	"context"
 	"errors"
 	"fmt"
+	"log/slog"
 	"os"
 	"os/exec"
 	"path/filepath"
@@ -16,13 +17,17 @@ import (
 
 type Runner struct {
 	GitCommand string
+	Logger     *slog.Logger
 }
 
-func New(command string) *Runner {
-	if command == "" {
-		command = "git"
+func New(gitCommand string, logger *slog.Logger) *Runner {
+	if gitCommand == "" {
+		gitCommand = "git"
+	}
+	return &Runner{
+		GitCommand: gitCommand,
+		Logger:     logger,
 	}
-	return &Runner{GitCommand: command}
 }
 
 func (r *Runner) EnsureMirror(ctx context.Context, repo config.Repo, cloneTimeout, fetchTimeout time.Duration) error {
@@ -69,19 +74,57 @@ func (r *Runner) EnsureMirror(ctx context.Context, repo config.Repo, cloneTimeou
 func (r *Runner) Output(ctx context.Context, dir string, args ...string) ([]byte, error) {
 	cmd := exec.CommandContext(ctx, r.GitCommand, args...)
 	cmd.Env = os.Environ()
+
 	if dir != "" {
 		cmd.Dir = dir
 	}
+
+	if r.Logger != nil {
+		r.Logger.Debug("git command started",
+			"dir", cmd.Dir,
+			"cmd", r.GitCommand,
+			"args", args,
+		)
+	}
+
+	start := time.Now()
+
 	var stdout, stderr bytes.Buffer
 	cmd.Stdout = &stdout
 	cmd.Stderr = &stderr
+
 	if err := cmd.Run(); err != nil {
+		dur := time.Since(start)
 		msg := strings.TrimSpace(stderr.String())
 		if msg == "" {
 			msg = err.Error()
 		}
+
+		if r.Logger != nil {
+			r.Logger.Warn("git command failed",
+				"dir", cmd.Dir,
+				"cmd", r.GitCommand,
+				"args", args,
+				"dur", dur,
+				"stdout_bytes", stdout.Len(),
+				"stderr", msg,
+			)
+		}
+
 		return stdout.Bytes(), fmt.Errorf("git %s: %s", strings.Join(args, " "), msg)
 	}
+
+	if r.Logger != nil {
+		r.Logger.Debug("git command finished",
+			"dir", cmd.Dir,
+			"cmd", r.GitCommand,
+			"args", args,
+			"dur", time.Since(start),
+			"stdout_bytes", stdout.Len(),
+			"stderr_bytes", stderr.Len(),
+		)
+	}
+
 	return stdout.Bytes(), nil
 }