-
-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implements LRU cache for git repos on disk (#17)
The GitRepoLRUCache struct and associated methods is a Least Recently Used cache whose main element is the GitRepoFilePath struct (which in itself represents an on-disk git repository). This also adds the git "providers" interface which wrap the methods for cloning and loading up repos from the different implementers. Signed-off-by: John McBride <[email protected]>
- Loading branch information
Showing
13 changed files
with
1,036 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,30 @@ | ||
# This file is useful for doing local development | ||
# when needing to load the postgres database secrets. | ||
# when needing to load the postgres database secrets and start a locally running | ||
# pizza oven service | ||
|
||
# Database env vars | ||
DATABASE_PORT=9999 | ||
DATABASE_HOST=localhost | ||
DATABASE_USER=opensauced-admin | ||
DATABASE_PASSWORD={YOUR-SECRET-PASSWORD-HERE} | ||
DATABASE_DBNAME=pizza | ||
|
||
# The port for the Pizza oven server | ||
# The port for the Pizza oven server to use | ||
SERVER_PORT=8080 | ||
|
||
# The git provider to use for the pizza oven service. | ||
# Must be one of "cache" or "memory" to designate the git provider that will be | ||
# used to clone and access repos. | ||
# - The "cache" git provider uses a local cache on disk to clone git repos into. | ||
# This uses much less memory than in-memory cloning. | ||
GIT_PROVIDER=cache | ||
|
||
# The settings for the cached git repos. | ||
# Must be set when "GIT_PROVIDER" is set to "cache" | ||
# | ||
# The root directory where the git repo cache should be stored | ||
CACHE_DIR=/tmp | ||
# The minimum amount of free disk in Gb to keep. This ensures that the cache | ||
# does not completely fill the disk and allows for some buffer before items | ||
# are evicted from the cache. | ||
MIN_FREE_DISK_GB=25 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package cache | ||
|
||
import ( | ||
"sync" | ||
|
||
"github.com/go-git/go-git/v5" | ||
) | ||
|
||
// GitRepoFilePath is a key / value pair with a locking mutex which represents | ||
// the key to a git repository (typically the remote URL) and its file path on disk. | ||
// This is used as the primary element in GitRepoLRUCache. | ||
// | ||
// When processing and operations are completed for an individual GitRepoFilePath, | ||
// always call "Done" to ensure no deadlocks occur on individual elements within | ||
// a given GItRepoLRUCache. | ||
// Example: "repo.Done()" | ||
type GitRepoFilePath struct { | ||
// A locking mutex is used to ensure that on-disk git repos are not | ||
// modified during processing. | ||
// Locking is done manually via "element.lock.Lock()" within the cache package. | ||
// Once operations are completed, in order to free up the resource, the "Done()" | ||
// method should be called. | ||
lock sync.Mutex | ||
|
||
// The key for the GitRepoFilePath key/value pair, generally, is the | ||
// remote URL for the git repository | ||
key string | ||
|
||
// path is the value in the GitRepoFilePath key/value and denotes the | ||
// filepath on-disk to the cloned git repository | ||
path string | ||
} | ||
|
||
// OpenAndFetch opens a git repository on-disk and fetches the latest changes. | ||
// If the git.NoErrAlreadyUpToDate error is produced, this function does not | ||
// return an error but, instead, continues and returns the repo. | ||
func (g *GitRepoFilePath) OpenAndFetch() (*git.Repository, error) { | ||
repo, err := git.PlainOpen(g.path) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// Get the worktree for the repository | ||
w, err := repo.Worktree() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// Pull the latest changes from the origin remote and merge into the current branch | ||
err = w.Pull(&git.PullOptions{}) | ||
if err != nil && err != git.NoErrAlreadyUpToDate { | ||
return nil, err | ||
} | ||
|
||
return repo, nil | ||
} | ||
|
||
// Done is a thin wrapper for unlocking the GitRepoFilePath's mutex. | ||
// This should ALWAYS be called when operations and processing for this | ||
// individual on-disk repo are completed in order to prevent a deadlock. | ||
func (g *GitRepoFilePath) Done() { | ||
g.lock.Unlock() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package cache | ||
|
||
import "testing" | ||
|
||
func TestOpenAndFetch(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
cacheDir string | ||
repos []string | ||
}{ | ||
{ | ||
name: "Puts repos into cache in sequential order", | ||
cacheDir: t.TempDir(), | ||
repos: []string{ | ||
"https://github.com/open-sauced/pizza", | ||
}, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
// Create a new LRU cache | ||
c, err := NewGitRepoLRUCache(tt.cacheDir, 100) | ||
if err != nil { | ||
t.Fatalf("unexpected err: %s", err.Error()) | ||
} | ||
|
||
// Populate the cache with the repos | ||
for _, repo := range tt.repos { | ||
repoFp, err := c.Put(repo) | ||
if err != nil { | ||
t.Fatalf("unexpected err putting to cache: %s", err.Error()) | ||
} | ||
repoFp.Done() | ||
} | ||
|
||
// Get the first element in the cache | ||
repoFp := c.dll.Front().Value.(*GitRepoFilePath) | ||
repoFp.lock.Lock() | ||
defer repoFp.Done() | ||
|
||
// Open and fetch the repo ensuring a non-nil git repo is returned | ||
openedRepo, err := repoFp.OpenAndFetch() | ||
if openedRepo == nil || err != nil { | ||
t.Fatalf("Opened repo unexpectedly failed to open and/or fetch: %s", err.Error()) | ||
} | ||
}) | ||
} | ||
} |
Oops, something went wrong.