Home GitHub Patreon
RSS Twitter

Archive subtrees under the same hierarchy as original in the archive files

The way archiving works in org-mode always annoyed me. Specifically the fact that the tree structure gets flattened when you archive your tasks. Sure, there is the property ARCHIVE_OLPATH but it is not as visual as having the tasks in trees. Plus it makes things like clock reports very difficult.

If your tree equals a project, clockreport with C-c C-x C-r will sum the times properly… well not anymore with flattened structure. This works so long as you archive the entire project at once, but I often have projects so huge (e.g. Emacs Configuration) that I archive subtress or parts or just single tasks on a regular basis, with the base project being practically never-ending.

Well, no more, I said, and wrote this handy advice that uses the aforementioned property to reconstruct the tree in the archive file. If the parent headings don't exist they are created on-demand. There is one caveat: if you have non-unique headings, this can sometimes file things under wrong tree; the search is done from top to bottom following the hierarchy.

(defadvice org-archive-subtree (around fix-hierarchy activate)
  (let* ((fix-archive-p (and (not current-prefix-arg)
                             (not (use-region-p))))
         (afile (org-extract-archive-file (org-get-local-archive-location)))
         (buffer (or (find-buffer-visiting afile) (find-file-noselect afile))))
    ad-do-it
    (when fix-archive-p
      (with-current-buffer buffer
        (goto-char (point-max))
        (while (org-up-heading-safe))
        (let* ((olpath (org-entry-get (point) "ARCHIVE_OLPATH"))
               (path (and olpath (split-string olpath "/")))
               (level 1)
               tree-text)
          (when olpath
            (org-mark-subtree)
            (setq tree-text (buffer-substring (region-beginning) (region-end)))
            (let (this-command) (org-cut-subtree))
            (goto-char (point-min))
            (save-restriction
              (widen)
              (-each path
                (lambda (heading)
                  (if (re-search-forward
                       (rx-to-string
                        `(: bol (repeat ,level "*") (1+ " ") ,heading)) nil t)
                      (org-narrow-to-subtree)
                    (goto-char (point-max))
                    (unless (looking-at "^")
                      (insert "\n"))
                    (insert (make-string level ?*)
                            " "
                            heading
                            "\n"))
                  (cl-incf level)))
              (widen)
              (org-end-of-subtree t t)
              (org-paste-subtree level tree-text))))))))

The code uses the old advice system because I like it better (it reads much better, even if it isn't so powerful), plus I still use Emacs 24. If you test this on new versions and find out it doesn't work, open an issue or a pull request. It also depends on dash.el so make sure you have that installed.


Last updated at: 2017-06-04 15:39
Found a typo? Edit on GitHub!