Here is my patch, which works for my use case.

Although the commit only changes a couple of lines, there are large blocks
whose indentation changes.
The non-whitespace changes are
1) to add a without-restriction as soon as buffer switches to archive
buffer.
2) to wrap the call to org-fold-show-all in that buffer with an
org-fold-core-save-visibility
3) Delete a call to widen, which had become redundant.

The function org-archive-subtree is quite long, but I believe I put the two
wrappers in the sensible place, without breaking anything.

Ihor, I don't quite understand your remark about the two previous patches,
are they something I need to take into account in this patch?

Best,
Benjamin

On Sat, Jul 19, 2025 at 11:41 PM Ihor Radchenko <yanta...@posteo.net> wrote:

> Benjamin McMillan <mcmilla...@gmail.com> writes:
>
> > When archiving a subtree to a different heading in the same buffer, all
> > headings are unfolded and the current narrowing is lost. This is easily
> > reproduced in a minimal instance of emacs by the following steps:
> >
> > 1. In an org file, create two headings "head" and "arch". Create a
> "child"
> > heading for "head".
> > 2. Add the archive property to "head"
> >    :PROPERTIES:
> >    #+ARCHIVE: ::* arch
> >    :END:
> > 3. org-narrow-to-subtree on "head".
> > 4. org-archive-subtree on "child"
> > 5. (This demonstrates the loss of narrowing, you can also create
> additional
> > headings with folded content to see the loss of folding.)
>
> Confirmed. This kind of intentional, but intentional in a sense that it
> was a (non-ideal) implementation detail of previous feature additions
> and bug fixes:
>
> https://cgit.git.savannah.gnu.org/cgit/emacs/org-mode.git/commit/?id=44e7ed1a59c8587c2d5c3a54917576f1505a6c7b
>
> https://cgit.git.savannah.gnu.org/cgit/emacs/org-mode.git/commit/?id=bf09955fec57307616926959b31e19af42520db0
>
> > Both issues can be fixed by small modification to org-archive-subtree, by
> > wrapping the archiving edits in org-fold-core-save-visibility and
> > save-excursion blocks.
> >
> > I am happy to submit a patch.
>
> That would be welcome.
>
> --
> Ihor Radchenko // yantar92,
> Org mode maintainer,
> Learn more about Org mode at <https://orgmode.org/>.
> Support Org development at <https://liberapay.com/org-mode>,
> or support my work at <https://liberapay.com/yantar92>
>
From 02fe8d7de38bf43351826afbea2b1d429f88780e Mon Sep 17 00:00:00 2001
From: Benjamin McMillan <mcmillanbb@gmail.com>
Date: Mon, 21 Jul 2025 21:41:46 +0900
Subject: [PATCH] org-archive.el: Fix issue archiving subtree to same file

* org-archive.el (org-archive-subtree): Fixes an issue where
archiving a subtree to the same file would lose both folding
and narrowing state, by adding without-restriction and
org-fold-core-save-visibility wrappers.

Reported-by: Benjamin McMillan <mcmillanbb@gmail.com>
Link: https://list.orgmode.org/87h5z8p7l5.fsf@localhost/T/#t
---
 lisp/org-archive.el | 195 ++++++++++++++++++++++----------------------
 1 file changed, 98 insertions(+), 97 deletions(-)

diff --git a/lisp/org-archive.el b/lisp/org-archive.el
index fb386e683..e36d1ca48 100644
--- a/lisp/org-archive.el
+++ b/lisp/org-archive.el
@@ -324,104 +324,105 @@ direct children of this heading."
 	    ;; which would lead to duplication of subtrees
 	    (let (this-command) (org-copy-subtree 1 nil t))
 	    (set-buffer buffer)
