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}