From dcaf2061e08feb6ca3d957950d91c292cef99b68 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Sun, 24 May 2026 14:33:32 -0500 Subject: refactor: share one conflict-gate dispatch across the three sync commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The description, title, and comment syncs each carried the same :noop / :conflict / :push dispatch — only the hash property, the local-text source, the fetch and push functions, the apply step, and the status messages differed. I pulled the dispatch into pearl--commit-sync-decision, which takes a spec plist for the varying parts (description alone advances LINEAR-DESC-UPDATED-AT, via :after-push). The three commands now build a spec and call it, and the comment's own-comment permission check stays in its caller. No behavior change — 353 tests green, including the sync, title-sync, comment-editing, and conflict suites. --- pearl.el | 175 ++++++++++++++++++++++++++++++++++----------------------------- 1 file changed, 95 insertions(+), 80 deletions(-) diff --git a/pearl.el b/pearl.el index f48c39e..ce56cf7 100644 --- a/pearl.el +++ b/pearl.el @@ -2055,6 +2055,48 @@ Callers wrap this in `save-excursion' when they must not move point." (unless (ignore-errors (org-back-to-heading t) t) (user-error "%s" (or message "Not on a Linear issue heading")))) +(defun pearl--commit-sync-decision (decision spec) + "Carry out DECISION for a gated text-field sync, per the SPEC plist. +DECISION is a `pearl--sync-decision' result. The description, title, and +comment syncs share this dispatch; they differ only in the SPEC values: + + :local local text being synced + :remote remote text just fetched + :marker marker at the entry whose drawer holds the hash + :prop SHA256 provenance property name + :label label for the conflict prompt + :apply FN (reconciled-text) writing it into the buffer + :push FN (text result-callback) performing the API push + :noop-message shown on `:noop' + :success-message shown after a successful `:push' + :fail-message shown on a failed `:push' + :after-push optional FN (result marker) for extra success bookkeeping + +The conflict path reuses `:push', adapting its result-callback to the +success-boolean `pearl--resolve-conflict' expects." + (let ((push (plist-get spec :push)) + (marker (plist-get spec :marker))) + (pcase decision + (:noop (message "%s" (plist-get spec :noop-message))) + (:conflict + (pearl--resolve-conflict + (plist-get spec :label) (plist-get spec :local) (plist-get spec :remote) + marker (plist-get spec :prop) (plist-get spec :apply) + (lambda (text cb) + (funcall push text (lambda (r) (funcall cb (plist-get r :success))))))) + (:push + (funcall push (plist-get spec :local) + (lambda (result) + (if (plist-get result :success) + (progn + (org-entry-put marker (plist-get spec :prop) + (secure-hash 'sha256 (plist-get spec :local))) + (when (plist-get spec :after-push) + (funcall (plist-get spec :after-push) result marker)) + (message "%s" (plist-get spec :success-message)) + (pearl--surface-buffer (marker-buffer marker))) + (message "%s" (plist-get spec :fail-message))))))))) + ;;;###autoload (defun pearl-sync-current-issue () "Push the description edited in the Org body of the issue at point to Linear. @@ -2080,35 +2122,26 @@ refused and the conflict reported (refresh to reconcile)." (lambda (remote) (if (null remote) (message "Could not fetch %s from Linear; not syncing" issue-id) - (pcase (pearl--sync-decision - local-md stored (plist-get remote :description)) - (:noop (message "%s already matches Linear" issue-id)) - (:conflict - (pearl--resolve-conflict - (format "%s description" issue-id) - local-md (plist-get remote :description) - marker "LINEAR-DESC-SHA256" - (lambda (md) - (org-with-point-at marker - (pearl--set-entry-body-at-point (pearl--md-to-org md)))) - (lambda (md cb) - (pearl--update-issue-description-async - issue-id md - (lambda (r) (funcall cb (plist-get r :success))))))) - (:push - (pearl--update-issue-description-async - issue-id local-md - (lambda (result) - (if (plist-get result :success) - (progn - (org-entry-put marker "LINEAR-DESC-SHA256" - (secure-hash 'sha256 local-md)) - (when (plist-get result :updated-at) - (org-entry-put marker "LINEAR-DESC-UPDATED-AT" - (plist-get result :updated-at))) - (message "Synced description for %s to Linear" issue-id) - (pearl--surface-buffer (marker-buffer marker))) - (message "Failed to sync description for %s" issue-id)))))))))))))) + (pearl--commit-sync-decision + (pearl--sync-decision local-md stored (plist-get remote :description)) + (list + :local local-md + :remote (plist-get remote :description) + :marker marker + :prop "LINEAR-DESC-SHA256" + :label (format "%s description" issue-id) + :apply (lambda (md) + (org-with-point-at marker + (pearl--set-entry-body-at-point (pearl--md-to-org md)))) + :push (lambda (text cb) + (pearl--update-issue-description-async issue-id text cb)) + :noop-message (format "%s already matches Linear" issue-id) + :success-message (format "Synced description for %s to Linear" issue-id) + :fail-message (format "Failed to sync description for %s" issue-id) + :after-push (lambda (result m) + (when (plist-get result :updated-at) + (org-entry-put m "LINEAR-DESC-UPDATED-AT" + (plist-get result :updated-at)))))))))))))) ;;;###autoload (defun pearl-sync-current-issue-title () @@ -2136,33 +2169,23 @@ advances the title provenance; both-changed refuses and reports the conflict." (lambda (remote) (if (null remote) (message "Could not fetch %s from Linear; not syncing" issue-id) - (pcase (pearl--sync-decision - local-title stored (plist-get remote :title)) - (:noop (message "%s title already matches Linear" issue-id)) - (:conflict - (pearl--resolve-conflict - (format "%s title" issue-id) - local-title (plist-get remote :title) - marker "LINEAR-TITLE-SHA256" - (lambda (md) - (org-with-point-at marker - (org-back-to-heading t) - (org-edit-headline md))) - (lambda (md cb) - (pearl--update-issue-title-async - issue-id md - (lambda (r) (funcall cb (plist-get r :success))))))) - (:push - (pearl--update-issue-title-async - issue-id local-title - (lambda (result) - (if (plist-get result :success) - (progn - (org-entry-put marker "LINEAR-TITLE-SHA256" - (secure-hash 'sha256 local-title)) - (message "Synced title for %s to Linear" issue-id) - (pearl--surface-buffer (marker-buffer marker))) - (message "Failed to sync title for %s" issue-id)))))))))))))) + (pearl--commit-sync-decision + (pearl--sync-decision local-title stored (plist-get remote :title)) + (list + :local local-title + :remote (plist-get remote :title) + :marker marker + :prop "LINEAR-TITLE-SHA256" + :label (format "%s title" issue-id) + :apply (lambda (md) + (org-with-point-at marker + (org-back-to-heading t) + (org-edit-headline md))) + :push (lambda (text cb) + (pearl--update-issue-title-async issue-id text cb)) + :noop-message (format "%s title already matches Linear" issue-id) + :success-message (format "Synced title for %s to Linear" issue-id) + :fail-message (format "Failed to sync title for %s" issue-id))))))))))) (defun pearl--replace-issue-subtree-at-point (issue) "Replace the issue subtree at point with a freshly formatted ISSUE entry. @@ -3455,30 +3478,22 @@ reported (refresh to reconcile)." (lambda (remote-md) (if (null remote-md) (message "Could not fetch the comment from Linear; not syncing") - (pcase (pearl--sync-decision local-md stored remote-md) - (:noop (message "Comment already matches Linear")) - (:conflict - (pearl--resolve-conflict - "comment" - local-md remote-md marker "LINEAR-COMMENT-SHA256" - (lambda (md) - (org-with-point-at marker - (pearl--set-entry-body-at-point (pearl--md-to-org md)))) - (lambda (md cb) - (pearl--update-comment-async - comment-id md - (lambda (r) (funcall cb (plist-get r :success))))))) - (:push - (pearl--update-comment-async - comment-id local-md - (lambda (result) - (if (plist-get result :success) - (progn - (org-entry-put marker "LINEAR-COMMENT-SHA256" - (secure-hash 'sha256 local-md)) - (message "Synced comment to Linear") - (pearl--surface-buffer (marker-buffer marker))) - (message "Failed to sync comment")))))))))))))))))) + (pearl--commit-sync-decision + (pearl--sync-decision local-md stored remote-md) + (list + :local local-md + :remote remote-md + :marker marker + :prop "LINEAR-COMMENT-SHA256" + :label "comment" + :apply (lambda (md) + (org-with-point-at marker + (pearl--set-entry-body-at-point (pearl--md-to-org md)))) + :push (lambda (text cb) + (pearl--update-comment-async comment-id text cb)) + :noop-message "Comment already matches Linear" + :success-message "Synced comment to Linear" + :fail-message "Failed to sync comment")))))))))))))) ;;; Transient Menu -- cgit v1.2.3