From 324682137bf48b920f0a269e9016e7d8efaf26dd Mon Sep 17 00:00:00 2001 From: Sander Maijers <3374183+sanmai-NL@users.noreply.github.com> Date: Mon, 22 Jan 2024 08:37:59 +0100 Subject: [PATCH] feat: add replaces to all template and parse files from stdin (#596) * Prefer repeated `--file` CLI params over splitting * Support adding files from standard input Using a null char as separator, which allows files with e.g. commas or end-of-line characters in their names. * Mark deprecation correctly * Simplify * chore: refactor files overrides from run arguments * chore: add integrity test on files override * chore: add a commend and remove odd spaces * chore: fix the error messages --------- Co-authored-by: Valentin Kiselev --- cmd/run.go | 27 +++++++++++++-- docs/usage.md | 2 +- internal/config/files.go | 4 +-- internal/lefthook/run.go | 31 ++++++++++++++++- internal/lefthook/run/prepare_command.go | 35 ++++++++++++-------- internal/lefthook/run/runner.go | 1 - testdata/files_override.txt | 42 ++++++++++++++++++++++++ 7 files changed, 121 insertions(+), 21 deletions(-) create mode 100644 testdata/files_override.txt diff --git a/cmd/run.go b/cmd/run.go index d8f45f91..bc73829e 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -5,6 +5,7 @@ import ( "github.com/evilmartians/lefthook/internal/config" "github.com/evilmartians/lefthook/internal/lefthook" + "github.com/evilmartians/lefthook/internal/log" ) func newRunCmd(opts *lefthook.Options) *cobra.Command { @@ -57,9 +58,31 @@ func newRunCmd(opts *lefthook.Options) *cobra.Command { "run hooks on all files", ) - runCmd.Flags().StringSliceVar(&runArgs.Files, "files", nil, "run on specified files. takes precedence over --all-files") + runCmd.Flags().BoolVar( + &runArgs.FilesFromStdin, "files-from-stdin", false, + "get files from standard input, null- or \\n-separated", + ) + + runCmd.Flags().StringSliceVar( + &runArgs.Files, "files", nil, + "run on specified files, comma-separated", + ) + + runCmd.Flags().StringArrayVar( + &runArgs.Files, "file", nil, + "run on specified file (repeat for multiple files). takes precedence over --all-files", + ) + + runCmd.Flags().StringSliceVar( + &runArgs.RunOnlyCommands, "commands", nil, + "run only specified commands", + ) + + err := runCmd.Flags().MarkDeprecated("files", "use --file flag instead") + if err != nil { + log.Warn("Unexpected error:", err) + } - runCmd.Flags().StringSliceVar(&runArgs.RunOnlyCommands, "commands", nil, "run only specified commands") _ = runCmd.RegisterFlagCompletionFunc("commands", runHookCommandCompletions) return &runCmd diff --git a/docs/usage.md b/docs/usage.md index fcfae0e1..3907ca1f 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -134,7 +134,7 @@ and optionally run either on all files (any `{staged_files}` placeholder acts as ```bash $ lefthook run pre-commit --all-files -$ lefthook run pre-commit --files file1.js,file2.js +$ lefthook run pre-commit --file file1.js --file file2.js ``` (if both are specified, `--all-files` is ignored) diff --git a/internal/config/files.go b/internal/config/files.go index ba9f1da5..82e70c4e 100644 --- a/internal/config/files.go +++ b/internal/config/files.go @@ -6,11 +6,11 @@ const ( SubFiles string = "{files}" SubAllFiles string = "{all_files}" SubStagedFiles string = "{staged_files}" - PushFiles string = "{push_files}" + SubPushFiles string = "{push_files}" ) func isRunnerFilesCompatible(runner string) bool { - if strings.Contains(runner, SubStagedFiles) && strings.Contains(runner, PushFiles) { + if strings.Contains(runner, SubStagedFiles) && strings.Contains(runner, SubPushFiles) { return false } return true diff --git a/internal/lefthook/run.go b/internal/lefthook/run.go index da16fcb6..2b548a7b 100644 --- a/internal/lefthook/run.go +++ b/internal/lefthook/run.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "io" "os" "os/signal" "path/filepath" @@ -24,6 +25,7 @@ const ( type RunArgs struct { NoTTY bool AllFiles bool + FilesFromStdin bool Force bool Files []string RunOnlyCommands []string @@ -110,6 +112,20 @@ Run 'lefthook install' manually.`, startTime := time.Now() resultChan := make(chan run.Result, len(hook.Commands)+len(hook.Scripts)) + if args.FilesFromStdin { + paths, err := io.ReadAll(os.Stdin) + if err != nil { + return fmt.Errorf("failed to read the files from standard input: %w", err) + } + args.Files = append(args.Files, parseFilesFromString(string(paths))...) + } else if args.AllFiles { + files, err := l.repo.AllFiles() + if err != nil { + return fmt.Errorf("failed to get all files: %w", err) + } + args.Files = append(args.Files, files...) + } + runner := run.NewRunner( run.Options{ Repo: l.repo, @@ -119,7 +135,6 @@ Run 'lefthook install' manually.`, ResultChan: resultChan, SkipSettings: logSettings, DisableTTY: cfg.NoTTY || args.NoTTY, - AllFiles: args.AllFiles, Files: args.Files, Force: args.Force, RunOnlyCommands: args.RunOnlyCommands, @@ -276,3 +291,17 @@ func (l *Lefthook) configHookCommandCompletions(hookName string) []string { return commands } } + +// parseFilesFromString parses both `\0`- and `\n`-separated files. +func parseFilesFromString(paths string) []string { + var result []string + start := 0 + for i, c := range paths { + if c == 0 || c == '\n' { + result = append(result, paths[start:i]) + start = i + 1 + } + } + result = append(result, paths[start:]) + return result +} diff --git a/internal/lefthook/run/prepare_command.go b/internal/lefthook/run/prepare_command.go index c984a624..2c6bef7d 100644 --- a/internal/lefthook/run/prepare_command.go +++ b/internal/lefthook/run/prepare_command.go @@ -74,20 +74,20 @@ func (r *Runner) buildRun(command *config.Command) (*run, error, error) { } var stagedFiles func() ([]string, error) - switch { - case len(r.Files) > 0: + var pushFiles func() ([]string, error) + var allFiles func() ([]string, error) + var cmdFiles func() ([]string, error) + + if len(r.Files) > 0 { stagedFiles = func() ([]string, error) { return r.Files, nil } - case r.AllFiles: - stagedFiles = r.Repo.AllFiles - default: + pushFiles = stagedFiles + allFiles = stagedFiles + cmdFiles = stagedFiles + } else { stagedFiles = r.Repo.StagedFiles - } - - filesFns := map[string]func() ([]string, error){ - config.SubStagedFiles: stagedFiles, - config.PushFiles: r.Repo.PushFiles, - config.SubAllFiles: r.Repo.AllFiles, - config.SubFiles: func() ([]string, error) { + pushFiles = r.Repo.PushFiles + allFiles = r.Repo.AllFiles + cmdFiles = func() ([]string, error) { var cmd []string if runtime.GOOS == "windows" { cmd = strings.Split(filesCmd, " ") @@ -95,7 +95,14 @@ func (r *Runner) buildRun(command *config.Command) (*run, error, error) { cmd = []string{"sh", "-c", filesCmd} } return r.Repo.FilesByCommand(cmd) - }, + } + } + + filesFns := map[string]func() ([]string, error){ + config.SubStagedFiles: stagedFiles, + config.SubPushFiles: pushFiles, + config.SubAllFiles: allFiles, + config.SubFiles: cmdFiles, } templates := make(map[string]*template) @@ -167,7 +174,7 @@ func (r *Runner) buildRun(command *config.Command) (*run, error, error) { } if config.HookUsesPushFiles(r.HookName) { - ok, err := canSkipCommand(command, templates[config.PushFiles], r.Repo.PushFiles) + ok, err := canSkipCommand(command, templates[config.SubPushFiles], r.Repo.PushFiles) if err != nil { return nil, err, nil } diff --git a/internal/lefthook/run/runner.go b/internal/lefthook/run/runner.go index 3ccd3c52..17fada29 100644 --- a/internal/lefthook/run/runner.go +++ b/internal/lefthook/run/runner.go @@ -45,7 +45,6 @@ type Options struct { ResultChan chan Result SkipSettings log.SkipSettings DisableTTY bool - AllFiles bool Force bool Files []string RunOnlyCommands []string diff --git a/testdata/files_override.txt b/testdata/files_override.txt new file mode 100644 index 00000000..d5032dcd --- /dev/null +++ b/testdata/files_override.txt @@ -0,0 +1,42 @@ +exec git init +exec lefthook install +exec git add -A + +exec lefthook run echo +stdout 'a-file\.js' + +exec lefthook run echo --all-files +stdout 'a-file\.js b_file\.go c,file\.rb' + +exec lefthook run echo --file a-file.js --file ghost.file +stdout 'a-file\.js ghost\.file' + +stdin b_file.go +exec lefthook run echo --files-from-stdin +stdout 'b_file\.go c,file\.rb' + +stdin b_file.go +exec lefthook run echo --files-from-stdin --file ghost.file +stdout 'ghost\.file b_file\.go c,file\.rb' + +-- lefthook.yml -- +skip_output: + - meta + - execution_info + - summary + +echo: + commands: + echo: + files: echo a-file.js + run: echo "{files}" + +-- a-file.js -- +a-file.js + +-- b_file.go -- +b_file.go +c,file.rb + +-- c,file.rb -- +c,file.rb