Refresh previously fetched each file's remote content via getFile() to
compare against local content — an O(n) network round-trip per file
even after the existing concurrency pool. Implements the two-phase
hybrid design from the issue discussion:
Phase 1 — bulk classify, no content:
- Extend GitTreeEntry with sha, populated from each service's tree
listing (GitHub/Gitea: item.sha, GitLab: item.id — GitLab's tree API
calls the blob SHA "id", not "sha").
- Add gitBlobSha(): sha1("blob " + byteLength + "\0" + contentBytes)
via crypto.subtle, matching `git hash-object` exactly (verified
against real git-produced SHAs in tests, including a multi-byte
UTF-8 case to check byte length vs char length).
- SyncStatusView.refreshFileStatus() now compares a locally-computed
blob SHA against the tree entry's SHA — no getFile call. Falls back
to the previous content-based comparison per-entry when a tree entry
has no SHA or isn't found in the tree at all (new local file).
- Symlink-aware hashing per the issue's follow-up note: a symlink's
blob content is its target path string, not the content it points
at. "real" mode hashes the raw local link target (readLocalSymlinkTarget)
instead of following it; "follow" mode always hashes the followed
content; "skip" entries are already excluded from the tree map.
Phase 2 — fetch content only when needed:
- Add getBlob(sha, path) to GitServiceInterface: GitHub/Gitea use
`git/blobs/{sha}` (base64 JSON, shared via a new
fetchGitHubStyleBlob() base helper); GitLab uses
`repository/blobs/{sha}/raw` (raw bytes, not JSON-wrapped).
- The diff panel no longer requires content prefetched during refresh.
FileListItem's Diff button always renders for a modified file and
fetches remote content on demand via a new onExpandDiff callback,
showing a loading placeholder while the request is in flight and
caching the result on the FileStatus so re-expanding doesn't refetch.
- A modified symlink shows "Symlink target changed" instead of running
a text diff.
Known limitation (pre-existing, not introduced here): comparing raw
file bytes has no CRLF/line-ending normalization, so a repo checked
out elsewhere with core.autocrlf could show a false "modified" status.
The previous content-based comparison (contentsEqual) had the same
exact-byte-equality behavior, so this isn't a regression.
Closes #36
|
||
|---|---|---|
| .agents/skills | ||
| .claude | ||
| .github/workflows | ||
| .husky | ||
| archive | ||
| docs | ||
| imgs | ||
| scripts | ||
| src | ||
| tests | ||
| .editorconfig | ||
| .gitignore | ||
| .npmrc | ||
| .releaserc.json | ||
| AGENTS.md | ||
| CHANGELOG.md | ||
| CLAUDE.md | ||
| esbuild.config.mjs | ||
| eslint.config.mts | ||
| feature_list.json | ||
| fix_order.mjs | ||
| init.sh | ||
| LICENSE | ||
| manifest.json | ||
| package-lock.json | ||
| package.json | ||
| pr_body.md | ||
| progress.md | ||
| README.md | ||
| ruleset_update.json | ||
| session-handoff.md | ||
| skills-lock.json | ||
| sonar-project.properties | ||
| styles.css | ||
| targeted_append.mjs | ||
| tsconfig.json | ||
| USAGE_zh.md | ||
| version-bump.mjs | ||
| versions.json | ||
| vitest.config.ts | ||
Git File Sync
Selective, file-by-file sync between your vault and GitHub, GitLab, or Gitea.
Push, pull, diff, and resolve conflicts — file by file, not whole-vault. Unlike full-vault sync solutions, Git File Sync gives you granular control over exactly what leaves your device, so you can keep personal notes private while sharing project files through a real Git repository.
The Sync Status View gives a bird's-eye view of your vault, letting you selectively push, pull, or diff modified files.
What's inside
- File-by-file control — Sync individual notes or whole folders, not your entire vault. No lock-in to a single sync provider for everything.
- Three Git providers — GitHub, GitLab (including self-hosted), and Gitea, all behind one consistent UI.
- Visual diffing — A built-in diff viewer compares local and remote versions side-by-side before anything is overwritten.
- Conflict resolution — When local and remote both changed, resolve manually with a dedicated conflict tool instead of guessing which version wins.
- Works on mobile — Full support for Obsidian Mobile with a touch-friendly sync dashboard.
Sync Status View
A single dashboard shows the state of every tracked file:
- Status filtering — instantly see what's modified, new, or missing.
- Visual diffs — line-by-line comparison of local vs. remote before syncing.
- Remote-only detection — spot files that exist on GitHub/GitLab/Gitea but haven't been pulled into the vault yet.
The built-in diff viewer compares local and remote changes before you push or pull.
Providers
| Provider | Hosting | Min. version |
|---|---|---|
| github.com · GitHub Enterprise | — | |
| gitlab.com · self-hosted | GitLab 13.0+ | |
| self-hosted | Gitea 1.12+ |
Gitea note: the plugin talks to the Gitea API v1 (
/api/v1) and resolves branch names to commit SHAs before fetching the file tree, which is what makes 1.12+ the floor.
Configuration
Pick a provider and supply its credentials in Settings > Git File Sync.
| Provider | Required info | Token scope | |
|---|---|---|---|
| GitHub | Personal access token, owner, repo name | repo |
|
| GitLab | Personal access token, project ID, base URL | api |
|
| Gitea | Personal access token, base URL, owner, repo name | (all) |
- GitHub token: Settings → Developer settings → Personal access tokens →
reposcope. - GitLab token: User settings → Access tokens →
apiscope. Base URL defaults tohttps://gitlab.com; change it for self-hosted instances. - Gitea token: User settings → Applications → Access tokens. Point the base URL at your instance (e.g.
https://gitea.example.com).
Other settings: branch to sync against (default main), root path prefix inside the repo, vault folder to scope which notes are tracked, and symbolic link handling (real — recreate the link, GitHub only; follow — sync the target's content; skip). See Symbolic link handling for details.
Daily workflow
Pushing:
- One note — the cloud icon in the ribbon, or the command
Push current file to GitLab/GitHub/Gitea. - Several notes — open the Sync Status View, filter to Modified, select files, click Push selected.
- From the file tree — right-click any file and choose
Push to GitLab/GitHub/Gitea.
Pulling:
- Open the Sync Status View and click Refresh status.
- Files with remote updates show as Modified or Remote only.
- Select them and click Pull selected. Pulling overwrites local changes — if both sides changed, the conflict tool opens instead.
Resolving a conflict:
- The Conflict Resolution window opens automatically.
- Left pane is your Local version, right pane is Remote.
- Choose Keep Local (overwrite remote on next push) or Keep Remote (accept remote, overwrite local).
On mobile: swipe from the left to open the ribbon and the Sync Status View, pull before you start editing, push when you're done.
Installation
From Community Plugins (recommended)
- Open Settings > Community plugins and turn off restricted mode.
- Click Browse, search for Git File Sync, click Install, then Enable.
Manual
- Download
main.js,manifest.json, andstyles.cssfrom the latest release. - Create
<vault>/.obsidian/plugins/git-file-sync/. - Copy the three files into that folder.
- Reload Obsidian and enable the plugin under Settings > Community plugins.
Quick start
- Configure a provider in Settings > Git File Sync (see Configuration).
- Open the Sync Status View — the list icon in the ribbon, or run
Open sync status viewfrom the Command Palette. - Click Refresh status to compare your vault against the remote repo.
- Select files and Push selected or Pull selected as needed.
Commands:
| Command | What it does |
|---|---|
| Open sync status view | Open the sync dashboard |
| Push current file to GitLab/GitHub/Gitea | Push the active note |
| Pull current file to GitLab/GitHub/Gitea | Pull the active note |
| Push all files | Push every tracked, changed file |
| Pull all files | Pull every tracked, changed file |
Privacy and security
- Local storage — personal access tokens are stored locally in the plugin's data folder inside your vault, and are only ever sent to the Git provider you configured.
- No telemetry — the plugin collects no usage data or analytics.
Requirements
- Obsidian 1.13.0 or later
- Desktop and mobile supported
Development
git clone https://github.com/firstsun-dev/git-files-sync.git
npm install
npm run dev # watch build
npm run build # type-check + production build
npm run test # vitest suite
npm run lint # eslint
License
MIT
Created by ClaudiaFang