diff --git a/api/graphql/graph/git.generated.go b/api/graphql/graph/git.generated.go index 4f719b7dd1cdb6c4bb3c6930b0b3bce6210001dd..e05bc345d26a37038f74c3bcf2992aed27fb56a3 100644 --- a/api/graphql/graph/git.generated.go +++ b/api/graphql/graph/git.generated.go @@ -28,6 +28,9 @@ type GitCommitResolver interface { Files(ctx context.Context, obj *models.GitCommitMeta, after *string, before *string, first *int, last *int) (*models.GitChangedFileConnection, error) Diff(ctx context.Context, obj *models.GitCommitMeta, path string) (*repository.FileDiff, error) } +type GitRefResolver interface { + Commit(ctx context.Context, obj *models.GitRef) (*models.GitCommitMeta, error) +} type GitTreeEntryResolver interface { LastCommit(ctx context.Context, obj *models.GitTreeEntry) (*models.GitCommitMeta, error) } @@ -2257,9 +2260,9 @@ func (ec *executionContext) _GitRef_type(ctx context.Context, field graphql.Coll } return graphql.Null } - res := resTmp.(models.GitRefType) + res := resTmp.(repository.GitRefType) fc.Result = res - return ec.marshalNGitRefType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐGitRefType(ctx, field.Selections, res) + return ec.marshalNGitRefType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋrepositoryᚐGitRefType(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_GitRef_type(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -2319,6 +2322,72 @@ func (ec *executionContext) fieldContext_GitRef_hash(_ context.Context, field gr return fc, nil } +func (ec *executionContext) _GitRef_commit(ctx context.Context, field graphql.CollectedField, obj *models.GitRef) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_GitRef_commit(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) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.GitRef().Commit(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.(*models.GitCommitMeta) + fc.Result = res + return ec.marshalNGitCommit2ᚖgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐGitCommitMeta(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_GitRef_commit(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "GitRef", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "hash": + return ec.fieldContext_GitCommit_hash(ctx, field) + case "shortHash": + return ec.fieldContext_GitCommit_shortHash(ctx, field) + case "message": + return ec.fieldContext_GitCommit_message(ctx, field) + case "fullMessage": + return ec.fieldContext_GitCommit_fullMessage(ctx, field) + case "authorName": + return ec.fieldContext_GitCommit_authorName(ctx, field) + case "authorEmail": + return ec.fieldContext_GitCommit_authorEmail(ctx, field) + case "date": + return ec.fieldContext_GitCommit_date(ctx, field) + case "parents": + return ec.fieldContext_GitCommit_parents(ctx, field) + case "files": + return ec.fieldContext_GitCommit_files(ctx, field) + case "diff": + return ec.fieldContext_GitCommit_diff(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type GitCommit", field.Name) + }, + } + return fc, nil +} + func (ec *executionContext) _GitRefConnection_nodes(ctx context.Context, field graphql.CollectedField, obj *models.GitRefConnection) (ret graphql.Marshaler) { fc, err := ec.fieldContext_GitRefConnection_nodes(ctx, field) if err != nil { @@ -2366,6 +2435,8 @@ func (ec *executionContext) fieldContext_GitRefConnection_nodes(_ context.Contex return ec.fieldContext_GitRef_type(ctx, field) case "hash": return ec.fieldContext_GitRef_hash(ctx, field) + case "commit": + return ec.fieldContext_GitRef_commit(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type GitRef", field.Name) }, @@ -3351,23 +3422,59 @@ func (ec *executionContext) _GitRef(ctx context.Context, sel ast.SelectionSet, o case "name": out.Values[i] = ec._GitRef_name(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "shortName": out.Values[i] = ec._GitRef_shortName(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "type": out.Values[i] = ec._GitRef_type(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "hash": out.Values[i] = ec._GitRef_hash(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } + case "commit": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._GitRef_commit(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -3604,6 +3711,10 @@ func (ec *executionContext) marshalNGitChangedFileConnection2ᚖgithubᚗcomᚋg return ec._GitChangedFileConnection(ctx, sel, v) } +func (ec *executionContext) marshalNGitCommit2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐGitCommitMeta(ctx context.Context, sel ast.SelectionSet, v models.GitCommitMeta) graphql.Marshaler { + return ec._GitCommit(ctx, sel, &v) +} + func (ec *executionContext) marshalNGitCommit2ᚕᚖgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐGitCommitMetaᚄ(ctx context.Context, sel ast.SelectionSet, v []*models.GitCommitMeta) graphql.Marshaler { ret := make(graphql.Array, len(v)) var wg sync.WaitGroup @@ -3910,15 +4021,15 @@ func (ec *executionContext) marshalNGitRefConnection2ᚖgithubᚗcomᚋgitᚑbug return ec._GitRefConnection(ctx, sel, v) } -func (ec *executionContext) unmarshalNGitRefType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐGitRefType(ctx context.Context, v any) (models.GitRefType, error) { +func (ec *executionContext) unmarshalNGitRefType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋrepositoryᚐGitRefType(ctx context.Context, v any) (repository.GitRefType, error) { tmp, err := graphql.UnmarshalString(v) - res := unmarshalNGitRefType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐGitRefType[tmp] + res := unmarshalNGitRefType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋrepositoryᚐGitRefType[tmp] return res, graphql.ErrorOnPath(ctx, err) } -func (ec *executionContext) marshalNGitRefType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐGitRefType(ctx context.Context, sel ast.SelectionSet, v models.GitRefType) graphql.Marshaler { +func (ec *executionContext) marshalNGitRefType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋrepositoryᚐGitRefType(ctx context.Context, sel ast.SelectionSet, v repository.GitRefType) graphql.Marshaler { _ = sel - res := graphql.MarshalString(marshalNGitRefType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐGitRefType[v]) + res := graphql.MarshalString(marshalNGitRefType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋrepositoryᚐGitRefType[v]) if res == graphql.Null { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { ec.Errorf(ctx, "the requested element is null which the schema does not allow") @@ -3928,13 +4039,15 @@ func (ec *executionContext) marshalNGitRefType2githubᚗcomᚋgitᚑbugᚋgitᚑ } var ( - unmarshalNGitRefType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐGitRefType = map[string]models.GitRefType{ - "BRANCH": models.GitRefTypeBranch, - "TAG": models.GitRefTypeTag, + unmarshalNGitRefType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋrepositoryᚐGitRefType = map[string]repository.GitRefType{ + "BRANCH": repository.GitRefTypeBranch, + "TAG": repository.GitRefTypeTag, + "COMMIT": repository.GitRefTypeCommit, } - marshalNGitRefType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐGitRefType = map[models.GitRefType]string{ - models.GitRefTypeBranch: "BRANCH", - models.GitRefTypeTag: "TAG", + marshalNGitRefType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋrepositoryᚐGitRefType = map[repository.GitRefType]string{ + repository.GitRefTypeBranch: "BRANCH", + repository.GitRefTypeTag: "TAG", + repository.GitRefTypeCommit: "COMMIT", } ) @@ -4013,33 +4126,42 @@ func (ec *executionContext) marshalOGitFileDiff2ᚖgithubᚗcomᚋgitᚑbugᚋgi return ec._GitFileDiff(ctx, sel, v) } -func (ec *executionContext) unmarshalOGitRefType2ᚖgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐGitRefType(ctx context.Context, v any) (*models.GitRefType, error) { +func (ec *executionContext) marshalOGitRef2ᚖgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐGitRef(ctx context.Context, sel ast.SelectionSet, v *models.GitRef) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._GitRef(ctx, sel, v) +} + +func (ec *executionContext) unmarshalOGitRefType2ᚖgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋrepositoryᚐGitRefType(ctx context.Context, v any) (*repository.GitRefType, error) { if v == nil { return nil, nil } tmp, err := graphql.UnmarshalString(v) - res := unmarshalOGitRefType2ᚖgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐGitRefType[tmp] + res := unmarshalOGitRefType2ᚖgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋrepositoryᚐGitRefType[tmp] return &res, graphql.ErrorOnPath(ctx, err) } -func (ec *executionContext) marshalOGitRefType2ᚖgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐGitRefType(ctx context.Context, sel ast.SelectionSet, v *models.GitRefType) graphql.Marshaler { +func (ec *executionContext) marshalOGitRefType2ᚖgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋrepositoryᚐGitRefType(ctx context.Context, sel ast.SelectionSet, v *repository.GitRefType) graphql.Marshaler { if v == nil { return graphql.Null } _ = sel _ = ctx - res := graphql.MarshalString(marshalOGitRefType2ᚖgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐGitRefType[*v]) + res := graphql.MarshalString(marshalOGitRefType2ᚖgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋrepositoryᚐGitRefType[*v]) return res } var ( - unmarshalOGitRefType2ᚖgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐGitRefType = map[string]models.GitRefType{ - "BRANCH": models.GitRefTypeBranch, - "TAG": models.GitRefTypeTag, - } - marshalOGitRefType2ᚖgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐGitRefType = map[models.GitRefType]string{ - models.GitRefTypeBranch: "BRANCH", - models.GitRefTypeTag: "TAG", + unmarshalOGitRefType2ᚖgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋrepositoryᚐGitRefType = map[string]repository.GitRefType{ + "BRANCH": repository.GitRefTypeBranch, + "TAG": repository.GitRefTypeTag, + "COMMIT": repository.GitRefTypeCommit, + } + marshalOGitRefType2ᚖgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋrepositoryᚐGitRefType = map[repository.GitRefType]string{ + repository.GitRefTypeBranch: "BRANCH", + repository.GitRefTypeTag: "TAG", + repository.GitRefTypeCommit: "COMMIT", } ) diff --git a/api/graphql/graph/repository.generated.go b/api/graphql/graph/repository.generated.go index 1ed350b21067bf6bca5e80f932c0871337352724..2e89793ba613f56aaca707f7d46928b081682662 100644 --- a/api/graphql/graph/repository.generated.go +++ b/api/graphql/graph/repository.generated.go @@ -13,6 +13,7 @@ import ( "github.com/99designs/gqlgen/graphql" "github.com/git-bug/git-bug/api/graphql/models" + "github.com/git-bug/git-bug/repository" "github.com/vektah/gqlparser/v2/ast" ) @@ -25,13 +26,13 @@ type RepositoryResolver interface { AllIdentities(ctx context.Context, obj *models.Repository, after *string, before *string, first *int, last *int) (*models.IdentityConnection, error) Identity(ctx context.Context, obj *models.Repository, prefix string) (models.IdentityWrapper, error) UserIdentity(ctx context.Context, obj *models.Repository) (models.IdentityWrapper, error) - Refs(ctx context.Context, obj *models.Repository, after *string, before *string, first *int, last *int, typeArg *models.GitRefType) (*models.GitRefConnection, error) + Refs(ctx context.Context, obj *models.Repository, after *string, before *string, first *int, last *int, typeArg *repository.GitRefType) (*models.GitRefConnection, error) Tree(ctx context.Context, obj *models.Repository, ref string, path *string) ([]*models.GitTreeEntry, error) Blob(ctx context.Context, obj *models.Repository, ref string, path string) (*models.GitBlob, error) Commits(ctx context.Context, obj *models.Repository, after *string, first *int, ref string, path *string, since *time.Time, until *time.Time) (*models.GitCommitConnection, error) Commit(ctx context.Context, obj *models.Repository, hash string) (*models.GitCommitMeta, error) LastCommits(ctx context.Context, obj *models.Repository, ref string, path *string, names []string) ([]*models.GitLastCommit, error) - Head(ctx context.Context, obj *models.Repository) (*models.GitCommitMeta, error) + Head(ctx context.Context, obj *models.Repository) (*models.GitRef, error) ValidLabels(ctx context.Context, obj *models.Repository, after *string, before *string, first *int, last *int) (*models.LabelConnection, error) } @@ -713,18 +714,18 @@ func (ec *executionContext) field_Repository_refs_argsLast( func (ec *executionContext) field_Repository_refs_argsType( ctx context.Context, rawArgs map[string]any, -) (*models.GitRefType, error) { +) (*repository.GitRefType, error) { if _, ok := rawArgs["type"]; !ok { - var zeroVal *models.GitRefType + var zeroVal *repository.GitRefType return zeroVal, nil } ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("type")) if tmp, ok := rawArgs["type"]; ok { - return ec.unmarshalOGitRefType2ᚖgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐGitRefType(ctx, tmp) + return ec.unmarshalOGitRefType2ᚖgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋrepositoryᚐGitRefType(ctx, tmp) } - var zeroVal *models.GitRefType + var zeroVal *repository.GitRefType return zeroVal, nil } @@ -1278,7 +1279,7 @@ func (ec *executionContext) _Repository_refs(ctx context.Context, field graphql. }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Repository().Refs(rctx, obj, fc.Args["after"].(*string), fc.Args["before"].(*string), fc.Args["first"].(*int), fc.Args["last"].(*int), fc.Args["type"].(*models.GitRefType)) + return ec.resolvers.Repository().Refs(rctx, obj, fc.Args["after"].(*string), fc.Args["before"].(*string), fc.Args["first"].(*int), fc.Args["last"].(*int), fc.Args["type"].(*repository.GitRefType)) }) if err != nil { ec.Error(ctx, err) @@ -1679,9 +1680,9 @@ func (ec *executionContext) _Repository_head(ctx context.Context, field graphql. if resTmp == nil { return graphql.Null } - res := resTmp.(*models.GitCommitMeta) + res := resTmp.(*models.GitRef) fc.Result = res - return ec.marshalOGitCommit2ᚖgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐGitCommitMeta(ctx, field.Selections, res) + return ec.marshalOGitRef2ᚖgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐGitRef(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_Repository_head(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -1692,28 +1693,18 @@ func (ec *executionContext) fieldContext_Repository_head(_ context.Context, fiel IsResolver: true, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { + case "name": + return ec.fieldContext_GitRef_name(ctx, field) + case "shortName": + return ec.fieldContext_GitRef_shortName(ctx, field) + case "type": + return ec.fieldContext_GitRef_type(ctx, field) case "hash": - return ec.fieldContext_GitCommit_hash(ctx, field) - case "shortHash": - return ec.fieldContext_GitCommit_shortHash(ctx, field) - case "message": - return ec.fieldContext_GitCommit_message(ctx, field) - case "fullMessage": - return ec.fieldContext_GitCommit_fullMessage(ctx, field) - case "authorName": - return ec.fieldContext_GitCommit_authorName(ctx, field) - case "authorEmail": - return ec.fieldContext_GitCommit_authorEmail(ctx, field) - case "date": - return ec.fieldContext_GitCommit_date(ctx, field) - case "parents": - return ec.fieldContext_GitCommit_parents(ctx, field) - case "files": - return ec.fieldContext_GitCommit_files(ctx, field) - case "diff": - return ec.fieldContext_GitCommit_diff(ctx, field) + return ec.fieldContext_GitRef_hash(ctx, field) + case "commit": + return ec.fieldContext_GitRef_commit(ctx, field) } - return nil, fmt.Errorf("no field named %q was found under type GitCommit", field.Name) + return nil, fmt.Errorf("no field named %q was found under type GitRef", field.Name) }, } return fc, nil diff --git a/api/graphql/graph/root_.generated.go b/api/graphql/graph/root_.generated.go index dfccd87bf2f78a2ce5af36870588330de8159713..6dc04076a1f6c7567402a970cb7556504f236420 100644 --- a/api/graphql/graph/root_.generated.go +++ b/api/graphql/graph/root_.generated.go @@ -12,6 +12,7 @@ import ( "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/introspection" "github.com/git-bug/git-bug/api/graphql/models" + "github.com/git-bug/git-bug/repository" gqlparser "github.com/vektah/gqlparser/v2" "github.com/vektah/gqlparser/v2/ast" ) @@ -50,6 +51,7 @@ type ResolverRoot interface { BugSetTitleTimelineItem() BugSetTitleTimelineItemResolver Color() ColorResolver GitCommit() GitCommitResolver + GitRef() GitRefResolver GitTreeEntry() GitTreeEntryResolver Identity() IdentityResolver Label() LabelResolver @@ -366,6 +368,7 @@ type ComplexityRoot struct { } GitRef struct { + Commit func(childComplexity int) int Hash func(childComplexity int) int Name func(childComplexity int) int ShortName func(childComplexity int) int @@ -482,7 +485,7 @@ type ComplexityRoot struct { Identity func(childComplexity int, prefix string) int LastCommits func(childComplexity int, ref string, path *string, names []string) int Name func(childComplexity int) int - Refs func(childComplexity int, after *string, before *string, first *int, last *int, typeArg *models.GitRefType) int + Refs func(childComplexity int, after *string, before *string, first *int, last *int, typeArg *repository.GitRefType) int Tree func(childComplexity int, ref string, path *string) int UserIdentity func(childComplexity int) int ValidLabels func(childComplexity int, after *string, before *string, first *int, last *int) int @@ -1814,6 +1817,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.complexity.GitLastCommit.Name(childComplexity), true + case "GitRef.commit": + if e.complexity.GitRef.Commit == nil { + break + } + + return e.complexity.GitRef.Commit(childComplexity), true + case "GitRef.hash": if e.complexity.GitRef.Hash == nil { break @@ -2395,7 +2405,7 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return 0, false } - return e.complexity.Repository.Refs(childComplexity, args["after"].(*string), args["before"].(*string), args["first"].(*int), args["last"].(*int), args["type"].(*models.GitRefType)), true + return e.complexity.Repository.Refs(childComplexity, args["after"].(*string), args["before"].(*string), args["first"].(*int), args["last"].(*int), args["type"].(*repository.GitRefType)), true case "Repository.tree": if e.complexity.Repository.Tree == nil { @@ -3166,7 +3176,8 @@ directive @goEnum( ) on ENUM_VALUE `, BuiltIn: false}, {Name: "../schema/git.graphql", Input: `"""A git branch or tag reference.""" -type GitRef { +type GitRef +@goModel(model: "github.com/git-bug/git-bug/api/graphql/models.GitRef") { """Full reference name, e.g. refs/heads/main or refs/tags/v1.0.""" name: String! """Short name, e.g. main or v1.0.""" @@ -3175,6 +3186,8 @@ type GitRef { type: GitRefType! """Commit hash the reference points to.""" hash: String! + """Git commit the reference points to.""" + commit: GitCommit! } """An entry in a git tree (directory listing).""" @@ -3335,13 +3348,15 @@ type GitDiffLine # ── enums ───────────────────────────────────────────────────────────────────── -"""The kind of git reference: a branch or a tag.""" +"""The kind of git reference: a branch, a tag, or a detached commit.""" enum GitRefType -@goModel(model: "github.com/git-bug/git-bug/api/graphql/models.GitRefType") { +@goModel(model: "github.com/git-bug/git-bug/repository.GitRefType") { """A local branch (refs/heads/*).""" - BRANCH @goEnum(value: "github.com/git-bug/git-bug/api/graphql/models.GitRefTypeBranch") + BRANCH @goEnum(value: "github.com/git-bug/git-bug/repository.GitRefTypeBranch") """An annotated or lightweight tag (refs/tags/*).""" - TAG @goEnum(value: "github.com/git-bug/git-bug/api/graphql/models.GitRefTypeTag") + TAG @goEnum(value: "github.com/git-bug/git-bug/repository.GitRefTypeTag") + """A detached HEAD pointing directly at a commit.""" + COMMIT @goEnum(value: "github.com/git-bug/git-bug/repository.GitRefTypeCommit") } """The type of object a git tree entry points to.""" @@ -3515,7 +3530,8 @@ type OperationEdge { """The identity created or selected by the user as its own""" userIdentity: Identity - """All branches and tags, optionally filtered by type.""" + """All branches and tags, optionally filtered by type. BRANCH and TAG are + the only accepted filter values; passing COMMIT returns an error.""" refs( """Returns the elements in the list that come after the specified cursor.""" after: String @@ -3525,7 +3541,7 @@ type OperationEdge { first: Int """Returns the last _n_ elements from the list.""" last: Int - """Restrict to references of this type.""" + """Restrict to references of this type. Accepts BRANCH or TAG only.""" type: GitRefType ): GitRefConnection! @@ -3562,9 +3578,10 @@ type OperationEdge { tree listing without blocking the initial tree fetch.""" lastCommits(ref: String!, path: String, names: [String!]!): [GitLastCommit!]! - """The currently checked-out commit (branch, tag, hash ...) in the git repository. - Null if there is none (bare repo).""" - head: GitCommit + """The reference pointed to by HEAD in the git repository. + Null if HEAD cannot be resolved, for example in an empty or unborn + repository, or if HEAD is missing or invalid.""" + head: GitRef """List of valid labels.""" validLabels( diff --git a/api/graphql/graphql_test.go b/api/graphql/graphql_test.go index 716febc6b10db20e924e6c57f0affa3ae1cdaa36..7dc34b6646d66f93e75e31b645f97e25c7b63757 100644 --- a/api/graphql/graphql_test.go +++ b/api/graphql/graphql_test.go @@ -440,6 +440,121 @@ func TestGitBrowseQueries(t *testing.T) { require.Equal(t, string(c2), got.Nodes[1].Hash) }) + // ── refs ───────────────────────────────────────────────────────────────── + + t.Run("refs_all", func(t *testing.T) { + var resp struct { + Repository struct { + Refs struct { + TotalCount int + Nodes []struct { + Name string + ShortName string + Type string `json:"type"` + Hash string + } + } + } + } + require.NoError(t, c.Post(`query { + repository { refs { totalCount nodes { name shortName type hash } } } + }`, &resp)) + nodes := resp.Repository.Refs.Nodes + require.Equal(t, 3, resp.Repository.Refs.TotalCount) + byShort := make(map[string]struct { + Name string + Type string + Hash string + }) + for _, n := range nodes { + byShort[n.ShortName] = struct { + Name string + Type string + Hash string + }{n.Name, n.Type, n.Hash} + } + require.Equal(t, "refs/heads/feature", byShort["feature"].Name) + require.Equal(t, "BRANCH", byShort["feature"].Type) + require.Equal(t, string(c2), byShort["feature"].Hash) + require.Equal(t, "refs/heads/main", byShort["main"].Name) + require.Equal(t, "BRANCH", byShort["main"].Type) + require.Equal(t, string(c3), byShort["main"].Hash) + require.Equal(t, "refs/tags/v1.0", byShort["v1.0"].Name) + require.Equal(t, "TAG", byShort["v1.0"].Type) + require.Equal(t, string(c1), byShort["v1.0"].Hash) + }) + + t.Run("refs_branch_filter", func(t *testing.T) { + var resp struct { + Repository struct { + Refs struct { + TotalCount int + Nodes []struct{ ShortName string } + } + } + } + require.NoError(t, c.Post(`query { + repository { refs(type: BRANCH) { totalCount nodes { shortName } } } + }`, &resp)) + require.Equal(t, 2, resp.Repository.Refs.TotalCount) + names := make([]string, len(resp.Repository.Refs.Nodes)) + for i, n := range resp.Repository.Refs.Nodes { + names[i] = n.ShortName + } + require.ElementsMatch(t, []string{"main", "feature"}, names) + }) + + t.Run("refs_tag_filter", func(t *testing.T) { + var resp struct { + Repository struct { + Refs struct { + TotalCount int + Nodes []struct{ ShortName string } + } + } + } + require.NoError(t, c.Post(`query { + repository { refs(type: TAG) { totalCount nodes { shortName } } } + }`, &resp)) + require.Equal(t, 1, resp.Repository.Refs.TotalCount) + require.Equal(t, "v1.0", resp.Repository.Refs.Nodes[0].ShortName) + }) + + t.Run("refs_commit_filter_error", func(t *testing.T) { + var resp struct { + Repository struct{ Refs *struct{ TotalCount int } } + } + err := c.Post(`query { + repository { refs(type: COMMIT) { totalCount } } + }`, &resp) + require.Error(t, err) + require.Contains(t, err.Error(), "COMMIT") + }) + + // ── head ───────────────────────────────────────────────────────────────── + + t.Run("head_detached", func(t *testing.T) { + require.NoError(t, repo.UpdateRef("HEAD", c3)) + var resp struct { + Repository struct { + Head struct { + Name string + ShortName string + Type string `json:"type"` + Hash string + } + } + } + require.NoError(t, c.Post(`query { + repository { head { name shortName type hash } } + }`, &resp)) + got := resp.Repository.Head + require.Equal(t, "HEAD", got.Name) + require.Equal(t, "HEAD", got.ShortName) + require.Equal(t, "COMMIT", got.Type) + require.Equal(t, string(c3), got.Hash) + }) + // ── lastCommits ─────────────────────────────────────────────────────────── t.Run("lastCommits", func(t *testing.T) { diff --git a/api/graphql/models/enums.go b/api/graphql/models/enums.go deleted file mode 100644 index 4684b7f5baca167db223d36fe6c9a5037f0c2227..0000000000000000000000000000000000000000 --- a/api/graphql/models/enums.go +++ /dev/null @@ -1,58 +0,0 @@ -package models - -import ( - "bytes" - "fmt" - "io" - "strconv" -) - -// GitRefType is the kind of git reference: a branch or a tag. -type GitRefType string - -const ( - // GitRefTypeBranch refers to a local branch (refs/heads/*). - GitRefTypeBranch GitRefType = "BRANCH" - // GitRefTypeTag refers to an annotated or lightweight tag (refs/tags/*). - GitRefTypeTag GitRefType = "TAG" -) - -func (e GitRefType) IsValid() bool { - switch e { - case GitRefTypeBranch, GitRefTypeTag: - return true - } - return false -} - -func (e GitRefType) String() string { return string(e) } - -func (e *GitRefType) UnmarshalGQL(v any) error { - str, ok := v.(string) - if !ok { - return fmt.Errorf("enums must be strings") - } - *e = GitRefType(str) - if !e.IsValid() { - return fmt.Errorf("%s is not a valid GitRefType", str) - } - return nil -} - -func (e GitRefType) MarshalGQL(w io.Writer) { - fmt.Fprint(w, strconv.Quote(e.String())) -} - -func (e *GitRefType) UnmarshalJSON(b []byte) error { - s, err := strconv.Unquote(string(b)) - if err != nil { - return err - } - return e.UnmarshalGQL(s) -} - -func (e GitRefType) MarshalJSON() ([]byte, error) { - var buf bytes.Buffer - e.MarshalGQL(&buf) - return buf.Bytes(), nil -} diff --git a/api/graphql/models/gen_models.go b/api/graphql/models/gen_models.go index 51e6452862411cc4ef8658e683f5768783f56595..b0a095e1f76d2de251cce0867f0b0ca53bb08a45 100644 --- a/api/graphql/models/gen_models.go +++ b/api/graphql/models/gen_models.go @@ -310,18 +310,6 @@ type GitLastCommit struct { Commit *GitCommitMeta `json:"commit"` } -// A git branch or tag reference. -type GitRef struct { - // Full reference name, e.g. refs/heads/main or refs/tags/v1.0. - Name string `json:"name"` - // Short name, e.g. main or v1.0. - ShortName string `json:"shortName"` - // Whether this reference is a branch or a tag. - Type GitRefType `json:"type"` - // Commit hash the reference points to. - Hash string `json:"hash"` -} - type GitRefConnection struct { Nodes []*GitRef `json:"nodes"` PageInfo *PageInfo `json:"pageInfo"` diff --git a/api/graphql/models/models.go b/api/graphql/models/models.go index 9678689df9c17e207a28ac046aec47d1cef2f74c..a2fc3c7765fb1a785b004f70d4d1690acf30a969 100644 --- a/api/graphql/models/models.go +++ b/api/graphql/models/models.go @@ -17,6 +17,13 @@ type Repository struct { Repo *cache.RepoCache } +// GitRef is a wrapper around a RefMeta that includes the Repo, +// to keep the repo context in sub-resolvers. +type GitRef struct { + Repo *cache.RepoCache + repository.RefMeta +} + // GitCommitMeta is a wrapper around a CommitMeta that includes the Repo, // to keep the repo context in sub-resolvers. type GitCommitMeta struct { diff --git a/api/graphql/resolvers/git.go b/api/graphql/resolvers/git.go index bddd03c64adb656e01a7a5ef569f5489fd4670dc..c40979324344696b526481da2e6d48d7c8246182 100644 --- a/api/graphql/resolvers/git.go +++ b/api/graphql/resolvers/git.go @@ -2,6 +2,7 @@ package resolvers import ( "context" + "errors" "github.com/git-bug/git-bug/api/graphql/connections" "github.com/git-bug/git-bug/api/graphql/graph" @@ -90,3 +91,19 @@ func (r gitTreeEntryResolver) LastCommit(_ context.Context, obj *models.GitTreeE } return &models.GitCommitMeta{Repo: obj.Repo, CommitMeta: meta}, nil } + +var _ graph.GitRefResolver = &gitRefResolver{} + +type gitRefResolver struct{} + +func (g gitRefResolver) Commit(ctx context.Context, obj *models.GitRef) (*models.GitCommitMeta, error) { + repo := obj.Repo.BrowseRepo() + detail, err := repo.CommitDetail(repository.Hash(obj.Hash)) + if errors.Is(err, repository.ErrNotFound) { + return nil, nil + } + if err != nil { + return nil, err + } + return &models.GitCommitMeta{Repo: obj.Repo, CommitMeta: detail.CommitMeta}, nil +} diff --git a/api/graphql/resolvers/repo.go b/api/graphql/resolvers/repo.go index bc19cf3572d0616a4281dbbfa2c8ef0178c9f31b..26352b82d330912920a65691125ccfdd4b010ee8 100644 --- a/api/graphql/resolvers/repo.go +++ b/api/graphql/resolvers/repo.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "errors" + "fmt" "io" "math" "sort" @@ -206,37 +207,47 @@ func (repoResolver) ValidLabels(_ context.Context, obj *models.Repository, after return connections.Connection(obj.Repo.Bugs().ValidLabels(), edger, conMaker, input) } -func (repoResolver) Refs(_ context.Context, obj *models.Repository, after *string, before *string, first *int, last *int, typeArg *models.GitRefType) (*models.GitRefConnection, error) { +func (repoResolver) Refs(_ context.Context, obj *models.Repository, after *string, before *string, first *int, last *int, typeArg *repository.GitRefType) (*models.GitRefConnection, error) { repo := obj.Repo.BrowseRepo() var refs []*models.GitRef - if typeArg == nil || *typeArg == models.GitRefTypeBranch { + if typeArg != nil && *typeArg == repository.GitRefTypeCommit { + return nil, fmt.Errorf("refs: COMMIT is not a valid filter; use BRANCH or TAG") + } + + if typeArg == nil || *typeArg == repository.GitRefTypeBranch { branches, err := repo.Branches() if err != nil { return nil, err } for _, b := range branches { refs = append(refs, &models.GitRef{ - Name: "refs/heads/" + b.Name, - ShortName: b.Name, - Type: models.GitRefTypeBranch, - Hash: string(b.Hash), + Repo: obj.Repo, + RefMeta: repository.RefMeta{ + Name: "refs/heads/" + b.Name, + ShortName: b.Name, + Type: repository.GitRefTypeBranch, + Hash: string(b.Hash), + }, }) } } - if typeArg == nil || *typeArg == models.GitRefTypeTag { + if typeArg == nil || *typeArg == repository.GitRefTypeTag { tags, err := repo.Tags() if err != nil { return nil, err } for _, t := range tags { refs = append(refs, &models.GitRef{ - Name: "refs/tags/" + t.Name, - ShortName: t.Name, - Type: models.GitRefTypeTag, - Hash: string(t.Hash), + Repo: obj.Repo, + RefMeta: repository.RefMeta{ + Name: "refs/tags/" + t.Name, + ShortName: t.Name, + Type: repository.GitRefTypeTag, + Hash: string(t.Hash), + }, }) } } @@ -422,7 +433,7 @@ func (repoResolver) LastCommits(_ context.Context, obj *models.Repository, ref s return result, nil } -func (repoResolver) Head(_ context.Context, obj *models.Repository) (*models.GitCommitMeta, error) { +func (repoResolver) Head(_ context.Context, obj *models.Repository) (*models.GitRef, error) { meta, err := obj.Repo.BrowseRepo().Head() if errors.Is(err, repository.ErrNotFound) { return nil, nil @@ -430,5 +441,5 @@ func (repoResolver) Head(_ context.Context, obj *models.Repository) (*models.Git if err != nil { return nil, err } - return &models.GitCommitMeta{Repo: obj.Repo, CommitMeta: meta}, nil + return &models.GitRef{Repo: obj.Repo, RefMeta: meta}, nil } diff --git a/api/graphql/resolvers/root.go b/api/graphql/resolvers/root.go index fb9948d129bccb4331fb968597ac93ba880d4b8b..24190edd71053969ccd13334f4befe503aebdad1 100644 --- a/api/graphql/resolvers/root.go +++ b/api/graphql/resolvers/root.go @@ -57,6 +57,10 @@ func (RootResolver) Bug() graph.BugResolver { return &bugResolver{} } +func (r RootResolver) GitRef() graph.GitRefResolver { + return &gitRefResolver{} +} + func (r RootResolver) GitCommit() graph.GitCommitResolver { return &gitCommitResolver{} } diff --git a/api/graphql/schema/git.graphql b/api/graphql/schema/git.graphql index abae2a7d07ad34936cbbd8092976ad46f06b3867..f06740feae782081d40ff58173a5e88a0af1ee76 100644 --- a/api/graphql/schema/git.graphql +++ b/api/graphql/schema/git.graphql @@ -1,5 +1,6 @@ """A git branch or tag reference.""" -type GitRef { +type GitRef +@goModel(model: "github.com/git-bug/git-bug/api/graphql/models.GitRef") { """Full reference name, e.g. refs/heads/main or refs/tags/v1.0.""" name: String! """Short name, e.g. main or v1.0.""" @@ -8,6 +9,8 @@ type GitRef { type: GitRefType! """Commit hash the reference points to.""" hash: String! + """Git commit the reference points to.""" + commit: GitCommit! } """An entry in a git tree (directory listing).""" @@ -168,13 +171,15 @@ type GitDiffLine # ── enums ───────────────────────────────────────────────────────────────────── -"""The kind of git reference: a branch or a tag.""" +"""The kind of git reference: a branch, a tag, or a detached commit.""" enum GitRefType -@goModel(model: "github.com/git-bug/git-bug/api/graphql/models.GitRefType") { +@goModel(model: "github.com/git-bug/git-bug/repository.GitRefType") { """A local branch (refs/heads/*).""" - BRANCH @goEnum(value: "github.com/git-bug/git-bug/api/graphql/models.GitRefTypeBranch") + BRANCH @goEnum(value: "github.com/git-bug/git-bug/repository.GitRefTypeBranch") """An annotated or lightweight tag (refs/tags/*).""" - TAG @goEnum(value: "github.com/git-bug/git-bug/api/graphql/models.GitRefTypeTag") + TAG @goEnum(value: "github.com/git-bug/git-bug/repository.GitRefTypeTag") + """A detached HEAD pointing directly at a commit.""" + COMMIT @goEnum(value: "github.com/git-bug/git-bug/repository.GitRefTypeCommit") } """The type of object a git tree entry points to.""" diff --git a/api/graphql/schema/repository.graphql b/api/graphql/schema/repository.graphql index 3e986a560f317e546e8a8f14e60fd36d50b029d1..92cfb383af115ed9294eb9164ae1c95827fe4e34 100644 --- a/api/graphql/schema/repository.graphql +++ b/api/graphql/schema/repository.graphql @@ -37,7 +37,8 @@ type Repository { """The identity created or selected by the user as its own""" userIdentity: Identity - """All branches and tags, optionally filtered by type.""" + """All branches and tags, optionally filtered by type. BRANCH and TAG are + the only accepted filter values; passing COMMIT returns an error.""" refs( """Returns the elements in the list that come after the specified cursor.""" after: String @@ -47,7 +48,7 @@ type Repository { first: Int """Returns the last _n_ elements from the list.""" last: Int - """Restrict to references of this type.""" + """Restrict to references of this type. Accepts BRANCH or TAG only.""" type: GitRefType ): GitRefConnection! @@ -84,10 +85,10 @@ type Repository { tree listing without blocking the initial tree fetch.""" lastCommits(ref: String!, path: String, names: [String!]!): [GitLastCommit!]! - """The commit pointed to by HEAD in the git repository. - Null if HEAD cannot be resolved to a commit, for example in an empty or unborn + """The reference pointed to by HEAD in the git repository. + Null if HEAD cannot be resolved, for example in an empty or unborn repository, or if HEAD is missing or invalid.""" - head: GitCommit + head: GitRef """List of valid labels.""" validLabels( diff --git a/repository/browse.go b/repository/browse.go index f0159b5f7d1a5e0d7a6971ac4b98cc0bebecaaa3..1a0b69a67bc9273dcccd50c832c8b1d2ef221d1d 100644 --- a/repository/browse.go +++ b/repository/browse.go @@ -1,6 +1,7 @@ package repository import ( + "bytes" "fmt" "io" "strconv" @@ -92,6 +93,69 @@ func (t *DiffLineType) UnmarshalGQL(v any) error { return nil } +// GitRefType is the kind of git reference: a branch, a tag, or a detached commit. +type GitRefType string + +const ( + // GitRefTypeBranch refers to a local branch (refs/heads/*). + GitRefTypeBranch GitRefType = "BRANCH" + // GitRefTypeTag refers to an annotated or lightweight tag (refs/tags/*). + GitRefTypeTag GitRefType = "TAG" + // GitRefTypeCommit represents a detached HEAD pointing directly at a commit. + GitRefTypeCommit GitRefType = "COMMIT" +) + +func (e GitRefType) IsValid() bool { + switch e { + case GitRefTypeBranch, GitRefTypeTag, GitRefTypeCommit: + return true + } + return false +} + +func (e GitRefType) String() string { return string(e) } + +func (e *GitRefType) UnmarshalGQL(v any) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + *e = GitRefType(str) + if !e.IsValid() { + return fmt.Errorf("%s is not a valid GitRefType", str) + } + return nil +} + +func (e GitRefType) MarshalGQL(w io.Writer) { + fmt.Fprint(w, strconv.Quote(e.String())) +} + +func (e *GitRefType) UnmarshalJSON(b []byte) error { + s, err := strconv.Unquote(string(b)) + if err != nil { + return err + } + return e.UnmarshalGQL(s) +} + +func (e GitRefType) MarshalJSON() ([]byte, error) { + var buf bytes.Buffer + e.MarshalGQL(&buf) + return buf.Bytes(), nil +} + +type RefMeta struct { + // Full reference name, e.g. refs/heads/main or refs/tags/v1.0. + Name string `json:"name"` + // Short name, e.g. main or v1.0. + ShortName string `json:"shortName"` + // Whether this reference is a branch or a tag. + Type GitRefType `json:"type"` + // Commit hash the reference points to. + Hash string `json:"hash"` +} + // CommitMeta holds the metadata for a single commit, suitable for listing. type CommitMeta struct { Hash Hash diff --git a/repository/gogit.go b/repository/gogit.go index 1403eb8c196fddfe4be7e6aa979481246bb91a45..6a8fabdc01c0d8a053b66d030b7b1a58f9011fa5 100644 --- a/repository/gogit.go +++ b/repository/gogit.go @@ -1550,30 +1550,6 @@ func (repo *GoGitRepo) CommitFileDiff(hash Hash, filePath string) (FileDiff, err return FileDiff{}, ErrNotFound } -// Head returns the commit that HEAD currently points to. -func (repo *GoGitRepo) Head() (CommitMeta, error) { - repo.rMutex.Lock() - defer repo.rMutex.Unlock() - - ref, err := repo.r.Head() - if err == plumbing.ErrReferenceNotFound { - return CommitMeta{}, ErrNotFound - } - if err != nil { - return CommitMeta{}, err - } - - c, err := repo.r.CommitObject(ref.Hash()) - if err == plumbing.ErrObjectNotFound { - return CommitMeta{}, ErrNotFound - } - if err != nil { - return CommitMeta{}, err - } - - return commitToMeta(c), nil -} - // buildDiffHunks converts a go-git FilePatch into DiffHunks with line numbers // and context grouping. func buildDiffHunks(fp fdiff.FilePatch) []DiffHunk { @@ -1674,6 +1650,37 @@ func buildDiffHunks(fp fdiff.FilePatch) []DiffHunk { return hunks } +// Head returns the ref that HEAD currently points to. +func (repo *GoGitRepo) Head() (RefMeta, error) { + repo.rMutex.Lock() + defer repo.rMutex.Unlock() + + ref, err := repo.r.Head() + if err == plumbing.ErrReferenceNotFound { + return RefMeta{}, ErrNotFound + } + if err != nil { + return RefMeta{}, err + } + + var refType GitRefType + switch { + case ref.Name().IsBranch(): + refType = GitRefTypeBranch + case ref.Name().IsTag(): + refType = GitRefTypeTag + default: + refType = GitRefTypeCommit + } + + return RefMeta{ + Name: ref.Name().String(), + ShortName: ref.Name().Short(), + Type: refType, + Hash: ref.Hash().String(), + }, nil +} + // AddRemote add a new remote to the repository // Not in the interface because it's only used for testing func (repo *GoGitRepo) AddRemote(name string, url string) error { diff --git a/repository/mock_repo.go b/repository/mock_repo.go index e13e18cfdfc2b314885c4513c60f5900fd3cc8dd..796284279119488adb7b003e498ab69a97461fb3 100644 --- a/repository/mock_repo.go +++ b/repository/mock_repo.go @@ -794,16 +794,20 @@ func (r *mockRepoDataBrowse) CommitFileDiff(hash Hash, filePath string) (FileDif return fd, nil } -func (r *mockRepoDataBrowse) Head() (CommitMeta, error) { +func (r *mockRepoDataBrowse) Head() (RefMeta, error) { hash, ok := r.refs["HEAD"] if !ok { - return CommitMeta{}, ErrNotFound + return RefMeta{}, ErrNotFound } - c, ok := r.commits[hash] - if !ok { - return CommitMeta{}, ErrNotFound + if _, ok := r.commits[hash]; !ok { + return RefMeta{}, ErrNotFound } - return mockCommitMeta(hash, c), nil + return RefMeta{ + Name: "HEAD", + ShortName: "HEAD", + Type: GitRefTypeCommit, + Hash: string(hash), + }, nil } // mockDiffHunks produces a single DiffHunk using a prefix/suffix scan. diff --git a/repository/repo.go b/repository/repo.go index e081038a0d285304ba0c095e6c9a201aa6abdf6c..d5c5306f554bdb0fc666aca77540106d42af2a06 100644 --- a/repository/repo.go +++ b/repository/repo.go @@ -265,7 +265,7 @@ type RepoBrowse interface { // Head returns the commit that HEAD currently points to. // Returns ErrNotFound if HEAD cannot be resolved to a commit, including // for an empty (unborn) repository. - Head() (CommitMeta, error) + Head() (RefMeta, error) } // ClockLoader hold which logical clock need to exist for an entity and diff --git a/repository/repo_testing.go b/repository/repo_testing.go index 53780c145e291c3ec0af1b8ecdc72e4cae24b008..d780ef9aaa9dd9e748ae43d6e4014ac6791556d7 100644 --- a/repository/repo_testing.go +++ b/repository/repo_testing.go @@ -772,10 +772,21 @@ func RepoBrowseTest(t *testing.T, repo browsable) { // ── Head ────────────────────────────────────────────────────────────────── t.Run("Head", func(t *testing.T) { + // Detached HEAD: UpdateRef sets HEAD to a bare hash. require.NoError(t, repo.UpdateRef("HEAD", c3)) meta, err := repo.Head() require.NoError(t, err) - require.Equal(t, c3, meta.Hash) + require.Equal(t, string(c3), meta.Hash) + require.Equal(t, GitRefTypeCommit, meta.Type) + // Detached HEAD has no branch/tag name; both name fields should be "HEAD". + require.Equal(t, "HEAD", meta.Name) + require.Equal(t, "HEAD", meta.ShortName) + + // Moving HEAD to a different commit should be reflected immediately. + require.NoError(t, repo.UpdateRef("HEAD", c1)) + meta2, err := repo.Head() + require.NoError(t, err) + require.Equal(t, string(c1), meta2.Hash) }) }