diff --git a/bug/bug.go b/bug/bug.go index d3cc41dd..61f58c78 100644 --- a/bug/bug.go +++ b/bug/bug.go @@ -259,7 +259,7 @@ func (bug *Bug) Append(op Operation) { // Write the staging area in Git and move the operations to the packs func (bug *Bug) Commit(repo repository.Repo) error { if bug.staging.IsEmpty() { - return fmt.Errorf("can't commit an empty bug") + return fmt.Errorf("can't commit a bug with no pending operation") } // Write the Ops as a Git blob containing the serialized array @@ -272,14 +272,31 @@ func (bug *Bug) Commit(repo repository.Repo) error { bug.rootPack = hash } - // Write a Git tree referencing this blob - hash, err = repo.StoreTree([]repository.TreeEntry{ + // Make a Git tree referencing this blob and all needed files + tree := []repository.TreeEntry{ // the last pack of ops {ObjectType: repository.Blob, Hash: hash, Name: opsEntryName}, // always the first pack of ops (might be the same) {ObjectType: repository.Blob, Hash: bug.rootPack, Name: rootEntryName}, - }) + } + counter := 0 + added := make(map[util.Hash]interface{}) + for _, ops := range bug.staging.Operations { + for _, file := range ops.Files() { + if _, has := added[file]; !has { + tree = append(tree, repository.TreeEntry{ + ObjectType: repository.Blob, + Hash: file, + Name: fmt.Sprintf("file%d", counter), + }) + counter++ + added[file] = struct{}{} + } + } + } + + hash, err = repo.StoreTree(tree) if err != nil { return err } diff --git a/bug/comment.go b/bug/comment.go index c0c07076..0b4fd6ad 100644 --- a/bug/comment.go +++ b/bug/comment.go @@ -1,6 +1,7 @@ package bug import ( + "github.com/MichaelMure/git-bug/util" "github.com/dustin/go-humanize" "time" ) @@ -8,6 +9,7 @@ import ( type Comment struct { Author Person Message string + Files []util.Hash // Creation time of the comment. // Should be used only for human display, never for ordering as we can't rely on it in a distributed system. diff --git a/bug/label.go b/bug/label.go index ccfa15f4..b19a980f 100644 --- a/bug/label.go +++ b/bug/label.go @@ -11,7 +11,7 @@ func (l Label) String() string { return string(l) } -// UnmarshalGQL implements the graphql.Marshaler interface +// UnmarshalGQL implements the graphql.Unmarshaler interface func (l *Label) UnmarshalGQL(v interface{}) error { _, ok := v.(string) if !ok { diff --git a/bug/operation.go b/bug/operation.go index 74be2fac..736c88dd 100644 --- a/bug/operation.go +++ b/bug/operation.go @@ -1,6 +1,9 @@ package bug -import "time" +import ( + "github.com/MichaelMure/git-bug/util" + "time" +) type OperationType int @@ -17,6 +20,7 @@ type Operation interface { OpType() OperationType Time() time.Time Apply(snapshot Snapshot) Snapshot + Files() []util.Hash // TODO: data validation (ex: a title is a single line) // Validate() bool diff --git a/bug/operations/add_comment.go b/bug/operations/add_comment.go index f35c572b..f2e76b73 100644 --- a/bug/operations/add_comment.go +++ b/bug/operations/add_comment.go @@ -2,6 +2,7 @@ package operations import ( "github.com/MichaelMure/git-bug/bug" + "github.com/MichaelMure/git-bug/util" ) // AddCommentOperation will add a new comment in the bug @@ -11,12 +12,14 @@ var _ bug.Operation = AddCommentOperation{} type AddCommentOperation struct { bug.OpBase Message string + files []util.Hash } func (op AddCommentOperation) Apply(snapshot bug.Snapshot) bug.Snapshot { comment := bug.Comment{ Message: op.Message, Author: op.Author, + Files: op.files, UnixTime: op.UnixTime, } @@ -25,15 +28,24 @@ func (op AddCommentOperation) Apply(snapshot bug.Snapshot) bug.Snapshot { return snapshot } -func NewAddCommentOp(author bug.Person, message string) AddCommentOperation { +func (op AddCommentOperation) Files() []util.Hash { + return op.files +} + +func NewAddCommentOp(author bug.Person, message string, files []util.Hash) AddCommentOperation { return AddCommentOperation{ OpBase: bug.NewOpBase(bug.AddCommentOp, author), Message: message, + files: files, } } // Convenience function to apply the operation func Comment(b *bug.Bug, author bug.Person, message string) { - addCommentOp := NewAddCommentOp(author, message) + CommentWithFiles(b, author, message, nil) +} + +func CommentWithFiles(b *bug.Bug, author bug.Person, message string, files []util.Hash) { + addCommentOp := NewAddCommentOp(author, message, files) b.Append(addCommentOp) } diff --git a/bug/operations/create.go b/bug/operations/create.go index 0ee7e857..ecbafb6f 100644 --- a/bug/operations/create.go +++ b/bug/operations/create.go @@ -2,6 +2,7 @@ package operations import ( "github.com/MichaelMure/git-bug/bug" + "github.com/MichaelMure/git-bug/util" ) // CreateOperation define the initial creation of a bug @@ -12,6 +13,7 @@ type CreateOperation struct { bug.OpBase Title string Message string + files []util.Hash } func (op CreateOperation) Apply(snapshot bug.Snapshot) bug.Snapshot { @@ -28,18 +30,27 @@ func (op CreateOperation) Apply(snapshot bug.Snapshot) bug.Snapshot { return snapshot } -func NewCreateOp(author bug.Person, title, message string) CreateOperation { +func (op CreateOperation) Files() []util.Hash { + return op.files +} + +func NewCreateOp(author bug.Person, title, message string, files []util.Hash) CreateOperation { return CreateOperation{ OpBase: bug.NewOpBase(bug.CreateOp, author), Title: title, Message: message, + files: files, } } // Convenience function to apply the operation func Create(author bug.Person, title, message string) (*bug.Bug, error) { + return CreateWithFiles(author, title, message, nil) +} + +func CreateWithFiles(author bug.Person, title, message string, files []util.Hash) (*bug.Bug, error) { newBug := bug.NewBug() - createOp := NewCreateOp(author, title, message) + createOp := NewCreateOp(author, title, message, files) newBug.Append(createOp) return newBug, nil diff --git a/bug/operations/label_change.go b/bug/operations/label_change.go index a711faef..f289fedc 100644 --- a/bug/operations/label_change.go +++ b/bug/operations/label_change.go @@ -3,6 +3,7 @@ package operations import ( "fmt" "github.com/MichaelMure/git-bug/bug" + "github.com/MichaelMure/git-bug/util" "io" "io/ioutil" "sort" @@ -50,6 +51,10 @@ AddLoop: return snapshot } +func (op LabelChangeOperation) Files() []util.Hash { + return nil +} + func NewLabelChangeOperation(author bug.Person, added, removed []bug.Label) LabelChangeOperation { return LabelChangeOperation{ OpBase: bug.NewOpBase(bug.LabelChangeOp, author), diff --git a/bug/operations/set_status.go b/bug/operations/set_status.go index ed6c328c..87ca14bf 100644 --- a/bug/operations/set_status.go +++ b/bug/operations/set_status.go @@ -2,6 +2,7 @@ package operations import ( "github.com/MichaelMure/git-bug/bug" + "github.com/MichaelMure/git-bug/util" ) // SetStatusOperation will change the status of a bug @@ -19,6 +20,10 @@ func (op SetStatusOperation) Apply(snapshot bug.Snapshot) bug.Snapshot { return snapshot } +func (op SetStatusOperation) Files() []util.Hash { + return nil +} + func NewSetStatusOp(author bug.Person, status bug.Status) SetStatusOperation { return SetStatusOperation{ OpBase: bug.NewOpBase(bug.SetStatusOp, author), diff --git a/bug/operations/set_title.go b/bug/operations/set_title.go index fab01d8a..d6186f41 100644 --- a/bug/operations/set_title.go +++ b/bug/operations/set_title.go @@ -2,6 +2,7 @@ package operations import ( "github.com/MichaelMure/git-bug/bug" + "github.com/MichaelMure/git-bug/util" ) // SetTitleOperation will change the title of a bug @@ -19,6 +20,10 @@ func (op SetTitleOperation) Apply(snapshot bug.Snapshot) bug.Snapshot { return snapshot } +func (op SetTitleOperation) Files() []util.Hash { + return nil +} + func NewSetTitleOp(author bug.Person, title string) SetTitleOperation { return SetTitleOperation{ OpBase: bug.NewOpBase(bug.SetTitleOp, author), diff --git a/cache/cache.go b/cache/cache.go index d2595723..b6f47c6d 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -7,6 +7,7 @@ import ( "github.com/MichaelMure/git-bug/bug" "github.com/MichaelMure/git-bug/bug/operations" "github.com/MichaelMure/git-bug/repository" + "github.com/MichaelMure/git-bug/util" ) type Cacher interface { @@ -26,6 +27,7 @@ type RepoCacher interface { // Mutations NewBug(title string, message string) (BugCacher, error) + NewBugWithFiles(title string, message string, files []util.Hash) (BugCacher, error) } type BugCacher interface { @@ -34,6 +36,7 @@ type BugCacher interface { // Mutations AddComment(message string) error + AddCommentWithFiles(message string, files []util.Hash) error ChangeLabels(added []string, removed []string) error Open() error Close() error @@ -159,12 +162,16 @@ func (c *RepoCache) ClearAllBugs() { } func (c *RepoCache) NewBug(title string, message string) (BugCacher, error) { + return c.NewBugWithFiles(title, message, nil) +} + +func (c *RepoCache) NewBugWithFiles(title string, message string, files []util.Hash) (BugCacher, error) { author, err := bug.GetUser(c.repo) if err != nil { return nil, err } - b, err := operations.Create(author, title, message) + b, err := operations.CreateWithFiles(author, title, message, files) if err != nil { return nil, err } @@ -208,12 +215,16 @@ func (c *BugCache) ClearSnapshot() { } func (c *BugCache) AddComment(message string) error { + return c.AddCommentWithFiles(message, nil) +} + +func (c *BugCache) AddCommentWithFiles(message string, files []util.Hash) error { author, err := bug.GetUser(c.repo) if err != nil { return err } - operations.Comment(c.bug, author, message) + operations.CommentWithFiles(c.bug, author, message, files) // TODO: perf --> the snapshot could simply be updated with the new op c.ClearSnapshot() diff --git a/commands/webui.go b/commands/webui.go index d5560180..0389d68b 100644 --- a/commands/webui.go +++ b/commands/webui.go @@ -64,9 +64,9 @@ func newGitFileHandler(repo repository.Repo) http.Handler { } func (gfh *gitFileHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) { - hash := mux.Vars(r)["hash"] + hash := util.Hash(mux.Vars(r)["hash"]) - if !isGitHash(hash) { + if !hash.IsValid() { http.Error(rw, "invalid git hash", http.StatusBadRequest) return } @@ -144,18 +144,6 @@ func (gufh *gitUploadFileHandler) ServeHTTP(rw http.ResponseWriter, r *http.Requ rw.Write(js) } -func isGitHash(s string) bool { - if len(s) != 40 { - return false - } - for _, r := range s { - if (r < 'a' || r > 'z') && (r < '0' || r > '9') { - return false - } - } - return true -} - var webUICmd = &cobra.Command{ Use: "webui", Short: "Launch the web UI", diff --git a/graphql/gqlgen.yml b/graphql/gqlgen.yml index 51c53b62..9be33ef7 100644 --- a/graphql/gqlgen.yml +++ b/graphql/gqlgen.yml @@ -17,6 +17,8 @@ models: model: github.com/MichaelMure/git-bug/bug.Person Label: model: github.com/MichaelMure/git-bug/bug.Label + Hash: + model: github.com/MichaelMure/git-bug/util.Hash Operation: model: github.com/MichaelMure/git-bug/bug.Operation CreateOperation: diff --git a/graphql/graph/gen_graph.go b/graphql/graph/gen_graph.go index 63a0c9b5..a9c40e1f 100644 --- a/graphql/graph/gen_graph.go +++ b/graphql/graph/gen_graph.go @@ -12,6 +12,7 @@ import ( bug "github.com/MichaelMure/git-bug/bug" operations "github.com/MichaelMure/git-bug/bug/operations" models "github.com/MichaelMure/git-bug/graphql/models" + util "github.com/MichaelMure/git-bug/util" graphql "github.com/vektah/gqlgen/graphql" introspection "github.com/vektah/gqlgen/neelance/introspection" query "github.com/vektah/gqlgen/neelance/query" @@ -40,8 +41,8 @@ type Resolvers interface { LabelChangeOperation_date(ctx context.Context, obj *operations.LabelChangeOperation) (time.Time, error) - Mutation_newBug(ctx context.Context, repoRef *string, title string, message string) (bug.Snapshot, error) - Mutation_addComment(ctx context.Context, repoRef *string, prefix string, message string) (bug.Snapshot, error) + Mutation_newBug(ctx context.Context, repoRef *string, title string, message string, files []util.Hash) (bug.Snapshot, error) + Mutation_addComment(ctx context.Context, repoRef *string, prefix string, message string, files []util.Hash) (bug.Snapshot, error) Mutation_changeLabels(ctx context.Context, repoRef *string, prefix string, added []string, removed []string) (bug.Snapshot, error) Mutation_open(ctx context.Context, repoRef *string, prefix string) (bug.Snapshot, error) Mutation_close(ctx context.Context, repoRef *string, prefix string) (bug.Snapshot, error) @@ -87,8 +88,8 @@ type LabelChangeOperationResolver interface { Date(ctx context.Context, obj *operations.LabelChangeOperation) (time.Time, error) } type MutationResolver interface { - NewBug(ctx context.Context, repoRef *string, title string, message string) (bug.Snapshot, error) - AddComment(ctx context.Context, repoRef *string, prefix string, message string) (bug.Snapshot, error) + NewBug(ctx context.Context, repoRef *string, title string, message string, files []util.Hash) (bug.Snapshot, error) + AddComment(ctx context.Context, repoRef *string, prefix string, message string, files []util.Hash) (bug.Snapshot, error) ChangeLabels(ctx context.Context, repoRef *string, prefix string, added []string, removed []string) (bug.Snapshot, error) Open(ctx context.Context, repoRef *string, prefix string) (bug.Snapshot, error) Close(ctx context.Context, repoRef *string, prefix string) (bug.Snapshot, error) @@ -139,12 +140,12 @@ func (s shortMapper) LabelChangeOperation_date(ctx context.Context, obj *operati return s.r.LabelChangeOperation().Date(ctx, obj) } -func (s shortMapper) Mutation_newBug(ctx context.Context, repoRef *string, title string, message string) (bug.Snapshot, error) { - return s.r.Mutation().NewBug(ctx, repoRef, title, message) +func (s shortMapper) Mutation_newBug(ctx context.Context, repoRef *string, title string, message string, files []util.Hash) (bug.Snapshot, error) { + return s.r.Mutation().NewBug(ctx, repoRef, title, message, files) } -func (s shortMapper) Mutation_addComment(ctx context.Context, repoRef *string, prefix string, message string) (bug.Snapshot, error) { - return s.r.Mutation().AddComment(ctx, repoRef, prefix, message) +func (s shortMapper) Mutation_addComment(ctx context.Context, repoRef *string, prefix string, message string, files []util.Hash) (bug.Snapshot, error) { + return s.r.Mutation().AddComment(ctx, repoRef, prefix, message, files) } func (s shortMapper) Mutation_changeLabels(ctx context.Context, repoRef *string, prefix string, added []string, removed []string) (bug.Snapshot, error) { @@ -264,6 +265,8 @@ func (ec *executionContext) _AddCommentOperation(ctx context.Context, sel []quer out.Values[i] = ec._AddCommentOperation_date(ctx, field, obj) case "message": out.Values[i] = ec._AddCommentOperation_message(ctx, field, obj) + case "files": + out.Values[i] = ec._AddCommentOperation_files(ctx, field, obj) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -324,6 +327,26 @@ func (ec *executionContext) _AddCommentOperation_message(ctx context.Context, fi return graphql.MarshalString(res) } +func (ec *executionContext) _AddCommentOperation_files(ctx context.Context, field graphql.CollectedField, obj *operations.AddCommentOperation) graphql.Marshaler { + rctx := graphql.GetResolverContext(ctx) + rctx.Object = "AddCommentOperation" + rctx.Args = nil + rctx.Field = field + rctx.PushField(field.Alias) + defer rctx.Pop() + res := obj.Files() + arr1 := graphql.Array{} + for idx1 := range res { + arr1 = append(arr1, func() graphql.Marshaler { + rctx := graphql.GetResolverContext(ctx) + rctx.PushIndex(idx1) + defer rctx.Pop() + return res[idx1] + }()) + } + return arr1 +} + var bugImplementors = []string{"Bug"} // nolint: gocyclo, errcheck, gas, goconst @@ -818,6 +841,8 @@ func (ec *executionContext) _Comment(ctx context.Context, sel []query.Selection, out.Values[i] = ec._Comment_author(ctx, field, obj) case "message": out.Values[i] = ec._Comment_message(ctx, field, obj) + case "files": + out.Values[i] = ec._Comment_files(ctx, field, obj) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -848,6 +873,26 @@ func (ec *executionContext) _Comment_message(ctx context.Context, field graphql. return graphql.MarshalString(res) } +func (ec *executionContext) _Comment_files(ctx context.Context, field graphql.CollectedField, obj *bug.Comment) graphql.Marshaler { + rctx := graphql.GetResolverContext(ctx) + rctx.Object = "Comment" + rctx.Args = nil + rctx.Field = field + rctx.PushField(field.Alias) + defer rctx.Pop() + res := obj.Files + arr1 := graphql.Array{} + for idx1 := range res { + arr1 = append(arr1, func() graphql.Marshaler { + rctx := graphql.GetResolverContext(ctx) + rctx.PushIndex(idx1) + defer rctx.Pop() + return res[idx1] + }()) + } + return arr1 +} + var commentConnectionImplementors = []string{"CommentConnection"} // nolint: gocyclo, errcheck, gas, goconst @@ -1007,6 +1052,8 @@ func (ec *executionContext) _CreateOperation(ctx context.Context, sel []query.Se out.Values[i] = ec._CreateOperation_title(ctx, field, obj) case "message": out.Values[i] = ec._CreateOperation_message(ctx, field, obj) + case "files": + out.Values[i] = ec._CreateOperation_files(ctx, field, obj) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -1078,6 +1125,26 @@ func (ec *executionContext) _CreateOperation_message(ctx context.Context, field return graphql.MarshalString(res) } +func (ec *executionContext) _CreateOperation_files(ctx context.Context, field graphql.CollectedField, obj *operations.CreateOperation) graphql.Marshaler { + rctx := graphql.GetResolverContext(ctx) + rctx.Object = "CreateOperation" + rctx.Args = nil + rctx.Field = field + rctx.PushField(field.Alias) + defer rctx.Pop() + res := obj.Files() + arr1 := graphql.Array{} + for idx1 := range res { + arr1 = append(arr1, func() graphql.Marshaler { + rctx := graphql.GetResolverContext(ctx) + rctx.PushIndex(idx1) + defer rctx.Pop() + return res[idx1] + }()) + } + return arr1 +} + var labelChangeOperationImplementors = []string{"LabelChangeOperation", "Operation", "Authored"} // nolint: gocyclo, errcheck, gas, goconst @@ -1264,6 +1331,25 @@ func (ec *executionContext) _Mutation_newBug(ctx context.Context, field graphql. } } args["message"] = arg2 + var arg3 []util.Hash + if tmp, ok := field.Args["files"]; ok { + var err error + var rawIf1 []interface{} + if tmp != nil { + if tmp1, ok := tmp.([]interface{}); ok { + rawIf1 = tmp1 + } + } + arg3 = make([]util.Hash, len(rawIf1)) + for idx1 := range rawIf1 { + err = (&arg3[idx1]).UnmarshalGQL(rawIf1[idx1]) + } + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + } + args["files"] = arg3 rctx := graphql.GetResolverContext(ctx) rctx.Object = "Mutation" rctx.Args = args @@ -1271,7 +1357,7 @@ func (ec *executionContext) _Mutation_newBug(ctx context.Context, field graphql. rctx.PushField(field.Alias) defer rctx.Pop() resTmp, err := ec.ResolverMiddleware(ctx, func(ctx context.Context) (interface{}, error) { - return ec.resolvers.Mutation_newBug(ctx, args["repoRef"].(*string), args["title"].(string), args["message"].(string)) + return ec.resolvers.Mutation_newBug(ctx, args["repoRef"].(*string), args["title"].(string), args["message"].(string), args["files"].([]util.Hash)) }) if err != nil { ec.Error(ctx, err) @@ -1321,6 +1407,25 @@ func (ec *executionContext) _Mutation_addComment(ctx context.Context, field grap } } args["message"] = arg2 + var arg3 []util.Hash + if tmp, ok := field.Args["files"]; ok { + var err error + var rawIf1 []interface{} + if tmp != nil { + if tmp1, ok := tmp.([]interface{}); ok { + rawIf1 = tmp1 + } + } + arg3 = make([]util.Hash, len(rawIf1)) + for idx1 := range rawIf1 { + err = (&arg3[idx1]).UnmarshalGQL(rawIf1[idx1]) + } + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + } + args["files"] = arg3 rctx := graphql.GetResolverContext(ctx) rctx.Object = "Mutation" rctx.Args = args @@ -1328,7 +1433,7 @@ func (ec *executionContext) _Mutation_addComment(ctx context.Context, field grap rctx.PushField(field.Alias) defer rctx.Pop() resTmp, err := ec.ResolverMiddleware(ctx, func(ctx context.Context) (interface{}, error) { - return ec.resolvers.Mutation_addComment(ctx, args["repoRef"].(*string), args["prefix"].(string), args["message"].(string)) + return ec.resolvers.Mutation_addComment(ctx, args["repoRef"].(*string), args["prefix"].(string), args["message"].(string), args["files"].([]util.Hash)) }) if err != nil { ec.Error(ctx, err) @@ -3108,6 +3213,7 @@ func (ec *executionContext) introspectType(name string) *introspection.Type { var parsedSchema = schema.MustParse(`scalar Time scalar Label +scalar Hash # Information about pagination in a connection. type PageInfo { @@ -3149,6 +3255,9 @@ type Comment implements Authored { # The message of this comment. message: String! + + # All media's hash referenced in this comment + files: [Hash!]! } enum Status { @@ -3188,6 +3297,7 @@ type CreateOperation implements Operation, Authored { title: String! message: String! + files: [Hash!]! } type SetTitleOperation implements Operation, Authored { @@ -3202,6 +3312,7 @@ type AddCommentOperation implements Operation, Authored { date: Time! message: String! + files: [Hash!]! } type SetStatusOperation implements Operation, Authored { @@ -3291,9 +3402,9 @@ type Query { } type Mutation { - newBug(repoRef: String, title: String!, message: String!): Bug! + newBug(repoRef: String, title: String!, message: String!, files: [Hash!]): Bug! - addComment(repoRef: String, prefix: String!, message: String!): Bug! + addComment(repoRef: String, prefix: String!, message: String!, files: [Hash!]): Bug! changeLabels(repoRef: String, prefix: String!, added: [String!], removed: [String!]): Bug! open(repoRef: String, prefix: String!): Bug! close(repoRef: String, prefix: String!): Bug! diff --git a/graphql/resolvers/mutation.go b/graphql/resolvers/mutation.go index a85459f2..2a81716c 100644 --- a/graphql/resolvers/mutation.go +++ b/graphql/resolvers/mutation.go @@ -5,6 +5,7 @@ import ( "github.com/MichaelMure/git-bug/bug" "github.com/MichaelMure/git-bug/cache" + "github.com/MichaelMure/git-bug/util" ) type mutationResolver struct { @@ -19,13 +20,13 @@ func (r mutationResolver) getRepo(repoRef *string) (cache.RepoCacher, error) { return r.cache.DefaultRepo() } -func (r mutationResolver) NewBug(ctx context.Context, repoRef *string, title string, message string) (bug.Snapshot, error) { +func (r mutationResolver) NewBug(ctx context.Context, repoRef *string, title string, message string, files []util.Hash) (bug.Snapshot, error) { repo, err := r.getRepo(repoRef) if err != nil { return bug.Snapshot{}, err } - b, err := repo.NewBug(title, message) + b, err := repo.NewBugWithFiles(title, message, files) if err != nil { return bug.Snapshot{}, err } @@ -56,7 +57,7 @@ func (r mutationResolver) Commit(ctx context.Context, repoRef *string, prefix st return *snap, nil } -func (r mutationResolver) AddComment(ctx context.Context, repoRef *string, prefix string, message string) (bug.Snapshot, error) { +func (r mutationResolver) AddComment(ctx context.Context, repoRef *string, prefix string, message string, files []util.Hash) (bug.Snapshot, error) { repo, err := r.getRepo(repoRef) if err != nil { return bug.Snapshot{}, err @@ -67,7 +68,7 @@ func (r mutationResolver) AddComment(ctx context.Context, repoRef *string, prefi return bug.Snapshot{}, err } - err = b.AddComment(message) + err = b.AddCommentWithFiles(message, files) if err != nil { return bug.Snapshot{}, err } diff --git a/graphql/schema.graphql b/graphql/schema.graphql index 410ecde9..d4364262 100644 --- a/graphql/schema.graphql +++ b/graphql/schema.graphql @@ -1,5 +1,6 @@ scalar Time scalar Label +scalar Hash # Information about pagination in a connection. type PageInfo { @@ -41,6 +42,9 @@ type Comment implements Authored { # The message of this comment. message: String! + + # All media's hash referenced in this comment + files: [Hash!]! } enum Status { @@ -80,6 +84,7 @@ type CreateOperation implements Operation, Authored { title: String! message: String! + files: [Hash!]! } type SetTitleOperation implements Operation, Authored { @@ -94,6 +99,7 @@ type AddCommentOperation implements Operation, Authored { date: Time! message: String! + files: [Hash!]! } type SetStatusOperation implements Operation, Authored { @@ -183,9 +189,9 @@ type Query { } type Mutation { - newBug(repoRef: String, title: String!, message: String!): Bug! + newBug(repoRef: String, title: String!, message: String!, files: [Hash!]): Bug! - addComment(repoRef: String, prefix: String!, message: String!): Bug! + addComment(repoRef: String, prefix: String!, message: String!, files: [Hash!]): Bug! changeLabels(repoRef: String, prefix: String!, added: [String!], removed: [String!]): Bug! open(repoRef: String, prefix: String!): Bug! close(repoRef: String, prefix: String!): Bug! diff --git a/termui/show_bug.go b/termui/show_bug.go index 34c7a922..ad700f6a 100644 --- a/termui/show_bug.go +++ b/termui/show_bug.go @@ -64,7 +64,7 @@ func (sb *showBug) layout(g *gocui.Gui) error { v.Frame = false v.BgColor = gocui.ColorBlue - fmt.Fprintf(v, "[q] Return [c] Add comment [t] Change title") + fmt.Fprintf(v, "[q] Return [c] Comment [t] Change title") } _, err = g.SetCurrentView(showBugView) diff --git a/util/hash.go b/util/hash.go index 088fd70e..df0a83b8 100644 --- a/util/hash.go +++ b/util/hash.go @@ -1,3 +1,43 @@ package util +import ( + "fmt" + "io" +) + type Hash string + +func (h Hash) String() string { + return string(h) +} + +func (h *Hash) UnmarshalGQL(v interface{}) error { + _, ok := v.(string) + if !ok { + return fmt.Errorf("labels must be strings") + } + + *h = v.(Hash) + + if !h.IsValid() { + return fmt.Errorf("invalid hash") + } + + return nil +} + +func (h Hash) MarshalGQL(w io.Writer) { + w.Write([]byte(`"` + h.String() + `"`)) +} + +func (h *Hash) IsValid() bool { + if len(*h) != 40 { + return false + } + for _, r := range *h { + if (r < 'a' || r > 'z') && (r < '0' || r > '9') { + return false + } + } + return true +}