A CLI tool that selectively mirrors remote Git repositories to local paths based on YAML configuration.
- Selective sync — choose exactly which branches and tags to mirror using exact names, wildcards (
*), or regex patterns - Pruning — detect and remove local branches/tags that no longer match any configured pattern
- Stale pruning — optionally remove inactive branches that have no new commits in a specified period (e.g., last 6 months);
prune_staleonly takes effect whenpruneis also enabled — stale branches are skipped before branch sync when both are set - Daemon mode — run as a foreground polling service with per-repo poll intervals
- Live config reload — daemon re-reads its config on
SIGHUPorPOST /reloadand applies adds, removes, and edits without a restart (Prometheus-style: explicit trigger, no filesystem watcher) - Partial-validate tolerance — one invalid repo (missing fields, bad regex, unreachable HTTPS URL) is logged and dropped instead of blocking every other repo
- SSH and HTTPS auth — private repos via SSH key, public repos via anonymous HTTPS
- Working tree checkout — optionally keep a working tree checked out on a specific branch or tag
- OpenVox mode — create per-branch/tag directories with sanitized names, ideal for Puppet environments
- Production alias (OpenVox) — optional
production_alias: truecreates/updates aproductionsymlink to the upstream default-branch directory when upstream does not have a realproductionbranch - Lightweight clones — repos are initialized empty and only configured refs are fetched
# Install
go install github.com/obmondo/gfetch/cmd/gfetch@latest
# Create a config file
cat <<'EOF' > config.yaml
repos:
my-repo:
url: https://github.com/example/repo.git
local_path: /var/repos/my-repo
poll_interval: 5m
branches:
- main
EOF
# Run a one-shot sync
gfetch syncOr run with Docker:
docker run -v /path/to/config.yaml:/home/gfetch/config.yaml \
-v /var/repos:/var/repos \
ghcr.io/obmondo/gfetch daemongit clone https://github.com/obmondo/gfetch.git
cd gfetch
go build -o gfetch ./cmd/gfetchgo install github.com/obmondo/gfetch/cmd/gfetch@latestPre-built binaries for Linux and macOS (amd64/arm64) are available via GitHub Releases. Each release includes a checksums.txt for verification.
A pre-built image is published to the GitHub Container Registry on every tagged release.
# Pull the latest image
docker pull ghcr.io/obmondo/gfetch
# Run the daemon with config and repo storage mounted
docker run -v /path/to/config.yaml:/home/gfetch/config.yaml \
-v /var/repos:/var/repos \
ghcr.io/obmondo/gfetch daemonTo build locally:
docker build -t gfetch .
# With version info
docker build --build-arg VERSION=1.0.0 \
--build-arg COMMIT=$(git rev-parse --short HEAD) \
--build-arg DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) \
-t gfetch .gfetch reads a YAML config file (default: config.yaml in the current directory). Use a defaults: key to share settings across multiple repositories.
defaults:
ssh_key_path: /home/gfetch/.ssh/id_rsa
poll_interval: 10m
prune: true # remove branches/tags no longer matching any pattern
prune_stale: true # remove branches with no commits in 6 months (requires prune: true)
stale_age: 180d # supports d (days)
production_alias: false # OpenVox-only: if true and upstream has no production branch,
# create/update production -> <default-branch-dir> symlink
# TODO: Add integration tests for real SSH Git repositories.
# TODO: Implement concurrent syncing for OpenVox mode to improve performance and reliability.
# TODO: Add configurable concurrency limit per repository.
repos:
my-service:
url: git@github.com:obmondo/my-service.git
branches:
- main
- /^release-.*/ # regex pattern
tags:
- "*" # wildcard matches all tags
internal-tool:
url: git@github.com:org/tool.git
prune_stale: false # override default for this repo
branches:
- mainSee docs/configuration.md for the full configuration reference, including all fields, pattern syntax, auth methods, and validation rules.
Important (OpenVox environments): set production_alias: true to keep a stable production symlink pointing to the upstream default branch directory. gfetch skips alias creation if upstream already has a production branch.
| Flag | Default | Description |
|---|---|---|
--config, -c |
config.yaml |
Path to config file |
--log-level |
info |
Log level: debug, info, warn, error |
One-shot sync of all repos (or a specific repo).
gfetch sync # sync all repos
gfetch sync --repo my-service # sync a specific repo
gfetch sync --prune # sync and remove obsolete branches/tags
gfetch sync --prune-stale # sync and remove branches with no commits in 6 months
gfetch sync --stale-age 30d # custom threshold for stale pruning
gfetch sync --prune --dry-run # show what would be pruned without deleting
gfetch sync --prune --prune-stale # also skips stale branches before branch sync| Flag | Default | Description |
|---|---|---|
--repo |
(empty) | Sync only the named repo |
--prune |
false |
Delete local branches/tags that no longer match any pattern |
--prune-stale |
false |
Delete local branches with no commits in the last 6 months; with --prune, stale branches are skipped before branch sync |
--stale-age |
180d |
Custom age threshold for stale pruning (e.g., 30d) |
--dry-run |
false |
Show what would be pruned without actually deleting |
Run as a foreground polling daemon. Each repo syncs immediately on start, then polls at its configured poll_interval. Shuts down gracefully on SIGINT or SIGTERM.
gfetch daemon
gfetch daemon --config /etc/gfetch/config.yaml --log-level debugPruning is controlled via prune and prune_stale config fields per-repo. Set prune: true in config to enable obsolete-ref pruning; prune_stale: true (also requires prune: true) to additionally prune inactive branches.
The daemon reloads --config (file or directory) explicitly on SIGHUP or POST /reload, following Prometheus's model — there is no filesystem watcher. Edits, adds, and removes are applied without a restart; in-flight syncs finish against the config snapshot they started with, and the next scheduled fire uses the new config. Reload failures (load, validate, apply) are logged and counted, and the previous config keeps running.
The daemon exposes the following HTTP endpoints on the listen address (default :8080):
| Method | Path | Description |
|---|---|---|
GET |
/health |
Liveness/readiness probe — returns 200 with {"status":"ok"}. |
GET |
/metrics |
Prometheus metrics. |
POST |
/reload |
Re-read the config from disk, validate it, and replace every running job. Returns {"repos": [...]} listing the repos now managed. |
POST |
/sync |
Trigger a manual sync of every configured repo. |
POST |
/sync/{repo} |
Trigger a manual sync of a single repo. |
To pick up a config change before syncing, call POST /reload (or send SIGHUP) first, then POST /sync.
Validate the config file and exit.
gfetch validate-config
gfetch validate-config -c /path/to/config.yamlPrint the fully resolved configuration as YAML. Loads the config, applies defaults, validates, and outputs the result to stdout.
gfetch cat
gfetch cat -c /path/to/config.yamlPrint version information.
$ gfetch version
gfetch dev (commit: none, built: unknown)Version, commit, and build date are injected at build time via ldflags when using GoReleaser or a manual build with -ldflags.
TBD