Subject: [PATCH] Add option to save a buffer without running save hooks

From: Daniel Martín <[email-masked]>
Date: Mon, 11 Sep 2023 12:24:40 +0200

Tags: patch

Users can customize `before-save-hook' to add things like
`delete-trailing-whitespace' or `copyright-update'.  However, there are
cases where you want to save a buffer without running any save hooks
without changing your configuration and then changing it back.

I've attached a patch to make `save-buffer', when invoked with a
negative argument (C-u - C-x C-s), temporarily avoid running any save
hooks.

>From 6e1f19403937c266acd3d858ecc6c9e0e6b48ade Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Mart=C3=ADn?= <[email-masked]>
Date: Mon, 11 Sep 2023 11:55:00 +0200
Subject: [PATCH] Add option to save a buffer without running save hooks

* lisp/files.el (save-buffer): Set `ignore-save-hooks' when
`save-buffer' is run with a negative argument.
(basic-save-buffer): Do not run `before-save-hook' or
`after-save-hook' if `ignore-save-hooks' is set.
* doc/emacs/files.texi (Save Commands): Update the user manual.
* etc/NEWS: Advertise it.
---
 doc/emacs/files.texi |  5 ++++-
 etc/NEWS             |  5 +++++
 lisp/files.el        | 21 +++++++++++++--------
 3 files changed, 22 insertions(+), 9 deletions(-)

diff --git a/doc/emacs/files.texi b/doc/emacs/files.texi
index 7efb4516d15..c1acd1e80d4 100644
--- a/doc/emacs/files.texi
+++ b/doc/emacs/files.texi
@@ -457,7 +457,10 @@ Save Commands
 @end example
 
 With a prefix argument, @kbd{C-u C-x C-s}, Emacs also marks the buffer
-to be backed up when the next save is done.  @xref{Backup}.
+to be backed up when the next save is done.  @xref{Backup}.  With a
+negative argument, @kbd{C-u - C-x C-s}, Emacs doesn't run the hook
+@code{before-save-hook} before saving the buffer, and doesn't run the
+hook @code{after-save-hook} after saving the buffer.
 
 @kindex C-x s
 @findex save-some-buffers
diff --git a/etc/NEWS b/etc/NEWS
index 51e89fc96dd..772aa49bc95 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -221,6 +221,11 @@ whereas if the mouse pointer is in the left half of a glyph, point
 will be put in front the buffer position corresponding to that glyph.
 By default this is disabled.
 
++++
+** You can now avoid running save hooks when saving a buffer.
+If you save a buffer with a negative argument, C-u - C-x C-s, Emacs
+won't run the hooks 'before-save-hook' and 'after-save-hook'.
+
 ** Internationalization
 
 ---
diff --git a/lisp/files.el b/lisp/files.el
index b67482a2f74..6ee28f23daa 100644
--- a/lisp/files.el
+++ b/lisp/files.el
@@ -5618,8 +5618,10 @@ save-buffer
  to become a backup when the next save is done,
  and makes the previous version into a backup file.
 
-With a numeric prefix argument of 0, never make the previous version
-into a backup file.
+With a numeric prefix argument of 0, never make the previous
+version into a backup file.  With a numeric prefix argument of
+-1, do not run the hooks `before-save-hook' and
+`after-save-hook'.
 
 Note that the various variables that control backups, such
 as `version-control', `backup-enable-predicate', `vc-make-backup-files',
@@ -5649,7 +5651,8 @@ save-buffer
   (interactive "p")
   (let ((modp (buffer-modified-p))
 	(make-backup-files (or (and make-backup-files (not (eq arg 0)))
-			       (memq arg '(16 64)))))
+			       (memq arg '(16 64))))
+        (ignore-save-hooks (eq arg -1)))
     (and modp (memq arg '(16 64)) (setq buffer-backed-up nil))
     ;; We used to display the message below only for files > 50KB, but
     ;; then Rmail-mbox never displays it due to buffer swapping.  If
@@ -5660,7 +5663,7 @@ save-buffer
              (not noninteractive)
              (not save-silently))
 	(message "Saving file %s..." (buffer-file-name)))
-    (basic-save-buffer (called-interactively-p 'any))
+    (basic-save-buffer (called-interactively-p 'any) ignore-save-hooks)
     (and modp (memq arg '(4 64)) (setq buffer-backed-up nil))))
 
 (defun delete-auto-save-file-if-necessary (&optional force)
@@ -5720,7 +5723,7 @@ save-buffer-coding-system
 
 (put 'save-buffer-coding-system 'permanent-local t)
 
-(defun basic-save-buffer (&optional called-interactively)
+(defun basic-save-buffer (&optional called-interactively ignore-save-hooks)
   "Save the current buffer in its visited file, if it has been modified.
 
 The hooks `write-contents-functions', `local-write-file-hooks'
@@ -5769,8 +5772,9 @@ basic-save-buffer
 		     (goto-char (point-max))
 		     (insert ?\n))))
 	    ;; Don't let errors prevent saving the buffer.
-	    (with-demoted-errors "Before-save hook error: %S"
-	      (run-hooks 'before-save-hook))
+            (unless ignore-save-hooks
+	      (with-demoted-errors "Before-save hook error: %S"
+	        (run-hooks 'before-save-hook)))
             ;; Give `write-contents-functions' a chance to
             ;; short-circuit the whole process.
 	    (unless (run-hook-with-args-until-success 'write-contents-functions)
@@ -5834,7 +5838,8 @@ basic-save-buffer
             ;; If the auto-save file was recent before this command,
 	    ;; delete it now.
 	    (delete-auto-save-file-if-necessary recent-save))
-	  (run-hooks 'after-save-hook))
+	  (unless ignore-save-hooks
+            (run-hooks 'after-save-hook)))
       (or noninteractive
           (not called-interactively)
           (files--message "(No changes need to be saved)")))))
-- 
2.40.1