I assume that you are talking about the property, containing last modification time of the entry, but not its subheadings.
The way to approach it may be computing and saving the hash of each entry text before, say, file save and updating the modification time of the property if the hash changes:
(defun yant/getentryhash ()
"Get the hash sum of the text in current entry, except :HASH: and :MODIFIED: property texts."
(let* ((beg (progn (org-back-to-heading) (point)))
(if (not (re-search-forward "^\*+ " (point-max) t))
(full-str (buffer-substring beg end))
(str-nohash (if (string-match "^ *:HASH:.+\n" full-str)
(replace-match "" nil nil full-str)
(str-nohash-nomod (if (string-match "^ *:MODIFIED:.+\n" str-nohash)
(replace-match "" nil nil str-nohash)
(str-nohash-nomod-nopropbeg (if (string-match "^ *:PROPERTIES:\n" str-nohash-nomod)
(replace-match "" nil nil 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)
(defun yant/update-modification-time ()
"Set the :MODIFIED: property of the current entry to NOW and update :HASH: property."
(org-set-property "HASH" (format "%s" (yant/getentryhash)))
(org-set-property "MODIFIED" (format-time-string "%Y-%m-%d %H:%M")))
(defun yant/skip-nonmodified ()
"Skip org entries, which were not modified according to the :HASH: property"
(let ((next-headline (save-excursion (or (outline-next-heading) (point-max)))))
(if (string= (org-entry-get (point) "HASH" nil) (format "%s" (yant/getentryhash)))
(add-hook 'before-save-hook (lambda ()
(when (eq major-mode 'org-mode)
(org-map-entries #'yant/update-modification-time nil 'file #'yant/skip-nonmodified))))
Addressing the following issues:
Non-existent agenda file /path/to/test.org. [R]emove from list or [A]bort? message appears in the buffer, which is not saved to a file.
The hash calculation is affected by inserting the hash info after the first hash data insertion.
The issue is due to the implementation of
org-map-entries, which calls
org-check-agenda-file causing the message (see org-mode maillist). I do not know a good solution for this, but one can consider advising
org-check-agenda-file just for the time when the hook above is beeing executed like the following:
(define-advice org-check-agenda-file (:around (OLDFUN file) ignore-non-existant-files)
"Suppress \"Non existent agenda file...\" query. Just continue."
(if (file-exists-p file)
(funcall OLDFUN file)
(throw 'nextfile t))))
(advice-remove 'org-check-agenda-file #'org-check-agenda-file@ignore-non-existant-files)
(when (eq major-mode 'org-mode)
(advice-add 'org-check-agenda-file :around #'org-check-agenda-file@ignore-non-existant-files)
(org-map-entries #'yant/update-modification-time nil 'file #'yant/skip-nonmodified)
(advice-remove 'org-check-agenda-file #'org-check-agenda-file@ignore-non-existant-files))))
I have changed the code in the answer to ignore
:END: statements. Adding of these keywords were causing the hash change in the entry, which did not have any property drawers at the beginning.
If one would like to ignore the text properties during hash calculation,
yant/getentryhash should be replaced by