diff --git a/docs/configuration.md b/docs/configuration.md index 773323ad..6cc3e41c 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -7,6 +7,10 @@ - [`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) - [`files`](#files-global) - [`parallel`](#parallel) @@ -57,7 +61,7 @@ colors: false ### `extends` -You can extend your config with another one YAML file. +You can extend your config with another one YAML file. Its content will be merged. Extends for `lefthook.yml`, `lefthook-local.yml`, and [`remote`](#remote) configs are handled separately, so you can have different extends in these files. **Example** @@ -65,15 +69,12 @@ You can extend your config with another one YAML file. # lefthook.yml extends: - - $HOME/work/lefthook-extend.yml - - $HOME/work/lefthook-extend-2.yml + - /home/user/work/lefthook-extend.yml + - /home/user/work/lefthook-extend-2.yml + - lefthook-extends/file.yml + - ../extend.yml ``` -**Notes** - -Files for extend should *not* be named "lefthook.yml". All file names should be unique. - - ### `min_version` If you want to specify a minimum version for lefthook binary (e.g. if you need some features older versions don't have) you can set this option. @@ -138,6 +139,80 @@ Change a directory for *local* script files (not stored in VCS). This option is useful if you have a `lefthook-local.yml` config file and want to reference different scripts there. +## `remote` + +> :test_tube: This feature is in **Beta** version + +You can provide a remote config if you want to share your lefthook configuration across many projects. Lefthook will automatically download and merge the configuration 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 `remote` will be merged to confiuration in `lefthook.yml`, so the priority will be the following: + +- `lefthook.yml` +- `remote` +- `lefthook-local.yml` + +This can be changed in the future. For convenience, please use `remote` configuration without any hooks configuration in `lefthook.yml`. + +### `git_url` + +A URL to Git repository. It will be accessed with priveleges of the machine lefthook runs on. + +**Example** + +```yml +# lefthook.yml + +remote: + git_url: git@github.com:evilmartians/lefthook +``` + +Or + +```yml +# lefthook.yml + +remote: + git_url: https://github.com/evilmartians/lefthook +``` + +### `ref` + +An optional *branch* or *tag* name. + +**Example** + +```yml +# lefthook.yml + +remote: + git_url: git@github.com:evilmartians/lefthook + ref: v1.0.0 +``` + + +### `config` + +**Default:** `lefthook.yml` + +An optional config path from remote's root. + +**Example** + +```yml +# lefthook.yml + +remote: + git_url: git@github.com:evilmartians/remote + ref: v1.0.0 + config: examples/ruby-linter.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). diff --git a/examples/remote/ping.yml b/examples/remote/ping.yml new file mode 100644 index 00000000..aecbe714 --- /dev/null +++ b/examples/remote/ping.yml @@ -0,0 +1,13 @@ +# Test `remote` config of lefthook. +# +# # lefthook.yml +# +# remote: +# git_url: git@github.com:evilmartians/lefthook +# config: examples/remote/ping.yml +# +# $ lefthook run pre-commit + +pre-commit: + commands: + run: echo pong diff --git a/go.mod b/go.mod index 0a80a6ce..b7b1d94a 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/mattn/go-colorable v0.1.12 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mattn/go-isatty v0.0.14 github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.1 // indirect github.com/spf13/cast v1.5.0 // indirect diff --git a/internal/config/config.go b/internal/config/config.go index 9ad25fec..2e6377af 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -7,6 +7,7 @@ import ( type Config struct { Colors bool `mapstructure:"colors"` Extends []string `mapstructure:"extends"` + Remote Remote `mapstructure:"remote"` MinVersion string `mapstructure:"min_version"` SkipOutput []string `mapstructure:"skip_output"` SourceDir string `mapstructure:"source_dir"` diff --git a/internal/config/load.go b/internal/config/load.go index 560a1c0b..66563c51 100644 --- a/internal/config/load.go +++ b/internal/config/load.go @@ -1,15 +1,20 @@ package config import ( + "fmt" "path/filepath" "regexp" "strings" "github.com/spf13/afero" "github.com/spf13/viper" + + "github.com/evilmartians/lefthook/internal/git" + "github.com/evilmartians/lefthook/internal/log" ) const ( + DefaultConfigName = "lefthook.yml" DefaultSourceDir = ".lefthook" DefaultSourceDirLocal = ".lefthook-local" DefaultColorsEnabled = true @@ -18,13 +23,13 @@ const ( var hookKeyRegexp = regexp.MustCompile(`^(?P[^.]+)\.(scripts|commands)`) // Loads configs from the given directory with extensions. -func Load(fs afero.Fs, path string) (*Config, error) { - global, err := read(fs, path, "lefthook") +func Load(fs afero.Fs, repo *git.Repository) (*Config, error) { + global, err := read(fs, repo.RootPath, "lefthook") if err != nil { return nil, err } - extends, err := mergeAllExtends(fs, path) + extends, err := mergeAll(fs, repo) if err != nil { return nil, err } @@ -61,43 +66,93 @@ func read(fs afero.Fs, path string, name string) (*viper.Viper, error) { return v, nil } -// Merges extends from .lefthook and .lefthook-local. -func mergeAllExtends(fs afero.Fs, path string) (*viper.Viper, error) { - extends, err := read(fs, path, "lefthook") +// mergeAll merges remotes and extends from .lefthook and .lefthook-local. +func mergeAll(fs afero.Fs, repo *git.Repository) (*viper.Viper, error) { + extends, err := read(fs, repo.RootPath, "lefthook") if err != nil { return nil, err } - if err := extend(fs, extends); err != nil { + if err := extend(extends, repo.RootPath); err != nil { return nil, err } - extends.SetConfigName("lefthook-local") - if err := extends.MergeInConfig(); err != nil { + if err := mergeRemote(fs, repo, extends); err != nil { + return nil, err + } + + if err := merge("lefthook-local", "", extends); err != nil { if _, notFoundErr := err.(viper.ConfigFileNotFoundError); !notFoundErr { return nil, err } } - if err := extend(fs, extends); err != nil { + if err := extend(extends, repo.RootPath); err != nil { return nil, err } return extends, nil } -func extend(fs afero.Fs, v *viper.Viper) error { - for _, path := range v.GetStringSlice("extends") { - name := strings.TrimSuffix(filepath.Base(path), filepath.Ext(path)) +// 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) + if err != nil { + return err + } - another, err := read(fs, filepath.Dir(path), name) - if err != nil { - return err + if !remote.Configured() { + return nil + } + + remotePath := repo.RemoteFolder(remote.GitURL) + configFile := DefaultConfigName + if len(remote.Config) > 0 { + configFile = remote.Config + } + configPath := filepath.Join(remotePath, configFile) + + log.Debugf("Merging remote config: %s", configPath) + + _, err = fs.Stat(configPath) + if err != nil { + return nil + } + + if err := merge("remote", configPath, v); err != nil { + return err + } + + if err := extend(v, filepath.Dir(configPath)); err != nil { + return err + } + + return nil +} + +// 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") { + if !filepath.IsAbs(path) { + path = filepath.Join(root, path) } - if err = v.MergeConfigMap(another.AllSettings()); err != nil { + if err := merge(fmt.Sprintf("extend_%d", i), path, v); err != nil { return err } } + return nil +} + +// 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) + } + if err := v.MergeInConfig(); err != nil { + return err + } return nil } @@ -112,7 +167,7 @@ func unmarshalConfigs(base, extra *viper.Viper, c *Config) error { } // For extra non-git hooks. - // This behavior will be deprecated in next versions. + // This behavior may be deprecated in next versions. for _, maybeHook := range base.AllKeys() { if !hookKeyRegexp.MatchString(maybeHook) { continue diff --git a/internal/config/load_test.go b/internal/config/load_test.go index ecf343d8..1d624d93 100644 --- a/internal/config/load_test.go +++ b/internal/config/load_test.go @@ -3,11 +3,14 @@ package config import ( "fmt" "path/filepath" + "strings" "testing" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/spf13/afero" + + "github.com/evilmartians/lefthook/internal/git" ) func TestLoad(t *testing.T) { @@ -17,29 +20,30 @@ func TestLoad(t *testing.T) { } for i, tt := range [...]struct { - name string - global []byte - local []byte - result *Config + name string + global, local, remote string + remoteConfigPath string + extends map[string]string + result *Config }{ { name: "simple", - global: []byte(` + global: ` pre-commit: commands: tests: runner: yarn test # Using deprecated field -`), - local: []byte(` +`, + local: ` post-commit: commands: ping-done: run: curl -x POST status.com/done -`), +`, result: &Config{ SourceDir: DefaultSourceDir, SourceDirLocal: DefaultSourceDirLocal, - Colors: true, // defaults to true + Colors: DefaultColorsEnabled, Hooks: map[string]*Hook{ "pre-commit": { Parallel: false, @@ -63,7 +67,7 @@ post-commit: }, { name: "with overrides", - global: []byte(` + global: ` min_version: 0.6.0 source_dir: $HOME/sources source_dir_local: $HOME/sources_local @@ -81,8 +85,8 @@ pre-commit: scripts: "format.sh": runner: bash -`), - local: []byte(` +`, + local: ` min_version: 1.0.0 colors: false @@ -101,7 +105,7 @@ pre-push: rubocop: run: bundle exec rubocop tags: [backend, linter] -`), +`, result: &Config{ MinVersion: "1.0.0", Colors: false, @@ -143,7 +147,7 @@ pre-push: }, { name: "with extra hooks", - global: []byte(` + global: ` tests: commands: tests: @@ -153,11 +157,11 @@ lints: scripts: "linter.sh": runner: bash -`), +`, result: &Config{ SourceDir: DefaultSourceDir, SourceDirLocal: DefaultSourceDirLocal, - Colors: true, // defaults to true + Colors: DefaultColorsEnabled, Hooks: map[string]*Hook{ "tests": { Parallel: false, @@ -177,18 +181,235 @@ lints: }, }, }, + { + name: "with remote", + global: ` +remote: + git_url: git@github.com:evilmartians/lefthook +`, + remote: ` +pre-commit: + commands: + lint: + run: yarn lint + scripts: + "test.sh": + runner: bash +`, + remoteConfigPath: filepath.Join(root, ".git", "info", "remotes", "lefthook", "lefthook.yml"), + result: &Config{ + SourceDir: DefaultSourceDir, + SourceDirLocal: DefaultSourceDirLocal, + Colors: DefaultColorsEnabled, + Remote: Remote{ + GitURL: "git@github.com:evilmartians/lefthook", + }, + Hooks: map[string]*Hook{ + "pre-commit": { + Commands: map[string]*Command{ + "lint": { + Run: "yarn lint", + }, + }, + Scripts: map[string]*Script{ + "test.sh": { + Runner: "bash", + }, + }, + }, + }, + }, + }, + { + name: "with remote and custom config name", + global: ` +remote: + git_url: git@github.com:evilmartians/lefthook + ref: v1.0.0 + config: examples/custom.yml + +pre-commit: + commands: + global: + run: echo 'Global!' + lint: + run: this will be overwritten +`, + remote: ` +pre-commit: + commands: + lint: + run: yarn lint + skip: true + scripts: + "test.sh": + runner: bash +`, + remoteConfigPath: filepath.Join(root, ".git", "info", "remotes", "lefthook", "examples", "custom.yml"), + result: &Config{ + SourceDir: DefaultSourceDir, + SourceDirLocal: DefaultSourceDirLocal, + Colors: DefaultColorsEnabled, + Remote: Remote{ + GitURL: "git@github.com:evilmartians/lefthook", + Ref: "v1.0.0", + Config: "examples/custom.yml", + }, + Hooks: map[string]*Hook{ + "pre-commit": { + Commands: map[string]*Command{ + "lint": { + Run: "yarn lint", + Skip: true, + }, + "global": { + Run: "echo 'Global!'", + }, + }, + Scripts: map[string]*Script{ + "test.sh": { + Runner: "bash", + }, + }, + }, + }, + }, + }, + { + name: "with extends", + global: ` +extends: + - global-extend.yml + +remote: + git_url: https://github.com/evilmartians/lefthook + config: examples/config.yml + +pre-push: + commands: + global: + run: echo global +`, + local: ` +extends: + - local-extend.yml + +pre-push: + commands: + local: + run: echo local +`, + remote: ` +extends: + - ../remote-extend.yml + +pre-push: + commands: + remote: + run: echo remote +`, + remoteConfigPath: filepath.Join(root, ".git", "info", "remotes", "lefthook", "examples", "config.yml"), + extends: map[string]string{ + "global-extend.yml": ` +pre-push: + scripts: + "global-extend": + runner: bash +`, + "local-extend.yml": ` +pre-push: + scripts: + "local-extend": + runner: bash +`, + ".git/info/remotes/lefthook/remote-extend.yml": ` +pre-push: + scripts: + "remote-extend": + runner: bash +`, + }, + result: &Config{ + SourceDir: DefaultSourceDir, + SourceDirLocal: DefaultSourceDirLocal, + Colors: DefaultColorsEnabled, + Remote: Remote{ + GitURL: "https://github.com/evilmartians/lefthook", + Config: "examples/config.yml", + }, + Extends: []string{"local-extend.yml"}, + Hooks: map[string]*Hook{ + "pre-push": { + Commands: map[string]*Command{ + "global": { + Run: "echo global", + }, + "local": { + Run: "echo local", + }, + "remote": { + Run: "echo remote", + }, + }, + Scripts: map[string]*Script{ + "global-extend": { + Runner: "bash", + }, + "local-extend": { + Runner: "bash", + }, + "remote-extend": { + Runner: "bash", + }, + }, + }, + }, + }, + }, } { fs := afero.Afero{Fs: afero.NewMemMapFs()} + repo := &git.Repository{ + Fs: fs, + RootPath: root, + InfoPath: filepath.Join(root, ".git", "info"), + } + t.Run(fmt.Sprintf("%d: %s", i, tt.name), func(t *testing.T) { - if err := fs.WriteFile(filepath.Join(root, "lefthook.yml"), tt.global, 0o644); err != nil { + if err := fs.WriteFile(filepath.Join(root, "lefthook.yml"), []byte(tt.global), 0o644); err != nil { t.Errorf("unexpected error: %s", err) } - if err := fs.WriteFile(filepath.Join(root, "lefthook-local.yml"), tt.local, 0o644); err != nil { + if err := fs.WriteFile(filepath.Join(root, "lefthook-local.yml"), []byte(tt.local), 0o644); err != nil { t.Errorf("unexpected error: %s", err) } - checkConfig, err := Load(fs.Fs, root) + if len(tt.remoteConfigPath) > 0 { + if err := fs.MkdirAll(filepath.Base(tt.remoteConfigPath), 0o755); err != nil { + t.Errorf("unexpected error: %s", err) + } + + if err := fs.WriteFile(tt.remoteConfigPath, []byte(tt.remote), 0o644); err != nil { + t.Errorf("unexpected error: %s", err) + } + } + + for name, content := range tt.extends { + path := filepath.Join( + root, + filepath.Join(strings.Split(name, "/")...), + ) + dir := filepath.Dir(path) + + if err := fs.MkdirAll(dir, 0o775); err != nil { + t.Errorf("unexpected error: %s", err) + } + + if err := fs.WriteFile(path, []byte(content), 0o644); err != nil { + t.Errorf("unexpected error: %s", err) + } + } + + checkConfig, err := Load(fs.Fs, repo) if err != nil { t.Errorf("should parse configs without errors: %s", err) diff --git a/internal/config/remote.go b/internal/config/remote.go new file mode 100644 index 00000000..525c59e3 --- /dev/null +++ b/internal/config/remote.go @@ -0,0 +1,11 @@ +package config + +type Remote struct { + GitURL string `mapstructure:"git_url"` + Ref string `mapstructure:"ref"` + Config string `mapstructure:"config"` +} + +func (r Remote) Configured() bool { + return len(r.GitURL) > 0 +} diff --git a/internal/git/remote.go b/internal/git/remote.go new file mode 100644 index 00000000..1f34a6ba --- /dev/null +++ b/internal/git/remote.go @@ -0,0 +1,93 @@ +package git + +import ( + "errors" + "os" + "path/filepath" + "strings" + + "github.com/evilmartians/lefthook/internal/log" +) + +const ( + remotesFolder = "remotes" + remotesFolderMode = 0o755 +) + +func (r *Repository) RemoteFolder(url string) string { + remotesPath := filepath.Join(r.InfoPath, remotesFolder) + + return filepath.Join( + remotesPath, + filepath.Base( + strings.TrimSuffix(url, filepath.Ext(url)), + ), + ) +} + +// SyncRemote clones or pulls the latest changes for a git repository that was +// specified as a remote config repository. If successful, the path to the root +// of the repository will be returned. +func (r *Repository) SyncRemote(url, ref string) error { + remotesPath := filepath.Join(r.InfoPath, remotesFolder) + + err := r.Fs.MkdirAll(remotesPath, remotesFolderMode) + if err != nil && !errors.Is(err, os.ErrExist) { + return err + } + + remotePath := filepath.Join( + remotesPath, + filepath.Base( + strings.TrimSuffix(url, filepath.Ext(url)), + ), + ) + + _, err = r.Fs.Stat(remotePath) + if err == nil { + if err := r.updateRemote(remotePath, ref); err != nil { + return err + } + + return nil + } + + if err := r.cloneRemote(remotesPath, url, ref); err != nil { + return err + } + + return nil +} + +func (r *Repository) updateRemote(path, ref string) error { + log.Debugf("Updating remote config repository: %s", path) + + cmdFetch := []string{"git", "-C", path, "pull", "--quiet"} + if len(ref) == 0 { + cmdFetch = append(cmdFetch, "origin", ref) + } + + _, err := execGit(strings.Join(cmdFetch, " ")) + if err != nil { + return err + } + + return nil +} + +func (r *Repository) cloneRemote(path, url, ref string) error { + log.Debugf("Cloning remote config repository: %v", path) + + cmdClone := []string{"git", "-C", path, "clone", "--quiet", "--depth", "1"} + if len(ref) > 0 { + cmdClone = append(cmdClone, "--branch", ref) + } + cmdClone = append(cmdClone, url) + + _, err := execGit(strings.Join(cmdClone, " ")) + if err != nil { + return err + } + + return nil +} diff --git a/internal/lefthook/add.go b/internal/lefthook/add.go index 150c073e..bcb05eb2 100644 --- a/internal/lefthook/add.go +++ b/internal/lefthook/add.go @@ -61,7 +61,7 @@ func (l *Lefthook) getSourceDirs() (global, local string) { global = config.DefaultSourceDir local = config.DefaultSourceDirLocal - cfg, err := config.Load(l.Fs, l.repo.RootPath) + cfg, err := config.Load(l.Fs, l.repo) if err == nil { if len(cfg.SourceDir) > 0 { global = cfg.SourceDir diff --git a/internal/lefthook/add_test.go b/internal/lefthook/add_test.go index 84f544d9..c750870b 100644 --- a/internal/lefthook/add_test.go +++ b/internal/lefthook/add_test.go @@ -144,7 +144,7 @@ source_dir_local: .source_dir_local for hook, content := range tt.existingHooks { path := hookPath(hook) - if err := fs.MkdirAll(filepath.Base(path), 0o755); err != nil { + if err := fs.MkdirAll(filepath.Dir(path), 0o755); err != nil { t.Errorf("unexpected error: %s", err) } if err := afero.WriteFile(fs, path, []byte(content), 0o644); err != nil { diff --git a/internal/lefthook/install.go b/internal/lefthook/install.go index 01f5537e..a54da87d 100644 --- a/internal/lefthook/install.go +++ b/internal/lefthook/install.go @@ -18,12 +18,11 @@ import ( ) const ( - configFileMode = 0o666 - checksumFileMode = 0o644 - configDefaultName = "lefthook.yml" - configGlob = "lefthook.y*ml" - timestampBase = 10 - timestampBitsize = 64 + configFileMode = 0o666 + checksumFileMode = 0o644 + configGlob = "lefthook.y*ml" + timestampBase = 10 + timestampBitsize = 64 ) var lefthookChecksumRegexp = regexp.MustCompile(`(\w+)\s+(\d+)`) @@ -48,6 +47,18 @@ func (l *Lefthook) Install(args *InstallArgs) error { return err } + if cfg.Remote.Configured() { + if err := l.repo.SyncRemote(cfg.Remote.GitURL, cfg.Remote.Ref); err != nil { + log.Warnf("Couldn't sync remotes. Will continue without them: %s", err) + } else { + // Reread the config file with synced remotes + cfg, err = l.readOrCreateConfig() + if err != nil { + return err + } + } + } + return l.createHooksIfNeeded(cfg, args.Force || args.Aggressive || l.Options.Force || l.Options.Aggressive) } @@ -63,7 +74,7 @@ func (l *Lefthook) readOrCreateConfig() (*config.Config, error) { } } - return config.Load(l.Fs, l.repo.RootPath) + return config.Load(l.Fs, l.repo) } func (l *Lefthook) configExists(path string) bool { @@ -82,7 +93,7 @@ func (l *Lefthook) configExists(path string) bool { } func (l *Lefthook) createConfig(path string) error { - file := filepath.Join(path, configDefaultName) + file := filepath.Join(path, config.DefaultConfigName) err := afero.WriteFile(l.Fs, file, templates.Config(), configFileMode) if err != nil { diff --git a/internal/lefthook/install_test.go b/internal/lefthook/install_test.go index 0eafaa8e..6fafcba6 100644 --- a/internal/lefthook/install_test.go +++ b/internal/lefthook/install_test.go @@ -294,7 +294,7 @@ post-commit: // Create files that should exist for hook, content := range tt.existingHooks { path := hookPath(hook) - if err := fs.MkdirAll(filepath.Base(path), 0o755); err != nil { + if err := fs.MkdirAll(filepath.Dir(path), 0o755); err != nil { t.Errorf("unexpected error: %s", err) } if err := afero.WriteFile(fs, path, []byte(content), 0o755); err != nil { diff --git a/internal/lefthook/run.go b/internal/lefthook/run.go index f7f97d2c..d24563f0 100644 --- a/internal/lefthook/run.go +++ b/internal/lefthook/run.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "os" + "path/filepath" "strings" "time" @@ -42,7 +43,7 @@ func (l *Lefthook) Run(hookName string, gitArgs []string) error { } // Load config - cfg, err := config.Load(l.Fs, l.repo.RootPath) + cfg, err := config.Load(l.Fs, l.repo) if err != nil { return err } @@ -70,7 +71,7 @@ func (l *Lefthook) Run(hookName string, gitArgs []string) error { // This line controls updating the git hook if config has changed if err = l.createHooksIfNeeded(cfg, false); err != nil { log.Warn( - `⚠️ There was a problem with synchronizing git hooks. + `⚠️ There was a problem with synchronizing git hooks. Run 'lefthook install' manually.`, ) } @@ -92,11 +93,24 @@ Run 'lefthook install' manually.`, resultChan := make(chan runner.Result, len(hook.Commands)+len(hook.Scripts)) run := runner.NewRunner(l.Fs, l.repo, hook, gitArgs, resultChan, logSettings) - go func() { - run.RunAll( - hookName, - []string{cfg.SourceDir, cfg.SourceDirLocal}, + sourceDirs := []string{ + filepath.Join(l.repo.RootPath, cfg.SourceDir), + filepath.Join(l.repo.RootPath, cfg.SourceDirLocal), + } + + if cfg.Remote.Configured() { + // Apend only source_dir, because source_dir_local doesn't make sense + sourceDirs = append( + sourceDirs, + filepath.Join( + l.repo.RemoteFolder(cfg.Remote.GitURL), + cfg.SourceDir, + ), ) + } + + go func() { + run.RunAll(hookName, sourceDirs) close(resultChan) }() diff --git a/internal/lefthook/runner/runner.go b/internal/lefthook/runner/runner.go index 910d4125..e90b3971 100644 --- a/internal/lefthook/runner/runner.go +++ b/internal/lefthook/runner/runner.go @@ -71,7 +71,7 @@ func (r *Runner) RunAll(hookName string, sourceDirs []string) { scriptDirs := make([]string, len(sourceDirs)) for _, sourceDir := range sourceDirs { scriptDirs = append(scriptDirs, filepath.Join( - r.repo.RootPath, sourceDir, hookName, + sourceDir, hookName, )) } diff --git a/internal/lefthook/runner/runner_test.go b/internal/lefthook/runner/runner_test.go index 6f34e36c..cb9799c7 100644 --- a/internal/lefthook/runner/runner_test.go +++ b/internal/lefthook/runner/runner_test.go @@ -189,7 +189,7 @@ func TestRunAll(t *testing.T) { }, { name: "with simple scripts", - sourceDirs: []string{config.DefaultSourceDir}, + sourceDirs: []string{filepath.Join(root, config.DefaultSourceDir)}, existingFiles: []string{ filepath.Join(root, config.DefaultSourceDir, hookName, "script.sh"), filepath.Join(root, config.DefaultSourceDir, hookName, "failing.js"), @@ -224,7 +224,7 @@ func TestRunAll(t *testing.T) { } for _, file := range tt.existingFiles { - if err := fs.MkdirAll(filepath.Base(file), 0o755); err != nil { + if err := fs.MkdirAll(filepath.Dir(file), 0o755); err != nil { t.Errorf("unexpected error: %s", err) } if err := afero.WriteFile(fs, file, []byte{}, 0o755); err != nil { diff --git a/internal/lefthook/uninstall_test.go b/internal/lefthook/uninstall_test.go index 29dd7704..308b00e2 100644 --- a/internal/lefthook/uninstall_test.go +++ b/internal/lefthook/uninstall_test.go @@ -111,7 +111,7 @@ func TestLefthookUninstall(t *testing.T) { // Prepare files that should exist for hook, content := range tt.existingHooks { path := hookPath(hook) - if err = fs.MkdirAll(filepath.Base(path), 0o755); err != nil { + if err = fs.MkdirAll(filepath.Dir(path), 0o755); err != nil { t.Errorf("unexpected error: %s", err) } if err = afero.WriteFile(fs, path, []byte(content), 0o755); err != nil {