Files
emacs/user-lisp/magit-ediff-all.el
2026-04-27 18:57:10 -04:00

116 lines
4.2 KiB
EmacsLisp

;;; magit-ediff-all.el --- Compare all changed files between revisions using Ediff -*- lexical-binding:t -*-
;; Copyright (C) 2025 The Magit Project Contributors
;; Author: [Your Name]
;; Maintainer: [Your Name]
;; SPDX-License-Identifier: GPL-3.0-or-later
;;; Commentary:
;; This library extends Magit's ediff support by adding a command to
;; compare all changed files between two revisions using an ediff
;; session group.
;;; Code:
(require 'magit)
(require 'magit-ediff)
(require 'ediff)
(require 'cl-lib)
;; Define the new suffix key and description
(defvar magit-ediff-all-key "A"
"Key binding for the ediff all command in the magit-ediff transient.")
(defvar magit-ediff-all-description "All changed files (between revisions)"
"Description for the ediff all command in the magit-ediff transient.")
(defun magit-ediff-all--write-file (temp-files temp-buffers rev dir file)
(let* ((rel-path (if (file-name-absolute-p file)
(file-relative-name file (magit-toplevel))
file))
(rel-file (expand-file-name rel-path dir))
;; Handle files that are added or removed - use empty buffer
(buf (or (magit-find-file-noselect rev file)
(generate-new-buffer " *empty*"))))
;; Create necessary subdirectories
(let ((dir-part (file-name-directory rel-file)))
(when dir-part
(make-directory dir-part t)))
(push rel-file temp-files)
(push buf temp-buffers)
(with-current-buffer buf
(write-region (point-min) (point-max) rel-file nil 'quiet)
(add-hook 'ediff-prepare-buffer-hook
(lambda ()
(rename-buffer (format "%s~%s~" rel-file rev)))
0 t))))
;;;###autoload
(defun magit-ediff-all-files (revA revB)
"Compare all changed files between REVA and REVB using Ediff.
This creates an Ediff session group that allows you to navigate
through all changed files between the two revisions and compare
them one at a time."
(interactive
(pcase-let ((`(,a ,b) (magit-ediff-compare--read-revisions nil)))
(list a b)))
(when (or (not revA) (not revB))
(user-error "Both revisions must be specified"))
(magit-with-toplevel
(let* ((changed-files (magit-changed-files revA revB))
(dirA (make-temp-file (format "magit-ediff-%s-" revA) t))
(dirB (make-temp-file (format "magit-ediff-%s-" revB) t))
temp-files
temp-buffers)
(message "Preparing %d files for comparison..." (length changed-files))
;; Create temporary files for each revision
(dolist (file changed-files)
(magit-ediff-all--write-file temp-files temp-buffers revA dirA file)
(magit-ediff-all--write-file temp-files temp-buffers revB dirB file))
;; Define cleanup function
(cl-labels
((magit-ediff-all--cleanup ()
(dolist (file temp-files)
(when (file-exists-p file)
(delete-file file)))
;; Kill any buffers visiting files in the temp directories
(dolist (buf (buffer-list))
(when-let ((filename (buffer-file-name buf)))
(when (or (string-prefix-p (expand-file-name dirA) filename)
(string-prefix-p (expand-file-name dirB) filename))
(when (buffer-live-p buf)
(kill-buffer buf)))))
(when (file-exists-p dirA)
(delete-directory dirA t))
(when (file-exists-p dirB)
(delete-directory dirB t))
(dolist (buf temp-buffers)
(when (buffer-live-p buf)
(kill-buffer buf)))
(remove-hook 'ediff-quit-session-group-hook #'magit-ediff-all--cleanup)))
;; Register hooks
(add-hook 'ediff-quit-session-group-hook #'magit-ediff-all--cleanup)
;; Open ediff on the two directories
(message "Opening Ediff session group...")
(ediff-directories dirA dirB nil)))))
;; Add the command to the magit-ediff transient
(with-eval-after-load 'magit-ediff
(transient-append-suffix 'magit-ediff "r"
`(,magit-ediff-all-key ,magit-ediff-all-description magit-ediff-all-files)))
(provide 'magit-ediff-all)
;;; magit-ediff-all.el ends here