diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e78f6669..50c65239 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,7 +32,7 @@ jobs: strategy: matrix: go-version: [1.21.x] - os: [ubuntu-latest, macos-latest] + os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} env: GOCOVERDIR: ${{ github.workspace }}/_icoverdir_ @@ -91,5 +91,5 @@ jobs: steps: - uses: coverallsapp/github-action@v2 with: - carryforward: "integration-1.21.x ubuntu-latest,integration-1.21.x macos-latest,1.21.x ubuntu-latest,1.21.x macos-latest,1.21.x windows-latest" + carryforward: "integration-1.21.x ubuntu-latest,integration-1.21.x macos-latest,integration-1.21.x windows-latest,1.21.x ubuntu-latest,1.21.x macos-latest,1.21.x windows-latest" parallel-finished: true diff --git a/docs/configuration.md b/docs/configuration.md index 5c8bb04e..4e9f2f02 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -163,8 +163,46 @@ If you want to specify a minimum version for lefthook binary (e.g. if you need s min_version: 1.1.3 ``` +### `output` + +You can manage verbosity using the `output` config. You can specify what to print in your output by setting these values, which you need to have + +Possible values are `meta,summary,success,failure,execution,execution_out,execution_info,skips`. +By default, all output values are enabled + +You can also disable all output with setting `output: false`. In this case only errors will be printed. + +This config quiets all outputs except for errors. + +`output` is enabled if there is no `skip_output` and `LEFTHOOK_QUIET`. + +**Example** + +```yml +# lefthook.yml + +output: + - meta # Print lefthook version + - summary # Print summary block (successful and failed steps) + - empty_summary # Print summary heading when there are no steps to run + - success # Print successful steps + - failure # Print failed steps printing + - execution # Print any execution logs (but prints if the execution failed) + - execution_out # Print execution output (but still prints failed commands output) + - execution_info # Print `EXECUTE > ...` logging + - skips # Print "skip" (i.e. no files matched) +``` + +You can also *extend* this list with an environment variable `LEFTHOOK_OUTPUT`: + +```bash +LEFTHOOK_OUTPUT="meta,success,summary" lefthook run pre-commit +``` + ### `skip_output` +> **Deprecated:** This feature is deprecated and might be removed in future versions. Please, use `[output]` instead for managing verbosity. + You can manage the verbosity using the `skip_output` config. You can set whether lefthook should print some parts of its output. Possible values are `meta,summary,success,failure,execution,execution_out,execution_info,skips`. @@ -884,6 +922,21 @@ pre-commit: run: yarn test ``` +Skipping hook by running a command: + +```yml +# lefthook.yml + +pre-commit: + skip: + - run: test "${NO_HOOK}" -eq 1 + commands: + lint: + run: yarn lint + text: + run: yarn test +``` + **Notes** Always skipping is useful when you have a `lefthook-local.yml` config and you don't want to run some commands locally. So you just overwrite the `skip` option for them to be `true`. diff --git a/go.mod b/go.mod index 61e74d94..40b08ca6 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.21 require ( github.com/MakeNowJust/heredoc v1.0.0 github.com/briandowns/spinner v1.23.0 - github.com/charmbracelet/lipgloss v0.9.1 + github.com/charmbracelet/lipgloss v0.10.0 github.com/creack/pty v1.1.21 github.com/gobwas/glob v0.2.3 github.com/google/go-cmp v0.5.9 diff --git a/go.sum b/go.sum index 2d21c113..98d7f7e2 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,8 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/briandowns/spinner v1.23.0 h1:alDF2guRWqa/FOZZYWjlMIx2L6H0wyewPxo/CH4Pt2A= github.com/briandowns/spinner v1.23.0/go.mod h1:rPG4gmXeN3wQV/TsAY4w8lPdIM6RX3yqeBQJSrbXjuE= -github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg= -github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I= +github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s= +github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= diff --git a/internal/config/command.go b/internal/config/command.go index d5fd2b8b..7fce4c1e 100644 --- a/internal/config/command.go +++ b/internal/config/command.go @@ -40,7 +40,8 @@ func (c Command) Validate() error { } func (c Command) DoSkip(gitState git.State) bool { - return doSkip(gitState, c.Skip, c.Only) + skipChecker := NewSkipChecker(NewOsExec()) + return skipChecker.Check(gitState, c.Skip, c.Only) } type commandRunReplace struct { diff --git a/internal/config/config.go b/internal/config/config.go index 19c72e5f..2af54794 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -20,6 +20,7 @@ type Config struct { SourceDirLocal string `mapstructure:"source_dir_local"` Rc string `mapstructure:"rc,omitempty"` SkipOutput interface{} `mapstructure:"skip_output,omitempty"` + Output interface{} `mapstructure:"output,omitempty"` Extends []string `mapstructure:"extends,omitempty"` NoTTY bool `mapstructure:"no_tty,omitempty"` AssertLefthookInstalled bool `mapstructure:"assert_lefthook_installed,omitempty"` diff --git a/internal/config/exec.go b/internal/config/exec.go new file mode 100644 index 00000000..87971979 --- /dev/null +++ b/internal/config/exec.go @@ -0,0 +1,35 @@ +package config + +import ( + "os/exec" + "runtime" +) + +type Exec interface { + Cmd(commandLine string) bool +} + +type osExec struct{} + +// NewOsExec returns an object that executes given commands in the OS. +func NewOsExec() Exec { + return &osExec{} +} + +// Cmd runs plain string command. It checks only exit code and returns bool value. +func (o *osExec) Cmd(commandLine string) bool { + if commandLine == "" { + return false + } + + var cmd *exec.Cmd + if runtime.GOOS == "windows" { + cmd = exec.Command("powershell", "-Command", commandLine) + } else { + cmd = exec.Command("sh", "-c", commandLine) + } + + err := cmd.Run() + + return err == nil +} diff --git a/internal/config/hook.go b/internal/config/hook.go index 364ec6c8..8103add4 100644 --- a/internal/config/hook.go +++ b/internal/config/hook.go @@ -43,7 +43,8 @@ func (h *Hook) Validate() error { } func (h *Hook) DoSkip(gitState git.State) bool { - return doSkip(gitState, h.Skip, h.Only) + skipChecker := NewSkipChecker(NewOsExec()) + return skipChecker.Check(gitState, h.Skip, h.Only) } func unmarshalHooks(base, extra *viper.Viper) (*Hook, error) { diff --git a/internal/config/script.go b/internal/config/script.go index 7d7a44dd..f84faad4 100644 --- a/internal/config/script.go +++ b/internal/config/script.go @@ -24,7 +24,8 @@ type Script struct { } func (s Script) DoSkip(gitState git.State) bool { - return doSkip(gitState, s.Skip, s.Only) + skipChecker := NewSkipChecker(NewOsExec()) + return skipChecker.Check(gitState, s.Skip, s.Only) } type scriptRunnerReplace struct { diff --git a/internal/config/skip.go b/internal/config/skip.go deleted file mode 100644 index 9b52f3bd..00000000 --- a/internal/config/skip.go +++ /dev/null @@ -1,50 +0,0 @@ -package config - -import ( - "github.com/gobwas/glob" - - "github.com/evilmartians/lefthook/internal/git" -) - -func doSkip(gitState git.State, skip, only interface{}) bool { - if skip != nil { - if matches(gitState, skip) { - return true - } - } - - if only != nil { - return !matches(gitState, only) - } - - return false -} - -func matches(gitState git.State, value interface{}) bool { - switch typedValue := value.(type) { - case bool: - return typedValue - case string: - return typedValue == gitState.Step - case []interface{}: - for _, state := range typedValue { - switch typedState := state.(type) { - case string: - if typedState == gitState.Step { - return true - } - case map[string]interface{}: - ref := typedState["ref"].(string) - if ref == gitState.Branch { - return true - } - - g := glob.MustCompile(ref) - if g.Match(gitState.Branch) { - return true - } - } - } - } - return false -} diff --git a/internal/config/skip_checker.go b/internal/config/skip_checker.go new file mode 100644 index 00000000..581610f7 --- /dev/null +++ b/internal/config/skip_checker.go @@ -0,0 +1,95 @@ +package config + +import ( + "github.com/gobwas/glob" + + "github.com/evilmartians/lefthook/internal/git" + "github.com/evilmartians/lefthook/internal/log" +) + +type SkipChecker struct { + Executor Exec +} + +func NewSkipChecker(executor Exec) *SkipChecker { + if executor == nil { + executor = NewOsExec() + } + + return &SkipChecker{Executor: executor} +} + +func (sc *SkipChecker) Check(gitState git.State, skip interface{}, only interface{}) bool { + if skip != nil { + if sc.matches(gitState, skip) { + return true + } + } + + if only != nil { + return !sc.matches(gitState, only) + } + + return false +} + +func (sc *SkipChecker) matches(gitState git.State, value interface{}) bool { + switch typedValue := value.(type) { + case bool: + return typedValue + case string: + return typedValue == gitState.Step + case []interface{}: + return sc.matchesSlices(gitState, typedValue) + } + return false +} + +func (sc *SkipChecker) matchesSlices(gitState git.State, slice []interface{}) bool { + for _, state := range slice { + switch typedState := state.(type) { + case string: + if typedState == gitState.Step { + return true + } + case map[string]interface{}: + if sc.matchesRef(gitState, typedState) { + return true + } + + if sc.matchesCommands(typedState) { + return true + } + } + } + + return false +} + +func (sc *SkipChecker) matchesRef(gitState git.State, typedState map[string]interface{}) bool { + ref, ok := typedState["ref"].(string) + if !ok { + return false + } + + if ref == gitState.Branch { + return true + } + + g := glob.MustCompile(ref) + + return g.Match(gitState.Branch) +} + +func (sc *SkipChecker) matchesCommands(typedState map[string]interface{}) bool { + commandLine, ok := typedState["run"].(string) + if !ok { + return false + } + + result := sc.Executor.Cmd(commandLine) + + log.Debugf("[lefthook] skip/only cmd: %s, result: %t", commandLine, result) + + return result +} diff --git a/internal/config/skip_test.go b/internal/config/skip_checker_test.go similarity index 79% rename from internal/config/skip_test.go rename to internal/config/skip_checker_test.go index d4d3c6f3..4956859f 100644 --- a/internal/config/skip_test.go +++ b/internal/config/skip_checker_test.go @@ -6,7 +6,15 @@ import ( "github.com/evilmartians/lefthook/internal/git" ) +type mockExecutor struct{} + +func (mc mockExecutor) Cmd(cmd string) bool { + return cmd == "success" +} + func TestDoSkip(t *testing.T) { + skipChecker := NewSkipChecker(mockExecutor{}) + for _, tt := range [...]struct { name string state git.State @@ -111,9 +119,27 @@ func TestDoSkip(t *testing.T) { only: "rebase", skipped: true, }, + { + name: "when skip with run command", + state: git.State{}, + skip: []interface{}{map[string]interface{}{"run": "success"}}, + skipped: true, + }, + { + name: "when skip with multi-run command", + state: git.State{Branch: "feat"}, + skip: []interface{}{map[string]interface{}{"run": "success", "ref": "feat"}}, + skipped: true, + }, + { + name: "when only with run command", + state: git.State{}, + only: []interface{}{map[string]interface{}{"run": "fail"}}, + skipped: true, + }, } { t.Run(tt.name, func(t *testing.T) { - if doSkip(tt.state, tt.skip, tt.only) != tt.skipped { + if skipChecker.Check(tt.state, tt.skip, tt.only) != tt.skipped { t.Errorf("Expected: %v, Was %v", tt.skipped, !tt.skipped) } }) diff --git a/internal/lefthook/run.go b/internal/lefthook/run.go index 6e0db14c..431c082f 100644 --- a/internal/lefthook/run.go +++ b/internal/lefthook/run.go @@ -18,8 +18,9 @@ import ( ) const ( - envEnabled = "LEFTHOOK" // "0", "false" - envSkipOutput = "LEFTHOOK_QUIET" // "meta,success,failure,summary,skips,execution,execution_out,execution_info" + envEnabled = "LEFTHOOK" // "0", "false" + envSkipOutput = "LEFTHOOK_QUIET" // "meta,success,failure,summary,skips,execution,execution_out,execution_info" + envOutput = "LEFTHOOK_OUTPUT" // "meta,success,failure,summary,skips,execution,execution_out,execution_info" ) type RunArgs struct { @@ -75,12 +76,23 @@ func (l *Lefthook) Run(hookName string, args RunArgs, gitArgs []string) error { log.SetLevel(log.WarnLevel) } - tags := os.Getenv(envSkipOutput) + outputLogTags := os.Getenv(envOutput) + outputSkipTags := os.Getenv(envSkipOutput) - var logSettings log.SkipSettings - (&logSettings).ApplySettings(tags, cfg.SkipOutput) + var logSettings log.Settings - if !logSettings.SkipMeta() { + if outputSkipTags == "" && cfg.SkipOutput == nil { + logSettings = log.NewSettings() + logSettings.ApplySettings(outputLogTags, cfg.Output) + } else { + // Deprecate skip_output in the future. Leaving as is to reduce noise in output. + // log.Warn("skip_output is deprecated, please use output option") + + logSettings = log.NewSkipSettings() //nolint:staticcheck //SA1019: for temporary backward compatibility + logSettings.ApplySettings(outputSkipTags, cfg.SkipOutput) + } + + if logSettings.LogMeta() { log.Box( log.Cyan("🥊 lefthook ")+log.Gray(fmt.Sprintf("v%s", version.Version(false))), log.Gray("hook: ")+log.Bold(hookName), @@ -133,7 +145,7 @@ Run 'lefthook install' manually.`, HookName: hookName, GitArgs: gitArgs, ResultChan: resultChan, - SkipSettings: logSettings, + LogSettings: logSettings, DisableTTY: cfg.NoTTY || args.NoTTY, Files: args.Files, Force: args.Force, @@ -176,9 +188,7 @@ Run 'lefthook install' manually.`, return errors.New("Interrupted") } - if !logSettings.SkipSummary() { - printSummary(time.Since(startTime), results, logSettings) - } + printSummary(time.Since(startTime), results, logSettings) for _, result := range results { if result.Status == run.StatusErr { @@ -192,33 +202,35 @@ Run 'lefthook install' manually.`, func printSummary( duration time.Duration, results []run.Result, - logSettings log.SkipSettings, + logSettings log.Settings, ) { - summaryPrint := log.Separate + if logSettings.LogSummary() { + summaryPrint := log.Separate - if logSettings.SkipExecution() || (logSettings.SkipExecutionInfo() && logSettings.SkipExecutionOutput()) { - summaryPrint = func(s string) { log.Info(s) } - } + if !logSettings.LogExecution() { + summaryPrint = func(s string) { log.Info(s) } + } - if len(results) == 0 { - if !logSettings.SkipEmptySummary() { - summaryPrint( - fmt.Sprintf( - "%s %s %s", - log.Cyan("summary:"), - log.Gray("(skip)"), - log.Yellow("empty"), - ), - ) + if len(results) == 0 { + if logSettings.LogEmptySummary() { + summaryPrint( + fmt.Sprintf( + "%s %s %s", + log.Cyan("summary:"), + log.Gray("(skip)"), + log.Yellow("empty"), + ), + ) + } + return } - return - } - summaryPrint( - log.Cyan("summary: ") + log.Gray(fmt.Sprintf("(done in %.2f seconds)", duration.Seconds())), - ) + summaryPrint( + log.Cyan("summary: ") + log.Gray(fmt.Sprintf("(done in %.2f seconds)", duration.Seconds())), + ) + } - if !logSettings.SkipSuccess() { + if logSettings.LogSuccess() { for _, result := range results { if result.Status != run.StatusOk { continue @@ -228,7 +240,7 @@ func printSummary( } } - if !logSettings.SkipFailure() { + if logSettings.LogFailure() { for _, result := range results { if result.Status != run.StatusErr { continue diff --git a/internal/lefthook/run/runner.go b/internal/lefthook/run/runner.go index d4364745..a49d4449 100644 --- a/internal/lefthook/run/runner.go +++ b/internal/lefthook/run/runner.go @@ -43,7 +43,7 @@ type Options struct { HookName string GitArgs []string ResultChan chan Result - SkipSettings log.SkipSettings + LogSettings log.Settings DisableTTY bool Force bool Files []string @@ -426,14 +426,14 @@ func (r *Runner) run(ctx context.Context, opts exec.Options, follow bool) bool { log.SetName(opts.Name) defer log.UnsetName(opts.Name) - if (follow || opts.Interactive) && !r.SkipSettings.SkipExecution() { + if (follow || opts.Interactive) && r.LogSettings.LogExecution() { r.logExecute(opts.Name, nil, nil) var out io.Writer - if r.SkipSettings.SkipExecutionOutput() { - out = io.Discard - } else { + if r.LogSettings.LogExecutionOutput() { out = os.Stdout + } else { + out = io.Discard } err := r.executor.Execute(ctx, opts, out) @@ -478,7 +478,7 @@ func intersect(a, b []string) bool { } func (r *Runner) logSkip(name, reason string) { - if r.SkipSettings.SkipSkips() { + if !r.LogSettings.LogSkips() { return } @@ -493,14 +493,14 @@ func (r *Runner) logSkip(name, reason string) { } func (r *Runner) logExecute(name string, err error, out io.Reader) { - if err == nil && r.SkipSettings.SkipExecution() { + if err == nil && !r.LogSettings.LogExecution() { return } var execLog string var color lipgloss.TerminalColor switch { - case r.SkipSettings.SkipExecutionInfo(): + case !r.LogSettings.LogExecutionInfo(): execLog = "" case err != nil: execLog = log.Red(fmt.Sprintf("%s ❯ ", name)) @@ -518,7 +518,7 @@ func (r *Runner) logExecute(name string, err error, out io.Reader) { log.Info() } - if err == nil && r.SkipSettings.SkipExecutionOutput() { + if err == nil && !r.LogSettings.LogExecutionOutput() { return } diff --git a/internal/lefthook/run/runner_test.go b/internal/lefthook/run/runner_test.go index 243800c4..76bcd4c9 100644 --- a/internal/lefthook/run/runner_test.go +++ b/internal/lefthook/run/runner_test.go @@ -16,6 +16,7 @@ import ( "github.com/evilmartians/lefthook/internal/config" "github.com/evilmartians/lefthook/internal/git" "github.com/evilmartians/lefthook/internal/lefthook/run/exec" + "github.com/evilmartians/lefthook/internal/log" ) type TestExecutor struct{} @@ -749,12 +750,13 @@ func TestRunAll(t *testing.T) { executor := TestExecutor{} runner := &Runner{ Options: Options{ - Repo: repo, - Hook: tt.hook, - HookName: tt.hookName, - GitArgs: tt.args, - ResultChan: resultChan, - Force: tt.force, + Repo: repo, + Hook: tt.hook, + HookName: tt.hookName, + LogSettings: log.NewSettings(), + GitArgs: tt.args, + ResultChan: resultChan, + Force: tt.force, }, executor: executor, } diff --git a/internal/log/settings.go b/internal/log/settings.go new file mode 100644 index 00000000..8cc3b59d --- /dev/null +++ b/internal/log/settings.go @@ -0,0 +1,140 @@ +package log + +import ( + "strings" +) + +const ( + meta = 1 << iota + success + failure + summary + skips + execution + executionOutput + executionInfo + emptySummary + enableAll = ^0 // Set all bits as 1 +) + +type Settings interface { + ApplySettings(tags string, skipOutput interface{}) + LogSuccess() bool + LogFailure() bool + LogSummary() bool + LogMeta() bool + LogExecution() bool + LogExecutionOutput() bool + LogExecutionInfo() bool + LogSkips() bool + LogEmptySummary() bool +} + +type OutputSettings int16 + +func NewSettings() Settings { + var s OutputSettings + return &s +} + +func (s *OutputSettings) ApplySettings(tags string, output interface{}) { + if tags == "" && (output == nil || output == "") { + s.enableAll(true) + return + } + + if val, ok := output.(bool); ok { + s.enableAll(val) + return + } + + if options, ok := output.([]interface{}); ok { + if len(options) == 0 { + s.enableAll(true) + return + } + for _, option := range options { + if optStr, ok := option.(string); ok { + s.applySetting(optStr) + } + } + } + + if tags != "" { + for _, tag := range strings.Split(tags, ",") { + s.applySetting(tag) + } + } +} + +func (s *OutputSettings) applySetting(setting string) { + switch setting { + case "meta": + *s |= meta + case "success": + *s |= success + case "failure": + *s |= failure + case "summary": + *s |= summary + case "skips": + *s |= skips + case "execution": + *s |= execution + case "execution_out": + *s |= executionOutput + case "execution_info": + *s |= executionInfo + case "empty_summary": + *s |= emptySummary + } +} + +func (s *OutputSettings) enableAll(val bool) { + if val { + *s = enableAll // Enable all params + } else { + *s |= failure // Disable all params + } +} + +// Checks the state of params. +func (s OutputSettings) isEnable(option int16) bool { + return int16(s)&option != 0 +} + +func (s OutputSettings) LogSuccess() bool { + return s.isEnable(success) || s.isEnable(summary) +} + +func (s OutputSettings) LogFailure() bool { + return s.isEnable(failure) || s.isEnable(summary) +} + +func (s OutputSettings) LogSummary() bool { + return s.isEnable(summary) +} + +func (s OutputSettings) LogMeta() bool { + return s.isEnable(meta) +} + +func (s OutputSettings) LogExecution() bool { + return s.isEnable(execution) || s.isEnable(executionOutput) || s.isEnable(executionInfo) +} + +func (s OutputSettings) LogExecutionOutput() bool { + return s.isEnable(execution) || s.isEnable(executionOutput) +} + +func (s OutputSettings) LogExecutionInfo() bool { + return s.isEnable(execution) || s.isEnable(executionInfo) +} + +func (s OutputSettings) LogSkips() bool { + return s.isEnable(skips) +} + +func (s OutputSettings) LogEmptySummary() bool { + return s.isEnable(emptySummary) +} diff --git a/internal/log/settings_test.go b/internal/log/settings_test.go new file mode 100644 index 00000000..76ce728c --- /dev/null +++ b/internal/log/settings_test.go @@ -0,0 +1,175 @@ +package log + +import ( + "fmt" + "testing" +) + +func TestSetting(t *testing.T) { + for i, tt := range [...]struct { + tags string + settings interface{} + results map[string]bool + }{ + { + tags: "", + settings: []interface{}{}, + results: map[string]bool{ + "meta": true, + "summary": true, + "success": true, + "failure": true, + "skips": true, + "execution": true, + "execution_out": true, + "execution_info": true, + "empty_summary": true, + }, + }, + { + tags: "", + settings: false, + results: map[string]bool{ + "failure": true, + }, + }, + { + tags: "", + settings: []interface{}{"success"}, + results: map[string]bool{ + "success": true, + }, + }, + { + tags: "", + settings: []interface{}{"summary"}, + results: map[string]bool{ + "summary": true, + "success": true, + "failure": true, + }, + }, + { + tags: "", + settings: []interface{}{"failure", "execution"}, + results: map[string]bool{ + "failure": true, + "execution": true, + "execution_info": true, + "execution_out": true, + }, + }, + { + tags: "", + settings: []interface{}{"failure", "execution_out"}, + results: map[string]bool{ + "failure": true, + "execution": true, + "execution_out": true, + }, + }, + { + tags: "", + settings: []interface{}{"failure", "execution_info"}, + results: map[string]bool{ + "failure": true, + "execution": true, + "execution_info": true, + }, + }, + { + tags: "", + settings: []interface{}{ + "meta", + "summary", + "success", + "failure", + "skips", + "execution", + "execution_out", + "execution_info", + "empty_summary", + }, + results: map[string]bool{ + "meta": true, + "summary": true, + "success": true, + "failure": true, + "skips": true, + "execution": true, + "execution_out": true, + "execution_info": true, + "empty_summary": true, + }, + }, + { + tags: "", + settings: true, + results: map[string]bool{ + "meta": true, + "summary": true, + "success": true, + "failure": true, + "skips": true, + "execution": true, + "execution_out": true, + "execution_info": true, + "empty_summary": true, + }, + }, + { + tags: "meta,summary,skips,empty_summary", + settings: nil, + results: map[string]bool{ + "meta": true, + "summary": true, + "success": true, + "failure": true, + "skips": true, + "empty_summary": true, + }, + }, + } { //nolint:dupl // In next versions the `skip_settings_test` will be removed + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + var settings OutputSettings + + (&settings).ApplySettings(tt.tags, tt.settings) + + if settings.LogMeta() != tt.results["meta"] { + t.Errorf("expected LogMeta to be %v", tt.results["meta"]) + } + + if settings.LogSuccess() != tt.results["success"] { + t.Errorf("expected LogSuccess to be %v", tt.results["success"]) + } + + if settings.LogFailure() != tt.results["failure"] { + t.Errorf("expected LogFailure to be %v", tt.results["failure"]) + } + + if settings.LogSummary() != tt.results["summary"] { + t.Errorf("expected LogSummary to be %v", tt.results["summary"]) + } + + if settings.LogExecution() != tt.results["execution"] { + t.Errorf("expected LogExecution to be %v", tt.results["execution"]) + } + + if settings.LogExecutionOutput() != tt.results["execution_out"] { + t.Errorf("expected LogExecutionOutput to be %v", tt.results["execution_out"]) + } + + if settings.LogExecutionInfo() != tt.results["execution_info"] { + t.Errorf("expected LogExecutionInfo to be %v", tt.results["execution_info"]) + } + + if settings.LogEmptySummary() != tt.results["empty_summary"] { + t.Errorf("expected LogEmptySummary to be %v", tt.results["empty_summary"]) + } + + if settings.LogSkips() != tt.results["skips"] { + t.Errorf("expected LogSkips to be %v", tt.results["skip"]) + } + }) + } +} diff --git a/internal/log/skip_settings.go b/internal/log/skip_settings.go index 2e9cb551..04841a10 100644 --- a/internal/log/skip_settings.go +++ b/internal/log/skip_settings.go @@ -17,12 +17,19 @@ const ( skipAll = (1 << iota) - 1 ) +// Deprecated: Use Settings instead. type SkipSettings int16 +// Deprecated: Use NewSettings instead. +func NewSkipSettings() Settings { + var s SkipSettings + return &s +} + func (s *SkipSettings) ApplySettings(tags string, skipOutput interface{}) { switch typedSkipOutput := skipOutput.(type) { case bool: - s.SkipAll(typedSkipOutput) + s.skipAll(typedSkipOutput) case []interface{}: for _, skipOption := range typedSkipOutput { s.applySetting(skipOption.(string)) @@ -59,48 +66,48 @@ func (s *SkipSettings) applySetting(setting string) { } } -func (s *SkipSettings) SkipAll(val bool) { +func (s *SkipSettings) skipAll(val bool) { if val { - *s = skipAll &^ skipFailure + *s = skipAll &^ skipFailure &^ skipSummary } else { *s = 0 } } -func (s SkipSettings) SkipSuccess() bool { - return s.doSkip(skipSuccess) +func (s SkipSettings) LogSuccess() bool { + return !s.doSkip(skipSuccess) && !s.doSkip(skipSummary) } -func (s SkipSettings) SkipFailure() bool { - return s.doSkip(skipFailure) +func (s SkipSettings) LogFailure() bool { + return !s.doSkip(skipFailure) && !s.doSkip(skipSummary) } -func (s SkipSettings) SkipSummary() bool { - return s.doSkip(skipSummary) +func (s SkipSettings) LogSummary() bool { + return !s.doSkip(skipSummary) } -func (s SkipSettings) SkipMeta() bool { - return s.doSkip(skipMeta) +func (s SkipSettings) LogMeta() bool { + return !s.doSkip(skipMeta) } -func (s SkipSettings) SkipExecution() bool { - return s.doSkip(skipExecution) +func (s SkipSettings) LogExecution() bool { + return !s.doSkip(skipExecution) } -func (s SkipSettings) SkipExecutionOutput() bool { - return s.doSkip(skipExecutionOutput) +func (s SkipSettings) LogExecutionOutput() bool { + return !s.doSkip(skipExecutionOutput) } -func (s SkipSettings) SkipExecutionInfo() bool { - return s.doSkip(skipExecutionInfo) +func (s SkipSettings) LogExecutionInfo() bool { + return !s.doSkip(skipExecutionInfo) } -func (s SkipSettings) SkipSkips() bool { - return s.doSkip(skipSkips) +func (s SkipSettings) LogSkips() bool { + return !s.doSkip(skipSkips) } -func (s SkipSettings) SkipEmptySummary() bool { - return s.doSkip(skipEmptySummary) +func (s SkipSettings) LogEmptySummary() bool { + return !s.doSkip(skipEmptySummary) } func (s SkipSettings) doSkip(option int16) bool { diff --git a/internal/log/skip_settings_test.go b/internal/log/skip_settings_test.go index 88aa9404..358c6433 100644 --- a/internal/log/skip_settings_test.go +++ b/internal/log/skip_settings_test.go @@ -14,19 +14,46 @@ func TestSkipSetting(t *testing.T) { { tags: "", settings: []interface{}{}, - results: map[string]bool{}, + results: map[string]bool{ + "meta": true, + "summary": true, + "success": true, + "failure": true, + "skips": true, + "execution": true, + "execution_out": true, + "execution_info": true, + "empty_summary": true, + }, }, { tags: "", settings: false, - results: map[string]bool{}, + results: map[string]bool{ + "meta": true, + "summary": true, + "success": true, + "failure": true, + "skips": true, + "execution": true, + "execution_out": true, + "execution_info": true, + "empty_summary": true, + }, }, { tags: "", settings: []interface{}{"failure", "execution"}, results: map[string]bool{ - "failure": true, - "execution": true, + "meta": true, + "summary": true, + "success": true, + "failure": false, + "skips": true, + "execution": false, + "execution_out": true, + "execution_info": true, + "empty_summary": true, }, }, { @@ -34,54 +61,40 @@ func TestSkipSetting(t *testing.T) { settings: []interface{}{ "meta", "summary", - "success", - "failure", "skips", "execution", "execution_out", "execution_info", "empty_summary", }, - results: map[string]bool{ - "meta": true, - "summary": true, - "success": true, - "failure": true, - "skips": true, - "execution": true, - "execution_out": true, - "execution_info": true, - "empty_summary": true, - }, + results: map[string]bool{}, }, { tags: "", settings: true, results: map[string]bool{ - "meta": true, - "summary": true, - "success": true, - "failure": false, - "skips": true, + "summary": true, + "failure": true, + }, + }, + { + tags: "meta,summary,success,skips,empty_summary", + settings: nil, + results: map[string]bool{ "execution": true, "execution_out": true, "execution_info": true, - "empty_summary": true, }, }, { - tags: "meta,summary,success,skips,empty_summary", + tags: "meta,success,skips,empty_summary", settings: nil, results: map[string]bool{ - "meta": true, "summary": true, - "success": true, - "failure": false, - "skips": true, - "execution": false, - "execution_out": false, - "execution_info": false, - "empty_summary": true, + "failure": true, + "execution": true, + "execution_out": true, + "execution_info": true, }, }, } { @@ -90,40 +103,40 @@ func TestSkipSetting(t *testing.T) { (&settings).ApplySettings(tt.tags, tt.settings) - if settings.SkipMeta() != tt.results["meta"] { - t.Errorf("expected SkipMeta to be %v", tt.results["meta"]) + if settings.LogMeta() != tt.results["meta"] { + t.Errorf("expected LogMeta to be %v", tt.results["meta"]) } - if settings.SkipSuccess() != tt.results["success"] { - t.Errorf("expected SkipSuccess to be %v", tt.results["success"]) + if settings.LogSuccess() != tt.results["success"] { + t.Errorf("expected LogSuccess to be %v", tt.results["success"]) } - if settings.SkipFailure() != tt.results["failure"] { - t.Errorf("expected SkipFailure to be %v", tt.results["failure"]) + if settings.LogFailure() != tt.results["failure"] { + t.Errorf("expected LogFailure to be %v", tt.results["failure"]) } - if settings.SkipSummary() != tt.results["summary"] { - t.Errorf("expected SkipSummary to be %v", tt.results["summary"]) + if settings.LogSummary() != tt.results["summary"] { + t.Errorf("expected LogSummary to be %v", tt.results["summary"]) } - if settings.SkipExecution() != tt.results["execution"] { - t.Errorf("expected SkipExecution to be %v", tt.results["execution"]) + if settings.LogExecution() != tt.results["execution"] { + t.Errorf("expected LogExecution to be %v", tt.results["execution"]) } - if settings.SkipExecutionOutput() != tt.results["execution_out"] { - t.Errorf("expected SkipExecutionOutput to be %v", tt.results["execution_out"]) + if settings.LogExecutionOutput() != tt.results["execution_out"] { + t.Errorf("expected LogExecutionOutput to be %v", tt.results["execution_out"]) } - if settings.SkipExecutionInfo() != tt.results["execution_info"] { - t.Errorf("expected SkipExecutionInfo to be %v", tt.results["execution_info"]) + if settings.LogExecutionInfo() != tt.results["execution_info"] { + t.Errorf("expected LogExecutionInfo to be %v", tt.results["execution_info"]) } - if settings.SkipEmptySummary() != tt.results["empty_summary"] { - t.Errorf("expected SkipEmptySummary to be %v", tt.results["empty_summary"]) + if settings.LogEmptySummary() != tt.results["empty_summary"] { + t.Errorf("expected LogEmptySummary to be %v", tt.results["empty_summary"]) } - if settings.SkipSkips() != tt.results["skips"] { - t.Errorf("expected SkipSkips to be %v", tt.results["skip"]) + if settings.LogSkips() != tt.results["skips"] { + t.Errorf("expected LogSkips to be %v", tt.results["skip"]) } }) } diff --git a/testdata/add.txt b/testdata/add.txt index 4aa86233..da9e0e95 100644 --- a/testdata/add.txt +++ b/testdata/add.txt @@ -1,3 +1,5 @@ +[windows] skip + exec git init exec lefthook add pre-commit ! stderr . diff --git a/testdata/dump.txt b/testdata/dump.txt index 3857f0d4..5a5559e3 100644 --- a/testdata/dump.txt +++ b/testdata/dump.txt @@ -1,3 +1,5 @@ +[windows] skip + exec git init exec lefthook dump cmp stdout lefthook-dumped.yml diff --git a/testdata/hide_unstaged.txt b/testdata/hide_unstaged.txt index 4147604e..5aa67510 100644 --- a/testdata/hide_unstaged.txt +++ b/testdata/hide_unstaged.txt @@ -1,3 +1,5 @@ +[windows] skip + exec git init exec lefthook install exec git config user.email "you@example.com" diff --git a/testdata/remote.txt b/testdata/remote.txt index 72435af1..df0c246b 100644 --- a/testdata/remote.txt +++ b/testdata/remote.txt @@ -1,3 +1,5 @@ +[windows] skip + exec git init exec lefthook install diff --git a/testdata/remotes.txt b/testdata/remotes.txt index 7858b6e3..beffc20f 100644 --- a/testdata/remotes.txt +++ b/testdata/remotes.txt @@ -1,3 +1,5 @@ +[windows] skip + exec git init exec lefthook install diff --git a/testdata/run_interrupt.txt b/testdata/run_interrupt.txt index 9103d014..2716c53d 100644 --- a/testdata/run_interrupt.txt +++ b/testdata/run_interrupt.txt @@ -1,3 +1,5 @@ +[windows] skip + chmod 0700 hook.sh chmod 0700 commit-with-interrupt.sh exec git init diff --git a/testdata/sh_syntax_in_files.txt b/testdata/sh_syntax_in_files.txt index a45c9a25..b65a7cb7 100644 --- a/testdata/sh_syntax_in_files.txt +++ b/testdata/sh_syntax_in_files.txt @@ -1,9 +1,12 @@ +[windows] skip + exec git init exec lefthook install exec git config user.email "you@example.com" exec git config user.name "Your Name" exec lefthook run echo_files + stdout '1.txt 10.txt' -- lefthook.yml -- diff --git a/testdata/skip_run.txt b/testdata/skip_run.txt new file mode 100644 index 00000000..7e2af672 --- /dev/null +++ b/testdata/skip_run.txt @@ -0,0 +1,42 @@ +[windows] skip + +exec git init +exec git add -A +exec lefthook run skip +! stdout 'Ha-ha!' +exec lefthook run no-skip +stdout 'Ha-ha!' + +exec lefthook run skip-var +! stdout 'Ha-ha!' + +env VAR=1 +exec lefthook run skip-var +stdout 'Ha-ha!' + +-- lefthook.yml -- +skip_output: + - skips + - meta + - summary + - execution_info +skip: + skip: + - run: test 1 -eq 1 + commands: + echo: + run: echo 'Ha-ha!' + +no-skip: + skip: + - run: "[ 1 -eq 0 ]" + commands: + echo: + run: echo 'Ha-ha!' + +skip-var: + skip: + - run: test -z $VAR + commands: + echo: + run: echo 'Ha-ha!' diff --git a/testdata/skip_run_windows.txt b/testdata/skip_run_windows.txt new file mode 100644 index 00000000..f5d6fd82 --- /dev/null +++ b/testdata/skip_run_windows.txt @@ -0,0 +1,42 @@ +[!windows] skip + +exec git init +exec git add -A +exec lefthook run skip +! stdout 'Ha-ha!' +exec lefthook run no-skip +stdout 'Ha-ha!' + +exec lefthook run skip-var +! stdout 'Ha-ha!' + +env VAR=1 +exec lefthook run skip-var +stdout 'Ha-ha!' + +-- lefthook.yml -- +skip_output: + - skips + - meta + - summary + - execution_info +skip: + skip: + - run: if (1 -eq 1) { exit 0 } else { exit 1 } + commands: + echo: + run: echo 'Ha-ha!' + +no-skip: + skip: + - run: if (1 -eq 0) { exit 0 } else { exit 1 } + commands: + echo: + run: echo 'Ha-ha!' + +skip-var: + skip: + - run: if ([string]::IsNullOrEmpty($env:VAR)) { exit 0 } else { exit 1 } + commands: + echo: + run: echo 'Ha-ha!'