package gitsync // import "git.griefed.de/griefed/repoman/internal/gitsync" Package gitsync brings a Forgejo repository's git refs up to date with its source, for repos that are NOT Forgejo pull mirrors. Why this exists: a one-time Forgejo migration copies git history once and never refetches it. repoman creates releases itself (Forgejo's bundled importer is serial, opaque, and skipped entirely by mirrors), and binds each release to an EXISTING tag. So on every run after the first, a release created upstream names a tag the Forgejo copy has never seen, and Forgejo rejects it with HTTP 409 "Release has no Tag". Fetching the source's refs and pushing them into Forgejo closes that gap, so the commits a tag points at exist before the release referencing it is created. It also carries refs/notes/*, which is not an optional extra: semantic-release stores each prerelease tag's release CHANNEL in refs/notes/semantic-release, and a mirror without those notes cannot tell which channel its prerelease tags belong to — so a release run on a prerelease branch discards them all and walks back to the last stable tag. Tags without their notes are an incomplete mirror. Mirrored repos do not come through here — Forgejo pulls those itself, and forgejo.Client.MirrorSync asks it to do so now rather than on its interval. (A pull mirror fetches every ref including notes, so that path needs nothing.) Like internal/repoarchive this uses the pure-Go go-git library rather than shelling out to git, keeping repoman a single static CGO-free binary with no host tooling dependency. go-git resolves transports through a PROCESS-GLOBAL registry, so the certificate-skipping HTTPS client that repoarchive installs for the dev-only SkipTLSVerify flag also applies to the pushes made here — no separate opt-in is needed or wanted. VARIABLES var ErrIncompleteSpec = errors.New("gitsync: both a source and a Forgejo clone URL are required") ErrIncompleteSpec is returned when a Spec lacks a source or target clone URL. Both are required; there is no meaningful default for either. TYPES type Result struct { // TagsUpdated is true when the tag push moved at least one ref. TagsUpdated bool // HeadsUpdated is true when the branch push moved at least one ref. HeadsUpdated bool // NotesUpdated is true when the notes push moved at least one ref. NotesUpdated bool // HeadsError holds a non-fatal branch-push failure (typically a diverged // branch). Tags may still have been pushed successfully. HeadsError error // NotesError holds a non-fatal notes-push failure. Notes carry release-channel // metadata rather than history, so a rejected notes push must not hide the tags // and branches that went through — but it must be reported, because a mirror // without them breaks downstream release tooling in a way nothing else reveals. NotesError error } Result reports what the sync achieved, so the caller can log something meaningful and decide whether re-listing tags is worthwhile. func PushRefs(ctx context.Context, spec Spec) (Result, error) PushRefs fetches every ref from the source into a temporary bare repository and pushes the tags, then the branches, into Forgejo. It is idempotent: an already-current repo pushes nothing and reports no error. The temporary clone is always removed, so disk use is bounded by one repository at a time. type Spec struct { SourceCloneURL string ForgejoCloneURL string ForgejoToken string // Label identifies the repo in error messages, e.g. "Griefed/repo". Label string } Spec describes one source→Forgejo ref sync. SourceCloneURL may embed credentials as userinfo (that is how the source clients hand it out); ForgejoToken authenticates the push, using Forgejo's convention of accepting an access token as the basic-auth username.