branch: externals/forgejo
commit e79049014fff3ed665cae8bebbf69c231f49da51
Author: Thanos Apollo <[email protected]>
Commit: Thanos Apollo <[email protected]>

    filter: Guard cache close-missing on partial sync
---
 lisp/forgejo-filter.el       |  8 ++++++++
 lisp/forgejo-issue.el        | 27 +++++++++++++++------------
 lisp/forgejo-pull.el         | 27 +++++++++++++++------------
 tests/forgejo-test-filter.el | 17 ++++++++++++++++-
 tests/forgejo-test-issue.el  | 36 ++++++++++++++++++++++++++++++++++++
 tests/forgejo-test-pull.el   | 36 ++++++++++++++++++++++++++++++++++++
 6 files changed, 126 insertions(+), 25 deletions(-)

diff --git a/lisp/forgejo-filter.el b/lisp/forgejo-filter.el
index 69092d3584..1092b0ae05 100644
--- a/lisp/forgejo-filter.el
+++ b/lisp/forgejo-filter.el
@@ -101,6 +101,14 @@ The :query value, if present, is appended as bare words."
       (push query parts))
     (string-join (nreverse parts) " ")))
 
+(defun forgejo-filter-authoritative-open-sync-p (filters partial)
+  "Return non-nil when FILTERS describe a complete open sync.
+PARTIAL non-nil means the API response did not include all pages."
+  (and (not partial)
+       (equal (plist-get filters :state) "open")
+       (not (cl-some (lambda (key) (plist-get filters key))
+                     '(:labels :milestone :author :query :page :since)))))
+
 ;;; API param building
 
 (defconst forgejo-filter--api-param-map
diff --git a/lisp/forgejo-issue.el b/lisp/forgejo-issue.el
index 3f83b402ce..c630c88434 100644
--- a/lisp/forgejo-issue.el
+++ b/lisp/forgejo-issue.el
@@ -125,18 +125,21 @@ When FORCE is non-nil, fetch all and mark missing issues 
as closed."
               buf-name host-url host owner repo forgejo-issue--filters))))
        ;; Done: close missing, set sync time, final re-render
        (lambda (all-data headers)
-         (when (and force (equal (plist-get filters :state) "open"))
-           (let ((numbers (mapcar (lambda (i) (alist-get 'number i)) 
all-data)))
-             (forgejo-db-close-missing host owner repo numbers)))
-         (forgejo-db-set-sync-time host owner repo "issues"
-                                   (format-time-string "%Y-%m-%dT%H:%M:%SZ"
-                                                       nil t))
-         (when (buffer-live-p (get-buffer buf-name))
-           (with-current-buffer buf-name
-             (forgejo-issue--render-from-db
-              buf-name host-url host owner repo forgejo-issue--filters)
-             (when-let* ((total (plist-get headers :total-count)))
-               (setq forgejo-issue--total-count total)))))))))
+         (let ((partial (plist-get headers :partial)))
+           (when (and force
+                      (forgejo-filter-authoritative-open-sync-p filters 
partial))
+             (let ((numbers (mapcar (lambda (i) (alist-get 'number i)) 
all-data)))
+               (forgejo-db-close-missing host owner repo numbers)))
+           (unless partial
+             (forgejo-db-set-sync-time host owner repo "issues"
+                                       (format-time-string "%Y-%m-%dT%H:%M:%SZ"
+                                                           nil t)))
+           (when (buffer-live-p (get-buffer buf-name))
+             (with-current-buffer buf-name
+               (forgejo-issue--render-from-db
+                buf-name host-url host owner repo forgejo-issue--filters)
+               (when-let* ((total (plist-get headers :total-count)))
+                 (setq forgejo-issue--total-count total))))))))))
 
 ;;; Entry commands
 
