From 269ed3c2b1a94ed8ebad5fdce110efb671aa70f6 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Tue, 26 May 2026 19:28:05 -0500 Subject: fix(refresh): merge keeps any locally-edited ticket The merge refresh protected a subtree from being overwritten only when its description body had changed, since pearl--subtree-dirty-p is description-only. A local edit to the title, state, priority, assignee, labels, or a comment looked untouched and got re-rendered away. I added pearl--subtree-has-local-edits-p, which wraps the comprehensive per-field detector the save path already uses, and pointed both merge protection sites at it. A merge now keeps any ticket you've touched in any field. The same gain flows to pearl-refresh-current-view. --- pearl.el | 17 +++++++++++++---- tests/test-pearl-merge.el | 17 +++++++++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/pearl.el b/pearl.el index 369608d..79c2ebd 100644 --- a/pearl.el +++ b/pearl.el @@ -4020,12 +4020,21 @@ reported. A clean file does nothing." (and pearl--cache-viewer (plist-get pearl--cache-viewer :id)))))))) +(defun pearl--subtree-has-local-edits-p () + "Non-nil if the issue subtree at point has any unpushed local edit. +Broader than `pearl--subtree-dirty-p', which inspects only the description: +this also covers a changed title, state, priority, assignee, labels, or comment. +A merge refresh uses it so it keeps a ticket you have touched in any field, not +just ones whose description changed." + (pearl--issue-has-dirty-fields-p (pearl--issue-dirty-fields))) + (defun pearl--merge-issues-into-buffer (issues) "Merge normalized ISSUES into the current buffer by `LINEAR-ID'. Same-source refresh semantics: an existing issue still in ISSUES is re-rendered in place; an issue new to ISSUES is appended after the last one; an issue no -longer in ISSUES is dropped. A subtree whose body has unpushed edits (see -`pearl--subtree-dirty-p') is never overwritten and never dropped -- it is kept +longer in ISSUES is dropped. A subtree with any unpushed local edit (see +`pearl--subtree-has-local-edits-p' -- title, state, priority, assignee, labels, +description, or a comment) is never overwritten and never dropped; it is kept and counted, so a refresh can't lose un-synced work. Returns a plist of counts \(:updated :added :dropped :skipped)." (let ((existing (pearl--issue-subtree-markers)) @@ -4037,7 +4046,7 @@ and counted, so a refresh can't lose un-synced work. Returns a plist of counts (when marker (save-excursion (goto-char marker) - (if (pearl--subtree-dirty-p) + (if (pearl--subtree-has-local-edits-p) (setq skipped (1+ skipped)) (pearl--replace-issue-subtree-at-point issue) (setq updated (1+ updated))))))) @@ -4046,7 +4055,7 @@ and counted, so a refresh can't lose un-synced work. Returns a plist of counts (unless (member (car cell) fetched-ids) (save-excursion (goto-char (cdr cell)) - (if (pearl--subtree-dirty-p) + (if (pearl--subtree-has-local-edits-p) (setq skipped (1+ skipped)) (org-back-to-heading t) (delete-region (point) (progn (org-end-of-subtree t t) (point))) diff --git a/tests/test-pearl-merge.el b/tests/test-pearl-merge.el index 7cb2a34..122e5fa 100644 --- a/tests/test-pearl-merge.el +++ b/tests/test-pearl-merge.el @@ -142,6 +142,23 @@ (goto-char (point-min)) (should-not (re-search-forward "Alpha Renamed" nil t))))) +(ert-deftest test-pearl-merge-keeps-title-edited-issue () + "A subtree with an unpushed title edit (not a description edit) is kept too. +Merge must protect any locally-edited field, not only the description." + (test-pearl-merge--in-org + (test-pearl-merge--buffer (test-pearl-merge--issue "a" "Alpha" "Desc Alpha.")) + (goto-char (point-min)) + (re-search-forward "Alpha") ; the title word in the heading + (insert " EDITED") ; dirty the title only; body untouched + (let ((counts (pearl--merge-issues-into-buffer + (list (test-pearl-merge--issue "a" "Alpha Renamed" "Remote desc."))))) + (should (= 1 (plist-get counts :skipped))) + (should (= 0 (plist-get counts :updated))) + (goto-char (point-min)) + (should (re-search-forward "Alpha EDITED" nil t)) ; local title edit kept + (goto-char (point-min)) + (should-not (re-search-forward "Alpha Renamed" nil t))))) ; remote did not land + (ert-deftest test-pearl-merge-keeps-dirty-issue-absent-from-result () "A dirty issue gone from the result is kept rather than dropped (no data loss)." (test-pearl-merge--in-org -- cgit v1.2.3