coverage~bigbes/sr-ht-compare9720ccc2gitx/log.go

Coverage
86.0% 49/57 statements
Δ
Blob
aa94333
1 package gitx
2
3 import (
4 "context"
5 "fmt"
6 "time"
7
8 "github.com/go-git/go-git/v5/plumbing"
9 "github.com/go-git/go-git/v5/plumbing/object"
10 "github.com/go-git/go-git/v5/plumbing/storer"
11
12 "sourcecraft.dev/bigbes/sr-ht-compare/core"
13 )
14
15 // CommitInfo is the metadata of a single commit.
16 type CommitInfo struct {
17 SHA string
18 ShortSHA string
19 AuthorName string
20 AuthorEmail string
21 Date time.Time
22 Subject string
23 Body string
24 // ParentSHAs lists the commit's parents in order. Empty for a root commit;
25 // length > 1 marks a merge commit.
26 ParentSHAs []string
27 }
28
29 // resolveCommit validates rev, resolves it to a commit, and returns the go-git
30 // object. A syntactically valid but unresolvable revision yields core.ErrBadRef.
31 31 func (r *Repo) resolveCommit(rev string) (*object.Commit, error) {
32 31 if !core.ValidRef(rev) {
33 1 return nil, fmt.Errorf("%w: %q", core.ErrBadRef, rev)
34 1 }
35 30 hash, err := r.repo.ResolveRevision(plumbing.Revision(rev))
36 30 if err != nil {
37 1 return nil, badRef(rev, err)
38 1 }
39 29 c, err := r.repo.CommitObject(*hash)
40 29 if err != nil {
41 0 return nil, badRef(rev, err)
42 0 }
43 29 return c, nil
44 }
45
46 // ResolveCommit resolves an arbitrary revision (branch, tag, short or full SHA)
47 // to a commit and returns its metadata.
48 6 func (r *Repo) ResolveCommit(ctx context.Context, rev string) (*CommitInfo, error) {
49 6 _, cancel := r.withTimeout(ctx)
50 6 defer cancel()
51 6
52 6 c, err := r.resolveCommit(rev)
53 6 if err != nil {
54 2 return nil, err
55 2 }
56 4 return commitInfo(c), nil
57 }
58
59 // Log returns up to limit commits in the range base..head (reachable from head
60 // but not from base), newest first. A non-positive limit defaults to 50.
61 2 func (r *Repo) Log(ctx context.Context, base, head string, limit int) ([]CommitInfo, error) {
62 2 if limit <= 0 {
63 0 limit = 50
64 0 }
65 2 _, cancel := r.withTimeout(ctx)
66 2 defer cancel()
67 2
68 2 baseCommit, err := r.resolveCommit(base)
69 2 if err != nil {
70 0 return nil, err
71 0 }
72 2 headCommit, err := r.resolveCommit(head)
73 2 if err != nil {
74 0 return nil, err
75 0 }
76
77 // Exclude everything reachable from base so a shared ancestor reached via a
78 // non-base path is still dropped — matching git's base..head semantics.
79 2 excluded, err := reachableSet(baseCommit)
80 2 if err != nil {
81 0 return nil, err
82 0 }
83
84 2 var out []CommitInfo
85 2 iter := object.NewCommitPreorderIter(headCommit, excluded, nil)
86 4 err = iter.ForEach(func(c *object.Commit) error {
87 4 if len(out) >= limit {
88 1 return storer.ErrStop
89 1 }
90 3 out = append(out, *commitInfo(c))
91 3 return nil
92 })
93 2 if err != nil {
94 0 return nil, err
95 0 }
96 2 return out, nil
97 }
98
99 // Parents returns the parent SHAs of a commit in order. A root commit yields an
100 // empty slice; length > 1 marks a merge commit.
101 2 func (r *Repo) Parents(ctx context.Context, rev string) ([]string, error) {
102 2 _, cancel := r.withTimeout(ctx)
103 2 defer cancel()
104 2
105 2 c, err := r.resolveCommit(rev)
106 2 if err != nil {
107 0 return nil, err
108 0 }
109 2 if len(c.ParentHashes) == 0 {
110 1 return nil, nil
111 1 }
112 1 parents := make([]string, 0, len(c.ParentHashes))
113 2 for _, p := range c.ParentHashes {
114 2 parents = append(parents, p.String())
115 2 }
116 1 return parents, nil
117 }
118
119 // reachableSet returns the set of every commit hash reachable from c
120 // (inclusive).
121 2 func reachableSet(c *object.Commit) (map[plumbing.Hash]bool, error) {
122 2 set := make(map[plumbing.Hash]bool)
123 2 iter := object.NewCommitPreorderIter(c, nil, nil)
124 4 err := iter.ForEach(func(x *object.Commit) error {
125 4 set[x.Hash] = true
126 4 return nil
127 4 })
128 2 if err != nil {
129 0 return nil, err
130 0 }
131 2 return set, nil
132 }