mirror of
https://github.com/firstsun-dev/git-files-sync.git
synced 2026-07-22 06:54:27 +00:00
fix(sync): fall back to adapter read for symlinked files
Reading a symlinked file via Obsidian's cached vault.read(TFile) can fail (notably on mobile), which broke both refresh and push: the status check swallowed the error and mislabeled the file as 'unsynced', so the user saw a Push button that then failed re-reading the symlink. Fall back to vault.adapter.read/readBinary(path) when vault.read throws, in both the status view and the sync manager. Also stop silently swallowing the status-check error so the real cause is logged. Add a regression test covering the adapter fallback on push. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DwioG4CNKUBuKiZdowLFWe
This commit is contained in:
parent
d8d6f9dcb1
commit
76405cf2f7
3 changed files with 47 additions and 8 deletions
|
|
@ -352,13 +352,22 @@ export class SyncManager {
|
|||
const binary = this.isBinary(path);
|
||||
|
||||
if (typeof fileOrPath === 'string') {
|
||||
return binary
|
||||
return binary
|
||||
? await this.app.vault.adapter.readBinary(fileOrPath)
|
||||
: await this.app.vault.adapter.read(fileOrPath);
|
||||
}
|
||||
return binary
|
||||
? await this.app.vault.readBinary(fileOrPath)
|
||||
: await this.app.vault.read(fileOrPath);
|
||||
try {
|
||||
return binary
|
||||
? await this.app.vault.readBinary(fileOrPath)
|
||||
: await this.app.vault.read(fileOrPath);
|
||||
} catch (e) {
|
||||
// Obsidian's cached vault.read can fail for symlinked files (notably
|
||||
// on mobile); fall back to reading the path directly via the adapter.
|
||||
logger.warn(`vault.read failed for ${path}; falling back to adapter`, e);
|
||||
return binary
|
||||
? await this.app.vault.adapter.readBinary(path)
|
||||
: await this.app.vault.adapter.read(path);
|
||||
}
|
||||
}
|
||||
|
||||
private async processSingleBatchPush(fileOrPath: TFile | string, path: string, name: string, isString: boolean): Promise<boolean> {
|
||||
|
|
|
|||
|
|
@ -449,8 +449,9 @@ export class SyncStatusView extends ItemView {
|
|||
const { status, diff } = this.determineFileStatus(binary, localContent, remote);
|
||||
|
||||
this.fileStatuses.set(path, { file, path, status, localContent, remoteContent: remote.content, remoteSha: remote.sha, diff });
|
||||
} catch {
|
||||
} catch (e) {
|
||||
const path = typeof fileOrPath === 'string' ? fileOrPath : fileOrPath.path;
|
||||
logger.warn(`Failed to determine sync status for ${path}`, e);
|
||||
this.fileStatuses.set(path, {
|
||||
file: typeof fileOrPath === 'string' ? undefined : fileOrPath,
|
||||
path,
|
||||
|
|
@ -466,9 +467,18 @@ export class SyncStatusView extends ItemView {
|
|||
: await this.app.vault.adapter.read(fileOrPath as string);
|
||||
}
|
||||
if (fileOrPath instanceof TFile) {
|
||||
return binary
|
||||
? await this.app.vault.readBinary(fileOrPath)
|
||||
: await this.app.vault.read(fileOrPath);
|
||||
try {
|
||||
return binary
|
||||
? await this.app.vault.readBinary(fileOrPath)
|
||||
: await this.app.vault.read(fileOrPath);
|
||||
} catch (e) {
|
||||
// Obsidian's cached vault.read can fail for symlinked files
|
||||
// (notably on mobile); fall back to reading the path directly.
|
||||
logger.warn(`vault.read failed for ${fileOrPath.path}; falling back to adapter`, e);
|
||||
return binary
|
||||
? await this.app.vault.adapter.readBinary(fileOrPath.path)
|
||||
: await this.app.vault.adapter.read(fileOrPath.path);
|
||||
}
|
||||
}
|
||||
// This should not happen if isStr is false and fileOrPath is TFile
|
||||
throw new Error('Expected TFile when isStr is false');
|
||||
|
|
|
|||
|
|
@ -88,6 +88,26 @@ describe('SyncManager', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('falls back to the adapter when vault.read fails (e.g. symlinked file)', async () => {
|
||||
const mockFile = Object.assign(new TFile(), { path: 'link.md', name: 'link.md' });
|
||||
const readSpy = vi.spyOn(mockApp.vault, 'read').mockRejectedValue(new Error('EINVAL: symlink'));
|
||||
const adapterReadSpy = vi.spyOn(mockApp.vault.adapter, 'read').mockResolvedValue('linked content');
|
||||
vi.spyOn(mockGitLab, 'getFile').mockResolvedValue({ content: 'different content', sha: 'old-sha' });
|
||||
const pushSpy = vi.spyOn(mockGitLab, 'pushFile').mockResolvedValue({ path: 'link.md', sha: 'new-sha' });
|
||||
|
||||
await manager.pushFile(mockFile);
|
||||
|
||||
expect(readSpy).toHaveBeenCalledWith(mockFile);
|
||||
expect(adapterReadSpy).toHaveBeenCalledWith('link.md');
|
||||
expect(pushSpy).toHaveBeenCalledWith(
|
||||
'link.md',
|
||||
'linked content',
|
||||
'main',
|
||||
'Update link.md from Obsidian',
|
||||
'old-sha'
|
||||
);
|
||||
});
|
||||
|
||||
it('should detect conflict when remote SHA differs from last synced SHA', async () => {
|
||||
const mockFile = Object.assign(new TFile(), { path: 'test.md', name: 'test.md' });
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue