README file from
GithubGitLab Gitless Sync
Sync an Obsidian vault with a GitLab repository without requiring Git on the device.
GitLab Gitless Sync keeps a vault and a single GitLab repository branch in sync using only the GitLab REST API. It is designed for mobile devices (where Git is unavailable) while staying fully interoperable with an ordinary desktop Git workflow against the same branch.
What you get
- Desktop and mobile support — sync the same vault from Obsidian on desktop, Android, and iPhone.
- No local Git required — the plugin talks to GitLab through its REST API, so it works on devices where Git cannot be installed.
- Automatic sync modes — sync on startup, after edits, on a timer, and on mobile app foreground/background events.
- Manual sync when you need it — run Sync with GitLab from the sidebar icon or command palette at any time.
- Data-safe sync — the default strategy tries to auto-merge text files first, and when files conflict, both versions are kept as separate files.
- Secure token storage — the GitLab token stays in Obsidian SecretStorage, while local sync metadata stays on the device and is never committed.
Installation
Requires Obsidian 1.11.4 or newer.
Community plugins
Once available in the Obsidian Community Plugins registry: open Settings → Community plugins → Browse, search for GitLab Gitless Sync, install and enable it.
Manual
Download main.js, manifest.json, and styles.css from the
latest release and copy them
into <vault>/.obsidian/plugins/gitlab-gitless-sync/, then enable the plugin.
Getting started
Four steps: create the repository and a token, connect the vault, turn on automatic sync, add a
.gitignore.
Step 1 — Create the GitLab repository and an access token
-
Create a project in GitLab — either on gitlab.com or on a self-managed instance (any HTTPS base URL works). A brand-new empty project is fine: the plugin can create the first commit from your vault. An existing project with content is fine too.
-
Create an access token for the plugin:
Project → Settings → Access tokens → Add new token
- Group and project access: Only specific groups or projects that I'm a member of, then select the vault project.
- Project: Read
- Repository: Commit Read, Commit Create, Branch Read, Code Read, Repository Read, Repository Tag Read
Fine-grained permissions do not cascade — grant each one explicitly. If the target branch is protected, add Protected Branch Read.
-
If your GitLab instance does not support fine-grained access tokens, use a project access token with role
Maintainerand scopeapi. The plugin uses GitLab's REST API to create commits;write_repositoryis only for Git-over-HTTP and is not enough for this plugin.
Keep the token private. If you stop using the plugin or move the vault to another repository, revoke the old token and create a new one.
Step 2 — Connect the vault
Back up the vault before connecting. The plugin never deletes a file and never overwrites one side silently, but connecting does bring remote content into the vault.
Fill in Settings → GitLab Gitless Sync → GitLab repository:
- GitLab base URL —
https://gitlab.com, or your self-managed HTTPS instance (e.g.https://gitlab.example.com). HTTPS is required. - Project path — namespace and project path, for example
developing/obsidian-world. - Branch — the branch to synchronize, for example
main. - GitLab token — the token from step 1. Stored securely in Obsidian SecretStorage on this device.
- Commit author name and Commit author email — used as the author of every commit the plugin creates. Required.
Then press Connect to GitLab. The plugin inspects both sides and shows a summary before changing anything; nothing happens until you confirm. One button handles every starting state:
- Repository has commits (the default case) → merge. Remote files are downloaded into the vault.
Files that exist only locally are kept and pushed on the next sync. When the same file exists on
both sides and differs, both versions are kept: the GitLab version takes the original path and
your version is saved beside it as
<name> (local conflict).<ext>. Nothing is deleted. - Repository is empty (no commits, no branches) → initial commit. Your whole vault is pushed as the first commit, and that commit creates the branch named in Branch, whatever you typed there — it does not have to match the default branch GitLab preconfigured for the project. Nothing is downloaded and nothing is deleted. This path needs the commit author name and email, and the vault must contain at least one syncable file (an empty vault plus an empty repository has nothing to push and reports an error).
Keep Obsidian open until the confirmation notice appears. After that, Sync with GitLab (sidebar icon or command palette) syncs on demand at any time.
Step 3 — Turn on automatic sync
All automatic modes live under Settings → GitLab Gitless Sync → Sync and are off-by-default except Sync on startup. Pick the set that matches the device.
On desktop — sync by time. The desktop app stays open for hours, so time-based triggers are what keep it current:
- Sync after edits — on. Runs a sync a few seconds after you stop typing.
- Edit debounce (seconds) —
8is a good starting point; raise it if you get commits mid-thought. - Sync on a timer — on. Pulls in what other devices pushed while you were idle.
- Timer interval (minutes) —
10for an actively shared vault,60if you mostly write alone. - Sync on startup — on. One sync when Obsidian launches.
On mobile — sync on app lifecycle. iOS and Android suspend the app instead of leaving it running, so foreground/background transitions are the most reliable mobile sync triggers:
- Sync on app foreground (mobile only) — on. Runs a sync when you reopen Obsidian from the background. This is the reliable mobile replacement for Sync on startup.
- Sync on app background (mobile only) — optional. Runs a sync when you leave the app. It gets your latest notes out sooner, but some devices suspend the app before the request finishes, so treat it as best-effort rather than a guarantee.
- Sync on startup — on. Useful when Obsidian is launched cold; foreground sync still handles the more common reopen-from-background case.
- Sync on a timer — off. A suspended app has no timer.
Every mode runs the same engine as the manual button, and each trigger has a cooldown so overlapping events cannot stack up syncs.
Step 4 — Add a .gitignore
The plugin excludes only its own runtime folder (.obsidian/plugins/gitlab-gitless-sync/), its log,
the .git directory, and sync-metadata files. Everything else — including the rest of
.obsidian/ — is synced unless you ignore it. So on a new vault, create a .gitignore in the
vault root.
Recommended — keep settings shared, drop device-specific state. Obsidian's workspace*.json and
caches change on every device on every session and cause constant churn and needless conflicts:
.obsidian/workspace.json
.obsidian/workspace-mobile.json
.obsidian/cache
.trash/
Also reasonable — keep Obsidian's config out of the repository entirely. Choose this when settings, themes, and plugin sets should stay per-device:
.obsidian/
Not recommended — no .gitignore at all. Everything syncs, including workspace.json, which
means the desktop and the phone fight over layout state on every sync and generate conflict copies
of a file you never edit by hand.
Root and nested .gitignore files are both honored, for normal syncs and for the first commit when
seeding an empty repository. Files that are already tracked stay tracked even if a rule starts
matching them later — ignore rules keep new files out, they do not remove history. .gitignore
itself is an ordinary file: it is committed and shared, so the rules apply on every device.
Features
Works without Git on the device. No local .git, no isomorphic-git, no shell Git — only HTTPS
calls to the GitLab REST API. That is what makes it work on iOS and Android.
Ordinary Git commits. The plugin writes real commits with your configured author, so desktop
collaborators keep using plain git pull/git push on the same branch and see nothing unusual.
Nothing is silently lost. Both sides of a conflict are always preserved; the losing version becomes a conflict copy instead of being overwritten. A delete that clashes with an edit produces a Markdown marker rather than removing the file. Binary files are copied byte-for-byte.
Fast, incremental syncs. A normal sync does not scan or hash the whole vault — it works from a local journal of what changed plus a remote diff. There is a separate Full audit and sync command for when you want an exhaustive local rescan.
Crash-safe. Sync runs as a transaction. If a remote commit succeeds but writing files locally is interrupted, the next sync detects the pending transaction and finishes it.
Local-only sync state. The tracked-file index, base commit, and journal live on the device and are never committed, so the repository stays clean and devices never fight over metadata.
Self-managed GitLab. Any HTTPS GitLab instance works, alongside gitlab.com.
Settings reference
Sync modes
| Setting | What it does |
|---|---|
| Sync on startup | One sync after Obsidian is ready. Desktop-oriented — on mobile the app is usually suspended before it runs. |
| Sync on app foreground (mobile only) | One sync when you reopen the app. The reliable mobile trigger. |
| Sync on app background (mobile only) | One sync when you leave the app. Best-effort: the OS may suspend the app first. |
| Sync after edits | One sync a few seconds after your last change, per Edit debounce. |
| Sync on a timer | One sync every Timer interval minutes, to pull other devices' changes. |
| Manual | Sync with GitLab — sidebar icon or command palette — runs the same engine on demand. |
Conflict strategy
A conflict is a file that changed on both the device and GitLab since the last sync. The plugin never overwrites either side; this setting only decides which version keeps the original path.
- Auto merge, fallback Remote (default) — try a line-based three-way merge of the text file first; if it conflicts or the file is binary, fall back to Remote.
- Auto merge, fallback Local — same merge attempt, falling back to Local.
- Remote — GitLab's version keeps the path; yours is saved as
<name> (local conflict).<ext>. - Local — your version keeps the path and gets committed; GitLab's is saved as a conflict copy.
A deletion that clashes with a change on the other side produces a Markdown conflict marker instead of removing the file.
Interface and diagnostics
- Show sidebar icon — a one-tap sync button in the left ribbon.
- Log level —
Offby default. UseDebugonly while diagnosing sync behavior. - Copy logs / Clean logs — export or clear the plugin log.
- Show GitLab sync status (command) — base commit, pending local changes, last sync result.
- Reset local sync state — forgets the local base commit, tracked index, and journal. It does not delete files and does not change GitLab; the next Connect re-reads the repository as the source of truth.
Limitations
- GitLab only, one project and one branch, REST API only.
- No local Git: no merge, rebase, force push, branch switching, Git LFS, submodules, symlinks, executable-bit changes, or multiple remotes.
- Seeding an empty repository pushes the whole vault in one commit (no chunking), so a very large vault is bounded by GitLab's request size limit.
- Automatic mobile sync depends on OS lifecycle events; background sync can be cut short by the system.
- Plugin runtime files and sync state are local-only and never committed.
Security
The GitLab token is stored in Obsidian SecretStorage. It is never written to plugin data, logs,
conflict files, URLs, or GitLab commits. Logs redact credential-shaped fields such as
PRIVATE-TOKEN, Authorization, token, and password. All traffic is HTTPS — a non-HTTPS base
URL is rejected.
Privacy
The plugin communicates only with the GitLab instance configured by the user. It does not collect telemetry or analytics and does not send vault data to any other service. A GitLab account/project and access token are required.
Development
- Install dependencies:
pnpm install. pnpm buildtype-checks (tsc -noEmit -skipLibCheck) and bundles the production plugin with esbuild tomain.js.pnpm testruns the Vitest suite.pnpm checkruns tests, type-check, lint, and build.main.jsanddist/are git-ignored build artifacts and are never committed.
Credits & License
This project is an independently maintained GitLab rewrite, based on GitHub Gitless Sync by Silvano Cerza. Modified in 2026 by Alexander Terekhin.
Licensed under AGPL-3.0-only. The upstream license and attribution are preserved — see
LICENSE and THIRD_PARTY_NOTICES.md.