Subject: bug#18344: Archive (e.g. tarball) file buffers are editable, but have no undo information

From: Glenn Morris <[email-masked]>
Date: Thu, 28 Aug 2014 13:44:15 -0400

Paul Pogonyshev wrote:

> 1) Visit any '.tar.gz' archve;
> 2) Open any contained file within that archive;
> 3) Press 'x' or any other self-inserting key;
> 4) Buffer is now in 'modified' state, but you cannot undo your modification.

Please at least say what version of Emacs your bug reports are about.

Anyway, by experiment, this is new since after 24.3.

The cause seems to be:
  
  revno: 113108
  committer: Stefan Monnier <[email-masked]>
  timestamp: Thu 2013-06-20 23:08:47 -0400
  message:
    * lisp/emacs-lisp/package.el: Use tar-mode rather than tar executable.

which removes previously existing code in tar-mode.el that did a little
dance with buffer-undo-list. Now it just sets it to t.

*** lisp/tar-mode.el	2013-05-09 01:42:00 +0000
--- lisp/tar-mode.el	2013-06-21 03:08:47 +0000
***************
*** 740,749 ****
  	  nil
  	  (error "This line does not describe a tar-file entry"))))
  
! (defun tar-get-descriptor ()
!   (let* ((descriptor (tar-current-descriptor))
! 	 (size (tar-header-size descriptor))
! 	 (link-p (tar-header-link-type descriptor)))
      (if link-p
  	(error "This is %s, not a real file"
  	       (cond ((eq link-p 5) "a directory")
--- 740,747 ----
  	  nil
  	  (error "This line does not describe a tar-file entry"))))
  
! (defun tar--check-descriptor (descriptor)
!   (let ((link-p (tar-header-link-type descriptor)))
      (if link-p
  	(error "This is %s, not a real file"
  	       (cond ((eq link-p 5) "a directory")
***************
*** 754,763 ****
  		     ((eq link-p 38) "a volume header")
  		     ((eq link-p 55) "a pax global extended header")
  		     ((eq link-p 72) "a pax extended header")
! 		     (t "a link"))))
      (if (zerop size) (message "This is a zero-length file"))
      descriptor))
  
  (defun tar-mouse-extract (event)
    "Extract a file whose tar directory line you click on."
    (interactive "e")
--- 752,775 ----
  		     ((eq link-p 38) "a volume header")
  		     ((eq link-p 55) "a pax global extended header")
  		     ((eq link-p 72) "a pax extended header")
! 		     (t "a link"))))))
! 
! (defun tar-get-descriptor ()
!   (let* ((descriptor (tar-current-descriptor))
! 	 (size (tar-header-size descriptor)))
!     (tar--check-descriptor descriptor)
      (if (zerop size) (message "This is a zero-length file"))
      descriptor))
  
+ (defun tar-get-file-descriptor (file)
+   ;; Used by package.el.
+   (let ((desc ()))
+     (dolist (hdr tar-parse-info)
+       (when (equal file (tar-header-name hdr))
+         (setq desc hdr)))
+     (tar--check-descriptor desc)
+     desc))
+ 
  (defun tar-mouse-extract (event)
    "Extract a file whose tar directory line you click on."
    (interactive "e")
***************
*** 776,816 ****
        (let ((file-name-handler-alist nil))
  	(apply op args))))
  
