Detailed changes
@@ -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",
}
)
@@ -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
@@ -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(
@@ -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) {
@@ -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
-}
@@ -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"`
@@ -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 {
@@ -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
+}
@@ -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
}
@@ -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{}
}
@@ -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."""
@@ -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(
@@ -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
@@ -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 {
@@ -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.
@@ -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
@@ -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)
})
}