diff --git a/lisp/forgejo-pull.el b/lisp/forgejo-pull.el
index 346362a38c..0ca16415fa 100644
--- a/lisp/forgejo-pull.el
+++ b/lisp/forgejo-pull.el
@@ -121,18 +121,21 @@ When FORCE is non-nil, fetch all and mark missing PRs as 
closed."
             buf-name host-url host owner repo forgejo-pull--filters))))
      ;; Done: close missing, set sync time, final re-render
      (lambda (all-data headers)
-       (when (and force (equal (plist-get filters :state) "open"))
-         (let ((numbers (mapcar (lambda (p) (alist-get 'number p)) all-data)))
-           (forgejo-db-close-missing host owner repo numbers t)))
-       (forgejo-db-set-sync-time host owner repo "pulls"
-                                 (format-time-string "%Y-%m-%dT%H:%M:%SZ"
-                                                     nil t))
-       (when (buffer-live-p (get-buffer buf-name))
-         (with-current-buffer buf-name
-           (forgejo-pull--render-from-db
-            buf-name host-url host owner repo forgejo-pull--filters)
-           (when-let* ((total (plist-get headers :total-count)))
-             (setq forgejo-pull--total-count total))))))))
+       (let ((partial (plist-get headers :partial)))
+         (when (and force
+                    (forgejo-filter-authoritative-open-sync-p filters partial))
+           (let ((numbers (mapcar (lambda (p) (alist-get 'number p)) 
all-data)))
+             (forgejo-db-close-missing host owner repo numbers t)))
+         (unless partial
+           (forgejo-db-set-sync-time host owner repo "pulls"
+                                     (format-time-string "%Y-%m-%dT%H:%M:%SZ"
+                                                         nil t)))
+         (when (buffer-live-p (get-buffer buf-name))
+           (with-current-buffer buf-name
+             (forgejo-pull--render-from-db
+              buf-name host-url host owner repo forgejo-pull--filters)
+             (when-let* ((total (plist-get headers :total-count)))
+               (setq forgejo-pull--total-count total)))))))))
 
 ;;;###autoload
 (defun forgejo-pull-list (&optional owner repo)
diff --git a/tests/forgejo-test-filter.el b/tests/forgejo-test-filter.el
index 5ce0b60155..8d2e5a0776 100644
--- a/tests/forgejo-test-filter.el
+++ b/tests/forgejo-test-filter.el
@@ -99,7 +99,22 @@
     (should (equal (plist-get parsed :state) (plist-get reparsed :state)))
     (should (equal (plist-get parsed :labels) (plist-get reparsed :labels)))))
 
