Skip to content

Commit

Permalink
feat: add priorities to commands (#589)
Browse files Browse the repository at this point in the history
  • Loading branch information
mrexox authored Nov 30, 2023
1 parent 64fefde commit 96e1fb5
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 21 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## master (unreleased)

- feat: add priorities to commands ([#589](https://github.com/evilmartians/lefthook/pull/589)) by @mrexox

## 1.5.4 (2023-11-27)

- chore: add typos fixer by @mrexox
Expand Down
65 changes: 51 additions & 14 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Lefthook [supports](#config-file) YAML, JSON, and TOML configuration. In this do
- [`stage_fixed`](#stage_fixed)
- [`interactive`](#interactive)
- [`use_stdin`](#use_stdin)
- [`priority`](#priority)
- [Script](#script)
- [`runner`](#runner)
- [`skip`](#skip)
Expand Down Expand Up @@ -339,7 +340,9 @@ remote:
ref: v1.0.0
```

> :warning: Please, note that if you initially had `ref` option, ran `lefthook install`, and then removed it, lefthook won't decide which branch/tag to use as a ref. So, if you added it once, please, use it always to avoid issues in local setups.
> **Note**
>
> :warning: If you initially had `ref` option, ran `lefthook install`, and then removed it, lefthook won't decide which branch/tag to use as a ref. So, if you added it once, please, use it always to avoid issues in local setups.

### `config`

Expand Down Expand Up @@ -427,9 +430,9 @@ pre-push:
run: yarn test
```

**Notes**

If used with [`parallel`](#parallel) the output can be a mess, so please avoid setting both options to `true`.
> **Note**
>
> If used with [`parallel`](#parallel) the output can be a mess, so please avoid setting both options to `true`.

### `exclude_tags`

Expand Down Expand Up @@ -599,7 +602,9 @@ pre-push:

Simply run `bundle exec rubocop` on all files with `.rb` extension excluding `application.rb` and `routes.rb` files.

**Note:** `--force-exclusion` will apply `Exclude` configuration setting of Rubocop.
> **Note**
>
> `--force-exclusion` will apply `Exclude` configuration setting of Rubocop.

```yml
# lefthook.yml
Expand Down Expand Up @@ -785,9 +790,9 @@ pre-commit:

You can force a command, script, or the whole hook to execute only in certain conditions. This option acts like the opposite of [`skip`](#skip). It accepts the same values but skips execution only if the condition is not satisfied.

**Note**

`skip` option takes precedence over `only` option, so if you have conflicting conditions the execution will be skipped.
> **Note**
>
> `skip` option takes precedence over `only` option, so if you have conflicting conditions the execution will be skipped.

**Example**

Expand Down Expand Up @@ -1084,14 +1089,46 @@ pre-commit:

**Default: `false`**

> **Note**
>
> If you want to pass stdin to your command or script but don't need to get the input from CLI, use [`use_stdin`](#use_stdin) option instead.


Whether to use interactive mode. This applies the certain behavior:
- All `interactive` commands/scripts are executed after non-interactive.
- When executing, lefthook tries to open /dev/tty (Linux/Unix only) and use it as stdin.
- When [`no_tty`](#no_tty) option is set, `interactive` is ignored.

**Note**
### `priority`

**Default: `0`**

> **Note**
>
> This option makes sense only when `parallel: false` or `piped: true` is set.
>
> Value `0` is considered an `+Infinity`, so commands with `priority: 0` or without this setting will be run at the very end.

If you want to pass stdin to your command or script but don't need to get the input from CLI, use [`use_stdin`](#use_stdin) option instead.
Set command priority from 1 to +Infinity. This option can be used to configure the order of the sequential commands.

**Example**

```yml
# lefthook.yml
post-checkout:
piped: true
commands:
db-create:
priority: 1
run: rails db:create
db-migrate:
priority: 2
run: rails db:migrate
db-seed:
priority: 3
run: rails db:seed
```

## Script

Expand Down Expand Up @@ -1141,11 +1178,11 @@ When you try to commit `git commit -m "bad commit text"` script `template_checke

### `use_stdin`

Pass the stdin from the OS to the command/script.
> **Note**
>
> With many commands or scripts having `use_stdin: true`, only one will receive the data. The others will have nothing. If you need to pass the data from stdin to every command or script, please, submit a [feature request](https://github.com/evilmartians/lefthook/issues/new?assignees=&labels=feature+request&projects=&template=feature_request.md).

**Note**

With many commands or scripts having `use_stdin: true`, only one will receive the data. The others will have nothing. If you need to pass the data from stdin to every command or script, please, submit a [feature request](https://github.com/evilmartians/lefthook/issues/new?assignees=&labels=feature+request&projects=&template=feature_request.md).
Pass the stdin from the OS to the command/script.

**Example**

Expand Down
5 changes: 3 additions & 2 deletions internal/config/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ type Command struct {
Files string `mapstructure:"files" yaml:",omitempty" json:"files,omitempty" toml:"files,omitempty"`
Env map[string]string `mapstructure:"env" yaml:",omitempty" json:"env,omitempty" toml:"env,omitempty"`

Root string `mapstructure:"root" yaml:",omitempty" json:"root,omitempty" toml:"root,omitempty"`
Exclude string `mapstructure:"exclude" yaml:",omitempty" json:"exclude,omitempty" toml:"exclude,omitempty"`
Root string `mapstructure:"root" yaml:",omitempty" json:"root,omitempty" toml:"root,omitempty"`
Exclude string `mapstructure:"exclude" yaml:",omitempty" json:"exclude,omitempty" toml:"exclude,omitempty"`
Priority int `mapstructure:"priority" yaml:",omitempty" json:"priority,omitempty" toml:"priority,omitempty"`

FailText string `mapstructure:"fail_text" yaml:"fail_text,omitempty" json:"fail_text,omitempty" toml:"fail_text,omitempty"`
Interactive bool `mapstructure:"interactive" yaml:",omitempty" json:"interactive,omitempty" toml:"interactive,omitempty"`
Expand Down
24 changes: 19 additions & 5 deletions internal/lefthook/run/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ func (r *Runner) runScripts(ctx context.Context, dir string) {
continue
}

if script.Interactive {
if script.Interactive && !r.Hook.Piped {
interactiveScripts = append(interactiveScripts, file)
continue
}
Expand Down Expand Up @@ -332,7 +332,7 @@ func (r *Runner) runCommands(ctx context.Context) {
}
}

sortAlnum(commands)
sortCommands(commands, r.Hook.Commands)

interactiveCommands := make([]string, 0)
var wg sync.WaitGroup
Expand All @@ -343,7 +343,7 @@ func (r *Runner) runCommands(ctx context.Context) {
continue
}

if r.Hook.Commands[name].Interactive {
if r.Hook.Commands[name].Interactive && !r.Hook.Piped {
interactiveCommands = append(interactiveCommands, name)
continue
}
Expand Down Expand Up @@ -532,12 +532,26 @@ func (r *Runner) logExecute(name string, err error, out io.Reader) {
}
}

// sortAlnum sorts the command names by preceding numbers if they occur.
// sortCommands sorts the command names by preceding numbers if they occur and special priority if it is set.
// If the command names starts with letter the command name will be sorted alphabetically.
//
// []string{"1_command", "10command", "3 command", "command5"} // -> 1_command, 3 command, 10command, command5
func sortAlnum(strs []string) {
func sortCommands(strs []string, commands map[string]*config.Command) {
sort.SliceStable(strs, func(i, j int) bool {
commandI, iOk := commands[strs[i]]
commandJ, jOk := commands[strs[j]]

if iOk && commandI.Priority != 0 || jOk && commandJ.Priority != 0 {
if !iOk || commandI.Priority == 0 {
return false
}
if !jOk || commandJ.Priority == 0 {
return true
}

return commandI.Priority < commandJ.Priority
}

numEnds := -1
for idx, ch := range strs[i] {
if unicode.IsDigit(ch) {
Expand Down
35 changes: 35 additions & 0 deletions internal/lefthook/run/runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -918,3 +918,38 @@ func TestReplaceQuoted(t *testing.T) {
})
}
}

func TestSortCommands(t *testing.T) {
for i, tt := range [...]struct {
name string
names []string
commands map[string]*config.Command
result []string
}{
{
name: "alphanumeric sort",
names: []string{"10_a", "1_a", "2_a", "5_a"},
commands: map[string]*config.Command{},
result: []string{"1_a", "2_a", "5_a", "10_a"},
},
{
name: "partial priority",
names: []string{"10_a", "1_a", "2_a", "5_a"},
commands: map[string]*config.Command{
"5_a": {Priority: 10},
"2_a": {Priority: 1},
"10_a": {},
},
result: []string{"2_a", "5_a", "1_a", "10_a"},
},
} {
t.Run(fmt.Sprintf("%d: %s", i+1, tt.name), func(t *testing.T) {
sortCommands(tt.names, tt.commands)
for i, name := range tt.result {
if tt.names[i] != name {
t.Errorf("Not matching on index %d: %s != %s", i, name, tt.names[i])
}
}
})
}
}

0 comments on commit 96e1fb5

Please sign in to comment.