! (defun tar-extract (&optional other-window-p)
!   "In Tar mode, extract this entry of the tar file into its own buffer."
!   (interactive)
!   (let* ((view-p (eq other-window-p 'view))
! 	 (descriptor (tar-get-descriptor))
! 	 (name (tar-header-name descriptor))
  	 (size (tar-header-size descriptor))
  	 (start (tar-header-data-start descriptor))
! 	 (end (+ start size)))
!     (let* ((tar-buffer (current-buffer))
  	   (tarname (buffer-name))
  	   (bufname (concat (file-name-nondirectory name)
  			    " ("
  			     tarname
  			     ")"))
! 	   (read-only-p (or buffer-read-only view-p))
! 	   (new-buffer-file-name (expand-file-name
! 				  ;; `:' is not allowed on Windows
!                                   (concat tarname "!"
!                                           (if (string-match "/" name)
!                                               name
!                                             ;; Make sure `name' contains a /
!                                             ;; so set-auto-mode doesn't try
!                                             ;; to look at `tarname' for hints.
!                                             (concat "./" name)))))
! 	   (buffer (get-file-buffer new-buffer-file-name))
! 	   (just-created nil)
! 	   undo-list)
!       (unless buffer
! 	(setq buffer (generate-new-buffer bufname))
  	(with-current-buffer buffer
! 	  (setq undo-list buffer-undo-list
! 		buffer-undo-list t))
! 	(setq bufname (buffer-name buffer))
! 	(setq just-created t)
  	(with-current-buffer tar-data-buffer
            (let (coding)
              (narrow-to-region start end)
--- 788,807 ----
        (let ((file-name-handler-alist nil))
  	(apply op args))))
  
! (defun tar--extract (descriptor)
!   "Extract this entry of the tar file into its own buffer."
!   (let* ((name (tar-header-name descriptor))
  	 (size (tar-header-size descriptor))
  	 (start (tar-header-data-start descriptor))
! 	 (end (+ start size))
           (tarname (buffer-name))
           (bufname (concat (file-name-nondirectory name)
                            " ("
                            tarname
                            ")"))
!          (buffer (generate-new-buffer bufname)))
      (with-current-buffer buffer
!       (setq buffer-undo-list t))
      (with-current-buffer tar-data-buffer
        (let (coding)
          (narrow-to-region start end)
***************
*** 839,844 ****
--- 830,860 ----
                (set-buffer-multibyte nil)))
          (widen)
          (decode-coding-region start end coding buffer)))
+     buffer))
+ 
+ (defun tar-extract (&optional other-window-p)
+   "In Tar mode, extract this entry of the tar file into its own buffer."
+   (interactive)
+   (let* ((view-p (eq other-window-p 'view))
+ 	 (descriptor (tar-get-descriptor))
+ 	 (name (tar-header-name descriptor))
+          (tar-buffer (current-buffer))
+          (tarname (buffer-name))
+          (read-only-p (or buffer-read-only view-p))
+          (new-buffer-file-name (expand-file-name
+                                 ;; `:' is not allowed on Windows
+                                 (concat tarname "!"
+                                         (if (string-match "/" name)
+                                             name
+                                           ;; Make sure `name' contains a /
+                                           ;; so set-auto-mode doesn't try
+                                           ;; to look at `tarname' for hints.
+                                           (concat "./" name)))))
+          (buffer (get-file-buffer new-buffer-file-name))
+          (just-created nil))
+     (unless buffer
+       (setq buffer (tar--extract descriptor))
+       (setq just-created t)
        (with-current-buffer buffer
          (goto-char (point-min))
          (setq buffer-file-name new-buffer-file-name)
***************
*** 852,860 ****
            (setq default-directory
                  (with-current-buffer tar-buffer
                    default-directory))
-           (rename-buffer bufname)
            (set-buffer-modified-p nil)
!           (setq buffer-undo-list undo-list)
            (normal-mode)  ; pick a mode.
            (set (make-local-variable 'tar-superior-buffer) tar-buffer)
            (set (make-local-variable 'tar-superior-descriptor) descriptor)
--- 868,875 ----
          (setq default-directory
                (with-current-buffer tar-buffer
                  default-directory))
          (set-buffer-modified-p nil)
!         (setq buffer-undo-list t)
          (normal-mode)                   ; pick a mode.
          (set (make-local-variable 'tar-superior-buffer) tar-buffer)
          (set (make-local-variable 'tar-superior-descriptor) descriptor)
***************
*** 865,871 ****
  	(view-buffer buffer (and just-created 'kill-buffer-if-not-modified)))
         ((eq other-window-p 'display) (display-buffer buffer))
         (other-window-p (switch-to-buffer-other-window buffer))
!        (t (switch-to-buffer buffer))))))

  (defun tar-extract-other-window ()
--- 880,886 ----
        (view-buffer buffer (and just-created 'kill-buffer-if-not-modified)))
       ((eq other-window-p 'display) (display-buffer buffer))
       (other-window-p (switch-to-buffer-other-window buffer))
!      (t (switch-to-buffer buffer)))))

  (defun tar-extract-other-window ()