-;;; Group 4: List entries
+;;; Group 4: Authoritative sync predicate
+
+(ert-deftest forgejo-test-filter-authoritative-open-sync-p ()
+  "Recognize only complete unfiltered open syncs."
+  (should (forgejo-filter-authoritative-open-sync-p '(:state "open") nil))
+  (should-not (forgejo-filter-authoritative-open-sync-p '(:state "open") t))
+  (dolist (filters '((:state "closed")
+                     (:state "open" :labels "bug")
+                     (:state "open" :milestone "v1")
+                     (:state "open" :author "alice")
+                     (:state "open" :query "crash")
+                     (:state "open" :page 2)
+                     (:state "open" :since "2026-01-01T00:00:00Z")))
+    (should-not (forgejo-filter-authoritative-open-sync-p filters nil))))
+
+;;; Group 5: List entries
 
 (ert-deftest forgejo-test-filter-list-entries ()
   "Convert API alists to tabulated-list entries."
diff --git a/tests/forgejo-test-issue.el b/tests/forgejo-test-issue.el
index e204e3493b..e6d7e2d3c4 100644
--- a/tests/forgejo-test-issue.el
+++ b/tests/forgejo-test-issue.el
@@ -52,5 +52,41 @@
       (should (string= (cdr (assoc "q" params)) "bug"))
       (should (string= (cdr (assoc "page" params)) "2")))))
 
+;;; Group 3: Sync finalization
+
+(ert-deftest forgejo-test-issue-sync-filtered-force-does-not-close-missing ()
+  "Filtered forced syncs must not mark unrelated cached issues closed."
+  (let (close-called sync-called)
+    (cl-letf (((symbol-function 'forgejo-api-get) (lambda (&rest _args) nil))
+              ((symbol-function 'forgejo-api-get-paged)
+               (lambda (_host _endpoint _params _page-callback done-callback)
+                 (funcall done-callback '(((number . 1))) '(:total-count 1))))
+              ((symbol-function 'forgejo-db-close-missing)
+               (lambda (&rest _args) (setq close-called t)))
+              ((symbol-function 'forgejo-db-set-sync-time)
+               (lambda (&rest _args) (setq sync-called t))))
+      (forgejo-issue--sync "https://codeberg.org"; "codeberg.org"
+                           "owner" "repo" '(:state "open" :labels "bug")
+                           " *forgejo-test-missing*" t)
+      (should-not close-called)
+      (should sync-called))))
+
+(ert-deftest forgejo-test-issue-sync-partial-does-not-finalize ()
+  "Partial forced syncs must not close missing issues or advance sync time."
+  (let (close-called sync-called)
+    (cl-letf (((symbol-function 'forgejo-api-get) (lambda (&rest _args) nil))
+              ((symbol-function 'forgejo-api-get-paged)
+               (lambda (_host _endpoint _params _page-callback done-callback)
+                 (funcall done-callback '(((number . 1))) '(:partial t))))
+              ((symbol-function 'forgejo-db-close-missing)
+               (lambda (&rest _args) (setq close-called t)))
+              ((symbol-function 'forgejo-db-set-sync-time)
+               (lambda (&rest _args) (setq sync-called t))))
+      (forgejo-issue--sync "https://codeberg.org"; "codeberg.org"
+                           "owner" "repo" '(:state "open")
+                           " *forgejo-test-missing*" t)
+      (should-not close-called)
+      (should-not sync-called))))
+
 (provide 'forgejo-test-issue)
 ;;; forgejo-test-issue.el ends here
diff --git a/tests/forgejo-test-pull.el b/tests/forgejo-test-pull.el
index 2ca6c33ef1..e32d8dfa9e 100644
--- a/tests/forgejo-test-pull.el
+++ b/tests/forgejo-test-pull.el
@@ -52,5 +52,41 @@
       (should (string= (cdr (assoc "created_by" params)) "alice"))
       (should (string= (cdr (assoc "page" params)) "3")))))
 
+;;; Group 3: Sync finalization
+
+(ert-deftest forgejo-test-pull-sync-filtered-force-does-not-close-missing ()
+  "Filtered forced syncs must not mark unrelated cached PRs closed."
+  (let (close-called sync-called)
+    (cl-letf (((symbol-function 'forgejo-api-get) (lambda (&rest _args) nil))
+              ((symbol-function 'forgejo-api-get-paged)
+               (lambda (_host _endpoint _params _page-callback done-callback)
+                 (funcall done-callback '(((number . 1))) '(:total-count 1))))
+              ((symbol-function 'forgejo-db-close-missing)
+               (lambda (&rest _args) (setq close-called t)))
+              ((symbol-function 'forgejo-db-set-sync-time)
+               (lambda (&rest _args) (setq sync-called t))))
+      (forgejo-pull--sync "https://codeberg.org"; "codeberg.org"
+                          "owner" "repo" '(:state "open" :labels "bug")
+                          " *forgejo-test-missing*" t)
+      (should-not close-called)
+      (should sync-called))))
+
+(ert-deftest forgejo-test-pull-sync-partial-does-not-finalize ()
+  "Partial forced syncs must not close missing PRs or advance sync time."
+  (let (close-called sync-called)
+    (cl-letf (((symbol-function 'forgejo-api-get) (lambda (&rest _args) nil))
+              ((symbol-function 'forgejo-api-get-paged)
+               (lambda (_host _endpoint _params _page-callback done-callback)
+                 (funcall done-callback '(((number . 1))) '(:partial t))))
+              ((symbol-function 'forgejo-db-close-missing)
+               (lambda (&rest _args) (setq close-called t)))
+              ((symbol-function 'forgejo-db-set-sync-time)
+               (lambda (&rest _args) (setq sync-called t))))
+      (forgejo-pull--sync "https://codeberg.org"; "codeberg.org"
+                          "owner" "repo" '(:state "open")
+                          " *forgejo-test-missing*" t)
+      (should-not close-called)
+      (should-not sync-called))))
+
 (provide 'forgejo-test-pull)
 ;;; forgejo-test-pull.el ends here

Reply via email to