Skip to content

Commit

Permalink
fix: allow multiple levels of extends (#755)
Browse files Browse the repository at this point in the history
* fix: allow recursive extends
* chore: add integrity test
* fix: detect recursion
  • Loading branch information
mrexox committed Jun 21, 2024
1 parent 9c0a884 commit 3d44375
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 15 deletions.
56 changes: 41 additions & 15 deletions internal/config/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,21 +63,27 @@ func Load(fs afero.Fs, repo *git.Repository) (*Config, error) {
}

func read(fs afero.Fs, path string, name string) (*viper.Viper, error) {
v := newViper(fs, path)
v.SetConfigName(name)

if err := v.ReadInConfig(); err != nil {
return nil, err
}

return v, nil
}

func newViper(fs afero.Fs, path string) *viper.Viper {
v := viper.New()
v.SetFs(fs)
v.AddConfigPath(path)
v.SetConfigName(name)

// Allow overwriting settings with ENV variables
v.SetEnvPrefix("LEFTHOOK")
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
v.AutomaticEnv()

if err := v.ReadInConfig(); err != nil {
return nil, err
}

return v, nil
return v
}

func readOne(fs afero.Fs, path string, names []string) (*viper.Viper, error) {
Expand Down Expand Up @@ -109,7 +115,7 @@ func mergeAll(fs afero.Fs, repo *git.Repository) (*viper.Viper, error) {
return nil, err
}

if err := extend(extends, repo.RootPath); err != nil {
if err := extend(fs, extends, repo.RootPath); err != nil {
return nil, err
}

Expand All @@ -125,7 +131,7 @@ func mergeAll(fs afero.Fs, repo *git.Repository) (*viper.Viper, error) {
// Local extends need to be re-applied only if they have different settings
localExtends := extends.GetStringSlice("extends")
if !slices.Equal(globalExtends, localExtends) {
if err = extend(extends, repo.RootPath); err != nil {
if err = extend(fs, extends, repo.RootPath); err != nil {
return nil, err
}
}
Expand Down Expand Up @@ -190,7 +196,7 @@ func mergeRemotes(fs afero.Fs, repo *git.Repository, v *viper.Viper) error {
return err
}

if err = extend(v, filepath.Dir(configPath)); err != nil {
if err = extend(fs, v, filepath.Dir(configPath)); err != nil {
return err
}
}
Expand All @@ -206,12 +212,34 @@ func mergeRemotes(fs afero.Fs, repo *git.Repository, v *viper.Viper) error {
}

// extend merges all files listed in 'extends' option into the config.
func extend(v *viper.Viper, root string) error {
for i, path := range v.GetStringSlice("extends") {
func extend(fs afero.Fs, v *viper.Viper, root string) error {
return extendRecursive(fs, v, root, make(map[string]struct{}))
}

// extendRecursive merges extends.
// If extends contain other extends they get merged too.
func extendRecursive(fs afero.Fs, v *viper.Viper, root string, extends map[string]struct{}) error {
for _, path := range v.GetStringSlice("extends") {
if _, contains := extends[path]; contains {
return fmt.Errorf("possible recursion in extends: path %s is specified multiple times", path)
}
extends[path] = struct{}{}

if !filepath.IsAbs(path) {
path = filepath.Join(root, path)
}
if err := merge(fmt.Sprintf("extend_%d", i), path, v); err != nil {

extendV := newViper(fs, root)
extendV.SetConfigFile(path)
if err := extendV.ReadInConfig(); err != nil {
return err
}

if err := extendRecursive(fs, extendV, root, extends); err != nil {
return err
}

if err := v.MergeConfigMap(extendV.AllSettings()); err != nil {
return err
}
}
Expand All @@ -222,9 +250,7 @@ func extend(v *viper.Viper, root string) error {
// merge merges the configuration using viper builtin MergeInConfig.
func merge(name, path string, v *viper.Viper) error {
v.SetConfigName(name)
if len(path) > 0 {
v.SetConfigFile(path)
}
v.SetConfigFile(path)
return v.MergeInConfig()
}

Expand Down
80 changes: 80 additions & 0 deletions testdata/many_extends_levels.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
[windows] skip

exec git init
exec lefthook dump
cmp stdout dump.yml
! stderr .

-- lefthook.yml --
extends:
- extends/e1.yml

pre-commit:
commands:
echo:
run: echo 0

-- extends/e1.yml --
extends:
- extends/e2.yml

pre-commit:
commands:
echo:
run: echo 1
skip: true

e1:
commands:
echo:
run: e1

-- extends/e2.yml --
extends:
- extends/e3.yml

pre-commit:
commands:
echo:
run: echo 2
tags: ["backend"]

e2:
commands:
echo:
run: e2

-- extends/e3.yml --
pre-commit:
commands:
echo:
glob: 3

e3:
commands:
echo:
run: e3

-- dump.yml --
e1:
commands:
echo:
run: e1
e2:
commands:
echo:
run: e2
e3:
commands:
echo:
run: e3
extends:
- extends/e3.yml
pre-commit:
commands:
echo:
run: echo 2
skip: true
tags:
- backend
glob: "3"

0 comments on commit 3d44375

Please sign in to comment.