Detailed changes
@@ -1,7 +1,7 @@
//go:generate genny -in=connection_template.go -out=gen_lazy_bug.go gen "Name=LazyBug NodeType=entity.Id EdgeType=LazyBugEdge ConnectionType=models.BugConnection"
//go:generate genny -in=connection_template.go -out=gen_lazy_identity.go gen "Name=LazyIdentity NodeType=entity.Id EdgeType=LazyIdentityEdge ConnectionType=models.IdentityConnection"
//go:generate genny -in=connection_template.go -out=gen_identity.go gen "Name=Identity NodeType=models.IdentityWrapper EdgeType=models.IdentityEdge ConnectionType=models.IdentityConnection"
-//go:generate genny -in=connection_template.go -out=gen_operation.go gen "Name=Operation NodeType=bug.Operation EdgeType=models.OperationEdge ConnectionType=models.OperationConnection"
+//go:generate genny -in=connection_template.go -out=gen_operation.go gen "Name=Operation NodeType=dag.Operation EdgeType=models.OperationEdge ConnectionType=models.OperationConnection"
//go:generate genny -in=connection_template.go -out=gen_comment.go gen "Name=Comment NodeType=bug.Comment EdgeType=models.CommentEdge ConnectionType=models.CommentConnection"
//go:generate genny -in=connection_template.go -out=gen_timeline.go gen "Name=TimelineItem NodeType=bug.TimelineItem EdgeType=models.TimelineItemEdge ConnectionType=models.TimelineItemConnection"
//go:generate genny -in=connection_template.go -out=gen_label.go gen "Name=Label NodeType=bug.Label EdgeType=models.LabelEdge ConnectionType=models.LabelConnection"
@@ -8,23 +8,23 @@ import (
"fmt"
"github.com/MichaelMure/git-bug/api/graphql/models"
- "github.com/MichaelMure/git-bug/bug"
+ "github.com/MichaelMure/git-bug/entity/dag"
)
-// BugOperationEdgeMaker define a function that take a bug.Operation and an offset and
+// DagOperationEdgeMaker define a function that take a dag.Operation and an offset and
// create an Edge.
-type OperationEdgeMaker func(value bug.Operation, offset int) Edge
+type OperationEdgeMaker func(value dag.Operation, offset int) Edge
// OperationConMaker define a function that create a models.OperationConnection
type OperationConMaker func(
edges []*models.OperationEdge,
- nodes []bug.Operation,
+ nodes []dag.Operation,
info *models.PageInfo,
totalCount int) (*models.OperationConnection, error)
// OperationCon will paginate a source according to the input of a relay connection
-func OperationCon(source []bug.Operation, edgeMaker OperationEdgeMaker, conMaker OperationConMaker, input models.ConnectionInput) (*models.OperationConnection, error) {
- var nodes []bug.Operation
+func OperationCon(source []dag.Operation, edgeMaker OperationEdgeMaker, conMaker OperationConMaker, input models.ConnectionInput) (*models.OperationConnection, error) {
+ var nodes []dag.Operation
var edges []*models.OperationEdge
var cursors []string
var pageInfo = &models.PageInfo{}
@@ -33,7 +33,7 @@ models:
Hash:
model: github.com/MichaelMure/git-bug/repository.Hash
Operation:
- model: github.com/MichaelMure/git-bug/bug.Operation
+ model: github.com/MichaelMure/git-bug/entity/dag.Operation
CreateOperation:
model: github.com/MichaelMure/git-bug/bug.CreateOperation
SetTitleOperation:
@@ -17,6 +17,7 @@ import (
"github.com/99designs/gqlgen/graphql/introspection"
"github.com/MichaelMure/git-bug/api/graphql/models"
"github.com/MichaelMure/git-bug/bug"
+ "github.com/MichaelMure/git-bug/entity/dag"
"github.com/MichaelMure/git-bug/repository"
gqlparser "github.com/vektah/gqlparser/v2"
"github.com/vektah/gqlparser/v2/ast"
@@ -1902,6 +1903,17 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
rc := graphql.GetOperationContext(ctx)
ec := executionContext{rc, e}
+ inputUnmarshalMap := graphql.BuildUnmarshalerMap(
+ ec.unmarshalInputAddCommentAndCloseBugInput,
+ ec.unmarshalInputAddCommentAndReopenBugInput,
+ ec.unmarshalInputAddCommentInput,
+ ec.unmarshalInputChangeLabelInput,
+ ec.unmarshalInputCloseBugInput,
+ ec.unmarshalInputEditCommentInput,
+ ec.unmarshalInputNewBugInput,
+ ec.unmarshalInputOpenBugInput,
+ ec.unmarshalInputSetTitleInput,
+ )
first := true
switch rc.Operation.Operation {
@@ -1911,6 +1923,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
return nil
}
first = false
+ ctx = graphql.WithUnmarshalerMap(ctx, inputUnmarshalMap)
data := ec._Query(ctx, rc.Operation.SelectionSet)
var buf bytes.Buffer
data.MarshalGQL(&buf)
@@ -1925,6 +1938,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
return nil
}
first = false
+ ctx = graphql.WithUnmarshalerMap(ctx, inputUnmarshalMap)
data := ec._Mutation(ctx, rc.Operation.SelectionSet)
var buf bytes.Buffer
data.MarshalGQL(&buf)
@@ -1959,7 +1973,7 @@ func (ec *executionContext) introspectType(name string) (*introspection.Type, er
}
var sources = []*ast.Source{
- {Name: "schema/bug.graphql", Input: `"""Represents a comment on a bug."""
+ {Name: "../schema/bug.graphql", Input: `"""Represents a comment on a bug."""
type Comment implements Authored {
"""The author of this comment."""
author: Identity!
@@ -2078,7 +2092,7 @@ type BugEdge {
node: Bug!
}
`, BuiltIn: false},
- {Name: "schema/identity.graphql", Input: `"""Represents an identity"""
+ {Name: "../schema/identity.graphql", Input: `"""Represents an identity"""
type Identity {
"""The identifier for this identity"""
id: String!
@@ -2110,7 +2124,7 @@ type IdentityEdge {
cursor: String!
node: Identity!
}`, BuiltIn: false},
- {Name: "schema/label.graphql", Input: `"""Label for a bug."""
+ {Name: "../schema/label.graphql", Input: `"""Label for a bug."""
type Label {
"""The name of the label."""
name: String!
@@ -2129,7 +2143,7 @@ type LabelEdge {
cursor: String!
node: Label!
}`, BuiltIn: false},
- {Name: "schema/mutations.graphql", Input: `input NewBugInput {
+ {Name: "../schema/mutations.graphql", Input: `input NewBugInput {
"""A unique identifier for the client performing the mutation."""
clientMutationId: String
"""The name of the repository. If not set, the default repository is used."""
@@ -2340,7 +2354,7 @@ type SetTitlePayload {
operation: SetTitleOperation!
}
`, BuiltIn: false},
- {Name: "schema/operations.graphql", Input: `"""An operation applied to a bug."""
+ {Name: "../schema/operations.graphql", Input: `"""An operation applied to a bug."""
interface Operation {
"""The identifier of the operation"""
id: String!
@@ -2441,7 +2455,7 @@ type LabelChangeOperation implements Operation & Authored {
removed: [Label!]!
}
`, BuiltIn: false},
- {Name: "schema/repository.graphql", Input: `
+ {Name: "../schema/repository.graphql", Input: `
type Repository {
"""The name of the repository"""
name: String
@@ -2492,7 +2506,7 @@ type Repository {
): LabelConnection!
}
`, BuiltIn: false},
- {Name: "schema/root.graphql", Input: `type Query {
+ {Name: "../schema/root.graphql", Input: `type Query {
"""Access a repository by reference/name. If no ref is given, the default repository is returned if any."""
repository(ref: String): Repository
}
@@ -2518,7 +2532,7 @@ type Mutation {
setTitle(input: SetTitleInput!): SetTitlePayload!
}
`, BuiltIn: false},
- {Name: "schema/timeline.graphql", Input: `"""An item in the timeline of events"""
+ {Name: "../schema/timeline.graphql", Input: `"""An item in the timeline of events"""
interface TimelineItem {
"""The identifier of the source operation"""
id: String!
@@ -2605,7 +2619,7 @@ type SetTitleTimelineItem implements TimelineItem & Authored {
was: String!
}
`, BuiltIn: false},
- {Name: "schema/types.graphql", Input: `scalar Time
+ {Name: "../schema/types.graphql", Input: `scalar Time
scalar Hash
"""Defines a color by red, green and blue components."""
@@ -3222,21 +3236,17 @@ func (ec *executionContext) field___Type_fields_args(ctx context.Context, rawArg
// region **************************** field.gotpl *****************************
func (ec *executionContext) _AddCommentAndCloseBugPayload_clientMutationId(ctx context.Context, field graphql.CollectedField, obj *models.AddCommentAndCloseBugPayload) (ret graphql.Marshaler) {
+ fc, err := ec.fieldContext_AddCommentAndCloseBugPayload_clientMutationId(ctx, field)
+ if err != nil {
+ return graphql.Null
+ }
+ ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
- fc := &graphql.FieldContext{
- Object: "AddCommentAndCloseBugPayload",
- Field: field,
- Args: nil,
- IsMethod: false,
- IsResolver: false,
- }
-
- ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.ClientMutationID, nil
@@ -3253,22 +3263,31 @@ func (ec *executionContext) _AddCommentAndCloseBugPayload_clientMutationId(ctx c
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
-func (ec *executionContext) _AddCommentAndCloseBugPayload_bug(ctx context.Context, field graphql.CollectedField, obj *models.AddCommentAndCloseBugPayload) (ret graphql.Marshaler) {
- defer func() {
- if r := recover(); r != nil {
- ec.Error(ctx, ec.Recover(ctx, r))
- ret = graphql.Null
- }
- }()
- fc := &graphql.FieldContext{
+func (ec *executionContext) fieldContext_AddCommentAndCloseBugPayload_clientMutationId(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+ fc = &graphql.FieldContext{
Object: "AddCommentAndCloseBugPayload",
Field: field,
- Args: nil,
IsMethod: false,
IsResolver: false,
+ Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+ return nil, errors.New("field of type String does not have child fields")
+ },
}
+ return fc, nil
+}
+func (ec *executionContext) _AddCommentAndCloseBugPayload_bug(ctx context.Context, field graphql.CollectedField, obj *models.AddCommentAndCloseBugPayload) (ret graphql.Marshaler) {
+ fc, err := ec.fieldContext_AddCommentAndCloseBugPayload_bug(ctx, field)
+ if err != nil {
+ return graphql.Null
+ }
ctx = graphql.WithFieldContext(ctx, fc)
+ defer func() {
+ if r := recover(); r != nil {
+ ec.Error(ctx, ec.Recover(ctx, r))
+ ret = graphql.Null
+ }
+ }()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Bug, nil
@@ -3288,22 +3307,59 @@ func (ec *executionContext) _AddCommentAndCloseBugPayload_bug(ctx context.Contex
return ec.marshalNBug2githubᚗcomᚋMichaelMureᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐBugWrapper(ctx, field.Selections, res)
}
+func (ec *executionContext) fieldContext_AddCommentAndCloseBugPayload_bug(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+ fc = &graphql.FieldContext{
+ Object: "AddCommentAndCloseBugPayload",
+ Field: field,
+ IsMethod: false,
+ IsResolver: false,
+ Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+ switch field.Name {
+ case "id":
+ return ec.fieldContext_Bug_id(ctx, field)
+ case "humanId":
+ return ec.fieldContext_Bug_humanId(ctx, field)
+ case "status":
+ return ec.fieldContext_Bug_status(ctx, field)
+ case "title":
+ return ec.fieldContext_Bug_title(ctx, field)
+ case "labels":
+ return ec.fieldContext_Bug_labels(ctx, field)
+ case "author":
+ return ec.fieldContext_Bug_author(ctx, field)
+ case "createdAt":
+ return ec.fieldContext_Bug_createdAt(ctx, field)
+ case "lastEdit":
+ return ec.fieldContext_Bug_lastEdit(ctx, field)
+ case "actors":
+ return ec.fieldContext_Bug_actors(ctx, field)
+ case "participants":
+ return ec.fieldContext_Bug_participants(ctx, field)
+ case "comments":
+ return ec.fieldContext_Bug_comments(ctx, field)
+ case "timeline":
+ return ec.fieldContext_Bug_timeline(ctx, field)
+ case "operations":
+ return ec.fieldContext_Bug_operations(ctx, field)
+ }
+ return nil, fmt.Errorf("no field named %q was found under type Bug", field.Name)
+ },
+ }
+ return fc, nil
+}
+
func (ec *executionContext) _AddCommentAndCloseBugPayload_commentOperation(ctx context.Context, field graphql.CollectedField, obj *models.AddCommentAndCloseBugPayload) (ret graphql.Marshaler) {
+ fc, err := ec.fieldContext_AddCommentAndCloseBugPayload_commentOperation(ctx, field)
+ if err != nil {
+ return graphql.Null
+ }
+ ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
- fc := &graphql.FieldContext{
- Object: "AddCommentAndCloseBugPayload",
- Field: field,
- Args: nil,
- IsMethod: false,
- IsResolver: false,
- }
-
- ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.CommentOperation, nil
@@ -3323,22 +3379,43 @@ func (ec *executionContext) _AddCommentAndCloseBugPayload_commentOperation(ctx c
return ec.marshalNAddCommentOperation2ᚖgithubᚗcomᚋMichaelMureᚋgitᚑbugᚋbugᚐAddCommentOperation(ctx, field.Selections, res)
}
-func (ec *executionContext) _AddCommentAndCloseBugPayload_statusOperation(ctx context.Context, field graphql.CollectedField, obj *models.AddCommentAndCloseBugPayload) (ret graphql.Marshaler) {
- defer func() {
- if r := recover(); r != nil {
- ec.Error(ctx, ec.Recover(ctx, r))
- ret = graphql.Null
- }
- }()
- fc := &graphql.FieldContext{
+func (ec *executionContext) fieldContext_AddCommentAndCloseBugPayload_commentOperation(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+ fc = &graphql.FieldContext{
Object: "AddCommentAndCloseBugPayload",
Field: field,
- Args: nil,
IsMethod: false,
IsResolver: false,
+ Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+ switch field.Name {
+ case "id":
+ return ec.fieldContext_AddCommentOperation_id(ctx, field)
+ case "author":
+ return ec.fieldContext_AddCommentOperation_author(ctx, field)
+ case "date":
+ return ec.fieldContext_AddCommentOperation_date(ctx, field)
+ case "message":
+ return ec.fieldContext_AddCommentOperation_message(ctx, field)
+ case "files":
+ return ec.fieldContext_AddCommentOperation_files(ctx, field)
+ }
+ return nil, fmt.Errorf("no field named %q was found under type AddCommentOperation", field.Name)
+ },
}
+ return fc, nil
+}
+func (ec *executionContext) _AddCommentAndCloseBugPayload_statusOperation(ctx context.Context, field graphql.CollectedField, obj *models.AddCommentAndCloseBugPayload) (ret graphql.Marshaler) {
+ fc, err := ec.fieldContext_AddCommentAndCloseBugPayload_statusOperation(ctx, field)
+ if err != nil {
+ return graphql.Null
+ }
ctx = graphql.WithFieldContext(ctx, fc)
+ defer func() {
+ if r := recover(); r != nil {
+ ec.Error(ctx, ec.Recover(ctx, r))
+ ret = graphql.Null
+ }
+ }()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.StatusOperation, nil
@@ -3358,22 +3435,41 @@ func (ec *executionContext) _AddCommentAndCloseBugPayload_statusOperation(ctx co
return ec.marshalNSetStatusOperation2ᚖgithubᚗcomᚋMichaelMureᚋgitᚑbugᚋbugᚐSetStatusOperation(ctx, field.Selections, res)
}
+func (ec *executionContext) fieldContext_AddCommentAndCloseBugPayload_statusOperation(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+ fc = &graphql.FieldContext{
+ Object: "AddCommentAndCloseBugPayload",
+ Field: field,
+ IsMethod: false,
+ IsResolver: false,
+ Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+ switch field.Name {
+ case "id":
+ return ec.fieldContext_SetStatusOperation_id(ctx, field)
+ case "author":
+ return ec.fieldContext_SetStatusOperation_author(ctx, field)
+ case "date":
+ return ec.fieldContext_SetStatusOperation_date(ctx, field)
+ case "status":
+ return ec.fieldContext_SetStatusOperation_status(ctx, field)
+ }
+ return nil, fmt.Errorf("no field named %q was found under type SetStatusOperation", field.Name)
+ },
+ }
+ return fc, nil
+}
+
func (ec *executionContext) _AddCommentAndReopenBugPayload_clientMutationId(ctx context.Context, field graphql.CollectedField, obj *models.AddCommentAndReopenBugPayload) (ret graphql.Marshaler) {
+ fc, err := ec.fieldContext_AddCommentAndReopenBugPayload_clientMutationId(ctx, field)
+ if err != nil {
+ return graphql.Null
+ }
+ ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
- fc := &graphql.FieldContext{
- Object: "AddCommentAndReopenBugPayload",
- Field: field,
- Args: nil,
- IsMethod: false,
- IsResolver: false,
- }
-
- ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.ClientMutationID, nil
@@ -3390,22 +3486,31 @@ func (ec *executionContext) _AddCommentAndReopenBugPayload_clientMutationId(ctx
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
-func (ec *executionContext) _AddCommentAndReopenBugPayload_bug(ctx context.Context, field graphql.CollectedField, obj *models.AddCommentAndReopenBugPayload) (ret graphql.Marshaler) {
- defer func() {
- if r := recover(); r != nil {
- ec.Error(ctx, ec.Recover(ctx, r))
- ret = graphql.Null
- }
- }()
- fc := &graphql.FieldContext{
+func (ec *executionContext) fieldContext_AddCommentAndReopenBugPayload_clientMutationId(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+ fc = &graphql.FieldContext{
Object: "AddCommentAndReopenBugPayload",
Field: field,
- Args: nil,
IsMethod: false,
IsResolver: false,
+ Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+ return nil, errors.New("field of type String does not have child fields")
+ },
}
+ return fc, nil
+}
+func (ec *executionContext) _AddCommentAndReopenBugPayload_bug(ctx context.Context, field graphql.CollectedField, obj *models.AddCommentAndReopenBugPayload) (ret graphql.Marshaler) {
+ fc, err := ec.fieldContext_AddCommentAndReopenBugPayload_bug(ctx, field)
+ if err != nil {
+ return graphql.Null
+ }
ctx = graphql.WithFieldContext(ctx, fc)
+ defer func() {
+ if r := recover(); r != nil {
+ ec.Error(ctx, ec.Recover(ctx, r))
+ ret = graphql.Null
+ }
+ }()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Bug, nil
@@ -3425,22 +3530,59 @@ func (ec *executionContext) _AddCommentAndReopenBugPayload_bug(ctx context.Conte
return ec.marshalNBug2githubᚗcomᚋMichaelMureᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐBugWrapper(ctx, field.Selections, res)
}
+func (ec *executionContext) fieldContext_AddCommentAndReopenBugPayload_bug(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+ fc = &graphql.FieldContext{
+ Object: "AddCommentAndReopenBugPayload",
+ Field: field,
+ IsMethod: false,
+ IsResolver: false,
+ Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+ switch field.Name {
+ case "id":
+ return ec.fieldContext_Bug_id(ctx, field)
+ case "humanId":
+ return ec.fieldContext_Bug_humanId(ctx, field)
+ case "status":
+ return ec.fieldContext_Bug_status(ctx, field)
+ case "title":
+ return ec.fieldContext_Bug_title(ctx, field)
+ case "labels":
+ return ec.fieldContext_Bug_labels(ctx, field)
+ case "author":
+ return ec.fieldContext_Bug_author(ctx, field)
+ case "createdAt":
+ return ec.fieldContext_Bug_createdAt(ctx, field)
+ case "lastEdit":
+ return ec.fieldContext_Bug_lastEdit(ctx, field)
+ case "actors":
+ return ec.fieldContext_Bug_actors(ctx, field)
+ case "participants":
+ return ec.fieldContext_Bug_participants(ctx, field)
+ case "comments":
+ return ec.fieldContext_Bug_comments(ctx, field)
+ case "timeline":
+ return ec.fieldContext_Bug_timeline(ctx, field)
+ case "operations":
+ return ec.fieldContext_Bug_operations(ctx, field)
+ }
+ return nil, fmt.Errorf("no field named %q was found under type Bug", field.Name)
+ },
+ }
+ return fc, nil
+}
+
func (ec *executionContext) _AddCommentAndReopenBugPayload_commentOperation(ctx context.Context, field graphql.CollectedField, obj *models.AddCommentAndReopenBugPayload) (ret graphql.Marshaler) {
+ fc, err := ec.fieldContext_AddCommentAndReopenBugPayload_commentOperation(ctx, field)
+ if err != nil {
+ return graphql.Null
+ }
+ ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
- fc := &graphql.FieldContext{
- Object: "AddCommentAndReopenBugPayload",
- Field: field,
- Args: nil,
- IsMethod: false,
- IsResolver: false,
- }
-
- ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.CommentOperation, nil
@@ -3460,22 +3602,43 @@ func (ec *executionContext) _AddCommentAndReopenBugPayload_commentOperation(ctx
return ec.marshalNAddCommentOperation2ᚖgithubᚗcomᚋMichaelMureᚋgitᚑbugᚋbugᚐAddCommentOperation(ctx, field.Selections, res)
}
-func (ec *executionContext) _AddCommentAndReopenBugPayload_statusOperation(ctx context.Context, field graphql.CollectedField, obj *models.AddCommentAndReopenBugPayload) (ret graphql.Marshaler) {
- defer func() {
- if r := recover(); r != nil {
- ec.Error(ctx, ec.Recover(ctx, r))
- ret = graphql.Null
- }
- }()
- fc := &graphql.FieldContext{
+func (ec *executionContext) fieldContext_AddCommentAndReopenBugPayload_commentOperation(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+ fc = &graphql.FieldContext{
Object: "AddCommentAndReopenBugPayload",
Field: field,
- Args: nil,
IsMethod: false,
IsResolver: false,
+ Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+ switch field.Name {
+ case "id":
+ return ec.fieldContext_AddCommentOperation_id(ctx, field)
+ case "author":
+ return ec.fieldContext_AddCommentOperation_author(ctx, field)
+ case "date":
+ return ec.fieldContext_AddCommentOperation_date(ctx, field)
+ case "message":
+ return ec.fieldContext_AddCommentOperation_message(ctx, field)
+ case "files":
+ return ec.fieldContext_AddCommentOperation_files(ctx, field)
+ }
+ return nil, fmt.Errorf("no field named %q was found under type AddCommentOperation", field.Name)
+ },
}
+ return fc, nil
+}
+func (ec *executionContext) _AddCommentAndReopenBugPayload_statusOperation(ctx context.Context, field graphql.CollectedField, obj *models.AddCommentAndReopenBugPayload) (ret graphql.Marshaler) {
+ fc, err := ec.fieldContext_AddCommentAndReopenBugPayload_statusOperation(ctx, field)
+ if err != nil {
+ return graphql.Null
+ }
ctx = graphql.WithFieldContext(ctx, fc)
+ defer func() {
+ if r := recover(); r != nil {
+ ec.Error(ctx, ec.Recover(ctx, r))
+ ret = graphql.Null
+ }
+ }()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.StatusOperation, nil
@@ -3495,22 +3658,41 @@ func (ec *executionContext) _AddCommentAndReopenBugPayload_statusOperation(ctx c
return ec.marshalNSetStatusOperation2ᚖgithubᚗcomᚋMichaelMureᚋgitᚑbugᚋbugᚐSetStatusOperation(ctx, field.Selections, res)
}
+func (ec *executionContext) fieldContext_AddCommentAndReopenBugPayload_statusOperation(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+ fc = &graphql.FieldContext{
+ Object: "AddCommentAndReopenBugPayload",
+ Field: field,
+ IsMethod: false,
+ IsResolver: false,
+ Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+ switch field.Name {
+ case "id":
+ return ec.fieldContext_SetStatusOperation_id(ctx, field)
+ case "author":
+ return ec.fieldContext_SetStatusOperation_author(ctx, field)
+ case "date":
+ return ec.fieldContext_SetStatusOperation_date(ctx, field)
+ case "status":
+ return ec.fieldContext_SetStatusOperation_status(ctx, field)
+ }
+ return nil, fmt.Errorf("no field named %q was found under type SetStatusOperation", field.Name)
+ },
+ }
+ return fc, nil
+}
+
func (ec *executionContext) _AddCommentOperation_id(ctx context.Context, field graphql.CollectedField, obj *bug.AddCommentOperation) (ret graphql.Marshaler) {
+ fc, err := ec.fieldContext_AddCommentOperation_id(ctx, field)
+ if err != nil {
+ return graphql.Null
+ }
+ ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
- fc := &graphql.FieldContext{
- Object: "AddCommentOperation",
- Field: field,
- Args: nil,
- IsMethod: true,
- IsResolver: true,
- }
-
- ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.AddCommentOperation().ID(rctx, obj)
@@ -3530,22 +3712,31 @@ func (ec *executionContext) _AddCommentOperation_id(ctx context.Context, field g
return ec.marshalNString2string(ctx, field.Selections, res)
}
-func (ec *executionContext) _AddCommentOperation_author(ctx context.Context, field graphql.CollectedField, obj *bug.AddCommentOperation) (ret graphql.Marshaler) {
- defer func() {
- if r := recover(); r != nil {
- ec.Error(ctx, ec.Recover(ctx, r))
- ret = graphql.Null
- }
- }()
- fc := &graphql.FieldContext{
+func (ec *executionContext) fieldContext_AddCommentOperation_id(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+ fc = &graphql.FieldContext{
Object: "AddCommentOperation",
Field: field,
- Args: nil,
IsMethod: true,
IsResolver: true,
+ Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+ return nil, errors.New("field of type String does not have child fields")
+ },
}
+ return fc, nil
+}
+func (ec *executionContext) _AddCommentOperation_author(ctx context.Context, field graphql.CollectedField, obj *bug.AddCommentOperation) (ret graphql.Marshaler) {
+ fc, err := ec.fieldContext_AddCommentOperation_author(ctx, field)
+ if err != nil {
+ return graphql.Null
+ }
ctx = graphql.WithFieldContext(ctx, fc)
+ defer func() {
+ if r := recover(); r != nil {
+ ec.Error(ctx, ec.Recover(ctx, r))
+ ret = graphql.Null
+ }
+ }()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.AddCommentOperation().Author(rctx, obj)
@@ -3565,22 +3756,49 @@ func (ec *executionContext) _AddCommentOperation_author(ctx context.Context, fie
return ec.marshalNIdentity2githubᚗcomᚋMichaelMureᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐIdentityWrapper(ctx, field.Selections, res)
}
+func (ec *executionContext) fieldContext_AddCommentOperation_author(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+ fc = &graphql.FieldContext{
+ Object: "AddCommentOperation",
+ Field: field,
+ IsMethod: true,
+ IsResolver: true,
+ Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+ switch field.Name {
+ case "id":
+ return ec.fieldContext_Identity_id(ctx, field)
+ case "humanId":
+ return ec.fieldContext_Identity_humanId(ctx, field)
+ case "name":
+ return ec.fieldContext_Identity_name(ctx, field)
+ case "email":
+ return ec.fieldContext_Identity_email(ctx, field)
+ case "login":
+ return ec.fieldContext_Identity_login(ctx, field)
+ case "displayName":
+ return ec.fieldContext_Identity_displayName(ctx, field)
+ case "avatarUrl":
+ return ec.fieldContext_Identity_avatarUrl(ctx, field)
+ case "isProtected":
+ return ec.fieldContext_Identity_isProtected(ctx, field)
+ }
+ return nil, fmt.Errorf("no field named %q was found under type Identity", field.Name)
+ },
+ }
+ return fc, nil
+}
+
func (ec *executionContext) _AddCommentOperation_date(ctx context.Context, field graphql.CollectedField, obj *bug.AddCommentOperation) (ret graphql.Marshaler) {
+ fc, err := ec.fieldContext_AddCommentOperation_date(ctx, field)
+ if err != nil {
+ return graphql.Null
+ }
+ ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
- fc := &graphql.FieldContext{
- Object: "AddCommentOperation",
- Field: field,
- Args: nil,
- IsMethod: true,
- IsResolver: true,
- }
-
- ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.AddCommentOperation().Date(rctx, obj)
@@ -3600,22 +3818,31 @@ func (ec *executionContext) _AddCommentOperation_date(ctx context.Context, field
return ec.marshalNTime2ᚖtimeᚐTime(ctx, field.Selections, res)
}
+func (ec *executionContext) fieldContext_AddCommentOperation_date(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+ fc = &graphql.FieldContext{
+ Object: "AddCommentOperation",
+ Field: field,
+ IsMethod: true,
+ IsResolver: true,
+ Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+ return nil, errors.New("field of type Time does not have child fields")
+ },
+ }
+ return fc, nil
+}
+
func (ec *executionContext) _AddCommentOperation_message(ctx context.Context, field graphql.CollectedField, obj *bug.AddCommentOperation) (ret graphql.Marshaler) {
+ fc, err := ec.fieldContext_AddCommentOperation_message(ctx, field)
+ if err != nil {
+ return graphql.Null
+ }
+ ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
- fc := &graphql.FieldContext{
- Object: "AddCommentOperation",
- Field: field,
- Args: nil,
- IsMethod: false,
- IsResolver: false,
- }
-
- ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Message, nil
@@ -3635,22 +3862,31 @@ func (ec *executionContext) _AddCommentOperation_message(ctx context.Context, fi
return ec.marshalNString2string(ctx, field.Selections, res)
}
-func (ec *executionContext) _AddCommentOperation_files(ctx context.Context, field graphql.CollectedField, obj *bug.AddCommentOperation) (ret graphql.Marshaler) {
- defer func() {
- if r := recover(); r != nil {
- ec.Error(ctx, ec.Recover(ctx, r))
- ret = graphql.Null
- }
- }()
- fc := &graphql.FieldContext{
+func (ec *executionContext) fieldContext_AddCommentOperation_message(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+ fc = &graphql.FieldContext{
Object: "AddCommentOperation",
Field: field,
- Args: nil,
IsMethod: false,
IsResolver: false,
+ Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+ return nil, errors.New("field of type String does not have child fields")
+ },
}
+ return fc, nil
+}
+func (ec *executionContext) _AddCommentOperation_files(ctx context.Context, field graphql.CollectedField, obj *bug.AddCommentOperation) (ret graphql.Marshaler) {
+ fc, err := ec.fieldContext_AddCommentOperation_files(ctx, field)
+ if err != nil {
+ return graphql.Null
+ }
ctx = graphql.WithFieldContext(ctx, fc)
+ defer func() {
+ if r := recover(); r != nil {
+ ec.Error(ctx, ec.Recover(ctx, r))
+ ret = graphql.Null
+ }
+ }()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Files, nil
@@ -3670,22 +3906,31 @@ func (ec *executionContext) _AddCommentOperation_files(ctx context.Context, fiel
return ec.marshalNHash2ᚕgithubᚗcomᚋMichaelMureᚋgitᚑbugᚋrepositoryᚐHashᚄ(ctx, field.Selections, res)
}
+func (ec *executionContext) fieldContext_AddCommentOperation_files(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+ fc = &graphql.FieldContext{
+ Object: "AddCommentOperation",
+ Field: field,
+ IsMethod: false,
+ IsResolver: false,
+ Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+ return nil, errors.New("field of type Hash does not have child fields")
+ },
+ }
+ return fc, nil
+}
+
func (ec *executionContext) _AddCommentPayload_clientMutationId(ctx context.Context, field graphql.CollectedField, obj *models.AddCommentPayload) (ret graphql.Marshaler) {
+ fc, err := ec.fieldContext_AddCommentPayload_clientMutationId(ctx, field)
+ if err != nil {
+ return graphql.Null
+ }
+ ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
- fc := &graphql.FieldContext{
- Object: "AddCommentPayload",
- Field: field,
- Args: nil,
- IsMethod: false,
- IsResolver: false,
- }
-
- ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.ClientMutationID, nil
@@ -3702,23 +3947,32 @@ func (ec *executionContext) _AddCommentPayload_clientMutationId(ctx context.Cont
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
-func (ec *executionContext) _AddCommentPayload_bug(ctx context.Context, field graphql.CollectedField, obj *models.AddCommentPayload) (ret graphql.Marshaler) {
- defer func() {
- if r := recover(); r != nil {
- ec.Error(ctx, ec.Recover(ctx, r))
- ret = graphql.Null
- }
- }()
- fc := &graphql.FieldContext{
+func (ec *executionContext) fieldContext_AddCommentPayload_clientMutationId(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+ fc = &graphql.FieldContext{
Object: "AddCommentPayload",
Field: field,
- Args: nil,
IsMethod: false,
IsResolver: false,
+ Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+ return nil, errors.New("field of type String does not have child fields")
+ },
}
+ return fc, nil
+}
- ctx = graphql.WithFieldContext(ctx, fc)
- resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+func (ec *executionContext) _AddCommentPayload_bug(ctx context.Context, field graphql.CollectedField, obj *models.AddCommentPayload) (ret graphql.Marshaler) {
+ fc, err := ec.fieldContext_AddCommentPayload_bug(ctx, field)
+ if err != nil {
+ return graphql.Null
+ }
+ ctx = graphql.WithFieldContext(ctx, fc)
+ defer func() {
+ if r := recover(); r != nil {
+ ec.Error(ctx, ec.Recover(ctx, r))
+ ret = graphql.Null
+ }
+ }()
+ resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Bug, nil
})
@@ -3737,22 +3991,59 @@ func (ec *executionContext) _AddCommentPayload_bug(ctx context.Context, field gr
return ec.marshalNBug2githubᚗcomᚋMichaelMureᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐBugWrapper(ctx, field.Selections, res)
}
+func (ec *executionContext) fieldContext_AddCommentPayload_bug(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+ fc = &graphql.FieldContext{
+ Object: "AddCommentPayload",
+ Field: field,
+ IsMethod: false,
+ IsResolver: false,
+ Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+ switch field.Name {
+ case "id":
+ return ec.fieldContext_Bug_id(ctx, field)
+ case "humanId":
+ return ec.fieldContext_Bug_humanId(ctx, field)
+ case "status":
+ return ec.fieldContext_Bug_status(ctx, field)
+ case "title":
+ return ec.fieldContext_Bug_title(ctx, field)
+ case "labels":
+ return ec.fieldContext_Bug_labels(ctx, field)
+ case "author":
+ return ec.fieldContext_Bug_author(ctx, field)
+ case "createdAt":
+ return ec.fieldContext_Bug_createdAt(ctx, field)
+ case "lastEdit":
+ return ec.fieldContext_Bug_lastEdit(ctx, field)
+ case "actors":
+ return ec.fieldContext_Bug_actors(ctx, field)
+ case "participants":
+ return ec.fieldContext_Bug_participants(ctx, field)
+ case "comments":
+ return ec.fieldContext_Bug_comments(ctx, field)
+ case "timeline":
+ return ec.fieldContext_Bug_timeline(ctx, field)
+ case "operations":
+ return ec.fieldContext_Bug_operations(ctx, field)
+ }
+ return nil, fmt.Errorf("no field named %q was found under type Bug", field.Name)
+ },
+ }
+ return fc, nil
+}
+
func (ec *executionContext) _AddCommentPayload_operation(ctx context.Context, field graphql.CollectedField, obj *models.AddCommentPayload) (ret graphql.Marshaler) {
+ fc, err := ec.fieldContext_AddCommentPayload_operation(ctx, field)
+ if err != nil {
+ return graphql.Null
+ }
+ ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
- fc := &graphql.FieldContext{
- Object: "AddCommentPayload",
- Field: field,
- Args: nil,
- IsMethod: false,
- IsResolver: false,
- }
-
- ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Operation, nil
@@ -3772,22 +4063,43 @@ func (ec *executionContext) _AddCommentPayload_operation(ctx context.Context, fi
return ec.marshalNAddCommentOperation2ᚖgithubᚗcomᚋMichaelMureᚋgitᚑbugᚋbugᚐAddCommentOperation(ctx, field.Selections, res)
}
+func (ec *executionContext) fieldContext_AddCommentPayload_operation(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+ fc = &graphql.FieldContext{
+ Object: "AddCommentPayload",
+ Field: field,
+ IsMethod: false,
+ IsResolver: false,
+ Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+ switch field.Name {
+ case "id":
+ return ec.fieldContext_AddCommentOperation_id(ctx, field)
+ case "author":
+ return ec.fieldContext_AddCommentOperation_author(ctx, field)
+ case "date":
+ return ec.fieldContext_AddCommentOperation_date(ctx, field)
+ case "message":
+ return ec.fieldContext_AddCommentOperation_message(ctx, field)
+ case "files":
+ return ec.fieldContext_AddCommentOperation_files(ctx, field)
+ }
+ return nil, fmt.Errorf("no field named %q was found under type AddCommentOperation", field.Name)
+ },
+ }
+ return fc, nil
+}
+
func (ec *executionContext) _AddCommentTimelineItem_id(ctx context.Context, field graphql.CollectedField, obj *bug.AddCommentTimelineItem) (ret graphql.Marshaler) {
+ fc, err := ec.fieldContext_AddCommentTimelineItem_id(ctx, field)
+ if err != nil {
+ return graphql.Null
+ }
+ ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
- fc := &graphql.FieldContext{
- Object: "AddCommentTimelineItem",
- Field: field,
- Args: nil,
- IsMethod: true,
- IsResolver: true,
- }
-
- ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.AddCommentTimelineItem().ID(rctx, obj)
@@ -3807,22 +4119,31 @@ func (ec *executionContext) _AddCommentTimelineItem_id(ctx context.Context, fiel
return ec.marshalNString2string(ctx, field.Selections, res)
}
-func (ec *executionContext) _AddCommentTimelineItem_author(ctx context.Context, field graphql.CollectedField, obj *bug.AddCommentTimelineItem) (ret graphql.Marshaler) {
- defer func() {
- if r := recover(); r != nil {
- ec.Error(ctx, ec.Recover(ctx, r))
- ret = graphql.Null
- }
- }()
- fc := &graphql.FieldContext{
+func (ec *executionContext) fieldContext_AddCommentTimelineItem_id(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+ fc = &graphql.FieldContext{
Object: "AddCommentTimelineItem",
Field: field,
- Args: nil,
IsMethod: true,
IsResolver: true,
+ Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+ return nil, errors.New("field of type String does not have child fields")
+ },
}
+ return fc, nil
+}
+func (ec *executionContext) _AddCommentTimelineItem_author(ctx context.Context, field graphql.CollectedField, obj *bug.AddCommentTimelineItem) (ret graphql.Marshaler) {
+ fc, err := ec.fieldContext_AddCommentTimelineItem_author(ctx, field)
+ if err != nil {
+ return graphql.Null
+ }
ctx = graphql.WithFieldContext(ctx, fc)
+ defer func() {
+ if r := recover(); r != nil {
+ ec.Error(ctx, ec.Recover(ctx, r))
+ ret = graphql.Null
+ }
+ }()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.AddCommentTimelineItem().Author(rctx, obj)
@@ -8,6 +8,7 @@ import (
"strconv"
"github.com/MichaelMure/git-bug/bug"
+ "github.com/MichaelMure/git-bug/entity/dag"
"github.com/MichaelMure/git-bug/repository"
)
@@ -250,7 +251,7 @@ type OpenBugPayload struct {
// The connection type for an Operation
type OperationConnection struct {
Edges []*OperationEdge `json:"edges"`
- Nodes []bug.Operation `json:"nodes"`
+ Nodes []dag.Operation `json:"nodes"`
PageInfo *PageInfo `json:"pageInfo"`
TotalCount int `json:"totalCount"`
}
@@ -258,7 +259,7 @@ type OperationConnection struct {
// Represent an Operation
type OperationEdge struct {
Cursor string `json:"cursor"`
- Node bug.Operation `json:"node"`
+ Node dag.Operation `json:"node"`
}
// Information about pagination in a connection.
@@ -7,6 +7,7 @@ import (
"github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/cache"
"github.com/MichaelMure/git-bug/entity"
+ "github.com/MichaelMure/git-bug/entity/dag"
)
// BugWrapper is an interface used by the GraphQL resolvers to handle a bug.
@@ -24,7 +25,7 @@ type BugWrapper interface {
Participants() ([]IdentityWrapper, error)
CreatedAt() time.Time
Timeline() ([]bug.TimelineItem, error)
- Operations() ([]bug.Operation, error)
+ Operations() ([]dag.Operation, error)
IsAuthored()
}
@@ -144,7 +145,7 @@ func (lb *lazyBug) Timeline() ([]bug.TimelineItem, error) {
return lb.snap.Timeline, nil
}
-func (lb *lazyBug) Operations() ([]bug.Operation, error) {
+func (lb *lazyBug) Operations() ([]dag.Operation, error) {
err := lb.load()
if err != nil {
return nil, err
@@ -210,6 +211,6 @@ func (l *loadedBug) Timeline() ([]bug.TimelineItem, error) {
return l.Snapshot.Timeline, nil
}
-func (l *loadedBug) Operations() ([]bug.Operation, error) {
+func (l *loadedBug) Operations() ([]dag.Operation, error) {
return l.Snapshot.Operations, nil
}
@@ -7,6 +7,7 @@ import (
"github.com/MichaelMure/git-bug/api/graphql/graph"
"github.com/MichaelMure/git-bug/api/graphql/models"
"github.com/MichaelMure/git-bug/bug"
+ "github.com/MichaelMure/git-bug/entity/dag"
)
var _ graph.BugResolver = &bugResolver{}
@@ -69,14 +70,14 @@ func (bugResolver) Operations(_ context.Context, obj models.BugWrapper, after *s
Last: last,
}
- edger := func(op bug.Operation, offset int) connections.Edge {
+ edger := func(op dag.Operation, offset int) connections.Edge {
return models.OperationEdge{
Node: op,
Cursor: connections.OffsetToCursor(offset),
}
}
- conMaker := func(edges []*models.OperationEdge, nodes []bug.Operation, info *models.PageInfo, totalCount int) (*models.OperationConnection, error) {
+ conMaker := func(edges []*models.OperationEdge, nodes []dag.Operation, info *models.PageInfo, totalCount int) (*models.OperationConnection, error) {
return &models.OperationConnection{
Edges: edges,
Nodes: nodes,
@@ -20,6 +20,7 @@ import (
"github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/cache"
"github.com/MichaelMure/git-bug/entity"
+ "github.com/MichaelMure/git-bug/entity/dag"
"github.com/MichaelMure/git-bug/identity"
)
@@ -288,7 +289,7 @@ func (ge *githubExporter) exportBug(ctx context.Context, b *cache.BugCache, out
for _, op := range snapshot.Operations[1:] {
// ignore SetMetadata operations
- if _, ok := op.(*bug.SetMetadataOperation); ok {
+ if _, ok := op.(dag.OperationDoesntChangeSnapshot); ok {
continue
}
@@ -15,8 +15,8 @@ import (
"github.com/MichaelMure/git-bug/bridge/core"
"github.com/MichaelMure/git-bug/bridge/core/auth"
- "github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/cache"
+ "github.com/MichaelMure/git-bug/entity/dag"
"github.com/MichaelMure/git-bug/repository"
"github.com/MichaelMure/git-bug/util/interrupt"
)
@@ -245,7 +245,7 @@ func TestGithubPushPull(t *testing.T) {
// verify operation have correct metadata
for _, op := range tt.bug.Snapshot().Operations {
// Check if the originals operations (*not* SetMetadata) are tagged properly
- if _, ok := op.(*bug.SetMetadataOperation); !ok {
+ if _, ok := op.(dag.OperationDoesntChangeSnapshot); !ok {
_, haveIDMetadata := op.GetMetadata(metaKeyGithubId)
require.True(t, haveIDMetadata)
@@ -13,6 +13,7 @@ import (
"github.com/MichaelMure/git-bug/bridge/core/auth"
"github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/cache"
+ "github.com/MichaelMure/git-bug/entity/dag"
"github.com/MichaelMure/git-bug/identity"
"github.com/MichaelMure/git-bug/repository"
"github.com/MichaelMure/git-bug/util/interrupt"
@@ -44,7 +45,7 @@ func TestGithubImporter(t *testing.T) {
name: "simple issue",
url: "https://github.com/MichaelMure/git-bug-test-github-bridge/issues/1",
bug: &bug.Snapshot{
- Operations: []bug.Operation{
+ Operations: []dag.Operation{
bug.NewCreateOp(author, 0, "simple issue", "initial comment", nil),
bug.NewAddCommentOp(author, 0, "first comment", nil),
bug.NewAddCommentOp(author, 0, "second comment", nil),
@@ -55,7 +56,7 @@ func TestGithubImporter(t *testing.T) {
name: "empty issue",
url: "https://github.com/MichaelMure/git-bug-test-github-bridge/issues/2",
bug: &bug.Snapshot{
- Operations: []bug.Operation{
+ Operations: []dag.Operation{
bug.NewCreateOp(author, 0, "empty issue", "", nil),
},
},
@@ -64,7 +65,7 @@ func TestGithubImporter(t *testing.T) {
name: "complex issue",
url: "https://github.com/MichaelMure/git-bug-test-github-bridge/issues/3",
bug: &bug.Snapshot{
- Operations: []bug.Operation{
+ Operations: []dag.Operation{
bug.NewCreateOp(author, 0, "complex issue", "initial comment", nil),
bug.NewLabelChangeOperation(author, 0, []bug.Label{"bug"}, []bug.Label{}),
bug.NewLabelChangeOperation(author, 0, []bug.Label{"duplicate"}, []bug.Label{}),
@@ -81,7 +82,7 @@ func TestGithubImporter(t *testing.T) {
name: "editions",
url: "https://github.com/MichaelMure/git-bug-test-github-bridge/issues/4",
bug: &bug.Snapshot{
- Operations: []bug.Operation{
+ Operations: []dag.Operation{
bug.NewCreateOp(author, 0, "editions", "initial comment edited", nil),
bug.NewEditCommentOp(author, 0, "", "erased then edited again", nil),
bug.NewAddCommentOp(author, 0, "first comment", nil),
@@ -93,7 +94,7 @@ func TestGithubImporter(t *testing.T) {
name: "comment deletion",
url: "https://github.com/MichaelMure/git-bug-test-github-bridge/issues/5",
bug: &bug.Snapshot{
- Operations: []bug.Operation{
+ Operations: []dag.Operation{
bug.NewCreateOp(author, 0, "comment deletion", "", nil),
},
},
@@ -102,7 +103,7 @@ func TestGithubImporter(t *testing.T) {
name: "edition deletion",
url: "https://github.com/MichaelMure/git-bug-test-github-bridge/issues/6",
bug: &bug.Snapshot{
- Operations: []bug.Operation{
+ Operations: []dag.Operation{
bug.NewCreateOp(author, 0, "edition deletion", "initial comment", nil),
bug.NewEditCommentOp(author, 0, "", "initial comment edited again", nil),
bug.NewAddCommentOp(author, 0, "first comment", nil),
@@ -114,7 +115,7 @@ func TestGithubImporter(t *testing.T) {
name: "hidden comment",
url: "https://github.com/MichaelMure/git-bug-test-github-bridge/issues/7",
bug: &bug.Snapshot{
- Operations: []bug.Operation{
+ Operations: []dag.Operation{
bug.NewCreateOp(author, 0, "hidden comment", "initial comment", nil),
bug.NewAddCommentOp(author, 0, "first comment", nil),
},
@@ -124,7 +125,7 @@ func TestGithubImporter(t *testing.T) {
name: "transfered issue",
url: "https://github.com/MichaelMure/git-bug-test-github-bridge/issues/8",
bug: &bug.Snapshot{
- Operations: []bug.Operation{
+ Operations: []dag.Operation{
bug.NewCreateOp(author, 0, "transfered issue", "", nil),
},
},
@@ -133,7 +134,7 @@ func TestGithubImporter(t *testing.T) {
name: "unicode control characters",
url: "https://github.com/MichaelMure/git-bug-test-github-bridge/issues/10",
bug: &bug.Snapshot{
- Operations: []bug.Operation{
+ Operations: []dag.Operation{
bug.NewCreateOp(author, 0, "unicode control characters", "u0000: \nu0001: \nu0002: \nu0003: \nu0004: \nu0005: \nu0006: \nu0007: \nu0008: \nu0009: \t\nu0010: \nu0011: \nu0012: \nu0013: \nu0014: \nu0015: \nu0016: \nu0017: \nu0018: \nu0019:", nil),
},
},
@@ -15,6 +15,7 @@ import (
"github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/cache"
"github.com/MichaelMure/git-bug/entity"
+ "github.com/MichaelMure/git-bug/entity/dag"
"github.com/MichaelMure/git-bug/identity"
)
@@ -256,7 +257,7 @@ func (ge *gitlabExporter) exportBug(ctx context.Context, b *cache.BugCache, out
labelSet := make(map[string]struct{})
for _, op := range snapshot.Operations[1:] {
// ignore SetMetadata operations
- if _, ok := op.(*bug.SetMetadataOperation); ok {
+ if _, ok := op.(dag.OperationDoesntChangeSnapshot); ok {
continue
}
@@ -11,11 +11,12 @@ import (
"github.com/xanzy/go-gitlab"
+ "github.com/MichaelMure/git-bug/entity/dag"
+
"github.com/stretchr/testify/require"
"github.com/MichaelMure/git-bug/bridge/core"
"github.com/MichaelMure/git-bug/bridge/core/auth"
- "github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/cache"
"github.com/MichaelMure/git-bug/repository"
"github.com/MichaelMure/git-bug/util/interrupt"
@@ -247,7 +248,7 @@ func TestGitlabPushPull(t *testing.T) {
// verify operation have correct metadata
for _, op := range tt.bug.Snapshot().Operations {
// Check if the originals operations (*not* SetMetadata) are tagged properly
- if _, ok := op.(*bug.SetMetadataOperation); !ok {
+ if _, ok := op.(dag.OperationDoesntChangeSnapshot); !ok {
_, haveIDMetadata := op.GetMetadata(metaKeyGitlabId)
require.True(t, haveIDMetadata)
@@ -272,7 +273,7 @@ func TestGitlabPushPull(t *testing.T) {
require.True(t, ok)
require.Equal(t, issueOrigin, target)
- //TODO: maybe more tests to ensure bug final state
+ // TODO: maybe more tests to ensure bug final state
})
}
}
@@ -13,6 +13,7 @@ import (
"github.com/MichaelMure/git-bug/bridge/core/auth"
"github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/cache"
+ "github.com/MichaelMure/git-bug/entity/dag"
"github.com/MichaelMure/git-bug/identity"
"github.com/MichaelMure/git-bug/repository"
"github.com/MichaelMure/git-bug/util/interrupt"
@@ -49,7 +50,7 @@ func TestGitlabImport(t *testing.T) {
name: "simple issue",
url: "https://gitlab.com/git-bug/test/-/issues/1",
bug: &bug.Snapshot{
- Operations: []bug.Operation{
+ Operations: []dag.Operation{
bug.NewCreateOp(author, 0, "simple issue", "initial comment", nil),
bug.NewAddCommentOp(author, 0, "first comment", nil),
bug.NewAddCommentOp(author, 0, "second comment", nil),
@@ -60,7 +61,7 @@ func TestGitlabImport(t *testing.T) {
name: "empty issue",
url: "https://gitlab.com/git-bug/test/-/issues/2",
bug: &bug.Snapshot{
- Operations: []bug.Operation{
+ Operations: []dag.Operation{
bug.NewCreateOp(author, 0, "empty issue", "", nil),
},
},
@@ -69,7 +70,7 @@ func TestGitlabImport(t *testing.T) {
name: "complex issue",
url: "https://gitlab.com/git-bug/test/-/issues/3",
bug: &bug.Snapshot{
- Operations: []bug.Operation{
+ Operations: []dag.Operation{
bug.NewCreateOp(author, 0, "complex issue", "initial comment", nil),
bug.NewAddCommentOp(author, 0, "### header\n\n**bold**\n\n_italic_\n\n> with quote\n\n`inline code`\n\n```\nmultiline code\n```\n\n- bulleted\n- list\n\n1. numbered\n1. list\n\n- [ ] task\n- [x] list\n\n@MichaelMure mention\n\n#2 reference issue\n#3 auto-reference issue", nil),
bug.NewSetTitleOp(author, 0, "complex issue edited", "complex issue"),
@@ -86,7 +87,7 @@ func TestGitlabImport(t *testing.T) {
name: "editions",
url: "https://gitlab.com/git-bug/test/-/issues/4",
bug: &bug.Snapshot{
- Operations: []bug.Operation{
+ Operations: []dag.Operation{
bug.NewCreateOp(author, 0, "editions", "initial comment edited", nil),
bug.NewAddCommentOp(author, 0, "first comment edited", nil),
},
@@ -15,6 +15,7 @@ import (
"github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/cache"
"github.com/MichaelMure/git-bug/entity"
+ "github.com/MichaelMure/git-bug/entity/dag"
"github.com/MichaelMure/git-bug/identity"
)
@@ -297,7 +298,7 @@ func (je *jiraExporter) exportBug(ctx context.Context, b *cache.BugCache, out ch
for _, op := range snapshot.Operations[1:] {
// ignore SetMetadata operations
- if _, ok := op.(*bug.SetMetadataOperation); ok {
+ if _, ok := op.(dag.OperationDoesntChangeSnapshot); ok {
continue
}
@@ -14,6 +14,7 @@ import (
"github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/cache"
"github.com/MichaelMure/git-bug/entity"
+ "github.com/MichaelMure/git-bug/entity/dag"
"github.com/MichaelMure/git-bug/util/text"
)
@@ -377,7 +378,7 @@ func labelSetsMatch(jiraSet []string, gitbugSet []bug.Label) bool {
// Create a bug.Operation (or a series of operations) from a JIRA changelog
// entry
-func (ji *jiraImporter) ensureChange(repo *cache.RepoCache, b *cache.BugCache, entry ChangeLogEntry, potentialOp bug.Operation) error {
+func (ji *jiraImporter) ensureChange(repo *cache.RepoCache, b *cache.BugCache, entry ChangeLogEntry, potentialOp dag.Operation) error {
// If we have an operation which is already mapped to the entire changelog
// entry then that means this changelog entry was induced by an export
@@ -44,11 +44,7 @@ func NewBug() *Bug {
// Read will read a bug from a repository
func Read(repo repository.ClockedRepo, id entity.Id) (*Bug, error) {
- e, err := dag.Read(def, repo, identity.NewSimpleResolver(repo), id)
- if err != nil {
- return nil, err
- }
- return &Bug{Entity: e}, nil
+ return ReadWithResolver(repo, identity.NewSimpleResolver(repo), id)
}
// ReadWithResolver will read a bug from its Id, with a custom identity.Resolver
@@ -158,7 +154,7 @@ func (bug *Bug) Compile() Snapshot {
return snap
}
-// Lookup for the very first operation of the bug.
+// FirstOp lookup for the very first operation of the bug.
// For a valid Bug, this operation should be a CreateOp
func (bug *Bug) FirstOp() Operation {
if fo := bug.Entity.FirstOp(); fo != nil {
@@ -167,7 +163,7 @@ func (bug *Bug) FirstOp() Operation {
return nil
}
-// Lookup for the very last operation of the bug.
+// LastOp lookup for the very last operation of the bug.
// For a valid Bug, should never be nil
func (bug *Bug) LastOp() Operation {
if lo := bug.Entity.LastOp(); lo != nil {
@@ -41,5 +41,5 @@ func (c Comment) FormatTime() string {
return c.UnixTime.Time().Format("Mon Jan 2 15:04:05 2006 +0200")
}
-// Sign post method for gqlgen
+// IsAuthored is a sign post method for gqlgen
func (c Comment) IsAuthored() {}
@@ -19,17 +19,17 @@ type Interface interface {
// Operations return the ordered operations
Operations() []Operation
- // Indicate that the in-memory state changed and need to be commit in the repository
+ // NeedCommit indicate that the in-memory state changed and need to be commit in the repository
NeedCommit() bool
// Commit write the staging area in Git and move the operations to the packs
Commit(repo repository.ClockedRepo) error
- // Lookup for the very first operation of the bug.
+ // FirstOp lookup for the very first operation of the bug.
// For a valid Bug, this operation should be a CreateOp
FirstOp() Operation
- // Lookup for the very last operation of the bug.
+ // LastOp lookup for the very last operation of the bug.
// For a valid Bug, should never be nil
LastOp() Operation
@@ -42,14 +42,3 @@ type Interface interface {
// EditLamportTime return the Lamport time of the last edit
EditLamportTime() lamport.Time
}
-
-func bugFromInterface(bug Interface) *Bug {
- switch bug := bug.(type) {
- case *Bug:
- return bug
- case *WithSnapshot:
- return bug.Bug
- default:
- panic("missing type case")
- }
-}
@@ -1,7 +1,6 @@
package bug
import (
- "encoding/json"
"fmt"
"github.com/MichaelMure/git-bug/entity"
@@ -17,24 +16,24 @@ var _ dag.OperationWithFiles = &AddCommentOperation{}
// AddCommentOperation will add a new comment in the bug
type AddCommentOperation struct {
- OpBase
+ dag.OpBase
Message string `json:"message"`
// TODO: change for a map[string]util.hash to store the filename ?
Files []repository.Hash `json:"files"`
}
func (op *AddCommentOperation) Id() entity.Id {
- return idOperation(op, &op.OpBase)
+ return dag.IdOperation(op, &op.OpBase)
}
func (op *AddCommentOperation) Apply(snapshot *Snapshot) {
- snapshot.addActor(op.Author_)
- snapshot.addParticipant(op.Author_)
+ snapshot.addActor(op.Author())
+ snapshot.addParticipant(op.Author())
comment := Comment{
id: entity.CombineIds(snapshot.Id(), op.Id()),
Message: op.Message,
- Author: op.Author_,
+ Author: op.Author(),
Files: op.Files,
UnixTime: timestamp.Timestamp(op.UnixTime),
}
@@ -64,52 +63,20 @@ func (op *AddCommentOperation) Validate() error {
return nil
}
-// UnmarshalJSON is a two-steps JSON unmarshalling
-// This workaround is necessary to avoid the inner OpBase.MarshalJSON
-// overriding the outer op's MarshalJSON
-func (op *AddCommentOperation) UnmarshalJSON(data []byte) error {
- // Unmarshal OpBase and the op separately
-
- base := OpBase{}
- err := json.Unmarshal(data, &base)
- if err != nil {
- return err
- }
-
- aux := struct {
- Message string `json:"message"`
- Files []repository.Hash `json:"files"`
- }{}
-
- err = json.Unmarshal(data, &aux)
- if err != nil {
- return err
- }
-
- op.OpBase = base
- op.Message = aux.Message
- op.Files = aux.Files
-
- return nil
-}
-
-// Sign post method for gqlgen
-func (op *AddCommentOperation) IsAuthored() {}
-
func NewAddCommentOp(author identity.Interface, unixTime int64, message string, files []repository.Hash) *AddCommentOperation {
return &AddCommentOperation{
- OpBase: newOpBase(AddCommentOp, author, unixTime),
+ OpBase: dag.NewOpBase(AddCommentOp, author, unixTime),
Message: message,
Files: files,
}
}
-// CreateTimelineItem replace a AddComment operation in the Timeline and hold its edition history
+// AddCommentTimelineItem hold a comment in the timeline
type AddCommentTimelineItem struct {
CommentTimelineItem
}
-// Sign post method for gqlgen
+// IsAuthored is a sign post method for gqlgen
func (a *AddCommentTimelineItem) IsAuthored() {}
// Convenience function to apply the operation
@@ -1,37 +1,18 @@
package bug
import (
- "encoding/json"
"testing"
- "time"
-
- "github.com/stretchr/testify/require"
+ "github.com/MichaelMure/git-bug/entity/dag"
"github.com/MichaelMure/git-bug/identity"
"github.com/MichaelMure/git-bug/repository"
)
func TestAddCommentSerialize(t *testing.T) {
- repo := repository.NewMockRepo()
-
- rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr")
- require.NoError(t, err)
-
- unix := time.Now().Unix()
- before := NewAddCommentOp(rene, unix, "message", nil)
-
- data, err := json.Marshal(before)
- require.NoError(t, err)
-
- var after AddCommentOperation
- err = json.Unmarshal(data, &after)
- require.NoError(t, err)
-
- // enforce creating the ID
- before.Id()
-
- // Replace the identity as it's not serialized
- after.Author_ = rene
-
- require.Equal(t, before, &after)
+ dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *AddCommentOperation {
+ return NewAddCommentOp(author, unixTime, "message", nil)
+ })
+ dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *AddCommentOperation {
+ return NewAddCommentOp(author, unixTime, "message", []repository.Hash{"hash1", "hash2"})
+ })
}
@@ -1,7 +1,6 @@
package bug
import (
- "encoding/json"
"fmt"
"github.com/MichaelMure/git-bug/entity"
@@ -17,53 +16,38 @@ var _ dag.OperationWithFiles = &CreateOperation{}
// CreateOperation define the initial creation of a bug
type CreateOperation struct {
- OpBase
+ dag.OpBase
Title string `json:"title"`
Message string `json:"message"`
Files []repository.Hash `json:"files"`
}
func (op *CreateOperation) Id() entity.Id {
- return idOperation(op, &op.OpBase)
-}
-
-// OVERRIDE
-func (op *CreateOperation) SetMetadata(key string, value string) {
- // sanity check: we make sure we are not in the following scenario:
- // - the bug is created with a first operation
- // - Id() is used
- // - metadata are added, which will change the Id
- // - Id() is used again
-
- if op.id != entity.UnsetId {
- panic("usage of Id() after changing the first operation")
- }
-
- op.OpBase.SetMetadata(key, value)
+ return dag.IdOperation(op, &op.OpBase)
}
func (op *CreateOperation) Apply(snapshot *Snapshot) {
// sanity check: will fail when adding a second Create
if snapshot.id != "" && snapshot.id != entity.UnsetId && snapshot.id != op.Id() {
- panic("adding a second Create operation")
+ return
}
snapshot.id = op.Id()
- snapshot.addActor(op.Author_)
- snapshot.addParticipant(op.Author_)
+ snapshot.addActor(op.Author())
+ snapshot.addParticipant(op.Author())
snapshot.Title = op.Title
comment := Comment{
id: entity.CombineIds(snapshot.Id(), op.Id()),
Message: op.Message,
- Author: op.Author_,
+ Author: op.Author(),
UnixTime: timestamp.Timestamp(op.UnixTime),
}
snapshot.Comments = []Comment{comment}
- snapshot.Author = op.Author_
+ snapshot.Author = op.Author()
snapshot.CreateTime = op.Time()
snapshot.Timeline = []TimelineItem{
@@ -82,13 +66,6 @@ func (op *CreateOperation) Validate() error {
return err
}
- if len(op.Nonce) > 64 {
- return fmt.Errorf("create nonce is too big")
- }
- if len(op.Nonce) < 20 {
- return fmt.Errorf("create nonce is too small")
- }
-
if text.Empty(op.Title) {
return fmt.Errorf("title is empty")
}
@@ -103,45 +80,9 @@ func (op *CreateOperation) Validate() error {
return nil
}
-// UnmarshalJSON is a two step JSON unmarshalling
-// This workaround is necessary to avoid the inner OpBase.MarshalJSON
-// overriding the outer op's MarshalJSON
-func (op *CreateOperation) UnmarshalJSON(data []byte) error {
- // Unmarshal OpBase and the op separately
-
- base := OpBase{}
- err := json.Unmarshal(data, &base)
- if err != nil {
- return err
- }
-
- aux := struct {
- Nonce []byte `json:"nonce"`
- Title string `json:"title"`
- Message string `json:"message"`
- Files []repository.Hash `json:"files"`
- }{}
-
- err = json.Unmarshal(data, &aux)
- if err != nil {
- return err
- }
-
- op.OpBase = base
- op.Nonce = aux.Nonce
- op.Title = aux.Title
- op.Message = aux.Message
- op.Files = aux.Files
-
- return nil
-}
-
-// Sign post method for gqlgen
-func (op *CreateOperation) IsAuthored() {}
-
func NewCreateOp(author identity.Interface, unixTime int64, title, message string, files []repository.Hash) *CreateOperation {
return &CreateOperation{
- OpBase: newOpBase(CreateOp, author, unixTime),
+ OpBase: dag.NewOpBase(CreateOp, author, unixTime),
Title: title,
Message: message,
Files: files,
@@ -153,7 +94,7 @@ type CreateTimelineItem struct {
CommentTimelineItem
}
-// Sign post method for gqlgen
+// IsAuthored is a sign post method for gqlgen
func (c *CreateTimelineItem) IsAuthored() {}
// Convenience function to apply the operation
@@ -1,13 +1,13 @@
package bug
import (
- "encoding/json"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/MichaelMure/git-bug/entity"
+ "github.com/MichaelMure/git-bug/entity/dag"
"github.com/MichaelMure/git-bug/identity"
"github.com/MichaelMure/git-bug/repository"
"github.com/MichaelMure/git-bug/util/timestamp"
@@ -58,26 +58,10 @@ func TestCreate(t *testing.T) {
}
func TestCreateSerialize(t *testing.T) {
- repo := repository.NewMockRepo()
-
- rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr")
- require.NoError(t, err)
-
- unix := time.Now().Unix()
- before := NewCreateOp(rene, unix, "title", "message", nil)
-
- data, err := json.Marshal(before)
- require.NoError(t, err)
-
- var after CreateOperation
- err = json.Unmarshal(data, &after)
- require.NoError(t, err)
-
- // enforce creating the ID
- before.Id()
-
- // Replace the identity as it's not serialized
- after.Author_ = rene
-
- require.Equal(t, before, &after)
+ dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *CreateOperation {
+ return NewCreateOp(author, unixTime, "title", "message", nil)
+ })
+ dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *CreateOperation {
+ return NewCreateOp(author, unixTime, "title", "message", []repository.Hash{"hash1", "hash2"})
+ })
}
@@ -1,7 +1,6 @@
package bug
import (
- "encoding/json"
"fmt"
"github.com/pkg/errors"
@@ -20,14 +19,14 @@ var _ dag.OperationWithFiles = &EditCommentOperation{}
// EditCommentOperation will change a comment in the bug
type EditCommentOperation struct {
- OpBase
+ dag.OpBase
Target entity.Id `json:"target"`
Message string `json:"message"`
Files []repository.Hash `json:"files"`
}
func (op *EditCommentOperation) Id() entity.Id {
- return idOperation(op, &op.OpBase)
+ return dag.IdOperation(op, &op.OpBase)
}
func (op *EditCommentOperation) Apply(snapshot *Snapshot) {
@@ -68,7 +67,7 @@ func (op *EditCommentOperation) Apply(snapshot *Snapshot) {
return
}
- snapshot.addActor(op.Author_)
+ snapshot.addActor(op.Author())
// Updating the corresponding comment
@@ -101,43 +100,9 @@ func (op *EditCommentOperation) Validate() error {
return nil
}
-// UnmarshalJSON is two steps JSON unmarshalling
-// This workaround is necessary to avoid the inner OpBase.MarshalJSON
-// overriding the outer op's MarshalJSON
-func (op *EditCommentOperation) UnmarshalJSON(data []byte) error {
- // Unmarshal OpBase and the op separately
-
- base := OpBase{}
- err := json.Unmarshal(data, &base)
- if err != nil {
- return err
- }
-
- aux := struct {
- Target entity.Id `json:"target"`
- Message string `json:"message"`
- Files []repository.Hash `json:"files"`
- }{}
-
- err = json.Unmarshal(data, &aux)
- if err != nil {
- return err
- }
-
- op.OpBase = base
- op.Target = aux.Target
- op.Message = aux.Message
- op.Files = aux.Files
-
- return nil
-}
-
-// Sign post method for gqlgen
-func (op *EditCommentOperation) IsAuthored() {}
-
func NewEditCommentOp(author identity.Interface, unixTime int64, target entity.Id, message string, files []repository.Hash) *EditCommentOperation {
return &EditCommentOperation{
- OpBase: newOpBase(EditCommentOp, author, unixTime),
+ OpBase: dag.NewOpBase(EditCommentOp, author, unixTime),
Target: target,
Message: message,
Files: files,
@@ -1,12 +1,12 @@
package bug
import (
- "encoding/json"
"testing"
"time"
"github.com/stretchr/testify/require"
+ "github.com/MichaelMure/git-bug/entity/dag"
"github.com/MichaelMure/git-bug/identity"
"github.com/MichaelMure/git-bug/repository"
)
@@ -75,26 +75,10 @@ func TestEdit(t *testing.T) {
}
func TestEditCommentSerialize(t *testing.T) {
- repo := repository.NewMockRepo()
-
- rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr")
- require.NoError(t, err)
-
- unix := time.Now().Unix()
- before := NewEditCommentOp(rene, unix, "target", "message", nil)
-
- data, err := json.Marshal(before)
- require.NoError(t, err)
-
- var after EditCommentOperation
- err = json.Unmarshal(data, &after)
- require.NoError(t, err)
-
- // enforce creating the ID
- before.Id()
-
- // Replace the identity as it's not serialized
- after.Author_ = rene
-
- require.Equal(t, before, &after)
+ dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *EditCommentOperation {
+ return NewEditCommentOp(author, unixTime, "target", "message", nil)
+ })
+ dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *EditCommentOperation {
+ return NewEditCommentOp(author, unixTime, "target", "message", []repository.Hash{"hash1", "hash2"})
+ })
}
@@ -1,13 +1,13 @@
package bug
import (
- "encoding/json"
"fmt"
"sort"
"github.com/pkg/errors"
"github.com/MichaelMure/git-bug/entity"
+ "github.com/MichaelMure/git-bug/entity/dag"
"github.com/MichaelMure/git-bug/identity"
"github.com/MichaelMure/git-bug/util/timestamp"
)
@@ -16,18 +16,18 @@ var _ Operation = &LabelChangeOperation{}
// LabelChangeOperation define a Bug operation to add or remove labels
type LabelChangeOperation struct {
- OpBase
+ dag.OpBase
Added []Label `json:"added"`
Removed []Label `json:"removed"`
}
func (op *LabelChangeOperation) Id() entity.Id {
- return idOperation(op, &op.OpBase)
+ return dag.IdOperation(op, &op.OpBase)
}
// Apply apply the operation
func (op *LabelChangeOperation) Apply(snapshot *Snapshot) {
- snapshot.addActor(op.Author_)
+ snapshot.addActor(op.Author())
// Add in the set
AddLoop:
@@ -59,7 +59,7 @@ AddLoop:
item := &LabelChangeTimelineItem{
id: op.Id(),
- Author: op.Author_,
+ Author: op.Author(),
UnixTime: timestamp.Timestamp(op.UnixTime),
Added: op.Added,
Removed: op.Removed,
@@ -92,41 +92,9 @@ func (op *LabelChangeOperation) Validate() error {
return nil
}
-// UnmarshalJSON is a two step JSON unmarshalling
-// This workaround is necessary to avoid the inner OpBase.MarshalJSON
-// overriding the outer op's MarshalJSON
-func (op *LabelChangeOperation) UnmarshalJSON(data []byte) error {
- // Unmarshal OpBase and the op separately
-
- base := OpBase{}
- err := json.Unmarshal(data, &base)
- if err != nil {
- return err
- }
-
- aux := struct {
- Added []Label `json:"added"`
- Removed []Label `json:"removed"`
- }{}
-
- err = json.Unmarshal(data, &aux)
- if err != nil {
- return err
- }
-
- op.OpBase = base
- op.Added = aux.Added
- op.Removed = aux.Removed
-
- return nil
-}
-
-// Sign post method for gqlgen
-func (op *LabelChangeOperation) IsAuthored() {}
-
func NewLabelChangeOperation(author identity.Interface, unixTime int64, added, removed []Label) *LabelChangeOperation {
return &LabelChangeOperation{
- OpBase: newOpBase(LabelChangeOp, author, unixTime),
+ OpBase: dag.NewOpBase(LabelChangeOp, author, unixTime),
Added: added,
Removed: removed,
}
@@ -144,7 +112,7 @@ func (l LabelChangeTimelineItem) Id() entity.Id {
return l.id
}
-// Sign post method for gqlgen
+// IsAuthored is a sign post method for gqlgen
func (l *LabelChangeTimelineItem) IsAuthored() {}
// ChangeLabels is a convenience function to apply the operation
@@ -1,37 +1,20 @@
package bug
import (
- "encoding/json"
"testing"
- "time"
-
- "github.com/stretchr/testify/require"
+ "github.com/MichaelMure/git-bug/entity/dag"
"github.com/MichaelMure/git-bug/identity"
- "github.com/MichaelMure/git-bug/repository"
)
func TestLabelChangeSerialize(t *testing.T) {
- repo := repository.NewMockRepo()
-
- rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr")
- require.NoError(t, err)
-
- unix := time.Now().Unix()
- before := NewLabelChangeOperation(rene, unix, []Label{"added"}, []Label{"removed"})
-
- data, err := json.Marshal(before)
- require.NoError(t, err)
-
- var after LabelChangeOperation
- err = json.Unmarshal(data, &after)
- require.NoError(t, err)
-
- // enforce creating the ID
- before.Id()
-
- // Replace the identity as it's not serialized
- after.Author_ = rene
-
- require.Equal(t, before, &after)
+ dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *LabelChangeOperation {
+ return NewLabelChangeOperation(author, unixTime, []Label{"added"}, []Label{"removed"})
+ })
+ dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *LabelChangeOperation {
+ return NewLabelChangeOperation(author, unixTime, []Label{"added"}, nil)
+ })
+ dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *LabelChangeOperation {
+ return NewLabelChangeOperation(author, unixTime, nil, []Label{"removed"})
+ })
}
@@ -1,77 +0,0 @@
-package bug
-
-import (
- "encoding/json"
-
- "github.com/MichaelMure/git-bug/entity"
- "github.com/MichaelMure/git-bug/identity"
-)
-
-var _ Operation = &NoOpOperation{}
-
-// NoOpOperation is an operation that does not change the bug state. It can
-// however be used to store arbitrary metadata in the bug history, for example
-// to support a bridge feature.
-type NoOpOperation struct {
- OpBase
-}
-
-func (op *NoOpOperation) Id() entity.Id {
- return idOperation(op, &op.OpBase)
-}
-
-func (op *NoOpOperation) Apply(snapshot *Snapshot) {
- // Nothing to do
-}
-
-func (op *NoOpOperation) Validate() error {
- return op.OpBase.Validate(op, NoOpOp)
-}
-
-// UnmarshalJSON is a two step JSON unmarshalling
-// This workaround is necessary to avoid the inner OpBase.MarshalJSON
-// overriding the outer op's MarshalJSON
-func (op *NoOpOperation) UnmarshalJSON(data []byte) error {
- // Unmarshal OpBase and the op separately
-
- base := OpBase{}
- err := json.Unmarshal(data, &base)
- if err != nil {
- return err
- }
-
- aux := struct{}{}
-
- err = json.Unmarshal(data, &aux)
- if err != nil {
- return err
- }
-
- op.OpBase = base
-
- return nil
-}
-
-// Sign post method for gqlgen
-func (op *NoOpOperation) IsAuthored() {}
-
-func NewNoOpOp(author identity.Interface, unixTime int64) *NoOpOperation {
- return &NoOpOperation{
- OpBase: newOpBase(NoOpOp, author, unixTime),
- }
-}
-
-// Convenience function to apply the operation
-func NoOp(b Interface, author identity.Interface, unixTime int64, metadata map[string]string) (*NoOpOperation, error) {
- op := NewNoOpOp(author, unixTime)
-
- for key, value := range metadata {
- op.SetMetadata(key, value)
- }
-
- if err := op.Validate(); err != nil {
- return nil, err
- }
- b.Append(op)
- return op, nil
-}
@@ -1,39 +0,0 @@
-package bug
-
-import (
- "encoding/json"
- "testing"
- "time"
-
- "github.com/stretchr/testify/require"
-
- "github.com/MichaelMure/git-bug/identity"
- "github.com/MichaelMure/git-bug/repository"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestNoopSerialize(t *testing.T) {
- repo := repository.NewMockRepo()
-
- rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr")
- require.NoError(t, err)
-
- unix := time.Now().Unix()
- before := NewNoOpOp(rene, unix)
-
- data, err := json.Marshal(before)
- assert.NoError(t, err)
-
- var after NoOpOperation
- err = json.Unmarshal(data, &after)
- assert.NoError(t, err)
-
- // enforce creating the ID
- before.Id()
-
- // Replace the identity as it's not serialized
- after.Author_ = rene
-
- assert.Equal(t, before, &after)
-}
@@ -1,108 +1,21 @@
package bug
import (
- "encoding/json"
- "fmt"
-
- "github.com/pkg/errors"
-
"github.com/MichaelMure/git-bug/entity"
+ "github.com/MichaelMure/git-bug/entity/dag"
"github.com/MichaelMure/git-bug/identity"
- "github.com/MichaelMure/git-bug/util/text"
)
-var _ Operation = &SetMetadataOperation{}
-
-type SetMetadataOperation struct {
- OpBase
- Target entity.Id `json:"target"`
- NewMetadata map[string]string `json:"new_metadata"`
-}
-
-func (op *SetMetadataOperation) Id() entity.Id {
- return idOperation(op, &op.OpBase)
-}
-
-func (op *SetMetadataOperation) Apply(snapshot *Snapshot) {
- for _, target := range snapshot.Operations {
- if target.Id() == op.Target {
- // Apply the metadata in an immutable way: if a metadata already
- // exist, it's not possible to override it.
- for key, value := range op.NewMetadata {
- target.setExtraMetadataImmutable(key, value)
- }
- return
- }
- }
-}
-
-func (op *SetMetadataOperation) Validate() error {
- if err := op.OpBase.Validate(op, SetMetadataOp); err != nil {
- return err
- }
-
- if err := op.Target.Validate(); err != nil {
- return errors.Wrap(err, "target invalid")
- }
-
- for key, val := range op.NewMetadata {
- if !text.SafeOneLine(key) {
- return fmt.Errorf("metadata key is unsafe")
- }
- if !text.Safe(val) {
- return fmt.Errorf("metadata value is not fully printable")
- }
- }
-
- return nil
-}
-
-// UnmarshalJSON is a two step JSON unmarshalling
-// This workaround is necessary to avoid the inner OpBase.MarshalJSON
-// overriding the outer op's MarshalJSON
-func (op *SetMetadataOperation) UnmarshalJSON(data []byte) error {
- // Unmarshal OpBase and the op separately
-
- base := OpBase{}
- err := json.Unmarshal(data, &base)
- if err != nil {
- return err
- }
-
- aux := struct {
- Target entity.Id `json:"target"`
- NewMetadata map[string]string `json:"new_metadata"`
- }{}
-
- err = json.Unmarshal(data, &aux)
- if err != nil {
- return err
- }
-
- op.OpBase = base
- op.Target = aux.Target
- op.NewMetadata = aux.NewMetadata
-
- return nil
-}
-
-// Sign post method for gqlgen
-func (op *SetMetadataOperation) IsAuthored() {}
-
-func NewSetMetadataOp(author identity.Interface, unixTime int64, target entity.Id, newMetadata map[string]string) *SetMetadataOperation {
- return &SetMetadataOperation{
- OpBase: newOpBase(SetMetadataOp, author, unixTime),
- Target: target,
- NewMetadata: newMetadata,
- }
+func NewSetMetadataOp(author identity.Interface, unixTime int64, target entity.Id, newMetadata map[string]string) *dag.SetMetadataOperation[*Snapshot] {
+ return dag.NewSetMetadataOp[*Snapshot](SetMetadataOp, author, unixTime, target, newMetadata)
}
// Convenience function to apply the operation
-func SetMetadata(b Interface, author identity.Interface, unixTime int64, target entity.Id, newMetadata map[string]string) (*SetMetadataOperation, error) {
- SetMetadataOp := NewSetMetadataOp(author, unixTime, target, newMetadata)
- if err := SetMetadataOp.Validate(); err != nil {
+func SetMetadata(b Interface, author identity.Interface, unixTime int64, target entity.Id, newMetadata map[string]string) (*dag.SetMetadataOperation[*Snapshot], error) {
+ op := NewSetMetadataOp(author, unixTime, target, newMetadata)
+ if err := op.Validate(); err != nil {
return nil, err
}
- b.Append(SetMetadataOp)
- return SetMetadataOp, nil
+ b.Append(op)
+ return op, nil
}
@@ -1,126 +0,0 @@
-package bug
-
-import (
- "encoding/json"
- "testing"
- "time"
-
- "github.com/MichaelMure/git-bug/identity"
- "github.com/MichaelMure/git-bug/repository"
-
- "github.com/stretchr/testify/require"
-)
-
-func TestSetMetadata(t *testing.T) {
- snapshot := Snapshot{}
-
- repo := repository.NewMockRepo()
-
- rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr")
- require.NoError(t, err)
-
- unix := time.Now().Unix()
-
- create := NewCreateOp(rene, unix, "title", "create", nil)
- create.SetMetadata("key", "value")
- create.Apply(&snapshot)
- snapshot.Operations = append(snapshot.Operations, create)
-
- id1 := create.Id()
- require.NoError(t, id1.Validate())
-
- comment := NewAddCommentOp(rene, unix, "comment", nil)
- comment.SetMetadata("key2", "value2")
- comment.Apply(&snapshot)
- snapshot.Operations = append(snapshot.Operations, comment)
-
- id2 := comment.Id()
- require.NoError(t, id2.Validate())
-
- op1 := NewSetMetadataOp(rene, unix, id1, map[string]string{
- "key": "override",
- "key2": "value",
- })
-
- op1.Apply(&snapshot)
- snapshot.Operations = append(snapshot.Operations, op1)
-
- createMetadata := snapshot.Operations[0].AllMetadata()
- require.Len(t, createMetadata, 2)
- // original key is not overrided
- require.Equal(t, createMetadata["key"], "value")
- // new key is set
- require.Equal(t, createMetadata["key2"], "value")
-
- commentMetadata := snapshot.Operations[1].AllMetadata()
- require.Len(t, commentMetadata, 1)
- require.Equal(t, commentMetadata["key2"], "value2")
-
- op2 := NewSetMetadataOp(rene, unix, id2, map[string]string{
- "key2": "value",
- "key3": "value3",
- })
-
- op2.Apply(&snapshot)
- snapshot.Operations = append(snapshot.Operations, op2)
-
- createMetadata = snapshot.Operations[0].AllMetadata()
- require.Len(t, createMetadata, 2)
- require.Equal(t, createMetadata["key"], "value")
- require.Equal(t, createMetadata["key2"], "value")
-
- commentMetadata = snapshot.Operations[1].AllMetadata()
- require.Len(t, commentMetadata, 2)
- // original key is not overrided
- require.Equal(t, commentMetadata["key2"], "value2")
- // new key is set
- require.Equal(t, commentMetadata["key3"], "value3")
-
- op3 := NewSetMetadataOp(rene, unix, id1, map[string]string{
- "key": "override",
- "key2": "override",
- })
-
- op3.Apply(&snapshot)
- snapshot.Operations = append(snapshot.Operations, op3)
-
- createMetadata = snapshot.Operations[0].AllMetadata()
- require.Len(t, createMetadata, 2)
- // original key is not overrided
- require.Equal(t, createMetadata["key"], "value")
- // previously set key is not overrided
- require.Equal(t, createMetadata["key2"], "value")
-
- commentMetadata = snapshot.Operations[1].AllMetadata()
- require.Len(t, commentMetadata, 2)
- require.Equal(t, commentMetadata["key2"], "value2")
- require.Equal(t, commentMetadata["key3"], "value3")
-}
-
-func TestSetMetadataSerialize(t *testing.T) {
- repo := repository.NewMockRepo()
-
- rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr")
- require.NoError(t, err)
-
- unix := time.Now().Unix()
- before := NewSetMetadataOp(rene, unix, "message", map[string]string{
- "key1": "value1",
- "key2": "value2",
- })
-
- data, err := json.Marshal(before)
- require.NoError(t, err)
-
- var after SetMetadataOperation
- err = json.Unmarshal(data, &after)
- require.NoError(t, err)
-
- // enforce creating the ID
- before.Id()
-
- // Replace the identity as it's not serialized
- after.Author_ = rene
-
- require.Equal(t, before, &after)
-}
@@ -1,11 +1,10 @@
package bug
import (
- "encoding/json"
-
"github.com/pkg/errors"
"github.com/MichaelMure/git-bug/entity"
+ "github.com/MichaelMure/git-bug/entity/dag"
"github.com/MichaelMure/git-bug/identity"
"github.com/MichaelMure/git-bug/util/timestamp"
)
@@ -14,21 +13,21 @@ var _ Operation = &SetStatusOperation{}
// SetStatusOperation will change the status of a bug
type SetStatusOperation struct {
- OpBase
+ dag.OpBase
Status Status `json:"status"`
}
func (op *SetStatusOperation) Id() entity.Id {
- return idOperation(op, &op.OpBase)
+ return dag.IdOperation(op, &op.OpBase)
}
func (op *SetStatusOperation) Apply(snapshot *Snapshot) {
snapshot.Status = op.Status
- snapshot.addActor(op.Author_)
+ snapshot.addActor(op.Author())
item := &SetStatusTimelineItem{
id: op.Id(),
- Author: op.Author_,
+ Author: op.Author(),
UnixTime: timestamp.Timestamp(op.UnixTime),
Status: op.Status,
}
@@ -48,39 +47,9 @@ func (op *SetStatusOperation) Validate() error {
return nil
}
-// UnmarshalJSON is a two step JSON unmarshalling
-// This workaround is necessary to avoid the inner OpBase.MarshalJSON
-// overriding the outer op's MarshalJSON
-func (op *SetStatusOperation) UnmarshalJSON(data []byte) error {
- // Unmarshal OpBase and the op separately
-
- base := OpBase{}
- err := json.Unmarshal(data, &base)
- if err != nil {
- return err
- }
-
- aux := struct {
- Status Status `json:"status"`
- }{}
-
- err = json.Unmarshal(data, &aux)
- if err != nil {
- return err
- }
-
- op.OpBase = base
- op.Status = aux.Status
-
- return nil
-}
-
-// Sign post method for gqlgen
-func (op *SetStatusOperation) IsAuthored() {}
-
func NewSetStatusOp(author identity.Interface, unixTime int64, status Status) *SetStatusOperation {
return &SetStatusOperation{
- OpBase: newOpBase(SetStatusOp, author, unixTime),
+ OpBase: dag.NewOpBase(SetStatusOp, author, unixTime),
Status: status,
}
}
@@ -96,7 +65,7 @@ func (s SetStatusTimelineItem) Id() entity.Id {
return s.id
}
-// Sign post method for gqlgen
+// IsAuthored is a sign post method for gqlgen
func (s *SetStatusTimelineItem) IsAuthored() {}
// Convenience function to apply the operation
@@ -1,37 +1,14 @@
package bug
import (
- "encoding/json"
"testing"
- "time"
-
- "github.com/stretchr/testify/require"
+ "github.com/MichaelMure/git-bug/entity/dag"
"github.com/MichaelMure/git-bug/identity"
- "github.com/MichaelMure/git-bug/repository"
)
func TestSetStatusSerialize(t *testing.T) {
- repo := repository.NewMockRepo()
-
- rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr")
- require.NoError(t, err)
-
- unix := time.Now().Unix()
- before := NewSetStatusOp(rene, unix, ClosedStatus)
-
- data, err := json.Marshal(before)
- require.NoError(t, err)
-
- var after SetStatusOperation
- err = json.Unmarshal(data, &after)
- require.NoError(t, err)
-
- // enforce creating the ID
- before.Id()
-
- // Replace the identity as it's not serialized
- after.Author_ = rene
-
- require.Equal(t, before, &after)
+ dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *SetStatusOperation {
+ return NewSetStatusOp(author, unixTime, ClosedStatus)
+ })
}
@@ -1,10 +1,10 @@
package bug
import (
- "encoding/json"
"fmt"
"github.com/MichaelMure/git-bug/entity"
+ "github.com/MichaelMure/git-bug/entity/dag"
"github.com/MichaelMure/git-bug/identity"
"github.com/MichaelMure/git-bug/util/timestamp"
@@ -15,22 +15,22 @@ var _ Operation = &SetTitleOperation{}
// SetTitleOperation will change the title of a bug
type SetTitleOperation struct {
- OpBase
+ dag.OpBase
Title string `json:"title"`
Was string `json:"was"`
}
func (op *SetTitleOperation) Id() entity.Id {
- return idOperation(op, &op.OpBase)
+ return dag.IdOperation(op, &op.OpBase)
}
func (op *SetTitleOperation) Apply(snapshot *Snapshot) {
snapshot.Title = op.Title
- snapshot.addActor(op.Author_)
+ snapshot.addActor(op.Author())
item := &SetTitleTimelineItem{
id: op.Id(),
- Author: op.Author_,
+ Author: op.Author(),
UnixTime: timestamp.Timestamp(op.UnixTime),
Title: op.Title,
Was: op.Was,
@@ -59,41 +59,9 @@ func (op *SetTitleOperation) Validate() error {
return nil
}
-// UnmarshalJSON is a two step JSON unmarshalling
-// This workaround is necessary to avoid the inner OpBase.MarshalJSON
-// overriding the outer op's MarshalJSON
-func (op *SetTitleOperation) UnmarshalJSON(data []byte) error {
- // Unmarshal OpBase and the op separately
-
- base := OpBase{}
- err := json.Unmarshal(data, &base)
- if err != nil {
- return err
- }
-
- aux := struct {
- Title string `json:"title"`
- Was string `json:"was"`
- }{}
-
- err = json.Unmarshal(data, &aux)
- if err != nil {
- return err
- }
-
- op.OpBase = base
- op.Title = aux.Title
- op.Was = aux.Was
-
- return nil
-}
-
-// Sign post method for gqlgen
-func (op *SetTitleOperation) IsAuthored() {}
-
func NewSetTitleOp(author identity.Interface, unixTime int64, title string, was string) *SetTitleOperation {
return &SetTitleOperation{
- OpBase: newOpBase(SetTitleOp, author, unixTime),
+ OpBase: dag.NewOpBase(SetTitleOp, author, unixTime),
Title: title,
Was: was,
}
@@ -111,7 +79,7 @@ func (s SetTitleTimelineItem) Id() entity.Id {
return s.id
}
-// Sign post method for gqlgen
+// IsAuthored is a sign post method for gqlgen
func (s *SetTitleTimelineItem) IsAuthored() {}
// Convenience function to apply the operation
@@ -1,37 +1,14 @@
package bug
import (
- "encoding/json"
"testing"
- "time"
-
- "github.com/stretchr/testify/require"
+ "github.com/MichaelMure/git-bug/entity/dag"
"github.com/MichaelMure/git-bug/identity"
- "github.com/MichaelMure/git-bug/repository"
)
func TestSetTitleSerialize(t *testing.T) {
- repo := repository.NewMockRepo()
-
- rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr")
- require.NoError(t, err)
-
- unix := time.Now().Unix()
- before := NewSetTitleOp(rene, unix, "title", "was")
-
- data, err := json.Marshal(before)
- require.NoError(t, err)
-
- var after SetTitleOperation
- err = json.Unmarshal(data, &after)
- require.NoError(t, err)
-
- // enforce creating the ID
- before.Id()
-
- // Replace the identity as it's not serialized
- after.Author_ = rene
-
- require.Equal(t, before, &after)
+ dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *SetTitleOperation {
+ return NewSetTitleOp(author, unixTime, "title", "was")
+ })
}
@@ -1,23 +1,15 @@
package bug
import (
- "crypto/rand"
"encoding/json"
"fmt"
- "time"
- "github.com/pkg/errors"
-
- "github.com/MichaelMure/git-bug/entity"
"github.com/MichaelMure/git-bug/entity/dag"
"github.com/MichaelMure/git-bug/identity"
)
-// OperationType is an operation type identifier
-type OperationType int
-
const (
- _ OperationType = iota
+ _ dag.OperationType = iota
CreateOp
SetTitleOp
AddCommentOp
@@ -32,55 +24,24 @@ const (
type Operation interface {
dag.Operation
- // Type return the type of the operation
- Type() OperationType
-
- // Time return the time when the operation was added
- Time() time.Time
// Apply the operation to a Snapshot to create the final state
Apply(snapshot *Snapshot)
-
- // SetMetadata store arbitrary metadata about the operation
- SetMetadata(key string, value string)
- // GetMetadata retrieve arbitrary metadata about the operation
- GetMetadata(key string) (string, bool)
- // AllMetadata return all metadata for this operation
- AllMetadata() map[string]string
-
- setExtraMetadataImmutable(key string, value string)
}
-func idOperation(op Operation, base *OpBase) entity.Id {
- if base.id == "" {
- // something went really wrong
- panic("op's id not set")
- }
- if base.id == entity.UnsetId {
- // This means we are trying to get the op's Id *before* it has been stored, for instance when
- // adding multiple ops in one go in an OperationPack.
- // As the Id is computed based on the actual bytes written on the disk, we are going to predict
- // those and then get the Id. This is safe as it will be the exact same code writing on disk later.
-
- data, err := json.Marshal(op)
- if err != nil {
- panic(err)
- }
-
- base.id = entity.DeriveId(data)
- }
- return base.id
-}
+// make sure that package external operations do conform to our interface
+var _ Operation = &dag.NoOpOperation[*Snapshot]{}
+var _ Operation = &dag.SetMetadataOperation[*Snapshot]{}
-func operationUnmarshaller(author identity.Interface, raw json.RawMessage, resolver identity.Resolver) (dag.Operation, error) {
+func operationUnmarshaller(raw json.RawMessage, resolver identity.Resolver) (dag.Operation, error) {
var t struct {
- OperationType OperationType `json:"type"`
+ OperationType dag.OperationType `json:"type"`
}
if err := json.Unmarshal(raw, &t); err != nil {
return nil, err
}
- var op Operation
+ var op dag.Operation
switch t.OperationType {
case AddCommentOp:
@@ -92,9 +53,9 @@ func operationUnmarshaller(author identity.Interface, raw json.RawMessage, resol
case LabelChangeOp:
op = &LabelChangeOperation{}
case NoOpOp:
- op = &NoOpOperation{}
+ op = &dag.NoOpOperation[*Snapshot]{}
case SetMetadataOp:
- op = &SetMetadataOperation{}
+ op = &dag.SetMetadataOperation[*Snapshot]{}
case SetStatusOp:
op = &SetStatusOperation{}
case SetTitleOp:
@@ -108,188 +69,5 @@ func operationUnmarshaller(author identity.Interface, raw json.RawMessage, resol
return nil, err
}
- switch op := op.(type) {
- case *AddCommentOperation:
- op.Author_ = author
- case *CreateOperation:
- op.Author_ = author
- case *EditCommentOperation:
- op.Author_ = author
- case *LabelChangeOperation:
- op.Author_ = author
- case *NoOpOperation:
- op.Author_ = author
- case *SetMetadataOperation:
- op.Author_ = author
- case *SetStatusOperation:
- op.Author_ = author
- case *SetTitleOperation:
- op.Author_ = author
- default:
- panic(fmt.Sprintf("unknown operation type %T", op))
- }
-
return op, nil
}
-
-// OpBase implement the common code for all operations
-type OpBase struct {
- OperationType OperationType `json:"type"`
- Author_ identity.Interface `json:"-"` // not serialized
- // TODO: part of the data model upgrade, this should eventually be a timestamp + lamport
- UnixTime int64 `json:"timestamp"`
- Metadata map[string]string `json:"metadata,omitempty"`
-
- // mandatory random bytes to ensure a better randomness of the data used to later generate the ID
- // len(Nonce) should be > 20 and < 64 bytes
- // It has no functional purpose and should be ignored.
- Nonce []byte `json:"nonce"`
-
- // Not serialized. Store the op's id in memory.
- id entity.Id
- // Not serialized. Store the extra metadata in memory,
- // compiled from SetMetadataOperation.
- extraMetadata map[string]string
-}
-
-// newOpBase is the constructor for an OpBase
-func newOpBase(opType OperationType, author identity.Interface, unixTime int64) OpBase {
- return OpBase{
- OperationType: opType,
- Author_: author,
- UnixTime: unixTime,
- Nonce: makeNonce(20),
- id: entity.UnsetId,
- }
-}
-
-func makeNonce(len int) []byte {
- result := make([]byte, len)
- _, err := rand.Read(result)
- if err != nil {
- panic(err)
- }
- return result
-}
-
-func (base *OpBase) UnmarshalJSON(data []byte) error {
- // Compute the Id when loading the op from disk.
- base.id = entity.DeriveId(data)
-
- aux := struct {
- OperationType OperationType `json:"type"`
- UnixTime int64 `json:"timestamp"`
- Metadata map[string]string `json:"metadata,omitempty"`
- Nonce []byte `json:"nonce"`
- }{}
-
- if err := json.Unmarshal(data, &aux); err != nil {
- return err
- }
-
- base.OperationType = aux.OperationType
- base.UnixTime = aux.UnixTime
- base.Metadata = aux.Metadata
- base.Nonce = aux.Nonce
-
- return nil
-}
-
-func (base *OpBase) Type() OperationType {
- return base.OperationType
-}
-
-// Time return the time when the operation was added
-func (base *OpBase) Time() time.Time {
- return time.Unix(base.UnixTime, 0)
-}
-
-// Validate check the OpBase for errors
-func (base *OpBase) Validate(op Operation, opType OperationType) error {
- if base.OperationType != opType {
- return fmt.Errorf("incorrect operation type (expected: %v, actual: %v)", opType, base.OperationType)
- }
-
- if op.Time().Unix() == 0 {
- return fmt.Errorf("time not set")
- }
-
- if base.Author_ == nil {
- return fmt.Errorf("author not set")
- }
-
- if err := op.Author().Validate(); err != nil {
- return errors.Wrap(err, "author")
- }
-
- if op, ok := op.(dag.OperationWithFiles); ok {
- for _, hash := range op.GetFiles() {
- if !hash.IsValid() {
- return fmt.Errorf("file with invalid hash %v", hash)
- }
- }
- }
-
- if len(base.Nonce) > 64 {
- return fmt.Errorf("nonce is too big")
- }
- if len(base.Nonce) < 20 {
- return fmt.Errorf("nonce is too small")
- }
-
- return nil
-}
-
-// SetMetadata store arbitrary metadata about the operation
-func (base *OpBase) SetMetadata(key string, value string) {
- if base.Metadata == nil {
- base.Metadata = make(map[string]string)
- }
-
- base.Metadata[key] = value
- base.id = entity.UnsetId
-}
-
-// GetMetadata retrieve arbitrary metadata about the operation
-func (base *OpBase) GetMetadata(key string) (string, bool) {
- val, ok := base.Metadata[key]
-
- if ok {
- return val, true
- }
-
- // extraMetadata can't replace the original operations value if any
- val, ok = base.extraMetadata[key]
-
- return val, ok
-}
-
-// AllMetadata return all metadata for this operation
-func (base *OpBase) AllMetadata() map[string]string {
- result := make(map[string]string)
-
- for key, val := range base.extraMetadata {
- result[key] = val
- }
-
- // Original metadata take precedence
- for key, val := range base.Metadata {
- result[key] = val
- }
-
- return result
-}
-
-func (base *OpBase) setExtraMetadataImmutable(key string, value string) {
- if base.extraMetadata == nil {
- base.extraMetadata = make(map[string]string)
- }
- if _, exist := base.extraMetadata[key]; !exist {
- base.extraMetadata[key] = value
- }
-}
-
-// Author return author identity
-func (base *OpBase) Author() identity.Interface {
- return base.Author_
-}
@@ -6,10 +6,13 @@ import (
"github.com/stretchr/testify/require"
+ "github.com/MichaelMure/git-bug/entity/dag"
"github.com/MichaelMure/git-bug/identity"
"github.com/MichaelMure/git-bug/repository"
)
+// TODO: move to entity/dag?
+
func TestValidate(t *testing.T) {
repo := repository.NewMockRepoClock()
@@ -44,11 +47,7 @@ func TestValidate(t *testing.T) {
NewSetStatusOp(makeIdentity(t, "René Descartes", "rene@descartes.fr\u001b"), unix, ClosedStatus),
NewSetStatusOp(makeIdentity(t, "René \nDescartes", "rene@descartes.fr"), unix, ClosedStatus),
NewSetStatusOp(makeIdentity(t, "René Descartes", "rene@\ndescartes.fr"), unix, ClosedStatus),
- &CreateOperation{OpBase: OpBase{
- Author_: rene,
- UnixTime: 0,
- OperationType: CreateOp,
- },
+ &CreateOperation{OpBase: dag.NewOpBase(CreateOp, rene, 0),
Title: "title",
Message: "message",
},
@@ -5,9 +5,12 @@ import (
"time"
"github.com/MichaelMure/git-bug/entity"
+ "github.com/MichaelMure/git-bug/entity/dag"
"github.com/MichaelMure/git-bug/identity"
)
+var _ dag.Snapshot = &Snapshot{}
+
// Snapshot is a compiled form of the Bug data structure used for storage and merge
type Snapshot struct {
id entity.Id
@@ -23,7 +26,7 @@ type Snapshot struct {
Timeline []TimelineItem
- Operations []Operation
+ Operations []dag.Operation
}
// Id returns the Bug identifier
@@ -35,6 +38,10 @@ func (snap *Snapshot) Id() entity.Id {
return snap.id
}
+func (snap *Snapshot) AllOperations() []dag.Operation {
+ return snap.Operations
+}
+
// EditTime returns the last time a bug was modified
func (snap *Snapshot) EditTime() time.Time {
if len(snap.Operations) == 0 {
@@ -133,5 +140,5 @@ func (snap *Snapshot) HasAnyActor(ids ...entity.Id) bool {
return false
}
-// Sign post method for gqlgen
+// IsAuthored is a sign post method for gqlgen
func (snap *Snapshot) IsAuthored() {}
@@ -7,6 +7,7 @@ import (
"github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/entity"
+ "github.com/MichaelMure/git-bug/entity/dag"
"github.com/MichaelMure/git-bug/repository"
)
@@ -287,7 +288,7 @@ func (c *BugCache) EditCommentRaw(author *IdentityCache, unixTime int64, target
return op, c.notifyUpdated()
}
-func (c *BugCache) SetMetadata(target entity.Id, newMetadata map[string]string) (*bug.SetMetadataOperation, error) {
+func (c *BugCache) SetMetadata(target entity.Id, newMetadata map[string]string) (*dag.SetMetadataOperation[*bug.Snapshot], error) {
author, err := c.repoCache.GetUserIdentity()
if err != nil {
return nil, err
@@ -296,7 +297,7 @@ func (c *BugCache) SetMetadata(target entity.Id, newMetadata map[string]string)
return c.SetMetadataRaw(author, time.Now().Unix(), target, newMetadata)
}
-func (c *BugCache) SetMetadataRaw(author *IdentityCache, unixTime int64, target entity.Id, newMetadata map[string]string) (*bug.SetMetadataOperation, error) {
+func (c *BugCache) SetMetadataRaw(author *IdentityCache, unixTime int64, target entity.Id, newMetadata map[string]string) (*dag.SetMetadataOperation[*bug.Snapshot], error) {
c.mu.Lock()
op, err := bug.SetMetadata(c.bug, author.Identity, unixTime, target, newMetadata)
if err != nil {
@@ -18,78 +18,73 @@ import (
Operations
*/
-type op1 struct {
- author identity.Interface
+const (
+ _ OperationType = iota
+ Op1
+ Op2
+)
- OperationType int `json:"type"`
- Field1 string `json:"field_1"`
- Files []repository.Hash `json:"files"`
+type op1 struct {
+ OpBase
+ Field1 string `json:"field_1"`
+ Files []repository.Hash `json:"files"`
}
func newOp1(author identity.Interface, field1 string, files ...repository.Hash) *op1 {
- return &op1{author: author, OperationType: 1, Field1: field1, Files: files}
+ return &op1{OpBase: NewOpBase(Op1, author, 0), Field1: field1, Files: files}
}
-func (o *op1) Id() entity.Id {
- data, _ := json.Marshal(o)
- return entity.DeriveId(data)
+func (op *op1) Id() entity.Id {
+ return IdOperation(op, &op.OpBase)
}
-func (o *op1) Validate() error { return nil }
+func (op *op1) Validate() error { return nil }
-func (o *op1) Author() identity.Interface {
- return o.author
-}
-
-func (o *op1) GetFiles() []repository.Hash {
- return o.Files
+func (op *op1) GetFiles() []repository.Hash {
+ return op.Files
}
type op2 struct {
- author identity.Interface
-
- OperationType int `json:"type"`
- Field2 string `json:"field_2"`
+ OpBase
+ Field2 string `json:"field_2"`
}
func newOp2(author identity.Interface, field2 string) *op2 {
- return &op2{author: author, OperationType: 2, Field2: field2}
+ return &op2{OpBase: NewOpBase(Op2, author, 0), Field2: field2}
}
-func (o *op2) Id() entity.Id {
- data, _ := json.Marshal(o)
- return entity.DeriveId(data)
+func (op *op2) Id() entity.Id {
+ return IdOperation(op, &op.OpBase)
}
-func (o *op2) Validate() error { return nil }
-
-func (o *op2) Author() identity.Interface {
- return o.author
-}
+func (op *op2) Validate() error { return nil }
-func unmarshaler(author identity.Interface, raw json.RawMessage, resolver identity.Resolver) (Operation, error) {
+func unmarshaler(raw json.RawMessage, resolver identity.Resolver) (Operation, error) {
var t struct {
- OperationType int `json:"type"`
+ OperationType OperationType `json:"type"`
}
if err := json.Unmarshal(raw, &t); err != nil {
return nil, err
}
+ var op Operation
+
switch t.OperationType {
- case 1:
- op := &op1{}
- err := json.Unmarshal(raw, &op)
- op.author = author
- return op, err
- case 2:
- op := &op2{}
- err := json.Unmarshal(raw, &op)
- op.author = author
- return op, err
+ case Op1:
+ op = &op1{}
+ case Op2:
+ op = &op2{}
default:
return nil, fmt.Errorf("unknown operation type %v", t.OperationType)
}
+
+ err := json.Unmarshal(raw, &op)
+ if err != nil {
+ return nil, err
+ }
+
+ return op, nil
}
/*
@@ -26,7 +26,7 @@ type Definition struct {
// the Namespace in git references (bugs, prs, ...)
Namespace string
// a function decoding a JSON message into an Operation
- OperationUnmarshaler func(author identity.Interface, raw json.RawMessage, resolver identity.Resolver) (Operation, error)
+ OperationUnmarshaler func(raw json.RawMessage, resolver identity.Resolver) (Operation, error)
// the expected format version number, that can be used for data migration/upgrade
FormatVersion uint
}
@@ -50,6 +50,8 @@ func TestWriteReadMultipleAuthor(t *testing.T) {
}
func assertEqualEntities(t *testing.T, a, b *Entity) {
+ t.Helper()
+
// testify doesn't support comparing functions and systematically fail if they are not nil
// so we have to set them to nil temporarily
@@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"os"
+ "time"
"github.com/MichaelMure/git-bug/entity"
"github.com/MichaelMure/git-bug/entity/dag"
@@ -64,10 +65,8 @@ type Operation interface {
Apply(snapshot *Snapshot)
}
-type OperationType int
-
const (
- _ OperationType = iota
+ _ dag.OperationType = iota
SetSignatureRequiredOp
AddAdministratorOp
RemoveAdministratorOp
@@ -75,37 +74,30 @@ const (
// SetSignatureRequired is an operation to set/unset if git signature are required.
type SetSignatureRequired struct {
- author identity.Interface
- OperationType OperationType `json:"type"`
- Value bool `json:"value"`
+ dag.OpBase
+ Value bool `json:"value"`
}
func NewSetSignatureRequired(author identity.Interface, value bool) *SetSignatureRequired {
- return &SetSignatureRequired{author: author, OperationType: SetSignatureRequiredOp, Value: value}
+ return &SetSignatureRequired{
+ OpBase: dag.NewOpBase(SetSignatureRequiredOp, author, time.Now().Unix()),
+ Value: value,
+ }
}
func (ssr *SetSignatureRequired) Id() entity.Id {
// the Id of the operation is the hash of the serialized data.
- // we could memorize the Id when deserializing, but that will do
- data, _ := json.Marshal(ssr)
- return entity.DeriveId(data)
+ return dag.IdOperation(ssr, &ssr.OpBase)
}
func (ssr *SetSignatureRequired) Validate() error {
- if ssr.author == nil {
- return fmt.Errorf("author not set")
- }
- return ssr.author.Validate()
-}
-
-func (ssr *SetSignatureRequired) Author() identity.Interface {
- return ssr.author
+ return ssr.OpBase.Validate(ssr, SetSignatureRequiredOp)
}
// Apply is the function that makes changes on the snapshot
func (ssr *SetSignatureRequired) Apply(snapshot *Snapshot) {
// check that we are allowed to change the config
- if _, ok := snapshot.Administrator[ssr.author]; !ok {
+ if _, ok := snapshot.Administrator[ssr.Author()]; !ok {
return
}
snapshot.SignatureRequired = ssr.Value
@@ -113,24 +105,20 @@ func (ssr *SetSignatureRequired) Apply(snapshot *Snapshot) {
// AddAdministrator is an operation to add a new administrator in the set
type AddAdministrator struct {
- author identity.Interface
- OperationType OperationType `json:"type"`
- ToAdd []identity.Interface `json:"to_add"`
-}
-
-// addAdministratorJson is a helper struct to deserialize identities with a concrete type.
-type addAdministratorJson struct {
- ToAdd []identity.IdentityStub `json:"to_add"`
+ dag.OpBase
+ ToAdd []identity.Interface `json:"to_add"`
}
func NewAddAdministratorOp(author identity.Interface, toAdd ...identity.Interface) *AddAdministrator {
- return &AddAdministrator{author: author, OperationType: AddAdministratorOp, ToAdd: toAdd}
+ return &AddAdministrator{
+ OpBase: dag.NewOpBase(AddAdministratorOp, author, time.Now().Unix()),
+ ToAdd: toAdd,
+ }
}
func (aa *AddAdministrator) Id() entity.Id {
- // we could memorize the Id when deserializing, but that will do
- data, _ := json.Marshal(aa)
- return entity.DeriveId(data)
+ // the Id of the operation is the hash of the serialized data.
+ return dag.IdOperation(aa, &aa.OpBase)
}
func (aa *AddAdministrator) Validate() error {
@@ -138,20 +126,13 @@ func (aa *AddAdministrator) Validate() error {
if len(aa.ToAdd) == 0 {
return fmt.Errorf("nothing to add")
}
- if aa.author == nil {
- return fmt.Errorf("author not set")
- }
- return aa.author.Validate()
-}
-
-func (aa *AddAdministrator) Author() identity.Interface {
- return aa.author
+ return aa.OpBase.Validate(aa, AddAdministratorOp)
}
// Apply is the function that makes changes on the snapshot
func (aa *AddAdministrator) Apply(snapshot *Snapshot) {
// check that we are allowed to change the config ... or if there is no admin yet
- if !snapshot.HasAdministrator(aa.author) && len(snapshot.Administrator) != 0 {
+ if !snapshot.HasAdministrator(aa.Author()) && len(snapshot.Administrator) != 0 {
return
}
for _, toAdd := range aa.ToAdd {
@@ -161,25 +142,20 @@ func (aa *AddAdministrator) Apply(snapshot *Snapshot) {
// RemoveAdministrator is an operation to remove an administrator from the set
type RemoveAdministrator struct {
- author identity.Interface
- OperationType OperationType `json:"type"`
- ToRemove []identity.Interface `json:"to_remove"`
-}
-
-// removeAdministratorJson is a helper struct to deserialize identities with a concrete type.
-type removeAdministratorJson struct {
+ dag.OpBase
ToRemove []identity.Interface `json:"to_remove"`
}
func NewRemoveAdministratorOp(author identity.Interface, toRemove ...identity.Interface) *RemoveAdministrator {
- return &RemoveAdministrator{author: author, OperationType: RemoveAdministratorOp, ToRemove: toRemove}
+ return &RemoveAdministrator{
+ OpBase: dag.NewOpBase(RemoveAdministratorOp, author, time.Now().Unix()),
+ ToRemove: toRemove,
+ }
}
func (ra *RemoveAdministrator) Id() entity.Id {
// the Id of the operation is the hash of the serialized data.
- // we could memorize the Id when deserializing, but that will do
- data, _ := json.Marshal(ra)
- return entity.DeriveId(data)
+ return dag.IdOperation(ra, &ra.OpBase)
}
func (ra *RemoveAdministrator) Validate() error {
@@ -188,26 +164,19 @@ func (ra *RemoveAdministrator) Validate() error {
if len(ra.ToRemove) == 0 {
return fmt.Errorf("nothing to remove")
}
- if ra.author == nil {
- return fmt.Errorf("author not set")
- }
- return ra.author.Validate()
-}
-
-func (ra *RemoveAdministrator) Author() identity.Interface {
- return ra.author
+ return ra.OpBase.Validate(ra, RemoveAdministratorOp)
}
// Apply is the function that makes changes on the snapshot
func (ra *RemoveAdministrator) Apply(snapshot *Snapshot) {
// check if we are allowed to make changes
- if !snapshot.HasAdministrator(ra.author) {
+ if !snapshot.HasAdministrator(ra.Author()) {
return
}
// special rule: we can't end up with no administrator
stillSome := false
for admin, _ := range snapshot.Administrator {
- if admin != ra.author {
+ if admin != ra.Author() {
stillSome = true
break
}
@@ -245,71 +214,52 @@ var def = dag.Definition{
// operationUnmarshaller is a function doing the de-serialization of the JSON data into our own
// concrete Operations. If needed, we can use the resolver to connect to other entities.
-func operationUnmarshaller(author identity.Interface, raw json.RawMessage, resolver identity.Resolver) (dag.Operation, error) {
+func operationUnmarshaller(raw json.RawMessage, resolver identity.Resolver) (dag.Operation, error) {
var t struct {
- OperationType OperationType `json:"type"`
+ OperationType dag.OperationType `json:"type"`
}
if err := json.Unmarshal(raw, &t); err != nil {
return nil, err
}
- var value interface{}
+ var op dag.Operation
switch t.OperationType {
case AddAdministratorOp:
- value = &addAdministratorJson{}
+ op = &AddAdministrator{}
case RemoveAdministratorOp:
- value = &removeAdministratorJson{}
+ op = &RemoveAdministrator{}
case SetSignatureRequiredOp:
- value = &SetSignatureRequired{}
+ op = &SetSignatureRequired{}
default:
panic(fmt.Sprintf("unknown operation type %v", t.OperationType))
}
- err := json.Unmarshal(raw, &value)
+ err := json.Unmarshal(raw, &op)
if err != nil {
return nil, err
}
- var op Operation
-
- switch value := value.(type) {
- case *SetSignatureRequired:
- value.author = author
- op = value
- case *addAdministratorJson:
- // We need something less straightforward to deserialize and resolve identities
- aa := &AddAdministrator{
- author: author,
- OperationType: AddAdministratorOp,
- ToAdd: make([]identity.Interface, len(value.ToAdd)),
- }
- for i, stub := range value.ToAdd {
+ switch op := op.(type) {
+ case *AddAdministrator:
+ // We need to resolve identities
+ for i, stub := range op.ToAdd {
iden, err := resolver.ResolveIdentity(stub.Id())
if err != nil {
return nil, err
}
- aa.ToAdd[i] = iden
+ op.ToAdd[i] = iden
}
- op = aa
- case *removeAdministratorJson:
- // We need something less straightforward to deserialize and resolve identities
- ra := &RemoveAdministrator{
- author: author,
- OperationType: RemoveAdministratorOp,
- ToRemove: make([]identity.Interface, len(value.ToRemove)),
- }
- for i, stub := range value.ToRemove {
+ case *RemoveAdministrator:
+ // We need to resolve identities
+ for i, stub := range op.ToRemove {
iden, err := resolver.ResolveIdentity(stub.Id())
if err != nil {
return nil, err
}
- ra.ToRemove[i] = iden
+ op.ToRemove[i] = iden
}
- op = ra
- default:
- panic(fmt.Sprintf("unknown operation type %T", value))
}
return op, nil
@@ -0,0 +1,39 @@
+package dag
+
+import (
+ "github.com/MichaelMure/git-bug/entity"
+ "github.com/MichaelMure/git-bug/identity"
+)
+
+var _ Operation = &NoOpOperation[Snapshot]{}
+var _ OperationDoesntChangeSnapshot = &NoOpOperation[Snapshot]{}
+
+// NoOpOperation is an operation that does not change the entity state. It can
+// however be used to store arbitrary metadata in the entity history, for example
+// to support a bridge feature.
+type NoOpOperation[SnapT Snapshot] struct {
+ OpBase
+}
+
+func NewNoOpOp[SnapT Snapshot](opType OperationType, author identity.Interface, unixTime int64) *NoOpOperation[SnapT] {
+ return &NoOpOperation[SnapT]{
+ OpBase: NewOpBase(opType, author, unixTime),
+ }
+}
+
+func (op *NoOpOperation[SnapT]) Id() entity.Id {
+ return IdOperation(op, &op.OpBase)
+}
+
+func (op *NoOpOperation[SnapT]) Apply(snapshot SnapT) {
+ // Nothing to do
+}
+
+func (op *NoOpOperation[SnapT]) Validate() error {
+ if err := op.OpBase.Validate(op, op.OperationType); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (op *NoOpOperation[SnapT]) DoesntChangeSnapshot() {}
@@ -0,0 +1,13 @@
+package dag
+
+import (
+ "testing"
+
+ "github.com/MichaelMure/git-bug/identity"
+)
+
+func TestNoopSerialize(t *testing.T) {
+ SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *NoOpOperation[*snapshotMock] {
+ return NewNoOpOp[*snapshotMock](1, author, unixTime)
+ })
+}
@@ -0,0 +1,68 @@
+package dag
+
+import (
+ "fmt"
+
+ "github.com/pkg/errors"
+
+ "github.com/MichaelMure/git-bug/entity"
+ "github.com/MichaelMure/git-bug/identity"
+ "github.com/MichaelMure/git-bug/util/text"
+)
+
+var _ Operation = &SetMetadataOperation[Snapshot]{}
+var _ OperationDoesntChangeSnapshot = &SetMetadataOperation[Snapshot]{}
+
+type SetMetadataOperation[SnapT Snapshot] struct {
+ OpBase
+ Target entity.Id `json:"target"`
+ NewMetadata map[string]string `json:"new_metadata"`
+}
+
+func NewSetMetadataOp[SnapT Snapshot](opType OperationType, author identity.Interface, unixTime int64, target entity.Id, newMetadata map[string]string) *SetMetadataOperation[SnapT] {
+ return &SetMetadataOperation[SnapT]{
+ OpBase: NewOpBase(opType, author, unixTime),
+ Target: target,
+ NewMetadata: newMetadata,
+ }
+}
+
+func (op *SetMetadataOperation[SnapT]) Id() entity.Id {
+ return IdOperation(op, &op.OpBase)
+}
+
+func (op *SetMetadataOperation[SnapT]) Apply(snapshot SnapT) {
+ for _, target := range snapshot.AllOperations() {
+ if target.Id() == op.Target {
+ // Apply the metadata in an immutable way: if a metadata already
+ // exist, it's not possible to override it.
+ for key, value := range op.NewMetadata {
+ target.setExtraMetadataImmutable(key, value)
+ }
+ return
+ }
+ }
+}
+
+func (op *SetMetadataOperation[SnapT]) Validate() error {
+ if err := op.OpBase.Validate(op, op.OperationType); err != nil {
+ return err
+ }
+
+ if err := op.Target.Validate(); err != nil {
+ return errors.Wrap(err, "target invalid")
+ }
+
+ for key, val := range op.NewMetadata {
+ if !text.SafeOneLine(key) {
+ return fmt.Errorf("metadata key is unsafe")
+ }
+ if !text.Safe(val) {
+ return fmt.Errorf("metadata value is not fully printable")
+ }
+ }
+
+ return nil
+}
+
+func (op *SetMetadataOperation[SnapT]) DoesntChangeSnapshot() {}
@@ -0,0 +1,106 @@
+package dag
+
+import (
+ "testing"
+ "time"
+
+ "github.com/MichaelMure/git-bug/identity"
+ "github.com/MichaelMure/git-bug/repository"
+
+ "github.com/stretchr/testify/require"
+)
+
+type snapshotMock struct {
+ ops []Operation
+}
+
+func (s *snapshotMock) AllOperations() []Operation {
+ return s.ops
+}
+
+func TestSetMetadata(t *testing.T) {
+ snap := &snapshotMock{}
+
+ repo := repository.NewMockRepo()
+
+ rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr")
+ require.NoError(t, err)
+
+ unix := time.Now().Unix()
+
+ target1 := NewNoOpOp[*snapshotMock](1, rene, unix)
+ target1.SetMetadata("key", "value")
+ snap.ops = append(snap.ops, target1)
+
+ target2 := NewNoOpOp[*snapshotMock](1, rene, unix)
+ target2.SetMetadata("key2", "value2")
+ snap.ops = append(snap.ops, target2)
+
+ op1 := NewSetMetadataOp[*snapshotMock](2, rene, unix, target1.Id(), map[string]string{
+ "key": "override",
+ "key2": "value",
+ })
+
+ op1.Apply(snap)
+ snap.ops = append(snap.ops, op1)
+
+ target1Metadata := snap.AllOperations()[0].AllMetadata()
+ require.Len(t, target1Metadata, 2)
+ // original key is not overrided
+ require.Equal(t, target1Metadata["key"], "value")
+ // new key is set
+ require.Equal(t, target1Metadata["key2"], "value")
+
+ target2Metadata := snap.AllOperations()[1].AllMetadata()
+ require.Len(t, target2Metadata, 1)
+ require.Equal(t, target2Metadata["key2"], "value2")
+
+ op2 := NewSetMetadataOp[*snapshotMock](2, rene, unix, target2.Id(), map[string]string{
+ "key2": "value",
+ "key3": "value3",
+ })
+
+ op2.Apply(snap)
+ snap.ops = append(snap.ops, op2)
+
+ target1Metadata = snap.AllOperations()[0].AllMetadata()
+ require.Len(t, target1Metadata, 2)
+ require.Equal(t, target1Metadata["key"], "value")
+ require.Equal(t, target1Metadata["key2"], "value")
+
+ target2Metadata = snap.AllOperations()[1].AllMetadata()
+ require.Len(t, target2Metadata, 2)
+ // original key is not overrided
+ require.Equal(t, target2Metadata["key2"], "value2")
+ // new key is set
+ require.Equal(t, target2Metadata["key3"], "value3")
+
+ op3 := NewSetMetadataOp[*snapshotMock](2, rene, unix, target1.Id(), map[string]string{
+ "key": "override",
+ "key2": "override",
+ })
+
+ op3.Apply(snap)
+ snap.ops = append(snap.ops, op3)
+
+ target1Metadata = snap.AllOperations()[0].AllMetadata()
+ require.Len(t, target1Metadata, 2)
+ // original key is not overrided
+ require.Equal(t, target1Metadata["key"], "value")
+ // previously set key is not overrided
+ require.Equal(t, target1Metadata["key2"], "value")
+
+ target2Metadata = snap.AllOperations()[1].AllMetadata()
+ require.Len(t, target2Metadata, 2)
+ require.Equal(t, target2Metadata["key2"], "value2")
+ require.Equal(t, target2Metadata["key3"], "value3")
+}
+
+func TestSetMetadataSerialize(t *testing.T) {
+ SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *SetMetadataOperation[*snapshotMock] {
+ return NewSetMetadataOp[*snapshotMock](1, author, unixTime, "message", map[string]string{
+ "key1": "value1",
+ "key2": "value2",
+ })
+ })
+}
@@ -1,11 +1,21 @@
package dag
import (
+ "crypto/rand"
+ "encoding/json"
+ "fmt"
+ "time"
+
+ "github.com/pkg/errors"
+
"github.com/MichaelMure/git-bug/entity"
"github.com/MichaelMure/git-bug/identity"
"github.com/MichaelMure/git-bug/repository"
)
+// OperationType is an operation type identifier
+type OperationType int
+
// Operation is a piece of data defining a change to reflect on the state of an Entity.
// What this Operation or Entity's state looks like is not of the resort of this package as it only deals with the
// data structure and storage.
@@ -22,23 +32,39 @@ type Operation interface {
// a minimal amount of entropy and avoid collision.
//
// Author's note: I tried to find a clever way around that inelegance (stuffing random useless data into the stored
- // structure is not exactly elegant) but I failed to find a proper way. Essentially, anything that would reuse some
+ // structure is not exactly elegant), but I failed to find a proper way. Essentially, anything that would reuse some
// other data (parent operation's Id, lamport clock) or the graph structure (depth) impose that the Id would only
// make sense in the context of the graph and yield some deep coupling between Entity and Operation. This in turn
// make the whole thing even less elegant.
//
// A common way to derive an Id will be to use the entity.DeriveId() function on the serialized operation data.
Id() entity.Id
+ // Type return the type of the operation
+ Type() OperationType
// Validate check if the Operation data is valid
Validate() error
// Author returns the author of this operation
Author() identity.Interface
+ // Time return the time when the operation was added
+ Time() time.Time
+
+ // SetMetadata store arbitrary metadata about the operation
+ SetMetadata(key string, value string)
+ // GetMetadata retrieve arbitrary metadata about the operation
+ GetMetadata(key string) (string, bool)
+ // AllMetadata return all metadata for this operation
+ AllMetadata() map[string]string
+
+ // setId allow to set the Id, used when unmarshalling only
+ setId(id entity.Id)
+ // setAuthor allow to set the author, used when unmarshalling only
+ setAuthor(author identity.Interface)
+ // setExtraMetadataImmutable add a metadata not carried by the operation itself on the operation
+ setExtraMetadataImmutable(key string, value string)
}
-// OperationWithFiles is an extended Operation that has files dependency, stored in git.
+// OperationWithFiles is an optional extension for an Operation that has files dependency, stored in git.
type OperationWithFiles interface {
- Operation
-
// GetFiles return the files needed by this operation
// This implies that the Operation maintain and store internally the references to those files. This is how
// this information is read later, when loading from storage.
@@ -46,3 +72,201 @@ type OperationWithFiles interface {
// hash).
GetFiles() []repository.Hash
}
+
+// OperationDoesntChangeSnapshot is an interface signaling that the Operation implementing it doesn't change the
+// snapshot, for example a metadata operation that act on other operations.
+type OperationDoesntChangeSnapshot interface {
+ DoesntChangeSnapshot()
+}
+
+// Snapshot is the minimal interface that a snapshot need to implement
+type Snapshot interface {
+ // AllOperations returns all the operations that have been applied to that snapshot, in order
+ AllOperations() []Operation
+}
+
+// OpBase implement the common feature that every Operation should support.
+type OpBase struct {
+ // Not serialized. Store the op's id in memory.
+ id entity.Id
+ // Not serialized
+ author identity.Interface
+
+ OperationType OperationType `json:"type"`
+ UnixTime int64 `json:"timestamp"`
+
+ // mandatory random bytes to ensure a better randomness of the data used to later generate the ID
+ // len(Nonce) should be > 20 and < 64 bytes
+ // It has no functional purpose and should be ignored.
+ Nonce []byte `json:"nonce"`
+
+ Metadata map[string]string `json:"metadata,omitempty"`
+ // Not serialized. Store the extra metadata in memory,
+ // compiled from SetMetadataOperation.
+ extraMetadata map[string]string
+}
+
+func NewOpBase(opType OperationType, author identity.Interface, unixTime int64) OpBase {
+ return OpBase{
+ OperationType: opType,
+ author: author,
+ UnixTime: unixTime,
+ Nonce: makeNonce(20),
+ id: entity.UnsetId,
+ }
+}
+
+func makeNonce(len int) []byte {
+ result := make([]byte, len)
+ _, err := rand.Read(result)
+ if err != nil {
+ panic(err)
+ }
+ return result
+}
+
+func IdOperation(op Operation, base *OpBase) entity.Id {
+ if base.id == "" {
+ // something went really wrong
+ panic("op's id not set")
+ }
+ if base.id == entity.UnsetId {
+ // This means we are trying to get the op's Id *before* it has been stored, for instance when
+ // adding multiple ops in one go in an OperationPack.
+ // As the Id is computed based on the actual bytes written on the disk, we are going to predict
+ // those and then get the Id. This is safe as it will be the exact same code writing on disk later.
+
+ data, err := json.Marshal(op)
+ if err != nil {
+ panic(err)
+ }
+
+ base.id = entity.DeriveId(data)
+ }
+ return base.id
+}
+
+func (base *OpBase) Type() OperationType {
+ return base.OperationType
+}
+
+// Time return the time when the operation was added
+func (base *OpBase) Time() time.Time {
+ return time.Unix(base.UnixTime, 0)
+}
+
+// Validate check the OpBase for errors
+func (base *OpBase) Validate(op Operation, opType OperationType) error {
+ if base.OperationType == 0 {
+ return fmt.Errorf("operation type unset")
+ }
+ if base.OperationType != opType {
+ return fmt.Errorf("incorrect operation type (expected: %v, actual: %v)", opType, base.OperationType)
+ }
+
+ if op.Time().Unix() == 0 {
+ return fmt.Errorf("time not set")
+ }
+
+ if base.author == nil {
+ return fmt.Errorf("author not set")
+ }
+
+ if err := op.Author().Validate(); err != nil {
+ return errors.Wrap(err, "author")
+ }
+
+ if op, ok := op.(OperationWithFiles); ok {
+ for _, hash := range op.GetFiles() {
+ if !hash.IsValid() {
+ return fmt.Errorf("file with invalid hash %v", hash)
+ }
+ }
+ }
+
+ if len(base.Nonce) > 64 {
+ return fmt.Errorf("nonce is too big")
+ }
+ if len(base.Nonce) < 20 {
+ return fmt.Errorf("nonce is too small")
+ }
+
+ return nil
+}
+
+// IsAuthored is a sign post method for gqlgen
+func (base *OpBase) IsAuthored() {}
+
+// Author return author identity
+func (base *OpBase) Author() identity.Interface {
+ return base.author
+}
+
+// IdIsSet returns true if the id has been set already
+func (base *OpBase) IdIsSet() bool {
+ return base.id != "" && base.id != entity.UnsetId
+}
+
+// SetMetadata store arbitrary metadata about the operation
+func (base *OpBase) SetMetadata(key string, value string) {
+ if base.IdIsSet() {
+ panic("set metadata on an operation with already an Id")
+ }
+
+ if base.Metadata == nil {
+ base.Metadata = make(map[string]string)
+ }
+ base.Metadata[key] = value
+}
+
+// GetMetadata retrieve arbitrary metadata about the operation
+func (base *OpBase) GetMetadata(key string) (string, bool) {
+ val, ok := base.Metadata[key]
+
+ if ok {
+ return val, true
+ }
+
+ // extraMetadata can't replace the original operations value if any
+ val, ok = base.extraMetadata[key]
+
+ return val, ok
+}
+
+// AllMetadata return all metadata for this operation
+func (base *OpBase) AllMetadata() map[string]string {
+ result := make(map[string]string)
+
+ for key, val := range base.extraMetadata {
+ result[key] = val
+ }
+
+ // Original metadata take precedence
+ for key, val := range base.Metadata {
+ result[key] = val
+ }
+
+ return result
+}
+
+// setId allow to set the Id, used when unmarshalling only
+func (base *OpBase) setId(id entity.Id) {
+ if base.id != "" && base.id != entity.UnsetId {
+ panic("trying to set id again")
+ }
+ base.id = id
+}
+
+// setAuthor allow to set the author, used when unmarshalling only
+func (base *OpBase) setAuthor(author identity.Interface) {
+ base.author = author
+}
+
+func (base *OpBase) setExtraMetadataImmutable(key string, value string) {
+ if base.extraMetadata == nil {
+ base.extraMetadata = make(map[string]string)
+ }
+ if _, exist := base.extraMetadata[key]; !exist {
+ base.extraMetadata[key] = value
+ }
+}
@@ -314,10 +314,15 @@ func unmarshallPack(def Definition, resolver identity.Resolver, data []byte) ([]
for _, raw := range aux.Operations {
// delegate to specialized unmarshal function
- op, err := def.OperationUnmarshaler(author, raw, resolver)
+ op, err := def.OperationUnmarshaler(raw, resolver)
if err != nil {
return nil, nil, err
}
+ // Set the id from the serialized data
+ op.setId(entity.DeriveId(raw))
+ // Set the author, taken from the OperationPack
+ op.setAuthor(author)
+
ops = append(ops, op)
}
@@ -11,13 +11,13 @@ import (
)
func TestOperationPackReadWrite(t *testing.T) {
- repo, id1, _, resolver, def := makeTestContext()
+ repo, author, _, resolver, def := makeTestContext()
opp := &operationPack{
- Author: id1,
+ Author: author,
Operations: []Operation{
- newOp1(id1, "foo"),
- newOp2(id1, "bar"),
+ newOp1(author, "foo"),
+ newOp2(author, "bar"),
},
CreateTime: 123,
EditTime: 456,
@@ -32,34 +32,26 @@ func TestOperationPackReadWrite(t *testing.T) {
opp2, err := readOperationPack(def, repo, resolver, commit)
require.NoError(t, err)
- require.Equal(t, opp, opp2)
-
- // make sure we get the same Id with the same data
- opp3 := &operationPack{
- Author: id1,
- Operations: []Operation{
- newOp1(id1, "foo"),
- newOp2(id1, "bar"),
- },
- CreateTime: 123,
- EditTime: 456,
+ for _, op := range opp.Operations {
+ // force the creation of the id
+ op.Id()
}
- require.Equal(t, opp.Id(), opp3.Id())
+ require.Equal(t, opp, opp2)
}
func TestOperationPackSignedReadWrite(t *testing.T) {
- repo, id1, _, resolver, def := makeTestContext()
+ repo, author, _, resolver, def := makeTestContext()
- err := id1.(*identity.Identity).Mutate(repo, func(orig *identity.Mutator) {
+ err := author.(*identity.Identity).Mutate(repo, func(orig *identity.Mutator) {
orig.Keys = append(orig.Keys, identity.GenerateKey())
})
require.NoError(t, err)
opp := &operationPack{
- Author: id1,
+ Author: author,
Operations: []Operation{
- newOp1(id1, "foo"),
- newOp2(id1, "bar"),
+ newOp1(author, "foo"),
+ newOp2(author, "bar"),
},
CreateTime: 123,
EditTime: 456,
@@ -74,23 +66,15 @@ func TestOperationPackSignedReadWrite(t *testing.T) {
opp2, err := readOperationPack(def, repo, resolver, commit)
require.NoError(t, err)
- require.Equal(t, opp, opp2)
-
- // make sure we get the same Id with the same data
- opp3 := &operationPack{
- Author: id1,
- Operations: []Operation{
- newOp1(id1, "foo"),
- newOp2(id1, "bar"),
- },
- CreateTime: 123,
- EditTime: 456,
+ for _, op := range opp.Operations {
+ // force the creation of the id
+ op.Id()
}
- require.Equal(t, opp.Id(), opp3.Id())
+ require.Equal(t, opp, opp2)
}
func TestOperationPackFiles(t *testing.T) {
- repo, id1, _, resolver, def := makeTestContext()
+ repo, author, _, resolver, def := makeTestContext()
blobHash1, err := repo.StoreData(randomData())
require.NoError(t, err)
@@ -99,10 +83,10 @@ func TestOperationPackFiles(t *testing.T) {
require.NoError(t, err)
opp := &operationPack{
- Author: id1,
+ Author: author,
Operations: []Operation{
- newOp1(id1, "foo", blobHash1, blobHash2),
- newOp1(id1, "foo", blobHash2),
+ newOp1(author, "foo", blobHash1, blobHash2),
+ newOp1(author, "foo", blobHash2),
},
CreateTime: 123,
EditTime: 456,
@@ -117,6 +101,10 @@ func TestOperationPackFiles(t *testing.T) {
opp2, err := readOperationPack(def, repo, resolver, commit)
require.NoError(t, err)
+ for _, op := range opp.Operations {
+ // force the creation of the id
+ op.Id()
+ }
require.Equal(t, opp, opp2)
require.ElementsMatch(t, opp2.Operations[0].(OperationWithFiles).GetFiles(), []repository.Hash{
@@ -0,0 +1,57 @@
+package dag
+
+import (
+ "encoding/json"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/require"
+
+ "github.com/MichaelMure/git-bug/entity"
+ "github.com/MichaelMure/git-bug/identity"
+ "github.com/MichaelMure/git-bug/repository"
+)
+
+// SerializeRoundTripTest realize a marshall/unmarshall round-trip in the same condition as with OperationPack,
+// and check if the recovered operation is identical.
+func SerializeRoundTripTest[OpT Operation](t *testing.T, maker func(author identity.Interface, unixTime int64) OpT) {
+ repo := repository.NewMockRepo()
+
+ rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr")
+ require.NoError(t, err)
+
+ op := maker(rene, time.Now().Unix())
+ // enforce having an id
+ op.Id()
+
+ rdt := &roundTripper[OpT]{Before: op, author: rene}
+
+ data, err := json.Marshal(rdt)
+ require.NoError(t, err)
+
+ err = json.Unmarshal(data, &rdt)
+ require.NoError(t, err)
+
+ require.Equal(t, op, rdt.after)
+}
+
+type roundTripper[OpT Operation] struct {
+ Before OpT
+ author identity.Interface
+ after OpT
+}
+
+func (r *roundTripper[OpT]) MarshalJSON() ([]byte, error) {
+ return json.Marshal(r.Before)
+}
+
+func (r *roundTripper[OpT]) UnmarshalJSON(data []byte) error {
+ if err := json.Unmarshal(data, &r.after); err != nil {
+ return err
+ }
+ // Set the id from the serialized data
+ r.after.setId(entity.DeriveId(data))
+ // Set the author, as OperationPack would do
+ r.after.setAuthor(r.author)
+ return nil
+}
@@ -1,17 +1,16 @@
module github.com/MichaelMure/git-bug
-go 1.16
+go 1.18
require (
- github.com/99designs/gqlgen v0.17.1
+ github.com/99designs/gqlgen v0.17.13
github.com/99designs/keyring v1.2.1
github.com/MichaelMure/go-term-text v0.3.1
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7
github.com/araddon/dateparse v0.0.0-20190622164848-0fb0a474d195
github.com/awesome-gocui/gocui v1.1.0
github.com/blevesearch/bleve v1.0.14
- github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9
- github.com/corpix/uarand v0.1.1 // indirect
+ github.com/cheekybits/genny v1.0.0
github.com/dustin/go-humanize v1.0.0
github.com/fatih/color v1.13.0
github.com/go-git/go-billy/v5 v5.3.1
@@ -23,11 +22,10 @@ require (
github.com/phayes/freeport v0.0.0-20171002181615-b8543db493a5
github.com/pkg/errors v0.9.1
github.com/shurcooL/githubv4 v0.0.0-20190601194912-068505affed7
- github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f // indirect
github.com/skratchdot/open-golang v0.0.0-20190402232053-79abb63cd66e
github.com/spf13/cobra v1.4.0
github.com/stretchr/testify v1.7.2
- github.com/vektah/gqlparser/v2 v2.4.1
+ github.com/vektah/gqlparser/v2 v2.4.6
github.com/xanzy/go-gitlab v0.68.0
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5
@@ -35,3 +33,71 @@ require (
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a
golang.org/x/text v0.3.7
)
+
+require (
+ github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
+ github.com/Microsoft/go-winio v0.4.16 // indirect
+ github.com/RoaringBitmap/roaring v0.4.23 // indirect
+ github.com/acomagu/bufpipe v1.0.3 // indirect
+ github.com/agnivade/levenshtein v1.1.1 // indirect
+ github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
+ github.com/blevesearch/mmap-go v1.0.2 // indirect
+ github.com/blevesearch/segment v0.9.0 // indirect
+ github.com/blevesearch/snowballstem v0.9.0 // indirect
+ github.com/blevesearch/zap/v11 v11.0.14 // indirect
+ github.com/blevesearch/zap/v12 v12.0.14 // indirect
+ github.com/blevesearch/zap/v13 v13.0.6 // indirect
+ github.com/blevesearch/zap/v14 v14.0.5 // indirect
+ github.com/blevesearch/zap/v15 v15.0.3 // indirect
+ github.com/corpix/uarand v0.1.1 // indirect
+ github.com/couchbase/vellum v1.0.2 // indirect
+ github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
+ github.com/danieljoos/wincred v1.1.2 // indirect
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/dvsekhvalnov/jose2go v1.5.0 // indirect
+ github.com/emirpasic/gods v1.12.0 // indirect
+ github.com/gdamore/encoding v1.0.0 // indirect
+ github.com/gdamore/tcell/v2 v2.4.0 // indirect
+ github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2 // indirect
+ github.com/go-git/gcfg v1.5.0 // indirect
+ github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
+ github.com/golang/protobuf v1.5.2 // indirect
+ github.com/golang/snappy v0.0.1 // indirect
+ github.com/google/go-querystring v1.1.0 // indirect
+ github.com/gorilla/websocket v1.5.0 // indirect
+ github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
+ github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
+ github.com/hashicorp/go-retryablehttp v0.7.1 // indirect
+ github.com/imdario/mergo v0.3.12 // indirect
+ github.com/inconshreveable/mousetrap v1.0.0 // indirect
+ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
+ github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
+ github.com/lucasb-eyer/go-colorful v1.0.3 // indirect
+ github.com/mattn/go-colorable v0.1.12 // indirect
+ github.com/mattn/go-runewidth v0.0.12 // indirect
+ github.com/mitchellh/go-homedir v1.1.0 // indirect
+ github.com/mitchellh/mapstructure v1.3.1 // indirect
+ github.com/mschoch/smat v0.2.0 // indirect
+ github.com/mtibben/percent v0.2.1 // indirect
+ github.com/philhofer/fwd v1.0.0 // indirect
+ github.com/pmezard/go-difflib v1.0.0 // indirect
+ github.com/rivo/uniseg v0.1.0 // indirect
+ github.com/russross/blackfriday/v2 v2.1.0 // indirect
+ github.com/sergi/go-diff v1.1.0 // indirect
+ github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f // indirect
+ github.com/spf13/pflag v1.0.5 // indirect
+ github.com/steveyen/gtreap v0.1.0 // indirect
+ github.com/stretchr/objx v0.3.0 // indirect
+ github.com/tinylib/msgp v1.1.0 // indirect
+ github.com/willf/bitset v1.1.10 // indirect
+ github.com/xanzy/ssh-agent v0.3.0 // indirect
+ go.etcd.io/bbolt v1.3.5 // indirect
+ golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 // indirect
+ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
+ golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
+ google.golang.org/appengine v1.6.7 // indirect
+ google.golang.org/protobuf v1.28.0 // indirect
+ gopkg.in/warnings.v0 v0.1.2 // indirect
+ gopkg.in/yaml.v2 v2.4.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+)
@@ -33,11 +33,12 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs=
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4=
-github.com/99designs/gqlgen v0.17.1 h1:i2qQMPKHQjHgBWYIpO4TsaQpPqMHCPK1+h95ipvH8VU=
-github.com/99designs/gqlgen v0.17.1/go.mod h1:K5fzLKwtph+FFgh9j7nFbRUdBKvTcGnsta51fsMTn3o=
+github.com/99designs/gqlgen v0.17.13 h1:ETUEqvRg5Zvr1lXtpoRdj026fzVay0ZlJPwI33qXLIw=
+github.com/99designs/gqlgen v0.17.13/go.mod h1:w1brbeOdqVyNJI553BGwtwdVcYu1LKeYE1opLWN9RgQ=
github.com/99designs/keyring v1.2.1 h1:tYLp1ULvO7i3fI5vE21ReQuj99QFSs7lGm0xWyJo87o=
github.com/99designs/keyring v1.2.1/go.mod h1:fc+wB5KTk9wQ9sDx0kFXB3A0MaeGHM9AwRStKOQ5vOA=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Masterminds/glide v0.13.2/go.mod h1:STyF5vcenH/rUqTEv+/hBXlSTo7KYwg2oc2f4tzPWic=
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
@@ -54,8 +55,8 @@ github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06
github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
-github.com/agnivade/levenshtein v1.1.0 h1:n6qGwyHG61v3ABce1rPVZklEYRT8NFpCMrpZdBUbYGM=
-github.com/agnivade/levenshtein v1.1.0/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
+github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8=
+github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
@@ -101,8 +102,8 @@ github.com/blevesearch/zap/v14 v14.0.5/go.mod h1:bWe8S7tRrSBTIaZ6cLRbgNH4TUDaC9L
github.com/blevesearch/zap/v15 v15.0.3 h1:Ylj8Oe+mo0P25tr9iLPp33lN6d4qcztGjaIsP51UxaY=
github.com/blevesearch/zap/v15 v15.0.3/go.mod h1:iuwQrImsh1WjWJ0Ue2kBqY83a0rFtJTqfa9fp1rbVVU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
-github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9 h1:a1zrFsLFac2xoM6zG1u72DWJwZG3ayttYLfmLbxVETk=
-github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
+github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
+github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -118,9 +119,7 @@ github.com/couchbase/ghistogram v0.1.0/go.mod h1:s1Jhy76zqfEecpNWJfWUiKZookAFaiG
github.com/couchbase/moss v0.1.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37grCIubs=
github.com/couchbase/vellum v1.0.2 h1:BrbP0NKiyDdndMPec8Jjhy0U47CZ0Lgx3xUC2r9rZqw=
github.com/couchbase/vellum v1.0.2/go.mod h1:FcwrEivFpNi24R3jLOs3n+fs5RnuQnQqCLBJ1uAg1W4=
-github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
-github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@@ -236,8 +235,8 @@ github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 h1:twflg0XRTjwKp
github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
-github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
-github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
+github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
@@ -289,11 +288,10 @@ github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
-github.com/matryer/moq v0.2.3/go.mod h1:9RtPYjTnH1bSBIkpvtHkFN7nbWAnO7oRpdJkEIn6UtE=
-github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
-github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
+github.com/matryer/moq v0.2.7/go.mod h1:kITsx543GOENm48TUAQyJ9+SAvFSr7iGQXPoth/VUBk=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
-github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
+github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
@@ -303,8 +301,8 @@ github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRC
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
-github.com/mitchellh/mapstructure v1.2.3 h1:f/MjBEBDLttYCGfRaKBbKSRVF5aV2O6fnBpzknuE3jU=
-github.com/mitchellh/mapstructure v1.2.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.3.1 h1:cCBH2gTD2K0OtLlv/Y5H01VQCqmlDxz30kS5Y5bqfLA=
+github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
@@ -331,9 +329,7 @@ github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qq
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
-github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
-github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
@@ -342,7 +338,6 @@ github.com/shurcooL/githubv4 v0.0.0-20190601194912-068505affed7 h1:Vk3RiBQpF0Ja+
github.com/shurcooL/githubv4 v0.0.0-20190601194912-068505affed7/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo=
github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f h1:tygelZueB1EtXkPI6mQ4o9DQ0+FKW41hTbunoXZCTqk=
github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg=
-github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/skratchdot/open-golang v0.0.0-20190402232053-79abb63cd66e h1:VAzdS5Nw68fbf5RZ8RDVlUvPXNU6Z3jtPCK/qvm4FoQ=
github.com/skratchdot/open-golang v0.0.0-20190402232053-79abb63cd66e/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
@@ -366,6 +361,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
@@ -376,10 +372,9 @@ github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7
github.com/tinylib/msgp v1.1.0 h1:9fQd+ICuRIu/ue4vxJZu6/LzxN0HwMds2nq/0cFvxHU=
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
-github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
-github.com/vektah/gqlparser/v2 v2.4.0/go.mod h1:flJWIR04IMQPGz+BXLrORkrARBxv/rtyIAFvd/MceW0=
-github.com/vektah/gqlparser/v2 v2.4.1 h1:QOyEn8DAPMUMARGMeshKDkDgNmVoEaEGiDB0uWxcSlQ=
-github.com/vektah/gqlparser/v2 v2.4.1/go.mod h1:flJWIR04IMQPGz+BXLrORkrARBxv/rtyIAFvd/MceW0=
+github.com/urfave/cli/v2 v2.8.1/go.mod h1:Z41J9TPoffeoqP0Iza0YbAhGvymRdZAd2uPmZ5JxRdY=
+github.com/vektah/gqlparser/v2 v2.4.6 h1:Yjzp66g6oVq93Jihbi0qhGnf/6zIWjcm8H6gA27zstE=
+github.com/vektah/gqlparser/v2 v2.4.6/go.mod h1:flJWIR04IMQPGz+BXLrORkrARBxv/rtyIAFvd/MceW0=
github.com/willf/bitset v1.1.10 h1:NotGKqX0KwQ72NUzqrjZq5ipPNDQex9lo3WpaS8L2sc=
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
github.com/xanzy/go-gitlab v0.68.0 h1:b2iMQHgZ1V+NyRqLRJVv6RFfr4xnd/AASeS/PETYL0Y=
@@ -387,6 +382,7 @@ github.com/xanzy/go-gitlab v0.68.0/go.mod h1:o4yExCtdaqlM8YGdDJWuZoBmfxBsmA9TPEj
github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI=
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
+github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -407,6 +403,7 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838 h1:71vQrMauZZhcTVK6KdYM+rklehEEwb3E+ZhaE5jrPrE=
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -440,6 +437,7 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
+golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -470,7 +468,6 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 h1:NWy5+hlRbC7HK+PmcXVUmW1IMyFce7to56IUvhUFm7Y=
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
@@ -497,7 +494,6 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -532,6 +528,7 @@ golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a h1:ppl5mZgokTT8uPkmYOyEUmPTr3ypaKkg5eFOGrAmxxE=
@@ -592,9 +589,9 @@ golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
-golang.org/x/tools v0.0.0-20200815165600-90abf76919f3/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
+golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -693,14 +690,12 @@ gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=