~bigbes/sr-ht-compare

ref: 4f3961888c6889fc2ea19c9bcbb68359015d3475 sr-ht-compare/gitx/log.go -rw-r--r-- 3.4 KiB
4f396188 — bigbes rename the service to diff.sr.ht 9 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package gitx

import (
	"context"
	"fmt"
	"time"

	"github.com/go-git/go-git/v5/plumbing"
	"github.com/go-git/go-git/v5/plumbing/object"
	"github.com/go-git/go-git/v5/plumbing/storer"

	"sourcecraft.dev/bigbes/sr-ht-compare/core"
)

// CommitInfo is the metadata of a single commit.
type CommitInfo struct {
	SHA         string
	ShortSHA    string
	AuthorName  string
	AuthorEmail string
	Date        time.Time
	Subject     string
	Body        string
	// ParentSHAs lists the commit's parents in order. Empty for a root commit;
	// length > 1 marks a merge commit.
	ParentSHAs []string
}

// resolveCommit validates rev, resolves it to a commit, and returns the go-git
// object. A syntactically valid but unresolvable revision yields core.ErrBadRef.
func (r *Repo) resolveCommit(rev string) (*object.Commit, error) {
	if !core.ValidRef(rev) {
		return nil, fmt.Errorf("%w: %q", core.ErrBadRef, rev)
	}
	hash, err := r.repo.ResolveRevision(plumbing.Revision(rev))
	if err != nil {
		return nil, badRef(rev, err)
	}
	c, err := r.repo.CommitObject(*hash)
	if err != nil {
		return nil, badRef(rev, err)
	}
	return c, nil
}

// ResolveCommit resolves an arbitrary revision (branch, tag, short or full SHA)
// to a commit and returns its metadata.
func (r *Repo) ResolveCommit(ctx context.Context, rev string) (*CommitInfo, error) {
	_, cancel := r.withTimeout(ctx)
	defer cancel()

	c, err := r.resolveCommit(rev)
	if err != nil {
		return nil, err
	}
	return commitInfo(c), nil
}

// Log returns up to limit commits in the range base..head (reachable from head
// but not from base), newest first. A non-positive limit defaults to 50.
func (r *Repo) Log(ctx context.Context, base, head string, limit int) ([]CommitInfo, error) {
	if limit <= 0 {
		limit = 50
	}
	_, cancel := r.withTimeout(ctx)
	defer cancel()

	baseCommit, err := r.resolveCommit(base)
	if err != nil {
		return nil, err
	}
	headCommit, err := r.resolveCommit(head)
	if err != nil {
		return nil, err
	}

	// Exclude everything reachable from base so a shared ancestor reached via a
	// non-base path is still dropped — matching git's base..head semantics.
	excluded, err := reachableSet(baseCommit)
	if err != nil {
		return nil, err
	}

	var out []CommitInfo
	iter := object.NewCommitPreorderIter(headCommit, excluded, nil)
	err = iter.ForEach(func(c *object.Commit) error {
		if len(out) >= limit {
			return storer.ErrStop
		}
		out = append(out, *commitInfo(c))
		return nil
	})
	if err != nil {
		return nil, err
	}
	return out, nil
}

// Parents returns the parent SHAs of a commit in order. A root commit yields an
// empty slice; length > 1 marks a merge commit.
func (r *Repo) Parents(ctx context.Context, rev string) ([]string, error) {
	_, cancel := r.withTimeout(ctx)
	defer cancel()

	c, err := r.resolveCommit(rev)
	if err != nil {
		return nil, err
	}
	if len(c.ParentHashes) == 0 {
		return nil, nil
	}
	parents := make([]string, 0, len(c.ParentHashes))
	for _, p := range c.ParentHashes {
		parents = append(parents, p.String())
	}
	return parents, nil
}

// reachableSet returns the set of every commit hash reachable from c
// (inclusive).
func reachableSet(c *object.Commit) (map[plumbing.Hash]bool, error) {
	set := make(map[plumbing.Hash]bool)
	iter := object.NewCommitPreorderIter(c, nil, nil)
	err := iter.ForEach(func(x *object.Commit) error {
		set[x.Hash] = true
		return nil
	})
	if err != nil {
		return nil, err
	}
	return set, nil
}