GitSyncer is a tool for synchronizing git repositories between multiple organizations (e.g., GitHub and Forgejo). It automatically keeps all branches in sync across different git hosting platforms.
It has been vibe coded mainly using AI tools (Claude Code CLI and amp).
📚 Full documentation is available in the doc/ directory
- Architecture Overview - System design and components
- API Reference - Complete reference of all packages and functions
- Configuration Guide - Detailed configuration options
- Usage Examples - Common usage patterns and workflows
- Development Guide - Contributing and development setup
- Sync repositories between multiple git organizations
- Automatic branch creation on remotes that don't have them
- Batch sync multiple repositories with a single command
- Discover and sync public GitHub repositories to configured destinations
- Automatic repository creation on GitHub and Forgejo
- SSH backup locations with automatic bare repository creation
- One-way backup to private SSH servers (e.g., home NAS)
- Merge conflict detection with clear error messages
- Never deletes branches (only adds/updates)
- GitHub token validation tool
- Backup sync for full-sync modes, with
--backupavailable for single-repo andsync allruns - Per-destination backup fail-fast for a run: after a backup fails, later repos skip only that destination
- Default once-daily sync limit with --force override
- Opt-in sync throttling with --throttle based on local activity
- AI-powered project showcase generation for documentation
- Weekly batch run mode with
manage batch-runfor automated synchronization
go build -o gitsyncer ./cmd/gitsyncerCreate a configuration file at ~/.config/gitsyncer/config.json (or specify a custom path with -c):
{
"organizations": [
{
"host": "ssh://git@forgejo.example",
"name": "yourusername",
"forgejo_api_base": "https://forgejo.example/api/v1",
"forgejo_owner": "yourusername"
},
{
"host": "git@github.com",
"name": "yourusername"
},
{
"host": "user@nas.local:git",
"backupLocation": true,
"forcePush": true,
"descriptionSyncHost": "root@nas.local",
"descriptionSyncRoot": "/srv/git/repos"
}
],
"repositories": [
"repo1",
"repo2"
],
"showcase_output_dir": "~/git/foo.zone-content/gemtext/about",
"showcase_stats_branches": {
"foo.zone": "content-gemtext"
},
}GitSyncer uses a modern command-based structure that provides:
- Clear organization of related functionality
- Built-in help for every command and subcommand
- Consistent flag naming and behavior
- Better discoverability of features
Explore available commands and get help:
# Show available commands
gitsyncer --help
# Show help for a specific command
gitsyncer sync --helpgitsyncer sync repo myproject
# Include backup locations for a single repository run
gitsyncer sync repo myproject --backup
# Preview what would be synced
gitsyncer sync repo myproject --dry-run
# Override sync interval checks
gitsyncer sync repo myproject --force
# Sync without AI-generated release notes
gitsyncer sync repo myproject --no-ai-release-notes
# Auto-create releases without prompts (AI notes enabled by default)
gitsyncer sync repo myproject --auto-create-releasesEach repository is synced at most once every 24 hours by default. Successful sync times are stored in .gitsyncer-state.json inside the work directory. Use --force to bypass sync interval checks.
# Throttle syncing based on local activity in ~/git/<repo>
gitsyncer sync repo myproject --throttle
# Throttle all public repo sync modes
gitsyncer sync bidirectional --throttle
gitsyncer sync github-to-forgejo --throttleWhen --throttle is enabled, GitSyncer still applies the default once-daily limit first, then checks ~/git/<repo> for commits in the last 7 days. If no recent commits are found (or the repo is missing locally), the repo sync is allowed only once per random interval between 60 and 120 days and the next allowed date is stored. Sync state is stored in .gitsyncer-state.json in the work directory. Use --force to bypass both interval checks.
gitsyncer sync all
# Include backup locations when running sync all
gitsyncer sync all --backup# Complete bidirectional sync of all public repos
gitsyncer sync bidirectional
# Preview what would be synced
gitsyncer sync bidirectional --dry-runsync bidirectional, sync github-to-forgejo, and manage batch-run now always try configured backup locations when backupLocation: true is present in the config. If a backup push fails because that host is offline or unavailable, GitSyncer records that failure in memory and skips that destination for the rest of the process while continuing the primary sync targets and other backups.
Forgejo can be used as a one-way public backup, as a one-way mirror that is pushed on every run, or as an optional bidirectional sync peer. Repository creation and descriptions use its Gitea-compatible API, while Git data is transferred over SSH. To make it a peer that is fetched, merged, and pushed whenever available, set optional: true instead of backupLocation:
{
"organizations": [{
"host": "ssh://git@code.f3s.buetow.org:2022",
"forgejo_api_base": "https://code.f3s.buetow.org/api/v1",
"forgejo_owner": "snonux",
"forgejo_owner_type": "organization",
"optional": true
}]
}To mirror GitHub to Forgejo one way, set pushOnly: true (it may be combined
with optional: true so an unreachable Forgejo is skipped instead of failing
the run):
{
"organizations": [{
"host": "ssh://git@code.f3s.buetow.org:2022",
"forgejo_api_base": "https://code.f3s.buetow.org/api/v1",
"forgejo_owner": "snonux",
"forgejo_owner_type": "organization",
"optional": true,
"pushOnly": true
}]
}A pushOnly organization is pushed on every sync (no --backup needed) but is
never fetched, merged, or cloned from, so commits, branches, and tags that exist
only there never reach GitHub. While a pushOnly organization is configured,
the GitHub discovery commands (sync github-to-forgejo, sync bidirectional,
manage batch-run) sync every public, non-fork, non-archived GitHub repository
instead of only those in repositories; that list then only decides which
repositories also go to Forgejo. Pushes are not forced unless forcePush: true
is set, so a branch or tag that diverged on the mirror makes its push fail
rather than being overwritten. GitSyncer never deletes branches or tags on the
mirror.
Set an API token with the minimal scopes for the configured owner type:
write:repository plus write:organization for an organization, or
write:repository plus write:user for a user. The owner type may be user
(the default for backward compatibility) or organization. For an organization
owner, use a token from an interactive user that is authorized to create
repositories in that organization; an organization pseudo-user cannot issue a
usable token. Store the token in
protected process environment as FORGEJO_TOKEN, or store it in
~/.gitsyncer_forgejo_token with mode 0600. The environment takes precedence;
surrounding
whitespace is removed from either source. Never put a Forgejo token in the JSON
configuration. A repository named demo uses
ssh://git@code.f3s.buetow.org:2022/snonux/demo.git. Forgejo targets with
backupLocation: true or pushOnly: true are never fetched. Targets with only
optional: true participate fully in branch discovery, fetch, merge, and push. Metadata API
failures are reported but do not prevent Git-over-SSH synchronization; a fetch
or push failure skips that Forgejo peer for the remainder of the run without
interrupting synchronization between the other hosts. GitSyncer never runs
remote shell repository-creation or description-file commands for Forgejo.
When a Forgejo organization is configured and FORGEJO_TOKEN (or
~/.gitsyncer_forgejo_token) is available, release checking also creates and
updates releases on Forgejo using the same Gitea-compatible API.
# Check all repositories
gitsyncer release check
# Check specific repository
gitsyncer release check myproject# Create releases with confirmation prompts (AI notes enabled by default)
gitsyncer release create
# Auto-create without prompts
gitsyncer release create --auto
# Create without AI-generated notes
gitsyncer release create --no-ai-notes
# Update existing releases with AI notes
gitsyncer release create --update-existing
# Create for specific repository without AI
gitsyncer release create myproject --no-ai-notes
# Use amp for AI release notes
gitsyncer release create --ai-tool amp- Default flow: tries
ollama run glm-5.3-flash:cloudfirst with the commit/diff payload and instruction prompt combined into a single argument (--hidethinkingso chain-of-thought is not captured). - Fallback: if ollama is not available or fails, falls back to
hexai, thenclaude --model sonnet, thenamp. - Explicit tool:
--ai-tool hexai,--ai-tool claude,--ai-tool amp, etc. adjust the fallback chain order. OpenRouter/piis no longer used. - Requirements: ensure
ollama,hexai,claude, orampare installed and available inPATH.
# Generate showcase with cached summaries
gitsyncer showcase
# Force regeneration of all summaries
gitsyncer showcase --force
# Generate showcase for one repository
gitsyncer showcase --repo gitsyncer
# Use a specific AI tool
gitsyncer showcase --ai-tool opencode# Delete repository from all organizations (with confirmation)
gitsyncer manage delete-repo old-project# Clean work directory (with confirmation)
gitsyncer manage clean
# Force clean without confirmation
gitsyncer manage clean --force# Run weekly batch sync (full sync + showcase)
gitsyncer manage batch-run
# Force run even if already run this week
gitsyncer manage batch-run --force# Test GitHub token
gitsyncer test github-token
# # Validate configuration
gitsyncer test config# List organizations
gitsyncer list orgs
# List repositories
gitsyncer list reposgitsyncer versionThese options are available for all commands:
-c, --config- Path to configuration file (default: ~/.config/gitsyncer/config.json)-w, --work-dir- Working directory (default: ~/git/gitsyncer-workdir)-h, --help- Show help for any command
The --backup flag enables syncing to backup locations configured in your config file. This is particularly useful when:
- Your backup server might be offline (e.g., home NAS)
- You want to control when backups happen
- You need to separate regular syncing from backup operations
Without --backup: GitSyncer only syncs between primary git hosts (GitHub, Forgejo, etc.)
With --backup: GitSyncer also pushes to backup locations marked with "backupLocation": true
# Regular sync (backup locations ignored)
gitsyncer sync repo myrepo
# Sync with backup enabled
gitsyncer sync repo myrepo --backup- GitSyncer clones the repository from the first configured organization
- Adds all other organizations as git remotes
- For each branch:
- Fetches from all remotes
- Merges changes from remotes that have the branch
- Pushes to all remotes (creating branches if needed)
By default, GitSyncer synchronizes only main, master, and develop. Set a
global list with branches, then override it for individual repositories with
repository_branches:
{
"branches": ["main", "stable"],
"repository_branches": {
"legacy-app": ["master", "release"]
}
}Branches outside the selected list are not synchronized. You can also exclude
branches from that list using regex patterns in exclude_branches:
{
"organizations": [...],
"repositories": [...],
"branches": ["main", "master", "develop"],
"excludScan report · 2026-10-12
- ✓ Prohibited terms or links
- ✓ Repository eligibility
- ✓ slopscore.md paperwork
- ✓ Content policy
- ✓ Risk review
0 comments
log in to comment.