diff options
-rw-r--r-- | cache/bug_cache.go | 60 | ||||
-rw-r--r-- | cache/bug_excerpt.go | 9 | ||||
-rw-r--r-- | cache/cache.go | 45 | ||||
-rw-r--r-- | cache/repo_cache.go | 133 | ||||
-rw-r--r-- | graphql/models/models.go | 8 | ||||
-rw-r--r-- | graphql/resolvers/mutation.go | 4 | ||||
-rw-r--r-- | graphql/resolvers/query.go | 2 | ||||
-rw-r--r-- | termui/bug_table.go | 10 | ||||
-rw-r--r-- | termui/show_bug.go | 8 | ||||
-rw-r--r-- | termui/termui.go | 18 |
10 files changed, 188 insertions, 109 deletions
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 diff --git a/graphql/models/models.go b/graphql/models/models.go index e55dfb3e..dfe3a29f 100644 --- a/graphql/models/models.go +++ b/graphql/models/models.go @@ -12,11 +12,11 @@ type ConnectionInput struct { } type Repository struct { - Cache cache.Cacher - Repo cache.RepoCacher + Cache *cache.RootCache + Repo *cache.RepoCache } type RepositoryMutation struct { - Cache cache.Cacher - Repo cache.RepoCacher + Cache *cache.RootCache + Repo *cache.RepoCache } diff --git a/graphql/resolvers/mutation.go b/graphql/resolvers/mutation.go index 2a81716c..9c5a589d 100644 --- a/graphql/resolvers/mutation.go +++ b/graphql/resolvers/mutation.go @@ -9,10 +9,10 @@ import ( ) type mutationResolver struct { - cache cache.Cacher + cache *cache.RootCache } -func (r mutationResolver) getRepo(repoRef *string) (cache.RepoCacher, error) { +func (r mutationResolver) getRepo(repoRef *string) (*cache.RepoCache, error) { if repoRef != nil { return r.cache.ResolveRepo(*repoRef) } diff --git a/graphql/resolvers/query.go b/graphql/resolvers/query.go index 4154964f..0e7f3a6f 100644 --- a/graphql/resolvers/query.go +++ b/graphql/resolvers/query.go @@ -8,7 +8,7 @@ import ( ) type rootQueryResolver struct { - cache cache.Cacher + cache *cache.RootCache } func (r rootQueryResolver) DefaultRepository(ctx context.Context) (*models.Repository, error) { diff --git a/termui/bug_table.go b/termui/bug_table.go index 1158f768..6f83a471 100644 --- a/termui/bug_table.go +++ b/termui/bug_table.go @@ -19,14 +19,14 @@ const bugTableInstructionView = "bugTableInstructionView" const remote = "origin" type bugTable struct { - repo cache.RepoCacher + repo *cache.RepoCache allIds []string - bugs []cache.BugCacher + bugs []*cache.BugCache pageCursor int selectCursor int } -func newBugTable(cache cache.RepoCacher) *bugTable { +func newBugTable(cache *cache.RepoCache) *bugTable { return &bugTable{ repo: cache, pageCursor: 0, @@ -230,14 +230,14 @@ func (bt *bugTable) doPaginate(allIds []string, max int) error { nb := minInt(len(allIds)-bt.pageCursor, max) if nb < 0 { - bt.bugs = []cache.BugCacher{} + bt.bugs = []*cache.BugCache{} return nil } // slice the data ids := allIds[bt.pageCursor : bt.pageCursor+nb] - bt.bugs = make([]cache.BugCacher, len(ids)) + bt.bugs = make([]*cache.BugCache, len(ids)) for i, id := range ids { b, err := bt.repo.ResolveBug(id) diff --git a/termui/show_bug.go b/termui/show_bug.go index 12cb6cf4..48592fc4 100644 --- a/termui/show_bug.go +++ b/termui/show_bug.go @@ -19,8 +19,8 @@ const showBugHeaderView = "showBugHeaderView" const timeLayout = "Jan 2 2006" type showBug struct { - cache cache.RepoCacher - bug cache.BugCacher + cache *cache.RepoCache + bug *cache.BugCache childViews []string mainSelectableView []string sideSelectableView []string @@ -29,13 +29,13 @@ type showBug struct { scroll int } -func newShowBug(cache cache.RepoCacher) *showBug { +func newShowBug(cache *cache.RepoCache) *showBug { return &showBug{ cache: cache, } } -func (sb *showBug) SetBug(bug cache.BugCacher) { +func (sb *showBug) SetBug(bug *cache.BugCache) { sb.bug = bug sb.scroll = 0 sb.selected = "" diff --git a/termui/termui.go b/termui/termui.go index 9894dfb2..91e92f00 100644 --- a/termui/termui.go +++ b/termui/termui.go @@ -13,7 +13,7 @@ var errTerminateMainloop = errors.New("terminate gocui mainloop") type termUI struct { g *gocui.Gui gError chan error - cache cache.RepoCacher + cache *cache.RepoCache activeWindow window @@ -43,7 +43,11 @@ type window interface { // Run will launch the termUI in the terminal func Run(repo repository.Repo) error { - c := cache.NewRepoCache(repo) + c, err := cache.NewRepoCache(repo) + + if err != nil { + return err + } ui = &termUI{ gError: make(chan error, 1), @@ -58,7 +62,7 @@ func Run(repo repository.Repo) error { initGui(nil) - err := <-ui.gError + err = <-ui.gError if err != nil && err != gocui.ErrQuit { return err @@ -157,7 +161,7 @@ func quit(g *gocui.Gui, v *gocui.View) error { return gocui.ErrQuit } -func newBugWithEditor(repo cache.RepoCacher) error { +func newBugWithEditor(repo *cache.RepoCache) error { // This is somewhat hacky. // As there is no way to pause gocui, run the editor and restart gocui, // we have to stop it entirely and start a new one later. @@ -176,7 +180,7 @@ func newBugWithEditor(repo cache.RepoCacher) error { return err } - var b cache.BugCacher + var b *cache.BugCache if err == input.ErrEmptyTitle { ui.msgPopup.Activate(msgPopupErrorTitle, "Empty title, aborting.") initGui(nil) @@ -197,7 +201,7 @@ func newBugWithEditor(repo cache.RepoCacher) error { } } -func addCommentWithEditor(bug cache.BugCacher) error { +func addCommentWithEditor(bug *cache.BugCache) error { // This is somewhat hacky. // As there is no way to pause gocui, run the editor and restart gocui, // we have to stop it entirely and start a new one later. @@ -230,7 +234,7 @@ func addCommentWithEditor(bug cache.BugCacher) error { return errTerminateMainloop } -func setTitleWithEditor(bug cache.BugCacher) error { +func setTitleWithEditor(bug *cache.BugCache) error { // This is somewhat hacky. // As there is no way to pause gocui, run the editor and restart gocui, // we have to stop it entirely and start a new one later. |