aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bug/bug.go10
-rw-r--r--bug/interface.go7
-rw-r--r--bug/operation.go13
-rw-r--r--bug/operations/add_comment.go2
-rw-r--r--bug/operations/create.go2
-rw-r--r--bug/operations/create_test.go2
-rw-r--r--bug/snapshot.go11
-rw-r--r--cache/bug_excerpt.go92
-rw-r--r--graphql/graph/gen_graph.go41
-rw-r--r--graphql/resolvers/bug.go5
-rw-r--r--termui/bug_table.go2
11 files changed, 171 insertions, 16 deletions
diff --git a/bug/bug.go b/bug/bug.go
index 1137ecfa..2c71d71b 100644
--- a/bug/bug.go
+++ b/bug/bug.go
@@ -579,6 +579,16 @@ func formatHumanId(id string) string {
return fmt.Sprintf(format, id)
}
+// CreateLamportTime return the Lamport time of creation
+func (bug *Bug) CreateLamportTime() util.LamportTime {
+ return bug.createTime
+}
+
+// EditLamportTime return the Lamport time of the last edit
+func (bug *Bug) EditLamportTime() util.LamportTime {
+ return bug.editTime
+}
+
// Lookup for the very first operation of the bug.
// For a valid Bug, this operation should be a CreateOp
func (bug *Bug) FirstOp() Operation {
diff --git a/bug/interface.go b/bug/interface.go
index af10b895..79333d07 100644
--- a/bug/interface.go
+++ b/bug/interface.go
@@ -2,6 +2,7 @@ package bug
import (
"github.com/MichaelMure/git-bug/repository"
+ "github.com/MichaelMure/git-bug/util"
)
type Interface interface {
@@ -38,6 +39,12 @@ type Interface interface {
// Compile a bug in a easily usable snapshot
Compile() Snapshot
+
+ // CreateLamportTime return the Lamport time of creation
+ CreateLamportTime() util.LamportTime
+
+ // EditLamportTime return the Lamport time of the last edit
+ EditLamportTime() util.LamportTime
}
func bugFromInterface(bug Interface) *Bug {
diff --git a/bug/operation.go b/bug/operation.go
index cdf87931..7d71e352 100644
--- a/bug/operation.go
+++ b/bug/operation.go
@@ -23,6 +23,8 @@ type Operation interface {
OpType() OperationType
// Time return the time when the operation was added
Time() time.Time
+ // unixTime return the unix timestamp when the operation was added
+ UnixTime() int64
// Apply the operation to a Snapshot to create the final state
Apply(snapshot Snapshot) Snapshot
// Files return the files needed by this operation
@@ -36,7 +38,7 @@ type Operation interface {
type OpBase struct {
OperationType OperationType
Author Person
- UnixTime int64
+ unixTime int64
}
// NewOpBase is the constructor for an OpBase
@@ -44,7 +46,7 @@ func NewOpBase(opType OperationType, author Person) OpBase {
return OpBase{
OperationType: opType,
Author: author,
- UnixTime: time.Now().Unix(),
+ unixTime: time.Now().Unix(),
}
}
@@ -55,7 +57,12 @@ func (op OpBase) OpType() OperationType {
// Time return the time when the operation was added
func (op OpBase) Time() time.Time {
- return time.Unix(op.UnixTime, 0)
+ return time.Unix(op.unixTime, 0)
+}
+
+// unixTime return the unix timestamp when the operation was added
+func (op OpBase) UnixTime() int64 {
+ return op.unixTime
}
// Files return the files needed by this operation
diff --git a/bug/operations/add_comment.go b/bug/operations/add_comment.go
index b4126a8e..5ecc471a 100644
--- a/bug/operations/add_comment.go
+++ b/bug/operations/add_comment.go
@@ -21,7 +21,7 @@ func (op AddCommentOperation) Apply(snapshot bug.Snapshot) bug.Snapshot {
Message: op.Message,
Author: op.Author,
Files: op.files,
- UnixTime: op.UnixTime,
+ UnixTime: op.UnixTime(),
}
snapshot.Comments = append(snapshot.Comments, comment)
diff --git a/bug/operations/create.go b/bug/operations/create.go
index ecbafb6f..5fc939dd 100644
--- a/bug/operations/create.go
+++ b/bug/operations/create.go
@@ -22,7 +22,7 @@ func (op CreateOperation) Apply(snapshot bug.Snapshot) bug.Snapshot {
{
Message: op.Message,
Author: op.Author,
- UnixTime: op.UnixTime,
+ UnixTime: op.UnixTime(),
},
}
snapshot.Author = op.Author
diff --git a/bug/operations/create_test.go b/bug/operations/create_test.go
index a20472d3..319cdb7f 100644
--- a/bug/operations/create_test.go
+++ b/bug/operations/create_test.go
@@ -21,7 +21,7 @@ func TestCreate(t *testing.T) {
expected := bug.Snapshot{
Title: "title",
Comments: []bug.Comment{
- {Author: rene, Message: "message", UnixTime: create.UnixTime},
+ {Author: rene, Message: "message", UnixTime: create.UnixTime()},
},
Author: rene,
CreatedAt: create.Time(),
diff --git a/bug/snapshot.go b/bug/snapshot.go
index 7f1d6099..1ef4534b 100644
--- a/bug/snapshot.go
+++ b/bug/snapshot.go
@@ -37,10 +37,19 @@ func (snap Snapshot) Summary() string {
}
// Return the last time a bug was modified
-func (snap Snapshot) LastEdit() time.Time {
+func (snap Snapshot) LastEditTime() time.Time {
if len(snap.Operations) == 0 {
return time.Unix(0, 0)
}
return snap.Operations[len(snap.Operations)-1].Time()
}
+
+// Return the last timestamp a bug was modified
+func (snap Snapshot) LastEditUnix() int64 {
+ if len(snap.Operations) == 0 {
+ return 0
+ }
+
+ return snap.Operations[len(snap.Operations)-1].UnixTime()
+}
diff --git a/cache/bug_excerpt.go b/cache/bug_excerpt.go
new file mode 100644
index 00000000..23ac459b
--- /dev/null
+++ b/cache/bug_excerpt.go
@@ -0,0 +1,92 @@
+package cache
+
+import (
+ "github.com/MichaelMure/git-bug/bug"
+ "github.com/MichaelMure/git-bug/util"
+)
+
+// BugExcerpt hold a subset of the bug values to be able to sort and filter bugs
+// efficiently without having to read and compile each raw bugs.
+type BugExcerpt struct {
+ Id string
+
+ CreateLamportTime util.LamportTime
+ EditLamportTime util.LamportTime
+ CreateUnixTime int64
+ EditUnixTime int64
+
+ Status bug.Status
+ Author bug.Person
+}
+
+func NewBugExcerpt(b *bug.Bug, snap bug.Snapshot) BugExcerpt {
+ return BugExcerpt{
+ Id: b.Id(),
+ CreateLamportTime: b.CreateLamportTime(),
+ EditLamportTime: b.EditLamportTime(),
+ CreateUnixTime: b.FirstOp().UnixTime(),
+ EditUnixTime: snap.LastEditUnix(),
+ Status: snap.Status,
+ Author: snap.Author,
+ }
+}
+
+/*
+ * Sorting
+ */
+
+type BugsByCreationTime []*BugExcerpt
+
+func (b BugsByCreationTime) Len() int {
+ return len(b)
+}
+
+func (b BugsByCreationTime) Less(i, j int) bool {
+ if b[i].CreateLamportTime < b[j].CreateLamportTime {
+ return true
+ }
+
+ if b[i].CreateLamportTime > b[j].CreateLamportTime {
+ return false
+ }
+
+ // When the logical clocks are identical, that means we had a concurrent
+ // edition. In this case we rely on the timestamp. While the timestamp might
+ // be incorrect due to a badly set clock, the drift in sorting is bounded
+ // by the first sorting using the logical clock. That means that if users
+ // synchronize their bugs regularly, the timestamp will rarely be used, and
+ // should still provide a kinda accurate sorting when needed.
+ return b[i].CreateUnixTime < b[j].CreateUnixTime
+}
+
+func (b BugsByCreationTime) Swap(i, j int) {
+ b[i], b[j] = b[j], b[i]
+}
+
+type BugsByEditTime []*BugExcerpt
+
+func (b BugsByEditTime) Len() int {
+ return len(b)
+}
+
+func (b BugsByEditTime) Less(i, j int) bool {
+ if b[i].EditLamportTime < b[j].EditLamportTime {
+ return true
+ }
+
+ if b[i].EditLamportTime > b[j].EditLamportTime {
+ return false
+ }
+
+ // When the logical clocks are identical, that means we had a concurrent
+ // edition. In this case we rely on the timestamp. While the timestamp might
+ // be incorrect due to a badly set clock, the drift in sorting is bounded
+ // by the first sorting using the logical clock. That means that if users
+ // synchronize their bugs regularly, the timestamp will rarely be used, and
+ // should still provide a kinda accurate sorting when needed.
+ return b[i].EditUnixTime < b[j].EditUnixTime
+}
+
+func (b BugsByEditTime) Swap(i, j int) {
+ b[i], b[j] = b[j], b[i]
+}
diff --git a/graphql/graph/gen_graph.go b/graphql/graph/gen_graph.go
index 06c65e84..798962e3 100644
--- a/graphql/graph/gen_graph.go
+++ b/graphql/graph/gen_graph.go
@@ -34,6 +34,7 @@ type Resolvers interface {
Bug_status(ctx context.Context, obj *bug.Snapshot) (models.Status, error)
+ Bug_lastEdit(ctx context.Context, obj *bug.Snapshot) (time.Time, error)
Bug_comments(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (models.CommentConnection, error)
Bug_operations(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (models.OperationConnection, error)
@@ -78,6 +79,7 @@ type AddCommentOperationResolver interface {
type BugResolver interface {
Status(ctx context.Context, obj *bug.Snapshot) (models.Status, error)
+ LastEdit(ctx context.Context, obj *bug.Snapshot) (time.Time, error)
Comments(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (models.CommentConnection, error)
Operations(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (models.OperationConnection, error)
}
@@ -124,6 +126,10 @@ func (s shortMapper) Bug_status(ctx context.Context, obj *bug.Snapshot) (models.
return s.r.Bug().Status(ctx, obj)
}
+func (s shortMapper) Bug_lastEdit(ctx context.Context, obj *bug.Snapshot) (time.Time, error) {
+ return s.r.Bug().LastEdit(ctx, obj)
+}
+
func (s shortMapper) Bug_comments(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (models.CommentConnection, error) {
return s.r.Bug().Comments(ctx, obj, after, before, first, last)
}
@@ -494,14 +500,33 @@ func (ec *executionContext) _Bug_createdAt(ctx context.Context, field graphql.Co
}
func (ec *executionContext) _Bug_lastEdit(ctx context.Context, field graphql.CollectedField, obj *bug.Snapshot) graphql.Marshaler {
- rctx := graphql.GetResolverContext(ctx)
- rctx.Object = "Bug"
- rctx.Args = nil
- rctx.Field = field
- rctx.PushField(field.Alias)
- defer rctx.Pop()
- res := obj.LastEdit()
- return graphql.MarshalTime(res)
+ ctx = graphql.WithResolverContext(ctx, &graphql.ResolverContext{
+ Object: "Bug",
+ Args: nil,
+ Field: field,
+ })
+ return graphql.Defer(func() (ret graphql.Marshaler) {
+ defer func() {
+ if r := recover(); r != nil {
+ userErr := ec.Recover(ctx, r)
+ ec.Error(ctx, userErr)
+ ret = graphql.Null
+ }
+ }()
+
+ resTmp, err := ec.ResolverMiddleware(ctx, func(ctx context.Context) (interface{}, error) {
+ return ec.resolvers.Bug_lastEdit(ctx, obj)
+ })
+ if err != nil {
+ ec.Error(ctx, err)
+ return graphql.Null
+ }
+ if resTmp == nil {
+ return graphql.Null
+ }
+ res := resTmp.(time.Time)
+ return graphql.MarshalTime(res)
+ })
}
func (ec *executionContext) _Bug_comments(ctx context.Context, field graphql.CollectedField, obj *bug.Snapshot) graphql.Marshaler {
diff --git a/graphql/resolvers/bug.go b/graphql/resolvers/bug.go
index b3385243..858feb16 100644
--- a/graphql/resolvers/bug.go
+++ b/graphql/resolvers/bug.go
@@ -2,6 +2,7 @@ package resolvers
import (
"context"
+ "time"
"github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/graphql/connections"
@@ -67,3 +68,7 @@ func (bugResolver) Operations(ctx context.Context, obj *bug.Snapshot, after *str
return connections.BugOperationCon(obj.Operations, edger, conMaker, input)
}
+
+func (bugResolver) LastEdit(ctx context.Context, obj *bug.Snapshot) (time.Time, error) {
+ return obj.LastEditTime(), nil
+}
diff --git a/termui/bug_table.go b/termui/bug_table.go
index 169dd7c1..1158f768 100644
--- a/termui/bug_table.go
+++ b/termui/bug_table.go
@@ -290,7 +290,7 @@ func (bt *bugTable) render(v *gocui.View, maxX int) {
title := util.LeftPaddedString(snap.Title, columnWidths["title"], 2)
author := util.LeftPaddedString(person.Name, columnWidths["author"], 2)
summary := util.LeftPaddedString(snap.Summary(), columnWidths["summary"], 2)
- lastEdit := util.LeftPaddedString(humanize.Time(snap.LastEdit()), columnWidths["lastEdit"], 2)
+ lastEdit := util.LeftPaddedString(humanize.Time(snap.LastEditTime()), columnWidths["lastEdit"], 2)
fmt.Fprintf(v, "%s %s %s %s %s %s\n",
util.Cyan(id),