A vibe coded tangled fork which supports pijul.
at 30381c8a74e47c864b80604b23ab42f947626fb3 105 lines 2.8 kB view raw
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}