Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.cache/
33 changes: 20 additions & 13 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
SHELL := /bin/sh

GOFILES := $(shell git ls-files '*.go')
GOFILES := $(shell git ls-files -- '*.go' | while IFS= read -r f; do [ -f "$$f" ] && printf '%s\n' "$$f"; done)
GO_TEST_CACHE_DIR := $(CURDIR)/.cache/go-build
GO_TEST_TMP_DIR := $(CURDIR)/.cache/go-tmp
GO_BIN_DIR := $(CURDIR)/.cache/bin
GO_RUN_ENV = GOCACHE="$(GO_TEST_CACHE_DIR)" GOTMPDIR="$(GO_TEST_TMP_DIR)"
GOLANGCI_LINT_VERSION := v2.10.1
GOLANGCI_LINT := $(GO_BIN_DIR)/golangci-lint

.PHONY: fmt check-fmt vet lint test test-race ci

Expand All @@ -20,21 +26,22 @@ check-fmt:
fi

vet:
go vet ./...

lint:
@if ! command -v golangci-lint >/dev/null 2>&1; then \
echo "golangci-lint is not installed."; \
echo "Install with:"; \
echo " go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.10.1"; \
exit 1; \
fi
golangci-lint run ./...
@mkdir -p "$(GO_TEST_CACHE_DIR)" "$(GO_TEST_TMP_DIR)"
$(GO_RUN_ENV) go vet ./...

$(GOLANGCI_LINT):
@mkdir -p "$(GO_TEST_CACHE_DIR)" "$(GO_TEST_TMP_DIR)" "$(GO_BIN_DIR)"
GOBIN="$(GO_BIN_DIR)" $(GO_RUN_ENV) go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION)

lint: $(GOLANGCI_LINT)
$(GO_RUN_ENV) $(GOLANGCI_LINT) run

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The $(GOLANGCI_LINT) target is just the binary path, so if GOLANGCI_LINT_VERSION changes later, make lint may keep using the previously installed binary because the file already exists and the rule won’t re-run. Consider versioning the installed path (e.g., include the version in the filename) or writing a small stamp file keyed by $(GOLANGCI_LINT_VERSION) so bumps reliably trigger a reinstall.

Copilot uses AI. Check for mistakes.

test:
go test ./...
@mkdir -p "$(GO_TEST_CACHE_DIR)" "$(GO_TEST_TMP_DIR)"
GOCACHE="$(GO_TEST_CACHE_DIR)" GOTMPDIR="$(GO_TEST_TMP_DIR)" go test ./...

test-race:
go test -race ./...
@mkdir -p "$(GO_TEST_CACHE_DIR)" "$(GO_TEST_TMP_DIR)"
GOCACHE="$(GO_TEST_CACHE_DIR)" GOTMPDIR="$(GO_TEST_TMP_DIR)" go test -race ./...

ci: check-fmt vet lint test test-race
27 changes: 10 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,22 +109,6 @@ Shows:
- no-op reason when include file is missing in source
- matched / copy planned / conflicts / missing source / skipped same / errors

### `git-worktreeinclude hook path`

Prints the hooks directory path while respecting `core.hooksPath`.

```sh
git-worktreeinclude hook path [--absolute]
```

### `git-worktreeinclude hook print post-checkout`

Prints the recommended `post-checkout` hook snippet.

```sh
git-worktreeinclude hook print post-checkout
```

## JSON output

`apply --json` emits a single JSON object to stdout.
Expand Down Expand Up @@ -173,7 +157,15 @@ This project does not auto-install hooks. Use manual setup.

```sh
mkdir -p .githooks
git-worktreeinclude hook print post-checkout > .githooks/post-checkout
cat > .githooks/post-checkout <<'EOF'
#!/bin/sh
set -eu

old="$1"
if [ "$old" = "0000000000000000000000000000000000000000" ]; then
git worktreeinclude apply --quiet || true
fi
EOF
chmod +x .githooks/post-checkout
git config core.hooksPath .githooks
```
Expand Down Expand Up @@ -233,6 +225,7 @@ make ci

