Changelog
All notable changes to github-backup are documented in this file.
The format follows Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased
Changes on main since v0.3.2 (2026-04-12). Nothing here is in a published
release yet; github-backup --version prints 0.3.2 for both until the version
is bumped.
Breaking changes and upgrade notes
- Exit status
3is new. A run that finishes but could not back up everything now exits3(it used to exit0and report success). Scripts and unit files that treated every non-zero status as "could not run" need no change (a systemd unit simply fails); scripts that only checked for1should accept3as "incomplete". The other statuses are0ok,1fatal,2usage,130/143interrupted. - Saved JSON is GitHub's complete response, with GitHub's key order, instead
of a typed subset. Files are roughly four times larger and now contain
properties that were dropped before (reactions,
node_id,_links, review comment anchors, merge commit SHAs, ...). Pull requests also getissue_comments/<n>.jsonandissue_events/<n>.json. - S3 object keys now include the owner:
<prefix>/<owner>/json/<path>(they were<prefix>/<path>). The first run after upgrading uploads everything again under the new keys; the old objects are not removed (--s3-delete-staleonly looks under the new<prefix>/<owner>/json/root), so delete them yourself once you no longer need them. Objects are now skipped by SHA-256 digest plus size instead of size alone; an object without a stored digest is uploaded once more. With--encrypt-keythe digest is keyed, so changing the key re-uploads everything. --mirror-topushes branches and tags only (git push --prunewith explicit refspecs, no--mirror), creates every repository private unless--mirror-publicis given and the source is known to be public (a private source is never published), and refuses to push into an existing repository that it did not create (unless that repository is empty). Gitea repositories for an organisation owner are created through the organisation endpoint. Working-tree clones (--clone-type full) are not pushed.- Git clones keep what GitHub deleted. Updates no longer prune: a branch or tag
deleted on GitHub stays in the clone (force-pushed branches are still
overwritten). Pass
--prune(config keyprune) for the old behaviour.--no-pruneand the config keyno_pruneare still accepted and ignored; they cannot be combined with--prune. If a deleted branchfoois replaced byfoo/bar, git cannot hold both: that update prunes once, logs a warning and continues. The TUI's "No Prune" toggle became "Prune deleted refs". --restoreis restore-only: it no longer runs a backup first and works from the local backup. It asks for confirmation first, skips issues it restored before, and exits3when items could not be restored. Target repositories must exist.--keep-lastand--max-age-daysare deprecated and ignored (a warning is logged). They deleted directories by name pattern and could not work with the tool's own layout. Rotate copies with your backup tool.--sincechanged meaning. Issue and pull request lists are always fetched in full and merged into the stored files;--since DATE(now alsoYYYY-MM-DD) is an expert override that is never stored. v0.3.2 applied the previous run's timestamp automatically and could overwriteissues.jsonwith only the changed issues.backup_state.jsonnow holds a watermark per repository; an old file only costs one full fetch.--lfsis now a mirror clone plusgit lfs fetch --all(it used to rungit lfs clone, which created a non-bare checkout named<repo>.gitwhose refs never updated, and it overrode--clone-type). Delete and re-clone existing--lfsrepositories to get a proper mirror.- Report, metrics, webhook and history gained fields: see Added.
successin the report now means "no failures" (it meantrepos_errored == 0). - Linux release binaries are static musl builds; the v0.3.2 x86_64 binary was glibc-linked and needed glibc 2.39 or newer.
Added
--pruneand--mirror-public(and the config keysprune,mirror_public); see the upgrade notes above for what they change.- Failure isolation and honest reporting. Every repository and every category
is an isolated step; a failure is recorded (scope, step, message) and the rest of
the run continues. Only credentials rejected (401), an exhausted rate-limit
budget, a full disk and cancellation stop a run. The summary banner, the
--reportJSON (schema_version,finished_at,failure_count,failures[]), the Prometheus metrics (github_backup_failures,github_backup_success,github_backup_last_run_timestamp_seconds;..._last_success_timestamp_secondsis carried over from the previous file when a run fails), the run history (failures, and failed runs are recorded too) and the webhook (statussuccess/partial/failure,failure_count,failed[]) all read the same list. A run that fails early still writes report, metrics, history and webhook. S3 upload failures, mirror push failures and a manifest that cannot be written count as failures. - Per-repository incremental watermarks (
backup_state.json): per-item files (issue and pull request comments, events, commits, reviews) are re-fetched only for items changed since the watermark, which advances only for repositories whose every step succeeded and is trusted only for the categories it was recorded with.--fullignores watermarks. repos.jsonis written (only the repositories the options include, merged with earlier listings) and used by--diff-with, which now actually compares repository names.- A user's own private repositories and secret gists are listed (through
GET /user/reposandGET /gists) when the token belongs to the owner; before they were silently missing. - Lossless JSON via
Raw<T>/Page<T>; an element the typed model cannot read is written verbatim and logged instead of aborting the list; nullable API fields are modelled asOption; golden tests use GitHub's own example payloads. - Release assets are streamed, verified against the size and
sha256:digest GitHub reports, written through a.partfile, and skipped on later runs only when complete; theAuthorizationheader is never sent to another origin on a redirect and an HTTPS to HTTP redirect is refused. --doctor,--check,--list-scopes,--print-config-template.--doctorchecks thegitbinary, the output directory, the kind of credential, API reachability (through the real client: API URL validation, proxy, CA bundle) and that GitHub accepts the token (GET /rate_limit); it does not check scopes or free space.--checkadds the resolved configuration.--list-scopesprints suggested classic scopes (it does not expand--all).- Friendly quickstart when run without arguments, a pre-run plan banner and an
end-of-run summary banner (which lists what failed),
--helpexamples, actionable hints for common errors, and token-format recognition in--doctor. GITHUB_BACKUP_RESTORE_YES=1as an alternative to--restore-yes; restored issues carry the original author and date and a hidden source marker so a repeated restore does not duplicate them.- Proxy support:
HTTPS_PROXY,HTTP_PROXY,ALL_PROXYandNO_PROXY(hosts, domains, ports, IPs, CIDR blocks); HTTP proxies only. Shared by the API client, release downloads, the webhook and--doctor. S3 and the mirror APIs do not use a proxy. - S3:
--s3-session-token/AWS_SESSION_TOKEN, credential validation before the first request, content-digest skipping, retries with back-off and an idle timeout (a request is abandoned only when no data moved for 60 s), a guarded--s3-delete-stale(nothing is deleted after a failed run, an unreadable or empty local tree, or a failed upload; only under<prefix>/<owner>/json/), and an in-process SigV4-verifying fake S3 server in the tests. - Mirror: a destination-side marker (
GitHub mirror of <owner>/<repo>) and per-repository failure reporting. - Checkpoint resume is limited to 6 hours after an interrupted run's last
activity;
--clone-starredmirrors are refreshed on every pass and earlier failures are retried automatically. --allhonours explicit--clone-starredand--action-runs; a config file acceptsclone_type = "shallow:3"(and{ shallow = 3 }); a--clone-typeon the command line beats the config file.--decryptneeds no OWNER.- Docker, Compose and Unraid: see Packaging below.
Changed
- Rate limits (403 or 429 with
Retry-After,X-RateLimit-Remaining: 0or a rate-limit message) are waited out and retried (up to 6 times, about an hour in total per request, at least a minute and doubling when GitHub gives no hint) instead of being treated as "no access" and skipped; GET requests retry 500/502/503/504, dropped connections, timeouts and stalled bodies three times. - Git subprocesses: a stall timeout (600 s without output; was a 600 s
wall-clock limit) with
--progress, no pipe deadlock for large outputs, async runner on the blocking pool, process-group kill on cancellation,git fetch --all --pruneinstead ofgit remote update --prune(pruning has since become opt-in, see above), bare clones update with explicit refspecs (they never advanced before), fresh clones go to a staging directory and are renamed when complete. Git runs with stdin closed,GIT_TERMINAL_PROMPT=0andLC_ALL=C. - Cancellation (
SIGINT/SIGTERM) abandons in-flight API requests (also the repository listing), kills git and releases the lock; exit130/143within a few seconds, with an exit watchdog behind it. - Locking is an operating-system lock (
flock/LockFileEx) in the output directory and in<owner>/json/, released by the kernel when the process dies; the lock files stay on disk. --dry-runwrites nothing: no lock, state, report, manifest, metrics, history, webhook, S3 upload or mirror push; no git, no owner data and no gists.- Manifest and
--verifyignore the history, state, checkpoint and lock files. NO_COLORfollows no-color.org literally (set and non-empty disables ANSI).- Report and Prometheus files are written atomically (temporary file and rename); a response body is capped at 16 MiB.
- Empty environment variables are treated as unset, and whitespace around option values is trimmed.
- Dependencies updated to clear five RustSec advisories (rustls, h2, lru, anyhow, crossbeam-epoch).
main.rswas split into focused modules (run,modes,setup,ui,metrics,notify,errors,shutdown).- TUI: failures are reported honestly, the terminal is restored on every exit (including a closed terminal), small screens and narrow layouts fit, and a cancelled run shows no misleading counters.
Fixed
- The CLI exited with a usage error whenever
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,MIRROR_TOKENorGITHUB_OAUTH_CLIENT_IDexisted in the environment, even empty (every Compose, Unraid and Kubernetes launcher); the dependency rules now apply to flags typed on the command line only. - Incremental runs lost data: the automatic
--sinceoverwroteissues.jsonwith only the changed issues (see Breaking changes), and a dry run advanced the same state. - A killed run could leave a half-initialised clone that later fetches treated as complete; a stale lock from PID reuse blocked runs in containers.
SIGTERMduring a slow call or a long clone could leave the process running until it was killed.- Repository names, release tags, asset names and gist ids from the API can no longer place files outside the output tree; an unsafe repository name is a recorded failure.
- A wiki that vanished upstream keeps its copy and logs a warning instead of failing; "exit 128" is only treated as "no wiki" when git says the repository was not found.
- Pull requests were missing their conversation comments and events (they share the issue number space).
--discussionsand--projectscalled REST routes that do not exist and logged "feature not enabled" for every repository; they now log one clear warning per run (the flags still back up nothing; GitHub offers Discussions through GraphQL only and Classic Projects are retired).- Webhook URLs are logged as
scheme://hostonly. - Docker and Compose: see Packaging below.
Removed
- The
GIT_ASKPASSscript and the PID-file lock (and with it the workspace's onlyunsafeblock). - The effect of
--keep-lastand--max-age-days(deprecated, see above). - Compose: the bundled
minio/minioservice and the defaultconfig.tomlbind mount.
Security
- Git credentials travel in an environment variable of the git child plus an
inline credential helper bound to the clone URL's origin (after resetting
credential.helper): never in an argument list, never on disk, never offered to another host, never persisted by a configured helper. Mirror pushes use the same mechanism. Commands in an existing repository trust exactly that path through-c safe.directory=<path>. - Failure messages, logs and reports are scrubbed of tokens and URL credentials.
- Release-asset downloads never forward the credential across origins.
- Mirrors are private by default (
--mirror-publicopts public sources in) and never overwrite a repository the tool did not create. - S3: keyed (HMAC) content digests for encrypted uploads so the bucket learns no
unkeyed hash of the plaintext; delete-stale guarded as described above; credentials
validated and kept out of
Debugoutput. github-backup-coreis#![forbid(unsafe_code)].- Dependencies updated past five open advisories.
- Token redaction in error output (
<prefix>_<redacted>), and the credential type'sDebugoutput no longer prints the token.
Packaging, CI and release
- Release: Linux binaries are static musl builds made by the Dockerfile's
exportstage (native on x86_64 and aarch64, nocross), checked withfileandlddand run with--versionbefore upload;--lockedeverywhere; a pinned toolchain; a tag must point at a commit onmain. Each of the five binaries gets a signed GitHub build-provenance attestation (actions/attest; the checksum files,SHA256SUMS.txtand the container images are not attested; no SBOM). CI builds and smoke-tests the Docker image; Dependabot and a daily advisory audit were added. - Image:
alpine:3.23runtime withgit,git-lfs,openssh-client,ca-certificatesandtini; numericUSER 1000:1000(uid 99 has a passwd entry for Unraid);safe.directorytrusts/backuponly; allow-list.dockerignore;UMASKsupport; the dependency layer compiles third-party crates. - Entrypoint wrapper (
docker/entrypoint.sh): arguments pass through verbatim; without arguments it builds them fromGITHUB_OWNER,BACKUP_MODEandBACKUP_FLAGS; empty optional variables are dropped; shell metacharacters inBACKUP_FLAGSandBACKUP_MODEare refused (exit 64). - Compose: profiles
doctor,tui,verify,s3,b2,minio(an S3 server you run),codebergandgitlab; every service inherits the full base environment (the B2/MinIO profiles previously uploaded unencrypted even withBACKUP_ENCRYPT_KEYset); fixed flags live inentrypoint:sorun SERVICE OWNER --allkeeps them; empty variables are no longer injected. - Unraid: Community Applications template with every important option as a form
field, runs as 99:100, no
--rm(sodocker startworks for scheduled runs), correctedCategoryandSupport,ca_profile.xmlin the repository root. The template's first entry claimed it matched binary v0.3.2; the run modes--doctor,--check,--list-scopes,--print-config-templateand the entrypoint wrapper are not in the v0.3.2 image.
Documentation
The documentation was rewritten against the code (every statement checked by
running the binary or reading the source): CLI reference and exit codes, config
precedence, incremental runs and the state file, monitoring (report, metrics,
webhook), restore, S3 and encryption, mirroring, Docker and Unraid, security,
troubleshooting and the operations runbook. Duplicated pages were merged (DOCKER.md,
ARCHITECTURE.md and CONTRIBUTING.md are now the single sources of their book
pages). The README and the book describe main; a version note says how that
differs from v0.3.2.
0.3.2 — 2026-04-12
Maintenance release focused on the release pipeline, distribution strategy, supply-chain hardening, mutation-testing coverage, and CI/docs polish. No runtime behaviour changes for end users; all existing configurations and command-line flags are unchanged.
Changed
- Release distribution: dropped crates.io; releases are now binary +
Docker only. Every workspace crate is marked
publish = false, thepublish/publish-dry-run/packagejobs and the protectedcrates-ioGitHub Environment have been removed from.github/workflows/release.yml, andCARGO_REGISTRY_TOKENis no longer referenced. Users install via one of three methods, in recommended order:- Pre-built binary from the GitHub Releases page (five targets:
Linux x86_64/aarch64, macOS x86_64/aarch64, Windows x86_64),
each with a
.sha256checksum and SLSA Level 2 build provenance attestation (correction added later: this was wrong for the binaries. The attestation step of the v0.3.2 workflow covered only the.cratearchives, which were uploaded as workflow artifacts and not published; the v0.3.2 release binaries have no attestation). - Multi-arch container image from GHCR
(
ghcr.io/tomtom215/github-backup-rust:<tag>), wired up with a Docker Compose file at the repo root that supports local, S3, B2, MinIO, and Codeberg profiles and reads secrets from a.envfile (template:compose.example.env). cargo install --git https://github.com/tomtom215/github-backup-rust --tag v0.3.2 github-backup. The rationale is thatgithub-backupis a CLI application, not a reusable library — the workspace split exists for internal code organisation, not for third-party consumption. Dropping crates.io removes the operational burden of seven name reservations, seven docs.rs builds, and per-release intra-workspace version-pin synchronisation.
- Pre-built binary from the GitHub Releases page (five targets:
Linux x86_64/aarch64, macOS x86_64/aarch64, Windows x86_64),
each with a
- Multi-stage release pipeline (
.github/workflows/release.yml):validate → ci → security → binaries → github-release → docker. The validate job enforces semver tag format, checks[workspace.package].versionagainst the tag, verifies every intra-workspace dependency line (if any carryversion =pins), and requires a matching## [X.Y.Z]CHANGELOG entry before any build runs. - Intra-workspace
version =pins dropped from the rootCargo.toml. With every member markedpublish = false, only thepath =source is used to resolve internal dependencies, so there is nothing to keep in lock-step every release. - Portfolio-grade docs, manifests, and CI polish (PR #16): the
root
README.md,ARCHITECTURE.md, every per-crateCargo.toml(descriptions, keywords, categories, documentation links), the full mdBook (docs/src/**),Dockerfile,clippy.toml, anddeny.tomlhave been reviewed and aligned.README.mdanddocs/src/getting-started/installation.mdhave been rewritten around the new binary / Docker / source install story. docker-compose.ymlnow pulls from GHCR by default via a shared YAML anchor (image: ghcr.io/tomtom215/github-backup-rust), mounts./backups→/backupand./config.toml→/etc/github-backup/config.toml, and fails fast if required secrets are missing from.env.build: .remains available as a commented-out override for local development. A newcompose.example.envtemplate ships at the repo root.mdBookpinned to0.4.40in CI and the Pages workflow to match the version that themdbook-linkcheckbackend is known to be compatible with.- Pages deployment publishes
docs/book/html/instead ofdocs/book/. Enabling thelinkcheckbackend causes mdBook to emit each backend into its own subdirectory, so the HTML tree is now one level deeper.
Added
- SLSA Level 2 build provenance attestations for every pre-built
binary produced by the release pipeline, verifiable with
gh attestation verify(correction added later: not true of the release binaries; see the note under Changed above. Attestation of the binaries starts with the first release after v0.3.2). - Mutation-testing configuration (
.cargo/mutants.toml): curated exclude list for generated / panic-only /#[cfg(...)]-gated code paths socargo mutantsproduces actionable survivors only. Aworkflow_dispatch-only CI job runs the full mutation suite on-demand without blocking every push to main. - Pagination malformed-URL tests in
github-backup-clientand rate-limit edge-case tests to catch mutants that would otherwise silently degrade GitHub API retry/backoff behaviour. BackupRunHistory::pushregression tests covering the deduplication and ordering mutants flagged bycargo mutants.cargo-auditconfiguration (.cargo/audit.toml) with an explicit ignore entry and rationale for the informationalrandadvisory that does not affect this project.
Fixed
- Release workflow
validatejob could never succeed: the awk extractor for[workspace.package].versionused the greedy regex.*"to strip theversion = "prefix, which matched through the closing quote and left an empty string. The job then always reported "could not parse[workspace.package].version" and failed before comparing against the tag. Replaced with^[^"]*"so the strip stops at the first quote. Deploy Book to GitHub Pagesworkflow failed with "The commandmdbook-linkcheckwasn't found":book.tomlenables[output.linkcheck], but the Pages workflow only installedmdbookitself. The link checker is now installed alongsidemdBook, matching the existingci.ymlstep.is_process_alivefalse positive on macOS:kill(0, 0)returns success on BSDs for PID 0 (the kernel swapper), which caused the lockfile staleness check to treat a stale lock with PID 0 as still held. The check now rejects PID 0 explicitly.- macOS CI permissions test that relied on
chmod 000being honoured when the test runs as root. cargo-denyadvisoriescheck now runs withcontents: readpermissions in CI; the previous implicit default blocked the step on pull requests from forks.BackupEvent::RepoCompletedmissing error field and relateddead_codeclippy errors surfaced by the mutation-testing work.- Rustdoc intra-doc link ambiguity for the
writemodule and several broken internal mdBook links that surfaced after the link-check backend was added.
Security
- Dependency audit: all advisories reviewed and either fixed,
upgraded away, or explicitly ignored with justification in
.cargo/audit.toml. The release pipeline now runscargo-deny check licenses bans advisories sourcesas a gating job before any binary or container image is produced.
0.3.1 — 2026-03-29
Added
-
--restoremode (labels, milestones, and issues): the--restoreflag reads every repository'slabels.json,milestones.json, andissues.jsonfrom the backup and re-creates them in the target organisation via the GitHub REST API. Pull requests embedded inissues.jsonare skipped. Existing resources (HTTP 422) are silently skipped. Requires--restore-target-organd a token with repository write access; an interactive confirmation banner is printed unless--restore-yesis supplied. -
AES-256-GCM at-rest encryption for S3 (
--encrypt-key): provide a 32-byte hex key (64 hex chars) and every file is encrypted with AES-256-GCM before upload. The wire format is[12-byte random nonce][ciphertext + 16-byte tag]. Encrypted objects receive a.encsuffix in S3. The key may also be supplied via theBACKUP_ENCRYPT_KEYenvironment variable, and a--decryptmode reverses the process locally. -
post_processmodule: mirror push, S3 sync, Prometheus metrics, diff, and retention logic live in a dedicatedpost_process.rsmodule in the main binary. -
Write endpoints in
GitHubClient:create_label(),create_milestone(), andcreate_issue()use a sharedpost_jsonhelper with the same rate-limit and 5xx retry behaviour as the GET path. -
Interactive TUI (
--tui): full-screen terminal interface built with Ratatui 0.30. Five screens — Dashboard, Configure, Run, Verify, Results — cover the end-to-end workflow without leaving the terminal. A customtracing_subscriber::Layerroutes log lines to the Run screen's log panel; atokio::sync::oneshotcancellation channel aborts a running backup onCtrl+C. The TUI crate ships unit tests that exercise the full state machine without a real terminal. -
Config file now covers S3 and mirror settings:
s3_bucket,s3_region,s3_prefix,s3_endpoint,s3_access_key,s3_secret_key,s3_include_assets,mirror_to,mirror_token,mirror_owner, andmirror_privateare valid TOML keys. All values can be overridden by CLI flags. -
Config file now covers clone behaviour:
prefer_ssh,clone_type,lfs,no_prune, andreportare valid TOML keys.
Changed
-
MSRV raised from 1.85 to 1.88:
ratatui@0.30and its transitive dependencies require Rust 1.88. The workspacerust-versioninCargo.tomlhas been updated accordingly. -
deny.tomlallows theZliblicence:foldhash@0.2(a transitive dependency ofratatui-core) is Zlib-licensed. -
s3_region/s3_prefixare nowOption<String>inArgs, consistent withconcurrency. The defaults (us-east-1and"") are applied atbuild_s3_configtime so a config file can supply the values when the CLI flags are absent.
Fixed
orgmerge bug:merge_confignow appliescfg.orgwhen the CLI--orgflag was not passed. Previously the config-file value was silently ignored.
Internal
repository.rssplit: inline test module extracted torepository_tests.rsvia the#[path]attribute, separating production code from its tests.
0.3.0 — 2026-03-29
Added
-
--clone-host <HOST>(GITHUB_CLONE_HOSTenv /clone_hostconfig key): overrides the hostname in every git clone URL returned by the API. Intended for GitHub Enterprise Server deployments where the API endpoint and the git clone endpoint are on separate hosts. Applied to repository, wiki, and gist clones. -
--concurrencyis now truly optional:Args::concurrencyisOption<usize>, so a config-file value such asconcurrency = 8is no longer overridden by the implicit CLI default. -
BackupStats::add_gists(n): batch increment replacing the previous per-item loop in the engine. -
repos_discoveredinBackupStats::Display: the summary line now showsN/M backed up(backed-up / discovered) so operators can see at a glance whether any repositories were skipped or errored.
Fixed
-
Dead code removed:
FsStorage::write_bytes_ownedand an unuseduse bytes::Bytesimport instorage.rs. -
run_gitsignature simplified: removed thein_cwd: boolparameter. Callers now pass the working directory directly.
Internal
- Module extraction: inline metadata backup blocks in
engine.rs(labels,milestones,hooks,security_advisories,topics,branches) split into dedicated modules underbackup/. endpoints/directory:client/endpoints.rssplit into eight focused submodules (actions,issues,keys,org,pulls,repo_meta,repos,social).api_client/directory: trait definition (mod.rs) split from the blanketimpl BackupClient for GitHubClient(impl_github.rs).config/directory:config.rssplit intocredential,output,clone_type,options, andfilesubmodules.report.rs: report-writing helpers extracted frommain.rsinto a dedicated module with unit tests.- Broken intra-doc link fixed in
api_client/mod.rs.
Added
-
GitHub Actions workflow backup (
--actions,--action-runs): newWorkflowandWorkflowRuntypes added togithub-backup-types. Two new client endpoints (list_workflows,list_workflow_runs) and a dedicated backup module (backup/actions.rs) ingithub-backup-core. The engine writesworkflows.jsonper repository when--actionsis set, and optionallyworkflow_runs_<id>.jsonper workflow when--action-runsis also set. Both endpoints handle 403/404 gracefully (Actions disabled, token scope).BackupStatsnow tracksworkflows_fetchedand the JSON report includes the counter.--action-runsis intentionally excluded from--alldue to its potentially large output. -
Deployment environment backup (
--environments): newEnvironment,EnvironmentProtectionRule, andDeploymentBranchPolicytypes added togithub-backup-types. New client endpoint (list_environments) and backup module (backup/environments.rs) writeenvironments.jsonper repository. 404/403 responses (no environments or insufficient permissions) are logged and skipped gracefully. -
TOML config file (
--config/-c): supply any backup option through aconfig.tomlfile; CLI flags always take precedence. The newConfigFiletype ingithub-backup-typesis parsed with thetomlcrate and merged intoArgsbefore the backup starts. -
Backup summary report (
--report <FILE>): write a machine-readable JSON summary of the run to an arbitrary path after the backup completes. The report now includestool_version,started_at(ISO 8601),duration_secs, per-category counters, and asuccessboolean — useful for monitoring and alerting integrations. -
Modular CLI:
cli.rs(724 lines) refactored into:cli/args.rs—Argsstruct,merge_config(),into_backup_options()cli/clone_type.rs—CliCloneTypeparsercli/mod.rs— re-exports
-
Modular git runner:
git.rs(600 lines) refactored into:git/mod.rs—CloneOptions,GitRunnertrait,ProcessGitRunnergit/askpass.rs—AskpassScriptRAII guardgit/spy.rs—SpyGitRunnertest stub + tests
-
Repository name filters (
--include-repos/--exclude-repos): back up only a subset of repositories using glob patterns (*/?), matching is case-insensitive. Patterns can be comma-separated or the flag can be repeated.--exclude-repostakes precedence over--include-repos. -
--since <DATETIME>: limit issue and pull-request API calls to items updated at or after an ISO 8601 timestamp. Enables efficient incremental backups — re-usestarted_atfrom the previous run's JSON report. -
Topics backup (
--topics): writetopics.json(repository tags) per repository. Already had aGitHubClientendpoint; now wired end-to-end through theBackupClienttrait and the engine. -
Branch list backup (
--branches): writebranches.jsonper repository containing all branch names, tip SHA-1s, and protection status. NewBranch/BranchCommittypes added togithub-backup-types. -
BackupStats::elapsed_secs(): wall-clock duration tracking usingstd::time::Instant; displayed in theDisplayoutput and included in the JSON report. -
GitHub Pages deployment (
pages.yml): CI workflow that builds the mdBook and deploys it to thegithub-pagesenvironment on every push tomain. -
Full mdBook documentation in
docs/covering installation, quick start, authentication, all backup categories, storage backends, configuration, deployment, monitoring, security, troubleshooting, and the workspace architecture. -
GitHub Enterprise Server support via
--api-url <URL>(orGITHUB_API_URLenvironment variable /api_urlconfig file key). Pass the GHES API base URL (e.g.https://github.example.com/api/v3) and all API requests are directed there. NewGitHubClient::with_api_url()constructor added togithub-backup-client. -
Extended backup stats:
BackupStatsnow tracksissues_fetchedandprs_fetchedacross all repositories. Both counters appear in the log output, theDisplaysummary, and the JSON report (--report). -
--sinceformat validation: the ISO 8601 value is now validated before the backup starts, producing a clear error for malformed timestamps. -
dry_rungap fixed:backup_gistsandbackup_user_datanow respectopts.dry_runand skip all I/O in dry-run mode (previously only per-repository operations were skipped). -
Modular code:
config.rssplit intoconfig.rs+glob.rs;args.rssplit intoargs.rs(struct) +args_impl.rs(merge_config/into_backup_options).
Changed
ownerpositional argument is now optional; it can be supplied via theownerkey in the config file instead.--outputflag now defaults to.when not specified via CLI or config.BackupClient::list_issuesandBackupClient::list_pull_requestsnow accept an optionalsince: Option<&str>parameter (used by--since).BackupOptions::all()now also enablestopicsandbranches.BackupStats::Displaynow includes elapsed time, issues fetched, and PRs fetched.backup_issuesandbackup_pull_requestsreturnu64(count of items fetched) instead of(). The engine uses these to populateBackupStats.
0.2.0 — 2026-01-15
Added
- OAuth device flow:
--device-auth+--oauth-client-idenable interactive authentication via GitHub's device authorisation flow without creating a long-lived PAT. - Gitea/Codeberg/Forgejo mirror push: after the primary backup, push every
cloned repository as a mirror to a Gitea-compatible instance using
--mirror-to,--mirror-token,--mirror-owner, and--mirror-private. - S3-compatible storage sync:
--s3-bucket(plus region, prefix, endpoint, access-key, secret-key flags) syncs JSON metadata — and optionally binary release assets — to any S3-compatible object store. Uses a pure-Rust SigV4 implementation; no AWS SDK or OpenSSL required. - Incremental S3 sync:
HeadObjectchecks before eachPutObjectso already-uploaded objects are skipped on subsequent runs. - Shallow clone support via
--clone-type shallow:<depth>. - Git LFS support via
--lfs. - Docker: multi-stage Alpine Dockerfile and
docker-compose.ymlwith service profiles for S3/B2/MinIO/Codeberg. BackupStats: lock-freeAtomicU64counters shared across concurrent repository backup tasks.ARCHITECTURE.mdandDOCKER.mddocumentation.
Changed
BackupEngineis now generic overStorageandGitRunnerfor compile-time dispatch and zero-overhead testability.
0.1.0 — 2025-12-01
Added
- Complete Rust rewrite of the Python
github-backupreference implementation. - Repositories:
mirror,bare, andfullclone modes. - Issues: metadata, comments, timeline events.
- Pull requests: metadata, review comments, commit lists, reviews.
- Releases: metadata + optional binary asset download.
- Gists: owned and starred.
- Wikis: bare mirror clones.
- User data: starred repos, watched repos, followers, following.
- Repository metadata: labels, milestones, webhooks, security advisories.
- Trait-based design:
Storage,GitRunner, andBackupClienttraits with full in-memory test stubs (MemStorage,SpyGitRunner,MockBackupClient). - RAII credential cleanup:
GIT_ASKPASStemp scripts are deleted even on panic, ensuring no tokens are left on disk. - Rate-limit awareness: automatic backoff on
X-RateLimit-Remaining: 0. - Retry on 5xx: up to 3 retries with exponential backoff.
- Concurrent backup: semaphore-based, configurable with
--concurrency. - Dry-run mode:
--dry-runpreviews what would be backed up. - Shell completions: bash, zsh, fish, PowerShell, elvish.
- 145 unit tests covering all modules.
proptestround-trip tests for all serialised types.- CI: rustfmt, clippy (
-D warnings), tests (Ubuntu + macOS), MSRV 1.85,cargo-audit,cargo-deny. - Dependency policy in
deny.toml: no OpenSSL, no reqwest, no native-tls.