-	    ;; Enforce Org mode for the archive buffer
-	    (if (not (derived-mode-p 'org-mode))
-		;; Force the mode for future visits.
-		(let ((org-insert-mode-line-in-empty-file t)
-		      (org-inhibit-startup t))
-		  (call-interactively 'org-mode)))
-	    (when (and newfile-p org-archive-file-header-format)
-	      (goto-char (point-max))
-	      (insert (format org-archive-file-header-format
-			      (buffer-file-name this-buffer))))
-	    (when datetree-date
-	      (require 'org-datetree)
-	      (org-datetree-find-date-create datetree-date)
-	      (org-narrow-to-subtree))
-	    ;; Force the TODO keywords of the original buffer
-	    (let ((org-todo-line-regexp tr-org-todo-line-regexp)
-		  (org-todo-keywords-1 tr-org-todo-keywords-1)
-		  (org-todo-kwd-alist tr-org-todo-kwd-alist)
-		  (org-done-keywords tr-org-done-keywords)
-		  (org-todo-regexp tr-org-todo-regexp)
-		  (org-todo-line-regexp tr-org-todo-line-regexp))
-	      (goto-char (point-min))
-	      (org-fold-show-all '(headings blocks))
-	      (if (and heading (not (and datetree-date (not datetree-subheading-p))))
-		  (progn
-		    (if (re-search-forward
-			 (concat "^" (regexp-quote heading)
-				 "\\([ \t]+:\\(" org-tag-re ":\\)+\\)?[ \t]*$")
-			 nil t)
-			(goto-char (match-end 0))
-		      ;; Heading not found, just insert it at the end
-		      (goto-char (point-max))
-		      (or (bolp) (insert "\n"))
-		      ;; datetrees don't need too much spacing
-		      (insert (if datetree-date "" "\n") heading "\n")
-		      (end-of-line 0))
-		    ;; Make the subtree visible
-		    (org-fold-show-subtree)
+            (without-restriction
+	      ;; Enforce Org mode for the archive buffer
+	      (if (not (derived-mode-p 'org-mode))
+		  ;; Force the mode for future visits.
+		  (let ((org-insert-mode-line-in-empty-file t)
+		        (org-inhibit-startup t))
+		    (call-interactively 'org-mode)))
+	      (when (and newfile-p org-archive-file-header-format)
+	        (goto-char (point-max))
+	        (insert (format org-archive-file-header-format
+			        (buffer-file-name this-buffer))))
+	      (when datetree-date
+	        (require 'org-datetree)
+	        (org-datetree-find-date-create datetree-date)
+	        (org-narrow-to-subtree))
+	      ;; Force the TODO keywords of the original buffer
+	      (let ((org-todo-line-regexp tr-org-todo-line-regexp)
+		    (org-todo-keywords-1 tr-org-todo-keywords-1)
+		    (org-todo-kwd-alist tr-org-todo-kwd-alist)
+		    (org-done-keywords tr-org-done-keywords)
+		    (org-todo-regexp tr-org-todo-regexp)
+		    (org-todo-line-regexp tr-org-todo-line-regexp))
+	        (goto-char (point-min))
+                (org-fold-core-save-visibility t
+                  (org-fold-show-all '(headings blocks))
+	          (if (and heading (not (and datetree-date (not datetree-subheading-p))))
+		      (progn
+		        (if (re-search-forward
+			     (concat "^" (regexp-quote heading)
+				     "\\([ \t]+:\\(" org-tag-re ":\\)+\\)?[ \t]*$")
+			     nil t)
+			    (goto-char (match-end 0))
+		          ;; Heading not found, just insert it at the end
+		          (goto-char (point-max))
+		          (or (bolp) (insert "\n"))
+		          ;; datetrees don't need too much spacing
+		          (insert (if datetree-date "" "\n") heading "\n")
+		          (end-of-line 0))
+		        ;; Make the subtree visible
+		        (org-fold-show-subtree)
+		        (if org-archive-reversed-order
+			    (progn
+			      (org-back-to-heading t)
+			      (outline-next-heading))
+		          (org-end-of-subtree t))
+		        (skip-chars-backward " \t\r\n")
+		        (and (looking-at "[ \t\r\n]*")
+			     ;; datetree archives don't need so much spacing.
+			     (replace-match (if datetree-date "\n" "\n\n"))))
+		    ;; No specific heading, just go to end of file, or to the
+		    ;; beginning, depending on `org-archive-reversed-order'.
 		    (if org-archive-reversed-order
-			(progn
-			  (org-back-to-heading t)
-			  (outline-next-heading))
-		      (org-end-of-subtree t))
-		    (skip-chars-backward " \t\r\n")
-		    (and (looking-at "[ \t\r\n]*")
-			 ;; datetree archives don't need so much spacing.
-			 (replace-match (if datetree-date "\n" "\n\n"))))
-		;; No specific heading, just go to end of file, or to the
-		;; beginning, depending on `org-archive-reversed-order'.
-		(if org-archive-reversed-order
-		    (progn
-		      (goto-char (point-min))
-		      (unless (org-at-heading-p) (outline-next-heading)))
-		  (goto-char (point-max))
-		  ;; Subtree narrowing can let the buffer end on
-		  ;; a headline.  `org-paste-subtree' then deletes it.
-		  ;; To prevent this, make sure visible part of buffer
-		  ;; always terminates on a new line, while limiting
-		  ;; number of blank lines in a date tree.
-		  (unless (and datetree-date (bolp)) (insert "\n"))))
-	      ;; Paste
-	      (org-paste-subtree (org-get-valid-level level (and heading 1)))
-	      ;; Shall we append inherited tags?
-	      (and inherited-tags
-		   (or (and (eq org-archive-subtree-add-inherited-tags 'infile)
-			    infile-p)
-		       (eq org-archive-subtree-add-inherited-tags t))
-		   (org-set-tags all-tags))
-	      ;; Mark the entry as done
-	      (when (and org-archive-mark-done
-			 (let ((case-fold-search nil))
-			   (looking-at org-todo-line-regexp))
-			 (or (not (match-end 2))
-			     (not (member (match-string 2) org-done-keywords))))
-		(let (org-log-done org-todo-log-states)
-		  (org-todo
-		   (car (or (member org-archive-mark-done org-done-keywords)
-			    org-done-keywords)))))
-
-	      ;; Add the context info.
-	      (dolist (item org-archive-save-context-info)
-		(let ((value (cdr (assq item context))))
-		  (when (org-string-nw-p value)
-		    (org-entry-put
-		     (point)
-		     (concat "ARCHIVE_" (upcase (symbol-name item)))
-		     value))))
-              (run-hooks 'org-archive-finalize-hook)
-	      ;; Save the buffer, if it is not the same buffer and
-	      ;; depending on `org-archive-subtree-save-file-p'.
-	      (unless (eq this-buffer buffer)
-		(when (or (eq org-archive-subtree-save-file-p t)
-			  (eq org-archive-subtree-save-file-p
-			      (if (boundp 'org-archive-from-agenda)
-				  'from-agenda
-				'from-org)))
-		  (save-buffer)))
-	      (widen))))
+		        (progn
+		          (goto-char (point-min))
+		          (unless (org-at-heading-p) (outline-next-heading)))
+		      (goto-char (point-max))
+		      ;; Subtree narrowing can let the buffer end on
+		      ;; a headline.  `org-paste-subtree' then deletes it.
+		      ;; To prevent this, make sure visible part of buffer
+		      ;; always terminates on a new line, while limiting
+		      ;; number of blank lines in a date tree.
+		      (unless (and datetree-date (bolp)) (insert "\n"))))
+	          ;; Paste
+	          (org-paste-subtree (org-get-valid-level level (and heading 1)))
+	          ;; Shall we append inherited tags?
+	          (and inherited-tags
+		       (or (and (eq org-archive-subtree-add-inherited-tags 'infile)
+			        infile-p)
+		           (eq org-archive-subtree-add-inherited-tags t))
+		       (org-set-tags all-tags))
+	          ;; Mark the entry as done
+	          (when (and org-archive-mark-done
+			     (let ((case-fold-search nil))
+			       (looking-at org-todo-line-regexp))
+			     (or (not (match-end 2))
+			         (not (member (match-string 2) org-done-keywords))))
+		    (let (org-log-done org-todo-log-states)
+		      (org-todo
+		       (car (or (member org-archive-mark-done org-done-keywords)
+			        org-done-keywords)))))
+
+	          ;; Add the context info.
+	          (dolist (item org-archive-save-context-info)
+		    (let ((value (cdr (assq item context))))
+		      (when (org-string-nw-p value)
+		        (org-entry-put
+		         (point)
+		         (concat "ARCHIVE_" (upcase (symbol-name item)))
+		         value))))
+                  (run-hooks 'org-archive-finalize-hook)
+	          ;; Save the buffer, if it is not the same buffer and
+	          ;; depending on `org-archive-subtree-save-file-p'.
+	          (unless (eq this-buffer buffer)
+		    (when (or (eq org-archive-subtree-save-file-p t)
+			      (eq org-archive-subtree-save-file-p
+			          (if (boundp 'org-archive-from-agenda)
+				      'from-agenda
+				    'from-org)))
+		      (save-buffer))))))))
 	;; Here we are back in the original buffer.  Everything seems
 	;; to have worked.  So now run hooks, cut the tree and finish
 	;; up.
-- 
2.50.1

Reply via email to