Skip to content

Commit a31f1a5

Browse files
committed
Support using reference revision as project-local
* diff-hl.el (diff-hl-mode): Set and clear buffer-local value of the reference revision, if the buffur belongs to a project with the custom reference revision, set by diff-hl-set-reference-rev-in-project. (diff-hl-reference-revision): Make it safe if string or nil. (diff-hl-set-reference-rev): Add resetting options when called with the prefix argument. (diff-hl-set-reference-rev-in-project): Add new command. (diff-hl-set-reference-rev-in-project-internal): New internal function. (diff-hl-reset-reference-rev): Add logic for calling with the prefix argument and turn off amend-mode. (diff-hl-reset-reference-rev-in-project): Add new command. (diff-hl--project-root): Add new internal function.
1 parent c1cecb2 commit a31f1a5

1 file changed

Lines changed: 165 additions & 26 deletions

File tree

diff-hl.el

Lines changed: 165 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -270,12 +270,22 @@ and passed the value `default-directory'.
270270
If any returns non-nil, `diff-hl-update' will run synchronously anyway."
271271
:type '(repeat :tag "Predicate" function))
272272

273+
(defvar diff-hl-reference-revision-projects-cache '()
274+
"Alist of cached directory roots for per-project reference revisions.
275+
Each element in this list has the form (DIR . REV).
276+
DIR is the expanded name of the directory.
277+
REV is the current reference revision.")
278+
273279
(defvar diff-hl-reference-revision nil
274280
"Revision to diff against. nil means the most recent one.
275281
276282
It can be a relative expression as well, such as \"HEAD^\" with Git, or
277283
\"-2\" with Mercurial.")
278284

285+
(put 'diff-hl-reference-revision 'safe-local-variable
286+
(lambda (value)
287+
(or (null value) (stringp value))))
288+
279289
(defun diff-hl-define-bitmaps ()
280290
(let* ((scale (if (and (boundp 'text-scale-mode-amount)
281291
(numberp text-scale-mode-amount))
@@ -1257,7 +1267,13 @@ The value of this variable is a mode line template as in
12571267
;; Magit versions 2.0-2.3 don't do the above and call this
12581268
;; instead, but only when they don't call `revert-buffer':
12591269
(add-hook 'magit-not-reverted-hook 'diff-hl-update nil t)
1260-
(add-hook 'text-scale-mode-hook 'diff-hl-maybe-redefine-bitmaps nil t))
1270+
(add-hook 'text-scale-mode-hook 'diff-hl-maybe-redefine-bitmaps nil t)
1271+
(when-let* ((rev (map-some
1272+
(lambda (root rev)
1273+
(when (string-prefix-p root default-directory)
1274+
rev))
1275+
diff-hl-reference-revision-projects-cache)))
1276+
(setq-local diff-hl-reference-revision rev)))
12611277
(remove-hook 'after-save-hook 'diff-hl-update t)
12621278
(remove-hook 'after-change-functions 'diff-hl-edit t)
12631279
(remove-hook 'find-file-hook 'diff-hl-update-once t)
@@ -1266,7 +1282,8 @@ The value of this variable is a mode line template as in
12661282
(remove-hook 'magit-not-reverted-hook 'diff-hl-update t)
12671283
(remove-hook 'text-scale-mode-hook 'diff-hl-maybe-redefine-bitmaps t)
12681284
(diff-hl-remove-overlays)
1269-
(diff-hl--autohide-margin)))
1285+
(diff-hl--autohide-margin)
1286+
(kill-local-variable 'diff-hl-reference-revision)))
12701287

12711288
(defun diff-hl-after-checkin ()
12721289
(let ((fileset (vc-deduce-fileset t)))
@@ -1517,6 +1534,11 @@ CONTEXT-LINES is the size of the unified diff context, defaults to 0."
15171534
"Set the reference revision globally to REV.
15181535
When called interactively, REV read with completion.
15191536
1537+
When called with a prefix argument, reset the global reference to the most
1538+
recent one instead. With two prefix arguments, do the same and discard
1539+
every per-project reference created by
1540+
`diff-hl-set-reference-rev-in-project`.
1541+
15201542
The default value chosen using one of methods below:
15211543
15221544
- In a log view buffer, it uses the revision of current entry.
@@ -1525,42 +1547,159 @@ view buffer.
15251547
- In a VC annotate buffer, it uses the revision of current line.
15261548
- In other situations, it uses the symbol at point.
15271549
1528-
Notice that this sets the reference revision globally, so in
1529-
files from other repositories, `diff-hl-mode' will not highlight
1530-
changes correctly, until you run `diff-hl-reset-reference-rev'.
1550+
Notice that this sets the reference revision globally, so in files from
1551+
other repositories, `diff-hl-mode' will not highlight changes correctly,
1552+
until you run `diff-hl-reset-reference-rev'. To set the reference on a
1553+
per-project basis, see `diff-hl-set-reference-rev-in-project`.
15311554
15321555
Also notice that this will disable `diff-hl-amend-mode' in
15331556
buffers that enables it, since `diff-hl-amend-mode' overrides its
15341557
effect."
15351558
(interactive
1536-
(let* ((def (or (and (equal major-mode 'vc-annotate-mode)
1537-
(car (vc-annotate-extract-revision-at-line)))
1538-
(log-view-current-tag)
1539-
(thing-at-point 'symbol t)))
1540-
(prompt (if def
1541-
(format "Reference revision (default %s): " def)
1542-
"Reference revision: ")))
1543-
(list (vc-read-revision prompt nil nil def))))
1544-
(if rev
1545-
(message "Set reference revision to %s" rev)
1559+
(if current-prefix-arg current-prefix-arg
1560+
(let* ((def (or (and (equal major-mode 'vc-annotate-mode)
1561+
(car (vc-annotate-extract-revision-at-line)))
1562+
(log-view-current-tag)
1563+
(thing-at-point 'symbol t)))
1564+
(prompt (if def
1565+
(format "Reference revision (default %s): " def)
1566+
"Reference revision: ")))
1567+
(list (vc-read-revision prompt nil nil def)))))
1568+
(unless rev
15461569
(user-error "No reference revision specified"))
1547-
(setq diff-hl-reference-revision rev)
1570+
(cond
1571+
((equal '(4) current-prefix-arg)
1572+
;; reset global value
1573+
(diff-hl-reset-reference-rev))
1574+
((equal '(16) current-prefix-arg)
1575+
;; reset global value and remove per-project value
1576+
(diff-hl-reset-reference-rev '(4)))
1577+
;; change only global value
1578+
(t (setq-default diff-hl-reference-revision rev)
1579+
(unless current-prefix-arg
1580+
(message "Set global reference revision to %s" rev))
1581+
(dolist (buf (buffer-list))
1582+
(with-current-buffer buf
1583+
(when diff-hl-mode
1584+
(when (bound-and-true-p diff-hl-amend-mode)
1585+
(diff-hl-amend-mode -1))
1586+
(when (not (local-variable-p 'diff-hl-reference-revision))
1587+
(diff-hl-update))))))))
1588+
1589+
;;;###autoload
1590+
(defun diff-hl-set-reference-rev-in-project (rev)
1591+
"Set the reference revision in the current project to REV.
1592+
When called interactively, REV read with completion.
1593+
1594+
When called with a prefix argument, reset to the global value instead.
1595+
1596+
The default value chosen using one of methods below:
1597+
1598+
- In a log view buffer, it uses the revision of current entry.
1599+
Call `vc-print-log' or `vc-print-root-log' first to open a log
1600+
view buffer.
1601+
- In a VC annotate buffer, it uses the revision of current line.
1602+
- In other situations, it uses the symbol at point.
1603+
1604+
Projects whose reference was set with this command are unaffected by
1605+
subsequent changes to the global reference (see
1606+
`diff-hl-set-reference-rev`).
1607+
1608+
Also notice that this will disable `diff-hl-amend-mode' in
1609+
buffers that enables it, since `diff-hl-amend-mode' overrides its
1610+
effect."
1611+
(interactive
1612+
(if current-prefix-arg current-prefix-arg
1613+
(let* ((def (or (and (equal major-mode 'vc-annotate-mode)
1614+
(car (vc-annotate-extract-revision-at-line)))
1615+
(log-view-current-tag)
1616+
(thing-at-point 'symbol t)))
1617+
(prompt (if def
1618+
(format "Reference revision (default %s): " def)
1619+
"Reference revision: ")))
1620+
(list (vc-read-revision prompt nil nil def)))))
1621+
(unless rev
1622+
(user-error "No reference revision specified"))
1623+
(let* ((proj (project-current))
1624+
(name (project-name proj)))
1625+
(cond
1626+
;; reset
1627+
(current-prefix-arg
1628+
(diff-hl-reset-reference-rev-in-project proj))
1629+
;; set
1630+
(t
1631+
(diff-hl-set-reference-rev-in-project-internal rev proj)
1632+
(message "Showing changes against %s (project %s)" rev name)))))
1633+
1634+
(defun diff-hl--project-root (proj)
1635+
;; Emacs 26 and 27 don't have `project-root'.
1636+
(expand-file-name
1637+
(or (and (fboundp 'project-root) (project-root proj))
1638+
(project-roots proj))))
1639+
1640+
(defun diff-hl-set-reference-rev-in-project-internal (rev proj)
1641+
(let* ((root (diff-hl--project-root proj)))
1642+
;; newly opened files will share this value
1643+
(setf (alist-get root diff-hl-reference-revision-projects-cache
1644+
nil nil #'string-equal)
1645+
rev)
1646+
;; update currently open files
1647+
(dolist (buf (project-buffers proj))
1648+
(with-current-buffer buf
1649+
(when diff-hl-mode
1650+
(when (bound-and-true-p diff-hl-amend-mode)
1651+
(diff-hl-amend-mode -1))
1652+
(setq-local diff-hl-reference-revision rev)
1653+
(diff-hl-update))))))
1654+
1655+
;;;###autoload
1656+
(defun diff-hl-reset-reference-rev (&optional arg)
1657+
"Reset the reference revision globally to the most recent one.
1658+
1659+
When called with a prefix argument, do the same and discard every
1660+
per-project reference created by `diff-hl-set-reference-rev-in-project'."
1661+
(interactive "P")
1662+
(setq-default diff-hl-reference-revision nil)
1663+
(when arg
1664+
;; reset all cache
1665+
(setq diff-hl-reference-revision-projects-cache nil))
15481666
(dolist (buf (buffer-list))
15491667
(with-current-buffer buf
15501668
(when diff-hl-mode
1669+
(when arg
1670+
;; reset value in buffers
1671+
(kill-local-variable 'diff-hl-reference-revision))
15511672
(when (bound-and-true-p diff-hl-amend-mode)
15521673
(diff-hl-amend-mode -1))
1553-
(diff-hl-update)))))
1554-
1555-
;;;###autoload
1556-
(defun diff-hl-reset-reference-rev ()
1557-
"Reset the reference revision globally to the most recent one."
1674+
;; Don't touch buffers with the local reference (set by
1675+
;; `diff-hl-set-reference-rev-in-project' ), when called without a
1676+
;; prefix.
1677+
(unless (local-variable-p 'diff-hl-reference-revision)
1678+
(diff-hl-update)))))
1679+
(message "Reference revision reset globally to the most recent revision"))
1680+
1681+
(defun diff-hl-reset-reference-rev-in-project (&optional proj)
1682+
"Reset the reference revision in the project PROJ to the
1683+
global value.
1684+
1685+
PROJ defaults to the current project."
15581686
(interactive)
1559-
(setq diff-hl-reference-revision nil)
1560-
(dolist (buf (buffer-list))
1561-
(with-current-buffer buf
1562-
(when diff-hl-mode
1563-
(diff-hl-update)))))
1687+
(when-let* ((proj (or proj (project-current))))
1688+
;; reset cache for the project
1689+
(setq diff-hl-reference-revision-projects-cache
1690+
(assoc-delete-all (diff-hl--project-root proj)
1691+
diff-hl-reference-revision-projects-cache
1692+
#'string-equal))
1693+
;; reset value in project buffers
1694+
(dolist (buf (project-buffers proj))
1695+
(with-current-buffer buf
1696+
(when diff-hl-mode
1697+
(when (bound-and-true-p diff-hl-amend-mode)
1698+
(diff-hl-amend-mode -1))
1699+
(kill-local-variable 'diff-hl-reference-revision)
1700+
(diff-hl-update))))
1701+
(message "Reference revision reset to the global value (project %s)"
1702+
(project-name proj))))
15641703

15651704
;;;###autoload
15661705
(define-globalized-minor-mode global-diff-hl-mode diff-hl-mode

0 commit comments

Comments
 (0)