aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRuijie Yu2023-03-06 11:03:32 +0800
committerEli Zaretskii2023-04-20 12:26:05 +0300
commitfd4c9246fc8daea4965b868e80e0f2d9d544dc22 (patch)
tree1ae2a962dad71de1ed46e283fa9b19b21aa5c722
parente0c8e4f12fb18695f309b1fd5ff26513ac5611e5 (diff)
downloademacs-fd4c9246fc8daea4965b868e80e0f2d9d544dc22.tar.gz
emacs-fd4c9246fc8daea4965b868e80e0f2d9d544dc22.zip
Handle modifications in extensionless zip files (bug#61326)
* lisp/arc-mode.el (archive-*-write-file-member) (archive-*-expunge): Refactor to correctly modify extensionless zip archives. (archive-expunge): Move implementation to a separate helper function to facilitate testing. (archive--act-files): New helper function to wrap around `call-process' calls. (archive--need-rename-p): New helper function to check whether a temporary rename is necessary. (archive--ensure-extension) (archive--maybe-rename): New helper functions to rename archive if the caller deems it necessary. (archive--with-ensure-extension): New helper function to handle writing an archive while ensuring extensionless archives work correctly by temporarily renaming them. * test/lisp/arc-mode-tests.el (arc-mode-test-zip-ensure-ext): New regression test for bug#61326.
-rw-r--r--lisp/arc-mode.el76
-rw-r--r--test/lisp/arc-mode-tests.el67
2 files changed, 123 insertions, 20 deletions
diff --git a/lisp/arc-mode.el b/lisp/arc-mode.el
index 5e696c091b2..0a971799746 100644
--- a/lisp/arc-mode.el
+++ b/lisp/arc-mode.el
@@ -645,6 +645,49 @@ Does not signal an error if optional argument NOERROR is non-nil."
645 (if (not noerror) 645 (if (not noerror)
646 (error "Line does not describe a member of the archive"))))) 646 (error "Line does not describe a member of the archive")))))
647;; ------------------------------------------------------------------------- 647;; -------------------------------------------------------------------------
648;;; Section: Helper functions for requiring filename extensions
649
650(defun archive--act-files (command files)
651 (lambda (archive)
652 (apply #'call-process (car command)
653 nil nil nil (append (cdr command) (cons archive files)))))
654
655(defun archive--need-rename-p (&optional archive)
656 (let ((archive
657 (file-name-nondirectory (or archive buffer-file-name))))
658 (cl-case archive-subtype
659 ((zip) (not (seq-contains-p archive ?. #'eq))))))
660
661(defun archive--ensure-extension (archive ensure-extension)
662 (if ensure-extension
663 (make-temp-name (expand-file-name (concat archive "_tmp.")))
664 archive))
665
666(defun archive--maybe-rename (newname need-rename-p)
667 ;; Operating with archive as current buffer, and protect
668 ;; `default-directory' from being modified in `rename-visited-file'.
669 (when need-rename-p
670 (let ((default-directory default-directory))
671 (rename-visited-file newname))))
672
673(defun archive--with-ensure-extension (archive proc-fn)
674 (let ((saved default-directory))
675 (with-current-buffer (find-buffer-visiting archive)
676 (let ((ensure-extension (archive--need-rename-p))
677 (default-directory saved))
678 (unwind-protect
679 ;; Some archive programs (like zip) expect filenames to
680 ;; have an extension, so if necessary, temporarily rename
681 ;; an extensionless file for write accesses.
682 (let ((archive (archive--ensure-extension
683 archive ensure-extension)))
684 (archive--maybe-rename archive ensure-extension)
685 (let ((exitcode (funcall proc-fn archive)))
686 (or (zerop exitcode)
687 (error "Updating was unsuccessful (%S)" exitcode))))
688 (progn (archive--maybe-rename archive ensure-extension)
689 (revert-buffer nil t)))))))
690;; -------------------------------------------------------------------------
648;;; Section: the mode definition 691;;; Section: the mode definition
649 692
650;;;###autoload 693;;;###autoload
@@ -1378,16 +1421,9 @@ NEW-NAME."
1378 (setq ename 1421 (setq ename
1379 (encode-coding-string ename archive-file-name-coding-system)) 1422 (encode-coding-string ename archive-file-name-coding-system))
1380 (let* ((coding-system-for-write 'no-conversion) 1423 (let* ((coding-system-for-write 'no-conversion)
1381 (default-directory (file-name-as-directory archive-tmpdir)) 1424 (default-directory (file-name-as-directory archive-tmpdir)))
1382 (exitcode (apply #'call-process 1425 (archive--with-ensure-extension
1383 (car command) 1426 archive (archive--act-files command (list ename)))))
1384 nil
1385 nil
1386 nil
1387 (append (cdr command)
1388 (list archive ename)))))
1389 (or (zerop exitcode)
1390 (error "Updating was unsuccessful (%S)" exitcode))))
1391 (archive-delete-local tmpfile)))) 1427 (archive-delete-local tmpfile))))
1392 1428
1393(defun archive-write-file (&optional file) 1429(defun archive-write-file (&optional file)
@@ -1510,9 +1546,7 @@ as a relative change like \"g+rw\" as for chmod(2)."
1510 (archive-resummarize)) 1546 (archive-resummarize))
1511 (error "Setting group is not supported for this archive type")))) 1547 (error "Setting group is not supported for this archive type"))))
1512 1548
1513(defun archive-expunge () 1549(defun archive--expunge-maybe-force (force)
1514 "Do the flagged deletions."
1515 (interactive)
1516 (let (files) 1550 (let (files)
1517 (save-excursion 1551 (save-excursion
1518 (goto-char archive-file-list-start) 1552 (goto-char archive-file-list-start)
@@ -1526,7 +1560,8 @@ as a relative change like \"g+rw\" as for chmod(2)."
1526 (and files 1560 (and files
1527 (or (not archive-read-only) 1561 (or (not archive-read-only)
1528 (error "Archive is read-only")) 1562 (error "Archive is read-only"))
1529 (or (yes-or-no-p (format "Really delete %d member%s? " 1563 (or force
1564 (yes-or-no-p (format "Really delete %d member%s? "
1530 (length files) 1565 (length files)
1531 (if (null (cdr files)) "" "s"))) 1566 (if (null (cdr files)) "" "s")))
1532 (error "Operation aborted")) 1567 (error "Operation aborted"))
@@ -1540,13 +1575,14 @@ as a relative change like \"g+rw\" as for chmod(2)."
1540 (archive-resummarize) 1575 (archive-resummarize)
1541 (revert-buffer)))))) 1576 (revert-buffer))))))
1542 1577
1578(defun archive-expunge ()
1579 "Do the flagged deletions."
1580 (interactive)
1581 (archive--expunge-maybe-force nil))
1582
1543(defun archive-*-expunge (archive files command) 1583(defun archive-*-expunge (archive files command)
1544 (apply #'call-process 1584 (archive--with-ensure-extension
1545 (car command) 1585 archive (archive--act-files command files)))
1546 nil
1547 nil
1548 nil
1549 (append (cdr command) (cons archive files))))
1550 1586
1551(defun archive-rename-entry (newname) 1587(defun archive-rename-entry (newname)
1552 "Change the name associated with this entry in the archive file." 1588 "Change the name associated with this entry in the archive file."
diff --git a/test/lisp/arc-mode-tests.el b/test/lisp/arc-mode-tests.el
index 32bce1b71bd..b6e06a563fe 100644
--- a/test/lisp/arc-mode-tests.el
+++ b/test/lisp/arc-mode-tests.el
@@ -46,6 +46,73 @@
46 (when (buffer-live-p zip-buffer) (kill-buffer zip-buffer)) 46 (when (buffer-live-p zip-buffer) (kill-buffer zip-buffer))
47 (when (buffer-live-p gz-buffer) (kill-buffer gz-buffer))))) 47 (when (buffer-live-p gz-buffer) (kill-buffer gz-buffer)))))
48 48
49(ert-deftest arc-mode-test-zip-ensure-ext ()
50 "Regression test for bug#61326."
51 (skip-unless (executable-find "zip"))
52 (let* ((default-directory arc-mode-tests-data-directory)
53 (base-zip-1 "base-1.zip")
54 (base-zip-2 "base-2.zip")
55 (content-1 '("1" "2"))
56 (content-2 '("3" "4"))
57 (make-file (lambda (name)
58 (with-temp-buffer
59 (insert name)
60 (write-file name))))
61 (make-zip
62 (lambda (zip files)
63 (delete-file zip nil)
64 (funcall (archive--act-files '("zip") files) zip)))
65 (update-fn
66 (lambda (zip-nonempty)
67 (with-current-buffer (find-file-noselect zip-nonempty)
68 (save-excursion
69 (goto-char archive-file-list-start)
70 (save-current-buffer
71 (archive-extract)
72 (save-excursion
73 (goto-char (point-max))
74 (insert ?a)
75 (save-buffer))
76 (kill-buffer (current-buffer)))
77 (archive-extract)
78 ;; [2] must be ?a; [3] must be (eobp)
79 (should (eq (char-after 2) ?a))
80 (should (eq (point-max) 3))))))
81 (delete-fn
82 (lambda (zip-nonempty)
83 (with-current-buffer (find-file-noselect zip-nonempty)
84 ;; mark delete and expunge first entry
85 (save-excursion
86 (goto-char archive-file-list-start)
87 (should (length= archive-files 2))
88 (archive-flag-deleted 1)
89 (archive--expunge-maybe-force t)
90 (should (length= archive-files 1))))))
91 (test-modify
92 (lambda (zip mod-fn)
93 (let ((zip-base (concat zip ".zip"))
94 (tag (gensym)))
95 (copy-file base-zip-1 zip t)
96 (copy-file base-zip-2 zip-base t)
97 (file-has-changed-p zip tag)
98 (file-has-changed-p zip-base tag)
99 (funcall mod-fn zip)
100 (should-not (file-has-changed-p zip-base tag))
101 (should (file-has-changed-p zip tag))))))
102 ;; setup: make two zip files with different contents
103 (mapc make-file (append content-1 content-2))
104 (mapc (lambda (args) (apply make-zip args))
105 (list (list base-zip-1 content-1)
106 (list base-zip-2 content-2)))
107 ;; test 1: with "test-update" and "test-update.zip", update
108 ;; "test-update": (1) ensure only "test-update" is modified, (2)
109 ;; ensure the contents of the new member is expected.
110 (funcall test-modify "test-update" update-fn)
111 ;; test 2: with "test-delete" and "test-delete.zip", delete entry
112 ;; from "test-delete": (1) ensure only "test-delete" is modified,
113 ;; (2) ensure the file list is reduced as expected.
114 (funcall test-modify "test-delete" delete-fn)))
115
49(provide 'arc-mode-tests) 116(provide 'arc-mode-tests)
50 117
51;;; arc-mode-tests.el ends here 118;;; arc-mode-tests.el ends here