← repoman internals

internal/gitsync

import "git.griefed.de/griefed/repoman/internal/gitsync"
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.