firstsun-dev_git-files-sync/docs/symlink-handling.md
Claude 9bcaed65f4
feat(sync): real symbolic link support (GitHub) with configurable handling
Adds end-to-end symlink syncing driven by the "Symbolic links" setting
(real / follow / skip; default real), building on the earlier detection.

- Pull: on desktop with "real", a remote symlink is recreated as a real OS
  link via Node fs (utils/symlink.ts, guarded by Platform/FileSystemAdapter);
  otherwise the target path is written as content.
- Push: GitHubService.pushSymlink commits a real symlink blob (mode 120000)
  through the Git Data API (blob -> tree -> commit -> ref). getFile now
  reports isSymlink/symlinkTarget.
- Config 防呆: only GitHub offers "real"; on GitLab/Gitea (no API to create
  symlinks) "real" resolves to "skip" via getEffectiveSymlinkHandling.
- Safety: a "follow" push never overwrites a detected remote symlink with a
  regular file; it is skipped with a notice.
- Docs: docs/symlink-handling.md plus a README settings note.

Lint is satisfied without disables (Electron global require, minimal Node
type shims). Adds tests for getFile detection, the pushSymlink Git Data
sequence, and the remote-symlink push guard.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DwioG4CNKUBuKiZdowLFWe
2026-06-28 05:14:35 +00:00

4 KiB

Symbolic Link Handling

This document describes how Git File Sync syncs symbolic links (symlinks) between your vault and the remote repository.

Background

In Git, a symbolic link is not a normal file — it is stored as a blob with file mode 120000 whose content is the link's target path (for example ../shared/note.md). Because of this, symlinks need special treatment:

  • They cannot be fetched/created like normal files through every provider's API.
  • Obsidian exposes no symlink API, and mobile platforms (iOS/Android) cannot create OS-level symlinks at all.

To keep behavior predictable, Git File Sync makes symlink handling an explicit, configurable choice.

The setting

Settings → Git File Sync → Symbolic links

Mode What it does
Real symlink (default) Recreates a real OS symlink on desktop when pulling, and pushes local symlinks back as real Git symlinks (mode 120000). Falls back to content on platforms/providers that can't do this (see below).
Follow Treats a symlink as the file it points to: syncs the target's content as a normal file. Never creates links.
Skip Ignores symlinks entirely — they are not listed, pulled, or pushed.

The default is Real symlink.

Provider support

Creating a symlink on the remote requires writing a 120000 blob, which is only possible with a provider that exposes the full Git Data API.

Provider Real symlink Notes
GitHub Full support via the Git Data API (blob → tree → commit → ref).
GitLab The Commits API can't set the symlink mode. "Real" is treated as Skip.
Gitea No write access to git data endpoints. "Real" is treated as Skip.

Config safeguard (防呆): the "Real symlink" option is only offered in the settings dropdown when the active provider is GitHub. On other providers a saved value of real is automatically treated as skip, so a symlink is never silently turned into an ordinary file.

Platform behavior

Desktop Mobile (iOS/Android)
Real symlink (GitHub) Creates/reads real OS symlinks via Node's fs. No symlink API — falls back: on pull, writes the target path as the file content.

What happens per direction

Pull (remote → vault)

  • Real + GitHub + desktop: a remote symlink is recreated as a real OS link pointing at its target.
  • Real on mobile (or if the link can't be created): the link target path is written into the file as its content, so it still round-trips.
  • Follow: the target's content is written as a normal file. (For a link whose target is a normal in-repo file, GitHub's API already returns that content.)
  • Skip: remote symlinks are excluded from the sync list.

Push (vault → remote)

  • Real + GitHub: a local symlink is committed as a real symlink blob (mode 120000) using the Git Data API.
  • Skip: local symlinks are not pushed.
  • Follow: the content the link points to is pushed as a normal file.

A follow push reads through a local symlink and would otherwise overwrite the remote with a regular file. To avoid silently converting a remote symlink into an ordinary file, if the remote path is detected as a symlink, the push is skipped with a notice. Use Real symlink (GitHub) if you actually want to manage the link.

Note: remote-symlink detection on push relies on the provider reporting the symlink type, which currently only GitHub does. On GitLab/Gitea a follow push cannot detect a remote symlink and may convert it to a regular file.

Error handling

Earlier versions surfaced confusing errors when a symlinked .gitignore (or other symlink) returned 404 during a refresh. Expected 404s are now logged at debug level instead of as errors, and symlinks are detected up front, so a normal refresh/pull no longer shows spurious "Git Service Request Failed (404)" messages.