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 applies the limits of the port and of the domain
26func (a *Adapter) ReadCommitObjects(ctx context.Context, repo domain.RepoName, ids []domain.ObjectID) (map[domain.ObjectID][]byte, error) {
27 if len(ids) > ports.MaxCommitLimit {
28 return nil, fmt.Errorf("%w: %d commit objects asked, at most %d", domain.ErrInvalid, len(ids), ports.MaxCommitLimit)
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 total int64
40 for _, id := range ids {
41 if err := ctx.Err(); err != nil {
42 return nil, err
43 }
44 if _, done := out[id]; done {
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 if typ != git.TypeCommit || size > domain.MaxCommitObjectBytes {
59 continue
60 }
61 if total+size > domain.MaxCommitObjectsBytes {
62 break
63 }
64 typ, data, err := r.ReadObject(gid, nil)
65 if errors.Is(err, git.ErrNotFound) {
66 continue // removed meanwhile
67 }
68 if err != nil {
69 return nil, err
70 }
71 if typ == git.TypeCommit && len(data) <= domain.MaxCommitObjectBytes && total+int64(len(data)) <= domain.MaxCommitObjectsBytes {
72 out[id] = data
73 total += int64(len(data))
74 }
75 }
76 return out, nil
77 }, cli)
78}