git.go

  1package resolvers
  2
  3import (
  4	"context"
  5	"errors"
  6
  7	"github.com/git-bug/git-bug/api/graphql/connections"
  8	"github.com/git-bug/git-bug/api/graphql/graph"
  9	"github.com/git-bug/git-bug/api/graphql/models"
 10	"github.com/git-bug/git-bug/repository"
 11)
 12
 13const blobTruncateSize = 1 << 20 // 1 MiB
 14
 15var _ graph.GitCommitResolver = &gitCommitResolver{}
 16
 17type gitCommitResolver struct{}
 18
 19func (r gitCommitResolver) ShortHash(_ context.Context, obj *models.GitCommitMeta) (string, error) {
 20	s := string(obj.Hash)
 21	if len(s) > 8 {
 22		s = s[:8]
 23	}
 24	return s, nil
 25}
 26
 27func (r gitCommitResolver) FullMessage(_ context.Context, obj *models.GitCommitMeta) (string, error) {
 28	repo := obj.Repo.BrowseRepo()
 29	detail, err := repo.CommitDetail(obj.Hash)
 30	if err != nil {
 31		return "", err
 32	}
 33	return detail.FullMessage, nil
 34}
 35
 36func (r gitCommitResolver) Parents(_ context.Context, obj *models.GitCommitMeta) ([]string, error) {
 37	out := make([]string, len(obj.Parents))
 38	for i, h := range obj.Parents {
 39		out[i] = string(h)
 40	}
 41	return out, nil
 42}
 43
 44func (r gitCommitResolver) Files(_ context.Context, obj *models.GitCommitMeta, after *string, before *string, first *int, last *int) (*models.GitChangedFileConnection, error) {
 45	repo := obj.Repo.BrowseRepo()
 46	detail, err := repo.CommitDetail(obj.Hash)
 47	if err != nil {
 48		return nil, err
 49	}
 50
 51	input := models.ConnectionInput{After: after, Before: before, First: first, Last: last}
 52	edger := func(f repository.ChangedFile, offset int) connections.Edge {
 53		return connections.CursorEdge{Cursor: connections.OffsetToCursor(offset)}
 54	}
 55	conMaker := func(_ []*connections.CursorEdge, nodes []repository.ChangedFile, info *models.PageInfo, total int) (*models.GitChangedFileConnection, error) {
 56		ptrs := make([]*repository.ChangedFile, len(nodes))
 57		for i := range nodes {
 58			ptrs[i] = &nodes[i]
 59		}
 60		return &models.GitChangedFileConnection{Nodes: ptrs, PageInfo: info, TotalCount: total}, nil
 61	}
 62	return connections.Connection(detail.Files, edger, conMaker, input)
 63}
 64
 65func (r gitCommitResolver) Diff(_ context.Context, obj *models.GitCommitMeta, path string) (*repository.FileDiff, error) {
 66	repo := obj.Repo.BrowseRepo()
 67	fd, err := repo.CommitFileDiff(obj.Hash, path)
 68	if err != nil {
 69		return nil, err
 70	}
 71	return &fd, nil
 72}
 73
 74var _ graph.GitTreeEntryResolver = &gitTreeEntryResolver{}
 75
 76type gitTreeEntryResolver struct{}
 77
 78func (r gitTreeEntryResolver) LastCommit(_ context.Context, obj *models.GitTreeEntry) (*models.GitCommitMeta, error) {
 79	repo := obj.Repo.BrowseRepo()
 80	// Pass all sibling names so the history walk covers the whole directory,
 81	// which is nearly the same cost as walking for a single entry.
 82	// Concurrent calls for the same directory are deduplicated by a singleflight
 83	// inside LastCommitForEntries; subsequent calls hit the LRU cache.
 84	commits, err := repo.LastCommitForEntries(obj.Ref, obj.Path, obj.SiblingNames)
 85	if err != nil {
 86		return nil, err
 87	}
 88	meta, ok := commits[obj.Name]
 89	if !ok {
 90		return nil, nil
 91	}
 92	return &models.GitCommitMeta{Repo: obj.Repo, CommitMeta: meta}, nil
 93}
 94
 95var _ graph.GitRefResolver = &gitRefResolver{}
 96
 97type gitRefResolver struct{}
 98
 99func (g gitRefResolver) Commit(ctx context.Context, obj *models.GitRef) (*models.GitCommitMeta, error) {
100	repo := obj.Repo.BrowseRepo()
101	detail, err := repo.CommitDetail(repository.Hash(obj.Hash))
102	if errors.Is(err, repository.ErrNotFound) {
103		return nil, nil
104	}
105	if err != nil {
106		return nil, err
107	}
108	return &models.GitCommitMeta{Repo: obj.Repo, CommitMeta: detail.CommitMeta}, nil
109}