# Git sync

Keep a vault in sync with a git remote: set it up, read the sync states, handle conflicts and encrypt the remote.

Git sync keeps a vault in sync with a git remote, usually a private GitHub repository. Use this page to set it up, read its states, solve a conflict and turn on encryption. The vault stays a plain git repository, so Obsidian Git, a terminal or another Mac can use the same remote.

## Set up sync

Loam uses the git installed on your Mac and its credentials: ssh keys, the osxkeychain helper or `gh auth`. With `sync.setup`, Loam stores no password or token, and it never prompts. Missing credentials show as **Sync needs attention**. The only secret Loam keeps for sync is the key of an encrypted vault, in the Keychain. The **Connect GitHub** button in the app keeps its GitHub sign-in in the Keychain too, under “Loam GitHub”.

1. Create an empty private repository on your git host, or use an existing one.
2. Set the vault up with the remote address. The setup also syncs once.
3. Check that `state` is `synced` in the status.

```bash
loam --vault ~/MyVault sync.setup url=https://github.com/<you>/<vault>.git
loam --vault ~/MyVault sync.status
```

`sync.setup` adds to what is there and never replaces it:

- Runs `git init` if the vault is not a repository. Use `gitDir=<path>` to keep git’s data outside the vault.
- Adds `.loam/cache/`, `.DS_Store` and `.trash/` to `.gitignore`.
- Sets a local git identity, only if none exists.
- Adds the remote. A remote that already has another address stops the setup.
- Turns automatic sync on, and syncs once.

> **Warning:** In iCloud Drive, keep git’s data outside the vault. iCloud can damage a `.git` folder it syncs. Use `gitDir=~/.vault-git/<name>.git`.

> **Note:** An Obsidian Git vault that already has its remote needs only `sync.setup`, run without `url`. Turn Obsidian Git’s automatic backup off on the same Mac first.

## How a sync works

A sync runs one cycle at a time, off the main thread. A cycle does five things:

1. Fetch the branch from the remote.
2. Save the unsaved typing of the notes the remote changed, so it is part of the merge.
3. Commit everything changed on this Mac, with a message such as `vault backup: 2026-10-20 14:05:03 (<device>)`.
4. Merge the remote branch. Loam merges and never rebases, so history is never rewritten.
5. Push.

While automatic sync is on, a cycle runs:

- 30 seconds after the last change (`commitDelaySeconds`), and at least every 5 minutes while changes keep coming.
- When the app comes to the front, if the remote was not checked in the last minute.
- Every 5 minutes while the app is in front (`pullIntervalMinutes`).
- On `sync.now`, or a click on the status bar item.

When the app is in the back, nothing runs, except a commit already pending. Offline, commits keep happening on this Mac, and Loam retries after 1, 2, 5, 10, then 30 minutes.

## Sync states

The status bar and `sync.status` show one of these states:

- **Synced**: everything is committed, and the remote has the same commits. With conflict copies, it says **1 conflict** or **N conflicts**.
- **Syncing…**: a cycle is running.
- **Offline**: the remote cannot be reached. Changes wait on this Mac.
- **Sync needs attention**: something needs you. Nothing more is written until it is fixed, and the message says what.
- **Sync off**: automatic sync is off, or the vault is not a git repository.

## Conflicts and safety

Git merges edits to different lines of a note by itself. A conflict happens when both sides changed the same lines, or the same file in another way. Loam then keeps both versions:

- Both sides changed the note, or both created it: the note keeps this Mac’s version. The other one is saved next to it as `Note (conflict <device> 2026-10-20).md`.
- One side deleted the file and the other changed it: the changed file is kept.
- A binary file, such as an image: the same rules, on whole files.
- A note open with unsaved typing that differs from the merge: the editor keeps its text, and the merged version is saved as a conflict copy.

Conflict copies are committed, so every device sees them. Open the copy from the status bar, merge it by hand, then delete it. A file never gets conflict markers.

Loam never runs a destructive git command:

- No `reset`, `clean`, `stash`, `rebase` or forced push.
- A merge, rebase or cherry-pick that another tool started is left alone. `sync.resolve` finishes a merge, with conflict copies, only when you run it.
- Deleting 50 files or more at once, when they are also a quarter of the vault or more, pauses sync until you run `sync.now confirm=deletions`.
- Files over 50 MB are left out of commits, and listed.
- `.loam/cache/` never syncs.
- If Loam cannot fetch for a day while offline, the state becomes **Sync needs attention**. A sync cannot stop silently.

## Encryption

With encryption, the remote only holds ciphertext. The vault on each Mac stays plain Markdown. Turn it on at setup:

```bash
loam --vault ~/MyVault sync.setup encrypt=true
```

- Encrypted: the content of every file, including notes, attachments and `.loam/config.json`.
- Not encrypted: file and folder names, sizes, commit dates and messages, `.gitattributes` and `.gitignore`.

The key is 32 random bytes, made by `sync.setup encrypt=true`. It stays in the Keychain of each Mac, under “Loam vault key”, and never in the vault. Loam encrypts through a git filter with AES-256-GCM-SIV. Obsidian Git, a phone or an agent that reads the repository without Loam sees only ciphertext. A terminal on a Mac with Loam reads it, for example with `git log -p`.

To add another Mac:

1. On a Mac that already syncs, export the key to a file with `sync.key.export file=~/Desktop/vault.key`.
2. Move the file to the new Mac, for example with AirDrop. Import it there with `sync.key.import file=~/Desktop/vault.key`, then delete the file.
3. Set the vault up as usual with `sync.setup url=<remote>`.

> **Warning:** Lose every copy of the key and the remote can never be read again. Keep a copy somewhere safe. The key is a secret: anyone with it and access to the remote can read the vault.

> **Note:** Turning encryption on encrypts the commits from then on. Older commits stay readable. Turning encryption off is not supported yet. `sync.key.rotate` changes the key, but history stays encrypted with the old key, which still opens it.

## Commands and settings

Settings live in `.loam/config.json` and travel with the vault: `auto`, `remote` (`origin`), `branch` (empty means the branch checked out), `commitDelaySeconds` (30) and `pullIntervalMinutes` (5). The device name belongs to each clone. `sync.setup device=<name>` sets it.

| Command | What it does |
| --- | --- |
| `sync.setup` | Sets the vault up and syncs once. `encrypt=true` encrypts the remote. |
| `sync.status` | Shows where sync stands, as JSON, without changing anything. |
| `sync.now` | Syncs now. `confirm=deletions` allows a mass deletion. `confirm=plain` pushes commits made before encryption. |
| `sync.enable` and `sync.disable` | Turns automatic sync on or off. |
| `sync.resolve` | Finishes a merge left by another tool, with conflict copies. |
| `sync.key.export` | Prints the key of an encrypted vault. `file=<path>` writes it to a file. |
| `sync.key.import` | Keeps the key on this Mac, from `key=loam-key:…` or `file=<path>`, then syncs. |
| `sync.key.rotate` | Encrypts every file again with a new key, in one commit, and pushes it. A Mac or agent without the new key stops syncing until you import it there. |
