locking: write JSON response for "list locks" API call to a cache file
Results of calling "list locks" API will be written in human-readable format to .git/lfs/cache/locks. Cache files will only be written for queries which are not limited or filtered. There will be a separate cache file for every remote ref (Client.RemoteRef). Cache file format is JSON, which should be considered as implementation detail and there is no guarantee that the file format will not be changed for future Git-LFS releases. The cache file can later be read by Git-LFS or third party tools which wish to retrieve the last known lock information without invoking "git lfs locks --cached" (e.g. for performance reasons). This commit prepares for a fix of issue #3107.
This commit is contained in:
parent
d7cf4e18f1
commit
e811a46ce7
@ -1,6 +1,7 @@
|
||||
package locking
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
@ -41,6 +42,7 @@ type Client struct {
|
||||
RemoteRef *git.Ref
|
||||
client *lockClient
|
||||
cache LockCacher
|
||||
cacheDir string
|
||||
|
||||
lockablePatterns []string
|
||||
lockableFilter *filepathfilter.Filter
|
||||
@ -79,6 +81,7 @@ func (c *Client) SetupFileCache(path string) error {
|
||||
}
|
||||
|
||||
c.cache = cache
|
||||
c.cacheDir = filepath.Join(path, "cache")
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -206,7 +209,21 @@ func (c *Client) SearchLocks(filter map[string]string, limit int, localOnly bool
|
||||
if localOnly {
|
||||
return c.searchLocalLocks(filter, limit)
|
||||
} else {
|
||||
return c.searchRemoteLocks(filter, limit)
|
||||
locks, err := c.searchRemoteLocks(filter, limit)
|
||||
if err != nil {
|
||||
return locks, err
|
||||
}
|
||||
|
||||
if len(filter) == 0 && limit == 0 {
|
||||
cacheFile, err := c.prepareCacheDirectory()
|
||||
if err != nil {
|
||||
return locks, err
|
||||
}
|
||||
|
||||
err = c.writeLocksToCacheFile(cacheFile, locks)
|
||||
}
|
||||
|
||||
return locks, err
|
||||
}
|
||||
}
|
||||
|
||||
@ -384,6 +401,44 @@ func init() {
|
||||
kv.RegisterTypeForStorage(&Lock{})
|
||||
}
|
||||
|
||||
func (c *Client) prepareCacheDirectory() (string, error) {
|
||||
cacheDir := filepath.Join(c.cacheDir, "locks")
|
||||
if c.RemoteRef != nil {
|
||||
cacheDir = filepath.Join(cacheDir, c.RemoteRef.Refspec())
|
||||
}
|
||||
|
||||
stat, err := os.Stat(cacheDir)
|
||||
if err == nil {
|
||||
if !stat.IsDir() {
|
||||
return cacheDir, errors.New("init cache directory " + cacheDir + " failed: already exists, but is no directory")
|
||||
}
|
||||
} else if os.IsNotExist(err) {
|
||||
err = os.MkdirAll(cacheDir, os.ModePerm)
|
||||
if err != nil {
|
||||
return cacheDir, errors.Wrap(err, "init cache directory "+cacheDir+" failed: directory creation failed")
|
||||
}
|
||||
} else {
|
||||
return cacheDir, errors.Wrap(err, "init cache directory "+cacheDir+" failed")
|
||||
}
|
||||
|
||||
return filepath.Join(cacheDir, "remote"), nil
|
||||
}
|
||||
|
||||
func (c *Client) writeLocksToCacheFile(path string, locks []Lock) error {
|
||||
file, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = json.NewEncoder(file).Encode(locks)
|
||||
if err != nil {
|
||||
file.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
return file.Close()
|
||||
}
|
||||
|
||||
type nilLockCacher struct{}
|
||||
|
||||
func (c *nilLockCacher) Add(l Lock) error {
|
||||
|
@ -5,10 +5,12 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/git-lfs/git-lfs/git"
|
||||
"github.com/git-lfs/git-lfs/lfsapi"
|
||||
"github.com/git-lfs/git-lfs/lfshttp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -21,6 +23,89 @@ func (a LocksById) Len() int { return len(a) }
|
||||
func (a LocksById) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a LocksById) Less(i, j int) bool { return a[i].Id < a[j].Id }
|
||||
|
||||
func TestRemoteLocksWithCache(t *testing.T) {
|
||||
var err error
|
||||
tempDir, err := ioutil.TempDir("", "testCacheLock")
|
||||
assert.Nil(t, err)
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "GET", r.Method)
|
||||
assert.Equal(t, "/api/locks", r.URL.Path)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
err = json.NewEncoder(w).Encode(&lockList{
|
||||
Locks: []Lock{
|
||||
Lock{Id: "100", Path: "folder/test1.dat", Owner: &User{Name: "Alice"}},
|
||||
Lock{Id: "101", Path: "folder/test2.dat", Owner: &User{Name: "Charles"}},
|
||||
Lock{Id: "102", Path: "folder/test3.dat", Owner: &User{Name: "Fred"}},
|
||||
},
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
}))
|
||||
|
||||
defer func() {
|
||||
srv.Close()
|
||||
}()
|
||||
|
||||
lfsclient, err := lfsapi.NewClient(lfshttp.NewContext(nil, nil, map[string]string{
|
||||
"lfs.url": srv.URL + "/api",
|
||||
"user.name": "Fred",
|
||||
"user.email": "fred@bloggs.com",
|
||||
}))
|
||||
require.Nil(t, err)
|
||||
|
||||
client, err := NewClient("", lfsclient)
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, client.SetupFileCache(tempDir))
|
||||
|
||||
client.RemoteRef = &git.Ref{Name: "refs/heads/master"}
|
||||
cacheFile, err := client.prepareCacheDirectory()
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Cache file should not exist
|
||||
fi, err := os.Stat(cacheFile)
|
||||
assert.True(t, os.IsNotExist(err))
|
||||
|
||||
// Need to include zero time in structure for equal to work
|
||||
zeroTime := time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
// REMOTE QUERY: No cache file will be created when querying with a filter
|
||||
locks, err := client.SearchLocks(map[string]string{
|
||||
"key": "value",
|
||||
}, 0, false)
|
||||
assert.Nil(t, err)
|
||||
// Just make sure we have have received anything, content doesn't matter
|
||||
assert.Equal(t, 3, len(locks))
|
||||
|
||||
fi, err = os.Stat(cacheFile)
|
||||
assert.True(t, os.IsNotExist(err))
|
||||
|
||||
// REMOTE QUERY: No cache file will be created when querying with a limit
|
||||
locks, err = client.SearchLocks(nil, 1, false)
|
||||
assert.Nil(t, err)
|
||||
// Just make sure we have have received anything, content doesn't matter
|
||||
assert.Equal(t, 1, len(locks))
|
||||
|
||||
fi, err = os.Stat(cacheFile)
|
||||
assert.True(t, os.IsNotExist(err))
|
||||
|
||||
// REMOTE QUERY: locks will be reported and cache file should be created
|
||||
locks, err = client.SearchLocks(nil, 0, false)
|
||||
assert.Nil(t, err)
|
||||
|
||||
fi, err = os.Stat(cacheFile)
|
||||
assert.Nil(t, err)
|
||||
const size int64 = 300
|
||||
assert.Equal(t, size, fi.Size())
|
||||
|
||||
sort.Sort(LocksById(locks))
|
||||
assert.Equal(t, []Lock{
|
||||
Lock{Path: "folder/test1.dat", Id: "100", Owner: &User{Name: "Alice"}, LockedAt: zeroTime},
|
||||
Lock{Path: "folder/test2.dat", Id: "101", Owner: &User{Name: "Charles"}, LockedAt: zeroTime},
|
||||
Lock{Path: "folder/test3.dat", Id: "102", Owner: &User{Name: "Fred"}, LockedAt: zeroTime},
|
||||
}, locks)
|
||||
}
|
||||
|
||||
func TestRefreshCache(t *testing.T) {
|
||||
var err error
|
||||
tempDir, err := ioutil.TempDir("", "testCacheLock")
|
||||
|
Loading…
Reference in New Issue
Block a user