aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cache/bug_cache.go60
-rw-r--r--cache/bug_excerpt.go9
-rw-r--r--cache/cache.go45
-rw-r--r--cache/repo_cache.go133
-rw-r--r--graphql/models/models.go8
-rw-r--r--graphql/resolvers/mutation.go4
-rw-r--r--graphql/resolvers/query.go2
-rw-r--r--termui/bug_table.go10
-rw-r--r--termui/show_bug.go8
-rw-r--r--termui/termui.go18
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.