From abe8b1501ac35582a3ca9f6fd40d7018fddc73e2 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Tue, 12 May 2020 15:55:38 -0400 Subject: [PATCH] api: implement log, commit diff --- api/graph/generated/generated.go | 245 ++++++++++++++++++++++++++++--- api/graph/model/commit.go | 22 +++ api/graph/model/models_gen.go | 5 + api/graph/model/object.go | 9 +- api/graph/schema.graphqls | 19 ++- api/graph/schema.resolvers.go | 60 +++++++- 6 files changed, 324 insertions(+), 36 deletions(-) diff --git a/api/graph/generated/generated.go b/api/graph/generated/generated.go index c515ebe..f153424 100644 --- a/api/graph/generated/generated.go +++ b/api/graph/generated/generated.go @@ -38,6 +38,7 @@ type Config struct { type ResolverRoot interface { ACL() ACLResolver + Commit() CommitResolver Mutation() MutationResolver Query() QueryResolver Repository() RepositoryResolver @@ -83,6 +84,7 @@ type ComplexityRoot struct { Commit struct { Author func(childComplexity int) int Committer func(childComplexity int) int + Diff func(childComplexity int) int ID func(childComplexity int) int Message func(childComplexity int) int Parents func(childComplexity int) int @@ -92,6 +94,11 @@ type ComplexityRoot struct { Type func(childComplexity int) int } + CommitCursor struct { + Cursor func(childComplexity int) int + Results func(childComplexity int) int + } + Mutation struct { CreateRepository func(childComplexity int, params *model.RepoInput) int DeleteACL func(childComplexity int, repoID int, entity string) int @@ -129,7 +136,7 @@ type ComplexityRoot struct { Description func(childComplexity int) int Head func(childComplexity int) int ID func(childComplexity int) int - Log func(childComplexity int, cursor *model.Cursor) int + Log func(childComplexity int, cursor *model.Cursor, from *string) int Name func(childComplexity int) int Objects func(childComplexity int, ids []*string) int Owner func(childComplexity int) int @@ -212,6 +219,9 @@ type ACLResolver interface { Repository(ctx context.Context, obj *model.ACL) (*model.Repository, error) Entity(ctx context.Context, obj *model.ACL) (model.Entity, error) } +type CommitResolver interface { + Diff(ctx context.Context, obj *model.Commit) (string, error) +} type MutationResolver interface { CreateRepository(ctx context.Context, params *model.RepoInput) (*model.Repository, error) UpdateRepository(ctx context.Context, id string, params *model.RepoInput) (*model.Repository, error) @@ -237,7 +247,7 @@ type RepositoryResolver interface { References(ctx context.Context, obj *model.Repository, cursor *model.Cursor) (*model.ReferenceCursor, error) Objects(ctx context.Context, obj *model.Repository, ids []*string) ([]model.Object, error) - Log(ctx context.Context, obj *model.Repository, cursor *model.Cursor) ([]*model.Commit, error) + Log(ctx context.Context, obj *model.Repository, cursor *model.Cursor, from *string) (*model.CommitCursor, error) Path(ctx context.Context, obj *model.Repository, revspec *string, path string) (*model.TreeEntry, error) RevparseSingle(ctx context.Context, obj *model.Repository, revspec string) (*model.Commit, error) } @@ -410,6 +420,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Commit.Committer(childComplexity), true + case "Commit.diff": + if e.complexity.Commit.Diff == nil { + break + } + + return e.complexity.Commit.Diff(childComplexity), true + case "Commit.id": if e.complexity.Commit.ID == nil { break @@ -459,6 +476,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Commit.Type(childComplexity), true + case "CommitCursor.cursor": + if e.complexity.CommitCursor.Cursor == nil { + break + } + + return e.complexity.CommitCursor.Cursor(childComplexity), true + + case "CommitCursor.results": + if e.complexity.CommitCursor.Results == nil { + break + } + + return e.complexity.CommitCursor.Results(childComplexity), true + case "Mutation.createRepository": if e.complexity.Mutation.CreateRepository == nil { break @@ -702,7 +733,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return 0, false } - return e.complexity.Repository.Log(childComplexity, args["cursor"].(*model.Cursor)), true + return e.complexity.Repository.Log(childComplexity, args["cursor"].(*model.Cursor), args["from"].(*string)), true case "Repository.name": if e.complexity.Repository.Name == nil { @@ -1257,9 +1288,11 @@ type Repository { # The HEAD reference for this repository (equivalent to the default branch) HEAD: Reference - # Returns a list of comments in topological order. ` + "`" + `cursor.from` + "`" + ` is used as - # the revspec to begin logging from. - log(cursor: Cursor): [Commit]! + # Returns a list of comments in topological order. + # + # If ` + "`" + `from` + "`" + ` is specified, it is interpreted as a revspec to start logging + # from. + log(cursor: Cursor, from: String): CommitCursor # # Returns a tree entry for a given path and revspec @@ -1301,6 +1334,16 @@ type ReferenceCursor { cursor: Cursor } +# A cursor for enumerating commits +# +# If there are additional results available, the cursor object may be passed +# back into the same endpoint to retrieve another page. If the cursor is null, +# there are no remaining results to return. +type CommitCursor { + results: [Commit]! + cursor: Cursor +} + # Access Control List entry type ACL { id: Int! @@ -1358,6 +1401,7 @@ type Commit implements Object { message: String! tree: Tree! parents: [Commit!]! + diff: String! } type Tree implements Object { @@ -1764,6 +1808,14 @@ func (ec *executionContext) field_Repository_log_args(ctx context.Context, rawAr } } args["cursor"] = arg0 + var arg1 *string + if tmp, ok := rawArgs["from"]; ok { + arg1, err = ec.unmarshalOString2ᚖstring(ctx, tmp) + if err != nil { + return nil, err + } + } + args["from"] = arg1 return args, nil } @@ -2863,6 +2915,105 @@ func (ec *executionContext) _Commit_parents(ctx context.Context, field graphql.C return ec.marshalNCommit2ᚕᚖgitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐCommitᚄ(ctx, field.Selections, res) } +func (ec *executionContext) _Commit_diff(ctx context.Context, field graphql.CollectedField, obj *model.Commit) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Commit", + Field: field, + Args: nil, + IsMethod: 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.Commit().Diff(rctx, obj) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _CommitCursor_results(ctx context.Context, field graphql.CollectedField, obj *model.CommitCursor) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "CommitCursor", + Field: field, + Args: nil, + IsMethod: 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.Results, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]*model.Commit) + fc.Result = res + return ec.marshalNCommit2ᚕᚖgitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐCommit(ctx, field.Selections, res) +} + +func (ec *executionContext) _CommitCursor_cursor(ctx context.Context, field graphql.CollectedField, obj *model.CommitCursor) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "CommitCursor", + Field: field, + Args: nil, + IsMethod: 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.Cursor, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*model.Cursor) + fc.Result = res + return ec.marshalOCursor2ᚖgitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐCursor(ctx, field.Selections, res) +} + func (ec *executionContext) _Mutation_createRepository(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -4079,21 +4230,18 @@ func (ec *executionContext) _Repository_log(ctx context.Context, field graphql.C fc.Args = args resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Repository().Log(rctx, obj, args["cursor"].(*model.Cursor)) + return ec.resolvers.Repository().Log(rctx, obj, args["cursor"].(*model.Cursor), args["from"].(*string)) }) if err != nil { ec.Error(ctx, err) return graphql.Null } if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } return graphql.Null } - res := resTmp.([]*model.Commit) + res := resTmp.(*model.CommitCursor) fc.Result = res - return ec.marshalNCommit2ᚕᚖgitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐCommit(ctx, field.Selections, res) + return ec.marshalOCommitCursor2ᚖgitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐCommitCursor(ctx, field.Selections, res) } func (ec *executionContext) _Repository_path(ctx context.Context, field graphql.CollectedField, obj *model.Repository) (ret graphql.Marshaler) { @@ -7016,48 +7164,91 @@ func (ec *executionContext) _Commit(ctx context.Context, sel ast.SelectionSet, o case "type": out.Values[i] = ec._Commit_type(ctx, field, obj) if out.Values[i] == graphql.Null { - invalids++ + atomic.AddUint32(&invalids, 1) } case "id": out.Values[i] = ec._Commit_id(ctx, field, obj) if out.Values[i] == graphql.Null { - invalids++ + atomic.AddUint32(&invalids, 1) } case "shortId": out.Values[i] = ec._Commit_shortId(ctx, field, obj) if out.Values[i] == graphql.Null { - invalids++ + atomic.AddUint32(&invalids, 1) } case "raw": out.Values[i] = ec._Commit_raw(ctx, field, obj) if out.Values[i] == graphql.Null { - invalids++ + atomic.AddUint32(&invalids, 1) } case "author": out.Values[i] = ec._Commit_author(ctx, field, obj) if out.Values[i] == graphql.Null { - invalids++ + atomic.AddUint32(&invalids, 1) } case "committer": out.Values[i] = ec._Commit_committer(ctx, field, obj) if out.Values[i] == graphql.Null { - invalids++ + atomic.AddUint32(&invalids, 1) } case "message": out.Values[i] = ec._Commit_message(ctx, field, obj) if out.Values[i] == graphql.Null { - invalids++ + atomic.AddUint32(&invalids, 1) } case "tree": out.Values[i] = ec._Commit_tree(ctx, field, obj) if out.Values[i] == graphql.Null { - invalids++ + atomic.AddUint32(&invalids, 1) } case "parents": out.Values[i] = ec._Commit_parents(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + case "diff": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Commit_diff(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + +var commitCursorImplementors = []string{"CommitCursor"} + +func (ec *executionContext) _CommitCursor(ctx context.Context, sel ast.SelectionSet, obj *model.CommitCursor) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, commitCursorImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("CommitCursor") + case "results": + out.Values[i] = ec._CommitCursor_results(ctx, field, obj) if out.Values[i] == graphql.Null { invalids++ } + case "cursor": + out.Values[i] = ec._CommitCursor_cursor(ctx, field, obj) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -7407,9 +7598,6 @@ func (ec *executionContext) _Repository(ctx context.Context, sel ast.SelectionSe } }() res = ec._Repository_log(ctx, field, obj) - if res == graphql.Null { - atomic.AddUint32(&invalids, 1) - } return res }) case "path": @@ -8892,6 +9080,17 @@ func (ec *executionContext) marshalOCommit2ᚖgitᚗsrᚗhtᚋאsircmpwnᚋgit return ec._Commit(ctx, sel, v) } +func (ec *executionContext) marshalOCommitCursor2gitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐCommitCursor(ctx context.Context, sel ast.SelectionSet, v model.CommitCursor) graphql.Marshaler { + return ec._CommitCursor(ctx, sel, &v) +} + +func (ec *executionContext) marshalOCommitCursor2ᚖgitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐCommitCursor(ctx context.Context, sel ast.SelectionSet, v *model.CommitCursor) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._CommitCursor(ctx, sel, v) +} + func (ec *executionContext) unmarshalOCursor2gitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐCursor(ctx context.Context, v interface{}) (model.Cursor, error) { var res model.Cursor return res, res.UnmarshalGQL(v) diff --git a/api/graph/model/commit.go b/api/graph/model/commit.go index 6ae93d3..d89168e 100644 --- a/api/graph/model/commit.go +++ b/api/graph/model/commit.go @@ -1,6 +1,8 @@ package model import ( + "context" + "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing/object" ) @@ -37,6 +39,15 @@ func (c *Commit) Committer() *Signature { } } +func (c *Commit) DiffContext(ctx context.Context) string { + parent, _ := c.commit.Parent(0) + patch, _ := c.commit.PatchContext(ctx, parent) + if patch != nil { + return patch.String() + } + return "" +} + func (c *Commit) Tree() *Tree { obj, err := LookupObject(c.repo, c.commit.TreeHash) if err != nil { @@ -58,3 +69,14 @@ func (c *Commit) Parents() []*Commit { } return parents } + +func CommitFromObject(repo *git.Repository, obj *object.Commit) *Commit { + return &Commit{ + Type: ObjectTypeCommit, + ID: obj.ID().String(), + ShortID: obj.ID().String()[:7], + + commit: obj, + repo: repo, + } +} diff --git a/api/graph/model/models_gen.go b/api/graph/model/models_gen.go index 5331c61..18f5286 100644 --- a/api/graph/model/models_gen.go +++ b/api/graph/model/models_gen.go @@ -32,6 +32,11 @@ type Artifact struct { URL string `json:"url"` } +type CommitCursor struct { + Results []*Commit `json:"results"` + Cursor *Cursor `json:"cursor"` +} + type Filter struct { Count *int `json:"count"` Search *string `json:"search"` diff --git a/api/graph/model/object.go b/api/graph/model/object.go index 27a406c..a9e44bf 100644 --- a/api/graph/model/object.go +++ b/api/graph/model/object.go @@ -21,14 +21,7 @@ func LookupObject(repo *git.Repository, hash plumbing.Hash) (Object, error) { // TODO: Add raw object data, if requested switch obj := obj.(type) { case *object.Commit: - return &Commit{ - Type: ObjectTypeCommit, - ID: obj.ID().String(), - ShortID: obj.ID().String()[:7], - - commit: obj, - repo: repo, - }, nil + return CommitFromObject(repo, obj), nil case *object.Tree: return TreeFromObject(repo, obj), nil case *object.Blob: diff --git a/api/graph/schema.graphqls b/api/graph/schema.graphqls index 0132e18..598c7a0 100644 --- a/api/graph/schema.graphqls +++ b/api/graph/schema.graphqls @@ -95,9 +95,11 @@ type Repository { # The HEAD reference for this repository (equivalent to the default branch) HEAD: Reference - # Returns a list of comments in topological order. `cursor.from` is used as - # the revspec to begin logging from. - log(cursor: Cursor): [Commit]! + # Returns a list of comments in topological order. + # + # If `from` is specified, it is interpreted as a revspec to start logging + # from. + log(cursor: Cursor, from: String): CommitCursor # # Returns a tree entry for a given path and revspec @@ -139,6 +141,16 @@ type ReferenceCursor { cursor: Cursor } +# A cursor for enumerating commits +# +# If there are additional results available, the cursor object may be passed +# back into the same endpoint to retrieve another page. If the cursor is null, +# there are no remaining results to return. +type CommitCursor { + results: [Commit]! + cursor: Cursor +} + # Access Control List entry type ACL { id: Int! @@ -196,6 +208,7 @@ type Commit implements Object { message: String! tree: Tree! parents: [Commit!]! + diff: String! } type Tree implements Object { diff --git a/api/graph/schema.resolvers.go b/api/graph/schema.resolvers.go index b60dbfc..203ecb6 100644 --- a/api/graph/schema.resolvers.go +++ b/api/graph/schema.resolvers.go @@ -15,8 +15,10 @@ import ( "git.sr.ht/~sircmpwn/git.sr.ht/api/graph/model" "git.sr.ht/~sircmpwn/git.sr.ht/api/loaders" "git.sr.ht/~sircmpwn/gqlgen/graphql" + git "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" + "github.com/go-git/go-git/v5/plumbing/storer" ) func (r *aCLResolver) Repository(ctx context.Context, obj *model.ACL) (*model.Repository, error) { @@ -53,6 +55,10 @@ func (r *aCLResolver) Entity(ctx context.Context, obj *model.ACL) (model.Entity, return user, nil } +func (r *commitResolver) Diff(ctx context.Context, obj *model.Commit) (string, error) { + return obj.DiffContext(ctx), nil +} + func (r *mutationResolver) CreateRepository(ctx context.Context, params *model.RepoInput) (*model.Repository, error) { panic(fmt.Errorf("createRepository: not implemented")) } @@ -212,8 +218,54 @@ func (r *repositoryResolver) Objects(ctx context.Context, obj *model.Repository, panic(fmt.Errorf("not implemented")) } -func (r *repositoryResolver) Log(ctx context.Context, obj *model.Repository, cursor *model.Cursor) ([]*model.Commit, error) { - panic(fmt.Errorf("not implemented")) +func (r *repositoryResolver) Log(ctx context.Context, obj *model.Repository, cursor *model.Cursor, from *string) (*model.CommitCursor, error) { + if cursor == nil { + cursor = model.NewCursor(nil) + if from != nil { + cursor.Next = *from + } + } + + opts := &git.LogOptions{ + Order: git.LogOrderCommitterTime, + } + if cursor.Next != "" { + rev, err := obj.Repo().ResolveRevision(plumbing.Revision(cursor.Next)) + if err != nil { + return nil, err + } + if rev == nil { + return nil, fmt.Errorf("No such revision") + } + opts.From = *rev + } + + log, err := obj.Repo().Log(opts) + if err != nil { + return nil, err + } + + var commits []*model.Commit + log.ForEach(func(c *object.Commit) error { + commits = append(commits, model.CommitFromObject(obj.Repo(), c)) + if len(commits) == cursor.Count+1 { + return storer.ErrStop + } + return nil + }) + + if len(commits) > cursor.Count { + cursor = &model.Cursor{ + Count: cursor.Count, + Next: commits[cursor.Count].ID, + Search: "", + } + commits = commits[:cursor.Count] + } else { + cursor = nil + } + + return &model.CommitCursor{commits, cursor}, nil } func (r *repositoryResolver) Path(ctx context.Context, obj *model.Repository, revspec *string, path string) (*model.TreeEntry, error) { @@ -296,6 +348,9 @@ func (r *userResolver) Repositories(ctx context.Context, obj *model.User, cursor // ACL returns generated.ACLResolver implementation. func (r *Resolver) ACL() generated.ACLResolver { return &aCLResolver{r} } +// Commit returns generated.CommitResolver implementation. +func (r *Resolver) Commit() generated.CommitResolver { return &commitResolver{r} } + // Mutation returns generated.MutationResolver implementation. func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} } @@ -312,6 +367,7 @@ func (r *Resolver) Tree() generated.TreeResolver { return &treeResolver{r} } func (r *Resolver) User() generated.UserResolver { return &userResolver{r} } type aCLResolver struct{ *Resolver } +type commitResolver struct{ *Resolver } type mutationResolver struct{ *Resolver } type queryResolver struct{ *Resolver } type repositoryResolver struct{ *Resolver } -- 2.38.4