-
Notifications
You must be signed in to change notification settings - Fork 220
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: configure hook remote repositories #323
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,10 +3,15 @@ package config | |
import ( | ||
"path/filepath" | ||
"regexp" | ||
"sort" | ||
"strings" | ||
"sync" | ||
|
||
"github.com/evilmartians/lefthook/internal/git" | ||
"github.com/evilmartians/lefthook/internal/log" | ||
Comment on lines
+10
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, I think it's better to devide merging configs and installing the |
||
"github.com/spf13/afero" | ||
"github.com/spf13/viper" | ||
"golang.org/x/sync/errgroup" | ||
) | ||
|
||
const ( | ||
|
@@ -24,7 +29,7 @@ func Load(fs afero.Fs, path string) (*Config, error) { | |
return nil, err | ||
} | ||
|
||
extends, err := mergeAllExtends(fs, path) | ||
extends, err := mergeAll(fs, path) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
@@ -61,13 +66,17 @@ 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) { | ||
// mergeAll merges remotes and extends from .lefthook and .lefthook-local. | ||
func mergeAll(fs afero.Fs, path string) (*viper.Viper, error) { | ||
extends, err := read(fs, path, "lefthook") | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if err := remotes(fs, extends); err != nil { | ||
return nil, err | ||
} | ||
|
||
if err := extend(fs, extends); err != nil { | ||
return nil, err | ||
} | ||
|
@@ -79,26 +88,99 @@ func mergeAllExtends(fs afero.Fs, path string) (*viper.Viper, error) { | |
} | ||
} | ||
|
||
if err := remotes(fs, extends); err != nil { | ||
return nil, err | ||
} | ||
|
||
Comment on lines
+91
to
+94
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need to run There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Originally, I hadn't added the option to specify the |
||
if err := extend(fs, extends); 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)) | ||
func remotes(fs afero.Fs, v *viper.Viper) error { | ||
var remotes []Remote | ||
err := v.UnmarshalKey("remotes", &remotes) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
another, err := read(fs, filepath.Dir(path), name) | ||
if err != nil { | ||
if len(remotes) == 0 { | ||
return nil | ||
} | ||
|
||
var ( | ||
wg sync.WaitGroup | ||
eg errgroup.Group | ||
configPaths []string | ||
configPathCh = make(chan string) | ||
) | ||
|
||
wg.Add(1) | ||
go func() { | ||
for configPath := range configPathCh { | ||
configPaths = append(configPaths, configPath) | ||
} | ||
wg.Done() | ||
}() | ||
|
||
for i := range remotes { | ||
remote := remotes[i] | ||
eg.Go(func() error { | ||
dir, err := git.InitRemote(fs, remote.URL, remote.Rev) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
for _, path := range remote.Configs { | ||
configPathCh <- filepath.Join(dir, path) | ||
} | ||
return nil | ||
}) | ||
} | ||
|
||
// Wait on errgroup to finish before closing the channel. | ||
err = eg.Wait() | ||
close(configPathCh) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// Wait for all of the configPaths to be added. | ||
wg.Wait() | ||
|
||
// Stable sort to ensure that the merge order is deterministic. | ||
sort.SliceStable(configPaths, func(i, j int) bool { return configPaths[i] < configPaths[j] }) | ||
|
||
for _, configPath := range configPaths { | ||
log.Debugf("Merging remote config: %v", configPath) | ||
if err := merge(fs, configPath, v); err != nil { | ||
return err | ||
} | ||
if err = v.MergeConfigMap(another.AllSettings()); err != nil { | ||
} | ||
return nil | ||
} | ||
|
||
func extend(fs afero.Fs, v *viper.Viper) error { | ||
for _, path := range v.GetStringSlice("extends") { | ||
if err := merge(fs, path, v); err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func merge(fs afero.Fs, path string, v *viper.Viper) error { | ||
name := strings.TrimSuffix(filepath.Base(path), filepath.Ext(path)) | ||
|
||
another, err := read(fs, filepath.Dir(path), name) | ||
if err != nil { | ||
return err | ||
} | ||
if err = v.MergeConfigMap(another.AllSettings()); err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package git | ||
|
||
import ( | ||
"errors" | ||
"os" | ||
"path/filepath" | ||
"strings" | ||
|
||
"github.com/evilmartians/lefthook/internal/log" | ||
"github.com/spf13/afero" | ||
) | ||
|
||
var defaultRemotePath = mustGetDefaultRemotesDir() | ||
|
||
// mustGetDefaultRemotesDir returns the default directory for the lefthook remotes. | ||
func mustGetDefaultRemotesDir() string { | ||
homeDir, err := os.UserHomeDir() | ||
if err != nil { | ||
panic(err) | ||
} | ||
return filepath.Join(homeDir, ".lefthook-remotes") | ||
} | ||
Comment on lines
+16
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we would really surprise users with new directories. I think it's better to put remote config in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree! I wasn't aware that the |
||
|
||
// InitRemote 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 InitRemote(fs afero.Fs, url, rev string) (string, error) { | ||
err := fs.MkdirAll(defaultRemotePath, 0755) | ||
if err != nil && !errors.Is(err, os.ErrExist) { | ||
return "", err | ||
} | ||
|
||
root := getRemoteDir(url) | ||
|
||
_, err = fs.Stat(root) | ||
if err == nil { | ||
if err := updateRemote(fs, root, url, rev); err != nil { | ||
return "", err | ||
} | ||
return root, nil | ||
} | ||
|
||
if err := cloneRemote(fs, root, url, rev); err != nil { | ||
return "", err | ||
} | ||
return root, nil | ||
} | ||
Comment on lines
+27
to
+47
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe this can be a part of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am in favor of having the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Feel free to use either |
||
|
||
func updateRemote(fs afero.Fs, root, url, rev string) error { | ||
log.Debugf("Updating remote config repository: %v", root) | ||
cmdFetch := []string{"git", "-C", root, "pull", "-q"} | ||
if len(rev) == 0 { | ||
cmdFetch = append(cmdFetch, "origin", rev) | ||
} | ||
_, err := execGit(strings.Join(cmdFetch, " ")) | ||
if err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
func cloneRemote(fs afero.Fs, root, url, rev string) error { | ||
log.Debugf("Cloning remote config repository: %v", root) | ||
cmdClone := []string{"git", "-C", defaultRemotePath, "clone", "-q"} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's better to use long names for git options just to be more verbose and obvious.
Comment on lines
+62
to
+64
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function makes sense 👍 I just think we don't need to store the whole repo. We can clone it into under a /tmp, take the desired file, and drop it. WDYT? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This sounds good to me! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use shallow clone as well to further cut back on larger repos. |
||
if len(rev) > 0 { | ||
cmdClone = append(cmdClone, "-b", rev) | ||
} | ||
cmdClone = append(cmdClone, url) | ||
_, err := execGit(strings.Join(cmdClone, " ")) | ||
if err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
func getRemoteDir(url string) string { | ||
// Removes any suffix that might have been used in the url like '.git'. | ||
trimmedURL := strings.TrimSuffix(url, filepath.Ext(url)) | ||
return filepath.Join(defaultRemotePath, filepath.Base(trimmedURL)) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think
url
option is too general. Maybegit
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would
git_url
orgit_repo
be an acceptable option? In #155 the posted example usesrepo
which may be another good alternative.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like
git_url
, it perfectly explains the meaning of an option.