Hello everyone 👋
Over time, Zettel Notes, a markdown note-taking app for Android, has steadily grown and is now used on 50K+ devices. One question that often comes up from users is:
How exactly does Git sync work inside the app?
In this post, I’ll explain the mechanism in simple terms — focusing on what actually happens behind the scenes when you tap Sync.
Quick Overview
Whenever a sync is triggered, the app runs a fixed sequence of steps:
- Check host connectivity
- Login and validate the local repository
- Commit local changes
- Pull changes from remote
- Push updates to remote
Each step must complete successfully before the next begins.
How Sync Works in the App
Every sync run follows this pipeline:
Connectivity Check → Login → Commit → Pull → Push → Notify Result
Each stage acts as a checkpoint. If something fails, the process stops and reports the issue instead of continuing.
Sync Session
The app starts by creating a sync session using GitSyncManager:
| Java | |
|---|---|
1 | |
It manages:
- Repository configuration
- Credentials
- Git operations
- Logs and error handling
Connectivity Check
| Java | |
|---|---|
1 | |
Before touching the repository, the app checks whether the Git host is reachable. This avoids unnecessary processing when the network is unavailable.
Login and Local Repository Validation
| Java | |
|---|---|
1 | |
During this phase the app:
- Reads remote URL and credentials from settings
- Opens the local Git repository
- Removes stale
.index.lockfiles (which can appear after crashes) - Ensures the selected branch exists
- Updates the
originremote if the URL has changed - Calls
ls-remoteto confirm remote access
Commit Phase
| Java | |
|---|---|
1 | |
Before pulling anything from the server, the app commits all local changes.
What happens here:
- If nothing changed → skip
- Stage modified files
- Stage deletions
- Add new (untracked) files
- Create a commit with the configured author and message
Pull Phase
| Java | |
|---|---|
1 | |
If the commit succeeds, the app fetches changes from the remote repository.
Default approach: Rebase
| Bash | |
|---|---|
1 | |
Rebasing keeps history clean and linear, which works well for syncing notes.
Conflict Handling
If rebase encounters conflicts:
- The app detects the conflicting files
- The rebase is aborted
- Sync falls back to a merge
Merge Fallback
During fallback, the merge strategy depends on the configured conflict preference:
- OURS (client wins) → keep local version
- THEIRS (server wins) → keep remote version
Push Phase — Sending Changes to Remote
| Java | |
|---|---|
1 | |
Push runs only if both commit and pull were successful.
Logging and Result Notification
After the pipeline finishes, the app writes a sync log:
| Java | |
|---|---|
1 2 | |