A vibe coded tangled fork which supports pijul.
1package knotmirror
2
3import (
4 "context"
5 "errors"
6 "fmt"
7 "os/exec"
8 "regexp"
9 "strings"
10
11 "github.com/go-git/go-git/v5"
12 gitconfig "github.com/go-git/go-git/v5/config"
13 "github.com/go-git/go-git/v5/plumbing/transport"
14)
15
16type GitMirrorClient interface {
17 Clone(ctx context.Context, path, url string) error
18 Fetch(ctx context.Context, path, url string) error
19}
20
21type CliGitMirrorClient struct{}
22
23var _ GitMirrorClient = new(CliGitMirrorClient)
24
25func (c *CliGitMirrorClient) Clone(ctx context.Context, path, url string) error {
26 cmd := exec.CommandContext(ctx, "git", "clone", "--mirror", url, path)
27 if out, err := cmd.CombinedOutput(); err != nil {
28 if ctx.Err() != nil {
29 return ctx.Err()
30 }
31 msg := string(out)
32 if classification := classifyError(msg); classification != nil {
33 return classification
34 }
35 return fmt.Errorf("cloning repo: %w\n%s", err, msg)
36 }
37 return nil
38}
39
40func (c *CliGitMirrorClient) Fetch(ctx context.Context, path, url string) error {
41 cmd := exec.CommandContext(ctx, "git", "-C", path, "fetch", "--prune", "origin")
42 if out, err := cmd.CombinedOutput(); err != nil {
43 if ctx.Err() != nil {
44 return ctx.Err()
45 }
46 return fmt.Errorf("fetching repo: %w\n%s", err, string(out))
47 }
48 return nil
49}
50
51var (
52 ErrDNSFailure = errors.New("git: dns failure (could not resolve host)")
53 ErrCertExpired = errors.New("git: certificate has expired")
54 ErrRepoNotFound = errors.New("git: repository not found")
55)
56
57var (
58 reDNS = regexp.MustCompile(`Could not resolve host:`)
59 reCertExpired = regexp.MustCompile(`SSL certificate OpenSSL verify result: certificate has expired`)
60 reRepoNotFound = regexp.MustCompile(`repository '.*' not found`)
61)
62
63func classifyError(stderr string) error {
64 msg := strings.TrimSpace(stderr)
65 switch {
66 case reDNS.MatchString(msg):
67 return ErrDNSFailure
68 case reCertExpired.MatchString(msg):
69 return ErrCertExpired
70 case reRepoNotFound.MatchString(msg):
71 return ErrRepoNotFound
72 }
73 return nil
74}
75
76type GoGitMirrorClient struct{}
77
78var _ GitMirrorClient = new(GoGitMirrorClient)
79
80func (c *GoGitMirrorClient) Clone(ctx context.Context, path string, url string) error {
81 _, err := git.PlainCloneContext(ctx, path, true, &git.CloneOptions{
82 URL: url,
83 Mirror: true,
84 })
85 if err != nil && !errors.Is(err, transport.ErrEmptyRemoteRepository) {
86 return fmt.Errorf("cloning repo: %w", err)
87 }
88 return nil
89}
90
91func (c *GoGitMirrorClient) Fetch(ctx context.Context, path string, url string) error {
92 gr, err := git.PlainOpen(path)
93 if err != nil {
94 return fmt.Errorf("opening local repo: %w", err)
95 }
96 if err := gr.FetchContext(ctx, &git.FetchOptions{
97 RemoteURL: url,
98 RefSpecs: []gitconfig.RefSpec{gitconfig.RefSpec("+refs/*:refs/*")},
99 Force: true,
100 Prune: true,
101 }); err != nil {
102 return fmt.Errorf("fetching reppo: %w", err)
103 }
104 return nil
105}