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