Created
October 19, 2025 15:03
-
-
Save mmarshall540/8fcefea266e041f9a54a1eb704cd9b3b to your computer and use it in GitHub Desktop.
Add "MODIFIED" (and "HASH") properties to Org-mode entries on save
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;; Org-mode: Custom property: MODIFIED | |
;; Adapted from: https://emacs.stackexchange.com/a/39376/31079 | |
(defun yant/getentryhash () | |
"Get the hash sum of the text in current entry. | |
Except :HASH: and :MODIFIED: property texts." | |
(save-excursion | |
(let* ((beg (progn (org-back-to-heading :invisible-ok) (point))) | |
(end (progn | |
(forward-char) | |
(if (not (re-search-forward "^\*+ " (point-max) t)) | |
(point-max) | |
(match-beginning 0)))) | |
(full-str (buffer-substring-no-properties beg end)) | |
(str-nohash (if (string-match "^ *:HASH:.+\n" full-str) | |
(replace-match "" nil nil full-str) | |
full-str)) | |
(str-nohash-nomod (if (string-match | |
"^ *:MODIFIED:.+\n" str-nohash) | |
(replace-match "" nil nil str-nohash) | |
str-nohash)) | |
(str-nohash-nomod-nopropbeg (if (string-match | |
"^ *:PROPERTIES:\n" | |
str-nohash-nomod) | |
(replace-match | |
"" nil nil | |
str-nohash-nomod) | |
str-nohash-nomod)) | |
(str-nohash-nomod-nopropbeg-end | |
(if (string-match "^ *:END:\n" str-nohash-nomod-nopropbeg) | |
(replace-match "" nil nil str-nohash-nomod-nopropbeg) | |
str-nohash-nomod-nopropbeg)) | |
;; Do not change hash just because whitespace was trimmed | |
;; from line-endings or extra newlines removed or added to | |
;; the end of an entry. | |
(final-str | |
(concat | |
(string-trim-right | |
(replace-regexp-in-string | |
"[ \t]+[\n\r]" "\n" | |
str-nohash-nomod-nopropbeg-end)) | |
"\n"))) | |
(secure-hash 'md5 final-str)))) | |
(defun yant/update-modification-time () | |
"Set the :MODIFIED: property of the current entry. | |
Set to NOW and update :HASH: property." | |
(org-set-property "HASH" (format "%s" (yant/getentryhash))) | |
(org-set-property "MODIFIED" | |
(format-time-string | |
(org-time-stamp-format :with-hm :inactive))) | |
(setq my/updated-org-entries-count | |
(1+ my/updated-org-entries-count))) | |
(defun yant/skip-nonmodified () | |
"Skip org entries, which were not modified." | |
(let ((next-headline (save-excursion (or (outline-next-heading) | |
(point-max))))) | |
(if (string= (org-entry-get (point) "HASH" nil) | |
(format "%s" (yant/getentryhash))) | |
next-headline | |
nil))) | |
(defvar my/updated-org-entries-count) | |
(defun my/org-update-modified-prop () | |
"Update the MODIFIED property if major-mode is `org-mode'." | |
(setq-local my/updated-org-entries-count 0) | |
(when (and (eq major-mode 'org-mode) | |
(equal default-directory (expand-file-name "~/org/"))) | |
(org-map-entries #'yant/update-modification-time | |
nil | |
'file | |
#'yant/skip-nonmodified) | |
(when (> my/updated-org-entries-count 0) | |
(message "Saved %s and updated %s entries!" | |
buffer-file-name | |
my/updated-org-entries-count)))) | |
(add-hook 'before-save-hook 'my/org-update-modified-prop) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment