1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package ermitegit
4
5import (
6 "context"
7 "errors"
8 "fmt"
9
10 git "bnema.dev/ermite-git"
11
12 "bnema.dev/ermite/internal/domain"
13 "bnema.dev/ermite/internal/ports"
14)
15
16var _ ports.CommitObjectReader = (*Adapter)(nil)
17
18// ReadCommitObjects reads the raw content of commits with ermite-git. Ids that
19// are missing, are not commits, or are larger than
20// domain.MaxCommitObjectBytes are left out. The read ends at the first commit
21// that would take the total over domain.MaxCommitObjectsBytes. It is not routable: the library
22// always serves it, and the wrapped reader only the repositories the library
23// cannot read.
24//
25//hexcheck:ignore suspicious-business-logic-in-adapter streams object headers and bytes; eligibility, deduplication and byte limits are decided by domain.CommitObjectBudget, request validation by domain.ValidateCommitObjects
26func (a *Adapter) ReadCommitObjects(ctx context.Context, repo domain.RepoName, ids []domain.ObjectID) (map[domain.ObjectID][]byte, error) {
27 if err := domain.ValidateCommitObjects(ids, ports.MaxCommitLimit); err != nil {
28 return nil, err
29 }
30 cli := func() (map[domain.ObjectID][]byte, error) {
31 r, ok := a.cli.(ports.CommitObjectReader)
32 if !ok {
33 return nil, fmt.Errorf("read commit objects of %q: the wrapped reader cannot read commit objects", repo)
34 }
35 return r.ReadCommitObjects(ctx, repo, ids)
36 }
37 return serve(ctx, a, repo, func(r *git.Repo) (map[domain.ObjectID][]byte, error) {
38 out := make(map[domain.ObjectID][]byte, len(ids))
39 var budget domain.CommitObjectBudget
40 for _, id := range ids {
41 if err := ctx.Err(); err != nil {
42 return nil, err
43 }
44 if budget.Seen(id) {
45 continue
46 }
47 gid, err := git.ParseID(id.String())
48 if err != nil {
49 return nil, err
50 }
51 typ, size, err := r.ObjectHeader(gid)
52 if errors.Is(err, git.ErrNotFound) {
53 continue
54 }
55 if err != nil {
56 return nil, err
57 }
58 take, stop := budget.Admit(id, typ == git.TypeCommit, size)
59 if stop {
60 break
61 }
62 if !take {
63 continue
64 }
65 typ, data, err := r.ReadObject(gid, nil)
66 if errors.Is(err, git.ErrNotFound) {
67 continue // removed meanwhile
68 }
69 if err != nil {
70 return nil, err
71 }
72 if take, _ := budget.Admit(id, typ == git.TypeCommit, int64(len(data))); take {
73 out[id] = data
74 budget.Charge(id, int64(len(data)))
75 }
76 }
77 return out, nil
78 }, cli)
79}