Repository navigation
Add sn delete duplicates - #120
Conversation
Standard Notes sets duplicate_of when the app duplicates a note, and 'sn stats' already lists those copies, but there was no way to remove them. 'sn delete duplicates' deletes them, after listing what it will remove and asking for confirmation. --dry-run lists them and stops; --yes skips the prompt. A duplicate whose original is no longer in the account is the only copy of that content left, so it is kept and reported rather than deleted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 54 |
| Duplication | 0 |
🟢 Coverage 14.29% diff coverage · +0.07% coverage variation
Metric Results Coverage variation ✅ +0.07% coverage variation (-1.00%) Diff coverage ✅ 14.29% diff coverage Coverage variation details
Coverable lines Covered lines Coverage Common ancestor commit (55e311e) 8825 899 10.19% Head commit (1d76372) 8978 (+153) 921 (+22) 10.26% (+0.07%) Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch:
<coverage of head commit> - <coverage of common ancestor commit>Diff coverage details
Coverable lines Covered lines Diff coverage Pull request (#120) 154 22 14.29% Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified:
<covered lines added or modified>/<coverable lines added or modified> * 100%
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
There are confirmed functional and UX issues in the new duplicates delete flow (DB handle reuse across the two-run command, misleading “no duplicates found” output, and potentially incorrect deleted counts) that should be corrected before merge.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 2
Open (3)
What changed in this PR
This PR adds a new sn delete duplicates subcommand to safely delete Standard Notes items marked with the duplicate_of marker, including a dry-run mode and confirmation prompt, and documents the new behavior.
Changes:
- Add duplicate detection and deletion logic (
FindDuplicateNotes,DeleteDuplicateNotesConfig.Run) with safeguards to keep duplicates whose originals are missing/deleted. - Wire a new
sn delete duplicatesCLI subcommand with--dry-runand--yes, and user-facing output of deletions/kept items. - Update README to document the new usage.
| File | Description |
|---|---|
| README.md | Documents sn delete duplicates usage and updates command list description. |
| internal/sncli/duplicates.go | Implements duplicate discovery + deletion workflow based on duplicate_of and original presence. |
| internal/sncli/duplicates_test.go | Adds unit tests for duplicate selection logic and safety rules. |
| cmd/sncli/duplicates.go | Adds CLI plumbing/output/prompting for sn delete duplicates. |
| cmd/sncli/delete.go | Registers the new delete duplicates subcommand and bash completion options. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| if len(found.Deleted) == 0 { | ||
| _, _ = fmt.Fprintln(c.App.Writer, "no duplicate notes found") | ||
|
|
||
| return nil | ||
| } |
| var notesToDelete items.Notes | ||
|
|
||
| for _, dup := range out.Deleted { | ||
| note := byUUID[dup.UUID] | ||
| if note == nil || note.GetContent() == nil { | ||
| continue | ||
| } | ||
|
|
||
| note.Content.SetText("") | ||
| note.SetDeleted(true) | ||
| notesToDelete = append(notesToDelete, *note) | ||
| } | ||
|
|
||
| if err = cache.SaveNotes(i.Session, so.DB, notesToDelete, true); err != nil { | ||
| return out, err | ||
| } |
| // KeptOriginalMissing holds duplicates left alone because the note they | ||
| // were copied from is no longer in the account. | ||
| KeptOriginalMissing []DuplicateNote |


Why
Standard Notes sets
duplicate_ofon a note when the app duplicates it, andsn statsalready lists those copies. There was no way to remove them.What it does
sn delete duplicatesdeletes notes carrying that marker. It lists what it will remove and asks for confirmation first.Only the marker counts as a duplicate. Notes that merely share a title or text are left alone, since the account doesn't say they're copies.
Safety
--yesis given. This followssn wipe.Tests
internal/sncli/duplicates_test.gocovers the selection logic: a marked copy with its original present, a missing original, a deleted original, an already-deleted duplicate, an account with no duplicates, and a copy of a copy.make lintreports 0 issues andmake testpasses.Worth knowing
The command syncs twice: once to show the list, and again to delete after you confirm. So it acts on the account as it is at the moment you confirm, rather than on a list that may have gone stale. If the two differ, the count reported at the end is what was actually deleted.
Not tested against a real account — no credentials were available, so only the selection logic is covered by tests. Worth a
--dry-runagainst a real account before merging.🤖 Generated with Claude Code