Skip to content

Commit

Permalink
feat: add refetch_frequency parameter to settings (#857)
Browse files Browse the repository at this point in the history
  • Loading branch information
gabriel-ss authored Oct 29, 2024
1 parent 5ec87c7 commit 1bcc75c
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 4 deletions.
24 changes: 24 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Config options:
- [`git_url`](#git_url)
- [`ref`](#ref-1)
- [`refetch`](#refetch)
- [`refetch_frequency`](#refetch_frequency)
- [`configs`](#configs)
- [`<pre-commit>`](#hook-name) hook name
- [`files` (global)](#files-global)
Expand Down Expand Up @@ -417,6 +418,29 @@ remotes:
refetch: true
```

### `refetch_frequency`

**Default:** Not set

Specifies how frequently Lefthook should refetch the remote configuration. This can be set to `always`, `never` or a time duration like `24h`, `30m`, etc.

- When set to `always`, Lefthook will always refetch the remote configuration on each run.
- When set to a duration (e.g., `24h`), Lefthook will check the last fetch time and refetch the configuration only if the specified amount of time has passed.
- When set to `never` or not set, Lefthook will not fetch from remote.

**Example**

```yml
# lefthook.yml
remotes:
- git_url: https://github.com/evilmartians/lefthook
refetch_frequency: 24h # Refetches once every 24 hours
```

> [!WARNING]
> If `refetch` is set to `true`, it overrides any setting in `refetch_frequency`.

### `configs`

**Default:** `[lefthook.yml]`
Expand Down
7 changes: 4 additions & 3 deletions internal/config/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ type Remote struct {
GitURL string `json:"git_url,omitempty" mapstructure:"git_url" toml:"git_url" yaml:"git_url"`
Ref string `json:"ref,omitempty" mapstructure:"ref,omitempty" toml:"ref,omitempty" yaml:",omitempty"`
// Deprecated
Config string `json:"config,omitempty" mapstructure:"config,omitempty" toml:"config,omitempty" yaml:",omitempty"`
Configs []string `json:"configs,omitempty" mapstructure:"configs,omitempty" toml:"configs,omitempty" yaml:",omitempty"`
Refetch bool `json:"refetch,omitempty" mapstructure:"refetch,omitempty" toml:"refetch,omitempty" yaml:",omitempty"`
Config string `json:"config,omitempty" mapstructure:"config,omitempty" toml:"config,omitempty" yaml:",omitempty"`
Configs []string `json:"configs,omitempty" mapstructure:"configs,omitempty" toml:"configs,omitempty" yaml:",omitempty"`
Refetch bool `json:"refetch,omitempty" mapstructure:"refetch,omitempty" toml:"refetch,omitempty" yaml:",omitempty"`
RefetchFrequency string `json:"refetch_frequency,omitempty" mapstructure:"refetch_frequency,omitempty" toml:"refetch_frequency,omitempty" yaml:",omitempty"`
}

func (r *Remote) Configured() bool {
Expand Down
35 changes: 34 additions & 1 deletion internal/lefthook/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"regexp"
"strconv"
"strings"
"time"

"github.com/gobwas/glob"
"github.com/spf13/afero"
Expand Down Expand Up @@ -118,7 +119,7 @@ func (l *Lefthook) syncHooks(cfg *config.Config, fetchRemotes bool) (*config.Con

if fetchRemotes {
for _, remote := range cfg.Remotes {
if remote.Configured() && remote.Refetch {
if remote.Configured() && l.shouldRefetch(remote) {
if err = l.repo.SyncRemote(remote.GitURL, remote.Ref, false); err != nil {
log.Warnf("Couldn't sync from %s. Will continue anyway: %s", remote.GitURL, err)
continue
Expand All @@ -141,6 +142,38 @@ func (l *Lefthook) syncHooks(cfg *config.Config, fetchRemotes bool) (*config.Con
return cfg, l.createHooksIfNeeded(cfg, true, false)
}

func (l *Lefthook) shouldRefetch(remote *config.Remote) bool {
if remote.Refetch || remote.RefetchFrequency == "always" {
return true
}
if remote.RefetchFrequency == "" || remote.RefetchFrequency == "never" {
return false
}

timedelta, err := time.ParseDuration(remote.RefetchFrequency)
if err != nil {
log.Warnf("Couldn't parse refetch frequency %s. Will continue anyway: %s", remote.RefetchFrequency, err)
return false
}

var lastFetchTime time.Time
remotePath := l.repo.RemoteFolder(remote.GitURL, remote.Ref)
info, err := l.Fs.Stat(filepath.Join(remotePath, ".git", "FETCH_HEAD"))

if err != nil {
if errors.Is(err, os.ErrNotExist) {
return true
}

log.Warnf("Failed to detect last fetch time: %s", err)
return false
}

lastFetchTime = info.ModTime()
return time.Now().After(lastFetchTime.Add(timedelta))

}

func (l *Lefthook) createHooksIfNeeded(cfg *config.Config, checkHashSum, force bool) error {
if checkHashSum && l.hooksSynchronized(cfg) {
return nil
Expand Down
127 changes: 127 additions & 0 deletions internal/lefthook/install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -410,3 +410,130 @@ post-commit:
})
}
}

func TestShouldRefetch(t *testing.T) {
root, err := filepath.Abs("src")
if err != nil {
t.Errorf("unexpected error: %s", err)
}

configPath := filepath.Join(root, "lefthook.yml")
fetchHeadPath := func(lefthook *Lefthook, remote *config.Remote) string {
remotePath := lefthook.repo.RemoteFolder(remote.GitURL, remote.Ref)
return filepath.Join(remotePath, ".git", "FETCH_HEAD")
}

repo := &git.Repository{
HooksPath: filepath.Join(root, ".git", "hooks"),
RootPath: root,
InfoPath: filepath.Join(root, ".git", "info"),
}
for n, tt := range [...]struct {
name, config string
shouldRefetchInitially, shouldRefetchAfter, shouldRefetchBefore bool
}{
{
name: "with refetch frequency configured to always",
config: `
remotes:
- git_url: https://github.com/evilmartians/lefthook
refetch_frequency: always
configs:
- examples/remote/ping.yml
`,
shouldRefetchInitially: true,
shouldRefetchAfter: true,
shouldRefetchBefore: true,
},
{
name: "with refetch frequency configured to 1 minute",
config: `
remotes:
- git_url: https://github.com/evilmartians/lefthook
refetch_frequency: 1m
configs:
- examples/remote/ping.yml
`,
shouldRefetchInitially: true,
shouldRefetchAfter: true,
shouldRefetchBefore: false,
},
{
name: "with refetch frequency configured to never",
config: `
remotes:
- git_url: https://github.com/evilmartians/lefthook
refetch_frequency: never
configs:
- examples/remote/ping.yml
`,
shouldRefetchInitially: false,
shouldRefetchAfter: false,
shouldRefetchBefore: false,
},
{
name: "with refetch frequency not configured",
config: `
remotes:
- git_url: https://github.com/evilmartians/lefthook
configs:
- examples/remote/ping.yml
`,
shouldRefetchInitially: false,
shouldRefetchAfter: false,
shouldRefetchBefore: false,
},
} {
fs := afero.NewMemMapFs()
lefthook := &Lefthook{
Options: &Options{Fs: fs},
repo: repo,
}

t.Run(fmt.Sprintf("%d: %s", n, tt.name), func(t *testing.T) {
// Create configuration file
if len(tt.config) > 0 {
if err := afero.WriteFile(fs, configPath, []byte(tt.config), 0o644); err != nil {
t.Errorf("unexpected error: %s", err)
}
timestamp := time.Date(2022, time.June, 22, 10, 40, 10, 1, time.UTC)
if err := fs.Chtimes(configPath, timestamp, timestamp); err != nil {
t.Errorf("unexpected error: %s", err)
}
}

cfg, err := config.Load(lefthook.Fs, repo)
if err != nil {
t.Errorf("unexpected error: %s", err)
}

remote := cfg.Remotes[0]

if lefthook.shouldRefetch(remote) != tt.shouldRefetchInitially {
t.Errorf("unexpected shouldRefetch return before first fetch")
}

if err := afero.WriteFile(fs, fetchHeadPath(lefthook, remote), []byte(""), 0o644); err != nil {
t.Errorf("unexpected error: %s", err)
}

firstFetchTime := time.Now().Add(-2 * time.Duration(time.Minute))

if err := fs.Chtimes(fetchHeadPath(lefthook, remote), firstFetchTime, firstFetchTime); err != nil {
t.Errorf("unexpected error: %s", err)
}

if lefthook.shouldRefetch(remote) != tt.shouldRefetchAfter {
t.Errorf("unexpected shouldRefetch return after refetch period")
}

if err := fs.Chtimes(fetchHeadPath(lefthook, remote), firstFetchTime, time.Now()); err != nil {
t.Errorf("unexpected error: %s", err)
}

if lefthook.shouldRefetch(remote) != tt.shouldRefetchBefore {
t.Errorf("unexpected shouldRefetch return before refetch period")
}
})
}
}

0 comments on commit 1bcc75c

Please sign in to comment.