From 0514edad1a6ee06902841e0c903fc2a2119b7e95 Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Thu, 23 Aug 2018 21:24:57 +0200 Subject: cache: maintain, write and load from disk bug excerpts --- cache/bug_cache.go | 60 ++++++++++------------- cache/bug_excerpt.go | 9 +++- cache/cache.go | 45 ++++++++--------- cache/repo_cache.go | 133 ++++++++++++++++++++++++++++++++++++++++----------- 4 files changed, 161 insertions(+), 86 deletions(-) (limited to 'cache') diff --git a/cache/bug_cache.go b/cache/bug_cache.go index f0fc7ff6..7df76efe 100644 --- a/cache/bug_cache.go +++ b/cache/bug_cache.go @@ -3,34 +3,18 @@ package cache import ( "github.com/MichaelMure/git-bug/bug" "github.com/MichaelMure/git-bug/bug/operations" - "github.com/MichaelMure/git-bug/repository" "github.com/MichaelMure/git-bug/util" ) -type BugCacher interface { - Snapshot() *bug.Snapshot - - // Mutations - AddComment(message string) error - AddCommentWithFiles(message string, files []util.Hash) error - ChangeLabels(added []string, removed []string) error - Open() error - Close() error - SetTitle(title string) error - - Commit() error - CommitAsNeeded() error -} - type BugCache struct { - repo repository.Repo - bug *bug.WithSnapshot + repoCache *RepoCache + bug *bug.WithSnapshot } -func NewBugCache(repo repository.Repo, b *bug.Bug) BugCacher { +func NewBugCache(repoCache *RepoCache, b *bug.Bug) *BugCache { return &BugCache{ - repo: repo, - bug: &bug.WithSnapshot{Bug: b}, + repoCache: repoCache, + bug: &bug.WithSnapshot{Bug: b}, } } @@ -38,23 +22,31 @@ func (c *BugCache) Snapshot() *bug.Snapshot { return c.bug.Snapshot() } +func (c *BugCache) notifyUpdated() error { + return c.repoCache.bugUpdated(c.bug.Id()) +} + func (c *BugCache) AddComment(message string) error { - return c.AddCommentWithFiles(message, nil) + if err := c.AddCommentWithFiles(message, nil); err != nil { + return err + } + + return c.notifyUpdated() } func (c *BugCache) AddCommentWithFiles(message string, files []util.Hash) error { - author, err := bug.GetUser(c.repo) + author, err := bug.GetUser(c.repoCache.repo) if err != nil { return err } operations.CommentWithFiles(c.bug, author, message, files) - return nil + return c.notifyUpdated() } func (c *BugCache) ChangeLabels(added []string, removed []string) error { - author, err := bug.GetUser(c.repo) + author, err := bug.GetUser(c.repoCache.repo) if err != nil { return err } @@ -64,49 +56,49 @@ func (c *BugCache) ChangeLabels(added []string, removed []string) error { return err } - return nil + return c.notifyUpdated() } func (c *BugCache) Open() error { - author, err := bug.GetUser(c.repo) + author, err := bug.GetUser(c.repoCache.repo) if err != nil { return err } operations.Open(c.bug, author) - return nil + return c.notifyUpdated() } func (c *BugCache) Close() error { - author, err := bug.GetUser(c.repo) + author, err := bug.GetUser(c.repoCache.repo) if err != nil { return err } operations.Close(c.bug, author) - return nil + return c.notifyUpdated() } func (c *BugCache) SetTitle(title string) error { - author, err := bug.GetUser(c.repo) + author, err := bug.GetUser(c.repoCache.repo) if err != nil { return err } operations.SetTitle(c.bug, author, title) - return nil + return c.notifyUpdated() } func (c *BugCache) Commit() error { - return c.bug.Commit(c.repo) + return c.bug.Commit(c.repoCache.repo) } func (c *BugCache) CommitAsNeeded() error { if c.bug.HasPendingOp() { - return c.bug.Commit(c.repo) + return c.bug.Commit(c.repoCache.repo) } return nil } diff --git a/cache/bug_excerpt.go b/cache/bug_excerpt.go index 23ac459b..572f461a 100644 --- a/cache/bug_excerpt.go +++ b/cache/bug_excerpt.go @@ -1,6 +1,8 @@ package cache import ( + "encoding/gob" + "github.com/MichaelMure/git-bug/bug" "github.com/MichaelMure/git-bug/util" ) @@ -19,7 +21,7 @@ type BugExcerpt struct { Author bug.Person } -func NewBugExcerpt(b *bug.Bug, snap bug.Snapshot) BugExcerpt { +func NewBugExcerpt(b bug.Interface, snap *bug.Snapshot) BugExcerpt { return BugExcerpt{ Id: b.Id(), CreateLamportTime: b.CreateLamportTime(), @@ -31,6 +33,11 @@ func NewBugExcerpt(b *bug.Bug, snap bug.Snapshot) BugExcerpt { } } +// Package initialisation used to register the type for (de)serialization +func init() { + gob.Register(BugExcerpt{}) +} + /* * Sorting */ diff --git a/cache/cache.go b/cache/cache.go index 618ec981..6dbafba1 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -13,29 +13,15 @@ import ( ) const lockfile = "lock" - -type Cacher interface { - // RegisterRepository register a named repository. Use this for multi-repo setup - RegisterRepository(ref string, repo repository.Repo) error - // RegisterDefaultRepository register a unnamed repository. Use this for mono-repo setup - RegisterDefaultRepository(repo repository.Repo) error - - // ResolveRepo retrieve a repository by name - ResolveRepo(ref string) (RepoCacher, error) - // DefaultRepo retrieve the default repository - DefaultRepo() (RepoCacher, error) - - // Close will do anything that is needed to close the cache properly - Close() error -} +const excerptsFile = "excerpts" type RootCache struct { - repos map[string]RepoCacher + repos map[string]*RepoCache } func NewCache() RootCache { return RootCache{ - repos: make(map[string]RepoCacher), + repos: make(map[string]*RepoCache), } } @@ -46,7 +32,12 @@ func (c *RootCache) RegisterRepository(ref string, repo repository.Repo) error { return err } - c.repos[ref] = NewRepoCache(repo) + r, err := NewRepoCache(repo) + if err != nil { + return err + } + + c.repos[ref] = r return nil } @@ -57,7 +48,12 @@ func (c *RootCache) RegisterDefaultRepository(repo repository.Repo) error { return err } - c.repos[""] = NewRepoCache(repo) + r, err := NewRepoCache(repo) + if err != nil { + return err + } + + c.repos[""] = r return nil } @@ -84,7 +80,7 @@ func (c *RootCache) lockRepository(repo repository.Repo) error { } // ResolveRepo retrieve a repository by name -func (c *RootCache) DefaultRepo() (RepoCacher, error) { +func (c *RootCache) DefaultRepo() (*RepoCache, error) { if len(c.repos) != 1 { return nil, fmt.Errorf("repository is not unique") } @@ -97,7 +93,7 @@ func (c *RootCache) DefaultRepo() (RepoCacher, error) { } // DefaultRepo retrieve the default repository -func (c *RootCache) ResolveRepo(ref string) (RepoCacher, error) { +func (c *RootCache) ResolveRepo(ref string) (*RepoCache, error) { r, ok := c.repos[ref] if !ok { return nil, fmt.Errorf("unknown repo") @@ -105,9 +101,10 @@ func (c *RootCache) ResolveRepo(ref string) (RepoCacher, error) { return r, nil } +// Close will do anything that is needed to close the cache properly func (c *RootCache) Close() error { for _, cachedRepo := range c.repos { - lockPath := repoLockFilePath(cachedRepo.Repository()) + lockPath := repoLockFilePath(cachedRepo.repo) err := os.Remove(lockPath) if err != nil { return err @@ -116,6 +113,10 @@ func (c *RootCache) Close() error { return nil } +// RepoIsAvailable check is the given repository is locked by a Cache. +// Note: this is a smart function that will cleanup the lock file if the +// corresponding process is not there anymore. +// If no error is returned, the repo is free to edit. func RepoIsAvailable(repo repository.Repo) error { lockPath := repoLockFilePath(repo) diff --git a/cache/repo_cache.go b/cache/repo_cache.go index e58165d2..c73dbe9f 100644 --- a/cache/repo_cache.go +++ b/cache/repo_cache.go @@ -1,8 +1,12 @@ package cache import ( + "bytes" + "encoding/gob" "fmt" "io" + "os" + "path" "strings" "github.com/MichaelMure/git-bug/bug" @@ -11,39 +15,110 @@ import ( "github.com/MichaelMure/git-bug/util" ) -type RepoCacher interface { - Repository() repository.Repo - ResolveBug(id string) (BugCacher, error) - ResolveBugPrefix(prefix string) (BugCacher, error) - AllBugIds() ([]string, error) - ClearAllBugs() - - // Mutations - NewBug(title string, message string) (BugCacher, error) - NewBugWithFiles(title string, message string, files []util.Hash) (BugCacher, error) - Fetch(remote string) (string, error) - MergeAll(remote string) <-chan bug.MergeResult - Pull(remote string, out io.Writer) error - Push(remote string) (string, error) -} - type RepoCache struct { - repo repository.Repo - bugs map[string]BugCacher + repo repository.Repo + excerpts map[string]BugExcerpt + bugs map[string]*BugCache } -func NewRepoCache(r repository.Repo) RepoCacher { - return &RepoCache{ +func NewRepoCache(r repository.Repo) (*RepoCache, error) { + c := &RepoCache{ repo: r, - bugs: make(map[string]BugCacher), + bugs: make(map[string]*BugCache), + } + + err := c.loadExcerpts() + + if err == nil { + return c, nil } + + c.buildAllExcerpt() + + return c, c.writeExcerpts() } func (c *RepoCache) Repository() repository.Repo { return c.repo } -func (c *RepoCache) ResolveBug(id string) (BugCacher, error) { +// bugUpdated is a callback to trigger when the excerpt of a bug changed, +// that is each time a bug is updated +func (c *RepoCache) bugUpdated(id string) error { + b, ok := c.bugs[id] + if !ok { + panic("missing bug in the cache") + } + + c.excerpts[id] = NewBugExcerpt(b.bug, b.Snapshot()) + + return c.writeExcerpts() +} + +// loadExcerpts will try to read from the disk the bug excerpt file +func (c *RepoCache) loadExcerpts() error { + excerptsPath := repoExcerptsFilePath(c.repo) + + f, err := os.Open(excerptsPath) + if err != nil { + return err + } + + decoder := gob.NewDecoder(f) + + var excerpts map[string]BugExcerpt + + err = decoder.Decode(&excerpts) + if err != nil { + return err + } + + c.excerpts = excerpts + return nil +} + +// writeExcerpts will serialize on disk the BugExcerpt array +func (c *RepoCache) writeExcerpts() error { + var data bytes.Buffer + + encoder := gob.NewEncoder(&data) + + err := encoder.Encode(c.excerpts) + if err != nil { + return err + } + + excerptsPath := repoExcerptsFilePath(c.repo) + + f, err := os.Create(excerptsPath) + if err != nil { + return err + } + + _, err = f.Write(data.Bytes()) + if err != nil { + return err + } + + return f.Close() +} + +func repoExcerptsFilePath(repo repository.Repo) string { + return path.Join(repo.GetPath(), ".git", "git-bug", excerptsFile) +} + +func (c *RepoCache) buildAllExcerpt() { + c.excerpts = make(map[string]BugExcerpt) + + allBugs := bug.ReadAllLocalBugs(c.repo) + + for b := range allBugs { + snap := b.Bug.Compile() + c.excerpts[b.Bug.Id()] = NewBugExcerpt(b.Bug, &snap) + } +} + +func (c *RepoCache) ResolveBug(id string) (*BugCache, error) { cached, ok := c.bugs[id] if ok { return cached, nil @@ -54,13 +129,13 @@ func (c *RepoCache) ResolveBug(id string) (BugCacher, error) { return nil, err } - cached = NewBugCache(c.repo, b) + cached = NewBugCache(c, b) c.bugs[id] = cached return cached, nil } -func (c *RepoCache) ResolveBugPrefix(prefix string) (BugCacher, error) { +func (c *RepoCache) ResolveBugPrefix(prefix string) (*BugCache, error) { // preallocate but empty matching := make([]string, 0, 5) @@ -87,7 +162,7 @@ func (c *RepoCache) ResolveBugPrefix(prefix string) (BugCacher, error) { return nil, err } - cached := NewBugCache(c.repo, b) + cached := NewBugCache(c, b) c.bugs[b.Id()] = cached return cached, nil @@ -98,14 +173,14 @@ func (c *RepoCache) AllBugIds() ([]string, error) { } func (c *RepoCache) ClearAllBugs() { - c.bugs = make(map[string]BugCacher) + c.bugs = make(map[string]*BugCache) } -func (c *RepoCache) NewBug(title string, message string) (BugCacher, error) { +func (c *RepoCache) NewBug(title string, message string) (*BugCache, error) { return c.NewBugWithFiles(title, message, nil) } -func (c *RepoCache) NewBugWithFiles(title string, message string, files []util.Hash) (BugCacher, error) { +func (c *RepoCache) NewBugWithFiles(title string, message string, files []util.Hash) (*BugCache, error) { author, err := bug.GetUser(c.repo) if err != nil { return nil, err @@ -121,7 +196,7 @@ func (c *RepoCache) NewBugWithFiles(title string, message string, files []util.H return nil, err } - cached := NewBugCache(c.repo, b) + cached := NewBugCache(c, b) c.bugs[b.Id()] = cached return cached, nil -- cgit