graphql: implement the missing mutations

Michael Muré created

Change summary

bug/operations/label_change.go |   5 
cache/cache.go                 | 124 +++++++++++
graphql/graph/gen_graph.go     | 396 ++++++++++++++++++++++++++++++++++++
graphql/resolvers/mutation.go  | 102 +++++++++
graphql/schema.graphql         |   8 
repository/git.go              |   3 
6 files changed, 634 insertions(+), 4 deletions(-)

Detailed changes

bug/operations/label_change.go 🔗

@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"github.com/MichaelMure/git-bug/bug"
 	"io"
+	"io/ioutil"
 	"sort"
 )
 
@@ -61,6 +62,10 @@ func NewLabelChangeOperation(author bug.Person, added, removed []bug.Label) Labe
 func ChangeLabels(out io.Writer, b *bug.Bug, author bug.Person, add, remove []string) error {
 	var added, removed []bug.Label
 
+	if out == nil {
+		out = ioutil.Discard
+	}
+
 	snap := b.Compile()
 
 	for _, str := range add {

cache/cache.go 🔗

@@ -27,15 +27,23 @@ type RepoCacher interface {
 	ResolveBugPrefix(prefix string) (BugCacher, error)
 	AllBugIds() ([]string, error)
 	ClearAllBugs()
+	Commit(bug BugCacher) error
 
 	// Mutations
 
 	NewBug(title string, message string) (BugCacher, error)
+
+	AddComment(repoRef *string, prefix string, message string) (BugCacher, error)
+	ChangeLabels(repoRef *string, prefix string, added []string, removed []string) (BugCacher, error)
+	Open(repoRef *string, prefix string) (BugCacher, error)
+	Close(repoRef *string, prefix string) (BugCacher, error)
+	SetTitle(repoRef *string, prefix string, title string) (BugCacher, error)
 }
 
 type BugCacher interface {
 	Snapshot() *bug.Snapshot
 	ClearSnapshot()
+	bug() *bug.Bug
 }
 
 // Cacher ------------------------
@@ -174,6 +182,14 @@ func (c *RepoCache) ClearAllBugs() {
 	c.bugs = make(map[string]BugCacher)
 }
 
+func (c *RepoCache) Commit(bug BugCacher) error {
+	err := bug.bug().Commit(c.repo)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
 func (c *RepoCache) NewBug(title string, message string) (BugCacher, error) {
 	author, err := bug.GetUser(c.repo)
 	if err != nil {
@@ -196,22 +212,120 @@ func (c *RepoCache) NewBug(title string, message string) (BugCacher, error) {
 	return cached, nil
 }
 
+func (c *RepoCache) AddComment(repoRef *string, prefix string, message string) (BugCacher, error) {
+	author, err := bug.GetUser(c.repo)
+	if err != nil {
+		return nil, err
+	}
+
+	cached, err := c.ResolveBugPrefix(prefix)
+	if err != nil {
+		return nil, err
+	}
+
+	operations.Comment(cached.bug(), author, message)
+
+	// TODO: perf --> the snapshot could simply be updated with the new op
+	cached.ClearSnapshot()
+
+	return cached, nil
+}
+
+func (c *RepoCache) ChangeLabels(repoRef *string, prefix string, added []string, removed []string) (BugCacher, error) {
+	author, err := bug.GetUser(c.repo)
+	if err != nil {
+		return nil, err
+	}
+
+	cached, err := c.ResolveBugPrefix(prefix)
+	if err != nil {
+		return nil, err
+	}
+
+	err = operations.ChangeLabels(nil, cached.bug(), author, added, removed)
+	if err != nil {
+		return nil, err
+	}
+
+	// TODO: perf --> the snapshot could simply be updated with the new op
+	cached.ClearSnapshot()
+
+	return cached, nil
+}
+
+func (c *RepoCache) Open(repoRef *string, prefix string) (BugCacher, error) {
+	author, err := bug.GetUser(c.repo)
+	if err != nil {
+		return nil, err
+	}
+
+	cached, err := c.ResolveBugPrefix(prefix)
+	if err != nil {
+		return nil, err
+	}
+
+	operations.Open(cached.bug(), author)
+
+	// TODO: perf --> the snapshot could simply be updated with the new op
+	cached.ClearSnapshot()
+
+	return cached, nil
+}
+
+func (c *RepoCache) Close(repoRef *string, prefix string) (BugCacher, error) {
+	author, err := bug.GetUser(c.repo)
+	if err != nil {
+		return nil, err
+	}
+
+	cached, err := c.ResolveBugPrefix(prefix)
+	if err != nil {
+		return nil, err
+	}
+
+	operations.Close(cached.bug(), author)
+
+	// TODO: perf --> the snapshot could simply be updated with the new op
+	cached.ClearSnapshot()
+
+	return cached, nil
+}
+
+func (c *RepoCache) SetTitle(repoRef *string, prefix string, title string) (BugCacher, error) {
+	author, err := bug.GetUser(c.repo)
+	if err != nil {
+		return nil, err
+	}
+
+	cached, err := c.ResolveBugPrefix(prefix)
+	if err != nil {
+		return nil, err
+	}
+
+	operations.SetTitle(cached.bug(), author, title)
+
+	// TODO: perf --> the snapshot could simply be updated with the new op
+	cached.ClearSnapshot()
+
+	return cached, nil
+}
+
 // Bug ------------------------
 
 type BugCache struct {
-	bug  *bug.Bug
+	b    *bug.Bug
 	snap *bug.Snapshot
 }
 
 func NewBugCache(b *bug.Bug) BugCacher {
 	return &BugCache{
-		bug: b,
+		b: b,
 	}
 }
 
 func (c *BugCache) Snapshot() *bug.Snapshot {
 	if c.snap == nil {
-		snap := c.bug.Compile()
+		snap := c.b.Compile()
 		c.snap = &snap
 	}
 	return c.snap
@@ -220,3 +334,7 @@ func (c *BugCache) Snapshot() *bug.Snapshot {
 func (c *BugCache) ClearSnapshot() {
 	c.snap = nil
 }
+
+func (c *BugCache) bug() *bug.Bug {
+	return c.b
+}

graphql/graph/gen_graph.go 🔗

@@ -41,6 +41,12 @@ 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_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)
+	Mutation_setTitle(ctx context.Context, repoRef *string, prefix string, title string) (bug.Snapshot, error)
+	Mutation_commit(ctx context.Context, repoRef *string, prefix string) (bug.Snapshot, error)
 
 	Query_defaultRepository(ctx context.Context) (*models.Repository, error)
 	Query_repository(ctx context.Context, id string) (*models.Repository, error)
@@ -82,6 +88,12 @@ type LabelChangeOperationResolver interface {
 }
 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)
+	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)
+	SetTitle(ctx context.Context, repoRef *string, prefix string, title string) (bug.Snapshot, error)
+	Commit(ctx context.Context, repoRef *string, prefix string) (bug.Snapshot, error)
 }
 type QueryResolver interface {
 	DefaultRepository(ctx context.Context) (*models.Repository, error)
@@ -131,6 +143,30 @@ func (s shortMapper) Mutation_newBug(ctx context.Context, repoRef *string, title
 	return s.r.Mutation().NewBug(ctx, repoRef, title, message)
 }
 
+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_changeLabels(ctx context.Context, repoRef *string, prefix string, added []string, removed []string) (bug.Snapshot, error) {
+	return s.r.Mutation().ChangeLabels(ctx, repoRef, prefix, added, removed)
+}
+
+func (s shortMapper) Mutation_open(ctx context.Context, repoRef *string, prefix string) (bug.Snapshot, error) {
+	return s.r.Mutation().Open(ctx, repoRef, prefix)
+}
+
+func (s shortMapper) Mutation_close(ctx context.Context, repoRef *string, prefix string) (bug.Snapshot, error) {
+	return s.r.Mutation().Close(ctx, repoRef, prefix)
+}
+
+func (s shortMapper) Mutation_setTitle(ctx context.Context, repoRef *string, prefix string, title string) (bug.Snapshot, error) {
+	return s.r.Mutation().SetTitle(ctx, repoRef, prefix, title)
+}
+
+func (s shortMapper) Mutation_commit(ctx context.Context, repoRef *string, prefix string) (bug.Snapshot, error) {
+	return s.r.Mutation().Commit(ctx, repoRef, prefix)
+}
+
 func (s shortMapper) Query_defaultRepository(ctx context.Context) (*models.Repository, error) {
 	return s.r.Query().DefaultRepository(ctx)
 }
@@ -1132,6 +1168,18 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel []query.Selection
 			out.Values[i] = graphql.MarshalString("Mutation")
 		case "newBug":
 			out.Values[i] = ec._Mutation_newBug(ctx, field)
+		case "addComment":
+			out.Values[i] = ec._Mutation_addComment(ctx, field)
+		case "changeLabels":
+			out.Values[i] = ec._Mutation_changeLabels(ctx, field)
+		case "open":
+			out.Values[i] = ec._Mutation_open(ctx, field)
+		case "close":
+			out.Values[i] = ec._Mutation_close(ctx, field)
+		case "setTitle":
+			out.Values[i] = ec._Mutation_setTitle(ctx, field)
+		case "commit":
+			out.Values[i] = ec._Mutation_commit(ctx, field)
 		default:
 			panic("unknown field " + strconv.Quote(field.Name))
 		}
@@ -1197,6 +1245,346 @@ func (ec *executionContext) _Mutation_newBug(ctx context.Context, field graphql.
 	return ec._Bug(ctx, field.Selections, &res)
 }
 
+func (ec *executionContext) _Mutation_addComment(ctx context.Context, field graphql.CollectedField) graphql.Marshaler {
+	args := map[string]interface{}{}
+	var arg0 *string
+	if tmp, ok := field.Args["repoRef"]; ok {
+		var err error
+		var ptr1 string
+		if tmp != nil {
+			ptr1, err = graphql.UnmarshalString(tmp)
+			arg0 = &ptr1
+		}
+
+		if err != nil {
+			ec.Error(ctx, err)
+			return graphql.Null
+		}
+	}
+	args["repoRef"] = arg0
+	var arg1 string
+	if tmp, ok := field.Args["prefix"]; ok {
+		var err error
+		arg1, err = graphql.UnmarshalString(tmp)
+		if err != nil {
+			ec.Error(ctx, err)
+			return graphql.Null
+		}
+	}
+	args["prefix"] = arg1
+	var arg2 string
+	if tmp, ok := field.Args["message"]; ok {
+		var err error
+		arg2, err = graphql.UnmarshalString(tmp)
+		if err != nil {
+			ec.Error(ctx, err)
+			return graphql.Null
+		}
+	}
+	args["message"] = arg2
+	rctx := graphql.GetResolverContext(ctx)
+	rctx.Object = "Mutation"
+	rctx.Args = args
+	rctx.Field = field
+	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))
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(bug.Snapshot)
+	return ec._Bug(ctx, field.Selections, &res)
+}
+
+func (ec *executionContext) _Mutation_changeLabels(ctx context.Context, field graphql.CollectedField) graphql.Marshaler {
+	args := map[string]interface{}{}
+	var arg0 *string
+	if tmp, ok := field.Args["repoRef"]; ok {
+		var err error
+		var ptr1 string
+		if tmp != nil {
+			ptr1, err = graphql.UnmarshalString(tmp)
+			arg0 = &ptr1
+		}
+
+		if err != nil {
+			ec.Error(ctx, err)
+			return graphql.Null
+		}
+	}
+	args["repoRef"] = arg0
+	var arg1 string
+	if tmp, ok := field.Args["prefix"]; ok {
+		var err error
+		arg1, err = graphql.UnmarshalString(tmp)
+		if err != nil {
+			ec.Error(ctx, err)
+			return graphql.Null
+		}
+	}
+	args["prefix"] = arg1
+	var arg2 []string
+	if tmp, ok := field.Args["added"]; ok {
+		var err error
+		var rawIf1 []interface{}
+		if tmp != nil {
+			if tmp1, ok := tmp.([]interface{}); ok {
+				rawIf1 = tmp1
+			}
+		}
+		arg2 = make([]string, len(rawIf1))
+		for idx1 := range rawIf1 {
+			arg2[idx1], err = graphql.UnmarshalString(rawIf1[idx1])
+		}
+		if err != nil {
+			ec.Error(ctx, err)
+			return graphql.Null
+		}
+	}
+	args["added"] = arg2
+	var arg3 []string
+	if tmp, ok := field.Args["removed"]; ok {
+		var err error
+		var rawIf1 []interface{}
+		if tmp != nil {
+			if tmp1, ok := tmp.([]interface{}); ok {
+				rawIf1 = tmp1
+			}
+		}
+		arg3 = make([]string, len(rawIf1))
+		for idx1 := range rawIf1 {
+			arg3[idx1], err = graphql.UnmarshalString(rawIf1[idx1])
+		}
+		if err != nil {
+			ec.Error(ctx, err)
+			return graphql.Null
+		}
+	}
+	args["removed"] = arg3
+	rctx := graphql.GetResolverContext(ctx)
+	rctx.Object = "Mutation"
+	rctx.Args = args
+	rctx.Field = field
+	rctx.PushField(field.Alias)
+	defer rctx.Pop()
+	resTmp, err := ec.ResolverMiddleware(ctx, func(ctx context.Context) (interface{}, error) {
+		return ec.resolvers.Mutation_changeLabels(ctx, args["repoRef"].(*string), args["prefix"].(string), args["added"].([]string), args["removed"].([]string))
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(bug.Snapshot)
+	return ec._Bug(ctx, field.Selections, &res)
+}
+
+func (ec *executionContext) _Mutation_open(ctx context.Context, field graphql.CollectedField) graphql.Marshaler {
+	args := map[string]interface{}{}
+	var arg0 *string
+	if tmp, ok := field.Args["repoRef"]; ok {
+		var err error
+		var ptr1 string
+		if tmp != nil {
+			ptr1, err = graphql.UnmarshalString(tmp)
+			arg0 = &ptr1
+		}
+
+		if err != nil {
+			ec.Error(ctx, err)
+			return graphql.Null
+		}
+	}
+	args["repoRef"] = arg0
+	var arg1 string
+	if tmp, ok := field.Args["prefix"]; ok {
+		var err error
+		arg1, err = graphql.UnmarshalString(tmp)
+		if err != nil {
+			ec.Error(ctx, err)
+			return graphql.Null
+		}
+	}
+	args["prefix"] = arg1
+	rctx := graphql.GetResolverContext(ctx)
+	rctx.Object = "Mutation"
+	rctx.Args = args
+	rctx.Field = field
+	rctx.PushField(field.Alias)
+	defer rctx.Pop()
+	resTmp, err := ec.ResolverMiddleware(ctx, func(ctx context.Context) (interface{}, error) {
+		return ec.resolvers.Mutation_open(ctx, args["repoRef"].(*string), args["prefix"].(string))
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(bug.Snapshot)
+	return ec._Bug(ctx, field.Selections, &res)
+}
+
+func (ec *executionContext) _Mutation_close(ctx context.Context, field graphql.CollectedField) graphql.Marshaler {
+	args := map[string]interface{}{}
+	var arg0 *string
+	if tmp, ok := field.Args["repoRef"]; ok {
+		var err error
+		var ptr1 string
+		if tmp != nil {
+			ptr1, err = graphql.UnmarshalString(tmp)
+			arg0 = &ptr1
+		}
+
+		if err != nil {
+			ec.Error(ctx, err)
+			return graphql.Null
+		}
+	}
+	args["repoRef"] = arg0
+	var arg1 string
+	if tmp, ok := field.Args["prefix"]; ok {
+		var err error
+		arg1, err = graphql.UnmarshalString(tmp)
+		if err != nil {
+			ec.Error(ctx, err)
+			return graphql.Null
+		}
+	}
+	args["prefix"] = arg1
+	rctx := graphql.GetResolverContext(ctx)
+	rctx.Object = "Mutation"
+	rctx.Args = args
+	rctx.Field = field
+	rctx.PushField(field.Alias)
+	defer rctx.Pop()
+	resTmp, err := ec.ResolverMiddleware(ctx, func(ctx context.Context) (interface{}, error) {
+		return ec.resolvers.Mutation_close(ctx, args["repoRef"].(*string), args["prefix"].(string))
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(bug.Snapshot)
+	return ec._Bug(ctx, field.Selections, &res)
+}
+
+func (ec *executionContext) _Mutation_setTitle(ctx context.Context, field graphql.CollectedField) graphql.Marshaler {
+	args := map[string]interface{}{}
+	var arg0 *string
+	if tmp, ok := field.Args["repoRef"]; ok {
+		var err error
+		var ptr1 string
+		if tmp != nil {
+			ptr1, err = graphql.UnmarshalString(tmp)
+			arg0 = &ptr1
+		}
+
+		if err != nil {
+			ec.Error(ctx, err)
+			return graphql.Null
+		}
+	}
+	args["repoRef"] = arg0
+	var arg1 string
+	if tmp, ok := field.Args["prefix"]; ok {
+		var err error
+		arg1, err = graphql.UnmarshalString(tmp)
+		if err != nil {
+			ec.Error(ctx, err)
+			return graphql.Null
+		}
+	}
+	args["prefix"] = arg1
+	var arg2 string
+	if tmp, ok := field.Args["title"]; ok {
+		var err error
+		arg2, err = graphql.UnmarshalString(tmp)
+		if err != nil {
+			ec.Error(ctx, err)
+			return graphql.Null
+		}
+	}
+	args["title"] = arg2
+	rctx := graphql.GetResolverContext(ctx)
+	rctx.Object = "Mutation"
+	rctx.Args = args
+	rctx.Field = field
+	rctx.PushField(field.Alias)
+	defer rctx.Pop()
+	resTmp, err := ec.ResolverMiddleware(ctx, func(ctx context.Context) (interface{}, error) {
+		return ec.resolvers.Mutation_setTitle(ctx, args["repoRef"].(*string), args["prefix"].(string), args["title"].(string))
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(bug.Snapshot)
+	return ec._Bug(ctx, field.Selections, &res)
+}
+
+func (ec *executionContext) _Mutation_commit(ctx context.Context, field graphql.CollectedField) graphql.Marshaler {
+	args := map[string]interface{}{}
+	var arg0 *string
+	if tmp, ok := field.Args["repoRef"]; ok {
+		var err error
+		var ptr1 string
+		if tmp != nil {
+			ptr1, err = graphql.UnmarshalString(tmp)
+			arg0 = &ptr1
+		}
+
+		if err != nil {
+			ec.Error(ctx, err)
+			return graphql.Null
+		}
+	}
+	args["repoRef"] = arg0
+	var arg1 string
+	if tmp, ok := field.Args["prefix"]; ok {
+		var err error
+		arg1, err = graphql.UnmarshalString(tmp)
+		if err != nil {
+			ec.Error(ctx, err)
+			return graphql.Null
+		}
+	}
+	args["prefix"] = arg1
+	rctx := graphql.GetResolverContext(ctx)
+	rctx.Object = "Mutation"
+	rctx.Args = args
+	rctx.Field = field
+	rctx.PushField(field.Alias)
+	defer rctx.Pop()
+	resTmp, err := ec.ResolverMiddleware(ctx, func(ctx context.Context) (interface{}, error) {
+		return ec.resolvers.Mutation_commit(ctx, args["repoRef"].(*string), args["prefix"].(string))
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(bug.Snapshot)
+	return ec._Bug(ctx, field.Selections, &res)
+}
+
 var operationConnectionImplementors = []string{"OperationConnection"}
 
 // nolint: gocyclo, errcheck, gas, goconst
@@ -2864,5 +3252,13 @@ type Query {
 
 type Mutation {
   newBug(repoRef: String, title: String!, message: String!): Bug!
+
+  addComment(repoRef: String, prefix: String!, message: String!): Bug!
+  changeLabels(repoRef: String, prefix: String!, added: [String!], removed: [String!]): Bug!
+  open(repoRef: String, prefix: String!): Bug!
+  close(repoRef: String, prefix: String!): Bug!
+  setTitle(repoRef: String, prefix: String!, title: String!): Bug!
+
+  commit(repoRef: String, prefix: String!): Bug!
 }
 `)

graphql/resolvers/mutation.go 🔗

@@ -2,6 +2,7 @@ package resolvers
 
 import (
 	"context"
+
 	"github.com/MichaelMure/git-bug/bug"
 	"github.com/MichaelMure/git-bug/cache"
 )
@@ -33,3 +34,104 @@ func (r mutationResolver) NewBug(ctx context.Context, repoRef *string, title str
 
 	return *snap, nil
 }
+
+func (r mutationResolver) Commit(ctx context.Context, repoRef *string, prefix string) (bug.Snapshot, error) {
+	repo, err := r.getRepo(repoRef)
+	if err != nil {
+		return bug.Snapshot{}, err
+	}
+
+	b, err := repo.ResolveBugPrefix(prefix)
+	if err != nil {
+		return bug.Snapshot{}, err
+	}
+
+	err = repo.Commit(b)
+	if err != nil {
+		return bug.Snapshot{}, err
+	}
+
+	snap := b.Snapshot()
+
+	return *snap, nil
+}
+
+func (r mutationResolver) AddComment(ctx context.Context, repoRef *string, prefix string, message string) (bug.Snapshot, error) {
+	repo, err := r.getRepo(repoRef)
+	if err != nil {
+		return bug.Snapshot{}, err
+	}
+
+	b, err := repo.AddComment(repoRef, prefix, message)
+	if err != nil {
+		return bug.Snapshot{}, err
+	}
+
+	snap := b.Snapshot()
+
+	return *snap, nil
+}
+
+func (r mutationResolver) ChangeLabels(ctx context.Context, repoRef *string, prefix string, added []string, removed []string) (bug.Snapshot, error) {
+	repo, err := r.getRepo(repoRef)
+	if err != nil {
+		return bug.Snapshot{}, err
+	}
+
+	b, err := repo.ChangeLabels(repoRef, prefix, added, removed)
+	if err != nil {
+		return bug.Snapshot{}, err
+	}
+
+	snap := b.Snapshot()
+
+	return *snap, nil
+}
+
+func (r mutationResolver) Open(ctx context.Context, repoRef *string, prefix string) (bug.Snapshot, error) {
+	repo, err := r.getRepo(repoRef)
+	if err != nil {
+		return bug.Snapshot{}, err
+	}
+
+	b, err := repo.Open(repoRef, prefix)
+	if err != nil {
+		return bug.Snapshot{}, err
+	}
+
+	snap := b.Snapshot()
+
+	return *snap, nil
+}
+
+func (r mutationResolver) Close(ctx context.Context, repoRef *string, prefix string) (bug.Snapshot, error) {
+	repo, err := r.getRepo(repoRef)
+	if err != nil {
+		return bug.Snapshot{}, err
+	}
+
+	b, err := repo.Close(repoRef, prefix)
+	if err != nil {
+		return bug.Snapshot{}, err
+	}
+
+	snap := b.Snapshot()
+
+	return *snap, nil
+}
+
+func (r mutationResolver) SetTitle(ctx context.Context, repoRef *string, prefix string, title string) (bug.Snapshot, error) {
+	repo, err := r.getRepo(repoRef)
+	if err != nil {
+		return bug.Snapshot{}, err
+	}
+
+	b, err := repo.SetTitle(repoRef, prefix, title)
+	if err != nil {
+		return bug.Snapshot{}, err
+	}
+
+	snap := b.Snapshot()
+
+	return *snap, nil
+}

graphql/schema.graphql 🔗

@@ -183,4 +183,12 @@ type Query {
 
 type Mutation {
   newBug(repoRef: String, title: String!, message: String!): Bug!
+
+  addComment(repoRef: String, prefix: String!, message: String!): Bug!
+  changeLabels(repoRef: String, prefix: String!, added: [String!], removed: [String!]): Bug!
+  open(repoRef: String, prefix: String!): Bug!
+  close(repoRef: String, prefix: String!): Bug!
+  setTitle(repoRef: String, prefix: String!, title: String!): Bug!
+
+  commit(repoRef: String, prefix: String!): Bug!
 }

repository/git.go 🔗

@@ -4,11 +4,12 @@ package repository
 import (
 	"bytes"
 	"fmt"
-	"github.com/MichaelMure/git-bug/util"
 	"io"
 	"os"
 	"os/exec"
 	"strings"
+
+	"github.com/MichaelMure/git-bug/util"
 )
 
 // GitRepo represents an instance of a (local) git repository.