Skip to content

Commit

Permalink
feat: add replaces to all template and parse files from stdin (#596)
Browse files Browse the repository at this point in the history
* 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 <mrexox@evilmartians.com>
  • Loading branch information
sanmai-NL and mrexox committed Jan 22, 2024
1 parent c0e9575 commit 3246821
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 21 deletions.
27 changes: 25 additions & 2 deletions cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions internal/config/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
31 changes: 30 additions & 1 deletion internal/lefthook/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"io"
"os"
"os/signal"
"path/filepath"
Expand All @@ -24,6 +25,7 @@ const (
type RunArgs struct {
NoTTY bool
AllFiles bool
FilesFromStdin bool
Force bool
Files []string
RunOnlyCommands []string
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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
}
35 changes: 21 additions & 14 deletions internal/lefthook/run/prepare_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,28 +74,35 @@ 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, " ")
} else {
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)
Expand Down Expand Up @@ -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
}
Expand Down
1 change: 0 additions & 1 deletion internal/lefthook/run/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ type Options struct {
ResultChan chan Result
SkipSettings log.SkipSettings
DisableTTY bool
AllFiles bool
Force bool
Files []string
RunOnlyCommands []string
Expand Down
42 changes: 42 additions & 0 deletions testdata/files_override.txt
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 3246821

Please sign in to comment.