aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonas Bernoulli <jonas@bernoul.li>2026-03-30 22:31:57 +0200
committerJonas Bernoulli <jonas@bernoul.li>2026-03-30 22:31:57 +0200
commitbef6bdc62fc0ffba77dd6a91baaf53bffcbcd5ad (patch)
tree81da8cf675323746362a8a720e9d8651221635b4
parent315d85c765afbce10b08c5c9164da64bc789197a (diff)
Support visiting anonymous blobs
I.e., support visiting a blob via its blob oid without knowing a commit and tree path in which it can be found.
-rw-r--r--lisp/magit-files.el45
-rw-r--r--lisp/magit-git.el12
-rw-r--r--lisp/magit-mode.el1
3 files changed, 37 insertions, 21 deletions
diff --git a/lisp/magit-files.el b/lisp/magit-files.el
index 1a2ee60..f98edee 100644
--- a/lisp/magit-files.el
+++ b/lisp/magit-files.el
@@ -43,6 +43,7 @@
(defvar magit-find-blob-hook (list #'magit-blob-mode))
+(defvar-local magit-buffer-blob-oid--init nil)
(defvar-local magit-buffer--volatile nil)
(put 'magit-buffer--volatile 'permanent-local t)
@@ -90,7 +91,8 @@ the line and column corresponding to that location."
(defun magit-find-file-noselect (rev file &optional no-restore-position volatile)
"Read FILE from REV into a buffer and return the buffer.
-REV is a revision or one of \"{worktree}\" or \"{index}\"."
+REV is a revision or one of \"{worktree}\" or \"{index}\".
+Non-interactively REV can also be a blob object."
(let* ((rev (pcase rev
('nil "{worktree}")
((and "{index}"
@@ -117,7 +119,9 @@ REV is a revision or one of \"{worktree}\" or \"{index}\"."
(error "%s is not inside Git repository %s" file topdir))
(with-current-buffer
(magit--get-blob-buffer rev file-relative volatile)
- (setq magit-buffer-revision rev)
+ (if (magit-blob-p rev)
+ (setq magit-buffer-blob-oid--init (magit-rev-parse rev))
+ (setq magit-buffer-revision rev))
(setq magit-buffer-file-name file)
(setq default-directory
(if (file-exists-p defdir) defdir topdir))
@@ -132,29 +136,33 @@ REV is a revision or one of \"{worktree}\" or \"{index}\"."
(apply #'magit-find-file--restore-position pos))))
buffer))
-(defun magit--get-blob-buffer (rev file &optional volatile)
- ;; REV is assumed to be abbreviated and FILE to be relative.
+(defun magit--get-blob-buffer (obj file &optional volatile)
+ ;; If OBJ is a commit, is assummed to be abbreviated.
+ ;; FILE is assumed to be relative to the top-level.
(cond-let
- ([buf (magit--find-buffer 'magit-buffer-revision rev
- 'magit-buffer-file-name file)]
+ ([buf (if (magit-blob-p obj)
+ (magit--find-buffer 'magit-buffer-blob-oid (magit-rev-parse obj)
+ 'magit-buffer-file-name file)
+ (magit--find-buffer 'magit-buffer-revision obj
+ 'magit-buffer-file-name file))]
(with-current-buffer buf
(when (and (not volatile) magit-buffer--volatile)
(setq magit-buffer--volatile nil)
- (rename-buffer (magit--blob-buffer-name rev file))
+ (rename-buffer (magit--blob-buffer-name obj file))
(magit--blob-cache-remove buf)))
buf)
- ([buf (get-buffer-create (magit--blob-buffer-name rev file volatile))]
+ ([buf (get-buffer-create (magit--blob-buffer-name obj file volatile))]
(with-current-buffer buf
(setq magit-buffer--volatile volatile)
(magit--blob-cache-put buf))
(buffer-enable-undo buf)
buf)))
-(defun magit--blob-buffer-name (rev file &optional volatile)
+(defun magit--blob-buffer-name (obj file &optional volatile)
(format "%s%s.~%s~"
(if volatile " " "")
- file
- (subst-char-in-string ?/ ?_ rev)))
+ (or file (and (magit-blob-p obj) "{blob}"))
+ (subst-char-in-string ?/ ?_ obj)))
(defun magit--revert-blob-buffer (_ignore-auto _noconfirm)
(let ((pos (magit-find-file--position)))
@@ -163,10 +171,15 @@ REV is a revision or one of \"{worktree}\" or \"{index}\"."
(defun magit--refresh-blob-buffer (&optional force)
(let ((old-blob-oid magit-buffer-blob-oid))
- (setq magit-buffer-revision-oid
- (magit-commit-oid magit-buffer-revision t))
- (setq magit-buffer-blob-oid
- (magit-blob-oid magit-buffer-revision magit-buffer-file-name))
+ (cond
+ (magit-buffer-revision
+ (setq magit-buffer-revision-oid
+ (magit-commit-oid magit-buffer-revision t))
+ (setq magit-buffer-blob-oid
+ (magit-blob-oid magit-buffer-revision magit-buffer-file-name)))
+ (magit-buffer-blob-oid--init
+ (setq magit-buffer-blob-oid magit-buffer-blob-oid--init)
+ (setq magit-buffer-blob-oid--init nil)))
(when (or force (not (equal old-blob-oid magit-buffer-blob-oid)))
(let ((inhibit-read-only t))
(erase-buffer)
@@ -308,7 +321,7 @@ Age is tracked in seconds. If nil, only use `magit--blob-cache-limit'.")
(define-advice lsp (:around (fn &rest args) magit-find-file)
"Do nothing when visiting blob using `magit-find-file' and similar.
See also https://github.com/doomemacs/doomemacs/pull/6309."
- (unless magit-buffer-revision
+ (unless magit-buffer-blob-oid
(apply fn args)))
;;; Update Index
diff --git a/lisp/magit-git.el b/lisp/magit-git.el
index 7a52a52..d8d9d83 100644
--- a/lisp/magit-git.el
+++ b/lisp/magit-git.el
@@ -1349,12 +1349,14 @@ Sorted from longest to shortest CYGWIN name."
(magit-git-lines "ls-files" "--stage" "--"
(magit-convert-filename-for-git file))))
-(defun magit--insert-blob-contents (rev file)
+(defun magit--insert-blob-contents (obj file)
(let ((coding-system-for-read (or coding-system-for-read 'undecided)))
- (magit-git-insert "cat-file" "-p"
- (if (equal rev "{index}")
- (concat ":" file)
- (concat rev ":" file)))
+ (if (magit-blob-p obj)
+ (magit-git-insert "cat-file" "blob" obj)
+ (magit-git-insert "cat-file" "-p"
+ (if (equal obj "{index}")
+ (concat ":" file)
+ (concat obj ":" file))))
(setq buffer-file-coding-system last-coding-system-used)
nil))
diff --git a/lisp/magit-mode.el b/lisp/magit-mode.el
index fa82632..00ee776 100644
--- a/lisp/magit-mode.el
+++ b/lisp/magit-mode.el
@@ -625,6 +625,7 @@ In an indirect buffer get the value for its base buffer."
(defun magit-buffer-revision ()
"Return `magit-buffer-revision' or if that is nil \"{worktree}\".
If not visiting a blob or file, or the file isn't being tracked,
+return nil. If visiting a blob but `magit-buffer-revision' is nil,
return nil."
(or magit-buffer-revision
(and buffer-file-name