Federated via graft
0

Configure Feed

Select the types of activity you want to include in your feed.

graft-source / internal / sync / syncer.go
3.2 kB 113 lines
1package sync 2 3import ( 4 "fmt" 5 "log/slog" 6 "net/url" 7 "path/filepath" 8 9 "graft/internal/config" 10 "graft/internal/forgejo" 11 "graft/internal/radicle" 12 "graft/internal/state" 13) 14 15// RepoSyncer runs one repo pair's enabled sync scopes for one pass. 16type RepoSyncer struct { 17 pair config.RepoPair 18 forgejo *forgejo.Client 19 radicle *radicle.Client 20 git *GitSyncer 21 issues *IssueSyncer 22 patches *PatchSyncer 23} 24 25// New builds a RepoSyncer for one configured pair. workDir is where this 26// pair's local git mirror is kept, e.g. <state-dir>/<pair-name>. 27func New(pair config.RepoPair, st *state.Store, workDir string) (*RepoSyncer, error) { 28 token, err := config.ReadToken(pair.Forgejo.TokenFile) 29 if err != nil { 30 return nil, err 31 } 32 fc := forgejo.New(pair.Forgejo.BaseURL, pair.Forgejo.Owner, pair.Forgejo.Repo, token) 33 34 repo, err := fc.GetRepository() 35 if err != nil { 36 return nil, fmt.Errorf("resolve forgejo repository: %w", err) 37 } 38 39 rc := radicle.New(pair.Radicle.HTTPBaseURL, pair.Radicle.RID, pair.Radicle.RadHome, "rad") 40 41 rs := &RepoSyncer{pair: pair, forgejo: fc, radicle: rc} 42 43 if pair.Sync.Git || pair.Sync.Patches { 44 rs.git = &GitSyncer{ 45 RepoPair: pair.Name, 46 WorkDir: workDir, 47 ForgejoURL: authenticatedCloneURL(repo.CloneURL, token), 48 ForgejoBranch: repo.DefaultBranch, 49 RID: pair.Radicle.RID, 50 RadHome: pair.Radicle.RadHome, 51 State: st, 52 } 53 } 54 if pair.Sync.Issues { 55 rs.issues = &IssueSyncer{RepoPair: pair.Name, Forgejo: fc, Radicle: rc, State: st} 56 } 57 if pair.Sync.Patches { 58 rs.patches = &PatchSyncer{ 59 RepoPair: pair.Name, 60 WorkDir: workDir, 61 RadHome: pair.Radicle.RadHome, 62 DefaultBranch: repo.DefaultBranch, 63 Forgejo: fc, 64 Radicle: rc, 65 State: st, 66 } 67 } 68 return rs, nil 69} 70 71// Name is this pair's name, as given in the config. 72func (rs *RepoSyncer) Name() string { return rs.pair.Name } 73 74// Run executes every enabled scope once, logging and continuing past 75// per-scope errors so one broken scope doesn't block the others. It 76// returns each scope's error (nil if disabled or successful) for the 77// caller to report via internal/status. 78func (rs *RepoSyncer) Run(log *slog.Logger) (gitErr, issuesErr, patchErr error) { 79 log = log.With("pair", rs.pair.Name) 80 81 if rs.git != nil { 82 if gitErr = rs.git.Sync(); gitErr != nil { 83 log.Error("git sync failed", "err", gitErr) 84 } 85 } 86 if rs.issues != nil { 87 if issuesErr = rs.issues.Sync(); issuesErr != nil { 88 log.Error("issue sync failed", "err", issuesErr) 89 } 90 } 91 if rs.patches != nil { 92 if patchErr = rs.patches.Sync(); patchErr != nil { 93 log.Error("patch sync failed", "err", patchErr) 94 } 95 } 96 return gitErr, issuesErr, patchErr 97} 98 99// authenticatedCloneURL embeds a token into an HTTPS clone URL the way 100// Forgejo expects (token as the basic-auth username). 101func authenticatedCloneURL(cloneURL, token string) string { 102 u, err := url.Parse(cloneURL) 103 if err != nil { 104 return cloneURL 105 } 106 u.User = url.User(token) 107 return u.String() 108} 109 110// WorkDirFor returns the per-pair git mirror directory under stateDir. 111func WorkDirFor(stateDir, pairName string) string { 112 return filepath.Join(stateDir, "repos", pairName) 113}