diff --git a/.goreleaser.yml b/.goreleaser.yml index 87abb00..7db4e42 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -18,7 +18,7 @@ builds: flags: - -trimpath ldflags: - - -s -w + - -s -w -X github.com/satococoa/git-worktreeinclude/internal/cli.Version={{ .Version }} archives: - id: default diff --git a/README.md b/README.md index de08bb6..05c25a7 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,15 @@ Example: ## Commands +### `git-worktreeinclude --version` + +Print the installed version. + +```sh +git-worktreeinclude --version +git-worktreeinclude -v +``` + ### `git-worktreeinclude apply` Uses the current worktree as target and copies from source worktree. diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 0b796ae..339a396 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -23,6 +23,9 @@ type App struct { engine *engine.Engine } +// Version is injected at build time via ldflags. Default is for local builds. +var Version = "dev" + func New(stdout, stderr io.Writer) *App { return &App{ stdout: stdout, @@ -51,6 +54,7 @@ func (a *App) newRootCommand() *ucli.Command { return &ucli.Command{ Name: "git-worktreeinclude", Usage: "apply ignored files listed in .worktreeinclude between Git worktrees", + Version: Version, Writer: a.stdout, ErrWriter: a.stderr, OnUsageError: a.onUsageError, diff --git a/internal/cli/cli_integration_test.go b/internal/cli/cli_integration_test.go index 0c7d195..444ccab 100644 --- a/internal/cli/cli_integration_test.go +++ b/internal/cli/cli_integration_test.go @@ -403,6 +403,29 @@ func TestRootCommandHelpAndNoImplicitApply(t *testing.T) { } } +func TestRootVersionFlags(t *testing.T) { + fx := setupFixture(t) + prefix := "git-worktreeinclude version " + + for _, args := range [][]string{{"--version"}, {"-v"}} { + stdout, stderr, code := runCmd(t, fx.wt, nil, testBinary, args...) + if code != 0 { + t.Fatalf("version command failed for %v: code=%d stderr=%s", args, code, stderr) + } + if strings.TrimSpace(stderr) != "" { + t.Fatalf("expected empty stderr for %v, got %q", args, stderr) + } + + line := strings.TrimSpace(stdout) + if !strings.HasPrefix(line, prefix) { + t.Fatalf("unexpected version output for %v: %q", args, line) + } + if strings.TrimSpace(strings.TrimPrefix(line, prefix)) == "" { + t.Fatalf("version value is empty for %v: %q", args, line) + } + } +} + func TestUnknownSubcommandAtRoot(t *testing.T) { fx := setupFixture(t) diff --git a/internal/cli/cli_unit_test.go b/internal/cli/cli_unit_test.go index 7502161..585b940 100644 --- a/internal/cli/cli_unit_test.go +++ b/internal/cli/cli_unit_test.go @@ -44,6 +44,44 @@ func TestRunRootHelp(t *testing.T) { } } +func TestRunRootVersion(t *testing.T) { + oldVersion := Version + Version = "test-version" + defer func() { + Version = oldVersion + }() + + var stdout bytes.Buffer + var stderr bytes.Buffer + app := New(&stdout, &stderr) + + code := app.Run([]string{"--version"}) + if code != exitcode.OK { + t.Fatalf("Run returned %d, want %d", code, exitcode.OK) + } + if strings.TrimSpace(stderr.String()) != "" { + t.Fatalf("stderr should be empty: %q", stderr.String()) + } + if got := strings.TrimSpace(stdout.String()); got != "git-worktreeinclude version test-version" { + t.Fatalf("stdout = %q, want %q", got, "git-worktreeinclude version test-version") + } + + stdout.Reset() + stderr.Reset() + app = New(&stdout, &stderr) + + code = app.Run([]string{"-v"}) + if code != exitcode.OK { + t.Fatalf("Run returned %d for -v, want %d", code, exitcode.OK) + } + if strings.TrimSpace(stderr.String()) != "" { + t.Fatalf("stderr should be empty for -v: %q", stderr.String()) + } + if got := strings.TrimSpace(stdout.String()); got != "git-worktreeinclude version test-version" { + t.Fatalf("stdout for -v = %q, want %q", got, "git-worktreeinclude version test-version") + } +} + func TestRunApplyRejectsQuietVerbose(t *testing.T) { var stdout bytes.Buffer var stderr bytes.Buffer