Documentation
¶
Overview ¶
Package backup mirrors git repositories out of self-hosted forges.
A backup has to survive the forge going away, so what it keeps is a bare mirror clone of every repository: every branch, every tag, and the namespace folder structure the forge used, refreshed in place on later runs. Archived and active repositories can be selected separately, and either set can be written out as a tar.gz alongside the mirror.
Index ¶
- Constants
- Variables
- func Archive(dir, out string) error
- func NewRootCommand(version string, newRunner NewRunner) *cobra.Command
- func TestBackup(t *testing.T, run TestDriver)
- func TestLister(t *testing.T, newLister func(t *testing.T) Lister)
- type ArchiveSelection
- type Config
- type ForgeConfig
- type Lister
- type Mirror
- type Mirrorer
- type NewRunner
- type Options
- type Remote
- type Remoter
- type Repo
- type Result
- type Runner
- type State
- type TestDriver
- type UnknownKindError
Constants ¶
const ( TestActiveRepoPath = "team/active-repo" TestArchivedRepoPath = "team/archived-repo" TestEmptyRepoPath = "team/empty-repo" )
The paths every Lister's and Runner's fixture data is expected to seed. An adapter's own fixture-seeding driver (the fake, Forgejo, GitLab) is responsible for creating repositories under these exact paths, which is what lets the suites below run unchanged against any of them.
Variables ¶
var ErrAmbiguousToken = errors.New("set exactly one of token or token_env")
ErrAmbiguousToken means a forge set both token and token_env. Only one can win silently, and picking one without saying so is how a token meant to override the other quietly gets ignored -- config validation fails instead, the same as issue #9's SSH-key-vs-token rule.
var ErrBadArchive = errors.New("archive must be one of: none, all, active, archived")
ErrBadArchive means --archive wasn't one of none, all, active, or archived.
var ErrBadState = errors.New("state must be one of: all, active, archived")
ErrBadState means --state wasn't one of all, active, or archived.
var ErrGitNotFound = errors.New("git not found on PATH")
ErrGitNotFound means git isn't on PATH.
var ErrMissingToken = errors.New("token environment variable not set")
ErrMissingToken means a forge's token_env names an environment variable that isn't set.
Functions ¶
func Archive ¶
Archive writes a gzipped tar of the mirror at dir to out, atomically: it writes to out+".tmp" first and renames over out only on success, so a crash mid-write never leaves a truncated archive where a good one was.
Every entry is written under a top-level directory named after dir's own base name, so "tar xzf out" leaves a self-contained "<name>.git/" a caller can git clone directly rather than scattering the mirror's contents into whatever directory they happened to extract into.
Entries are read through an os.Root rooted at dir, which is what stops a symlink inside the mirror from making the walk reference anything outside it. Every entry's ModTime, Uid, Gid, Uname and Gname are zeroed, and entries are written in sorted path order, so archiving an unchanged mirror twice produces a byte-identical file -- which is what makes rsync or deduplication of the backup tree cheap.
func NewRootCommand ¶
NewRootCommand builds the backup-git-repos command line. Each RunE stays a thin shell: parse and validate the flags, then call into runBackup, which knows nothing about cobra.
func TestBackup ¶
func TestBackup(t *testing.T, run TestDriver)
TestBackup runs the specification every forge's backup pipeline must satisfy, in domain terms: it talks only about a destination directory and the state it asks for, so the same spec runs unchanged against a fake or a real forge.
func TestLister ¶
TestLister runs the behaviour every Lister must satisfy, whether it's backed by a fake or a real forge. Exported here, rather than living in a _test.go file, so an adapter under internal/ can run the same suite against itself -- otherwise an unexported helper in this package's own tests would be unreachable from anywhere that isn't this package.
Types ¶
type ArchiveSelection ¶
type ArchiveSelection int
ArchiveSelection selects which repositories also get written out as a tar.gz alongside their mirror, in addition to being mirrored.
const ( ArchiveNone ArchiveSelection = iota ArchiveAll ArchiveActive ArchiveArchived )
The values ArchiveSelection can hold.
func ParseArchive ¶
func ParseArchive(s string) (ArchiveSelection, error)
ParseArchive parses a --archive flag value.
type Config ¶
type Config struct {
Dest string `yaml:"dest"`
Forges []ForgeConfig `yaml:"forges"`
}
Config is the top-level shape of the YAML config file.
func LoadConfig ¶
LoadConfig reads and validates the config file at path: every forge's kind must be one this build supports, and every forge's token_env must name a set environment variable. Failing before touching the network beats a 401 partway through a run.
type ForgeConfig ¶
type ForgeConfig struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
URL string `yaml:"url"`
TokenEnv string `yaml:"token_env"`
TokenLiteral string `yaml:"token"`
Token string `yaml:"-"`
// SkipMirrors excludes repositories a forge reports as mirrors of an
// external upstream from both listing and mirroring. Only the forgejo
// adapter interprets it today; other kinds ignore it.
SkipMirrors bool `yaml:"skip_mirrors"`
}
ForgeConfig is one forge entry from the config file. Token is resolved during LoadConfig, either from the environment variable named by TokenEnv or copied straight from TokenLiteral -- never read as-is from the struct the YAML unmarshals into, so every other field reaching a Runner has gone through the same validation regardless of which form the file used.
type Mirror ¶
type Mirror struct {
GitPath string
}
Mirror keeps a bare mirror clone of a repository up to date on disk. The zero value works: it resolves git from PATH.
type NewRunner ¶
type NewRunner func(ForgeConfig) (Runner, error)
NewRunner builds the Runner for a configured forge. Implementations live with their adapters under internal/, so this package -- which every adapter imports for Repo, Lister and the rest -- can't reference them directly without a cycle. The composition root (main) supplies one.
type Options ¶
type Options struct {
Dest string
State State
Archive ArchiveSelection
ArchiveDir string
Concurrency int
Timeout time.Duration
Log *slog.Logger
// Progress, if set, is called every time a repository is skipped or
// finishes mirroring (successfully or not), reporting how many of the
// total have been accounted for so far. Run serializes calls to it, so
// it doesn't need its own locking even though repositories are
// mirrored concurrently.
Progress func(done, total int)
}
Options configures a Run.
type Remote ¶
Remote is a repository's clone URL and, where the forge needs one, the value of the Authorization header to send with it. Keeping the credential in a header rather than the URL is what keeps it out of the mirror's own .git/config -- a URL embedded there would leave the token in the clear in every mirror on disk.
type Repo ¶
Repo is a repository as reported by a forge: where it lives in the forge's namespace, and whether it's archived or carries no commits yet.
type Runner ¶
Runner backs up one forge: it lists repositories, then mirrors each one into a destination tree that mirrors the forge's own namespace structure.
func (Runner) Run ¶
Run lists repositories filtered by opts.State and mirrors each one, skipping repositories with no refs -- a mirror of one would just confuse the next refresh. It stays synchronous even though it mirrors up to opts.Concurrency repositories at once: the call blocks until every repo has been attempted, and the caller decides whether to run it concurrently with anything else. One repository failing is logged and doesn't stop the rest; Result.Failed is how the caller finds out afterwards.
type State ¶
type State int
State selects which repositories a Lister returns.
The states a Lister can be asked to filter on.
type TestDriver ¶
TestDriver runs a backup the way Runner.Run does, whether that's against an in-memory fake or a real forge container.
type UnknownKindError ¶
type UnknownKindError struct {
Kind string
}
UnknownKindError means a forge's kind isn't one backup-git-repos knows how to talk to.
func (*UnknownKindError) Error ¶
func (e *UnknownKindError) Error() string
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
backup-git-repos
command
Command backup-git-repos mirrors git repositories out of self-hosted forges.
|
Command backup-git-repos mirrors git repositories out of self-hosted forges. |
|
internal
|
|
|
forgejo
Package forgejo lists and mirrors repositories from a self-hosted Forgejo (or Gitea) instance over its REST API.
|
Package forgejo lists and mirrors repositories from a self-hosted Forgejo (or Gitea) instance over its REST API. |
|
github
Package github lists and mirrors repositories from GitHub.com over its REST API.
|
Package github lists and mirrors repositories from GitHub.com over its REST API. |
|
gitlab
Package gitlab lists and mirrors repositories from a self-hosted GitLab instance over its REST API.
|
Package gitlab lists and mirrors repositories from a self-hosted GitLab instance over its REST API. |