Skip to content

Commit

Permalink
feat(remotes): add remotes and configs
Browse files Browse the repository at this point in the history
  • Loading branch information
NikitaCOEUR committed Jan 13, 2024
1 parent 5f79b8c commit 7e774af
Show file tree
Hide file tree
Showing 10 changed files with 499 additions and 122 deletions.
220 changes: 161 additions & 59 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,63 +2,63 @@

Lefthook [supports](#config-file) YAML, JSON, and TOML configuration. In this document `lefthook.yml` is used for simplicity.

- [Config file](#config-file)
- [Top level options](#top-level-options)
- [`assert_lefthook_installed`](#assert_lefthook_installed)
- [`colors`](#colors)
- [`yellow`](#colors)
- [`green`](#colors)
- [`cyan`](#colors)
- [`gray`](#colors)
- [`red`](#colors)
- [`extends`](#extends)
- [`min_version`](#min_version)
- [`no_tty`](#no_tty)
- [`rc`](#rc)
- [`skip_output`](#skip_output)
- [`source_dir`](#source_dir)
- [`source_dir_local`](#source_dir_local)
- [`remote` (Beta :test_tube:)](#remote)
- [`git_url`](#git_url)
- [`ref`](#ref)
- [`config`](#config)
- [Hook](#git-hook)
- [`skip`](#skip)
- [`only`](#only)
- [`files`](#files-global)
- [`parallel`](#parallel)
- [`piped`](#piped)
- [`follow`](#follow)
- [`exclude_tags`](#exclude_tags)
- [`commands`](#commands)
- [`scripts`](#scripts)
- [Command](#command)
- [`run`](#run)
- [`skip`](#skip)
- [`only`](#only)
- [`tags`](#tags)
- [`glob`](#glob)
- [`files`](#files)
- [`env`](#env)
- [`root`](#root)
- [`exclude`](#exclude)
- [`fail_text`](#fail_text)
- [`stage_fixed`](#stage_fixed)
- [`interactive`](#interactive)
- [`use_stdin`](#use_stdin)
- [`priority`](#priority)
- [Script](#script)
- [`runner`](#runner)
- [`skip`](#skip)
- [`only`](#only)
- [`tags`](#tags)
- [`env`](#env)
- [`fail_text`](#fail_text)
- [`stage_fixed`](#stage_fixed)
- [`interactive`](#interactive)
- [`use_stdin`](#use_stdin)
- [Examples](#examples)
- [More info](#more-info)
- [Configure lefthook](#configure-lefthook)
- [Config file](#config-file)
- [Top level options](#top-level-options)
- [`assert_lefthook_installed`](#assert_lefthook_installed)
- [`colors`](#colors)
- [`no_tty`](#no_tty)
- [`extends`](#extends)
- [`min_version`](#min_version)
- [`skip_output`](#skip_output)
- [`source_dir`](#source_dir)
- [`source_dir_local`](#source_dir_local)
- [`rc`](#rc)
- [`remote` // DEPRECATED show remotes instead](#remote--deprecated-show-remotes-instead)
- [`git_url`](#git_url)
- [`ref`](#ref)
- [`config` // DEPRECATED use configs like specified in `remotes`](#config--deprecated-use-configs-like-specified-in-remotes)
- [`remotes` (Replace `remote`)](#remotes-replace-remote)
- [`git_url`](#git_url-1)
- [`ref`](#ref-1)
- [`configs`](#configs)
- [Remotes full example :](#remotes-full-example-)
- [Git hook](#git-hook)
- [`files` (global)](#files-global)
- [`parallel`](#parallel)
- [`piped`](#piped)
- [`follow`](#follow)
- [`exclude_tags`](#exclude_tags)
- [`commands`](#commands)
- [`scripts`](#scripts)
- [Command](#command)
- [`run`](#run)
- [`{files}` template](#files-template)
- [`{staged_files}` template](#staged_files-template)
- [`{push_files}` template](#push_files-template)
- [`{all_files}` template](#all_files-template)
- [`{cmd}` template](#cmd-template)
- [Git arguments](#git-arguments)
- [Rubocop](#rubocop)
- [Quotes](#quotes)
- [`skip`](#skip)
- [`only`](#only)
- [`tags`](#tags)
- [`glob`](#glob)
- [`files`](#files)
- [`env`](#env)
- [Extending PATH](#extending-path)
- [`root`](#root)
- [`exclude`](#exclude)
- [`fail_text`](#fail_text)
- [`stage_fixed`](#stage_fixed)
- [`interactive`](#interactive)
- [`priority`](#priority)
- [Script](#script)
- [`use_stdin`](#use_stdin)
- [`runner`](#runner)
- [Examples](#examples)
- [More info](#more-info)

----

Expand Down Expand Up @@ -286,7 +286,7 @@ $ lefthook install -f

Now any program that runs your hooks will have a tweaked PATH environment variable and will be able to get `nvm` :wink:

## `remote`
## `remote` // DEPRECATED show remotes instead

> :test_tube: This feature is in **Beta** version

Expand Down Expand Up @@ -346,7 +346,7 @@ remote:
>
> :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`
### `config` // DEPRECATED use configs like specified in `remotes`

**Default:** `lefthook.yml`

Expand All @@ -363,6 +363,108 @@ remote:
config: examples/ruby-linter.yml
```

## `remotes` (Replace `remote`)

> :test_tube: This feature is in **Beta** version

You can provide multiple remotes configs if you want to share yours lefthook configurations across many projects. Lefthook will automatically download and merge configurations into your local `lefthook.yml`.

You can use [`extends`](#extends) related to the config file (not absolute paths).

If you provide [`scripts`](#scripts) in a remote file, the [scripts](#source_dir) folder must be in the **root of the repository**.

**Note**

Configuration in `remotes` will be merged to configuration in `lefthook.yml`, so the priority will be the following:

- `lefthook.yml`
- `remotes`
- `lefthook-local.yml`

This can be changed in the future. For convenience, please use `remotes` configuration without any hooks configuration in `lefthook.yml`.

### `git_url`

A URL to Git repository. It will be accessed with privileges of the machine lefthook runs on.

**Example**

```yml
# lefthook.yml
remotes:
- git_url: [email protected]:evilmartians/lefthook
```

Or

```yml
# lefthook.yml
remotes:
- git_url: https://github.com/evilmartians/lefthook
```

### `ref`

An optional *branch* or *tag* name.

**Example**

```yml
# lefthook.yml
remotes:
- git_url: [email protected]:evilmartians/lefthook
ref: v1.0.0
```

> **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.

### `configs`

**Default:** `- lefthook.yml`

An optional array of config paths from remote's root.

**Example**

```yml
# lefthook.yml
remotes:
- git_url: [email protected]:evilmartians/remote
ref: v1.0.0
configs:
- examples/ruby-linter.yml
- examples/test.yml
```

### Remotes full example :

A more complete example here :
```yml
# lefthook.yml
remotes:
- git_url: [email protected]:evilmartians/remote
ref: v1.0.0
configs:
- examples/ruby-linter.yml
- examples/test.yml
- git_url : https://github.com:example/repository
configs:
- lefthooks/pre_commit.yml
- lefthooks/post_merge.yml
- git_url : https://github.com:example2/repository2
ref: specific_branch
configs:
- example/pre-push.yml
```

## Git hook

Commands and scripts are defined for git hooks. You can defined a hook for all hooks listed in [this file](../internal/config/available_hooks.go).
Expand Down
9 changes: 5 additions & 4 deletions examples/remote/ping.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# Test `remote` config of lefthook.
# Test `remotes` config of lefthook.
#
# # lefthook.yml
#
# remote:
# git_url: [email protected]:evilmartians/lefthook
# config: examples/remote/ping.yml
# remotes:
# - git_url: [email protected]:evilmartians/lefthook
# configs:
# - examples/remote/ping.yml
#
# $ lefthook run pre-commit

Expand Down
3 changes: 2 additions & 1 deletion internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ type Config struct {
NoTTY bool `mapstructure:"no_tty,omitempty"`
AssertLefthookInstalled bool `mapstructure:"assert_lefthook_installed,omitempty"`
Colors interface{} `mapstructure:"colors,omitempty"`
Remote *Remote `mapstructure:"remote,omitempty"`
Remote *Remote `mapstructure:"remote,omitempty"` // Deprecated in favor of Remotes
Remotes []*Remote `mapstructure:"remotes,omitempty"`

Hooks map[string]*Hook `mapstructure:"-"`
}
Expand Down
83 changes: 58 additions & 25 deletions internal/config/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func readOne(fs afero.Fs, path string, names []string) (*viper.Viper, error) {
return nil, NotFoundError{fmt.Sprintf("No config files with names %q could not be found in \"%s\"", names, path)}
}

// mergeAll merges (.lefthook or lefthook) and (extended config) and (remote)
// mergeAll merges (.lefthook or lefthook) and (extended config) and (remotes)
// and (.lefthook-local or .lefthook-local) configs.
func mergeAll(fs afero.Fs, repo *git.Repository) (*viper.Viper, error) {
extends, err := readOne(fs, repo.RootPath, []string{"lefthook", ".lefthook"})
Expand All @@ -106,7 +106,7 @@ func mergeAll(fs afero.Fs, repo *git.Repository) (*viper.Viper, error) {
return nil, err
}

if err := mergeRemote(fs, repo, extends); err != nil {
if err := mergeRemotes(fs, repo, extends); err != nil {
return nil, err
}

Expand All @@ -124,42 +124,75 @@ func mergeAll(fs afero.Fs, repo *git.Repository) (*viper.Viper, error) {
return extends, nil
}

// mergeRemote merges remote config to the current one.
func mergeRemote(fs afero.Fs, repo *git.Repository, v *viper.Viper) error {
var remote Remote
err := v.UnmarshalKey("remote", &remote)
// mergeRemotes merges remotes config to the current one.
func mergeRemotes(fs afero.Fs, repo *git.Repository, v *viper.Viper) error {
var remotes []*Remote
var remote *Remote // Use for backward compatibility

err := v.UnmarshalKey("remotes", &remotes)
if err != nil {
return err
}

if !remote.Configured() {
return nil
// Use for backward compatibility
err = v.UnmarshalKey("remote", &remote)
if err != nil {
return err
}

remotePath := repo.RemoteFolder(remote.GitURL)
configFile := DefaultConfigName
if len(remote.Config) > 0 {
configFile = remote.Config
// Use for backward compatibility
// If "remote" key exists, append it to "remotes"
if remote != nil {
// Not logged because it's breaking tests
// log.Warn("DEPRECATED: \"remote\" key is deprecated, use \"remotes\" instead")
remotes = append(remotes, remote)
}
configPath := filepath.Join(remotePath, configFile)

log.Debugf("Merging remote config: %s", configPath)
for _, remote := range remotes {
if !remote.Configured() {
continue
}

_, err = fs.Stat(configPath)
if err != nil {
return nil
}
// Use for backward compatibility with "remote(s).config" instead of "remote(s).configs"
if remote.Config != "" {
// Not logged because it's breaking tests
// log.Warn("DEPRECATED: \"config\" key is deprecated, use \"configs\" instead for remotes")
remote.Configs = append(remote.Configs, remote.Config)
}

if err := merge("remote", configPath, v); err != nil {
return err
}
if len(remote.Configs) == 0 {
remote.Configs = append(remote.Configs, DefaultConfigName)
}

if err := extend(v, filepath.Dir(configPath)); err != nil {
return err
for _, config := range remote.Configs {
remotePath := repo.RemoteFolder(remote.GitURL, remote.Ref)
configFile := config
configPath := filepath.Join(remotePath, configFile)

log.Debugf("Merging remote config: %s", configPath)

_, err = fs.Stat(configPath)
if err != nil {
continue
}

if err = merge("remotes", configPath, v); err != nil {
return err
}

if err = extend(v, filepath.Dir(configPath)); err != nil {
return err
}
}

// Reset extends to omit issues when extending with remote extends.
err = v.MergeConfigMap(map[string]interface{}{"extends": nil})
if err != nil {
return err
}
}

// Reset extends to omit issues when extending with remote extends.
return v.MergeConfigMap(map[string]interface{}{"extends": nil})
return nil
}

// extend merges all files listed in 'extends' option into the config.
Expand Down
Loading

0 comments on commit 7e774af

Please sign in to comment.