CI runs on pull requests and pushes to `main` via GitHub Actions.
`golangci-lint` is used with its default configuration (no `.golangci.yml`).
`make lint` installs a pinned `golangci-lint` binary into `.cache/bin` on first run, so the first run needs network access to fetch the tool.

## License

Expand Down
76 changes: 0 additions & 76 deletions internal/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,12 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"strings"

ucli "github.com/urfave/cli/v3"

"github.com/satococoa/git-worktreeinclude/internal/engine"
"github.com/satococoa/git-worktreeinclude/internal/exitcode"
"github.com/satococoa/git-worktreeinclude/internal/hooks"
)

type App struct {
Expand Down Expand Up @@ -62,7 +60,6 @@ func (a *App) newRootCommand() *ucli.Command {
Commands: []*ucli.Command{
a.newApplyCommand(),
a.newDoctorCommand(),
a.newHookCommand(),
},
}
}
Expand Down Expand Up @@ -260,75 +257,6 @@ func (a *App) runDoctor(ctx context.Context, cmd *ucli.Command) error {
return nil
}

func (a *App) newHookCommand() *ucli.Command {
return &ucli.Command{
Name: "hook",
Usage: "hook helpers",
OnUsageError: a.onUsageError,
Action: func(ctx context.Context, cmd *ucli.Command) error {
if cmd.Args().Len() == 0 {
return a.onUsageError(ctx, cmd, errors.New("hook subcommand is required"), true)
}
name := cmd.Args().First()
if cmd.Command(name) == nil {
return a.onUsageError(ctx, cmd, fmt.Errorf("unknown hook subcommand: %s", name), true)
}
return nil
},
Commands: []*ucli.Command{
{
Name: "path",
Usage: "print hooks path",
OnUsageError: a.onUsageError,
Flags: []ucli.Flag{
&ucli.BoolFlag{Name: "absolute", Usage: "print absolute hooks path"},
},
Action: a.runHookPath,
},
{
Name: "print",
Usage: "print hook snippet",
ArgsUsage: "post-checkout",
OnUsageError: a.onUsageError,
Action: a.runHookPrint,
},
},
}
}

func (a *App) runHookPath(ctx context.Context, cmd *ucli.Command) error {
if cmd.Args().Len() != 0 {
return a.onUsageError(ctx, cmd, errors.New("hook path does not accept positional arguments"), true)
}

wd, err := currentWorkdir()
if err != nil {
return ucli.Exit(err.Error(), exitcode.Env)
}

p, err := a.engine.HookPath(ctx, wd, cmd.Bool("absolute"))
if err != nil {
return ucli.Exit(err, codedOrDefault(err, exitcode.Internal))
}

writeln(a.stdout, filepath.ToSlash(p))
return nil
}

func (a *App) runHookPrint(ctx context.Context, cmd *ucli.Command) error {
if cmd.Args().Len() != 1 {
return a.onUsageError(ctx, cmd, errors.New("hook print requires exactly one argument: post-checkout"), true)
}

snippet, err := hooks.PrintSnippet(cmd.Args().First())
if err != nil {
return ucli.Exit(err.Error(), exitcode.Args)
}

write(a.stdout, snippet)
return nil
}

func (a *App) handleExitError(_ context.Context, _ *ucli.Command, err error) {
var exitErr ucli.ExitCoder
if !errors.As(err, &exitErr) {
Expand Down Expand Up @@ -438,10 +366,6 @@ func currentWorkdir() (string, error) {
return wd, nil
}

func write(w io.Writer, s string) {
_, _ = fmt.Fprint(w, s)
}

func writeln(w io.Writer, a ...any) {
_, _ = fmt.Fprintln(w, a...)
}
Expand Down
Loading
Loading