Skip to content

fix: skip re-export when content unchanged, avoid redundant backups - #74

Open
cgongac wants to merge 2 commits into
bingryan:masterfrom
cgongac:fix/dedup-backup
Open

fix: skip re-export when content unchanged, avoid redundant backups#74
cgongac wants to merge 2 commits into
bingryan:masterfrom
cgongac:fix/dedup-backup

Conversation

@cgongac

@cgongac cgongac commented Aug 12, 2026

Copy link
Copy Markdown

Summary

Currently every full export (cmd + pibook export) re-creates every book file. When the backupWhenExist setting is enabled, each run renames the existing file to a -bk-{timestamp}.md copy — even when the content is identical. After a major update or repeated exports, this causes backup files to pile up indefinitely.

There is also a related bug: when backupWhenExist is disabled, an existing file is never updated at all — vault.create() throws file already exists, which is silently swallowed by the catch, so the old file stays forever.

Changes (src/export.tssave())

  1. If the target file exists, read its content first:

    • Content unchanged → skip entirely (no backup, no rewrite).
    • Content changed + backupWhenExist on → rename old file to -bk-{timestamp}.md, then write the new one (backup only happens on real changes).
    • Content changed + backupWhenExist off → remove old file, then write the new one, so overwriting actually works.
  2. await the rename / create calls so operations are properly sequenced.

Effect

  • No more -bk-*.md pile-up on repeated full exports.
  • Disabling backups now behaves as "overwrite" instead of "never update".

Test

  • npm run build (tsc + esbuild) passes.
  • npm run lint passes (0 errors; pre-existing warnings in src/typings only).

Summary by CodeRabbit

  • Bug Fixes
    • Prevented unnecessary file updates when exported content is unchanged.
    • Improved replacement handling by preserving timestamped backups or removing outdated files before saving.
    • Ensured file creation completes reliably before the operation finishes.

Currently every full export (cmd+p "ibook export") re-creates every book
file. With backupWhenExist enabled, each run renames the existing file to
a -bk-{timestamp}.md copy even when content is identical, causing backup
files to pile up after major updates or repeated exports.

Change save() to:
- read existing file content and skip entirely when unchanged
- only back up (or remove) the old file when content actually differs
- still fall back to the old file path when backup is disabled, so
  overwriting actually works instead of being swallowed by the
  "file already exists" catch
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@cgongac, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 51 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0d1722b1-2059-43f2-8438-324e9d41c45a

📥 Commits

Reviewing files that changed from the base of the PR and between 91fd998 and d7c4e42.

📒 Files selected for processing (2)
  • src/export.ts
  • src/ui/search.ts
📝 Walkthrough

Walkthrough

save now skips unchanged files and safely replaces changed files by creating a timestamped backup or removing the old file before writing the new content.

Changes

File replacement

Layer / File(s) Summary
Save file content safely
src/export.ts
save compares existing content before writing. It skips unchanged files, backs up or removes changed files, and awaits backup and creation operations.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes skipping unchanged exports and avoiding unnecessary backups.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/export.ts`:
- Around line 104-119: Update generate() to await this.save(...) and ensure
callers such as the search flow await generate(). Serialize concurrent save
operations per filePath so each read, rename/remove, and create sequence
completes before another save for the same path begins.
- Around line 110-117: Update save() to preserve the existing export until
replacement succeeds: use Vault.modify() for existing files or create the
replacement at a temporary path before renaming it into place, including backup
behavior. Update generate() to await save(), and serialize concurrent exports so
they cannot overwrite or remove each other’s files.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 75885ae5-69eb-47a8-bbec-f15f406daad1

📥 Commits

Reviewing files that changed from the base of the PR and between 9eb94c0 and 91fd998.

📒 Files selected for processing (1)
  • src/export.ts

Comment thread src/export.ts Outdated
Comment on lines 104 to 119
if (isExist) {
// skip if content is identical, avoid redundant backups (issue: #44, #69)
const oldContent = await this.plugin.app.vault.adapter.read(filePath);
if (oldContent === content) {
return;
}
if (this.plugin.settings.backupWhenExist) {
// backup file if file already exists
// issue: #44
const backupPath = normalizePath(path.join(this.plugin.settings.output, `${fileName}-bk-${Date.now()}.md`));
await this.plugin.app.vault.adapter.rename(filePath, backupPath);
} else {
// remove old file so create() below can overwrite it
await this.plugin.app.vault.adapter.remove(filePath);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Await save() before allowing another export to start.

This code now performs a multi-step read, rename/remove, and create sequence. However, generate() calls this.save(...) without await at Line 71, and the supplied caller in src/ui/search.ts:51-61 also launches generate() without awaiting it. Two exports for the same filePath can both pass exists() and then race, causing failed replacements or lost updates. Await save() from generate() and serialize concurrent saves for the same path.

Suggested sequencing fix
-		this.save(renderData.library.ZTITLE, content);
+		await this.save(renderData.library.ZTITLE, content);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/export.ts` around lines 104 - 119, Update generate() to await
this.save(...) and ensure callers such as the search flow await generate().
Serialize concurrent save operations per filePath so each read, rename/remove,
and create sequence completes before another save for the same path begins.

Comment thread src/export.ts Outdated
Address CodeRabbit review:
- await save() from generate() and the search flow so consecutive
  exports cannot race on the same filePath
- keep the existing export intact while replacing it: when backup is
  enabled, rename to -bk-{timestamp}.md then create the new file; when
  backup is disabled, overwrite in place via Vault.modify() instead of
  remove-then-create so a failed write never leaves the file missing
@cgongac

cgongac commented Aug 12, 2026

Copy link
Copy Markdown
Author

Thanks for the review! Both issues are addressed in the follow-up commit fix: await save and preserve existing file during replace:

  1. Await save()generate() now awaits this.save(...), and the search flow (IBookSearchModal.onChooseItem) also awaits generate(). The "export all" loop in all() was already sequential (await this.generate(...) per book), so each read/replace sequence completes before the next begins.

  2. Preserve the existing file during replacementsave() no longer removes or renames the live path before the new content is in place:

    • Backup enabled: rename the existing TFile to -bk-{timestamp}.md, then Vault.create() the new file. If creation fails, the backup copy still holds the old export.
    • Backup disabled: overwrite in place via Vault.modify() on the existing TFile — the file is never left missing, unlike the previous remove-then-create flow.

Verified locally with npm run build (tsc + esbuild) and npm run lint (0 errors).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant