repo: add RepoBrowse features to support code browsing UIs (#1541)

Michael MurΓ© created

Change summary

api/graphql/connections/edges.go          |    8 
api/graphql/graph/git.generated.go        | 3998 +++++++++++++++++++++++++
api/graphql/graph/prelude.generated.go    |   41 
api/graphql/graph/repository.generated.go |  987 +++++
api/graphql/graph/root.generated.go       |   12 
api/graphql/graph/root_.generated.go      |  816 +++++
api/graphql/graph/types.generated.go      |   18 
api/graphql/graphql_test.go               |  226 +
api/graphql/models/enums.go               |   58 
api/graphql/models/gen_models.go          |   61 
api/graphql/models/models.go              |   11 
api/graphql/resolvers/git.go              |   74 
api/graphql/resolvers/query.go            |    7 
api/graphql/resolvers/repo.go             |  229 +
api/graphql/resolvers/root.go             |    6 
api/graphql/schema/directives.graphql     |    4 
api/graphql/schema/git.graphql            |  214 +
api/graphql/schema/repository.graphql     |   51 
api/graphql/schema/root.graphql           |    3 
cache/repo_cache_common.go                |   10 
repository/browse.go                      |  160 +
repository/common.go                      |    7 
repository/gogit.go                       |  792 ++++
repository/mock_repo.go                   |  517 +++
repository/repo.go                        |   64 
repository/repo_testing.go                |  380 ++
repository/tree_entry.go                  |   58 
27 files changed, 8,675 insertions(+), 137 deletions(-)

Detailed changes

api/graphql/connections/edges.go πŸ”—

@@ -2,6 +2,14 @@ package connections
 
 import "github.com/git-bug/git-bug/entity"
 
+// CursorEdge is a minimal edge carrying only a cursor. Use it with
+// connections.Connection when the edge type needs no additional fields.
+type CursorEdge struct {
+	Cursor string
+}
+
+func (e CursorEdge) GetCursor() string { return e.Cursor }
+
 // LazyBugEdge is a special relay edge used to implement a lazy loading connection
 type LazyBugEdge struct {
 	Id     entity.Id

api/graphql/graph/git.generated.go πŸ”—

@@ -0,0 +1,3998 @@
+// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.
+
+package graph
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"strconv"
+	"sync"
+	"sync/atomic"
+	"time"
+
+	"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"
+)
+
+// region    ************************** generated!.gotpl **************************
+
+type GitCommitResolver interface {
+	ShortHash(ctx context.Context, obj *models.GitCommitMeta) (string, error)
+
+	FullMessage(ctx context.Context, obj *models.GitCommitMeta) (string, error)
+
+	Parents(ctx context.Context, obj *models.GitCommitMeta) ([]string, error)
+	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)
+}
+
+// endregion ************************** generated!.gotpl **************************
+
+// region    ***************************** args.gotpl *****************************
+
+func (ec *executionContext) field_GitCommit_diff_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
+	var err error
+	args := map[string]any{}
+	arg0, err := ec.field_GitCommit_diff_argsPath(ctx, rawArgs)
+	if err != nil {
+		return nil, err
+	}
+	args["path"] = arg0
+	return args, nil
+}
+func (ec *executionContext) field_GitCommit_diff_argsPath(
+	ctx context.Context,
+	rawArgs map[string]any,
+) (string, error) {
+	if _, ok := rawArgs["path"]; !ok {
+		var zeroVal string
+		return zeroVal, nil
+	}
+
+	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("path"))
+	if tmp, ok := rawArgs["path"]; ok {
+		return ec.unmarshalNString2string(ctx, tmp)
+	}
+
+	var zeroVal string
+	return zeroVal, nil
+}
+
+func (ec *executionContext) field_GitCommit_files_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
+	var err error
+	args := map[string]any{}
+	arg0, err := ec.field_GitCommit_files_argsAfter(ctx, rawArgs)
+	if err != nil {
+		return nil, err
+	}
+	args["after"] = arg0
+	arg1, err := ec.field_GitCommit_files_argsBefore(ctx, rawArgs)
+	if err != nil {
+		return nil, err
+	}
+	args["before"] = arg1
+	arg2, err := ec.field_GitCommit_files_argsFirst(ctx, rawArgs)
+	if err != nil {
+		return nil, err
+	}
+	args["first"] = arg2
+	arg3, err := ec.field_GitCommit_files_argsLast(ctx, rawArgs)
+	if err != nil {
+		return nil, err
+	}
+	args["last"] = arg3
+	return args, nil
+}
+func (ec *executionContext) field_GitCommit_files_argsAfter(
+	ctx context.Context,
+	rawArgs map[string]any,
+) (*string, error) {
+	if _, ok := rawArgs["after"]; !ok {
+		var zeroVal *string
+		return zeroVal, nil
+	}
+
+	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("after"))
+	if tmp, ok := rawArgs["after"]; ok {
+		return ec.unmarshalOString2αš–string(ctx, tmp)
+	}
+
+	var zeroVal *string
+	return zeroVal, nil
+}
+
+func (ec *executionContext) field_GitCommit_files_argsBefore(
+	ctx context.Context,
+	rawArgs map[string]any,
+) (*string, error) {
+	if _, ok := rawArgs["before"]; !ok {
+		var zeroVal *string
+		return zeroVal, nil
+	}
+
+	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("before"))
+	if tmp, ok := rawArgs["before"]; ok {
+		return ec.unmarshalOString2αš–string(ctx, tmp)
+	}
+
+	var zeroVal *string
+	return zeroVal, nil
+}
+
+func (ec *executionContext) field_GitCommit_files_argsFirst(
+	ctx context.Context,
+	rawArgs map[string]any,
+) (*int, error) {
+	if _, ok := rawArgs["first"]; !ok {
+		var zeroVal *int
+		return zeroVal, nil
+	}
+
+	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("first"))
+	if tmp, ok := rawArgs["first"]; ok {
+		return ec.unmarshalOInt2αš–int(ctx, tmp)
+	}
+
+	var zeroVal *int
+	return zeroVal, nil
+}
+
+func (ec *executionContext) field_GitCommit_files_argsLast(
+	ctx context.Context,
+	rawArgs map[string]any,
+) (*int, error) {
+	if _, ok := rawArgs["last"]; !ok {
+		var zeroVal *int
+		return zeroVal, nil
+	}
+
+	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("last"))
+	if tmp, ok := rawArgs["last"]; ok {
+		return ec.unmarshalOInt2αš–int(ctx, tmp)
+	}
+
+	var zeroVal *int
+	return zeroVal, nil
+}
+
+// endregion ***************************** args.gotpl *****************************
+
+// region    ************************** directives.gotpl **************************
+
+// endregion ************************** directives.gotpl **************************
+
+// region    **************************** field.gotpl *****************************
+
+func (ec *executionContext) _GitBlob_path(ctx context.Context, field graphql.CollectedField, obj *models.GitBlob) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitBlob_path(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 obj.Path, 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.(string)
+	fc.Result = res
+	return ec.marshalNString2string(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitBlob_path(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitBlob",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type String does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitBlob_hash(ctx context.Context, field graphql.CollectedField, obj *models.GitBlob) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitBlob_hash(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 obj.Hash, 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.(string)
+	fc.Result = res
+	return ec.marshalNString2string(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitBlob_hash(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitBlob",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type String does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitBlob_text(ctx context.Context, field graphql.CollectedField, obj *models.GitBlob) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitBlob_text(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 obj.Text, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*string)
+	fc.Result = res
+	return ec.marshalOString2αš–string(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitBlob_text(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitBlob",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type String does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitBlob_size(ctx context.Context, field graphql.CollectedField, obj *models.GitBlob) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitBlob_size(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 obj.Size, 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.(int)
+	fc.Result = res
+	return ec.marshalNInt2int(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitBlob_size(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitBlob",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type Int does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitBlob_isBinary(ctx context.Context, field graphql.CollectedField, obj *models.GitBlob) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitBlob_isBinary(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 obj.IsBinary, 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.(bool)
+	fc.Result = res
+	return ec.marshalNBoolean2bool(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitBlob_isBinary(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitBlob",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type Boolean does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitBlob_isTruncated(ctx context.Context, field graphql.CollectedField, obj *models.GitBlob) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitBlob_isTruncated(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 obj.IsTruncated, 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.(bool)
+	fc.Result = res
+	return ec.marshalNBoolean2bool(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitBlob_isTruncated(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitBlob",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type Boolean does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitChangedFile_path(ctx context.Context, field graphql.CollectedField, obj *repository.ChangedFile) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitChangedFile_path(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 obj.Path, 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.(string)
+	fc.Result = res
+	return ec.marshalNString2string(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitChangedFile_path(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitChangedFile",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type String does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitChangedFile_oldPath(ctx context.Context, field graphql.CollectedField, obj *repository.ChangedFile) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitChangedFile_oldPath(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 obj.OldPath, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*string)
+	fc.Result = res
+	return ec.marshalOString2αš–string(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitChangedFile_oldPath(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitChangedFile",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type String does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitChangedFile_status(ctx context.Context, field graphql.CollectedField, obj *repository.ChangedFile) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitChangedFile_status(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 obj.Status, 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.(repository.ChangeStatus)
+	fc.Result = res
+	return ec.marshalNGitChangeStatus2githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹repositoryᚐChangeStatus(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitChangedFile_status(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitChangedFile",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type GitChangeStatus does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitChangedFileConnection_nodes(ctx context.Context, field graphql.CollectedField, obj *models.GitChangedFileConnection) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitChangedFileConnection_nodes(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 obj.Nodes, 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.([]*repository.ChangedFile)
+	fc.Result = res
+	return ec.marshalNGitChangedFile2αš•αš–githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹repositoryᚐChangedFileαš„(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitChangedFileConnection_nodes(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitChangedFileConnection",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			switch field.Name {
+			case "path":
+				return ec.fieldContext_GitChangedFile_path(ctx, field)
+			case "oldPath":
+				return ec.fieldContext_GitChangedFile_oldPath(ctx, field)
+			case "status":
+				return ec.fieldContext_GitChangedFile_status(ctx, field)
+			}
+			return nil, fmt.Errorf("no field named %q was found under type GitChangedFile", field.Name)
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitChangedFileConnection_pageInfo(ctx context.Context, field graphql.CollectedField, obj *models.GitChangedFileConnection) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitChangedFileConnection_pageInfo(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 obj.PageInfo, 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.(*models.PageInfo)
+	fc.Result = res
+	return ec.marshalNPageInfo2αš–githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹apiαš‹graphqlαš‹modelsᚐPageInfo(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitChangedFileConnection_pageInfo(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitChangedFileConnection",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			switch field.Name {
+			case "hasNextPage":
+				return ec.fieldContext_PageInfo_hasNextPage(ctx, field)
+			case "hasPreviousPage":
+				return ec.fieldContext_PageInfo_hasPreviousPage(ctx, field)
+			case "startCursor":
+				return ec.fieldContext_PageInfo_startCursor(ctx, field)
+			case "endCursor":
+				return ec.fieldContext_PageInfo_endCursor(ctx, field)
+			}
+			return nil, fmt.Errorf("no field named %q was found under type PageInfo", field.Name)
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitChangedFileConnection_totalCount(ctx context.Context, field graphql.CollectedField, obj *models.GitChangedFileConnection) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitChangedFileConnection_totalCount(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 obj.TotalCount, 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.(int)
+	fc.Result = res
+	return ec.marshalNInt2int(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitChangedFileConnection_totalCount(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitChangedFileConnection",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type Int does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitCommit_hash(ctx context.Context, field graphql.CollectedField, obj *models.GitCommitMeta) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitCommit_hash(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 obj.Hash, 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.(repository.Hash)
+	fc.Result = res
+	return ec.marshalNString2githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹repositoryᚐHash(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitCommit_hash(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitCommit",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type String does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitCommit_shortHash(ctx context.Context, field graphql.CollectedField, obj *models.GitCommitMeta) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitCommit_shortHash(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.GitCommit().ShortHash(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) fieldContext_GitCommit_shortHash(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitCommit",
+		Field:      field,
+		IsMethod:   true,
+		IsResolver: true,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type String does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitCommit_message(ctx context.Context, field graphql.CollectedField, obj *models.GitCommitMeta) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitCommit_message(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 obj.Message, 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.(string)
+	fc.Result = res
+	return ec.marshalNString2string(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitCommit_message(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitCommit",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type String does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitCommit_fullMessage(ctx context.Context, field graphql.CollectedField, obj *models.GitCommitMeta) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitCommit_fullMessage(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.GitCommit().FullMessage(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) fieldContext_GitCommit_fullMessage(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitCommit",
+		Field:      field,
+		IsMethod:   true,
+		IsResolver: true,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type String does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitCommit_authorName(ctx context.Context, field graphql.CollectedField, obj *models.GitCommitMeta) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitCommit_authorName(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 obj.AuthorName, 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.(string)
+	fc.Result = res
+	return ec.marshalNString2string(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitCommit_authorName(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitCommit",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type String does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitCommit_authorEmail(ctx context.Context, field graphql.CollectedField, obj *models.GitCommitMeta) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitCommit_authorEmail(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 obj.AuthorEmail, 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.(string)
+	fc.Result = res
+	return ec.marshalNString2string(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitCommit_authorEmail(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitCommit",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type String does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitCommit_date(ctx context.Context, field graphql.CollectedField, obj *models.GitCommitMeta) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitCommit_date(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 obj.Date, 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.(time.Time)
+	fc.Result = res
+	return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitCommit_date(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitCommit",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type Time does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitCommit_parents(ctx context.Context, field graphql.CollectedField, obj *models.GitCommitMeta) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitCommit_parents(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.GitCommit().Parents(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.marshalNString2αš•stringαš„(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitCommit_parents(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitCommit",
+		Field:      field,
+		IsMethod:   true,
+		IsResolver: true,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type String does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitCommit_files(ctx context.Context, field graphql.CollectedField, obj *models.GitCommitMeta) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitCommit_files(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.GitCommit().Files(rctx, obj, fc.Args["after"].(*string), fc.Args["before"].(*string), fc.Args["first"].(*int), fc.Args["last"].(*int))
+	})
+	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.GitChangedFileConnection)
+	fc.Result = res
+	return ec.marshalNGitChangedFileConnection2αš–githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹apiαš‹graphqlαš‹modelsᚐGitChangedFileConnection(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitCommit_files(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitCommit",
+		Field:      field,
+		IsMethod:   true,
+		IsResolver: true,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			switch field.Name {
+			case "nodes":
+				return ec.fieldContext_GitChangedFileConnection_nodes(ctx, field)
+			case "pageInfo":
+				return ec.fieldContext_GitChangedFileConnection_pageInfo(ctx, field)
+			case "totalCount":
+				return ec.fieldContext_GitChangedFileConnection_totalCount(ctx, field)
+			}
+			return nil, fmt.Errorf("no field named %q was found under type GitChangedFileConnection", field.Name)
+		},
+	}
+	defer func() {
+		if r := recover(); r != nil {
+			err = ec.Recover(ctx, r)
+			ec.Error(ctx, err)
+		}
+	}()
+	ctx = graphql.WithFieldContext(ctx, fc)
+	if fc.Args, err = ec.field_GitCommit_files_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
+		ec.Error(ctx, err)
+		return fc, err
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitCommit_diff(ctx context.Context, field graphql.CollectedField, obj *models.GitCommitMeta) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitCommit_diff(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.GitCommit().Diff(rctx, obj, fc.Args["path"].(string))
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*repository.FileDiff)
+	fc.Result = res
+	return ec.marshalOGitFileDiff2αš–githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹repositoryᚐFileDiff(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitCommit_diff(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitCommit",
+		Field:      field,
+		IsMethod:   true,
+		IsResolver: true,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			switch field.Name {
+			case "path":
+				return ec.fieldContext_GitFileDiff_path(ctx, field)
+			case "oldPath":
+				return ec.fieldContext_GitFileDiff_oldPath(ctx, field)
+			case "isBinary":
+				return ec.fieldContext_GitFileDiff_isBinary(ctx, field)
+			case "isNew":
+				return ec.fieldContext_GitFileDiff_isNew(ctx, field)
+			case "isDelete":
+				return ec.fieldContext_GitFileDiff_isDelete(ctx, field)
+			case "hunks":
+				return ec.fieldContext_GitFileDiff_hunks(ctx, field)
+			}
+			return nil, fmt.Errorf("no field named %q was found under type GitFileDiff", field.Name)
+		},
+	}
+	defer func() {
+		if r := recover(); r != nil {
+			err = ec.Recover(ctx, r)
+			ec.Error(ctx, err)
+		}
+	}()
+	ctx = graphql.WithFieldContext(ctx, fc)
+	if fc.Args, err = ec.field_GitCommit_diff_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
+		ec.Error(ctx, err)
+		return fc, err
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitCommitConnection_nodes(ctx context.Context, field graphql.CollectedField, obj *models.GitCommitConnection) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitCommitConnection_nodes(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 obj.Nodes, 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.([]*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_GitCommitConnection_nodes(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitCommitConnection",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		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) _GitCommitConnection_pageInfo(ctx context.Context, field graphql.CollectedField, obj *models.GitCommitConnection) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitCommitConnection_pageInfo(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 obj.PageInfo, 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.(*models.PageInfo)
+	fc.Result = res
+	return ec.marshalNPageInfo2αš–githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹apiαš‹graphqlαš‹modelsᚐPageInfo(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitCommitConnection_pageInfo(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitCommitConnection",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			switch field.Name {
+			case "hasNextPage":
+				return ec.fieldContext_PageInfo_hasNextPage(ctx, field)
+			case "hasPreviousPage":
+				return ec.fieldContext_PageInfo_hasPreviousPage(ctx, field)
+			case "startCursor":
+				return ec.fieldContext_PageInfo_startCursor(ctx, field)
+			case "endCursor":
+				return ec.fieldContext_PageInfo_endCursor(ctx, field)
+			}
+			return nil, fmt.Errorf("no field named %q was found under type PageInfo", field.Name)
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitCommitConnection_totalCount(ctx context.Context, field graphql.CollectedField, obj *models.GitCommitConnection) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitCommitConnection_totalCount(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 obj.TotalCount, 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.(int)
+	fc.Result = res
+	return ec.marshalNInt2int(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitCommitConnection_totalCount(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitCommitConnection",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type Int does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitDiffHunk_oldStart(ctx context.Context, field graphql.CollectedField, obj *repository.DiffHunk) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitDiffHunk_oldStart(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 obj.OldStart, 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.(int)
+	fc.Result = res
+	return ec.marshalNInt2int(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitDiffHunk_oldStart(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitDiffHunk",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type Int does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitDiffHunk_oldLines(ctx context.Context, field graphql.CollectedField, obj *repository.DiffHunk) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitDiffHunk_oldLines(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 obj.OldLines, 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.(int)
+	fc.Result = res
+	return ec.marshalNInt2int(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitDiffHunk_oldLines(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitDiffHunk",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type Int does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitDiffHunk_newStart(ctx context.Context, field graphql.CollectedField, obj *repository.DiffHunk) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitDiffHunk_newStart(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 obj.NewStart, 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.(int)
+	fc.Result = res
+	return ec.marshalNInt2int(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitDiffHunk_newStart(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitDiffHunk",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type Int does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitDiffHunk_newLines(ctx context.Context, field graphql.CollectedField, obj *repository.DiffHunk) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitDiffHunk_newLines(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 obj.NewLines, 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.(int)
+	fc.Result = res
+	return ec.marshalNInt2int(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitDiffHunk_newLines(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitDiffHunk",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type Int does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitDiffHunk_lines(ctx context.Context, field graphql.CollectedField, obj *repository.DiffHunk) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitDiffHunk_lines(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 obj.Lines, 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.([]repository.DiffLine)
+	fc.Result = res
+	return ec.marshalNGitDiffLine2αš•githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹repositoryᚐDiffLineαš„(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitDiffHunk_lines(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitDiffHunk",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			switch field.Name {
+			case "type":
+				return ec.fieldContext_GitDiffLine_type(ctx, field)
+			case "content":
+				return ec.fieldContext_GitDiffLine_content(ctx, field)
+			case "oldLine":
+				return ec.fieldContext_GitDiffLine_oldLine(ctx, field)
+			case "newLine":
+				return ec.fieldContext_GitDiffLine_newLine(ctx, field)
+			}
+			return nil, fmt.Errorf("no field named %q was found under type GitDiffLine", field.Name)
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitDiffLine_type(ctx context.Context, field graphql.CollectedField, obj *repository.DiffLine) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitDiffLine_type(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 obj.Type, 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.(repository.DiffLineType)
+	fc.Result = res
+	return ec.marshalNGitDiffLineType2githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹repositoryᚐDiffLineType(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitDiffLine_type(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitDiffLine",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type GitDiffLineType does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitDiffLine_content(ctx context.Context, field graphql.CollectedField, obj *repository.DiffLine) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitDiffLine_content(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 obj.Content, 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.(string)
+	fc.Result = res
+	return ec.marshalNString2string(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitDiffLine_content(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitDiffLine",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type String does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitDiffLine_oldLine(ctx context.Context, field graphql.CollectedField, obj *repository.DiffLine) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitDiffLine_oldLine(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 obj.OldLine, 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.(int)
+	fc.Result = res
+	return ec.marshalNInt2int(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitDiffLine_oldLine(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitDiffLine",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type Int does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitDiffLine_newLine(ctx context.Context, field graphql.CollectedField, obj *repository.DiffLine) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitDiffLine_newLine(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 obj.NewLine, 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.(int)
+	fc.Result = res
+	return ec.marshalNInt2int(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitDiffLine_newLine(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitDiffLine",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type Int does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitFileDiff_path(ctx context.Context, field graphql.CollectedField, obj *repository.FileDiff) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitFileDiff_path(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 obj.Path, 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.(string)
+	fc.Result = res
+	return ec.marshalNString2string(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitFileDiff_path(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitFileDiff",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type String does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitFileDiff_oldPath(ctx context.Context, field graphql.CollectedField, obj *repository.FileDiff) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitFileDiff_oldPath(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 obj.OldPath, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*string)
+	fc.Result = res
+	return ec.marshalOString2αš–string(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitFileDiff_oldPath(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitFileDiff",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type String does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitFileDiff_isBinary(ctx context.Context, field graphql.CollectedField, obj *repository.FileDiff) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitFileDiff_isBinary(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 obj.IsBinary, 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.(bool)
+	fc.Result = res
+	return ec.marshalNBoolean2bool(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitFileDiff_isBinary(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitFileDiff",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type Boolean does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitFileDiff_isNew(ctx context.Context, field graphql.CollectedField, obj *repository.FileDiff) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitFileDiff_isNew(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 obj.IsNew, 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.(bool)
+	fc.Result = res
+	return ec.marshalNBoolean2bool(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitFileDiff_isNew(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitFileDiff",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type Boolean does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitFileDiff_isDelete(ctx context.Context, field graphql.CollectedField, obj *repository.FileDiff) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitFileDiff_isDelete(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 obj.IsDelete, 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.(bool)
+	fc.Result = res
+	return ec.marshalNBoolean2bool(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitFileDiff_isDelete(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitFileDiff",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type Boolean does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitFileDiff_hunks(ctx context.Context, field graphql.CollectedField, obj *repository.FileDiff) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitFileDiff_hunks(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 obj.Hunks, 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.([]repository.DiffHunk)
+	fc.Result = res
+	return ec.marshalNGitDiffHunk2αš•githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹repositoryᚐDiffHunkαš„(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitFileDiff_hunks(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitFileDiff",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			switch field.Name {
+			case "oldStart":
+				return ec.fieldContext_GitDiffHunk_oldStart(ctx, field)
+			case "oldLines":
+				return ec.fieldContext_GitDiffHunk_oldLines(ctx, field)
+			case "newStart":
+				return ec.fieldContext_GitDiffHunk_newStart(ctx, field)
+			case "newLines":
+				return ec.fieldContext_GitDiffHunk_newLines(ctx, field)
+			case "lines":
+				return ec.fieldContext_GitDiffHunk_lines(ctx, field)
+			}
+			return nil, fmt.Errorf("no field named %q was found under type GitDiffHunk", field.Name)
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitLastCommit_name(ctx context.Context, field graphql.CollectedField, obj *models.GitLastCommit) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitLastCommit_name(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 obj.Name, 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.(string)
+	fc.Result = res
+	return ec.marshalNString2string(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitLastCommit_name(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitLastCommit",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type String does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitLastCommit_commit(ctx context.Context, field graphql.CollectedField, obj *models.GitLastCommit) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitLastCommit_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 obj.Commit, 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.(*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_GitLastCommit_commit(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitLastCommit",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		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) _GitRef_name(ctx context.Context, field graphql.CollectedField, obj *models.GitRef) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitRef_name(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 obj.Name, 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.(string)
+	fc.Result = res
+	return ec.marshalNString2string(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitRef_name(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitRef",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type String does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitRef_shortName(ctx context.Context, field graphql.CollectedField, obj *models.GitRef) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitRef_shortName(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 obj.ShortName, 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.(string)
+	fc.Result = res
+	return ec.marshalNString2string(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitRef_shortName(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitRef",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type String does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitRef_type(ctx context.Context, field graphql.CollectedField, obj *models.GitRef) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitRef_type(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 obj.Type, 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.(models.GitRefType)
+	fc.Result = res
+	return ec.marshalNGitRefType2githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹apiαš‹graphqlαš‹modelsᚐGitRefType(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitRef_type(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitRef",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type GitRefType does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitRef_hash(ctx context.Context, field graphql.CollectedField, obj *models.GitRef) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitRef_hash(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 obj.Hash, 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.(string)
+	fc.Result = res
+	return ec.marshalNString2string(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitRef_hash(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitRef",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type String does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitRef_isDefault(ctx context.Context, field graphql.CollectedField, obj *models.GitRef) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitRef_isDefault(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 obj.IsDefault, 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.(bool)
+	fc.Result = res
+	return ec.marshalNBoolean2bool(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitRef_isDefault(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitRef",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type Boolean does not have child fields")
+		},
+	}
+	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 {
+		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 obj.Nodes, 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.([]*models.GitRef)
+	fc.Result = res
+	return ec.marshalNGitRef2αš•αš–githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹apiαš‹graphqlαš‹modelsᚐGitRefαš„(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitRefConnection_nodes(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitRefConnection",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		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_GitRef_hash(ctx, field)
+			case "isDefault":
+				return ec.fieldContext_GitRef_isDefault(ctx, field)
+			}
+			return nil, fmt.Errorf("no field named %q was found under type GitRef", field.Name)
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitRefConnection_pageInfo(ctx context.Context, field graphql.CollectedField, obj *models.GitRefConnection) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitRefConnection_pageInfo(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 obj.PageInfo, 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.(*models.PageInfo)
+	fc.Result = res
+	return ec.marshalNPageInfo2αš–githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹apiαš‹graphqlαš‹modelsᚐPageInfo(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitRefConnection_pageInfo(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitRefConnection",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			switch field.Name {
+			case "hasNextPage":
+				return ec.fieldContext_PageInfo_hasNextPage(ctx, field)
+			case "hasPreviousPage":
+				return ec.fieldContext_PageInfo_hasPreviousPage(ctx, field)
+			case "startCursor":
+				return ec.fieldContext_PageInfo_startCursor(ctx, field)
+			case "endCursor":
+				return ec.fieldContext_PageInfo_endCursor(ctx, field)
+			}
+			return nil, fmt.Errorf("no field named %q was found under type PageInfo", field.Name)
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitRefConnection_totalCount(ctx context.Context, field graphql.CollectedField, obj *models.GitRefConnection) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitRefConnection_totalCount(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 obj.TotalCount, 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.(int)
+	fc.Result = res
+	return ec.marshalNInt2int(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitRefConnection_totalCount(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitRefConnection",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type Int does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitTreeEntry_name(ctx context.Context, field graphql.CollectedField, obj *repository.TreeEntry) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitTreeEntry_name(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 obj.Name, 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.(string)
+	fc.Result = res
+	return ec.marshalNString2string(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitTreeEntry_name(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitTreeEntry",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type String does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitTreeEntry_type(ctx context.Context, field graphql.CollectedField, obj *repository.TreeEntry) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitTreeEntry_type(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 obj.ObjectType, 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.(repository.ObjectType)
+	fc.Result = res
+	return ec.marshalNGitObjectType2githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹repositoryᚐObjectType(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitTreeEntry_type(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitTreeEntry",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type GitObjectType does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _GitTreeEntry_hash(ctx context.Context, field graphql.CollectedField, obj *repository.TreeEntry) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_GitTreeEntry_hash(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 obj.Hash, 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.(repository.Hash)
+	fc.Result = res
+	return ec.marshalNString2githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹repositoryᚐHash(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_GitTreeEntry_hash(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "GitTreeEntry",
+		Field:      field,
+		IsMethod:   false,
+		IsResolver: false,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type String does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+// endregion **************************** field.gotpl *****************************
+
+// region    **************************** input.gotpl *****************************
+
+// endregion **************************** input.gotpl *****************************
+
+// region    ************************** interface.gotpl ***************************
+
+// endregion ************************** interface.gotpl ***************************
+
+// region    **************************** object.gotpl ****************************
+
+var gitBlobImplementors = []string{"GitBlob"}
+
+func (ec *executionContext) _GitBlob(ctx context.Context, sel ast.SelectionSet, obj *models.GitBlob) graphql.Marshaler {
+	fields := graphql.CollectFields(ec.OperationContext, sel, gitBlobImplementors)
+
+	out := graphql.NewFieldSet(fields)
+	deferred := make(map[string]*graphql.FieldSet)
+	for i, field := range fields {
+		switch field.Name {
+		case "__typename":
+			out.Values[i] = graphql.MarshalString("GitBlob")
+		case "path":
+			out.Values[i] = ec._GitBlob_path(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		case "hash":
+			out.Values[i] = ec._GitBlob_hash(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		case "text":
+			out.Values[i] = ec._GitBlob_text(ctx, field, obj)
+		case "size":
+			out.Values[i] = ec._GitBlob_size(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		case "isBinary":
+			out.Values[i] = ec._GitBlob_isBinary(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		case "isTruncated":
+			out.Values[i] = ec._GitBlob_isTruncated(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		default:
+			panic("unknown field " + strconv.Quote(field.Name))
+		}
+	}
+	out.Dispatch(ctx)
+	if out.Invalids > 0 {
+		return graphql.Null
+	}
+
+	atomic.AddInt32(&ec.deferred, int32(len(deferred)))
+
+	for label, dfs := range deferred {
+		ec.processDeferredGroup(graphql.DeferredGroup{
+			Label:    label,
+			Path:     graphql.GetPath(ctx),
+			FieldSet: dfs,
+			Context:  ctx,
+		})
+	}
+
+	return out
+}
+
+var gitChangedFileImplementors = []string{"GitChangedFile"}
+
+func (ec *executionContext) _GitChangedFile(ctx context.Context, sel ast.SelectionSet, obj *repository.ChangedFile) graphql.Marshaler {
+	fields := graphql.CollectFields(ec.OperationContext, sel, gitChangedFileImplementors)
+
+	out := graphql.NewFieldSet(fields)
+	deferred := make(map[string]*graphql.FieldSet)
+	for i, field := range fields {
+		switch field.Name {
+		case "__typename":
+			out.Values[i] = graphql.MarshalString("GitChangedFile")
+		case "path":
+			out.Values[i] = ec._GitChangedFile_path(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		case "oldPath":
+			out.Values[i] = ec._GitChangedFile_oldPath(ctx, field, obj)
+		case "status":
+			out.Values[i] = ec._GitChangedFile_status(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		default:
+			panic("unknown field " + strconv.Quote(field.Name))
+		}
+	}
+	out.Dispatch(ctx)
+	if out.Invalids > 0 {
+		return graphql.Null
+	}
+
+	atomic.AddInt32(&ec.deferred, int32(len(deferred)))
+
+	for label, dfs := range deferred {
+		ec.processDeferredGroup(graphql.DeferredGroup{
+			Label:    label,
+			Path:     graphql.GetPath(ctx),
+			FieldSet: dfs,
+			Context:  ctx,
+		})
+	}
+
+	return out
+}
+
+var gitChangedFileConnectionImplementors = []string{"GitChangedFileConnection"}
+
+func (ec *executionContext) _GitChangedFileConnection(ctx context.Context, sel ast.SelectionSet, obj *models.GitChangedFileConnection) graphql.Marshaler {
+	fields := graphql.CollectFields(ec.OperationContext, sel, gitChangedFileConnectionImplementors)
+
+	out := graphql.NewFieldSet(fields)
+	deferred := make(map[string]*graphql.FieldSet)
+	for i, field := range fields {
+		switch field.Name {
+		case "__typename":
+			out.Values[i] = graphql.MarshalString("GitChangedFileConnection")
+		case "nodes":
+			out.Values[i] = ec._GitChangedFileConnection_nodes(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		case "pageInfo":
+			out.Values[i] = ec._GitChangedFileConnection_pageInfo(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		case "totalCount":
+			out.Values[i] = ec._GitChangedFileConnection_totalCount(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		default:
+			panic("unknown field " + strconv.Quote(field.Name))
+		}
+	}
+	out.Dispatch(ctx)
+	if out.Invalids > 0 {
+		return graphql.Null
+	}
+
+	atomic.AddInt32(&ec.deferred, int32(len(deferred)))
+
+	for label, dfs := range deferred {
+		ec.processDeferredGroup(graphql.DeferredGroup{
+			Label:    label,
+			Path:     graphql.GetPath(ctx),
+			FieldSet: dfs,
+			Context:  ctx,
+		})
+	}
+
+	return out
+}
+
+var gitCommitImplementors = []string{"GitCommit"}
+
+func (ec *executionContext) _GitCommit(ctx context.Context, sel ast.SelectionSet, obj *models.GitCommitMeta) graphql.Marshaler {
+	fields := graphql.CollectFields(ec.OperationContext, sel, gitCommitImplementors)
+
+	out := graphql.NewFieldSet(fields)
+	deferred := make(map[string]*graphql.FieldSet)
+	for i, field := range fields {
+		switch field.Name {
+		case "__typename":
+			out.Values[i] = graphql.MarshalString("GitCommit")
+		case "hash":
+			out.Values[i] = ec._GitCommit_hash(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				atomic.AddUint32(&out.Invalids, 1)
+			}
+		case "shortHash":
+			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._GitCommit_shortHash(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) })
+		case "message":
+			out.Values[i] = ec._GitCommit_message(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				atomic.AddUint32(&out.Invalids, 1)
+			}
+		case "fullMessage":
+			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._GitCommit_fullMessage(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) })
+		case "authorName":
+			out.Values[i] = ec._GitCommit_authorName(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				atomic.AddUint32(&out.Invalids, 1)
+			}
+		case "authorEmail":
+			out.Values[i] = ec._GitCommit_authorEmail(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				atomic.AddUint32(&out.Invalids, 1)
+			}
+		case "date":
+			out.Values[i] = ec._GitCommit_date(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				atomic.AddUint32(&out.Invalids, 1)
+			}
+		case "parents":
+			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._GitCommit_parents(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) })
+		case "files":
+			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._GitCommit_files(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) })
+		case "diff":
+			field := field
+
+			innerFunc := func(ctx context.Context, _ *graphql.FieldSet) (res graphql.Marshaler) {
+				defer func() {
+					if r := recover(); r != nil {
+						ec.Error(ctx, ec.Recover(ctx, r))
+					}
+				}()
+				res = ec._GitCommit_diff(ctx, field, obj)
+				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))
+		}
+	}
+	out.Dispatch(ctx)
+	if out.Invalids > 0 {
+		return graphql.Null
+	}
+
+	atomic.AddInt32(&ec.deferred, int32(len(deferred)))
+
+	for label, dfs := range deferred {
+		ec.processDeferredGroup(graphql.DeferredGroup{
+			Label:    label,
+			Path:     graphql.GetPath(ctx),
+			FieldSet: dfs,
+			Context:  ctx,
+		})
+	}
+
+	return out
+}
+
+var gitCommitConnectionImplementors = []string{"GitCommitConnection"}
+
+func (ec *executionContext) _GitCommitConnection(ctx context.Context, sel ast.SelectionSet, obj *models.GitCommitConnection) graphql.Marshaler {
+	fields := graphql.CollectFields(ec.OperationContext, sel, gitCommitConnectionImplementors)
+
+	out := graphql.NewFieldSet(fields)
+	deferred := make(map[string]*graphql.FieldSet)
+	for i, field := range fields {
+		switch field.Name {
+		case "__typename":
+			out.Values[i] = graphql.MarshalString("GitCommitConnection")
+		case "nodes":
+			out.Values[i] = ec._GitCommitConnection_nodes(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		case "pageInfo":
+			out.Values[i] = ec._GitCommitConnection_pageInfo(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		case "totalCount":
+			out.Values[i] = ec._GitCommitConnection_totalCount(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		default:
+			panic("unknown field " + strconv.Quote(field.Name))
+		}
+	}
+	out.Dispatch(ctx)
+	if out.Invalids > 0 {
+		return graphql.Null
+	}
+
+	atomic.AddInt32(&ec.deferred, int32(len(deferred)))
+
+	for label, dfs := range deferred {
+		ec.processDeferredGroup(graphql.DeferredGroup{
+			Label:    label,
+			Path:     graphql.GetPath(ctx),
+			FieldSet: dfs,
+			Context:  ctx,
+		})
+	}
+
+	return out
+}
+
+var gitDiffHunkImplementors = []string{"GitDiffHunk"}
+
+func (ec *executionContext) _GitDiffHunk(ctx context.Context, sel ast.SelectionSet, obj *repository.DiffHunk) graphql.Marshaler {
+	fields := graphql.CollectFields(ec.OperationContext, sel, gitDiffHunkImplementors)
+
+	out := graphql.NewFieldSet(fields)
+	deferred := make(map[string]*graphql.FieldSet)
+	for i, field := range fields {
+		switch field.Name {
+		case "__typename":
+			out.Values[i] = graphql.MarshalString("GitDiffHunk")
+		case "oldStart":
+			out.Values[i] = ec._GitDiffHunk_oldStart(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		case "oldLines":
+			out.Values[i] = ec._GitDiffHunk_oldLines(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		case "newStart":
+			out.Values[i] = ec._GitDiffHunk_newStart(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		case "newLines":
+			out.Values[i] = ec._GitDiffHunk_newLines(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		case "lines":
+			out.Values[i] = ec._GitDiffHunk_lines(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		default:
+			panic("unknown field " + strconv.Quote(field.Name))
+		}
+	}
+	out.Dispatch(ctx)
+	if out.Invalids > 0 {
+		return graphql.Null
+	}
+
+	atomic.AddInt32(&ec.deferred, int32(len(deferred)))
+
+	for label, dfs := range deferred {
+		ec.processDeferredGroup(graphql.DeferredGroup{
+			Label:    label,
+			Path:     graphql.GetPath(ctx),
+			FieldSet: dfs,
+			Context:  ctx,
+		})
+	}
+
+	return out
+}
+
+var gitDiffLineImplementors = []string{"GitDiffLine"}
+
+func (ec *executionContext) _GitDiffLine(ctx context.Context, sel ast.SelectionSet, obj *repository.DiffLine) graphql.Marshaler {
+	fields := graphql.CollectFields(ec.OperationContext, sel, gitDiffLineImplementors)
+
+	out := graphql.NewFieldSet(fields)
+	deferred := make(map[string]*graphql.FieldSet)
+	for i, field := range fields {
+		switch field.Name {
+		case "__typename":
+			out.Values[i] = graphql.MarshalString("GitDiffLine")
+		case "type":
+			out.Values[i] = ec._GitDiffLine_type(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		case "content":
+			out.Values[i] = ec._GitDiffLine_content(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		case "oldLine":
+			out.Values[i] = ec._GitDiffLine_oldLine(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		case "newLine":
+			out.Values[i] = ec._GitDiffLine_newLine(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		default:
+			panic("unknown field " + strconv.Quote(field.Name))
+		}
+	}
+	out.Dispatch(ctx)
+	if out.Invalids > 0 {
+		return graphql.Null
+	}
+
+	atomic.AddInt32(&ec.deferred, int32(len(deferred)))
+
+	for label, dfs := range deferred {
+		ec.processDeferredGroup(graphql.DeferredGroup{
+			Label:    label,
+			Path:     graphql.GetPath(ctx),
+			FieldSet: dfs,
+			Context:  ctx,
+		})
+	}
+
+	return out
+}
+
+var gitFileDiffImplementors = []string{"GitFileDiff"}
+
+func (ec *executionContext) _GitFileDiff(ctx context.Context, sel ast.SelectionSet, obj *repository.FileDiff) graphql.Marshaler {
+	fields := graphql.CollectFields(ec.OperationContext, sel, gitFileDiffImplementors)
+
+	out := graphql.NewFieldSet(fields)
+	deferred := make(map[string]*graphql.FieldSet)
+	for i, field := range fields {
+		switch field.Name {
+		case "__typename":
+			out.Values[i] = graphql.MarshalString("GitFileDiff")
+		case "path":
+			out.Values[i] = ec._GitFileDiff_path(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		case "oldPath":
+			out.Values[i] = ec._GitFileDiff_oldPath(ctx, field, obj)
+		case "isBinary":
+			out.Values[i] = ec._GitFileDiff_isBinary(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		case "isNew":
+			out.Values[i] = ec._GitFileDiff_isNew(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		case "isDelete":
+			out.Values[i] = ec._GitFileDiff_isDelete(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		case "hunks":
+			out.Values[i] = ec._GitFileDiff_hunks(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		default:
+			panic("unknown field " + strconv.Quote(field.Name))
+		}
+	}
+	out.Dispatch(ctx)
+	if out.Invalids > 0 {
+		return graphql.Null
+	}
+
+	atomic.AddInt32(&ec.deferred, int32(len(deferred)))
+
+	for label, dfs := range deferred {
+		ec.processDeferredGroup(graphql.DeferredGroup{
+			Label:    label,
+			Path:     graphql.GetPath(ctx),
+			FieldSet: dfs,
+			Context:  ctx,
+		})
+	}
+
+	return out
+}
+
+var gitLastCommitImplementors = []string{"GitLastCommit"}
+
+func (ec *executionContext) _GitLastCommit(ctx context.Context, sel ast.SelectionSet, obj *models.GitLastCommit) graphql.Marshaler {
+	fields := graphql.CollectFields(ec.OperationContext, sel, gitLastCommitImplementors)
+
+	out := graphql.NewFieldSet(fields)
+	deferred := make(map[string]*graphql.FieldSet)
+	for i, field := range fields {
+		switch field.Name {
+		case "__typename":
+			out.Values[i] = graphql.MarshalString("GitLastCommit")
+		case "name":
+			out.Values[i] = ec._GitLastCommit_name(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		case "commit":
+			out.Values[i] = ec._GitLastCommit_commit(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		default:
+			panic("unknown field " + strconv.Quote(field.Name))
+		}
+	}
+	out.Dispatch(ctx)
+	if out.Invalids > 0 {
+		return graphql.Null
+	}
+
+	atomic.AddInt32(&ec.deferred, int32(len(deferred)))
+
+	for label, dfs := range deferred {
+		ec.processDeferredGroup(graphql.DeferredGroup{
+			Label:    label,
+			Path:     graphql.GetPath(ctx),
+			FieldSet: dfs,
+			Context:  ctx,
+		})
+	}
+
+	return out
+}
+
+var gitRefImplementors = []string{"GitRef"}
+
+func (ec *executionContext) _GitRef(ctx context.Context, sel ast.SelectionSet, obj *models.GitRef) graphql.Marshaler {
+	fields := graphql.CollectFields(ec.OperationContext, sel, gitRefImplementors)
+
+	out := graphql.NewFieldSet(fields)
+	deferred := make(map[string]*graphql.FieldSet)
+	for i, field := range fields {
+		switch field.Name {
+		case "__typename":
+			out.Values[i] = graphql.MarshalString("GitRef")
+		case "name":
+			out.Values[i] = ec._GitRef_name(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		case "shortName":
+			out.Values[i] = ec._GitRef_shortName(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		case "type":
+			out.Values[i] = ec._GitRef_type(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		case "hash":
+			out.Values[i] = ec._GitRef_hash(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		case "isDefault":
+			out.Values[i] = ec._GitRef_isDefault(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		default:
+			panic("unknown field " + strconv.Quote(field.Name))
+		}
+	}
+	out.Dispatch(ctx)
+	if out.Invalids > 0 {
+		return graphql.Null
+	}
+
+	atomic.AddInt32(&ec.deferred, int32(len(deferred)))
+
+	for label, dfs := range deferred {
+		ec.processDeferredGroup(graphql.DeferredGroup{
+			Label:    label,
+			Path:     graphql.GetPath(ctx),
+			FieldSet: dfs,
+			Context:  ctx,
+		})
+	}
+
+	return out
+}
+
+var gitRefConnectionImplementors = []string{"GitRefConnection"}
+
+func (ec *executionContext) _GitRefConnection(ctx context.Context, sel ast.SelectionSet, obj *models.GitRefConnection) graphql.Marshaler {
+	fields := graphql.CollectFields(ec.OperationContext, sel, gitRefConnectionImplementors)
+
+	out := graphql.NewFieldSet(fields)
+	deferred := make(map[string]*graphql.FieldSet)
+	for i, field := range fields {
+		switch field.Name {
+		case "__typename":
+			out.Values[i] = graphql.MarshalString("GitRefConnection")
+		case "nodes":
+			out.Values[i] = ec._GitRefConnection_nodes(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		case "pageInfo":
+			out.Values[i] = ec._GitRefConnection_pageInfo(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		case "totalCount":
+			out.Values[i] = ec._GitRefConnection_totalCount(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		default:
+			panic("unknown field " + strconv.Quote(field.Name))
+		}
+	}
+	out.Dispatch(ctx)
+	if out.Invalids > 0 {
+		return graphql.Null
+	}
+
+	atomic.AddInt32(&ec.deferred, int32(len(deferred)))
+
+	for label, dfs := range deferred {
+		ec.processDeferredGroup(graphql.DeferredGroup{
+			Label:    label,
+			Path:     graphql.GetPath(ctx),
+			FieldSet: dfs,
+			Context:  ctx,
+		})
+	}
+
+	return out
+}
+
+var gitTreeEntryImplementors = []string{"GitTreeEntry"}
+
+func (ec *executionContext) _GitTreeEntry(ctx context.Context, sel ast.SelectionSet, obj *repository.TreeEntry) graphql.Marshaler {
+	fields := graphql.CollectFields(ec.OperationContext, sel, gitTreeEntryImplementors)
+
+	out := graphql.NewFieldSet(fields)
+	deferred := make(map[string]*graphql.FieldSet)
+	for i, field := range fields {
+		switch field.Name {
+		case "__typename":
+			out.Values[i] = graphql.MarshalString("GitTreeEntry")
+		case "name":
+			out.Values[i] = ec._GitTreeEntry_name(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		case "type":
+			out.Values[i] = ec._GitTreeEntry_type(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		case "hash":
+			out.Values[i] = ec._GitTreeEntry_hash(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				out.Invalids++
+			}
+		default:
+			panic("unknown field " + strconv.Quote(field.Name))
+		}
+	}
+	out.Dispatch(ctx)
+	if out.Invalids > 0 {
+		return graphql.Null
+	}
+
+	atomic.AddInt32(&ec.deferred, int32(len(deferred)))
+
+	for label, dfs := range deferred {
+		ec.processDeferredGroup(graphql.DeferredGroup{
+			Label:    label,
+			Path:     graphql.GetPath(ctx),
+			FieldSet: dfs,
+			Context:  ctx,
+		})
+	}
+
+	return out
+}
+
+// endregion **************************** object.gotpl ****************************
+
+// region    ***************************** type.gotpl *****************************
+
+func (ec *executionContext) unmarshalNGitChangeStatus2githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹repositoryᚐChangeStatus(ctx context.Context, v any) (repository.ChangeStatus, error) {
+	var res repository.ChangeStatus
+	err := res.UnmarshalGQL(v)
+	return res, graphql.ErrorOnPath(ctx, err)
+}
+
+func (ec *executionContext) marshalNGitChangeStatus2githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹repositoryᚐChangeStatus(ctx context.Context, sel ast.SelectionSet, v repository.ChangeStatus) graphql.Marshaler {
+	return v
+}
+
+func (ec *executionContext) marshalNGitChangedFile2αš•αš–githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹repositoryᚐChangedFileαš„(ctx context.Context, sel ast.SelectionSet, v []*repository.ChangedFile) graphql.Marshaler {
+	ret := make(graphql.Array, len(v))
+	var wg sync.WaitGroup
+	isLen1 := len(v) == 1
+	if !isLen1 {
+		wg.Add(len(v))
+	}
+	for i := range v {
+		i := i
+		fc := &graphql.FieldContext{
+			Index:  &i,
+			Result: &v[i],
+		}
+		ctx := graphql.WithFieldContext(ctx, fc)
+		f := func(i int) {
+			defer func() {
+				if r := recover(); r != nil {
+					ec.Error(ctx, ec.Recover(ctx, r))
+					ret = nil
+				}
+			}()
+			if !isLen1 {
+				defer wg.Done()
+			}
+			ret[i] = ec.marshalNGitChangedFile2αš–githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹repositoryᚐChangedFile(ctx, sel, v[i])
+		}
+		if isLen1 {
+			f(i)
+		} else {
+			go f(i)
+		}
+
+	}
+	wg.Wait()
+
+	for _, e := range ret {
+		if e == graphql.Null {
+			return graphql.Null
+		}
+	}
+
+	return ret
+}
+
+func (ec *executionContext) marshalNGitChangedFile2αš–githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹repositoryᚐChangedFile(ctx context.Context, sel ast.SelectionSet, v *repository.ChangedFile) graphql.Marshaler {
+	if v == nil {
+		if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
+			ec.Errorf(ctx, "the requested element is null which the schema does not allow")
+		}
+		return graphql.Null
+	}
+	return ec._GitChangedFile(ctx, sel, v)
+}
+
+func (ec *executionContext) marshalNGitChangedFileConnection2githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹apiαš‹graphqlαš‹modelsᚐGitChangedFileConnection(ctx context.Context, sel ast.SelectionSet, v models.GitChangedFileConnection) graphql.Marshaler {
+	return ec._GitChangedFileConnection(ctx, sel, &v)
+}
+
+func (ec *executionContext) marshalNGitChangedFileConnection2αš–githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹apiαš‹graphqlαš‹modelsᚐGitChangedFileConnection(ctx context.Context, sel ast.SelectionSet, v *models.GitChangedFileConnection) graphql.Marshaler {
+	if v == nil {
+		if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
+			ec.Errorf(ctx, "the requested element is null which the schema does not allow")
+		}
+		return graphql.Null
+	}
+	return ec._GitChangedFileConnection(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
+	isLen1 := len(v) == 1
+	if !isLen1 {
+		wg.Add(len(v))
+	}
+	for i := range v {
+		i := i
+		fc := &graphql.FieldContext{
+			Index:  &i,
+			Result: &v[i],
+		}
+		ctx := graphql.WithFieldContext(ctx, fc)
+		f := func(i int) {
+			defer func() {
+				if r := recover(); r != nil {
+					ec.Error(ctx, ec.Recover(ctx, r))
+					ret = nil
+				}
+			}()
+			if !isLen1 {
+				defer wg.Done()
+			}
+			ret[i] = ec.marshalNGitCommit2αš–githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹apiαš‹graphqlαš‹modelsᚐGitCommitMeta(ctx, sel, v[i])
+		}
+		if isLen1 {
+			f(i)
+		} else {
+			go f(i)
+		}
+
+	}
+	wg.Wait()
+
+	for _, e := range ret {
+		if e == graphql.Null {
+			return graphql.Null
+		}
+	}
+
+	return ret
+}
+
+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 {
+	if v == nil {
+		if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
+			ec.Errorf(ctx, "the requested element is null which the schema does not allow")
+		}
+		return graphql.Null
+	}
+	return ec._GitCommit(ctx, sel, v)
+}
+
+func (ec *executionContext) marshalNGitCommitConnection2githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹apiαš‹graphqlαš‹modelsᚐGitCommitConnection(ctx context.Context, sel ast.SelectionSet, v models.GitCommitConnection) graphql.Marshaler {
+	return ec._GitCommitConnection(ctx, sel, &v)
+}
+
+func (ec *executionContext) marshalNGitCommitConnection2αš–githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹apiαš‹graphqlαš‹modelsᚐGitCommitConnection(ctx context.Context, sel ast.SelectionSet, v *models.GitCommitConnection) graphql.Marshaler {
+	if v == nil {
+		if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
+			ec.Errorf(ctx, "the requested element is null which the schema does not allow")
+		}
+		return graphql.Null
+	}
+	return ec._GitCommitConnection(ctx, sel, v)
+}
+
+func (ec *executionContext) marshalNGitDiffHunk2githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹repositoryᚐDiffHunk(ctx context.Context, sel ast.SelectionSet, v repository.DiffHunk) graphql.Marshaler {
+	return ec._GitDiffHunk(ctx, sel, &v)
+}
+
+func (ec *executionContext) marshalNGitDiffHunk2αš•githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹repositoryᚐDiffHunkαš„(ctx context.Context, sel ast.SelectionSet, v []repository.DiffHunk) graphql.Marshaler {
+	ret := make(graphql.Array, len(v))
+	var wg sync.WaitGroup
+	isLen1 := len(v) == 1
+	if !isLen1 {
+		wg.Add(len(v))
+	}
+	for i := range v {
+		i := i
+		fc := &graphql.FieldContext{
+			Index:  &i,
+			Result: &v[i],
+		}
+		ctx := graphql.WithFieldContext(ctx, fc)
+		f := func(i int) {
+			defer func() {
+				if r := recover(); r != nil {
+					ec.Error(ctx, ec.Recover(ctx, r))
+					ret = nil
+				}
+			}()
+			if !isLen1 {
+				defer wg.Done()
+			}
+			ret[i] = ec.marshalNGitDiffHunk2githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹repositoryᚐDiffHunk(ctx, sel, v[i])
+		}
+		if isLen1 {
+			f(i)
+		} else {
+			go f(i)
+		}
+
+	}
+	wg.Wait()
+
+	for _, e := range ret {
+		if e == graphql.Null {
+			return graphql.Null
+		}
+	}
+
+	return ret
+}
+
+func (ec *executionContext) marshalNGitDiffLine2githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹repositoryᚐDiffLine(ctx context.Context, sel ast.SelectionSet, v repository.DiffLine) graphql.Marshaler {
+	return ec._GitDiffLine(ctx, sel, &v)
+}
+
+func (ec *executionContext) marshalNGitDiffLine2αš•githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹repositoryᚐDiffLineαš„(ctx context.Context, sel ast.SelectionSet, v []repository.DiffLine) graphql.Marshaler {
+	ret := make(graphql.Array, len(v))
+	var wg sync.WaitGroup
+	isLen1 := len(v) == 1
+	if !isLen1 {
+		wg.Add(len(v))
+	}
+	for i := range v {
+		i := i
+		fc := &graphql.FieldContext{
+			Index:  &i,
+			Result: &v[i],
+		}
+		ctx := graphql.WithFieldContext(ctx, fc)
+		f := func(i int) {
+			defer func() {
+				if r := recover(); r != nil {
+					ec.Error(ctx, ec.Recover(ctx, r))
+					ret = nil
+				}
+			}()
+			if !isLen1 {
+				defer wg.Done()
+			}
+			ret[i] = ec.marshalNGitDiffLine2githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹repositoryᚐDiffLine(ctx, sel, v[i])
+		}
+		if isLen1 {
+			f(i)
+		} else {
+			go f(i)
+		}
+
+	}
+	wg.Wait()
+
+	for _, e := range ret {
+		if e == graphql.Null {
+			return graphql.Null
+		}
+	}
+
+	return ret
+}
+
+func (ec *executionContext) unmarshalNGitDiffLineType2githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹repositoryᚐDiffLineType(ctx context.Context, v any) (repository.DiffLineType, error) {
+	var res repository.DiffLineType
+	err := res.UnmarshalGQL(v)
+	return res, graphql.ErrorOnPath(ctx, err)
+}
+
+func (ec *executionContext) marshalNGitDiffLineType2githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹repositoryᚐDiffLineType(ctx context.Context, sel ast.SelectionSet, v repository.DiffLineType) graphql.Marshaler {
+	return v
+}
+
+func (ec *executionContext) marshalNGitLastCommit2αš•αš–githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹apiαš‹graphqlαš‹modelsᚐGitLastCommitαš„(ctx context.Context, sel ast.SelectionSet, v []*models.GitLastCommit) graphql.Marshaler {
+	ret := make(graphql.Array, len(v))
+	var wg sync.WaitGroup
+	isLen1 := len(v) == 1
+	if !isLen1 {
+		wg.Add(len(v))
+	}
+	for i := range v {
+		i := i
+		fc := &graphql.FieldContext{
+			Index:  &i,
+			Result: &v[i],
+		}
+		ctx := graphql.WithFieldContext(ctx, fc)
+		f := func(i int) {
+			defer func() {
+				if r := recover(); r != nil {
+					ec.Error(ctx, ec.Recover(ctx, r))
+					ret = nil
+				}
+			}()
+			if !isLen1 {
+				defer wg.Done()
+			}
+			ret[i] = ec.marshalNGitLastCommit2αš–githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹apiαš‹graphqlαš‹modelsᚐGitLastCommit(ctx, sel, v[i])
+		}
+		if isLen1 {
+			f(i)
+		} else {
+			go f(i)
+		}
+
+	}
+	wg.Wait()
+
+	for _, e := range ret {
+		if e == graphql.Null {
+			return graphql.Null
+		}
+	}
+
+	return ret
+}
+
+func (ec *executionContext) marshalNGitLastCommit2αš–githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹apiαš‹graphqlαš‹modelsᚐGitLastCommit(ctx context.Context, sel ast.SelectionSet, v *models.GitLastCommit) graphql.Marshaler {
+	if v == nil {
+		if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
+			ec.Errorf(ctx, "the requested element is null which the schema does not allow")
+		}
+		return graphql.Null
+	}
+	return ec._GitLastCommit(ctx, sel, v)
+}
+
+func (ec *executionContext) unmarshalNGitObjectType2githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹repositoryᚐObjectType(ctx context.Context, v any) (repository.ObjectType, error) {
+	var res repository.ObjectType
+	err := res.UnmarshalGQL(v)
+	return res, graphql.ErrorOnPath(ctx, err)
+}
+
+func (ec *executionContext) marshalNGitObjectType2githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹repositoryᚐObjectType(ctx context.Context, sel ast.SelectionSet, v repository.ObjectType) graphql.Marshaler {
+	return v
+}
+
+func (ec *executionContext) marshalNGitRef2αš•αš–githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹apiαš‹graphqlαš‹modelsᚐGitRefαš„(ctx context.Context, sel ast.SelectionSet, v []*models.GitRef) graphql.Marshaler {
+	ret := make(graphql.Array, len(v))
+	var wg sync.WaitGroup
+	isLen1 := len(v) == 1
+	if !isLen1 {
+		wg.Add(len(v))
+	}
+	for i := range v {
+		i := i
+		fc := &graphql.FieldContext{
+			Index:  &i,
+			Result: &v[i],
+		}
+		ctx := graphql.WithFieldContext(ctx, fc)
+		f := func(i int) {
+			defer func() {
+				if r := recover(); r != nil {
+					ec.Error(ctx, ec.Recover(ctx, r))
+					ret = nil
+				}
+			}()
+			if !isLen1 {
+				defer wg.Done()
+			}
+			ret[i] = ec.marshalNGitRef2αš–githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹apiαš‹graphqlαš‹modelsᚐGitRef(ctx, sel, v[i])
+		}
+		if isLen1 {
+			f(i)
+		} else {
+			go f(i)
+		}
+
+	}
+	wg.Wait()
+
+	for _, e := range ret {
+		if e == graphql.Null {
+			return graphql.Null
+		}
+	}
+
+	return ret
+}
+
+func (ec *executionContext) marshalNGitRef2αš–githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹apiαš‹graphqlαš‹modelsᚐGitRef(ctx context.Context, sel ast.SelectionSet, v *models.GitRef) graphql.Marshaler {
+	if v == nil {
+		if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
+			ec.Errorf(ctx, "the requested element is null which the schema does not allow")
+		}
+		return graphql.Null
+	}
+	return ec._GitRef(ctx, sel, v)
+}
+
+func (ec *executionContext) marshalNGitRefConnection2githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹apiαš‹graphqlαš‹modelsᚐGitRefConnection(ctx context.Context, sel ast.SelectionSet, v models.GitRefConnection) graphql.Marshaler {
+	return ec._GitRefConnection(ctx, sel, &v)
+}
+
+func (ec *executionContext) marshalNGitRefConnection2αš–githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹apiαš‹graphqlαš‹modelsᚐGitRefConnection(ctx context.Context, sel ast.SelectionSet, v *models.GitRefConnection) graphql.Marshaler {
+	if v == nil {
+		if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
+			ec.Errorf(ctx, "the requested element is null which the schema does not allow")
+		}
+		return graphql.Null
+	}
+	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) {
+	tmp, err := graphql.UnmarshalString(v)
+	res := unmarshalNGitRefType2githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹apiαš‹graphqlαš‹modelsᚐ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 {
+	_ = sel
+	res := graphql.MarshalString(marshalNGitRefType2githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹apiαš‹graphqlαš‹modelsᚐ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")
+		}
+	}
+	return res
+}
+
+var (
+	unmarshalNGitRefType2githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹apiαš‹graphqlαš‹modelsᚐGitRefType = map[string]models.GitRefType{
+		"BRANCH": models.GitRefTypeBranch,
+		"TAG":    models.GitRefTypeTag,
+	}
+	marshalNGitRefType2githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹apiαš‹graphqlαš‹modelsᚐGitRefType = map[models.GitRefType]string{
+		models.GitRefTypeBranch: "BRANCH",
+		models.GitRefTypeTag:    "TAG",
+	}
+)
+
+func (ec *executionContext) marshalNGitTreeEntry2αš•αš–githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹repositoryᚐTreeEntryαš„(ctx context.Context, sel ast.SelectionSet, v []*repository.TreeEntry) graphql.Marshaler {
+	ret := make(graphql.Array, len(v))
+	var wg sync.WaitGroup
+	isLen1 := len(v) == 1
+	if !isLen1 {
+		wg.Add(len(v))
+	}
+	for i := range v {
+		i := i
+		fc := &graphql.FieldContext{
+			Index:  &i,
+			Result: &v[i],
+		}
+		ctx := graphql.WithFieldContext(ctx, fc)
+		f := func(i int) {
+			defer func() {
+				if r := recover(); r != nil {
+					ec.Error(ctx, ec.Recover(ctx, r))
+					ret = nil
+				}
+			}()
+			if !isLen1 {
+				defer wg.Done()
+			}
+			ret[i] = ec.marshalNGitTreeEntry2αš–githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹repositoryᚐTreeEntry(ctx, sel, v[i])
+		}
+		if isLen1 {
+			f(i)
+		} else {
+			go f(i)
+		}
+
+	}
+	wg.Wait()
+
+	for _, e := range ret {
+		if e == graphql.Null {
+			return graphql.Null
+		}
+	}
+
+	return ret
+}
+
+func (ec *executionContext) marshalNGitTreeEntry2αš–githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹repositoryᚐTreeEntry(ctx context.Context, sel ast.SelectionSet, v *repository.TreeEntry) graphql.Marshaler {
+	if v == nil {
+		if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
+			ec.Errorf(ctx, "the requested element is null which the schema does not allow")
+		}
+		return graphql.Null
+	}
+	return ec._GitTreeEntry(ctx, sel, v)
+}
+
+func (ec *executionContext) marshalOGitBlob2αš–githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹apiαš‹graphqlαš‹modelsᚐGitBlob(ctx context.Context, sel ast.SelectionSet, v *models.GitBlob) graphql.Marshaler {
+	if v == nil {
+		return graphql.Null
+	}
+	return ec._GitBlob(ctx, sel, v)
+}
+
+func (ec *executionContext) marshalOGitCommit2αš–githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹apiαš‹graphqlαš‹modelsᚐGitCommitMeta(ctx context.Context, sel ast.SelectionSet, v *models.GitCommitMeta) graphql.Marshaler {
+	if v == nil {
+		return graphql.Null
+	}
+	return ec._GitCommit(ctx, sel, v)
+}
+
+func (ec *executionContext) marshalOGitFileDiff2αš–githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹repositoryᚐFileDiff(ctx context.Context, sel ast.SelectionSet, v *repository.FileDiff) graphql.Marshaler {
+	if v == nil {
+		return graphql.Null
+	}
+	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) {
+	if v == nil {
+		return nil, nil
+	}
+	tmp, err := graphql.UnmarshalString(v)
+	res := unmarshalOGitRefType2αš–githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹apiαš‹graphqlαš‹modelsᚐ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 {
+	if v == nil {
+		return graphql.Null
+	}
+	_ = sel
+	_ = ctx
+	res := graphql.MarshalString(marshalOGitRefType2αš–githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹apiαš‹graphqlαš‹modelsᚐ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",
+	}
+)
+
+// endregion ***************************** type.gotpl *****************************

api/graphql/graph/prelude.generated.go πŸ”—

@@ -13,6 +13,7 @@ import (
 	"github.com/99designs/gqlgen/graphql"
 	"github.com/99designs/gqlgen/graphql/introspection"
 	"github.com/git-bug/git-bug/entity"
+	"github.com/git-bug/git-bug/repository"
 	"github.com/vektah/gqlparser/v2/ast"
 )
 
@@ -2539,6 +2540,16 @@ func (ec *executionContext) marshalNString2githubαš—comαš‹gitαš‘bugαš‹gitαš‘bug
 	return v
 }
 
+func (ec *executionContext) unmarshalNString2githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹repositoryᚐHash(ctx context.Context, v any) (repository.Hash, error) {
+	var res repository.Hash
+	err := res.UnmarshalGQL(v)
+	return res, graphql.ErrorOnPath(ctx, err)
+}
+
+func (ec *executionContext) marshalNString2githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹repositoryᚐHash(ctx context.Context, sel ast.SelectionSet, v repository.Hash) graphql.Marshaler {
+	return v
+}
+
 func (ec *executionContext) unmarshalNString2string(ctx context.Context, v any) (string, error) {
 	res, err := graphql.UnmarshalString(v)
 	return res, graphql.ErrorOnPath(ctx, err)
@@ -2555,6 +2566,36 @@ func (ec *executionContext) marshalNString2string(ctx context.Context, sel ast.S
 	return res
 }
 
+func (ec *executionContext) unmarshalNString2αš•stringαš„(ctx context.Context, v any) ([]string, error) {
+	var vSlice []any
+	vSlice = graphql.CoerceList(v)
+	var err error
+	res := make([]string, len(vSlice))
+	for i := range vSlice {
+		ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i))
+		res[i], err = ec.unmarshalNString2string(ctx, vSlice[i])
+		if err != nil {
+			return nil, err
+		}
+	}
+	return res, nil
+}
+
+func (ec *executionContext) marshalNString2αš•stringαš„(ctx context.Context, sel ast.SelectionSet, v []string) graphql.Marshaler {
+	ret := make(graphql.Array, len(v))
+	for i := range v {
+		ret[i] = ec.marshalNString2string(ctx, sel, v[i])
+	}
+
+	for _, e := range ret {
+		if e == graphql.Null {
+			return graphql.Null
+		}
+	}
+
+	return ret
+}
+
 func (ec *executionContext) marshalN__Directive2githubαš—comαš‹99designsαš‹gqlgenαš‹graphqlαš‹introspectionᚐDirective(ctx context.Context, sel ast.SelectionSet, v introspection.Directive) graphql.Marshaler {
 	return ec.___Directive(ctx, sel, &v)
 }

api/graphql/graph/repository.generated.go πŸ”—

@@ -9,9 +9,11 @@ import (
 	"strconv"
 	"sync"
 	"sync/atomic"
+	"time"
 
 	"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"
 )
 
@@ -24,6 +26,12 @@ 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)
+	Tree(ctx context.Context, obj *models.Repository, ref string, path *string) ([]*repository.TreeEntry, 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)
 	ValidLabels(ctx context.Context, obj *models.Repository, after *string, before *string, first *int, last *int) (*models.LabelConnection, error)
 }
 
@@ -248,6 +256,57 @@ func (ec *executionContext) field_Repository_allIdentities_argsLast(
 	return zeroVal, nil
 }
 
+func (ec *executionContext) field_Repository_blob_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
+	var err error
+	args := map[string]any{}
+	arg0, err := ec.field_Repository_blob_argsRef(ctx, rawArgs)
+	if err != nil {
+		return nil, err
+	}
+	args["ref"] = arg0
+	arg1, err := ec.field_Repository_blob_argsPath(ctx, rawArgs)
+	if err != nil {
+		return nil, err
+	}
+	args["path"] = arg1
+	return args, nil
+}
+func (ec *executionContext) field_Repository_blob_argsRef(
+	ctx context.Context,
+	rawArgs map[string]any,
+) (string, error) {
+	if _, ok := rawArgs["ref"]; !ok {
+		var zeroVal string
+		return zeroVal, nil
+	}
+
+	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("ref"))
+	if tmp, ok := rawArgs["ref"]; ok {
+		return ec.unmarshalNString2string(ctx, tmp)
+	}
+
+	var zeroVal string
+	return zeroVal, nil
+}
+
+func (ec *executionContext) field_Repository_blob_argsPath(
+	ctx context.Context,
+	rawArgs map[string]any,
+) (string, error) {
+	if _, ok := rawArgs["path"]; !ok {
+		var zeroVal string
+		return zeroVal, nil
+	}
+
+	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("path"))
+	if tmp, ok := rawArgs["path"]; ok {
+		return ec.unmarshalNString2string(ctx, tmp)
+	}
+
+	var zeroVal string
+	return zeroVal, nil
+}
+
 func (ec *executionContext) field_Repository_bug_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
 	var err error
 	args := map[string]any{}
@@ -276,6 +335,177 @@ func (ec *executionContext) field_Repository_bug_argsPrefix(
 	return zeroVal, nil
 }
 
+func (ec *executionContext) field_Repository_commit_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
+	var err error
+	args := map[string]any{}
+	arg0, err := ec.field_Repository_commit_argsHash(ctx, rawArgs)
+	if err != nil {
+		return nil, err
+	}
+	args["hash"] = arg0
+	return args, nil
+}
+func (ec *executionContext) field_Repository_commit_argsHash(
+	ctx context.Context,
+	rawArgs map[string]any,
+) (string, error) {
+	if _, ok := rawArgs["hash"]; !ok {
+		var zeroVal string
+		return zeroVal, nil
+	}
+
+	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("hash"))
+	if tmp, ok := rawArgs["hash"]; ok {
+		return ec.unmarshalNString2string(ctx, tmp)
+	}
+
+	var zeroVal string
+	return zeroVal, nil
+}
+
+func (ec *executionContext) field_Repository_commits_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
+	var err error
+	args := map[string]any{}
+	arg0, err := ec.field_Repository_commits_argsAfter(ctx, rawArgs)
+	if err != nil {
+		return nil, err
+	}
+	args["after"] = arg0
+	arg1, err := ec.field_Repository_commits_argsFirst(ctx, rawArgs)
+	if err != nil {
+		return nil, err
+	}
+	args["first"] = arg1
+	arg2, err := ec.field_Repository_commits_argsRef(ctx, rawArgs)
+	if err != nil {
+		return nil, err
+	}
+	args["ref"] = arg2
+	arg3, err := ec.field_Repository_commits_argsPath(ctx, rawArgs)
+	if err != nil {
+		return nil, err
+	}
+	args["path"] = arg3
+	arg4, err := ec.field_Repository_commits_argsSince(ctx, rawArgs)
+	if err != nil {
+		return nil, err
+	}
+	args["since"] = arg4
+	arg5, err := ec.field_Repository_commits_argsUntil(ctx, rawArgs)
+	if err != nil {
+		return nil, err
+	}
+	args["until"] = arg5
+	return args, nil
+}
+func (ec *executionContext) field_Repository_commits_argsAfter(
+	ctx context.Context,
+	rawArgs map[string]any,
+) (*string, error) {
+	if _, ok := rawArgs["after"]; !ok {
+		var zeroVal *string
+		return zeroVal, nil
+	}
+
+	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("after"))
+	if tmp, ok := rawArgs["after"]; ok {
+		return ec.unmarshalOString2αš–string(ctx, tmp)
+	}
+
+	var zeroVal *string
+	return zeroVal, nil
+}
+
+func (ec *executionContext) field_Repository_commits_argsFirst(
+	ctx context.Context,
+	rawArgs map[string]any,
+) (*int, error) {
+	if _, ok := rawArgs["first"]; !ok {
+		var zeroVal *int
+		return zeroVal, nil
+	}
+
+	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("first"))
+	if tmp, ok := rawArgs["first"]; ok {
+		return ec.unmarshalOInt2αš–int(ctx, tmp)
+	}
+
+	var zeroVal *int
+	return zeroVal, nil
+}
+
+func (ec *executionContext) field_Repository_commits_argsRef(
+	ctx context.Context,
+	rawArgs map[string]any,
+) (string, error) {
+	if _, ok := rawArgs["ref"]; !ok {
+		var zeroVal string
+		return zeroVal, nil
+	}
+
+	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("ref"))
+	if tmp, ok := rawArgs["ref"]; ok {
+		return ec.unmarshalNString2string(ctx, tmp)
+	}
+
+	var zeroVal string
+	return zeroVal, nil
+}
+
+func (ec *executionContext) field_Repository_commits_argsPath(
+	ctx context.Context,
+	rawArgs map[string]any,
+) (*string, error) {
+	if _, ok := rawArgs["path"]; !ok {
+		var zeroVal *string
+		return zeroVal, nil
+	}
+
+	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("path"))
+	if tmp, ok := rawArgs["path"]; ok {
+		return ec.unmarshalOString2αš–string(ctx, tmp)
+	}
+
+	var zeroVal *string
+	return zeroVal, nil
+}
+
+func (ec *executionContext) field_Repository_commits_argsSince(
+	ctx context.Context,
+	rawArgs map[string]any,
+) (*time.Time, error) {
+	if _, ok := rawArgs["since"]; !ok {
+		var zeroVal *time.Time
+		return zeroVal, nil
+	}
+
+	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("since"))
+	if tmp, ok := rawArgs["since"]; ok {
+		return ec.unmarshalOTime2αš–timeᚐTime(ctx, tmp)
+	}
+
+	var zeroVal *time.Time
+	return zeroVal, nil
+}
+
+func (ec *executionContext) field_Repository_commits_argsUntil(
+	ctx context.Context,
+	rawArgs map[string]any,
+) (*time.Time, error) {
+	if _, ok := rawArgs["until"]; !ok {
+		var zeroVal *time.Time
+		return zeroVal, nil
+	}
+
+	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("until"))
+	if tmp, ok := rawArgs["until"]; ok {
+		return ec.unmarshalOTime2αš–timeᚐTime(ctx, tmp)
+	}
+
+	var zeroVal *time.Time
+	return zeroVal, nil
+}
+
 func (ec *executionContext) field_Repository_identity_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
 	var err error
 	args := map[string]any{}
@@ -304,32 +534,111 @@ func (ec *executionContext) field_Repository_identity_argsPrefix(
 	return zeroVal, nil
 }
 
-func (ec *executionContext) field_Repository_validLabels_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
+func (ec *executionContext) field_Repository_lastCommits_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
 	var err error
 	args := map[string]any{}
-	arg0, err := ec.field_Repository_validLabels_argsAfter(ctx, rawArgs)
+	arg0, err := ec.field_Repository_lastCommits_argsRef(ctx, rawArgs)
+	if err != nil {
+		return nil, err
+	}
+	args["ref"] = arg0
+	arg1, err := ec.field_Repository_lastCommits_argsPath(ctx, rawArgs)
+	if err != nil {
+		return nil, err
+	}
+	args["path"] = arg1
+	arg2, err := ec.field_Repository_lastCommits_argsNames(ctx, rawArgs)
+	if err != nil {
+		return nil, err
+	}
+	args["names"] = arg2
+	return args, nil
+}
+func (ec *executionContext) field_Repository_lastCommits_argsRef(
+	ctx context.Context,
+	rawArgs map[string]any,
+) (string, error) {
+	if _, ok := rawArgs["ref"]; !ok {
+		var zeroVal string
+		return zeroVal, nil
+	}
+
+	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("ref"))
+	if tmp, ok := rawArgs["ref"]; ok {
+		return ec.unmarshalNString2string(ctx, tmp)
+	}
+
+	var zeroVal string
+	return zeroVal, nil
+}
+
+func (ec *executionContext) field_Repository_lastCommits_argsPath(
+	ctx context.Context,
+	rawArgs map[string]any,
+) (*string, error) {
+	if _, ok := rawArgs["path"]; !ok {
+		var zeroVal *string
+		return zeroVal, nil
+	}
+
+	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("path"))
+	if tmp, ok := rawArgs["path"]; ok {
+		return ec.unmarshalOString2αš–string(ctx, tmp)
+	}
+
+	var zeroVal *string
+	return zeroVal, nil
+}
+
+func (ec *executionContext) field_Repository_lastCommits_argsNames(
+	ctx context.Context,
+	rawArgs map[string]any,
+) ([]string, error) {
+	if _, ok := rawArgs["names"]; !ok {
+		var zeroVal []string
+		return zeroVal, nil
+	}
+
+	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("names"))
+	if tmp, ok := rawArgs["names"]; ok {
+		return ec.unmarshalNString2αš•stringαš„(ctx, tmp)
+	}
+
+	var zeroVal []string
+	return zeroVal, nil
+}
+
+func (ec *executionContext) field_Repository_refs_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
+	var err error
+	args := map[string]any{}
+	arg0, err := ec.field_Repository_refs_argsAfter(ctx, rawArgs)
 	if err != nil {
 		return nil, err
 	}
 	args["after"] = arg0
-	arg1, err := ec.field_Repository_validLabels_argsBefore(ctx, rawArgs)
+	arg1, err := ec.field_Repository_refs_argsBefore(ctx, rawArgs)
 	if err != nil {
 		return nil, err
 	}
 	args["before"] = arg1
-	arg2, err := ec.field_Repository_validLabels_argsFirst(ctx, rawArgs)
+	arg2, err := ec.field_Repository_refs_argsFirst(ctx, rawArgs)
 	if err != nil {
 		return nil, err
 	}
 	args["first"] = arg2
-	arg3, err := ec.field_Repository_validLabels_argsLast(ctx, rawArgs)
+	arg3, err := ec.field_Repository_refs_argsLast(ctx, rawArgs)
 	if err != nil {
 		return nil, err
 	}
 	args["last"] = arg3
+	arg4, err := ec.field_Repository_refs_argsType(ctx, rawArgs)
+	if err != nil {
+		return nil, err
+	}
+	args["type"] = arg4
 	return args, nil
 }
-func (ec *executionContext) field_Repository_validLabels_argsAfter(
+func (ec *executionContext) field_Repository_refs_argsAfter(
 	ctx context.Context,
 	rawArgs map[string]any,
 ) (*string, error) {
@@ -337,80 +646,626 @@ func (ec *executionContext) field_Repository_validLabels_argsAfter(
 		var zeroVal *string
 		return zeroVal, nil
 	}
-
-	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("after"))
-	if tmp, ok := rawArgs["after"]; ok {
-		return ec.unmarshalOString2αš–string(ctx, tmp)
+
+	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("after"))
+	if tmp, ok := rawArgs["after"]; ok {
+		return ec.unmarshalOString2αš–string(ctx, tmp)
+	}
+
+	var zeroVal *string
+	return zeroVal, nil
+}
+
+func (ec *executionContext) field_Repository_refs_argsBefore(
+	ctx context.Context,
+	rawArgs map[string]any,
+) (*string, error) {
+	if _, ok := rawArgs["before"]; !ok {
+		var zeroVal *string
+		return zeroVal, nil
+	}
+
+	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("before"))
+	if tmp, ok := rawArgs["before"]; ok {
+		return ec.unmarshalOString2αš–string(ctx, tmp)
+	}
+
+	var zeroVal *string
+	return zeroVal, nil
+}
+
+func (ec *executionContext) field_Repository_refs_argsFirst(
+	ctx context.Context,
+	rawArgs map[string]any,
+) (*int, error) {
+	if _, ok := rawArgs["first"]; !ok {
+		var zeroVal *int
+		return zeroVal, nil
+	}
+
+	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("first"))
+	if tmp, ok := rawArgs["first"]; ok {
+		return ec.unmarshalOInt2αš–int(ctx, tmp)
+	}
+
+	var zeroVal *int
+	return zeroVal, nil
+}
+
+func (ec *executionContext) field_Repository_refs_argsLast(
+	ctx context.Context,
+	rawArgs map[string]any,
+) (*int, error) {
+	if _, ok := rawArgs["last"]; !ok {
+		var zeroVal *int
+		return zeroVal, nil
+	}
+
+	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("last"))
+	if tmp, ok := rawArgs["last"]; ok {
+		return ec.unmarshalOInt2αš–int(ctx, tmp)
+	}
+
+	var zeroVal *int
+	return zeroVal, nil
+}
+
+func (ec *executionContext) field_Repository_refs_argsType(
+	ctx context.Context,
+	rawArgs map[string]any,
+) (*models.GitRefType, error) {
+	if _, ok := rawArgs["type"]; !ok {
+		var zeroVal *models.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)
+	}
+
+	var zeroVal *models.GitRefType
+	return zeroVal, nil
+}
+
+func (ec *executionContext) field_Repository_tree_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
+	var err error
+	args := map[string]any{}
+	arg0, err := ec.field_Repository_tree_argsRef(ctx, rawArgs)
+	if err != nil {
+		return nil, err
+	}
+	args["ref"] = arg0
+	arg1, err := ec.field_Repository_tree_argsPath(ctx, rawArgs)
+	if err != nil {
+		return nil, err
+	}
+	args["path"] = arg1
+	return args, nil
+}
+func (ec *executionContext) field_Repository_tree_argsRef(
+	ctx context.Context,
+	rawArgs map[string]any,
+) (string, error) {
+	if _, ok := rawArgs["ref"]; !ok {
+		var zeroVal string
+		return zeroVal, nil
+	}
+
+	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("ref"))
+	if tmp, ok := rawArgs["ref"]; ok {
+		return ec.unmarshalNString2string(ctx, tmp)
+	}
+
+	var zeroVal string
+	return zeroVal, nil
+}
+
+func (ec *executionContext) field_Repository_tree_argsPath(
+	ctx context.Context,
+	rawArgs map[string]any,
+) (*string, error) {
+	if _, ok := rawArgs["path"]; !ok {
+		var zeroVal *string
+		return zeroVal, nil
+	}
+
+	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("path"))
+	if tmp, ok := rawArgs["path"]; ok {
+		return ec.unmarshalOString2αš–string(ctx, tmp)
+	}
+
+	var zeroVal *string
+	return zeroVal, nil
+}
+
+func (ec *executionContext) field_Repository_validLabels_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
+	var err error
+	args := map[string]any{}
+	arg0, err := ec.field_Repository_validLabels_argsAfter(ctx, rawArgs)
+	if err != nil {
+		return nil, err
+	}
+	args["after"] = arg0
+	arg1, err := ec.field_Repository_validLabels_argsBefore(ctx, rawArgs)
+	if err != nil {
+		return nil, err
+	}
+	args["before"] = arg1
+	arg2, err := ec.field_Repository_validLabels_argsFirst(ctx, rawArgs)
+	if err != nil {
+		return nil, err
+	}
+	args["first"] = arg2
+	arg3, err := ec.field_Repository_validLabels_argsLast(ctx, rawArgs)
+	if err != nil {
+		return nil, err
+	}
+	args["last"] = arg3
+	return args, nil
+}
+func (ec *executionContext) field_Repository_validLabels_argsAfter(
+	ctx context.Context,
+	rawArgs map[string]any,
+) (*string, error) {
+	if _, ok := rawArgs["after"]; !ok {
+		var zeroVal *string
+		return zeroVal, nil
+	}
+
+	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("after"))
+	if tmp, ok := rawArgs["after"]; ok {
+		return ec.unmarshalOString2αš–string(ctx, tmp)
+	}
+
+	var zeroVal *string
+	return zeroVal, nil
+}
+
+func (ec *executionContext) field_Repository_validLabels_argsBefore(
+	ctx context.Context,
+	rawArgs map[string]any,
+) (*string, error) {
+	if _, ok := rawArgs["before"]; !ok {
+		var zeroVal *string
+		return zeroVal, nil
+	}
+
+	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("before"))
+	if tmp, ok := rawArgs["before"]; ok {
+		return ec.unmarshalOString2αš–string(ctx, tmp)
+	}
+
+	var zeroVal *string
+	return zeroVal, nil
+}
+
+func (ec *executionContext) field_Repository_validLabels_argsFirst(
+	ctx context.Context,
+	rawArgs map[string]any,
+) (*int, error) {
+	if _, ok := rawArgs["first"]; !ok {
+		var zeroVal *int
+		return zeroVal, nil
+	}
+
+	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("first"))
+	if tmp, ok := rawArgs["first"]; ok {
+		return ec.unmarshalOInt2αš–int(ctx, tmp)
+	}
+
+	var zeroVal *int
+	return zeroVal, nil
+}
+
+func (ec *executionContext) field_Repository_validLabels_argsLast(
+	ctx context.Context,
+	rawArgs map[string]any,
+) (*int, error) {
+	if _, ok := rawArgs["last"]; !ok {
+		var zeroVal *int
+		return zeroVal, nil
+	}
+
+	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("last"))
+	if tmp, ok := rawArgs["last"]; ok {
+		return ec.unmarshalOInt2αš–int(ctx, tmp)
+	}
+
+	var zeroVal *int
+	return zeroVal, nil
+}
+
+// endregion ***************************** args.gotpl *****************************
+
+// region    ************************** directives.gotpl **************************
+
+// endregion ************************** directives.gotpl **************************
+
+// region    **************************** field.gotpl *****************************
+
+func (ec *executionContext) _Repository_name(ctx context.Context, field graphql.CollectedField, obj *models.Repository) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_Repository_name(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.Repository().Name(rctx, obj)
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*string)
+	fc.Result = res
+	return ec.marshalOString2αš–string(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_Repository_name(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "Repository",
+		Field:      field,
+		IsMethod:   true,
+		IsResolver: true,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			return nil, errors.New("field of type String does not have child fields")
+		},
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _Repository_allBugs(ctx context.Context, field graphql.CollectedField, obj *models.Repository) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_Repository_allBugs(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.Repository().AllBugs(rctx, obj, fc.Args["after"].(*string), fc.Args["before"].(*string), fc.Args["first"].(*int), fc.Args["last"].(*int), fc.Args["query"].(*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.(*models.BugConnection)
+	fc.Result = res
+	return ec.marshalNBugConnection2αš–githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹apiαš‹graphqlαš‹modelsᚐBugConnection(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_Repository_allBugs(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "Repository",
+		Field:      field,
+		IsMethod:   true,
+		IsResolver: true,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			switch field.Name {
+			case "edges":
+				return ec.fieldContext_BugConnection_edges(ctx, field)
+			case "nodes":
+				return ec.fieldContext_BugConnection_nodes(ctx, field)
+			case "pageInfo":
+				return ec.fieldContext_BugConnection_pageInfo(ctx, field)
+			case "totalCount":
+				return ec.fieldContext_BugConnection_totalCount(ctx, field)
+			}
+			return nil, fmt.Errorf("no field named %q was found under type BugConnection", field.Name)
+		},
+	}
+	defer func() {
+		if r := recover(); r != nil {
+			err = ec.Recover(ctx, r)
+			ec.Error(ctx, err)
+		}
+	}()
+	ctx = graphql.WithFieldContext(ctx, fc)
+	if fc.Args, err = ec.field_Repository_allBugs_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
+		ec.Error(ctx, err)
+		return fc, err
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _Repository_bug(ctx context.Context, field graphql.CollectedField, obj *models.Repository) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_Repository_bug(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.Repository().Bug(rctx, obj, fc.Args["prefix"].(string))
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(models.BugWrapper)
+	fc.Result = res
+	return ec.marshalOBug2githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹apiαš‹graphqlαš‹modelsᚐBugWrapper(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_Repository_bug(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "Repository",
+		Field:      field,
+		IsMethod:   true,
+		IsResolver: true,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			switch field.Name {
+			case "id":
+				return ec.fieldContext_Bug_id(ctx, field)
+			case "humanId":
+				return ec.fieldContext_Bug_humanId(ctx, field)
+			case "status":
+				return ec.fieldContext_Bug_status(ctx, field)
+			case "title":
+				return ec.fieldContext_Bug_title(ctx, field)
+			case "labels":
+				return ec.fieldContext_Bug_labels(ctx, field)
+			case "author":
+				return ec.fieldContext_Bug_author(ctx, field)
+			case "createdAt":
+				return ec.fieldContext_Bug_createdAt(ctx, field)
+			case "lastEdit":
+				return ec.fieldContext_Bug_lastEdit(ctx, field)
+			case "actors":
+				return ec.fieldContext_Bug_actors(ctx, field)
+			case "participants":
+				return ec.fieldContext_Bug_participants(ctx, field)
+			case "comments":
+				return ec.fieldContext_Bug_comments(ctx, field)
+			case "timeline":
+				return ec.fieldContext_Bug_timeline(ctx, field)
+			case "operations":
+				return ec.fieldContext_Bug_operations(ctx, field)
+			}
+			return nil, fmt.Errorf("no field named %q was found under type Bug", field.Name)
+		},
+	}
+	defer func() {
+		if r := recover(); r != nil {
+			err = ec.Recover(ctx, r)
+			ec.Error(ctx, err)
+		}
+	}()
+	ctx = graphql.WithFieldContext(ctx, fc)
+	if fc.Args, err = ec.field_Repository_bug_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
+		ec.Error(ctx, err)
+		return fc, err
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _Repository_allIdentities(ctx context.Context, field graphql.CollectedField, obj *models.Repository) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_Repository_allIdentities(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.Repository().AllIdentities(rctx, obj, fc.Args["after"].(*string), fc.Args["before"].(*string), fc.Args["first"].(*int), fc.Args["last"].(*int))
+	})
+	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.IdentityConnection)
+	fc.Result = res
+	return ec.marshalNIdentityConnection2αš–githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹apiαš‹graphqlαš‹modelsᚐIdentityConnection(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_Repository_allIdentities(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "Repository",
+		Field:      field,
+		IsMethod:   true,
+		IsResolver: true,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			switch field.Name {
+			case "edges":
+				return ec.fieldContext_IdentityConnection_edges(ctx, field)
+			case "nodes":
+				return ec.fieldContext_IdentityConnection_nodes(ctx, field)
+			case "pageInfo":
+				return ec.fieldContext_IdentityConnection_pageInfo(ctx, field)
+			case "totalCount":
+				return ec.fieldContext_IdentityConnection_totalCount(ctx, field)
+			}
+			return nil, fmt.Errorf("no field named %q was found under type IdentityConnection", field.Name)
+		},
+	}
+	defer func() {
+		if r := recover(); r != nil {
+			err = ec.Recover(ctx, r)
+			ec.Error(ctx, err)
+		}
+	}()
+	ctx = graphql.WithFieldContext(ctx, fc)
+	if fc.Args, err = ec.field_Repository_allIdentities_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
+		ec.Error(ctx, err)
+		return fc, err
+	}
+	return fc, nil
+}
+
+func (ec *executionContext) _Repository_identity(ctx context.Context, field graphql.CollectedField, obj *models.Repository) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_Repository_identity(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.Repository().Identity(rctx, obj, fc.Args["prefix"].(string))
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
 	}
-
-	var zeroVal *string
-	return zeroVal, nil
+	res := resTmp.(models.IdentityWrapper)
+	fc.Result = res
+	return ec.marshalOIdentity2githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹apiαš‹graphqlαš‹modelsᚐIdentityWrapper(ctx, field.Selections, res)
 }
 
-func (ec *executionContext) field_Repository_validLabels_argsBefore(
-	ctx context.Context,
-	rawArgs map[string]any,
-) (*string, error) {
-	if _, ok := rawArgs["before"]; !ok {
-		var zeroVal *string
-		return zeroVal, nil
+func (ec *executionContext) fieldContext_Repository_identity(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "Repository",
+		Field:      field,
+		IsMethod:   true,
+		IsResolver: true,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			switch field.Name {
+			case "id":
+				return ec.fieldContext_Identity_id(ctx, field)
+			case "humanId":
+				return ec.fieldContext_Identity_humanId(ctx, field)
+			case "name":
+				return ec.fieldContext_Identity_name(ctx, field)
+			case "email":
+				return ec.fieldContext_Identity_email(ctx, field)
+			case "login":
+				return ec.fieldContext_Identity_login(ctx, field)
+			case "displayName":
+				return ec.fieldContext_Identity_displayName(ctx, field)
+			case "avatarUrl":
+				return ec.fieldContext_Identity_avatarUrl(ctx, field)
+			case "isProtected":
+				return ec.fieldContext_Identity_isProtected(ctx, field)
+			}
+			return nil, fmt.Errorf("no field named %q was found under type Identity", field.Name)
+		},
 	}
-
-	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("before"))
-	if tmp, ok := rawArgs["before"]; ok {
-		return ec.unmarshalOString2αš–string(ctx, tmp)
+	defer func() {
+		if r := recover(); r != nil {
+			err = ec.Recover(ctx, r)
+			ec.Error(ctx, err)
+		}
+	}()
+	ctx = graphql.WithFieldContext(ctx, fc)
+	if fc.Args, err = ec.field_Repository_identity_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
+		ec.Error(ctx, err)
+		return fc, err
 	}
-
-	var zeroVal *string
-	return zeroVal, nil
+	return fc, nil
 }
 
-func (ec *executionContext) field_Repository_validLabels_argsFirst(
-	ctx context.Context,
-	rawArgs map[string]any,
-) (*int, error) {
-	if _, ok := rawArgs["first"]; !ok {
-		var zeroVal *int
-		return zeroVal, nil
+func (ec *executionContext) _Repository_userIdentity(ctx context.Context, field graphql.CollectedField, obj *models.Repository) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_Repository_userIdentity(ctx, field)
+	if err != nil {
+		return graphql.Null
 	}
-
-	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("first"))
-	if tmp, ok := rawArgs["first"]; ok {
-		return ec.unmarshalOInt2αš–int(ctx, tmp)
+	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.Repository().UserIdentity(rctx, obj)
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
 	}
-
-	var zeroVal *int
-	return zeroVal, nil
-}
-
-func (ec *executionContext) field_Repository_validLabels_argsLast(
-	ctx context.Context,
-	rawArgs map[string]any,
-) (*int, error) {
-	if _, ok := rawArgs["last"]; !ok {
-		var zeroVal *int
-		return zeroVal, nil
+	if resTmp == nil {
+		return graphql.Null
 	}
+	res := resTmp.(models.IdentityWrapper)
+	fc.Result = res
+	return ec.marshalOIdentity2githubαš—comαš‹gitαš‘bugαš‹gitαš‘bugαš‹apiαš‹graphqlαš‹modelsᚐIdentityWrapper(ctx, field.Selections, res)
+}
 
-	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("last"))
-	if tmp, ok := rawArgs["last"]; ok {
-		return ec.unmarshalOInt2αš–int(ctx, tmp)
+func (ec *executionContext) fieldContext_Repository_userIdentity(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "Repository",
+		Field:      field,
+		IsMethod:   true,
+		IsResolver: true,
+		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+			switch field.Name {
+			case "id":
+				return ec.fieldContext_Identity_id(ctx, field)
+			case "humanId":
+				return ec.fieldContext_Identity_humanId(ctx, field)
+			case "name":
+				return ec.fieldContext_Identity_name(ctx, field)
+			case "email":
+				return ec.fieldContext_Identity_email(ctx, field)
+			case "login":
+				return ec.fieldContext_Identity_login(ctx, field)
+			case "displayName":
+				return ec.fieldContext_Identity_displayName(ctx, field)
+			case "avatarUrl":
+				return ec.fieldContext_Identity_avatarUrl(ctx, field)
+			case "isProtected":
+				return ec.fieldContext_Identity_isProtected(ctx, field)
+			}
+			return nil, fmt.Errorf("no field named %q was found under type Identity", field.Name)
+		},
 	}
-
-	var zeroVal *int
-	return zeroVal, nil
+	return fc, nil
 }
 
-// endregion ***************************** args.gotpl *****************************
-
-// region    ************************** directives.gotpl **************************
-
-// endregion ************************** directives.gotpl **************************
-
-// region    **************************** field.gotpl *****************************
-
-func (ec *executionContext) _Repository_name(ctx context.Context, field graphql.CollectedField, obj *models.Repository) (ret graphql.Marshaler) {
-	fc, err := ec.fieldContext_Repository_name(ctx, field)
+func (ec *executionContext) _Repository_refs(ctx context.Context, field graphql.CollectedField, obj *models.Repository) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_Repository_refs(ctx, field)
 	if err != nil {
 		return graphql.Null
 	}

api/graphql/graph/root.generated.go πŸ”—

@@ -1070,6 +1070,18 @@ func (ec *executionContext) fieldContext_Query_repository(ctx context.Context, f
 				return ec.fieldContext_Repository_identity(ctx, field)
 			case "userIdentity":
 				return ec.fieldContext_Repository_userIdentity(ctx, field)
+			case "refs":
+				return ec.fieldContext_Repository_refs(ctx, field)
+			case "tree":
+				return ec.fieldContext_Repository_tree(ctx, field)
+			case "blob":
+				return ec.fieldContext_Repository_blob(ctx, field)
+			case "commits":
+				return ec.fieldContext_Repository_commits(ctx, field)
+			case "commit":
+				return ec.fieldContext_Repository_commit(ctx, field)
+			case "lastCommits":
+				return ec.fieldContext_Repository_lastCommits(ctx, field)
 			case "validLabels":
 				return ec.fieldContext_Repository_validLabels(ctx, field)
 			}

api/graphql/graph/root_.generated.go πŸ”—

@@ -7,6 +7,7 @@ import (
 	"context"
 	"errors"
 	"sync/atomic"
+	"time"
 
 	"github.com/99designs/gqlgen/graphql"
 	"github.com/99designs/gqlgen/graphql/introspection"
@@ -48,6 +49,7 @@ type ResolverRoot interface {
 	BugSetTitleOperation() BugSetTitleOperationResolver
 	BugSetTitleTimelineItem() BugSetTitleTimelineItemResolver
 	Color() ColorResolver
+	GitCommit() GitCommitResolver
 	Identity() IdentityResolver
 	Label() LabelResolver
 	Mutation() MutationResolver
@@ -293,6 +295,95 @@ type ComplexityRoot struct {
 		Type   func(childComplexity int) int
 	}
 
+	GitBlob struct {
+		Hash        func(childComplexity int) int
+		IsBinary    func(childComplexity int) int
+		IsTruncated func(childComplexity int) int
+		Path        func(childComplexity int) int
+		Size        func(childComplexity int) int
+		Text        func(childComplexity int) int
+	}
+
+	GitChangedFile struct {
+		OldPath func(childComplexity int) int
+		Path    func(childComplexity int) int
+		Status  func(childComplexity int) int
+	}
+
+	GitChangedFileConnection struct {
+		Nodes      func(childComplexity int) int
+		PageInfo   func(childComplexity int) int
+		TotalCount func(childComplexity int) int
+	}
+
+	GitCommit struct {
+		AuthorEmail func(childComplexity int) int
+		AuthorName  func(childComplexity int) int
+		Date        func(childComplexity int) int
+		Diff        func(childComplexity int, path string) int
+		Files       func(childComplexity int, after *string, before *string, first *int, last *int) int
+		FullMessage func(childComplexity int) int
+		Hash        func(childComplexity int) int
+		Message     func(childComplexity int) int
+		Parents     func(childComplexity int) int
+		ShortHash   func(childComplexity int) int
+	}
+
+	GitCommitConnection struct {
+		Nodes      func(childComplexity int) int
+		PageInfo   func(childComplexity int) int
+		TotalCount func(childComplexity int) int
+	}
+
+	GitDiffHunk struct {
+		Lines    func(childComplexity int) int
+		NewLines func(childComplexity int) int
+		NewStart func(childComplexity int) int
+		OldLines func(childComplexity int) int
+		OldStart func(childComplexity int) int
+	}
+
+	GitDiffLine struct {
+		Content func(childComplexity int) int
+		NewLine func(childComplexity int) int
+		OldLine func(childComplexity int) int
+		Type    func(childComplexity int) int
+	}
+
+	GitFileDiff struct {
+		Hunks    func(childComplexity int) int
+		IsBinary func(childComplexity int) int
+		IsDelete func(childComplexity int) int
+		IsNew    func(childComplexity int) int
+		OldPath  func(childComplexity int) int
+		Path     func(childComplexity int) int
+	}
+
+	GitLastCommit struct {
+		Commit func(childComplexity int) int
+		Name   func(childComplexity int) int
+	}
+
+	GitRef struct {
+		Hash      func(childComplexity int) int
+		IsDefault func(childComplexity int) int
+		Name      func(childComplexity int) int
+		ShortName func(childComplexity int) int
+		Type      func(childComplexity int) int
+	}
+
+	GitRefConnection struct {
+		Nodes      func(childComplexity int) int
+		PageInfo   func(childComplexity int) int
+		TotalCount func(childComplexity int) int
+	}
+
+	GitTreeEntry struct {
+		Hash       func(childComplexity int) int
+		Name       func(childComplexity int) int
+		ObjectType func(childComplexity int) int
+	}
+
 	Identity struct {
 		AvatarUrl   func(childComplexity int) int
 		DisplayName func(childComplexity int) int
@@ -382,9 +473,15 @@ type ComplexityRoot struct {
 	Repository struct {
 		AllBugs       func(childComplexity int, after *string, before *string, first *int, last *int, query *string) int
 		AllIdentities func(childComplexity int, after *string, before *string, first *int, last *int) int
+		Blob          func(childComplexity int, ref string, path string) int
 		Bug           func(childComplexity int, prefix string) int
+		Commit        func(childComplexity int, hash string) int
+		Commits       func(childComplexity int, after *string, first *int, ref string, path *string, since *time.Time, until *time.Time) int
 		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
+		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
 	}
@@ -1411,6 +1508,387 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
 
 		return e.complexity.EntityEvent.Type(childComplexity), true
 
+	case "GitBlob.hash":
+		if e.complexity.GitBlob.Hash == nil {
+			break
+		}
+
+		return e.complexity.GitBlob.Hash(childComplexity), true
+
+	case "GitBlob.isBinary":
+		if e.complexity.GitBlob.IsBinary == nil {
+			break
+		}
+
+		return e.complexity.GitBlob.IsBinary(childComplexity), true
+
+	case "GitBlob.isTruncated":
+		if e.complexity.GitBlob.IsTruncated == nil {
+			break
+		}
+
+		return e.complexity.GitBlob.IsTruncated(childComplexity), true
+
+	case "GitBlob.path":
+		if e.complexity.GitBlob.Path == nil {
+			break
+		}
+
+		return e.complexity.GitBlob.Path(childComplexity), true
+
+	case "GitBlob.size":
+		if e.complexity.GitBlob.Size == nil {
+			break
+		}
+
+		return e.complexity.GitBlob.Size(childComplexity), true
+
+	case "GitBlob.text":
+		if e.complexity.GitBlob.Text == nil {
+			break
+		}
+
+		return e.complexity.GitBlob.Text(childComplexity), true
+
+	case "GitChangedFile.oldPath":
+		if e.complexity.GitChangedFile.OldPath == nil {
+			break
+		}
+
+		return e.complexity.GitChangedFile.OldPath(childComplexity), true
+
+	case "GitChangedFile.path":
+		if e.complexity.GitChangedFile.Path == nil {
+			break
+		}
+
+		return e.complexity.GitChangedFile.Path(childComplexity), true
+
+	case "GitChangedFile.status":
+		if e.complexity.GitChangedFile.Status == nil {
+			break
+		}
+
+		return e.complexity.GitChangedFile.Status(childComplexity), true
+
+	case "GitChangedFileConnection.nodes":
+		if e.complexity.GitChangedFileConnection.Nodes == nil {
+			break
+		}
+
+		return e.complexity.GitChangedFileConnection.Nodes(childComplexity), true
+
+	case "GitChangedFileConnection.pageInfo":
+		if e.complexity.GitChangedFileConnection.PageInfo == nil {
+			break
+		}
+
+		return e.complexity.GitChangedFileConnection.PageInfo(childComplexity), true
+
+	case "GitChangedFileConnection.totalCount":
+		if e.complexity.GitChangedFileConnection.TotalCount == nil {
+			break
+		}
+
+		return e.complexity.GitChangedFileConnection.TotalCount(childComplexity), true
+
+	case "GitCommit.authorEmail":
+		if e.complexity.GitCommit.AuthorEmail == nil {
+			break
+		}
+
+		return e.complexity.GitCommit.AuthorEmail(childComplexity), true
+
+	case "GitCommit.authorName":
+		if e.complexity.GitCommit.AuthorName == nil {
+			break
+		}
+
+		return e.complexity.GitCommit.AuthorName(childComplexity), true
+
+	case "GitCommit.date":
+		if e.complexity.GitCommit.Date == nil {
+			break
+		}
+
+		return e.complexity.GitCommit.Date(childComplexity), true
+
+	case "GitCommit.diff":
+		if e.complexity.GitCommit.Diff == nil {
+			break
+		}
+
+		args, err := ec.field_GitCommit_diff_args(ctx, rawArgs)
+		if err != nil {
+			return 0, false
+		}
+
+		return e.complexity.GitCommit.Diff(childComplexity, args["path"].(string)), true
+
+	case "GitCommit.files":
+		if e.complexity.GitCommit.Files == nil {
+			break
+		}
+
+		args, err := ec.field_GitCommit_files_args(ctx, rawArgs)
+		if err != nil {
+			return 0, false
+		}
+
+		return e.complexity.GitCommit.Files(childComplexity, args["after"].(*string), args["before"].(*string), args["first"].(*int), args["last"].(*int)), true
+
+	case "GitCommit.fullMessage":
+		if e.complexity.GitCommit.FullMessage == nil {
+			break
+		}
+
+		return e.complexity.GitCommit.FullMessage(childComplexity), true
+
+	case "GitCommit.hash":
+		if e.complexity.GitCommit.Hash == nil {
+			break
+		}
+
+		return e.complexity.GitCommit.Hash(childComplexity), true
+
+	case "GitCommit.message":
+		if e.complexity.GitCommit.Message == nil {
+			break
+		}
+
+		return e.complexity.GitCommit.Message(childComplexity), true
+
+	case "GitCommit.parents":
+		if e.complexity.GitCommit.Parents == nil {
+			break
+		}
+
+		return e.complexity.GitCommit.Parents(childComplexity), true
+
+	case "GitCommit.shortHash":
+		if e.complexity.GitCommit.ShortHash == nil {
+			break
+		}
+
+		return e.complexity.GitCommit.ShortHash(childComplexity), true
+
+	case "GitCommitConnection.nodes":
+		if e.complexity.GitCommitConnection.Nodes == nil {
+			break
+		}
+
+		return e.complexity.GitCommitConnection.Nodes(childComplexity), true
+
+	case "GitCommitConnection.pageInfo":
+		if e.complexity.GitCommitConnection.PageInfo == nil {
+			break
+		}
+
+		return e.complexity.GitCommitConnection.PageInfo(childComplexity), true
+
+	case "GitCommitConnection.totalCount":
+		if e.complexity.GitCommitConnection.TotalCount == nil {
+			break
+		}
+
+		return e.complexity.GitCommitConnection.TotalCount(childComplexity), true
+
+	case "GitDiffHunk.lines":
+		if e.complexity.GitDiffHunk.Lines == nil {
+			break
+		}
+
+		return e.complexity.GitDiffHunk.Lines(childComplexity), true
+
+	case "GitDiffHunk.newLines":
+		if e.complexity.GitDiffHunk.NewLines == nil {
+			break
+		}
+
+		return e.complexity.GitDiffHunk.NewLines(childComplexity), true
+
+	case "GitDiffHunk.newStart":
+		if e.complexity.GitDiffHunk.NewStart == nil {
+			break
+		}
+
+		return e.complexity.GitDiffHunk.NewStart(childComplexity), true
+
+	case "GitDiffHunk.oldLines":
+		if e.complexity.GitDiffHunk.OldLines == nil {
+			break
+		}
+
+		return e.complexity.GitDiffHunk.OldLines(childComplexity), true
+
+	case "GitDiffHunk.oldStart":
+		if e.complexity.GitDiffHunk.OldStart == nil {
+			break
+		}
+
+		return e.complexity.GitDiffHunk.OldStart(childComplexity), true
+
+	case "GitDiffLine.content":
+		if e.complexity.GitDiffLine.Content == nil {
+			break
+		}
+
+		return e.complexity.GitDiffLine.Content(childComplexity), true
+
+	case "GitDiffLine.newLine":
+		if e.complexity.GitDiffLine.NewLine == nil {
+			break
+		}
+
+		return e.complexity.GitDiffLine.NewLine(childComplexity), true
+
+	case "GitDiffLine.oldLine":
+		if e.complexity.GitDiffLine.OldLine == nil {
+			break
+		}
+
+		return e.complexity.GitDiffLine.OldLine(childComplexity), true
+
+	case "GitDiffLine.type":
+		if e.complexity.GitDiffLine.Type == nil {
+			break
+		}
+
+		return e.complexity.GitDiffLine.Type(childComplexity), true
+
+	case "GitFileDiff.hunks":
+		if e.complexity.GitFileDiff.Hunks == nil {
+			break
+		}
+
+		return e.complexity.GitFileDiff.Hunks(childComplexity), true
+
+	case "GitFileDiff.isBinary":
+		if e.complexity.GitFileDiff.IsBinary == nil {
+			break
+		}
+
+		return e.complexity.GitFileDiff.IsBinary(childComplexity), true
+
+	case "GitFileDiff.isDelete":
+		if e.complexity.GitFileDiff.IsDelete == nil {
+			break
+		}
+
+		return e.complexity.GitFileDiff.IsDelete(childComplexity), true
+
+	case "GitFileDiff.isNew":
+		if e.complexity.GitFileDiff.IsNew == nil {
+			break
+		}
+
+		return e.complexity.GitFileDiff.IsNew(childComplexity), true
+
+	case "GitFileDiff.oldPath":
+		if e.complexity.GitFileDiff.OldPath == nil {
+			break
+		}
+
+		return e.complexity.GitFileDiff.OldPath(childComplexity), true
+
+	case "GitFileDiff.path":
+		if e.complexity.GitFileDiff.Path == nil {
+			break
+		}
+
+		return e.complexity.GitFileDiff.Path(childComplexity), true
+
+	case "GitLastCommit.commit":
+		if e.complexity.GitLastCommit.Commit == nil {
+			break
+		}
+
+		return e.complexity.GitLastCommit.Commit(childComplexity), true
+
+	case "GitLastCommit.name":
+		if e.complexity.GitLastCommit.Name == nil {
+			break
+		}
+
+		return e.complexity.GitLastCommit.Name(childComplexity), true
+
+	case "GitRef.hash":
+		if e.complexity.GitRef.Hash == nil {
+			break
+		}
+
+		return e.complexity.GitRef.Hash(childComplexity), true
+
+	case "GitRef.isDefault":
+		if e.complexity.GitRef.IsDefault == nil {
+			break
+		}
+
+		return e.complexity.GitRef.IsDefault(childComplexity), true
+
+	case "GitRef.name":
+		if e.complexity.GitRef.Name == nil {
+			break
+		}
+
+		return e.complexity.GitRef.Name(childComplexity), true
+
+	case "GitRef.shortName":
+		if e.complexity.GitRef.ShortName == nil {
+			break
+		}
+
+		return e.complexity.GitRef.ShortName(childComplexity), true
+
+	case "GitRef.type":
+		if e.complexity.GitRef.Type == nil {
+			break
+		}
+
+		return e.complexity.GitRef.Type(childComplexity), true
+
+	case "GitRefConnection.nodes":
+		if e.complexity.GitRefConnection.Nodes == nil {
+			break
+		}
+
+		return e.complexity.GitRefConnection.Nodes(childComplexity), true
+
+	case "GitRefConnection.pageInfo":
+		if e.complexity.GitRefConnection.PageInfo == nil {
+			break
+		}
+
+		return e.complexity.GitRefConnection.PageInfo(childComplexity), true
+
+	case "GitRefConnection.totalCount":
+		if e.complexity.GitRefConnection.TotalCount == nil {
+			break
+		}
+
+		return e.complexity.GitRefConnection.TotalCount(childComplexity), true
+
+	case "GitTreeEntry.hash":
+		if e.complexity.GitTreeEntry.Hash == nil {
+			break
+		}
+
+		return e.complexity.GitTreeEntry.Hash(childComplexity), true
+
+	case "GitTreeEntry.name":
+		if e.complexity.GitTreeEntry.Name == nil {
+			break
+		}
+
+		return e.complexity.GitTreeEntry.Name(childComplexity), true
+
+	case "GitTreeEntry.type":
+		if e.complexity.GitTreeEntry.ObjectType == nil {
+			break
+		}
+
+		return e.complexity.GitTreeEntry.ObjectType(childComplexity), true
+
 	case "Identity.avatarUrl":
 		if e.complexity.Identity.AvatarUrl == nil {
 			break
@@ -1819,6 +2297,18 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
 
 		return e.complexity.Repository.AllIdentities(childComplexity, args["after"].(*string), args["before"].(*string), args["first"].(*int), args["last"].(*int)), true
 
+	case "Repository.blob":
+		if e.complexity.Repository.Blob == nil {
+			break
+		}
+
+		args, err := ec.field_Repository_blob_args(ctx, rawArgs)
+		if err != nil {
+			return 0, false
+		}
+
+		return e.complexity.Repository.Blob(childComplexity, args["ref"].(string), args["path"].(string)), true
+
 	case "Repository.bug":
 		if e.complexity.Repository.Bug == nil {
 			break
@@ -1831,6 +2321,30 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
 
 		return e.complexity.Repository.Bug(childComplexity, args["prefix"].(string)), true
 
+	case "Repository.commit":
+		if e.complexity.Repository.Commit == nil {
+			break
+		}
+
+		args, err := ec.field_Repository_commit_args(ctx, rawArgs)
+		if err != nil {
+			return 0, false
+		}
+
+		return e.complexity.Repository.Commit(childComplexity, args["hash"].(string)), true
+
+	case "Repository.commits":
+		if e.complexity.Repository.Commits == nil {
+			break
+		}
+
+		args, err := ec.field_Repository_commits_args(ctx, rawArgs)
+		if err != nil {
+			return 0, false
+		}
+
+		return e.complexity.Repository.Commits(childComplexity, args["after"].(*string), args["first"].(*int), args["ref"].(string), args["path"].(*string), args["since"].(*time.Time), args["until"].(*time.Time)), true
+
 	case "Repository.identity":
 		if e.complexity.Repository.Identity == nil {
 			break
@@ -1843,6 +2357,18 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
 
 		return e.complexity.Repository.Identity(childComplexity, args["prefix"].(string)), true
 
+	case "Repository.lastCommits":
+		if e.complexity.Repository.LastCommits == nil {
+			break
+		}
+
+		args, err := ec.field_Repository_lastCommits_args(ctx, rawArgs)
+		if err != nil {
+			return 0, false
+		}
+
+		return e.complexity.Repository.LastCommits(childComplexity, args["ref"].(string), args["path"].(*string), args["names"].([]string)), true
+
 	case "Repository.name":
 		if e.complexity.Repository.Name == nil {
 			break
@@ -1850,6 +2376,30 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
 
 		return e.complexity.Repository.Name(childComplexity), true
 
+	case "Repository.refs":
+		if e.complexity.Repository.Refs == nil {
+			break
+		}
+
+		args, err := ec.field_Repository_refs_args(ctx, rawArgs)
+		if err != nil {
+			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
+
+	case "Repository.tree":
+		if e.complexity.Repository.Tree == nil {
+			break
+		}
+
+		args, err := ec.field_Repository_tree_args(ctx, rawArgs)
+		if err != nil {
+			return 0, false
+		}
+
+		return e.complexity.Repository.Tree(childComplexity, args["ref"].(string), args["path"].(*string)), true
+
 	case "Repository.userIdentity":
 		if e.complexity.Repository.UserIdentity == nil {
 			break
@@ -2601,6 +3151,225 @@ directive @goTag(
     key: String!
     value: String
 ) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION
+
+directive @goEnum(
+    value: String
+) on ENUM_VALUE
+`, BuiltIn: false},
+	{Name: "../schema/git.graphql", Input: `"""A git branch or tag reference."""
+type GitRef {
+    """Full reference name, e.g. refs/heads/main or refs/tags/v1.0."""
+    name: String!
+    """Short name, e.g. main or v1.0."""
+    shortName: String!
+    """Whether this reference is a branch or a tag."""
+    type: GitRefType!
+    """Commit hash the reference points to."""
+    hash: String!
+    """True for the branch HEAD currently points to."""
+    isDefault: Boolean!
+}
+
+"""An entry in a git tree (directory listing)."""
+type GitTreeEntry
+@goModel(model: "github.com/git-bug/git-bug/repository.TreeEntry") {
+    """File or directory name within the parent tree."""
+    name: String!
+    """Whether this entry is a file, directory, symlink, or submodule."""
+    type: GitObjectType! @goField(name: "ObjectType")
+    """Git object hash."""
+    hash: String!
+}
+
+"""The content of a git blob (file)."""
+type GitBlob {
+    """Path of the file relative to the repository root."""
+    path: String!
+    """Git object hash. Can be used as a stable cache key or to construct a
+    raw download URL."""
+    hash: String!
+    """UTF-8 text content of the file. Null when isBinary is true or when
+    the file is too large to be returned inline (see isTruncated)."""
+    text: String
+    """Size in bytes."""
+    size: Int!
+    """True when the file contains null bytes and is treated as binary.
+    text will be null."""
+    isBinary: Boolean!
+    """True when the file exceeds the maximum inline size and text has been
+    omitted. Use the raw download endpoint to retrieve the full content."""
+    isTruncated: Boolean!
+}
+
+"""Metadata for a single git commit."""
+type GitCommit
+@goModel(model: "github.com/git-bug/git-bug/api/graphql/models.GitCommitMeta") {
+    """Full SHA-1 commit hash."""
+    hash: String!
+    """Abbreviated commit hash, typically 8 characters."""
+    shortHash: String!
+    """First line of the commit message."""
+    message: String!
+    """Full commit message."""
+    fullMessage: String!
+    """Name of the commit author."""
+    authorName: String!
+    """Email address of the commit author."""
+    authorEmail: String!
+    """Timestamp from the author field (when the change was originally made)."""
+    date: Time!
+    """Hashes of parent commits. Empty for the initial commit."""
+    parents: [String!]!
+    """Files changed relative to the first parent (or the empty tree for the
+    initial commit)."""
+    files(
+        """Returns the elements in the list that come after the specified cursor."""
+        after: String
+        """Returns the elements in the list that come before the specified cursor."""
+        before: String
+        """Returns the first _n_ elements from the list."""
+        first: Int
+        """Returns the last _n_ elements from the list."""
+        last: Int
+    ): GitChangedFileConnection!
+    """Unified diff for a single file in this commit."""
+    diff(path: String!): GitFileDiff
+}
+
+"""The last commit that touched each requested entry in a directory."""
+type GitLastCommit {
+    """Entry name within the directory."""
+    name: String!
+    """Most recent commit that modified this entry."""
+    commit: GitCommit!
+}
+
+# ── connection types ──────────────────────────────────────────────────────────
+
+type GitRefConnection {
+    nodes: [GitRef!]!
+    pageInfo: PageInfo!
+    totalCount: Int!
+}
+
+"""Paginated list of commits."""
+type GitCommitConnection {
+    nodes: [GitCommit!]!
+    pageInfo: PageInfo!
+    totalCount: Int!
+}
+
+type GitChangedFileConnection {
+    nodes: [GitChangedFile!]!
+    pageInfo: PageInfo!
+    totalCount: Int!
+}
+
+# ── commit sub-types ──────────────────────────────────────────────────────────
+
+"""A file that was changed in a commit."""
+type GitChangedFile
+@goModel(model: "github.com/git-bug/git-bug/repository.ChangedFile") {
+    """Path of the file in the new version of the commit."""
+    path: String!
+    """Previous path, non-null only for renames."""
+    oldPath: String
+    """How the file was affected by the commit."""
+    status: GitChangeStatus!
+}
+
+"""The diff for a single file in a commit."""
+type GitFileDiff
+@goModel(model: "github.com/git-bug/git-bug/repository.FileDiff") {
+    """Path of the file in the new version."""
+    path: String!
+    """Previous path, non-null only for renames."""
+    oldPath: String
+    """True when the file is binary and no textual diff is available."""
+    isBinary: Boolean!
+    """True when the file was created in this commit."""
+    isNew: Boolean!
+    """True when the file was deleted in this commit."""
+    isDelete: Boolean!
+    """Contiguous blocks of changes. Empty for binary files."""
+    hunks: [GitDiffHunk!]!
+}
+
+"""A contiguous block of changes in a unified diff."""
+type GitDiffHunk
+@goModel(model: "github.com/git-bug/git-bug/repository.DiffHunk") {
+    """Starting line number in the old file."""
+    oldStart: Int!
+    """Number of lines from the old file included in this hunk."""
+    oldLines: Int!
+    """Starting line number in the new file."""
+    newStart: Int!
+    """Number of lines from the new file included in this hunk."""
+    newLines: Int!
+    """Lines in this hunk, including context, additions, and deletions."""
+    lines: [GitDiffLine!]!
+}
+
+"""A single line in a unified diff hunk."""
+type GitDiffLine
+@goModel(model: "github.com/git-bug/git-bug/repository.DiffLine") {
+    """Whether this line is context, an addition, or a deletion."""
+    type: GitDiffLineType!
+    """Raw line content, without the leading +/- prefix."""
+    content: String!
+    """Line number in the old file. 0 for added lines."""
+    oldLine: Int!
+    """Line number in the new file. 0 for deleted lines."""
+    newLine: Int!
+}
+
+# ── enums ─────────────────────────────────────────────────────────────────────
+
+"""The kind of git reference: a branch or a tag."""
+enum GitRefType
+@goModel(model: "github.com/git-bug/git-bug/api/graphql/models.GitRefType") {
+    """A local branch (refs/heads/*)."""
+    BRANCH @goEnum(value: "github.com/git-bug/git-bug/api/graphql/models.GitRefTypeBranch")
+    """An annotated or lightweight tag (refs/tags/*)."""
+    TAG @goEnum(value: "github.com/git-bug/git-bug/api/graphql/models.GitRefTypeTag")
+}
+
+"""The type of object a git tree entry points to."""
+enum GitObjectType
+@goModel(model: "github.com/git-bug/git-bug/repository.ObjectType") {
+    """A directory."""
+    TREE
+    """A regular or executable file."""
+    BLOB
+    """A symbolic link."""
+    SYMLINK
+    """A git submodule."""
+    SUBMODULE
+}
+
+"""How a file was affected by a commit."""
+enum GitChangeStatus
+@goModel(model: "github.com/git-bug/git-bug/repository.ChangeStatus") {
+    """File was created in this commit."""
+    ADDED
+    """File content changed in this commit."""
+    MODIFIED
+    """File was removed in this commit."""
+    DELETED
+    """File was moved or renamed in this commit."""
+    RENAMED
+}
+
+"""The role of a line within a unified diff hunk."""
+enum GitDiffLineType
+@goModel(model: "github.com/git-bug/git-bug/repository.DiffLineType") {
+    """An unchanged line present in both old and new versions."""
+    CONTEXT
+    """A line added in the new version."""
+    ADDED
+    """A line removed from the old version."""
+    DELETED
+}
 `, BuiltIn: false},
 	{Name: "../schema/identity.graphql", Input: `"""Represents an identity"""
 type Identity implements Entity {
@@ -2734,6 +3503,53 @@ type OperationEdge {
     """The identity created or selected by the user as its own"""
     userIdentity: Identity
 
+    """All branches and tags, optionally filtered by type."""
+    refs(
+        """Returns the elements in the list that come after the specified cursor."""
+        after: String
+        """Returns the elements in the list that come before the specified cursor."""
+        before: String
+        """Returns the first _n_ elements from the list."""
+        first: Int
+        """Returns the last _n_ elements from the list."""
+        last: Int
+        """Restrict to references of this type."""
+        type: GitRefType
+    ): GitRefConnection!
+
+    """Directory listing at path under ref. An empty path returns the root tree."""
+    tree(ref: String!, path: String): [GitTreeEntry!]!
+
+    """Content of the file at path under ref. Null if the path does not exist
+    or resolves to a tree rather than a blob."""
+    blob(ref: String!, path: String!): GitBlob
+
+    """Paginated commit log reachable from ref, optionally filtered to commits
+    touching path."""
+    commits(
+        """Returns the elements in the list that come after the specified cursor."""
+        after: String
+        """Returns the first _n_ elements from the list (max 100, default 20)."""
+        first: Int
+        """Branch name, tag name, full ref (e.g. refs/heads/main), or commit hash
+        to start the log from."""
+        ref: String!
+        """Restrict to commits that touched this path."""
+        path: String
+        """Restrict to commits authored on or after this timestamp."""
+        since: Time
+        """Restrict to commits authored before or on this timestamp."""
+        until: Time
+    ): GitCommitConnection!
+
+    """A single commit by hash."""
+    commit(hash: String!): GitCommit
+
+    """The most recent commit that touched each of the named entries in the
+    directory at path under ref. Use this to populate last-commit info on a
+    tree listing without blocking the initial tree fetch."""
+    lastCommits(ref: String!, path: String, names: [String!]!): [GitLastCommit!]!
+
     """List of valid labels."""
     validLabels(
         """Returns the elements in the list that come after the specified cursor."""

api/graphql/graph/types.generated.go πŸ”—

@@ -809,4 +809,22 @@ func (ec *executionContext) marshalOHash2αš•githubαš—comαš‹gitαš‘bugαš‹gitαš‘bug
 	return ret
 }
 
+func (ec *executionContext) unmarshalOTime2αš–timeᚐTime(ctx context.Context, v any) (*time.Time, error) {
+	if v == nil {
+		return nil, nil
+	}
+	res, err := graphql.UnmarshalTime(v)
+	return &res, graphql.ErrorOnPath(ctx, err)
+}
+
+func (ec *executionContext) marshalOTime2αš–timeᚐTime(ctx context.Context, sel ast.SelectionSet, v *time.Time) graphql.Marshaler {
+	if v == nil {
+		return graphql.Null
+	}
+	_ = sel
+	_ = ctx
+	res := graphql.MarshalTime(*v)
+	return res
+}
+
 // endregion ***************************** type.gotpl *****************************

api/graphql/graphql_test.go πŸ”—

@@ -220,6 +220,232 @@ func TestQueries(t *testing.T) {
 	assert.NoError(t, err)
 }
 
+// TestGitBrowseQueries exercises the git-browsing GraphQL fields (commit, blob,
+// tree, commits, lastCommits) against a synthetic fixture repo with the same
+// commit graph used by RepoBrowseTest:
+//
+//	c1 ── c2 ── c3   refs/heads/main
+//	       └────────  refs/heads/feature
+//	c1 ←── refs/tags/v1.0
+func TestGitBrowseQueries(t *testing.T) {
+	repo := repository.CreateGoGitTestRepo(t, false)
+	require.NoError(t, repo.LocalConfig().StoreString("init.defaultBranch", "main"))
+
+	// ── build fixture ─────────────────────────────────────────────────────────
+
+	readmeV1 := []byte("# Hello\n")
+	readmeV3 := []byte("# Hello\n\n## Updated\n")
+	mainV1 := []byte("package main\n")
+	mainV2 := []byte("package main\n\n// updated\n")
+	libV1 := []byte("package lib\n")
+	utilV1 := []byte("package util\n")
+
+	hReadmeV1, err := repo.StoreData(readmeV1)
+	require.NoError(t, err)
+	hReadmeV3, err := repo.StoreData(readmeV3)
+	require.NoError(t, err)
+	hMainV1, err := repo.StoreData(mainV1)
+	require.NoError(t, err)
+	hMainV2, err := repo.StoreData(mainV2)
+	require.NoError(t, err)
+	hLibV1, err := repo.StoreData(libV1)
+	require.NoError(t, err)
+	hUtilV1, err := repo.StoreData(utilV1)
+	require.NoError(t, err)
+
+	srcTreeV1, err := repo.StoreTree([]repository.TreeEntry{
+		{ObjectType: repository.Blob, Hash: hLibV1, Name: "lib.go"},
+	})
+	require.NoError(t, err)
+	rootTreeV1, err := repo.StoreTree([]repository.TreeEntry{
+		{ObjectType: repository.Blob, Hash: hReadmeV1, Name: "README.md"},
+		{ObjectType: repository.Blob, Hash: hMainV1, Name: "main.go"},
+		{ObjectType: repository.Tree, Hash: srcTreeV1, Name: "src"},
+	})
+	require.NoError(t, err)
+
+	srcTreeV2, err := repo.StoreTree([]repository.TreeEntry{
+		{ObjectType: repository.Blob, Hash: hLibV1, Name: "lib.go"},
+		{ObjectType: repository.Blob, Hash: hUtilV1, Name: "util.go"},
+	})
+	require.NoError(t, err)
+	rootTreeV2, err := repo.StoreTree([]repository.TreeEntry{
+		{ObjectType: repository.Blob, Hash: hReadmeV1, Name: "README.md"},
+		{ObjectType: repository.Blob, Hash: hMainV2, Name: "main.go"},
+		{ObjectType: repository.Tree, Hash: srcTreeV2, Name: "src"},
+	})
+	require.NoError(t, err)
+
+	rootTreeV3, err := repo.StoreTree([]repository.TreeEntry{
+		{ObjectType: repository.Blob, Hash: hReadmeV3, Name: "README.md"},
+		{ObjectType: repository.Blob, Hash: hMainV2, Name: "main.go"},
+		{ObjectType: repository.Tree, Hash: srcTreeV2, Name: "src"},
+	})
+	require.NoError(t, err)
+
+	c1, err := repo.StoreCommit(rootTreeV1)
+	require.NoError(t, err)
+	c2, err := repo.StoreCommit(rootTreeV2, c1)
+	require.NoError(t, err)
+	c3, err := repo.StoreCommit(rootTreeV3, c2)
+	require.NoError(t, err)
+
+	require.NoError(t, repo.UpdateRef("refs/heads/main", c3))
+	require.NoError(t, repo.UpdateRef("refs/heads/feature", c2))
+	require.NoError(t, repo.UpdateRef("refs/tags/v1.0", c1))
+
+	// ── set up GraphQL handler ─────────────────────────────────────────────────
+
+	mrc := cache.NewMultiRepoCache()
+	_, events := mrc.RegisterDefaultRepository(repo)
+	for event := range events {
+		require.NoError(t, event.Err)
+	}
+	c := client.New(NewHandler(mrc, nil))
+
+	// ── commit ────────────────────────────────────────────────────────────────
+
+	t.Run("commit", func(t *testing.T) {
+		var resp struct {
+			Repository struct {
+				Commit struct {
+					Hash    string
+					Parents []string
+				}
+			}
+		}
+		require.NoError(t, c.Post(`query($hash: String!) {
+			repository { commit(hash: $hash) { hash parents } }
+		}`, &resp, client.Var("hash", string(c3))))
+		got := resp.Repository.Commit
+		require.Equal(t, string(c3), got.Hash)
+		require.Equal(t, []string{string(c2)}, got.Parents)
+	})
+
+	t.Run("commit_not_found", func(t *testing.T) {
+		var resp struct {
+			Repository struct {
+				Commit *struct{ Hash string }
+			}
+		}
+		require.NoError(t, c.Post(`query {
+			repository { commit(hash: "0000000000000000000000000000000000000000") { hash } }
+		}`, &resp))
+		require.Nil(t, resp.Repository.Commit)
+	})
+
+	// ── blob ──────────────────────────────────────────────────────────────────
+
+	t.Run("blob", func(t *testing.T) {
+		var resp struct {
+			Repository struct {
+				Blob struct {
+					Hash     string
+					IsBinary bool
+					Size     int
+					Text     *string
+				}
+			}
+		}
+		require.NoError(t, c.Post(`query {
+			repository { blob(ref: "main", path: "README.md") { hash isBinary size text } }
+		}`, &resp))
+		got := resp.Repository.Blob
+		require.Equal(t, string(hReadmeV3), got.Hash)
+		require.False(t, got.IsBinary)
+		require.Equal(t, len(readmeV3), got.Size)
+		require.NotNil(t, got.Text)
+		require.Equal(t, string(readmeV3), *got.Text)
+	})
+
+	t.Run("blob_not_found", func(t *testing.T) {
+		var resp struct {
+			Repository struct {
+				Blob *struct{ Hash string }
+			}
+		}
+		require.NoError(t, c.Post(`query {
+			repository { blob(ref: "main", path: "nonexistent.go") { hash } }
+		}`, &resp))
+		require.Nil(t, resp.Repository.Blob)
+	})
+
+	// ── tree ──────────────────────────────────────────────────────────────────
+
+	t.Run("tree", func(t *testing.T) {
+		var resp struct {
+			Repository struct {
+				Tree []struct {
+					Name string
+					Type string `json:"type"`
+				}
+			}
+		}
+		require.NoError(t, c.Post(`query {
+			repository { tree(ref: "main", path: "") { name type } }
+		}`, &resp))
+		byName := make(map[string]string)
+		for _, e := range resp.Repository.Tree {
+			byName[e.Name] = e.Type
+		}
+		require.Equal(t, "BLOB", byName["README.md"])
+		require.Equal(t, "BLOB", byName["main.go"])
+		require.Equal(t, "TREE", byName["src"])
+	})
+
+	// ── commits ───────────────────────────────────────────────────────────────
+
+	t.Run("commits", func(t *testing.T) {
+		var resp struct {
+			Repository struct {
+				Commits struct {
+					TotalCount int
+					PageInfo   struct{ HasNextPage bool }
+					Nodes      []struct{ Hash string }
+				}
+			}
+		}
+		require.NoError(t, c.Post(`query {
+			repository {
+				commits(ref: "main", first: 2) {
+					totalCount pageInfo { hasNextPage } nodes { hash }
+				}
+			}
+		}`, &resp))
+		got := resp.Repository.Commits
+		require.Equal(t, 2, got.TotalCount)
+		require.True(t, got.PageInfo.HasNextPage)
+		require.Equal(t, string(c3), got.Nodes[0].Hash)
+		require.Equal(t, string(c2), got.Nodes[1].Hash)
+	})
+
+	// ── lastCommits ───────────────────────────────────────────────────────────
+
+	t.Run("lastCommits", func(t *testing.T) {
+		var resp struct {
+			Repository struct {
+				LastCommits []struct {
+					Name   string
+					Commit struct{ Hash string }
+				}
+			}
+		}
+		require.NoError(t, c.Post(`query {
+			repository {
+				lastCommits(ref: "main", names: ["README.md", "main.go"]) {
+					name commit { hash }
+				}
+			}
+		}`, &resp))
+		byName := make(map[string]string)
+		for _, lc := range resp.Repository.LastCommits {
+			byName[lc.Name] = lc.Commit.Hash
+		}
+		require.Equal(t, string(c3), byName["README.md"]) // changed in c3
+		require.Equal(t, string(c2), byName["main.go"])   // changed in c2
+	})
+}
+
 func TestBugEventsSubscription(t *testing.T) {
 	repo := repository.CreateGoGitTestRepo(t, false)
 

api/graphql/models/enums.go πŸ”—

@@ -0,0 +1,58 @@
+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
+}

api/graphql/models/gen_models.go πŸ”—

@@ -269,6 +269,67 @@ type EntityEvent struct {
 	Entity Entity                `json:"entity,omitempty"`
 }
 
+// The content of a git blob (file).
+type GitBlob struct {
+	// Path of the file relative to the repository root.
+	Path string `json:"path"`
+	// Git object hash. Can be used as a stable cache key or to construct a
+	//     raw download URL.
+	Hash string `json:"hash"`
+	// UTF-8 text content of the file. Null when isBinary is true or when
+	//     the file is too large to be returned inline (see isTruncated).
+	Text *string `json:"text,omitempty"`
+	// Size in bytes.
+	Size int `json:"size"`
+	// True when the file contains null bytes and is treated as binary.
+	//     text will be null.
+	IsBinary bool `json:"isBinary"`
+	// True when the file exceeds the maximum inline size and text has been
+	//     omitted. Use the raw download endpoint to retrieve the full content.
+	IsTruncated bool `json:"isTruncated"`
+}
+
+type GitChangedFileConnection struct {
+	Nodes      []*repository.ChangedFile `json:"nodes"`
+	PageInfo   *PageInfo                 `json:"pageInfo"`
+	TotalCount int                       `json:"totalCount"`
+}
+
+// Paginated list of commits.
+type GitCommitConnection struct {
+	Nodes      []*GitCommitMeta `json:"nodes"`
+	PageInfo   *PageInfo        `json:"pageInfo"`
+	TotalCount int              `json:"totalCount"`
+}
+
+// The last commit that touched each requested entry in a directory.
+type GitLastCommit struct {
+	// Entry name within the directory.
+	Name string `json:"name"`
+	// Most recent commit that modified this entry.
+	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"`
+	// True for the branch HEAD currently points to.
+	IsDefault bool `json:"isDefault"`
+}
+
+type GitRefConnection struct {
+	Nodes      []*GitRef `json:"nodes"`
+	PageInfo   *PageInfo `json:"pageInfo"`
+	TotalCount int       `json:"totalCount"`
+}
+
 type IdentityConnection struct {
 	Edges      []*IdentityEdge   `json:"edges"`
 	Nodes      []IdentityWrapper `json:"nodes"`

api/graphql/models/models.go πŸ”—

@@ -3,6 +3,7 @@ package models
 
 import (
 	"github.com/git-bug/git-bug/cache"
+	"github.com/git-bug/git-bug/repository"
 )
 
 type ConnectionInput struct {
@@ -13,6 +14,12 @@ type ConnectionInput struct {
 }
 
 type Repository struct {
-	Cache *cache.MultiRepoCache
-	Repo  *cache.RepoCache
+	Repo *cache.RepoCache
+}
+
+// GitCommitMeta is a wrapper around a CommitMeta that includes the Repo,
+// to keep the repo context in sub-resolvers.
+type GitCommitMeta struct {
+	Repo *cache.RepoCache
+	repository.CommitMeta
 }

api/graphql/resolvers/git.go πŸ”—

@@ -0,0 +1,74 @@
+package resolvers
+
+import (
+	"context"
+
+	"github.com/git-bug/git-bug/api/graphql/connections"
+	"github.com/git-bug/git-bug/api/graphql/graph"
+	"github.com/git-bug/git-bug/api/graphql/models"
+	"github.com/git-bug/git-bug/cache"
+	"github.com/git-bug/git-bug/repository"
+)
+
+const blobTruncateSize = 1 << 20 // 1 MiB
+
+var _ graph.GitCommitResolver = &gitCommitResolver{}
+
+type gitCommitResolver struct {
+	cache *cache.MultiRepoCache
+}
+
+func (r gitCommitResolver) ShortHash(_ context.Context, obj *models.GitCommitMeta) (string, error) {
+	s := string(obj.Hash)
+	if len(s) > 8 {
+		s = s[:8]
+	}
+	return s, nil
+}
+
+func (r gitCommitResolver) FullMessage(_ context.Context, obj *models.GitCommitMeta) (string, error) {
+	repo := obj.Repo.BrowseRepo()
+	detail, err := repo.CommitDetail(obj.Hash)
+	if err != nil {
+		return "", err
+	}
+	return detail.FullMessage, nil
+}
+
+func (r gitCommitResolver) Parents(_ context.Context, obj *models.GitCommitMeta) ([]string, error) {
+	out := make([]string, len(obj.Parents))
+	for i, h := range obj.Parents {
+		out[i] = string(h)
+	}
+	return out, nil
+}
+
+func (r gitCommitResolver) Files(_ context.Context, obj *models.GitCommitMeta, after *string, before *string, first *int, last *int) (*models.GitChangedFileConnection, error) {
+	repo := obj.Repo.BrowseRepo()
+	detail, err := repo.CommitDetail(obj.Hash)
+	if err != nil {
+		return nil, err
+	}
+
+	input := models.ConnectionInput{After: after, Before: before, First: first, Last: last}
+	edger := func(f repository.ChangedFile, offset int) connections.Edge {
+		return connections.CursorEdge{Cursor: connections.OffsetToCursor(offset)}
+	}
+	conMaker := func(_ []*connections.CursorEdge, nodes []repository.ChangedFile, info *models.PageInfo, total int) (*models.GitChangedFileConnection, error) {
+		ptrs := make([]*repository.ChangedFile, len(nodes))
+		for i := range nodes {
+			ptrs[i] = &nodes[i]
+		}
+		return &models.GitChangedFileConnection{Nodes: ptrs, PageInfo: info, TotalCount: total}, nil
+	}
+	return connections.Connection(detail.Files, edger, conMaker, input)
+}
+
+func (r gitCommitResolver) Diff(_ context.Context, obj *models.GitCommitMeta, path string) (*repository.FileDiff, error) {
+	repo := obj.Repo.BrowseRepo()
+	fd, err := repo.CommitFileDiff(obj.Hash, path)
+	if err != nil {
+		return nil, err
+	}
+	return &fd, nil
+}

api/graphql/resolvers/query.go πŸ”—

@@ -26,12 +26,11 @@ func (r rootQueryResolver) Repository(_ context.Context, ref *string) (*models.R
 	}
 
 	if err != nil {
-		return nil, err
+		return nil, nil
 	}
 
 	return &models.Repository{
-		Cache: r.cache,
-		Repo:  repo,
+		Repo: repo,
 	}, nil
 }
 
@@ -48,7 +47,7 @@ func (r rootQueryResolver) Repositories(_ context.Context, after *string, before
 
 	edger := func(repo *cache.RepoCache, offset int) connections.Edge {
 		return models.RepositoryEdge{
-			Node:   &models.Repository{Cache: r.cache, Repo: repo},
+			Node:   &models.Repository{Repo: repo},
 			Cursor: connections.OffsetToCursor(offset),
 		}
 	}

api/graphql/resolvers/repo.go πŸ”—

@@ -1,7 +1,13 @@
 package resolvers
 
 import (
+	"bytes"
 	"context"
+	"errors"
+	"io"
+	"math"
+	"sort"
+	"time"
 
 	"github.com/git-bug/git-bug/api/auth"
 	"github.com/git-bug/git-bug/api/graphql/connections"
@@ -10,6 +16,7 @@ import (
 	"github.com/git-bug/git-bug/entities/common"
 	"github.com/git-bug/git-bug/entity"
 	"github.com/git-bug/git-bug/query"
+	"github.com/git-bug/git-bug/repository"
 )
 
 var _ graph.RepositoryResolver = &repoResolver{}
@@ -17,6 +24,9 @@ var _ graph.RepositoryResolver = &repoResolver{}
 type repoResolver struct{}
 
 func (repoResolver) Name(_ context.Context, obj *models.Repository) (*string, error) {
+	if obj.Repo.IsDefaultRepo() {
+		return nil, nil
+	}
 	name := obj.Repo.Name()
 	return &name, nil
 }
@@ -87,6 +97,9 @@ func (repoResolver) AllBugs(_ context.Context, obj *models.Repository, after *st
 
 func (repoResolver) Bug(_ context.Context, obj *models.Repository, prefix string) (models.BugWrapper, error) {
 	excerpt, err := obj.Repo.Bugs().ResolveExcerptPrefix(prefix)
+	if entity.IsErrNotFound(err) {
+		return nil, nil
+	}
 	if err != nil {
 		return nil, err
 	}
@@ -146,6 +159,9 @@ func (repoResolver) AllIdentities(_ context.Context, obj *models.Repository, aft
 
 func (repoResolver) Identity(_ context.Context, obj *models.Repository, prefix string) (models.IdentityWrapper, error) {
 	excerpt, err := obj.Repo.Identities().ResolveExcerptPrefix(prefix)
+	if entity.IsErrNotFound(err) {
+		return nil, nil
+	}
 	if err != nil {
 		return nil, err
 	}
@@ -189,3 +205,216 @@ 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) {
+	repo := obj.Repo.BrowseRepo()
+
+	var refs []*models.GitRef
+
+	if typeArg == nil || *typeArg == models.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),
+				IsDefault: b.IsDefault,
+			})
+		}
+	}
+
+	if typeArg == nil || *typeArg == models.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),
+			})
+		}
+	}
+
+	// Sort by type (branches before tags) then by short name for stable cursors.
+	sort.Slice(refs, func(i, j int) bool {
+		if refs[i].Type != refs[j].Type {
+			return refs[i].Type < refs[j].Type
+		}
+		return refs[i].ShortName < refs[j].ShortName
+	})
+
+	input := models.ConnectionInput{After: after, Before: before, First: first, Last: last}
+	edger := func(r *models.GitRef, offset int) connections.Edge {
+		return connections.CursorEdge{Cursor: connections.OffsetToCursor(offset)}
+	}
+	conMaker := func(edges []*connections.CursorEdge, nodes []*models.GitRef, info *models.PageInfo, total int) (*models.GitRefConnection, error) {
+		return &models.GitRefConnection{Nodes: nodes, PageInfo: info, TotalCount: total}, nil
+	}
+	return connections.Connection(refs, edger, conMaker, input)
+}
+
+func (repoResolver) Tree(_ context.Context, obj *models.Repository, ref string, path *string) ([]*repository.TreeEntry, error) {
+	repo := obj.Repo.BrowseRepo()
+	p := ""
+	if path != nil {
+		p = *path
+	}
+	entries, err := repo.TreeAtPath(ref, p)
+	if err != nil {
+		return nil, err
+	}
+	ptrs := make([]*repository.TreeEntry, len(entries))
+	for i := range entries {
+		ptrs[i] = &entries[i]
+	}
+	return ptrs, nil
+}
+
+func (repoResolver) Blob(_ context.Context, obj *models.Repository, ref string, path string) (*models.GitBlob, error) {
+	repo := obj.Repo.BrowseRepo()
+	rc, size, hash, err := repo.BlobAtPath(ref, path)
+	if errors.Is(err, repository.ErrNotFound) {
+		return nil, nil
+	}
+	if err != nil {
+		return nil, err
+	}
+	defer rc.Close()
+
+	limited := io.LimitReader(rc, blobTruncateSize+1)
+	data, err := io.ReadAll(limited)
+	if err != nil {
+		return nil, err
+	}
+
+	// Binary detection: same heuristic as git β€” a null byte anywhere in the
+	// content means binary. Git caps its probe at 8000 bytes; we probe all
+	// bytes read (up to blobTruncateSize+1) before slicing, so a NUL in the
+	// extra byte also triggers the flag. Files whose first blobTruncateSize
+	// bytes are all non-NUL will be reported as text even if the remainder is
+	// binary; this is a documented prefix-based heuristic.
+	isBinary := bytes.IndexByte(data, 0) >= 0
+
+	isTruncated := int64(len(data)) > blobTruncateSize
+	if isTruncated {
+		data = data[:blobTruncateSize]
+	}
+
+	blob := &models.GitBlob{
+		Path: path,
+		Hash: string(hash),
+		// GraphQL Int is 32-bit; clamp to avoid overflow on 32-bit platforms or for
+		// exceptionally large files (which will be truncated anyway).
+		Size:        int(min(size, int64(math.MaxInt32))),
+		IsBinary:    isBinary,
+		IsTruncated: isTruncated,
+	}
+	if !isBinary {
+		text := string(data)
+		blob.Text = &text
+	}
+	return blob, nil
+}
+
+func (repoResolver) Commits(_ context.Context, obj *models.Repository, after *string, first *int, ref string, path *string, since *time.Time, until *time.Time) (*models.GitCommitConnection, error) {
+	// This is not using the normal relay pagination (connection.Connection()), because that requires having the
+	// full list in memory. Here, go-git does a partial walk only, which is better.
+
+	repo := obj.Repo.BrowseRepo()
+
+	p := ""
+	if path != nil {
+		p = *path
+	}
+
+	const defaultFirst = 20
+	const maxFirst = 100
+
+	n := defaultFirst
+	if first != nil {
+		n = *first
+		if n > maxFirst {
+			n = maxFirst
+		}
+	}
+	limit := n + 1 // fetch one extra to detect hasNextPage
+
+	var afterHash repository.Hash
+	if after != nil {
+		afterHash = repository.Hash(*after)
+	}
+
+	commits, err := repo.CommitLog(ref, p, limit, afterHash, since, until)
+	if err != nil {
+		return nil, err
+	}
+
+	hasNextPage := false
+	if len(commits) > n {
+		hasNextPage = true
+		commits = commits[:n]
+	}
+
+	nodes := make([]*models.GitCommitMeta, len(commits))
+	for i := range commits {
+		nodes[i] = &models.GitCommitMeta{Repo: obj.Repo, CommitMeta: commits[i]}
+	}
+
+	startCursor := ""
+	endCursor := ""
+	if len(nodes) > 0 {
+		startCursor = string(nodes[0].Hash)
+		endCursor = string(nodes[len(nodes)-1].Hash)
+	}
+
+	return &models.GitCommitConnection{
+		Nodes: nodes,
+		PageInfo: &models.PageInfo{
+			HasNextPage:     hasNextPage,
+			HasPreviousPage: after != nil,
+			StartCursor:     startCursor,
+			EndCursor:       endCursor,
+		},
+		TotalCount: len(nodes), // lower bound; exact total unknown without full walk
+	}, nil
+}
+
+func (repoResolver) Commit(_ context.Context, obj *models.Repository, hash string) (*models.GitCommitMeta, error) {
+	repo := obj.Repo.BrowseRepo()
+	detail, err := repo.CommitDetail(repository.Hash(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
+}
+
+func (repoResolver) LastCommits(_ context.Context, obj *models.Repository, ref string, path *string, names []string) ([]*models.GitLastCommit, error) {
+	repo := obj.Repo.BrowseRepo()
+	p := ""
+	if path != nil {
+		p = *path
+	}
+	byName, err := repo.LastCommitForEntries(ref, p, names)
+	if err != nil {
+		return nil, err
+	}
+	// Iterate over the input names to preserve caller-specified order.
+	result := make([]*models.GitLastCommit, 0, len(names))
+	for _, name := range names {
+		if meta, ok := byName[name]; ok {
+			m := meta
+			result = append(result, &models.GitLastCommit{Name: name, Commit: &models.GitCommitMeta{Repo: obj.Repo, CommitMeta: m}})
+		}
+	}
+	return result, nil
+}

api/graphql/resolvers/root.go πŸ”—

@@ -56,3 +56,9 @@ func (RootResolver) Repository() graph.RepositoryResolver {
 func (RootResolver) Bug() graph.BugResolver {
 	return &bugResolver{}
 }
+
+func (r RootResolver) GitCommit() graph.GitCommitResolver {
+	return &gitCommitResolver{
+		cache: r.MultiRepoCache,
+	}
+}

api/graphql/schema/git.graphql πŸ”—

@@ -0,0 +1,214 @@
+"""A git branch or tag reference."""
+type GitRef {
+    """Full reference name, e.g. refs/heads/main or refs/tags/v1.0."""
+    name: String!
+    """Short name, e.g. main or v1.0."""
+    shortName: String!
+    """Whether this reference is a branch or a tag."""
+    type: GitRefType!
+    """Commit hash the reference points to."""
+    hash: String!
+    """True for the branch HEAD currently points to."""
+    isDefault: Boolean!
+}
+
+"""An entry in a git tree (directory listing)."""
+type GitTreeEntry
+@goModel(model: "github.com/git-bug/git-bug/repository.TreeEntry") {
+    """File or directory name within the parent tree."""
+    name: String!
+    """Whether this entry is a file, directory, symlink, or submodule."""
+    type: GitObjectType! @goField(name: "ObjectType")
+    """Git object hash."""
+    hash: String!
+}
+
+"""The content of a git blob (file)."""
+type GitBlob {
+    """Path of the file relative to the repository root."""
+    path: String!
+    """Git object hash. Can be used as a stable cache key or to construct a
+    raw download URL."""
+    hash: String!
+    """UTF-8 text content of the file. Null when isBinary is true or when
+    the file is too large to be returned inline (see isTruncated)."""
+    text: String
+    """Size in bytes."""
+    size: Int!
+    """True when the file contains null bytes and is treated as binary.
+    text will be null."""
+    isBinary: Boolean!
+    """True when the file exceeds the maximum inline size and text has been
+    omitted. Use the raw download endpoint to retrieve the full content."""
+    isTruncated: Boolean!
+}
+
+"""Metadata for a single git commit."""
+type GitCommit
+@goModel(model: "github.com/git-bug/git-bug/api/graphql/models.GitCommitMeta") {
+    """Full SHA-1 commit hash."""
+    hash: String!
+    """Abbreviated commit hash, typically 8 characters."""
+    shortHash: String!
+    """First line of the commit message."""
+    message: String!
+    """Full commit message."""
+    fullMessage: String!
+    """Name of the commit author."""
+    authorName: String!
+    """Email address of the commit author."""
+    authorEmail: String!
+    """Timestamp from the author field (when the change was originally made)."""
+    date: Time!
+    """Hashes of parent commits. Empty for the initial commit."""
+    parents: [String!]!
+    """Files changed relative to the first parent (or the empty tree for the
+    initial commit)."""
+    files(
+        """Returns the elements in the list that come after the specified cursor."""
+        after: String
+        """Returns the elements in the list that come before the specified cursor."""
+        before: String
+        """Returns the first _n_ elements from the list."""
+        first: Int
+        """Returns the last _n_ elements from the list."""
+        last: Int
+    ): GitChangedFileConnection!
+    """Unified diff for a single file in this commit."""
+    diff(path: String!): GitFileDiff
+}
+
+"""The last commit that touched each requested entry in a directory."""
+type GitLastCommit {
+    """Entry name within the directory."""
+    name: String!
+    """Most recent commit that modified this entry."""
+    commit: GitCommit!
+}
+
+# ── connection types ──────────────────────────────────────────────────────────
+
+type GitRefConnection {
+    nodes: [GitRef!]!
+    pageInfo: PageInfo!
+    totalCount: Int!
+}
+
+"""Paginated list of commits."""
+type GitCommitConnection {
+    nodes: [GitCommit!]!
+    pageInfo: PageInfo!
+    totalCount: Int!
+}
+
+type GitChangedFileConnection {
+    nodes: [GitChangedFile!]!
+    pageInfo: PageInfo!
+    totalCount: Int!
+}
+
+# ── commit sub-types ──────────────────────────────────────────────────────────
+
+"""A file that was changed in a commit."""
+type GitChangedFile
+@goModel(model: "github.com/git-bug/git-bug/repository.ChangedFile") {
+    """Path of the file in the new version of the commit."""
+    path: String!
+    """Previous path, non-null only for renames."""
+    oldPath: String
+    """How the file was affected by the commit."""
+    status: GitChangeStatus!
+}
+
+"""The diff for a single file in a commit."""
+type GitFileDiff
+@goModel(model: "github.com/git-bug/git-bug/repository.FileDiff") {
+    """Path of the file in the new version."""
+    path: String!
+    """Previous path, non-null only for renames."""
+    oldPath: String
+    """True when the file is binary and no textual diff is available."""
+    isBinary: Boolean!
+    """True when the file was created in this commit."""
+    isNew: Boolean!
+    """True when the file was deleted in this commit."""
+    isDelete: Boolean!
+    """Contiguous blocks of changes. Empty for binary files."""
+    hunks: [GitDiffHunk!]!
+}
+
+"""A contiguous block of changes in a unified diff."""
+type GitDiffHunk
+@goModel(model: "github.com/git-bug/git-bug/repository.DiffHunk") {
+    """Starting line number in the old file."""
+    oldStart: Int!
+    """Number of lines from the old file included in this hunk."""
+    oldLines: Int!
+    """Starting line number in the new file."""
+    newStart: Int!
+    """Number of lines from the new file included in this hunk."""
+    newLines: Int!
+    """Lines in this hunk, including context, additions, and deletions."""
+    lines: [GitDiffLine!]!
+}
+
+"""A single line in a unified diff hunk."""
+type GitDiffLine
+@goModel(model: "github.com/git-bug/git-bug/repository.DiffLine") {
+    """Whether this line is context, an addition, or a deletion."""
+    type: GitDiffLineType!
+    """Raw line content, without the leading +/- prefix."""
+    content: String!
+    """Line number in the old file. 0 for added lines."""
+    oldLine: Int!
+    """Line number in the new file. 0 for deleted lines."""
+    newLine: Int!
+}
+
+# ── enums ─────────────────────────────────────────────────────────────────────
+
+"""The kind of git reference: a branch or a tag."""
+enum GitRefType
+@goModel(model: "github.com/git-bug/git-bug/api/graphql/models.GitRefType") {
+    """A local branch (refs/heads/*)."""
+    BRANCH @goEnum(value: "github.com/git-bug/git-bug/api/graphql/models.GitRefTypeBranch")
+    """An annotated or lightweight tag (refs/tags/*)."""
+    TAG @goEnum(value: "github.com/git-bug/git-bug/api/graphql/models.GitRefTypeTag")
+}
+
+"""The type of object a git tree entry points to."""
+enum GitObjectType
+@goModel(model: "github.com/git-bug/git-bug/repository.ObjectType") {
+    """A directory."""
+    TREE
+    """A regular or executable file."""
+    BLOB
+    """A symbolic link."""
+    SYMLINK
+    """A git submodule."""
+    SUBMODULE
+}
+
+"""How a file was affected by a commit."""
+enum GitChangeStatus
+@goModel(model: "github.com/git-bug/git-bug/repository.ChangeStatus") {
+    """File was created in this commit."""
+    ADDED
+    """File content changed in this commit."""
+    MODIFIED
+    """File was removed in this commit."""
+    DELETED
+    """File was moved or renamed in this commit."""
+    RENAMED
+}
+
+"""The role of a line within a unified diff hunk."""
+enum GitDiffLineType
+@goModel(model: "github.com/git-bug/git-bug/repository.DiffLineType") {
+    """An unchanged line present in both old and new versions."""
+    CONTEXT
+    """A line added in the new version."""
+    ADDED
+    """A line removed from the old version."""
+    DELETED
+}

api/graphql/schema/repository.graphql πŸ”—

@@ -1,5 +1,5 @@
 type Repository {
-    """The name of the repository"""
+    """The name of the repository. Null for the default (unnamed) repository in a single-repo setup."""
     name: String
 
     """All the bugs"""
@@ -16,6 +16,7 @@ type Repository {
         query: String
     ): BugConnection!
 
+    """Look up a bug by id prefix. Returns null if no bug matches the prefix."""
     bug(prefix: String!): Bug
 
     """All the identities"""
@@ -30,11 +31,59 @@ type Repository {
         last: Int
     ): IdentityConnection!
 
+    """Look up an identity by id prefix. Returns null if no identity matches the prefix."""
     identity(prefix: String!): Identity
 
     """The identity created or selected by the user as its own"""
     userIdentity: Identity
 
+    """All branches and tags, optionally filtered by type."""
+    refs(
+        """Returns the elements in the list that come after the specified cursor."""
+        after: String
+        """Returns the elements in the list that come before the specified cursor."""
+        before: String
+        """Returns the first _n_ elements from the list."""
+        first: Int
+        """Returns the last _n_ elements from the list."""
+        last: Int
+        """Restrict to references of this type."""
+        type: GitRefType
+    ): GitRefConnection!
+
+    """Directory listing at path under ref. An empty path returns the root tree."""
+    tree(ref: String!, path: String): [GitTreeEntry!]!
+
+    """Content of the file at path under ref. Null if the path does not exist
+    or resolves to a tree rather than a blob."""
+    blob(ref: String!, path: String!): GitBlob
+
+    """Paginated commit log reachable from ref, optionally filtered to commits
+    touching path."""
+    commits(
+        """Returns the elements in the list that come after the specified cursor."""
+        after: String
+        """Returns the first _n_ elements from the list (max 100, default 20)."""
+        first: Int
+        """Branch name, tag name, full ref (e.g. refs/heads/main), or commit hash
+        to start the log from."""
+        ref: String!
+        """Restrict to commits that touched this path."""
+        path: String
+        """Restrict to commits authored on or after this timestamp."""
+        since: Time
+        """Restrict to commits authored before or on this timestamp."""
+        until: Time
+    ): GitCommitConnection!
+
+    """A single commit by hash. Returns null if the hash does not exist in the repository."""
+    commit(hash: String!): GitCommit
+
+    """The most recent commit that touched each of the named entries in the
+    directory at path under ref. Use this to populate last-commit info on a
+    tree listing without blocking the initial tree fetch."""
+    lastCommits(ref: String!, path: String, names: [String!]!): [GitLastCommit!]!
+
     """List of valid labels."""
     validLabels(
         """Returns the elements in the list that come after the specified cursor."""

api/graphql/schema/root.graphql πŸ”—

@@ -1,5 +1,6 @@
 type Query {
-    """Access a repository by reference/name. If no ref is given, the default repository is returned if any."""
+    """Access a repository by reference/name. If no ref is given, the default repository is returned if any.
+    Returns null if the referenced repository does not exist."""
     repository(ref: String): Repository
 
     """List all registered repositories."""

cache/repo_cache_common.go πŸ”—

@@ -15,6 +15,16 @@ func (c *RepoCache) Name() string {
 	return c.name
 }
 
+// IsDefaultRepo reports whether this is an unnamed (single-repo) repository.
+func (c *RepoCache) IsDefaultRepo() bool {
+	return c.name == defaultRepoName
+}
+
+// BrowseRepo returns the underlying RepoBrowse implementation.
+func (c *RepoCache) BrowseRepo() repository.RepoBrowse {
+	return c.repo
+}
+
 // LocalConfig give access to the repository scoped configuration
 func (c *RepoCache) LocalConfig() repository.Config {
 	return c.repo.LocalConfig()

repository/browse.go πŸ”—

@@ -0,0 +1,160 @@
+package repository
+
+import (
+	"fmt"
+	"io"
+	"strconv"
+	"time"
+)
+
+// ChangeStatus describes how a file was affected by a commit.
+type ChangeStatus string
+
+const (
+	ChangeStatusAdded    ChangeStatus = "added"
+	ChangeStatusModified ChangeStatus = "modified"
+	ChangeStatusDeleted  ChangeStatus = "deleted"
+	ChangeStatusRenamed  ChangeStatus = "renamed"
+)
+
+func (s ChangeStatus) MarshalGQL(w io.Writer) {
+	switch s {
+	case ChangeStatusAdded:
+		fmt.Fprint(w, strconv.Quote("ADDED"))
+	case ChangeStatusModified:
+		fmt.Fprint(w, strconv.Quote("MODIFIED"))
+	case ChangeStatusDeleted:
+		fmt.Fprint(w, strconv.Quote("DELETED"))
+	case ChangeStatusRenamed:
+		fmt.Fprint(w, strconv.Quote("RENAMED"))
+	default:
+		panic(fmt.Sprintf("unknown ChangeStatus value %q", string(s)))
+	}
+}
+
+func (s *ChangeStatus) UnmarshalGQL(v any) error {
+	str, ok := v.(string)
+	if !ok {
+		return fmt.Errorf("enums must be strings")
+	}
+	switch str {
+	case "ADDED":
+		*s = ChangeStatusAdded
+	case "MODIFIED":
+		*s = ChangeStatusModified
+	case "DELETED":
+		*s = ChangeStatusDeleted
+	case "RENAMED":
+		*s = ChangeStatusRenamed
+	default:
+		return fmt.Errorf("%q is not a valid ChangeStatus", str)
+	}
+	return nil
+}
+
+// DiffLineType is the role of a line within a unified diff hunk.
+type DiffLineType string
+
+const (
+	DiffLineContext DiffLineType = "context"
+	DiffLineAdded   DiffLineType = "added"
+	DiffLineDeleted DiffLineType = "deleted"
+)
+
+func (t DiffLineType) MarshalGQL(w io.Writer) {
+	switch t {
+	case DiffLineContext:
+		fmt.Fprint(w, strconv.Quote("CONTEXT"))
+	case DiffLineAdded:
+		fmt.Fprint(w, strconv.Quote("ADDED"))
+	case DiffLineDeleted:
+		fmt.Fprint(w, strconv.Quote("DELETED"))
+	default:
+		panic(fmt.Sprintf("unknown DiffLineType value %q", string(t)))
+	}
+}
+
+func (t *DiffLineType) UnmarshalGQL(v any) error {
+	str, ok := v.(string)
+	if !ok {
+		return fmt.Errorf("enums must be strings")
+	}
+	switch str {
+	case "CONTEXT":
+		*t = DiffLineContext
+	case "ADDED":
+		*t = DiffLineAdded
+	case "DELETED":
+		*t = DiffLineDeleted
+	default:
+		return fmt.Errorf("%q is not a valid DiffLineType", str)
+	}
+	return nil
+}
+
+// CommitMeta holds the metadata for a single commit, suitable for listing.
+type CommitMeta struct {
+	Hash        Hash
+	Message     string
+	AuthorName  string
+	AuthorEmail string
+	Date        time.Time
+	Parents     []Hash
+}
+
+// ChangedFile describes a file that was modified in a commit.
+type ChangedFile struct {
+	Path    string
+	OldPath *string // non-nil for renames
+	Status  ChangeStatus
+}
+
+// CommitDetail extends CommitMeta with the full message and the list of
+// changed files (relative to the first parent).
+type CommitDetail struct {
+	CommitMeta
+	FullMessage string
+	Files       []ChangedFile
+}
+
+// DiffLine represents one line in a unified diff hunk.
+type DiffLine struct {
+	Type    DiffLineType
+	Content string
+	OldLine int
+	NewLine int
+}
+
+// DiffHunk is a contiguous block of changes in a unified diff.
+type DiffHunk struct {
+	OldStart int
+	OldLines int
+	NewStart int
+	NewLines int
+	Lines    []DiffLine
+}
+
+// FileDiff is the diff for a single file in a commit.
+type FileDiff struct {
+	Path     string
+	OldPath  *string // non-nil for renames
+	IsBinary bool
+	IsNew    bool
+	IsDelete bool
+	Hunks    []DiffHunk
+}
+
+// BranchInfo describes a local branch returned by RepoBrowse.Branches.
+type BranchInfo struct {
+	Name      string
+	Hash      Hash // commit hash
+	IsDefault bool // true for the branch HEAD points to
+}
+
+// TagInfo describes a tag returned by RepoBrowse.Tags.
+type TagInfo struct {
+	Name string
+	// Hash is always the target commit hash.  For annotated tags the tag
+	// object is dereferenced; for lightweight tags this is the ref hash.
+	Hash Hash
+}

repository/common.go πŸ”—

@@ -65,3 +65,10 @@ func deArmorSignature(armoredSig io.Reader) (io.Reader, error) {
 	}
 	return block.Body, nil
 }
+
+func must[T any](v T, err error) T {
+	if err != nil {
+		panic(err)
+	}
+	return v
+}

repository/gogit.go πŸ”—

@@ -19,7 +19,9 @@ import (
 	"github.com/go-git/go-git/v5/config"
 	"github.com/go-git/go-git/v5/plumbing"
 	"github.com/go-git/go-git/v5/plumbing/filemode"
+	fdiff "github.com/go-git/go-git/v5/plumbing/format/diff"
 	"github.com/go-git/go-git/v5/plumbing/object"
+	lru "github.com/hashicorp/golang-lru/v2"
 	"golang.org/x/sync/errgroup"
 	"golang.org/x/sys/execabs"
 
@@ -29,6 +31,17 @@ import (
 const clockPath = "clocks"
 const indexPath = "indexes"
 
+// lastCommitDepthLimit is the maximum number of commits walked by
+// LastCommitForEntries. Entries not found within this horizon are omitted from
+// the result rather than stalling the caller indefinitely.
+const lastCommitDepthLimit = 1000
+
+// lastCommitCacheSize is the number of (resolvedHash, dirPath) pairs kept in
+// the LRU cache for LastCommitForEntries. Each entry holds one CommitMeta per
+// directory entry (β‰ˆ a few KB for a typical directory), so 256 slots β‰ˆ a few
+// MB of memory at most.
+const lastCommitCacheSize = 256
+
 var _ ClockedRepo = &GoGitRepo{}
 var _ TestedRepo = &GoGitRepo{}
 
@@ -47,6 +60,13 @@ type GoGitRepo struct {
 	indexesMutex sync.Mutex
 	indexes      map[string]Index
 
+	// lastCommitCache caches LastCommitForEntries results keyed by
+	// "<treeHash>\x00<path>". Git trees are content-addressed and
+	// immutable, so entries never need invalidation and can be shared
+	// across refs that point to the same directory tree. The LRU bounds
+	// memory to lastCommitCacheSize unique (treeHash, directory) pairs.
+	lastCommitCache *lru.Cache[string, map[string]CommitMeta]
+
 	keyring      Keyring
 	localStorage LocalStorage
 }
@@ -72,12 +92,13 @@ func OpenGoGitRepo(path, namespace string, clockLoaders []ClockLoader) (*GoGitRe
 	}
 
 	repo := &GoGitRepo{
-		r:            r,
-		path:         path,
-		clocks:       make(map[string]lamport.Clock),
-		indexes:      make(map[string]Index),
-		keyring:      k,
-		localStorage: billyLocalStorage{Filesystem: osfs.New(filepath.Join(path, namespace))},
+		r:               r,
+		path:            path,
+		clocks:          make(map[string]lamport.Clock),
+		indexes:         make(map[string]Index),
+		lastCommitCache: must(lru.New[string, map[string]CommitMeta](lastCommitCacheSize)),
+		keyring:         k,
+		localStorage:    billyLocalStorage{Filesystem: osfs.New(filepath.Join(path, namespace))},
 	}
 
 	loaderToRun := make([]ClockLoader, 0, len(clockLoaders))
@@ -126,12 +147,13 @@ func InitGoGitRepo(path, namespace string) (*GoGitRepo, error) {
 	}
 
 	return &GoGitRepo{
-		r:            r,
-		path:         filepath.Join(path, ".git"),
-		clocks:       make(map[string]lamport.Clock),
-		indexes:      make(map[string]Index),
-		keyring:      k,
-		localStorage: billyLocalStorage{Filesystem: osfs.New(filepath.Join(path, ".git", namespace))},
+		r:               r,
+		path:            filepath.Join(path, ".git"),
+		clocks:          make(map[string]lamport.Clock),
+		indexes:         make(map[string]Index),
+		lastCommitCache: must(lru.New[string, map[string]CommitMeta](lastCommitCacheSize)),
+		keyring:         k,
+		localStorage:    billyLocalStorage{Filesystem: osfs.New(filepath.Join(path, ".git", namespace))},
 	}, nil
 }
 
@@ -151,12 +173,13 @@ func InitBareGoGitRepo(path, namespace string) (*GoGitRepo, error) {
 	}
 
 	return &GoGitRepo{
-		r:            r,
-		path:         path,
-		clocks:       make(map[string]lamport.Clock),
-		indexes:      make(map[string]Index),
-		keyring:      k,
-		localStorage: billyLocalStorage{Filesystem: osfs.New(filepath.Join(path, namespace))},
+		r:               r,
+		path:            path,
+		clocks:          make(map[string]lamport.Clock),
+		indexes:         make(map[string]Index),
+		lastCommitCache: must(lru.New[string, map[string]CommitMeta](lastCommitCacheSize)),
+		keyring:         k,
+		localStorage:    billyLocalStorage{Filesystem: osfs.New(filepath.Join(path, namespace))},
 	}, nil
 }
 
@@ -915,6 +938,739 @@ func (repo *GoGitRepo) Witness(name string, time lamport.Time) error {
 	return c.Witness(time)
 }
 
+// commitToMeta converts a go-git Commit to a CommitMeta.
+func commitToMeta(c *object.Commit) CommitMeta {
+	h := Hash(c.Hash.String())
+	parents := make([]Hash, len(c.ParentHashes))
+	for i, p := range c.ParentHashes {
+		parents[i] = Hash(p.String())
+	}
+	// Use first line of message as the short message.
+	msg := strings.TrimSpace(c.Message)
+	if idx := strings.Index(msg, "\n"); idx >= 0 {
+		msg = msg[:idx]
+	}
+	return CommitMeta{
+		Hash:        h,
+		Message:     msg,
+		AuthorName:  c.Author.Name,
+		AuthorEmail: c.Author.Email,
+		Date:        c.Author.When,
+		Parents:     parents,
+	}
+}
+
+// peelToCommit follows tag objects until it reaches a commit hash.
+// This is necessary for annotated tags, whose ref hash points to a tag object
+// rather than directly to a commit.
+func (repo *GoGitRepo) peelToCommit(h plumbing.Hash) (plumbing.Hash, error) {
+	for {
+		if _, err := repo.r.CommitObject(h); err == nil {
+			return h, nil
+		}
+		tagObj, err := repo.r.TagObject(h)
+		if err != nil {
+			return plumbing.ZeroHash, ErrNotFound
+		}
+		h = tagObj.Target
+	}
+}
+
+// resolveRefToHash resolves a branch/tag name or raw hash to a commit hash.
+// Resolution order: refs/heads/<ref>, refs/tags/<ref>, full ref name, raw commit hash.
+// Annotated tags are peeled to their target commit.
+func (repo *GoGitRepo) resolveRefToHash(ref string) (plumbing.Hash, error) {
+	for _, prefix := range []string{"refs/heads/", "refs/tags/"} {
+		r, err := repo.r.Reference(plumbing.ReferenceName(prefix+ref), true)
+		if err == nil {
+			return repo.peelToCommit(r.Hash())
+		}
+	}
+	// try as a full ref name
+	r, err := repo.r.Reference(plumbing.ReferenceName(ref), true)
+	if err == nil {
+		return repo.peelToCommit(r.Hash())
+	}
+	// try as a raw commit hash
+	h := plumbing.NewHash(ref)
+	if h != plumbing.ZeroHash {
+		if _, err := repo.r.CommitObject(h); err == nil {
+			return h, nil
+		}
+	}
+	return plumbing.ZeroHash, ErrNotFound
+}
+
+// defaultBranchName returns the short name of the default branch.
+func (repo *GoGitRepo) defaultBranchName() string {
+	repo.rMutex.Lock()
+	defer repo.rMutex.Unlock()
+
+	// refs/remotes/origin/HEAD is a symbolic ref set by git clone that points
+	// to the remote's default branch (e.g. refs/remotes/origin/main). It is
+	// the most reliable signal for "what does the upstream consider default".
+	ref, err := repo.r.Reference("refs/remotes/origin/HEAD", false)
+	if err == nil && ref.Type() == plumbing.SymbolicReference {
+		const prefix = "refs/remotes/origin/"
+		if target := ref.Target().String(); strings.HasPrefix(target, prefix) {
+			return strings.TrimPrefix(target, prefix)
+		}
+	}
+	// Fall back to well-known names for repos without a configured remote.
+	for _, name := range []string{"main", "master", "trunk", "develop"} {
+		_, err := repo.r.Reference(plumbing.NewBranchReferenceName(name), false)
+		if err == nil {
+			return name
+		}
+	}
+	return ""
+}
+
+// Branches returns all local branches. IsDefault marks the upstream's default
+// branch, determined in order:
+//  1. refs/remotes/origin/HEAD (set by git clone, reflects the server default)
+//  2. First match among: main, master, trunk, develop
+//  3. No branch marked if none of the above resolve
+func (repo *GoGitRepo) Branches() ([]BranchInfo, error) {
+	defaultBranch := repo.defaultBranchName()
+
+	repo.rMutex.Lock()
+	defer repo.rMutex.Unlock()
+
+	refs, err := repo.r.References()
+	if err != nil {
+		return nil, err
+	}
+
+	var branches []BranchInfo
+	err = refs.ForEach(func(r *plumbing.Reference) error {
+		if !r.Name().IsBranch() {
+			return nil
+		}
+		branches = append(branches, BranchInfo{
+			Name:      r.Name().Short(),
+			Hash:      Hash(r.Hash().String()),
+			IsDefault: r.Name().Short() == defaultBranch,
+		})
+		return nil
+	})
+	if err != nil {
+		return nil, err
+	}
+	if branches == nil {
+		branches = []BranchInfo{}
+	}
+	return branches, nil
+}
+
+// Tags returns all tags. For annotated tags the hash is dereferenced to the
+// target commit; for lightweight tags it is the commit hash directly.
+func (repo *GoGitRepo) Tags() ([]TagInfo, error) {
+	repo.rMutex.Lock()
+	defer repo.rMutex.Unlock()
+
+	refs, err := repo.r.References()
+	if err != nil {
+		return nil, err
+	}
+
+	var tags []TagInfo
+	err = refs.ForEach(func(r *plumbing.Reference) error {
+		if !r.Name().IsTag() {
+			return nil
+		}
+		// Peel to the target commit hash, handling arbitrarily nested tag objects.
+		commit, err := repo.peelToCommit(r.Hash())
+		if err != nil {
+			// Skip refs that don't resolve to a commit (shouldn't happen for tags).
+			return nil
+		}
+		tags = append(tags, TagInfo{
+			Name: r.Name().Short(),
+			Hash: Hash(commit.String()),
+		})
+		return nil
+	})
+	if err != nil {
+		return nil, err
+	}
+	if tags == nil {
+		tags = []TagInfo{}
+	}
+	return tags, nil
+}
+
+// TreeAtPath returns the entries of the directory at path under ref.
+func (repo *GoGitRepo) TreeAtPath(ref, path string) ([]TreeEntry, error) {
+	path = strings.Trim(path, "/")
+
+	repo.rMutex.Lock()
+	defer repo.rMutex.Unlock()
+
+	startHash, err := repo.resolveRefToHash(ref)
+	if err != nil {
+		return nil, ErrNotFound
+	}
+	commit, err := repo.r.CommitObject(startHash)
+	if err != nil {
+		return nil, err
+	}
+	tree, err := commit.Tree()
+	if err != nil {
+		return nil, err
+	}
+	if path != "" {
+		subtree, err := tree.Tree(path)
+		if err != nil {
+			return nil, ErrNotFound
+		}
+		tree = subtree
+	}
+
+	entries := make([]TreeEntry, len(tree.Entries))
+	for i, e := range tree.Entries {
+		entries[i] = TreeEntry{
+			Name:       e.Name,
+			Hash:       Hash(e.Hash.String()),
+			ObjectType: objectTypeFromFileMode(e.Mode),
+		}
+	}
+	return entries, nil
+}
+
+// objectTypeFromFileMode maps a go-git filemode to the repository ObjectType.
+func objectTypeFromFileMode(m filemode.FileMode) ObjectType {
+	switch m {
+	case filemode.Dir:
+		return Tree
+	case filemode.Regular:
+		return Blob
+	case filemode.Executable:
+		return Executable
+	case filemode.Symlink:
+		return Symlink
+	case filemode.Submodule:
+		return Submodule
+	default:
+		return Unknown
+	}
+}
+
+// BlobAtPath returns the content, size, and git object hash of the file at
+// path under ref. rMutex is held for the entire function, covering all
+// shared-Scanner access (CommitObject, Tree, File). The returned reader is
+// safe to use without the mutex: small blobs are already materialized into a
+// MemoryObject (bytes.Reader) by the time File() returns; large blobs come
+// back as an FSObject whose Reader() opens its own independent file handle and
+// Scanner and then reads via ReadAt β€” no shared state is touched after this
+// function returns. Callers must Close the reader.
+func (repo *GoGitRepo) BlobAtPath(ref, path string) (io.ReadCloser, int64, Hash, error) {
+	path = strings.Trim(path, "/")
+	if path == "" {
+		return nil, 0, "", ErrNotFound
+	}
+
+	repo.rMutex.Lock()
+	defer repo.rMutex.Unlock()
+
+	startHash, err := repo.resolveRefToHash(ref)
+	if err != nil {
+		return nil, 0, "", ErrNotFound
+	}
+	commit, err := repo.r.CommitObject(startHash)
+	if err != nil {
+		return nil, 0, "", err
+	}
+	tree, err := commit.Tree()
+	if err != nil {
+		return nil, 0, "", err
+	}
+	f, err := tree.File(path)
+	if err != nil {
+		return nil, 0, "", ErrNotFound
+	}
+	r, err := f.Reader()
+	if err != nil {
+		return nil, 0, "", err
+	}
+
+	return r, f.Blob.Size, Hash(f.Blob.Hash.String()), nil
+}
+
+// CommitLog returns at most limit commits reachable from ref, optionally
+// filtered to those that touched path, starting after the given cursor hash,
+// and bounded by the since/until author-date range.
+func (repo *GoGitRepo) CommitLog(ref, path string, limit int, after Hash, since, until *time.Time) ([]CommitMeta, error) {
+	repo.rMutex.Lock()
+	defer repo.rMutex.Unlock()
+
+	startHash, err := repo.resolveRefToHash(ref)
+	if err != nil {
+		return nil, err
+	}
+
+	// Normalize path: strip leading/trailing slashes so prefix matching works.
+	path = strings.Trim(path, "/")
+
+	opts := &gogit.LogOptions{
+		From:  startHash,
+		Order: gogit.LogOrderCommitterTime,
+	}
+	if path != "" {
+		opts.PathFilter = func(p string) bool {
+			return p == path || strings.HasPrefix(p, path+"/")
+		}
+	}
+
+	iter, err := repo.r.Log(opts)
+	if err != nil {
+		return nil, err
+	}
+	defer iter.Close()
+
+	var result []CommitMeta
+	skipping := after != ""
+	for {
+		c, err := iter.Next()
+		if err == io.EOF {
+			break
+		}
+		if err != nil {
+			return nil, err
+		}
+		h := Hash(c.Hash.String())
+		if skipping {
+			if h == after {
+				skipping = false
+			}
+			continue
+		}
+		if since != nil && c.Author.When.Before(*since) {
+			continue
+		}
+		if until != nil && c.Author.When.After(*until) {
+			continue
+		}
+		result = append(result, commitToMeta(c))
+		if limit > 0 && len(result) >= limit {
+			break
+		}
+	}
+	return result, nil
+}
+
+// treeEntriesAtPath returns the tree hash and a name→entry-hash map for the
+// directory at dirPath inside the given commit. An empty dirPath means the
+// root tree. The tree hash is content-addressed and can be used as a stable
+// cache key regardless of which branch or ref was resolved.
+func treeEntriesAtPath(c *object.Commit, dirPath string) (plumbing.Hash, map[string]plumbing.Hash, error) {
+	tree, err := c.Tree()
+	if err != nil {
+		return plumbing.ZeroHash, nil, err
+	}
+	if dirPath != "" {
+		subtree, err := tree.Tree(dirPath)
+		if err != nil {
+			return plumbing.ZeroHash, nil, err
+		}
+		tree = subtree
+	}
+	result := make(map[string]plumbing.Hash, len(tree.Entries))
+	for _, e := range tree.Entries {
+		result[e.Name] = e.Hash
+	}
+	return tree.Hash, result, nil
+}
+
+// LastCommitForEntries performs a single history walk to find, for each name,
+// the most recent commit that changed that entry in the directory at path.
+//
+// Results are cached by (dirTreeHash, path). Because git trees are
+// content-addressed, two refs that point to the same directory tree share one
+// cache entry, and the cache never needs invalidation: a changed directory
+// produces a new tree hash, which becomes a new key.
+func (repo *GoGitRepo) LastCommitForEntries(ref, path string, names []string) (map[string]CommitMeta, error) {
+	// Normalize path up front so the cache key is canonical.
+	path = strings.Trim(path, "/")
+
+	// Resolve ref and load the current directory tree in one brief lock.
+	// We need the tree hash for the cache key and we keep the entries to
+	// seed the parent-reuse optimisation in the walk below.
+	repo.rMutex.Lock()
+	startHash, err := repo.resolveRefToHash(ref)
+	if err != nil {
+		repo.rMutex.Unlock()
+		return nil, err
+	}
+	startCommit, err := repo.r.CommitObject(startHash)
+	if err != nil {
+		repo.rMutex.Unlock()
+		return nil, err
+	}
+	treeHash, startEntries, err := treeEntriesAtPath(startCommit, path)
+	repo.rMutex.Unlock()
+	if err != nil {
+		// path doesn't exist at HEAD β€” nothing to return.
+		return map[string]CommitMeta{}, nil
+	}
+
+	// The cache is keyed by the directory's tree hash (content-addressed)
+	// plus the path so two directories with identical content but different
+	// locations don't collide.
+	cacheKey := treeHash.String() + "\x00" + path
+
+	// Cache hit: filter the stored result down to the requested names.
+	if cached, ok := repo.lastCommitCache.Get(cacheKey); ok {
+		result := make(map[string]CommitMeta, len(names))
+		for _, n := range names {
+			if m, found := cached[n]; found {
+				result[n] = m
+			}
+		}
+		return result, nil
+	}
+
+	// Cache miss: walk history for ALL entries in this directory so the
+	// cached result is complete and valid for any future name subset.
+	remaining := make(map[string]bool, len(startEntries))
+	for name := range startEntries {
+		remaining[name] = true
+	}
+	result := make(map[string]CommitMeta, len(remaining))
+
+	repo.rMutex.Lock()
+
+	iter, err := repo.r.Log(&gogit.LogOptions{
+		From:  startHash,
+		Order: gogit.LogOrderCommitterTime,
+	})
+	if err != nil {
+		repo.rMutex.Unlock()
+		return nil, err
+	}
+
+	// Seed the parent-reuse cache with the entries we already fetched above
+	// so the first iteration's current-tree read is skipped for free.
+	// In a linear history this halves tree reads for every subsequent step:
+	// the parent fetched at depth D is the current commit at depth D+1.
+	cachedParentHash := startHash
+	cachedParentEntries := startEntries
+
+	for depth := 0; len(remaining) > 0 && depth < lastCommitDepthLimit; depth++ {
+		c, err := iter.Next()
+		if err == io.EOF {
+			break
+		}
+		if err != nil {
+			iter.Close()
+			repo.rMutex.Unlock()
+			return nil, err
+		}
+
+		var currentEntries map[string]plumbing.Hash
+		if c.Hash == cachedParentHash && cachedParentEntries != nil {
+			currentEntries = cachedParentEntries
+		} else {
+			_, currentEntries, err = treeEntriesAtPath(c, path)
+			if err != nil {
+				// path may not exist in this commit; treat as empty
+				currentEntries = map[string]plumbing.Hash{}
+			}
+		}
+
+		var parentEntries map[string]plumbing.Hash
+		cachedParentHash = plumbing.ZeroHash
+		cachedParentEntries = nil
+		if len(c.ParentHashes) > 0 {
+			if parent, err := c.Parents().Next(); err == nil {
+				_, parentEntries, _ = treeEntriesAtPath(parent, path)
+				cachedParentHash = c.ParentHashes[0]
+				cachedParentEntries = parentEntries
+			}
+		}
+
+		meta := commitToMeta(c)
+		for name := range remaining {
+			curHash, inCurrent := currentEntries[name]
+			parentHash, inParent := parentEntries[name]
+			if inCurrent != inParent || (inCurrent && curHash != parentHash) {
+				result[name] = meta
+				delete(remaining, name)
+			}
+		}
+	}
+
+	iter.Close()
+	repo.rMutex.Unlock()
+
+	// Store a defensive copy so that callers cannot mutate cached entries.
+	// The cached map contains all directory entries, not just the requested
+	// names, so future calls for the same directory are fully served from
+	// cache regardless of which names they request.
+	cached := make(map[string]CommitMeta, len(result))
+	for k, v := range result {
+		cached[k] = v
+	}
+	repo.lastCommitCache.Add(cacheKey, cached)
+
+	// Return only the entries that were requested.
+	filtered := make(map[string]CommitMeta, len(names))
+	for _, n := range names {
+		if m, ok := result[n]; ok {
+			filtered[n] = m
+		}
+	}
+	return filtered, nil
+}
+
+// CommitDetail returns the full commit metadata and list of changed files.
+func (repo *GoGitRepo) CommitDetail(hash Hash) (CommitDetail, error) {
+	repo.rMutex.Lock()
+	defer repo.rMutex.Unlock()
+
+	c, err := repo.r.CommitObject(plumbing.NewHash(hash.String()))
+	if err == plumbing.ErrObjectNotFound {
+		return CommitDetail{}, ErrNotFound
+	}
+	if err != nil {
+		return CommitDetail{}, err
+	}
+
+	toTree, err := c.Tree()
+	if err != nil {
+		return CommitDetail{}, err
+	}
+
+	var fromTree *object.Tree
+	if len(c.ParentHashes) > 0 {
+		parent, err := repo.r.CommitObject(c.ParentHashes[0])
+		if err != nil {
+			return CommitDetail{}, fmt.Errorf("loading parent commit: %w", err)
+		}
+		fromTree, err = parent.Tree()
+		if err != nil {
+			return CommitDetail{}, fmt.Errorf("loading parent tree: %w", err)
+		}
+	}
+
+	changes, err := object.DiffTree(fromTree, toTree)
+	if err != nil {
+		return CommitDetail{}, err
+	}
+
+	// Use ch.From.Name / ch.To.Name directly β€” these come from the tree
+	// metadata and do not require reading any blob content.
+	files := make([]ChangedFile, 0, len(changes))
+	for _, ch := range changes {
+		files = append(files, changedFileFromChange(ch.From.Name, ch.To.Name))
+	}
+
+	return CommitDetail{
+		CommitMeta:  commitToMeta(c),
+		FullMessage: c.Message,
+		Files:       files,
+	}, nil
+}
+
+func changedFileFromChange(fromName, toName string) ChangedFile {
+	switch {
+	case fromName == "":
+		return ChangedFile{Path: toName, Status: ChangeStatusAdded}
+	case toName == "":
+		return ChangedFile{Path: fromName, Status: ChangeStatusDeleted}
+	case fromName != toName:
+		op := fromName
+		return ChangedFile{Path: toName, OldPath: &op, Status: ChangeStatusRenamed}
+	default:
+		return ChangedFile{Path: toName, Status: ChangeStatusModified}
+	}
+}
+
+// CommitFileDiff returns the unified diff for a single file in a commit,
+// relative to the first parent.
+func (repo *GoGitRepo) CommitFileDiff(hash Hash, filePath string) (FileDiff, error) {
+	repo.rMutex.Lock()
+	defer repo.rMutex.Unlock()
+
+	c, err := repo.r.CommitObject(plumbing.NewHash(hash.String()))
+	if err == plumbing.ErrObjectNotFound {
+		return FileDiff{}, ErrNotFound
+	}
+	if err != nil {
+		return FileDiff{}, err
+	}
+
+	toTree, err := c.Tree()
+	if err != nil {
+		return FileDiff{}, err
+	}
+
+	var fromTree *object.Tree
+	if len(c.ParentHashes) > 0 {
+		parent, err := repo.r.CommitObject(c.ParentHashes[0])
+		if err != nil {
+			return FileDiff{}, fmt.Errorf("loading parent commit: %w", err)
+		}
+		fromTree, err = parent.Tree()
+		if err != nil {
+			return FileDiff{}, fmt.Errorf("loading parent tree: %w", err)
+		}
+	}
+
+	changes, err := object.DiffTree(fromTree, toTree)
+	if err != nil {
+		return FileDiff{}, err
+	}
+
+	for _, ch := range changes {
+		name := ch.To.Name
+		if name == "" {
+			name = ch.From.Name
+		}
+		// match on either new or old path
+		if name != filePath && ch.From.Name != filePath {
+			continue
+		}
+
+		from, to, err := ch.Files()
+		if err != nil {
+			return FileDiff{}, err
+		}
+
+		patch, err := ch.Patch()
+		if err != nil {
+			return FileDiff{}, err
+		}
+
+		fd := FileDiff{
+			IsNew:    from == nil,
+			IsDelete: to == nil,
+		}
+		if to != nil {
+			fd.Path = to.Name
+		}
+		if from != nil {
+			if fd.Path == "" {
+				fd.Path = from.Name
+			} else if from.Name != fd.Path {
+				op := from.Name
+				fd.OldPath = &op
+			}
+		}
+
+		fps := patch.FilePatches()
+		if len(fps) > 0 {
+			fp := fps[0]
+			fd.IsBinary = fp.IsBinary()
+			if !fd.IsBinary {
+				fd.Hunks = buildDiffHunks(fp)
+			}
+		}
+		return fd, nil
+	}
+	return FileDiff{}, ErrNotFound
+}
+
+// buildDiffHunks converts a go-git FilePatch into DiffHunks with line numbers
+// and context grouping.
+func buildDiffHunks(fp fdiff.FilePatch) []DiffHunk {
+	type pendingLine struct {
+		typ     DiffLineType
+		content string
+		oldLine int
+		newLine int
+	}
+
+	var allLines []pendingLine
+	oldLine, newLine := 1, 1
+	for _, chunk := range fp.Chunks() {
+		lines := strings.Split(chunk.Content(), "\n")
+		// strip trailing empty element produced by a trailing newline
+		if len(lines) > 0 && lines[len(lines)-1] == "" {
+			lines = lines[:len(lines)-1]
+		}
+		switch chunk.Type() {
+		case fdiff.Equal:
+			for _, l := range lines {
+				allLines = append(allLines, pendingLine{DiffLineContext, l, oldLine, newLine})
+				oldLine++
+				newLine++
+			}
+		case fdiff.Add:
+			for _, l := range lines {
+				allLines = append(allLines, pendingLine{DiffLineAdded, l, 0, newLine})
+				newLine++
+			}
+		case fdiff.Delete:
+			for _, l := range lines {
+				allLines = append(allLines, pendingLine{DiffLineDeleted, l, oldLine, 0})
+				oldLine++
+			}
+		}
+	}
+	if len(allLines) == 0 {
+		return nil
+	}
+
+	const ctx = 3 // context lines around each changed block
+
+	// find spans of changed lines
+	type span struct{ start, end int }
+	var spans []span
+	for i, l := range allLines {
+		if l.typ == DiffLineContext {
+			continue
+		}
+		if len(spans) == 0 || i > spans[len(spans)-1].end+1 {
+			spans = append(spans, span{i, i})
+		} else {
+			spans[len(spans)-1].end = i
+		}
+	}
+
+	// expand each span by ctx lines and merge overlapping ones
+	var merged []span
+	for _, s := range spans {
+		s.start = max(0, s.start-ctx)
+		s.end = min(len(allLines)-1, s.end+ctx)
+		if len(merged) > 0 && s.start <= merged[len(merged)-1].end+1 {
+			merged[len(merged)-1].end = s.end
+		} else {
+			merged = append(merged, s)
+		}
+	}
+
+	hunks := make([]DiffHunk, 0, len(merged))
+	for _, s := range merged {
+		segment := allLines[s.start : s.end+1]
+		dl := make([]DiffLine, len(segment))
+		var oldStart, newStart, oldCount, newCount int
+		for i, l := range segment {
+			dl[i] = DiffLine{Type: l.typ, Content: l.content, OldLine: l.oldLine, NewLine: l.newLine}
+			if l.oldLine > 0 {
+				if oldStart == 0 {
+					oldStart = l.oldLine
+				}
+				oldCount++
+			}
+			if l.newLine > 0 {
+				if newStart == 0 {
+					newStart = l.newLine
+				}
+				newCount++
+			}
+		}
+		hunks = append(hunks, DiffHunk{
+			OldStart: oldStart,
+			OldLines: oldCount,
+			NewStart: newStart,
+			NewLines: newCount,
+			Lines:    dl,
+		})
+	}
+	return hunks
+}
+
 // 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 {

repository/mock_repo.go πŸ”—

@@ -4,8 +4,10 @@ import (
 	"bytes"
 	"crypto/sha1"
 	"fmt"
+	"io"
 	"strings"
 	"sync"
+	"time"
 
 	"github.com/99designs/keyring"
 	"github.com/ProtonMail/go-crypto/openpgp"
@@ -24,7 +26,7 @@ type mockRepo struct {
 	*mockRepoCommon
 	*mockRepoStorage
 	*mockRepoIndex
-	*mockRepoData
+	*mockRepoDataBrowse
 	*mockRepoClock
 	*mockRepoTest
 }
@@ -33,14 +35,14 @@ func (m *mockRepo) Close() error { return nil }
 
 func NewMockRepo() *mockRepo {
 	return &mockRepo{
-		mockRepoConfig:  NewMockRepoConfig(),
-		mockRepoKeyring: NewMockRepoKeyring(),
-		mockRepoCommon:  NewMockRepoCommon(),
-		mockRepoStorage: NewMockRepoStorage(),
-		mockRepoIndex:   newMockRepoIndex(),
-		mockRepoData:    NewMockRepoData(),
-		mockRepoClock:   NewMockRepoClock(),
-		mockRepoTest:    NewMockRepoTest(),
+		mockRepoConfig:     NewMockRepoConfig(),
+		mockRepoKeyring:    NewMockRepoKeyring(),
+		mockRepoCommon:     NewMockRepoCommon(),
+		mockRepoStorage:    NewMockRepoStorage(),
+		mockRepoIndex:      newMockRepoIndex(),
+		mockRepoDataBrowse: newMockRepoDataBrowse(),
+		mockRepoClock:      NewMockRepoClock(),
+		mockRepoTest:       NewMockRepoTest(),
 	}
 }
 
@@ -219,47 +221,51 @@ func (m *mockIndex) Close() error {
 	return nil
 }
 
-var _ RepoData = &mockRepoData{}
+var _ RepoData = &mockRepoDataBrowse{}
 
 type commit struct {
 	treeHash Hash
 	parents  []Hash
 	sig      string
+	date     time.Time
+	message  string
 }
 
-type mockRepoData struct {
-	blobs   map[Hash][]byte
-	trees   map[Hash]string
-	commits map[Hash]commit
-	refs    map[string]Hash
+type mockRepoDataBrowse struct {
+	blobs         map[Hash][]byte
+	trees         map[Hash]string
+	commits       map[Hash]commit
+	refs          map[string]Hash
+	defaultBranch string
 }
 
-func NewMockRepoData() *mockRepoData {
-	return &mockRepoData{
-		blobs:   make(map[Hash][]byte),
-		trees:   make(map[Hash]string),
-		commits: make(map[Hash]commit),
-		refs:    make(map[string]Hash),
+func newMockRepoDataBrowse() *mockRepoDataBrowse {
+	return &mockRepoDataBrowse{
+		blobs:         make(map[Hash][]byte),
+		trees:         make(map[Hash]string),
+		commits:       make(map[Hash]commit),
+		refs:          make(map[string]Hash),
+		defaultBranch: "main",
 	}
 }
 
-func (r *mockRepoData) FetchRefs(remote string, prefixes ...string) (string, error) {
+func (r *mockRepoDataBrowse) FetchRefs(remote string, prefixes ...string) (string, error) {
 	panic("implement me")
 }
 
 // PushRefs push git refs to a remote
-func (r *mockRepoData) PushRefs(remote string, prefixes ...string) (string, error) {
+func (r *mockRepoDataBrowse) PushRefs(remote string, prefixes ...string) (string, error) {
 	panic("implement me")
 }
 
-func (r *mockRepoData) StoreData(data []byte) (Hash, error) {
+func (r *mockRepoDataBrowse) StoreData(data []byte) (Hash, error) {
 	rawHash := sha1.Sum(data)
 	hash := Hash(fmt.Sprintf("%x", rawHash))
 	r.blobs[hash] = data
 	return hash, nil
 }
 
-func (r *mockRepoData) ReadData(hash Hash) ([]byte, error) {
+func (r *mockRepoDataBrowse) ReadData(hash Hash) ([]byte, error) {
 	data, ok := r.blobs[hash]
 	if !ok {
 		return nil, ErrNotFound
@@ -268,7 +274,7 @@ func (r *mockRepoData) ReadData(hash Hash) ([]byte, error) {
 	return data, nil
 }
 
-func (r *mockRepoData) StoreTree(entries []TreeEntry) (Hash, error) {
+func (r *mockRepoDataBrowse) StoreTree(entries []TreeEntry) (Hash, error) {
 	buffer := prepareTreeEntries(entries)
 	rawHash := sha1.Sum(buffer.Bytes())
 	hash := Hash(fmt.Sprintf("%x", rawHash))
@@ -277,7 +283,7 @@ func (r *mockRepoData) StoreTree(entries []TreeEntry) (Hash, error) {
 	return hash, nil
 }
 
-func (r *mockRepoData) ReadTree(hash Hash) ([]TreeEntry, error) {
+func (r *mockRepoDataBrowse) ReadTree(hash Hash) ([]TreeEntry, error) {
 	var data string
 
 	data, ok := r.trees[hash]
@@ -300,11 +306,11 @@ func (r *mockRepoData) ReadTree(hash Hash) ([]TreeEntry, error) {
 	return readTreeEntries(data)
 }
 
-func (r *mockRepoData) StoreCommit(treeHash Hash, parents ...Hash) (Hash, error) {
+func (r *mockRepoDataBrowse) StoreCommit(treeHash Hash, parents ...Hash) (Hash, error) {
 	return r.StoreSignedCommit(treeHash, nil, parents...)
 }
 
-func (r *mockRepoData) StoreSignedCommit(treeHash Hash, signKey *openpgp.Entity, parents ...Hash) (Hash, error) {
+func (r *mockRepoDataBrowse) StoreSignedCommit(treeHash Hash, signKey *openpgp.Entity, parents ...Hash) (Hash, error) {
 	hasher := sha1.New()
 	hasher.Write([]byte(treeHash))
 	for _, parent := range parents {
@@ -315,6 +321,7 @@ func (r *mockRepoData) StoreSignedCommit(treeHash Hash, signKey *openpgp.Entity,
 	c := commit{
 		treeHash: treeHash,
 		parents:  parents,
+		date:     time.Now(),
 	}
 	if signKey != nil {
 		// unlike go-git, we only sign the tree hash for simplicity instead of all the fields (parents ...)
@@ -328,7 +335,7 @@ func (r *mockRepoData) StoreSignedCommit(treeHash Hash, signKey *openpgp.Entity,
 	return hash, nil
 }
 
-func (r *mockRepoData) ReadCommit(hash Hash) (Commit, error) {
+func (r *mockRepoDataBrowse) ReadCommit(hash Hash) (Commit, error) {
 	c, ok := r.commits[hash]
 	if !ok {
 		return Commit{}, ErrNotFound
@@ -350,7 +357,7 @@ func (r *mockRepoData) ReadCommit(hash Hash) (Commit, error) {
 	return result, nil
 }
 
-func (r *mockRepoData) ResolveRef(ref string) (Hash, error) {
+func (r *mockRepoDataBrowse) ResolveRef(ref string) (Hash, error) {
 	h, ok := r.refs[ref]
 	if !ok {
 		return "", ErrNotFound
@@ -358,17 +365,17 @@ func (r *mockRepoData) ResolveRef(ref string) (Hash, error) {
 	return h, nil
 }
 
-func (r *mockRepoData) UpdateRef(ref string, hash Hash) error {
+func (r *mockRepoDataBrowse) UpdateRef(ref string, hash Hash) error {
 	r.refs[ref] = hash
 	return nil
 }
 
-func (r *mockRepoData) RemoveRef(ref string) error {
+func (r *mockRepoDataBrowse) RemoveRef(ref string) error {
 	delete(r.refs, ref)
 	return nil
 }
 
-func (r *mockRepoData) ListRefs(refPrefix string) ([]string, error) {
+func (r *mockRepoDataBrowse) ListRefs(refPrefix string) ([]string, error) {
 	var keys []string
 
 	for k := range r.refs {
@@ -380,12 +387,12 @@ func (r *mockRepoData) ListRefs(refPrefix string) ([]string, error) {
 	return keys, nil
 }
 
-func (r *mockRepoData) RefExist(ref string) (bool, error) {
+func (r *mockRepoDataBrowse) RefExist(ref string) (bool, error) {
 	_, exist := r.refs[ref]
 	return exist, nil
 }
 
-func (r *mockRepoData) CopyRef(source string, dest string) error {
+func (r *mockRepoDataBrowse) CopyRef(source string, dest string) error {
 	hash, exist := r.refs[source]
 
 	if !exist {
@@ -396,10 +403,446 @@ func (r *mockRepoData) CopyRef(source string, dest string) error {
 	return nil
 }
 
-func (r *mockRepoData) ListCommits(ref string) ([]Hash, error) {
+func (r *mockRepoDataBrowse) ListCommits(ref string) ([]Hash, error) {
 	return nonNativeListCommits(r, ref)
 }
 
+// resolveRef resolves a ref matching the RepoBrowse contract:
+// refs/heads/<ref>, refs/tags/<ref>, full ref name, raw commit hash.
+func (r *mockRepoDataBrowse) resolveRef(ref string) (Hash, error) {
+	for _, candidate := range []string{"refs/heads/" + ref, "refs/tags/" + ref, ref} {
+		if h, ok := r.refs[candidate]; ok {
+			return h, nil
+		}
+	}
+	if _, ok := r.commits[Hash(ref)]; ok {
+		return Hash(ref), nil
+	}
+	return "", ErrNotFound
+}
+
+// treeEntriesAtHash parses the entries of the tree stored under hash.
+func (r *mockRepoDataBrowse) treeEntriesAtHash(hash Hash) ([]TreeEntry, error) {
+	data, ok := r.trees[hash]
+	if !ok {
+		return nil, ErrNotFound
+	}
+	return readTreeEntries(data)
+}
+
+// treeEntriesAt returns the directory entries at path inside the tree rooted at
+// treeHash. path="" returns root entries. Returns ErrNotFound if path doesn't
+// exist or resolves to a blob rather than a tree.
+func (r *mockRepoDataBrowse) treeEntriesAt(treeHash Hash, path string) ([]TreeEntry, error) {
+	path = strings.Trim(path, "/")
+	if path == "" {
+		return r.treeEntriesAtHash(treeHash)
+	}
+	seg, rest, _ := strings.Cut(path, "/")
+	entries, err := r.treeEntriesAtHash(treeHash)
+	if err != nil {
+		return nil, err
+	}
+	for _, e := range entries {
+		if e.Name != seg || e.ObjectType != Tree {
+			continue
+		}
+		if rest == "" {
+			return r.treeEntriesAtHash(e.Hash)
+		}
+		return r.treeEntriesAt(e.Hash, rest)
+	}
+	return nil, ErrNotFound
+}
+
+// blobHashAt walks the tree to find the blob hash for the file at path.
+func (r *mockRepoDataBrowse) blobHashAt(treeHash Hash, path string) (Hash, error) {
+	path = strings.Trim(path, "/")
+	seg, rest, hasRest := strings.Cut(path, "/")
+	entries, err := r.treeEntriesAtHash(treeHash)
+	if err != nil {
+		return "", err
+	}
+	for _, e := range entries {
+		if e.Name != seg {
+			continue
+		}
+		if !hasRest {
+			return e.Hash, nil
+		}
+		if e.ObjectType != Tree {
+			return "", ErrNotFound
+		}
+		return r.blobHashAt(e.Hash, rest)
+	}
+	return "", ErrNotFound
+}
+
+// diffTrees returns the changed files between two trees, recursing into
+// sub-trees. fromHash=="" means an empty (non-existent) tree.
+func (r *mockRepoDataBrowse) diffTrees(fromHash, toHash Hash, prefix string) []ChangedFile {
+	var fromEntries, toEntries []TreeEntry
+	if fromHash != "" {
+		fromEntries, _ = r.treeEntriesAtHash(fromHash)
+	}
+	if toHash != "" {
+		toEntries, _ = r.treeEntriesAtHash(toHash)
+	}
+
+	fromMap := make(map[string]TreeEntry, len(fromEntries))
+	for _, e := range fromEntries {
+		fromMap[e.Name] = e
+	}
+	toMap := make(map[string]TreeEntry, len(toEntries))
+	for _, e := range toEntries {
+		toMap[e.Name] = e
+	}
+
+	var result []ChangedFile
+	for _, e := range toEntries {
+		path := prefix + e.Name
+		f, existed := fromMap[e.Name]
+		if e.ObjectType == Tree {
+			var sub Hash
+			if existed {
+				sub = f.Hash
+			}
+			result = append(result, r.diffTrees(sub, e.Hash, path+"/")...)
+		} else if !existed {
+			result = append(result, ChangedFile{Path: path, Status: ChangeStatusAdded})
+		} else if f.Hash != e.Hash {
+			result = append(result, ChangedFile{Path: path, Status: ChangeStatusModified})
+		}
+	}
+	for _, f := range fromEntries {
+		if _, exists := toMap[f.Name]; exists {
+			continue
+		}
+		path := prefix + f.Name
+		if f.ObjectType == Tree {
+			result = append(result, r.diffTrees(f.Hash, "", path+"/")...)
+		} else {
+			result = append(result, ChangedFile{Path: path, Status: ChangeStatusDeleted})
+		}
+	}
+	return result
+}
+
+func mockCommitMeta(hash Hash, c commit) CommitMeta {
+	return CommitMeta{
+		Hash:    hash,
+		Parents: c.parents,
+		Date:    c.date,
+		Message: c.message,
+	}
+}
+
+func (r *mockRepoDataBrowse) Branches() ([]BranchInfo, error) {
+	var branches []BranchInfo
+	for ref, hash := range r.refs {
+		name, ok := strings.CutPrefix(ref, "refs/heads/")
+		if !ok {
+			continue
+		}
+		branches = append(branches, BranchInfo{
+			Name:      name,
+			Hash:      hash,
+			IsDefault: name == r.defaultBranch,
+		})
+	}
+	return branches, nil
+}
+
+func (r *mockRepoDataBrowse) Tags() ([]TagInfo, error) {
+	var tags []TagInfo
+	for ref, hash := range r.refs {
+		name, ok := strings.CutPrefix(ref, "refs/tags/")
+		if !ok {
+			continue
+		}
+		tags = append(tags, TagInfo{Name: name, Hash: hash})
+	}
+	return tags, nil
+}
+
+func (r *mockRepoDataBrowse) TreeAtPath(ref, path string) ([]TreeEntry, error) {
+	startHash, err := r.resolveRef(ref)
+	if err != nil {
+		return nil, ErrNotFound
+	}
+	c, ok := r.commits[startHash]
+	if !ok {
+		return nil, ErrNotFound
+	}
+	return r.treeEntriesAt(c.treeHash, path)
+}
+
+func (r *mockRepoDataBrowse) BlobAtPath(ref, path string) (io.ReadCloser, int64, Hash, error) {
+	startHash, err := r.resolveRef(ref)
+	if err != nil {
+		return nil, 0, "", ErrNotFound
+	}
+	c, ok := r.commits[startHash]
+	if !ok {
+		return nil, 0, "", ErrNotFound
+	}
+	blobHash, err := r.blobHashAt(c.treeHash, path)
+	if err != nil {
+		return nil, 0, "", ErrNotFound
+	}
+	data, ok := r.blobs[blobHash]
+	if !ok {
+		return nil, 0, "", ErrNotFound
+	}
+	return io.NopCloser(bytes.NewReader(data)), int64(len(data)), blobHash, nil
+}
+
+func (r *mockRepoDataBrowse) CommitLog(ref, path string, limit int, after Hash, since, until *time.Time) ([]CommitMeta, error) {
+	startHash, err := r.resolveRef(ref)
+	if err != nil {
+		return nil, ErrNotFound
+	}
+	path = strings.Trim(path, "/")
+	var result []CommitMeta
+	skipping := after != ""
+	current := startHash
+	seen := make(map[Hash]bool)
+	for {
+		if seen[current] {
+			break
+		}
+		seen[current] = true
+		c, ok := r.commits[current]
+		if !ok {
+			break
+		}
+		if skipping {
+			if current == after {
+				skipping = false
+			}
+			if len(c.parents) == 0 {
+				break
+			}
+			current = c.parents[0]
+			continue
+		}
+		meta := mockCommitMeta(current, c)
+		if since != nil && meta.Date.Before(*since) {
+			if len(c.parents) == 0 {
+				break
+			}
+			current = c.parents[0]
+			continue
+		}
+		if until != nil && meta.Date.After(*until) {
+			if len(c.parents) == 0 {
+				break
+			}
+			current = c.parents[0]
+			continue
+		}
+		if path != "" {
+			var fromTreeHash Hash
+			if len(c.parents) > 0 {
+				if parent, ok := r.commits[c.parents[0]]; ok {
+					fromTreeHash = parent.treeHash
+				}
+			}
+			touched := false
+			for _, f := range r.diffTrees(fromTreeHash, c.treeHash, "") {
+				if f.Path == path || strings.HasPrefix(f.Path, path+"/") {
+					touched = true
+					break
+				}
+			}
+			if !touched {
+				if len(c.parents) == 0 {
+					break
+				}
+				current = c.parents[0]
+				continue
+			}
+		}
+		result = append(result, meta)
+		if limit > 0 && len(result) >= limit {
+			break
+		}
+		if len(c.parents) == 0 {
+			break
+		}
+		current = c.parents[0]
+	}
+	return result, nil
+}
+
+func (r *mockRepoDataBrowse) LastCommitForEntries(ref, path string, names []string) (map[string]CommitMeta, error) {
+	startHash, err := r.resolveRef(ref)
+	if err != nil {
+		return nil, ErrNotFound
+	}
+	path = strings.Trim(path, "/")
+	remaining := make(map[string]bool, len(names))
+	for _, n := range names {
+		remaining[n] = true
+	}
+	result := make(map[string]CommitMeta)
+	current := startHash
+	seen := make(map[Hash]bool)
+	for len(remaining) > 0 {
+		if seen[current] {
+			break
+		}
+		seen[current] = true
+		c, ok := r.commits[current]
+		if !ok {
+			break
+		}
+		curEntries, err := r.treeEntriesAt(c.treeHash, path)
+		if err != nil {
+			if len(c.parents) == 0 {
+				break
+			}
+			current = c.parents[0]
+			continue
+		}
+		curMap := make(map[string]Hash, len(curEntries))
+		for _, e := range curEntries {
+			curMap[e.Name] = e.Hash
+		}
+		if len(c.parents) == 0 {
+			for name := range remaining {
+				if _, ok := curMap[name]; ok {
+					result[name] = mockCommitMeta(current, c)
+					delete(remaining, name)
+				}
+			}
+			break
+		}
+		pc, ok := r.commits[c.parents[0]]
+		if !ok {
+			break
+		}
+		parentEntries, _ := r.treeEntriesAt(pc.treeHash, path)
+		parentMap := make(map[string]Hash, len(parentEntries))
+		for _, e := range parentEntries {
+			parentMap[e.Name] = e.Hash
+		}
+		for name := range remaining {
+			cur, curExists := curMap[name]
+			par, parExists := parentMap[name]
+			if curExists && (!parExists || cur != par) {
+				result[name] = mockCommitMeta(current, c)
+				delete(remaining, name)
+			}
+		}
+		current = c.parents[0]
+	}
+	return result, nil
+}
+
+func (r *mockRepoDataBrowse) CommitDetail(hash Hash) (CommitDetail, error) {
+	c, ok := r.commits[hash]
+	if !ok {
+		return CommitDetail{}, ErrNotFound
+	}
+	var fromTreeHash Hash
+	if len(c.parents) > 0 {
+		if parent, ok := r.commits[c.parents[0]]; ok {
+			fromTreeHash = parent.treeHash
+		}
+	}
+	return CommitDetail{
+		CommitMeta: mockCommitMeta(hash, c),
+		Files:      r.diffTrees(fromTreeHash, c.treeHash, ""),
+	}, nil
+}
+
+func (r *mockRepoDataBrowse) CommitFileDiff(hash Hash, filePath string) (FileDiff, error) {
+	c, ok := r.commits[hash]
+	if !ok {
+		return FileDiff{}, ErrNotFound
+	}
+	var fromTreeHash Hash
+	if len(c.parents) > 0 {
+		if parent, ok := r.commits[c.parents[0]]; ok {
+			fromTreeHash = parent.treeHash
+		}
+	}
+	files := r.diffTrees(fromTreeHash, c.treeHash, "")
+	var matched *ChangedFile
+	for i := range files {
+		if files[i].Path == filePath {
+			matched = &files[i]
+			break
+		}
+	}
+	if matched == nil {
+		return FileDiff{}, ErrNotFound
+	}
+	fd := FileDiff{
+		Path:     filePath,
+		IsNew:    matched.Status == ChangeStatusAdded,
+		IsDelete: matched.Status == ChangeStatusDeleted,
+	}
+	var oldContent, newContent []byte
+	if fromTreeHash != "" {
+		if bh, err := r.blobHashAt(fromTreeHash, filePath); err == nil {
+			oldContent = r.blobs[bh]
+		}
+	}
+	if bh, err := r.blobHashAt(c.treeHash, filePath); err == nil {
+		newContent = r.blobs[bh]
+	}
+	fd.Hunks = mockDiffHunks(oldContent, newContent)
+	return fd, nil
+}
+
+// mockDiffHunks produces a single DiffHunk using a prefix/suffix scan.
+func mockDiffHunks(old, new []byte) []DiffHunk {
+	oldLines := splitBlobLines(old)
+	newLines := splitBlobLines(new)
+	i := 0
+	for i < len(oldLines) && i < len(newLines) && oldLines[i] == newLines[i] {
+		i++
+	}
+	j, k := len(oldLines), len(newLines)
+	for j > i && k > i && oldLines[j-1] == newLines[k-1] {
+		j--
+		k--
+	}
+	if j == i && k == i {
+		return nil // no changed region
+	}
+	oldLine, newLine := 1, 1
+	var lines []DiffLine
+	for _, l := range oldLines[:i] {
+		lines = append(lines, DiffLine{Type: DiffLineContext, Content: l, OldLine: oldLine, NewLine: newLine})
+		oldLine++
+		newLine++
+	}
+	for _, l := range oldLines[i:j] {
+		lines = append(lines, DiffLine{Type: DiffLineDeleted, Content: l, OldLine: oldLine})
+		oldLine++
+	}
+	for _, l := range newLines[i:k] {
+		lines = append(lines, DiffLine{Type: DiffLineAdded, Content: l, NewLine: newLine})
+		newLine++
+	}
+	for _, l := range oldLines[j:] {
+		lines = append(lines, DiffLine{Type: DiffLineContext, Content: l, OldLine: oldLine, NewLine: newLine})
+		oldLine++
+		newLine++
+	}
+	return []DiffHunk{{OldStart: 1, OldLines: len(oldLines), NewStart: 1, NewLines: len(newLines), Lines: lines}}
+}
+
+func splitBlobLines(data []byte) []string {
+	if len(data) == 0 {
+		return nil
+	}
+	return strings.Split(strings.TrimRight(string(data), "\n"), "\n")
+}
+
 var _ RepoClock = &mockRepoClock{}
 
 type mockRepoClock struct {

repository/repo.go πŸ”—

@@ -4,6 +4,7 @@ package repository
 import (
 	"errors"
 	"io"
+	"time"
 
 	"github.com/ProtonMail/go-crypto/openpgp"
 	"github.com/go-git/go-billy/v5"
@@ -28,6 +29,7 @@ type Repo interface {
 	RepoStorage
 	RepoIndex
 	RepoData
+	RepoBrowse
 
 	Close() error
 }
@@ -182,7 +184,7 @@ type RepoData interface {
 	// ListRefs will return a list of Git ref matching the given refspec
 	ListRefs(refPrefix string) ([]string, error)
 
-	// RefExist will check if a reference exist in Git
+	// RefExist will check if a reference exists in Git
 	RefExist(ref string) (bool, error)
 
 	// CopyRef will create a new reference with the same value as another one
@@ -209,11 +211,63 @@ type RepoClock interface {
 	Witness(name string, time lamport.Time) error
 }
 
+// RepoBrowse is implemented by all Repo implementations and provides
+// code-browsing endpoints (file tree, history, diffs).
+//
+// All methods accepting a ref parameter resolve it in order:
+// refs/heads/<ref>, refs/tags/<ref>, full ref name, raw commit hash.
+type RepoBrowse interface {
+	// Branches returns all local branches (refs/heads/*).
+	// IsDefault marks the branch HEAD points to.
+	// All other ref namespaces β€” including git-bug's internal refs
+	// (refs/bugs/, refs/identities/, …) β€” are excluded.
+	Branches() ([]BranchInfo, error)
+
+	// Tags returns all tags (refs/tags/*).
+	// All other ref namespaces are excluded.
+	Tags() ([]TagInfo, error)
+
+	// TreeAtPath returns the entries of the directory at path under ref.
+	// An empty path returns the root tree.
+	// Returns ErrNotFound if ref or path does not exist, or if path
+	// resolves to a blob rather than a tree.
+	// Symlinks appear as entries with ObjectType Symlink; they are not followed.
+	TreeAtPath(ref, path string) ([]TreeEntry, error)
+
+	// BlobAtPath returns the raw content, byte size, and git object hash of
+	// the file at path under ref. Returns ErrNotFound if ref or path does
+	// not exist, or if path resolves to a tree. Symlinks are not followed.
+	// The caller must close the reader.
+	BlobAtPath(ref, path string) (io.ReadCloser, int64, Hash, error)
+
+	// CommitLog returns at most limit commits reachable from ref, filtered
+	// to those touching path (empty = unrestricted). after is an exclusive
+	// cursor; pass Hash("") for no cursor. since and until bound the author
+	// date (inclusive); pass nil for no bound. Merge commits appear once,
+	// compared against the first parent only.
+	CommitLog(ref, path string, limit int, after Hash, since, until *time.Time) ([]CommitMeta, error)
+
+	// LastCommitForEntries returns the most recent commit that touched each
+	// name in the directory at path under ref. Entries not resolved within
+	// the implementation's depth limit are silently absent from the result.
+	LastCommitForEntries(ref, path string, names []string) (map[string]CommitMeta, error)
+
+	// CommitDetail returns the full metadata and changed-file list for a
+	// single commit identified by its hash. Diffs against the first parent
+	// only; the initial commit is diffed against the empty tree.
+	CommitDetail(hash Hash) (CommitDetail, error)
+
+	// CommitFileDiff returns the unified diff for a single file in a commit
+	// identified by its hash. Diffs against the first parent only; the
+	// initial commit is diffed against the empty tree.
+	CommitFileDiff(hash Hash, filePath string) (FileDiff, error)
+}
+
 // ClockLoader hold which logical clock need to exist for an entity and
 // how to create them if they don't.
 type ClockLoader struct {
-	// Clocks hold the name of all the clocks this loader deal with.
-	// Those clocks will be checked when the repo load. If not present or broken,
+	// Clocks hold the name of all the clocks this loader deals with.
+	// Those clocks will be checked when the repo loads. If not present or broken,
 	// Witnesser will be used to create them.
 	Clocks []string
 	// Witnesser is a function that will initialize the clocks of a repo
@@ -221,13 +275,13 @@ type ClockLoader struct {
 	Witnesser func(repo ClockedRepo) error
 }
 
-// TestedRepo is an extended ClockedRepo with function for testing only
+// TestedRepo is an extended ClockedRepo with functions for testing only
 type TestedRepo interface {
 	ClockedRepo
 	repoTest
 }
 
-// repoTest give access to test only functions
+// repoTest give access to test-only functions
 type repoTest interface {
 	// AddRemote add a new remote to the repository
 	AddRemote(name string, url string) error

repository/repo_testing.go πŸ”—

@@ -1,9 +1,11 @@
 package repository
 
 import (
+	"io"
 	"math/rand"
 	"os"
 	"testing"
+	"time"
 
 	"github.com/ProtonMail/go-crypto/openpgp"
 	"github.com/stretchr/testify/require"
@@ -27,6 +29,10 @@ func RepoTest(t *testing.T, creator RepoCreator) {
 				RepoDataSignatureTest(t, repo)
 			})
 
+			t.Run("Browse", func(t *testing.T) {
+				RepoBrowseTest(t, repo)
+			})
+
 			t.Run("Config", func(t *testing.T) {
 				RepoConfigTest(t, repo)
 			})
@@ -360,3 +366,377 @@ func randomData() []byte {
 	}
 	return b
 }
+
+// browsable is the interface required by RepoBrowseTest.
+type browsable interface {
+	RepoConfig
+	RepoData
+	RepoBrowse
+}
+
+// RepoBrowseTest exercises the RepoBrowse interface against any implementation.
+//
+// Commit graph (oldest β†’ newest):
+//
+//	c1 ── c2 ── c3   refs/heads/main (default)
+//	       └────────  refs/heads/feature
+//	c1 ←── refs/tags/v1.0
+func RepoBrowseTest(t *testing.T, repo browsable) {
+	t.Helper()
+
+	require.NoError(t, repo.LocalConfig().StoreString("init.defaultBranch", "main"))
+
+	// ── build fixture ─────────────────────────────────────────────────────────
+
+	readmeV1 := []byte("# Hello\n")
+	readmeV3 := []byte("# Hello\n\n## Updated\n")
+	mainV1 := []byte("package main\n")
+	mainV2 := []byte("package main\n\n// updated\n")
+	libV1 := []byte("package lib\n")
+	utilV1 := []byte("package util\n")
+
+	hReadmeV1, err := repo.StoreData(readmeV1)
+	require.NoError(t, err)
+	hReadmeV3, err := repo.StoreData(readmeV3)
+	require.NoError(t, err)
+	hMainV1, err := repo.StoreData(mainV1)
+	require.NoError(t, err)
+	hMainV2, err := repo.StoreData(mainV2)
+	require.NoError(t, err)
+	hLibV1, err := repo.StoreData(libV1)
+	require.NoError(t, err)
+	hUtilV1, err := repo.StoreData(utilV1)
+	require.NoError(t, err)
+
+	srcTreeV1, err := repo.StoreTree([]TreeEntry{
+		{ObjectType: Blob, Hash: hLibV1, Name: "lib.go"},
+	})
+	require.NoError(t, err)
+	rootTreeV1, err := repo.StoreTree([]TreeEntry{
+		{ObjectType: Blob, Hash: hReadmeV1, Name: "README.md"},
+		{ObjectType: Blob, Hash: hMainV1, Name: "main.go"},
+		{ObjectType: Tree, Hash: srcTreeV1, Name: "src"},
+	})
+	require.NoError(t, err)
+
+	srcTreeV2, err := repo.StoreTree([]TreeEntry{
+		{ObjectType: Blob, Hash: hLibV1, Name: "lib.go"},
+		{ObjectType: Blob, Hash: hUtilV1, Name: "util.go"},
+	})
+	require.NoError(t, err)
+	rootTreeV2, err := repo.StoreTree([]TreeEntry{
+		{ObjectType: Blob, Hash: hReadmeV1, Name: "README.md"},
+		{ObjectType: Blob, Hash: hMainV2, Name: "main.go"},
+		{ObjectType: Tree, Hash: srcTreeV2, Name: "src"},
+	})
+	require.NoError(t, err)
+
+	rootTreeV3, err := repo.StoreTree([]TreeEntry{
+		{ObjectType: Blob, Hash: hReadmeV3, Name: "README.md"},
+		{ObjectType: Blob, Hash: hMainV2, Name: "main.go"},
+		{ObjectType: Tree, Hash: srcTreeV2, Name: "src"},
+	})
+	require.NoError(t, err)
+
+	c1, err := repo.StoreCommit(rootTreeV1)
+	require.NoError(t, err)
+	c2, err := repo.StoreCommit(rootTreeV2, c1)
+	require.NoError(t, err)
+	c3, err := repo.StoreCommit(rootTreeV3, c2)
+	require.NoError(t, err)
+
+	require.NoError(t, repo.UpdateRef("refs/heads/main", c3))
+	require.NoError(t, repo.UpdateRef("refs/heads/feature", c2))
+	require.NoError(t, repo.UpdateRef("refs/tags/v1.0", c1))
+
+	// ── Branches ──────────────────────────────────────────────────────────────
+
+	t.Run("Branches", func(t *testing.T) {
+		branches, err := repo.Branches()
+		require.NoError(t, err)
+		require.Len(t, branches, 2)
+
+		byName := make(map[string]BranchInfo)
+		for _, b := range branches {
+			byName[b.Name] = b
+		}
+
+		require.Equal(t, c3, byName["main"].Hash)
+		require.True(t, byName["main"].IsDefault)
+
+		require.Equal(t, c2, byName["feature"].Hash)
+		require.False(t, byName["feature"].IsDefault)
+	})
+
+	// ── Tags ──────────────────────────────────────────────────────────────────
+
+	t.Run("Tags", func(t *testing.T) {
+		tags, err := repo.Tags()
+		require.NoError(t, err)
+		require.Len(t, tags, 1)
+		require.Equal(t, "v1.0", tags[0].Name)
+		require.Equal(t, c1, tags[0].Hash)
+	})
+
+	// ── TreeAtPath ────────────────────────────────────────────────────────────
+
+	t.Run("TreeAtPath", func(t *testing.T) {
+		entries, err := repo.TreeAtPath("main", "")
+		require.NoError(t, err)
+		byName := make(map[string]TreeEntry)
+		for _, e := range entries {
+			byName[e.Name] = e
+		}
+		require.Equal(t, Blob, byName["README.md"].ObjectType)
+		require.Equal(t, Blob, byName["main.go"].ObjectType)
+		require.Equal(t, Tree, byName["src"].ObjectType)
+
+		// subdirectory
+		srcEntries, err := repo.TreeAtPath("main", "src")
+		require.NoError(t, err)
+		srcByName := make(map[string]TreeEntry)
+		for _, e := range srcEntries {
+			srcByName[e.Name] = e
+		}
+		require.Equal(t, Blob, srcByName["lib.go"].ObjectType)
+		require.Equal(t, Blob, srcByName["util.go"].ObjectType)
+
+		// v1.0 tag (at c1) predates util.go β€” src only has lib.go
+		v1Src, err := repo.TreeAtPath("v1.0", "src")
+		require.NoError(t, err)
+		require.Len(t, v1Src, 1)
+		require.Equal(t, "lib.go", v1Src[0].Name)
+
+		// unknown ref
+		_, err = repo.TreeAtPath("nonexistent-ref", "")
+		require.ErrorIs(t, err, ErrNotFound)
+
+		// path resolves to a blob, not a tree
+		_, err = repo.TreeAtPath("main", "README.md")
+		require.Error(t, err)
+	})
+
+	// ── BlobAtPath ────────────────────────────────────────────────────────────
+
+	t.Run("BlobAtPath", func(t *testing.T) {
+		rc, size, hash, err := repo.BlobAtPath("main", "README.md")
+		require.NoError(t, err)
+		defer rc.Close()
+		data, err := io.ReadAll(rc)
+		require.NoError(t, err)
+		require.Equal(t, readmeV3, data)
+		require.Equal(t, int64(len(readmeV3)), size)
+		require.NotEmpty(t, hash)
+
+		// feature branch still has readmeV1
+		rc2, _, _, err := repo.BlobAtPath("feature", "README.md")
+		require.NoError(t, err)
+		data2, err := io.ReadAll(rc2)
+		rc2.Close()
+		require.NoError(t, err)
+		require.Equal(t, readmeV1, data2)
+
+		// file in subdirectory
+		rc3, _, _, err := repo.BlobAtPath("main", "src/lib.go")
+		require.NoError(t, err)
+		data3, err := io.ReadAll(rc3)
+		rc3.Close()
+		require.NoError(t, err)
+		require.Equal(t, libV1, data3)
+
+		// path not found
+		_, _, _, err = repo.BlobAtPath("main", "nonexistent.go")
+		require.ErrorIs(t, err, ErrNotFound)
+
+		// hash is stable across calls for the same content
+		rc4, _, hash2, err := repo.BlobAtPath("main", "README.md")
+		require.NoError(t, err)
+		rc4.Close()
+		require.Equal(t, hash, hash2, "blob hash should be stable across calls")
+
+		// different content β†’ different hash
+		rc5, _, hashLib, err := repo.BlobAtPath("main", "src/lib.go")
+		require.NoError(t, err)
+		rc5.Close()
+		require.NotEqual(t, hash, hashLib, "different files should have different hashes")
+	})
+
+	// ── CommitLog ─────────────────────────────────────────────────────────────
+
+	t.Run("CommitLog", func(t *testing.T) {
+		// all commits, newest first
+		commits, err := repo.CommitLog("main", "", 10, "", nil, nil)
+		require.NoError(t, err)
+		require.Len(t, commits, 3)
+		require.Equal(t, c3, commits[0].Hash)
+		require.Equal(t, c2, commits[1].Hash)
+		require.Equal(t, c1, commits[2].Hash)
+
+		// limit
+		limited, err := repo.CommitLog("main", "", 2, "", nil, nil)
+		require.NoError(t, err)
+		require.Len(t, limited, 2)
+		require.Equal(t, c3, limited[0].Hash)
+		require.Equal(t, c2, limited[1].Hash)
+
+		// after cursor (exclusive): start after c3 β†’ get c2, c1
+		after, err := repo.CommitLog("main", "", 10, c3, nil, nil)
+		require.NoError(t, err)
+		require.Len(t, after, 2)
+		require.Equal(t, c2, after[0].Hash)
+		require.Equal(t, c1, after[1].Hash)
+
+		// feature branch only has c1, c2
+		featureLog, err := repo.CommitLog("feature", "", 10, "", nil, nil)
+		require.NoError(t, err)
+		require.Len(t, featureLog, 2)
+		require.Equal(t, c2, featureLog[0].Hash)
+
+		// path filtering: only commits that touched the given path
+		// README.md was created in c1 and updated in c3
+		readmeLog, err := repo.CommitLog("main", "README.md", 10, "", nil, nil)
+		require.NoError(t, err)
+		require.Len(t, readmeLog, 2)
+		require.Equal(t, c3, readmeLog[0].Hash)
+		require.Equal(t, c1, readmeLog[1].Hash)
+	})
+
+	t.Run("CommitLog/since-until", func(t *testing.T) {
+		// since = far future β†’ no commits
+		future := time.Now().Add(24 * time.Hour)
+		none, err := repo.CommitLog("main", "", 10, "", &future, nil)
+		require.NoError(t, err)
+		require.Empty(t, none, "since=future should return no commits")
+
+		// until = zero time (long before any real commit) β†’ no commits
+		zero := time.Time{}
+		none2, err := repo.CommitLog("main", "", 10, "", nil, &zero)
+		require.NoError(t, err)
+		require.Empty(t, none2, "until=zero should return no commits")
+
+		// Both bounds open β†’ all commits returned (filtering is a no-op)
+		all, err := repo.CommitLog("main", "", 10, "", nil, nil)
+		require.NoError(t, err)
+		require.Len(t, all, 3, "nil since/until should return all commits")
+
+		// since = far past and until = far future β†’ all commits still returned
+		past := time.Unix(0, 0)
+		all2, err := repo.CommitLog("main", "", 10, "", &past, &future)
+		require.NoError(t, err)
+		require.Len(t, all2, 3, "wide since/until bounds should return all commits")
+	})
+
+	// ── LastCommitForEntries ──────────────────────────────────────────────────
+
+	t.Run("LastCommitForEntries", func(t *testing.T) {
+		result, err := repo.LastCommitForEntries("main", "", []string{"README.md", "main.go", "src"})
+		require.NoError(t, err)
+
+		// README.md was last changed in c3
+		require.Equal(t, c3, result["README.md"].Hash)
+		// main.go was last changed in c2
+		require.Equal(t, c2, result["main.go"].Hash)
+		// src tree changed in c2 (util.go added)
+		require.Equal(t, c2, result["src"].Hash)
+
+		// subdirectory: last commits for entries in src/
+		srcResult, err := repo.LastCommitForEntries("main", "src", []string{"lib.go", "util.go"})
+		require.NoError(t, err)
+		// lib.go was added in c1 and never changed
+		require.Equal(t, c1, srcResult["lib.go"].Hash)
+		// util.go was added in c2
+		require.Equal(t, c2, srcResult["util.go"].Hash)
+
+		// requesting a name that doesn't exist returns no entry for it
+		partial, err := repo.LastCommitForEntries("main", "", []string{"README.md", "ghost.txt"})
+		require.NoError(t, err)
+		require.Contains(t, partial, "README.md")
+		require.NotContains(t, partial, "ghost.txt")
+	})
+
+	t.Run("LastCommitForEntries/cache-subset", func(t *testing.T) {
+		// First call with one name β€” seeds (or hits) the cache for this directory.
+		r1, err := repo.LastCommitForEntries("main", "", []string{"README.md"})
+		require.NoError(t, err)
+		require.Contains(t, r1, "README.md")
+		require.Equal(t, c3, r1["README.md"].Hash)
+
+		// Second call for the same directory but a different name.
+		// A buggy implementation that caches only the requested subset would
+		// return an empty map here (cache hit, but "main.go" was never stored).
+		r2, err := repo.LastCommitForEntries("main", "", []string{"main.go"})
+		require.NoError(t, err)
+		require.Contains(t, r2, "main.go", "second call with different name should hit correct result, not empty cache")
+		require.Equal(t, c2, r2["main.go"].Hash)
+
+		// Third call requesting both names should also work.
+		r3, err := repo.LastCommitForEntries("main", "", []string{"README.md", "main.go"})
+		require.NoError(t, err)
+		require.Equal(t, c3, r3["README.md"].Hash)
+		require.Equal(t, c2, r3["main.go"].Hash)
+	})
+
+	// ── CommitDetail ──────────────────────────────────────────────────────────
+
+	t.Run("CommitDetail", func(t *testing.T) {
+		detail, err := repo.CommitDetail(c2)
+		require.NoError(t, err)
+		require.Equal(t, c2, detail.Hash)
+		require.Equal(t, []Hash{c1}, detail.Parents)
+
+		filesByPath := make(map[string]ChangedFile)
+		for _, f := range detail.Files {
+			filesByPath[f.Path] = f
+		}
+		require.Equal(t, ChangeStatusModified, filesByPath["main.go"].Status)
+		require.Equal(t, ChangeStatusAdded, filesByPath["src/util.go"].Status)
+
+		// initial commit: diffs against empty tree, everything is "added"
+		initDetail, err := repo.CommitDetail(c1)
+		require.NoError(t, err)
+		for _, f := range initDetail.Files {
+			require.Equal(t, ChangeStatusAdded, f.Status, "file %s", f.Path)
+		}
+
+		// unknown hash
+		_, err = repo.CommitDetail(randomHash())
+		require.ErrorIs(t, err, ErrNotFound)
+	})
+
+	// ── CommitFileDiff ────────────────────────────────────────────────────────
+
+	t.Run("CommitFileDiff", func(t *testing.T) {
+		fd, err := repo.CommitFileDiff(c2, "main.go")
+		require.NoError(t, err)
+		require.Equal(t, "main.go", fd.Path)
+		require.False(t, fd.IsBinary)
+		require.False(t, fd.IsNew)
+		require.False(t, fd.IsDelete)
+		require.NotEmpty(t, fd.Hunks)
+
+		// find the added lines
+		var addedContent []string
+		for _, h := range fd.Hunks {
+			for _, l := range h.Lines {
+				if l.Type == DiffLineAdded {
+					addedContent = append(addedContent, l.Content)
+				}
+			}
+		}
+		require.Contains(t, addedContent, "// updated")
+
+		// new file in initial commit
+		initFD, err := repo.CommitFileDiff(c1, "main.go")
+		require.NoError(t, err)
+		require.True(t, initFD.IsNew)
+		require.Equal(t, "main.go", initFD.Path)
+
+		// file not in this commit's diff
+		_, err = repo.CommitFileDiff(c3, "main.go")
+		require.ErrorIs(t, err, ErrNotFound)
+
+		// unknown hash
+		_, err = repo.CommitFileDiff(randomHash(), "main.go")
+		require.ErrorIs(t, err, ErrNotFound)
+	})
+}

repository/tree_entry.go πŸ”—

@@ -3,6 +3,8 @@ package repository
 import (
 	"bytes"
 	"fmt"
+	"io"
+	"strconv"
 	"strings"
 )
 
@@ -15,9 +17,12 @@ type TreeEntry struct {
 type ObjectType int
 
 const (
-	Unknown ObjectType = iota
-	Blob
-	Tree
+	Unknown    ObjectType = iota
+	Blob                  // regular file      (100644)
+	Tree                  // directory         (040000)
+	Executable            // executable file   (100755)
+	Symlink               // symbolic link     (120000)
+	Submodule             // git submodule     (160000)
 )
 
 func ParseTreeEntry(line string) (TreeEntry, error) {
@@ -54,17 +59,64 @@ func (ot ObjectType) Format() string {
 		return "100644 blob"
 	case Tree:
 		return "040000 tree"
+	case Executable:
+		return "100755 blob"
+	case Symlink:
+		return "120000 blob"
+	case Submodule:
+		return "160000 commit"
 	default:
 		panic("Unknown git object type")
 	}
 }
 
+func (ot ObjectType) MarshalGQL(w io.Writer) {
+	switch ot {
+	case Tree:
+		fmt.Fprint(w, strconv.Quote("TREE"))
+	case Blob, Executable:
+		fmt.Fprint(w, strconv.Quote("BLOB"))
+	case Symlink:
+		fmt.Fprint(w, strconv.Quote("SYMLINK"))
+	case Submodule:
+		fmt.Fprint(w, strconv.Quote("SUBMODULE"))
+	default:
+		panic(fmt.Sprintf("unknown ObjectType value %d", int(ot)))
+	}
+}
+
+func (ot *ObjectType) UnmarshalGQL(v any) error {
+	str, ok := v.(string)
+	if !ok {
+		return fmt.Errorf("enums must be strings")
+	}
+	switch str {
+	case "TREE":
+		*ot = Tree
+	case "BLOB":
+		*ot = Blob
+	case "SYMLINK":
+		*ot = Symlink
+	case "SUBMODULE":
+		*ot = Submodule
+	default:
+		return fmt.Errorf("%q is not a valid ObjectType", str)
+	}
+	return nil
+}
+
 func ParseObjectType(mode, objType string) (ObjectType, error) {
 	switch {
 	case mode == "100644" && objType == "blob":
 		return Blob, nil
 	case mode == "040000" && objType == "tree":
 		return Tree, nil
+	case mode == "100755" && objType == "blob":
+		return Executable, nil
+	case mode == "120000" && objType == "blob":
+		return Symlink, nil
+	case mode == "160000" && objType == "commit":
+		return Submodule, nil
 	default:
 		return Unknown, fmt.Errorf("Unknown git object type %s %s", mode, objType)
 	}