English | 中文
Runs on Linux, macOS, Windows, and Android (Termux).
Git Auto Sync is built for personal repositories where keeping a working copy in sync with its upstream matters more than a tidy commit history. It commits local changes with an auto-generated message, rebases onto the upstream branch, and pushes, so an editor or note-taking workflow stays mirrored across machines without manual commits. It is not a good fit for shared repositories where reviewers rely on meaningful, human-written commit messages.
- Git
- A configured Git identity (
user.nameanduser.email) for commits - Building from source additionally requires Go 1.25 or newer
Choose one of the following two methods.
Option A - Download a release
Download the archive for your platform from the releases page and extract it. Each archive contains the two binaries (git-auto-sync, git-auto-sync-daemon; with .exe on Windows) and a completions/ folder with the shell completion scripts.
# Example: extract a Linux x86_64 release into ~/.local/share/git-auto-sync
mkdir -p ~/.local/share/git-auto-sync
tar -xzf git-auto-sync_*_Linux_x86_64.tar.gz -C ~/.local/share/git-auto-syncOn Linux and macOS, make both extracted binaries executable before running them:
chmod +x /path/to/binaries/git-auto-sync /path/to/binaries/git-auto-sync-daemonWithout the executable permission, the shell cannot run the files even if their directory is on PATH.
Option B - Build from source
git clone https://github.com/northhalf/git-auto-sync.git
cd git-auto-sync
makeBoth git-auto-sync and git-auto-sync-daemon are built into ./bin. Completion scripts live in completions/.
Whichever method you used, put the directory holding the binaries on your PATH so you can invoke git-auto-sync directly:
# Linux / macOS - add to ~/.bashrc or ~/.zshrc
export PATH="$PATH:/path/to/binaries"
# Windows (PowerShell) - set for the current user
[Environment]::SetEnvironmentVariable("PATH", $env:PATH + ";C:\path\to\binaries", "User")The completions/ folder ships scripts for bash, zsh, and PowerShell. Source the one for your shell (replace /path/to/completions/ with the actual path - the completions/ folder from the release archive, or completions/ in the cloned repo):
# bash - add to ~/.bashrc. Requires the bash-completion package.
source /path/to/completions/bash_autocomplete
# zsh - add to ~/.zshrc, or drop the file into a directory on your $fpath
source /path/to/completions/zsh_autocomplete
# PowerShell - dot-source from your profile
. C:\path\to\completions\powershell_autocomplete.ps1Run inside any Git repository:
git-auto-sync syncThis commits eligible changes, fetches the configured upstream branch, rebases onto it, and pushes.
Register a repository for continuous monitoring:
git-auto-sync daemon add /path/to/repoCheck status:
git-auto-sync daemon statusThe daemon watches the filesystem, polls every configured interval, and syncs automatically.
Download the Android_arm64 release archive. Do not use the Linux_arm64 archive in Termux: Android applies a different syscall policy, and ordinary Linux Go binaries can terminate with SIGSYS while looking up executables.
Install the runtime dependencies you need:
pkg install git
# Required only for daemon commands.
pkg install termux-services
# Optional: enables Android system notifications.
pkg install termux-apiAfter installing termux-services, restart Termux or run:
source "$PREFIX/etc/profile.d/start-services.sh"daemon run and daemon add create and manage $PREFIX/var/service/git-auto-sync-daemon through runit. daemon uninstall removes only that managed service definition; application configuration, repository registrations, state, and logs are preserved. An existing service directory not created by Git Auto Sync is never overwritten or removed.
Global settings are stored at $XDG_CONFIG_HOME/git-auto-sync/config.json when XDG_CONFIG_HOME is set. Otherwise, the path is $HOME/.config/git-auto-sync/config.json, normally /data/data/com.termux/files/home/.config/git-auto-sync/config.json in Termux. The git-auto-sync config --global ... commands read and write this file.
Android notifications also require the Termux:API Android app from the same distribution source as Termux. If termux-notification is missing, sync, watch, and daemon commands print a warning but continue normally. Android has no systemd-logind wake source, so wake notifications are disabled; filesystem events and the configured syncInterval continue to trigger synchronization.
Android 12 and newer may terminate Termux background processes. Exempting Termux from battery optimization can improve reliability. termux-wake-lock is optional and increases power use.
Git Auto Sync provides two modes:
- Manual:
git-auto-sync syncruns the sync pipeline once. - Daemon:
git-auto-sync daemon add <repo>starts a background service that monitors the repository.daemon run,daemon stop,daemon restart, anddaemon uninstallcontrol the service lifecycle.
Manage syncInterval, debounce, and gitexec separately with git-auto-sync config <key> [value] at --global (default) or --local scope.
Run git-auto-sync --help or git-auto-sync daemon --help for all commands.
Settings live at two scopes: global (in the platform config file, e.g.
~/.config/git-auto-sync/config.json on Linux - see File locations) and
per-repository (in the Git config section [auto-sync]). Repository settings override global
settings, which override defaults.
The config command accepts three settings:
| Key | Meaning | Accepted value | Default | Scope |
|---|---|---|---|---|
syncInterval |
Interval between periodic sync triggers. A periodic trigger starts a sync immediately without waiting for debounce. |
Positive integer, in minutes | 60 |
--global or --local |
debounce |
Quiet period after the latest eligible filesystem create, write, rename, or remove event before an event-driven sync. Each new eligible event resets the timer; periodic and wake triggers bypass it. | Positive integer, in minutes | 10 |
--global or --local |
gitexec |
Git executable used for Git subprocesses. | Path to an existing Git executable | git, resolved through PATH |
--global or --local |
--global is the default scope. --local writes to the current repository, and --unset removes the value at the selected scope so resolution falls back to the next level. Repository registration and daemon environment entries use daemon add, daemon rm, and daemon env; they are not config keys.
git-auto-sync config syncInterval 60 # minutes, default 60 (global)
git-auto-sync config --local syncInterval 30 # per-repo override
git-auto-sync config --local debounce 5 # minutes, default 10
git-auto-sync config --global gitexec /usr/bin/git # default: git from PATH
git-auto-sync config --list # show effective settings
git-auto-sync config --unset syncInterval # remove a setting (default: global)The watcher recursively listens for create, write, rename, and remove events on files and directories under the repository. A write event covers file-content modifications. The watcher applies the ignored-file rules before debounce handling, so events from Git metadata, ignored files, empty files, and other excluded paths do not reset the timer.
Each eligible filesystem event resets the debounce timer (default 10 minutes). A sync runs only after the configured period elapses with no further eligible events, so a burst of edits coalesces into a single commit rather than one commit per save. Periodic ticks from syncInterval and machine-wake events bypass the debounce and trigger a sync immediately, so scheduled and resume syncs are never delayed. Triggers that arrive while a sync is already running are coalesced into one follow-up sync after it finishes.
Every commit message is generated from git status --porcelain. Each eligible change becomes one line in the form XY path, where XY is the two-character Git status code (for example M, A, or ??) and path is the repository-relative path. The lines are sorted alphabetically and joined with newlines, then passed to git commit -m:
?? notes/2026-07-18.md
M src/main.go
A docs/changelog.md
There is no human-written summary. This is why the tool suits workflows that prioritize staying in sync over a readable history (see Use case).
Git Auto Sync uses rebase, not merge. Several conditions stop the watcher from syncing a repository until you intervene. When any of them is detected, it sends a desktop notification and pauses that repository; recovery requires fixing the condition and then restarting the daemon (or removing and re-adding the repository):
- A Git operation is in progress - an unfinished merge, rebase, cherry-pick, or revert.
- Detached HEAD - HEAD is not on a branch, so there is nothing to rebase onto or push.
- No upstream - the current branch has no configured upstream tracking branch.
- Missing Git identity -
user.nameoruser.emailis not set. - Rebase conflict - a rebase onto the upstream conflicts; the rebase is aborted and the repository pauses before push.
Network errors from fetch and push do not pause the repository. The watcher retries them with capped backoff (2, 4, 8, 15, 30, then 60 minutes) and resumes automatically once the remote is reachable again.
Files already tracked by Git are always synced and bypass every ignore rule. For untracked paths, any path with a dot-prefixed component is excluded from commits and filesystem monitoring, unless it is .github/ content, a Git control file (.gitignore, .gitattributes, .gitmodules, .gitkeep, or .mailmap) at any depth, or a file whose name ends in .example. Empty files(other than .gitkeep), Git-ignored files, Git metadata, and editor swap/backup files (e.g., Vim, Emacs) stay excluded even when an exception applies.
If you want a path that is excluded by default (for example a dotfile that is not a Git control file) to be synced, stage it yourself with git add. Once Git tracks the file it is always eligible and bypasses every ignore rule above.
In addition to the dot-prefix convention, OS-level hidden attributes are honored on platforms that have them, with no name exceptions:
- Windows - a file or any ancestor directory carrying the
FILE_ATTRIBUTE_HIDDENattribute (set through File Explorer properties orattrib +H) is excluded. - macOS - a file or any ancestor directory carrying the
UF_HIDDENfile flag (set withchflags hidden) is excluded.
A hidden attribute on an ancestor directory excludes every untracked path beneath it. Tracked files still bypass these checks. Linux has no equivalent filesystem attribute, so only the dot-prefix convention applies there; the GTK .hidden file convention is not implemented because it is desktop-specific (Nautilus/Nemo only) and name-based rather than a filesystem attribute.
You can register a repository through a directory symbolic link, including Termux paths under ~/storage. Git Auto Sync keeps the configured path in settings and status output, but resolves the repository root for filesystem-event boundary checks. Git Auto Sync treats events reported through either the configured link or its target as paths in the same repository. If a delete or rename event names a path that no longer exists, the checker resolves the nearest existing parent and restores the missing suffix before checking containment.
This normalization applies only to repository-boundary checks. Git Auto Sync does not rewrite config.json, traverse targets of symbolic links stored inside the worktree, or change Git's normal symbolic-link behavior. The checker does not guarantee that distinct bind-mount paths, Windows SUBST drives, or network-mapped paths identify the same repository.
Nested Git repositories found inside the worktree are detected and skipped, so they are never staged or committed as embedded gitlinks (mode 160000). This applies to any nested repository, including linked worktrees created under .claude/worktrees/. Changes inside a nested repository belong to that repository, not the one being synchronized.
Git Auto Sync uses go-git only for read-only repository inspection (discovery, HEAD and branch configuration, ignore matching, and author validation). Every mutating and network operation - status, add, commit, fetch, rebase, and push - shells out to the git executable, resolved through PATH or the gitexec setting. A working git is therefore required.
If your repository uses Git LFS, install the git-lfs extension yourself so Git's clean and smudge filters run. Git Auto Sync detects the case where git status reports an LFS pointer as modified but git add stages nothing (for example a pointer-only working tree under GIT_LFS_SKIP_SMUDGE) and skips cleanly instead of failing the commit, but it does not manage LFS objects.
The CLI and daemon use separate rotating log files. Multiple CLI processes, such as a running watch command and a manual sync, still write to the same git-auto-sync.log file. The rotation library does not coordinate across processes, so concurrent CLI processes may exceed the configured rotation size or lose log records during rotation.
Git Auto Sync stores configuration and logs in platform-specific directories.
Configuration - the global settings file holds repos, envs, syncInterval, debounce, and gitexec:
| Platform | Path |
|---|---|
| Linux | ~/.config/git-auto-sync/config.json |
| Android / Termux | $XDG_CONFIG_HOME/git-auto-sync/config.json if set; otherwise ~/.config/git-auto-sync/config.json |
| macOS | ~/Library/Application Support/git-auto-sync/config.json |
| Windows | %AppData%\git-auto-sync\config.json |
Per-repository settings are stored in the repository's own Git config under the [auto-sync] section, not in this file.
Logs - the CLI and daemon each write a rotating log file (10 MB per file, 3 backups retained):
| Platform | Directory |
|---|---|
| Linux | ~/.local/share/git-auto-sync/log/ |
| Android / Termux | ~/.local/share/git-auto-sync/log/ |
| macOS | ~/Library/Logs/ |
| Windows | %LOCALAPPDATA%\git-auto-sync\logs\ |
| File | Writer |
|---|---|
git-auto-sync.log |
git-auto-sync CLI |
git-auto-sync-daemon.log |
daemon service |
See Log rotation limitation for caveats about concurrent CLI processes sharing the same log file.
The daemon runs as a Windows service installed under the LocalSystem account. Subcommands that manage that service - daemon run, stop, restart, and uninstall - go through the Windows Service Control Manager and require an administrator terminal. In a non-elevated terminal they fail with Access is denied.
Open the terminal as administrator before using these daemon subcommands. As a convenience, Windows Terminal can launch a profile as administrator by default: open the profile dropdown, choose Settings, select the target profile, and turn on Run this profile as Administrator:
Git Auto Sync is based on GitJournal/git-auto-sync. The original project's commits up to and including 50cb029 are the baseline; everything since is this project's own work. Notable changes:
- Engine modernization - migrated from
src-d/go-git.v4togo-git/go-git/v5, modernized the Go toolchain and dependencies, and reorganized shared code into focusedinternal/packages. - Git CLI for mutations -
status,add,commit,fetch,rebase, andpushnow run through thegitexecutable instead of go-git, so content filters (including Git LFS) behave correctly. Nested repositories are detected and skipped instead of being staged as gitlinks. - Smarter sync - resolves the HEAD-versus-upstream state to skip redundant rebases and pushes (equal, local-ahead, upstream-ahead, or diverged).
- Repo-state guarding - pauses before any mutation when the repository has an operation in progress, a detached HEAD, or no upstream, instead of failing mid-sync.
- Watcher hardening - debounces file changes without delaying scheduled syncs, isolates per-repository failures with capped retry backoff for remote errors, and forwards Linux and Windows wake events so a resumed machine syncs immediately.
- Unified configuration - a
configCLI and config-file polling reload global and per-repository settings (syncInterval,debounce,gitexec) without restarting the daemon. - Improved ignore rules - tracked files are always eligible, untracked hidden paths are ignored with explicit exceptions (
.github/, Git control files,*.example), and ignore matching normalizes paths and caches the index per sync round. - Daemon and CLI UX -
daemon run,stop,restart, anduninstallcommands; structured rotating logs for CLI and daemon; full parent-environment inheritance with secret redaction; and Windows service fixes so LocalSystem shares the user's paths and Git config. - Monitoring list visualization - unlike the original project,
daemon lsanddaemon statusrender every monitored repository as an aligned table: a live runtime status (running,paused (<reason>), orunknown (daemon may not be running)) and a last-synced time (never synced, or a relative duration such assynced 3m ago), beneath a header reporting the daemon service state. Which repositories are healthy, paused, or stale is visible at a glance. - Android / Termux support - dedicated Android ARM64 releases avoid Linux syscall incompatibilities, runit manages the daemon through
termux-services, Android notifications usetermux-notification, and unavailable optional notification support degrades to a warning.
Beyond the improvements listed above, the following defects present in the original GitJournal/git-auto-sync baseline (commit 50cb029) have been fixed:
- The daemon service failed to start on Windows - the service executable path was registered without an
.exesuffix, which Windows does not append when launching services, so the service could not start. The path now carries the platform-correct suffix. (2f781ef) - The Windows daemon ran as LocalSystem and lost the user's paths and Git config - installed services run as
LocalSystem, whose blank profile left the daemon unable to write logs, resolve repositories, findgitonPATH, read the user's Git identity, or operate the user's worktrees (dubious-ownership). The installer now injects the user'sAPPDATA,LOCALAPPDATA,USERPROFILE, andPath, and passes-c safe.directory=<repo>per repository on Windows. (6c21dbf)
- Unchanged Git LFS files were re-committed on every sync, and
checkcrashed on empty or root paths - go-git's status and staging bypass Git's clean/smudge filters, so LFS-tracked files with unchanged pointers read as modified and were committed every cycle;ShouldIgnoreFilealso panicked on empty or repository-root paths. LFS pointers are now skipped and path validation rejects empty and root paths. (906d831) - Nested Git repositories were staged as embedded gitlinks, and linked worktrees were recursed into - any untracked, non-ignored path was staged through go-git, including nested repositories (committed as mode-
160000gitlinks) andgit worktreedirectories. The commit stage now usesgit status --porcelainandgit add, detecting and skipping nested repositories. (d3c6e0f)
- Git ignore rules for nested or absolute paths did not apply - full paths were handed to go-git's ignore matcher as a single component rather than split segments, so most nested ignore patterns silently failed. Paths are now normalized to worktree-relative segments before matching. (
d28c2a7) - Tracked files could be filtered out, and untracked hidden paths were not ignored despite the README claiming otherwise -
ShouldIgnoreFiledid not check whether a file was tracked, so real changes to tracked files could be skipped; untracked dot-prefixed paths were not filtered at all. Tracked files now bypass every ignore check, and untracked hidden paths are excluded with explicit exceptions. (0aa7a3e)
- Git subprocesses did not inherit the parent environment, and command errors leaked environment values - only
repoConfig.EnvplusHOMEreached Git, soSSH_AUTH_SOCK,PATH,XDG_CONFIG_HOME, andGIT_*never did; fullEnvslices were also embedded in command error messages, leaking secrets and agent sockets into logs. Git now inherits the full parent environment with per-repository overrides, and errors expose only variable keys, not values. (fb3b7ec)
- A single repository's sync failure killed the entire daemon -
AutoSyncfailures calledlog.Fatallnin the watcher goroutine, terminating the process and interrupting every other repository, even on transient network errors. Sync failures are now classified by pipeline stage, fetch/push errors retry with capped backoff, and only the affected repository pauses. (3f7c00b)
- The rebase-conflict warning icon failed to load in installed binaries - the icon was referenced by the relative path
assets/warning.png, which binaries installed outside the source checkout could not find. The icon is now embedded withgo:embedand passed to the notifier as PNG bytes. (699fd7b)
Apache-2.0
