mirror of
git://git.sv.gnu.org/emacs.git
synced 2025-12-05 22:20:24 -08:00
Compare commits
17 commits
c2cb66ffd3
...
ef903e0f5a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef903e0f5a | ||
|
|
c499c2f67b | ||
|
|
9f2b1c43c9 | ||
|
|
1677c4681a | ||
|
|
917f5e25de | ||
|
|
577821f143 | ||
|
|
ad8ced8bbb | ||
|
|
029d87a810 | ||
|
|
19f0b0e1e8 | ||
|
|
5f63dc6d85 | ||
|
|
b69152ea75 | ||
|
|
d65423306a | ||
|
|
ae4416f8f7 | ||
|
|
a74b693683 | ||
|
|
5c0d2ca79a | ||
|
|
821b63eef7 | ||
|
|
cb2e9dd483 |
15 changed files with 426 additions and 87 deletions
|
|
@ -4016,7 +4016,7 @@ Various spell-checkers are compatible with Emacs, including:
|
||||||
@uref{http://aspell.net/}
|
@uref{http://aspell.net/}
|
||||||
|
|
||||||
@item Ispell
|
@item Ispell
|
||||||
@uref{http://fmg-www.cs.ucla.edu/geoff/ispell.html}
|
@uref{https://www.cs.hmc.edu/~geoff/ispell.html}
|
||||||
|
|
||||||
@item Enchant
|
@item Enchant
|
||||||
@uref{https://abiword.github.io/enchant/}
|
@uref{https://abiword.github.io/enchant/}
|
||||||
|
|
|
||||||
|
|
@ -945,6 +945,9 @@ since it could result in memory overflow and make Emacs crash."
|
||||||
(fboundp 'new-fontset))
|
(fboundp 'new-fontset))
|
||||||
((string-match "xwidget-" (symbol-name symbol))
|
((string-match "xwidget-" (symbol-name symbol))
|
||||||
(boundp 'xwidget-internal))
|
(boundp 'xwidget-internal))
|
||||||
|
((string-match "treesit-" (symbol-name symbol))
|
||||||
|
;; Any function from treesit.c will do.
|
||||||
|
(fboundp 'treesit-language-available-p))
|
||||||
(t t))))
|
(t t))))
|
||||||
(if (not (boundp symbol))
|
(if (not (boundp symbol))
|
||||||
;; If variables are removed from C code, give an error here!
|
;; If variables are removed from C code, give an error here!
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,7 @@ the result of calling FUNCTION with zero arguments. This is the
|
||||||
only case where FUNCTION is called with fewer than two arguments.
|
only case where FUNCTION is called with fewer than two arguments.
|
||||||
|
|
||||||
If SEQ contains exactly one element and no :INITIAL-VALUE is
|
If SEQ contains exactly one element and no :INITIAL-VALUE is
|
||||||
specified, then return that element and FUNCTION is not called.
|
specified, then just return that element wihout calling FUNCTION.
|
||||||
|
|
||||||
If :FROM-END is non-nil, the reduction occurs from the back of
|
If :FROM-END is non-nil, the reduction occurs from the back of
|
||||||
the SEQ moving forward, and the order of arguments to the
|
the SEQ moving forward, and the order of arguments to the
|
||||||
|
|
|
||||||
|
|
@ -385,8 +385,7 @@ third element of SEQUENCE, etc. FUNCTION will be called with
|
||||||
INITIAL-VALUE (and then the accumulated value) as the first
|
INITIAL-VALUE (and then the accumulated value) as the first
|
||||||
argument, and the elements from SEQUENCE as the second argument.
|
argument, and the elements from SEQUENCE as the second argument.
|
||||||
|
|
||||||
If SEQUENCE is empty, return INITIAL-VALUE and FUNCTION is not called.
|
If SEQUENCE is empty, return INITIAL-VALUE without calling FUNCTION.
|
||||||
|
|
||||||
This does not modify SEQUENCE."
|
This does not modify SEQUENCE."
|
||||||
(if (seq-empty-p sequence)
|
(if (seq-empty-p sequence)
|
||||||
initial-value
|
initial-value
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,10 @@ of face attribute/value pairs. If more than one face is listed,
|
||||||
that specifies an aggregate face, in the same way as in a `face'
|
that specifies an aggregate face, in the same way as in a `face'
|
||||||
text property, except for possible priority changes noted below.
|
text property, except for possible priority changes noted below.
|
||||||
|
|
||||||
|
If a face property list specifies `:font', the value should be
|
||||||
|
either a font-spec object or the return value of `font-face-attributes'
|
||||||
|
called with a font object, font spec, or font entity.
|
||||||
|
|
||||||
The face remapping specified by SPECS takes effect alongside the
|
The face remapping specified by SPECS takes effect alongside the
|
||||||
remappings from other calls to `face-remap-add-relative' for the
|
remappings from other calls to `face-remap-add-relative' for the
|
||||||
same FACE, as well as the normal definition of FACE (at lowest
|
same FACE, as well as the normal definition of FACE (at lowest
|
||||||
|
|
@ -192,6 +196,10 @@ The remaining arguments, SPECS, specify the base of the remapping.
|
||||||
Each one of SPECS should be either a face name or a property list
|
Each one of SPECS should be either a face name or a property list
|
||||||
of face attribute/value pairs, like in a `face' text property.
|
of face attribute/value pairs, like in a `face' text property.
|
||||||
|
|
||||||
|
If a face property list specifies `:font', the value should be
|
||||||
|
either a font-spec object or the return value of `font-face-attributes'
|
||||||
|
called with a font object, font spec, or font entity.
|
||||||
|
|
||||||
If SPECS is empty or a single face `eq' to FACE, call `face-remap-reset-base'
|
If SPECS is empty or a single face `eq' to FACE, call `face-remap-reset-base'
|
||||||
to use the normal definition of FACE as the base remapping; note that
|
to use the normal definition of FACE as the base remapping; note that
|
||||||
this is different from SPECS containing a single value nil, which means
|
this is different from SPECS containing a single value nil, which means
|
||||||
|
|
@ -572,6 +580,10 @@ one face is listed, that specifies an aggregate face, like in a
|
||||||
`face' text property. If SPECS is nil or omitted, disable
|
`face' text property. If SPECS is nil or omitted, disable
|
||||||
`buffer-face-mode'.
|
`buffer-face-mode'.
|
||||||
|
|
||||||
|
If a face property list specifies `:font', the value should be
|
||||||
|
either a font-spec object or the return value of `font-face-attributes'
|
||||||
|
called with a font object, font spec, or font entity.
|
||||||
|
|
||||||
This function makes the variable `buffer-face-mode-face' buffer
|
This function makes the variable `buffer-face-mode-face' buffer
|
||||||
local, and sets it to FACE."
|
local, and sets it to FACE."
|
||||||
(interactive (list (read-face-name "Set buffer face" (face-at-point t))))
|
(interactive (list (read-face-name "Set buffer face" (face-at-point t))))
|
||||||
|
|
@ -590,6 +602,10 @@ or a property list of face attributes and values. If more than
|
||||||
one face is listed, that specifies an aggregate face, like in a
|
one face is listed, that specifies an aggregate face, like in a
|
||||||
`face' text property.
|
`face' text property.
|
||||||
|
|
||||||
|
If a face property list specifies `:font', the value should be
|
||||||
|
either a font-spec object or the return value of `font-face-attributes'
|
||||||
|
called with a font object, font spec, or font entity.
|
||||||
|
|
||||||
If `buffer-face-mode' is already enabled, and is currently using
|
If `buffer-face-mode' is already enabled, and is currently using
|
||||||
the face specs SPECS, then it is disabled; if `buffer-face-mode'
|
the face specs SPECS, then it is disabled; if `buffer-face-mode'
|
||||||
is disabled, or is enabled and currently displaying some other
|
is disabled, or is enabled and currently displaying some other
|
||||||
|
|
@ -615,6 +631,10 @@ SPECS can be any value suitable for a `face' text property,
|
||||||
including a face name, a plist of face attributes and values,
|
including a face name, a plist of face attributes and values,
|
||||||
or a list of faces.
|
or a list of faces.
|
||||||
|
|
||||||
|
If a face property list specifies `:font', the value should be
|
||||||
|
either a font-spec object or the return value of `font-face-attributes'
|
||||||
|
called with a font object, font spec, or font entity.
|
||||||
|
|
||||||
If INTERACTIVE is non-nil, display a message describing the
|
If INTERACTIVE is non-nil, display a message describing the
|
||||||
result.
|
result.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -251,6 +251,7 @@ automatically)."
|
||||||
'("pylsp" "pyls" ("basedpyright-langserver" "--stdio")
|
'("pylsp" "pyls" ("basedpyright-langserver" "--stdio")
|
||||||
("pyright-langserver" "--stdio")
|
("pyright-langserver" "--stdio")
|
||||||
("pyrefly" "lsp")
|
("pyrefly" "lsp")
|
||||||
|
("ty" "server")
|
||||||
"jedi-language-server" ("ruff" "server") "ruff-lsp")))
|
"jedi-language-server" ("ruff" "server") "ruff-lsp")))
|
||||||
((js-json-mode json-mode json-ts-mode jsonc-mode)
|
((js-json-mode json-mode json-ts-mode jsonc-mode)
|
||||||
. ,(eglot-alternatives '(("vscode-json-language-server" "--stdio")
|
. ,(eglot-alternatives '(("vscode-json-language-server" "--stdio")
|
||||||
|
|
|
||||||
|
|
@ -392,31 +392,37 @@ to customize the information in the time stamp and where it is written."
|
||||||
(let ((nl-start 0))
|
(let ((nl-start 0))
|
||||||
(while (string-match "\n" ts-format nl-start)
|
(while (string-match "\n" ts-format nl-start)
|
||||||
(setq format-lines (1+ format-lines) nl-start (match-end 0)))))
|
(setq format-lines (1+ format-lines) nl-start (match-end 0)))))
|
||||||
(let ((nl-start 0))
|
(cond
|
||||||
(while (string-match "\n" ts-end nl-start)
|
((not (and (stringp ts-start)
|
||||||
(setq end-lines (1+ end-lines) nl-start (match-end 0))))
|
(stringp ts-end)))
|
||||||
;; Find overall what lines to look at
|
(message "time-stamp-start or time-stamp-end is not a string")
|
||||||
(save-excursion
|
(sit-for 1))
|
||||||
(save-restriction
|
(t
|
||||||
(widen)
|
(let ((nl-start 0))
|
||||||
(cond ((> line-limit 0)
|
(while (string-match "\n" ts-end nl-start)
|
||||||
(goto-char (setq start (point-min)))
|
(setq end-lines (1+ end-lines) nl-start (match-end 0))))
|
||||||
(forward-line line-limit)
|
;; Find overall what lines to look at
|
||||||
(setq search-limit (point-marker)))
|
(save-excursion
|
||||||
((< line-limit 0)
|
(save-restriction
|
||||||
(goto-char (setq search-limit (point-max-marker)))
|
(widen)
|
||||||
(forward-line line-limit)
|
(cond ((> line-limit 0)
|
||||||
(setq start (point)))
|
(goto-char (setq start (point-min)))
|
||||||
(t ;0 => no limit (use with care!)
|
(forward-line line-limit)
|
||||||
(setq start (point-min))
|
(setq search-limit (point-marker)))
|
||||||
(setq search-limit (point-max-marker))))))
|
((< line-limit 0)
|
||||||
(while (and start
|
(goto-char (setq search-limit (point-max-marker)))
|
||||||
(< start search-limit)
|
(forward-line line-limit)
|
||||||
(> ts-count 0))
|
(setq start (point)))
|
||||||
(setq start (time-stamp-once start search-limit ts-start ts-end
|
(t ;0 => no limit (use with care!)
|
||||||
ts-format format-lines end-lines))
|
(setq start (point-min))
|
||||||
(setq ts-count (1- ts-count)))
|
(setq search-limit (point-max-marker))))))
|
||||||
(set-marker search-limit nil))
|
(while (and start
|
||||||
|
(< start search-limit)
|
||||||
|
(> ts-count 0))
|
||||||
|
(setq start (time-stamp-once start search-limit ts-start ts-end
|
||||||
|
ts-format format-lines end-lines))
|
||||||
|
(setq ts-count (1- ts-count)))
|
||||||
|
(set-marker search-limit nil))))
|
||||||
nil)
|
nil)
|
||||||
|
|
||||||
(defun time-stamp-once (start search-limit ts-start ts-end
|
(defun time-stamp-once (start search-limit ts-start ts-end
|
||||||
|
|
@ -463,11 +469,8 @@ Returns the end point, which is where `time-stamp' begins the next search."
|
||||||
;; don't signal an error in a hook
|
;; don't signal an error in a hook
|
||||||
(progn
|
(progn
|
||||||
(message "Warning: time-stamp-active is off; did not time-stamp buffer.")
|
(message "Warning: time-stamp-active is off; did not time-stamp buffer.")
|
||||||
(sit-for 1))))
|
(sit-for 1)))
|
||||||
((not (and (stringp ts-start)
|
nil)
|
||||||
(stringp ts-end)))
|
|
||||||
(message "time-stamp-start or time-stamp-end is not a string")
|
|
||||||
(sit-for 1))
|
|
||||||
(t
|
(t
|
||||||
(let ((new-time-stamp (time-stamp-string ts-format)))
|
(let ((new-time-stamp (time-stamp-string ts-format)))
|
||||||
(if (and (stringp new-time-stamp)
|
(if (and (stringp new-time-stamp)
|
||||||
|
|
@ -484,10 +487,9 @@ Returns the end point, which is where `time-stamp' begins the next search."
|
||||||
(if (search-backward "\t" start t)
|
(if (search-backward "\t" start t)
|
||||||
(progn
|
(progn
|
||||||
(untabify start end)
|
(untabify start end)
|
||||||
(setq end (point))))))))))))
|
(setq end (point))))))))
|
||||||
;; return the location after this time stamp, if there was one
|
;; return the location after this time stamp
|
||||||
(and end end-length
|
(+ end (max advance-nudge end-length))))))))
|
||||||
(+ end (max advance-nudge end-length)))))
|
|
||||||
|
|
||||||
|
|
||||||
;;;###autoload
|
;;;###autoload
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,8 @@ in a Emacs not built with tree-sitter library."
|
||||||
(declare-function treesit-available-p "treesit.c")
|
(declare-function treesit-available-p "treesit.c")
|
||||||
|
|
||||||
(defvar treesit-thing-settings)
|
(defvar treesit-thing-settings)
|
||||||
(defvar treesit-major-mode-remap-alist)))
|
(defvar treesit-major-mode-remap-alist)
|
||||||
|
(defvar treesit-extra-load-path)))
|
||||||
|
|
||||||
(treesit-declare-unavailable-functions)
|
(treesit-declare-unavailable-functions)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -980,14 +980,17 @@ data such as \"Index: ...\" and such."
|
||||||
(goto-char orig)
|
(goto-char orig)
|
||||||
(signal (car err) (cdr err)))))
|
(signal (car err) (cdr err)))))
|
||||||
|
|
||||||
(defun diff-file-kill ()
|
(defun diff-file-kill (&optional delete)
|
||||||
"Kill current file's hunks."
|
"Kill current file's hunks.
|
||||||
|
When called from Lisp with optional argument DELETE non-nil, delete
|
||||||
|
them, instead."
|
||||||
(interactive)
|
(interactive)
|
||||||
(if (not (diff--some-hunks-p))
|
(if (not (diff--some-hunks-p))
|
||||||
(error "No hunks")
|
(error "No hunks")
|
||||||
(diff-beginning-of-hunk t)
|
(diff-beginning-of-hunk t)
|
||||||
(let ((inhibit-read-only t))
|
(let ((inhibit-read-only t))
|
||||||
(apply #'kill-region (diff-bounds-of-file)))
|
(apply (if delete #'delete-region #'kill-region)
|
||||||
|
(diff-bounds-of-file)))
|
||||||
(ignore-errors (diff-beginning-of-hunk t))))
|
(ignore-errors (diff-beginning-of-hunk t))))
|
||||||
|
|
||||||
(defun diff-kill-junk ()
|
(defun diff-kill-junk ()
|
||||||
|
|
@ -1052,7 +1055,7 @@ data such as \"Index: ...\" and such."
|
||||||
(defvar diff-remembered-defdir nil)
|
(defvar diff-remembered-defdir nil)
|
||||||
|
|
||||||
(defun diff-filename-drop-dir (file)
|
(defun diff-filename-drop-dir (file)
|
||||||
(when (string-match "/" file) (substring file (match-end 0))))
|
(and (string-match "[/\\]" file) (substring file (match-end 0))))
|
||||||
|
|
||||||
(defun diff-merge-strings (ancestor from to)
|
(defun diff-merge-strings (ancestor from to)
|
||||||
"Merge the diff between ANCESTOR and FROM into TO.
|
"Merge the diff between ANCESTOR and FROM into TO.
|
||||||
|
|
@ -1209,6 +1212,21 @@ Optional arguments OLD and NOPROMPT are passed on to
|
||||||
(ignore-errors (diff-file-next)))
|
(ignore-errors (diff-file-next)))
|
||||||
(point)))))
|
(point)))))
|
||||||
|
|
||||||
|
(defun diff-kill-creations-deletions (&optional delete)
|
||||||
|
"Kill all hunks for file creations and deletions.
|
||||||
|
Optional argument DELETE is passed on to `diff-file-kill'."
|
||||||
|
(save-excursion
|
||||||
|
(cl-loop initially
|
||||||
|
(goto-char (point-min))
|
||||||
|
(ignore-errors (diff-file-next))
|
||||||
|
for (name1 name2) = (diff-hunk-file-names)
|
||||||
|
if (or (equal name1 null-device)
|
||||||
|
(equal name2 null-device))
|
||||||
|
do (diff-file-kill delete)
|
||||||
|
else if (eq (prog1 (point)
|
||||||
|
(ignore-errors (diff-file-next)))
|
||||||
|
(point))
|
||||||
|
do (cl-return))))
|
||||||
|
|
||||||
(defun diff-ediff-patch ()
|
(defun diff-ediff-patch ()
|
||||||
"Call `ediff-patch-file' on the current buffer."
|
"Call `ediff-patch-file' on the current buffer."
|
||||||
|
|
|
||||||
|
|
@ -2340,10 +2340,10 @@ It is an error if REV is not on the current branch."
|
||||||
(vc-git-command nil 0 nil "reset" "--hard" rev))
|
(vc-git-command nil 0 nil "reset" "--hard" rev))
|
||||||
|
|
||||||
(defun vc-git-uncommit-revisions-from-end (rev)
|
(defun vc-git-uncommit-revisions-from-end (rev)
|
||||||
"Soft reset back to REV.
|
"Mixed reset back to REV.
|
||||||
It is an error if REV is not on the current branch."
|
It is an error if REV is not on the current branch."
|
||||||
(vc-git--assert-revision-on-branch rev (vc-git--current-branch))
|
(vc-git--assert-revision-on-branch rev (vc-git--current-branch))
|
||||||
(vc-git-command nil 0 nil "reset" "--soft" rev))
|
(vc-git-command nil 0 nil "reset" "--mixed" rev))
|
||||||
|
|
||||||
(defvar vc-git-extra-menu-map
|
(defvar vc-git-extra-menu-map
|
||||||
(let ((map (make-sparse-keymap)))
|
(let ((map (make-sparse-keymap)))
|
||||||
|
|
|
||||||
|
|
@ -175,7 +175,8 @@ A value of `default' means to use the value of `vc-resolve-conflicts'."
|
||||||
"SVN-specific version of `vc-state'."
|
"SVN-specific version of `vc-state'."
|
||||||
(let (process-file-side-effects)
|
(let (process-file-side-effects)
|
||||||
(with-temp-buffer
|
(with-temp-buffer
|
||||||
(cd (file-name-directory file))
|
(when-let* ((d (file-name-directory file)))
|
||||||
|
(cd d))
|
||||||
(vc-svn-command t 0 file "status" "-v")
|
(vc-svn-command t 0 file "status" "-v")
|
||||||
(vc-svn-parse-status file))))
|
(vc-svn-parse-status file))))
|
||||||
|
|
||||||
|
|
|
||||||
189
lisp/vc/vc.el
189
lisp/vc/vc.el
|
|
@ -5268,6 +5268,34 @@ option to non-nil to skip the prompting."
|
||||||
:group 'vc
|
:group 'vc
|
||||||
:version "31.1")
|
:version "31.1")
|
||||||
|
|
||||||
|
(defun vc--fileset-by-state (fileset)
|
||||||
|
"Return alist of VC states of all files in FILESET.
|
||||||
|
The keys into the alist are VC states, and the values are file names.
|
||||||
|
For directories in FILESET, the alist includes values for all
|
||||||
|
non-ignored, non-up-to-date files within those directories."
|
||||||
|
(let ((backend (car fileset))
|
||||||
|
(remaining (cadr fileset))
|
||||||
|
ret-val)
|
||||||
|
(while remaining
|
||||||
|
(cond* ((bind* (next (pop remaining))))
|
||||||
|
((atom next)
|
||||||
|
(push next (alist-get (vc-state next backend) ret-val)))
|
||||||
|
((bind* (file (car next))))
|
||||||
|
((file-directory-p file)
|
||||||
|
(setq remaining
|
||||||
|
(nconc (vc-dir-status-files file nil backend)
|
||||||
|
remaining)))
|
||||||
|
(t
|
||||||
|
(push file (alist-get (cadr next) ret-val)))))
|
||||||
|
ret-val))
|
||||||
|
|
||||||
|
(declare-function diff-kill-creations-deletions "diff-mode")
|
||||||
|
(declare-function diff-filename-drop-dir "diff-mode")
|
||||||
|
(declare-function diff-hunk-file-names "diff-mode")
|
||||||
|
(declare-function diff-file-next "diff-mode")
|
||||||
|
(defvar diff-hunk-header-re)
|
||||||
|
(declare-function vc-dir-resynch-file "vc-dir")
|
||||||
|
|
||||||
(defun vc--apply-to-other-working-tree
|
(defun vc--apply-to-other-working-tree
|
||||||
(directory mirror-dir fileset patch-string move)
|
(directory mirror-dir fileset patch-string move)
|
||||||
"Workhorse routine for copying/moving changes to other working trees.
|
"Workhorse routine for copying/moving changes to other working trees.
|
||||||
|
|
@ -5285,37 +5313,136 @@ MOVE non-nil means to move instead of copy."
|
||||||
(propertize "move" 'face 'bold))))
|
(propertize "move" 'face 'bold))))
|
||||||
(user-error "Aborted"))
|
(user-error "Aborted"))
|
||||||
(vc-buffer-sync-fileset fileset nil)
|
(vc-buffer-sync-fileset fileset nil)
|
||||||
(with-temp-buffer
|
(let* ((fileset (cl-list* (car fileset)
|
||||||
(if (not patch-string)
|
(mapcar #'file-relative-name (cadr fileset))
|
||||||
(let ((display-buffer-overriding-action '(display-buffer-no-window
|
(cddr fileset)))
|
||||||
(allow-no-window . t))))
|
(backend (car fileset))
|
||||||
(vc-diff-internal nil fileset nil nil nil (current-buffer)))
|
(by-state (vc--fileset-by-state fileset))
|
||||||
(diff-mode)
|
(copies (append (alist-get 'added by-state)
|
||||||
(insert patch-string))
|
(alist-get 'unregistered by-state)))
|
||||||
(let ((default-directory mirror-dir))
|
(deletions (append (alist-get 'removed by-state)
|
||||||
(vc-buffer-sync-fileset (diff-vc-deduce-fileset) nil))
|
(alist-get 'missing by-state)))
|
||||||
(when-let* (move
|
(whole-files (append copies deletions))
|
||||||
(failed (diff-apply-buffer nil nil 'reverse 'test)))
|
(orig-dd default-directory)
|
||||||
;; If PATCH-STRING is non-nil and this fails, the user called us
|
non-empty-patch-p)
|
||||||
;; from a `diff-mode' buffer that doesn't reverse-apply; that's
|
(with-temp-buffer
|
||||||
;; a `user-error'.
|
(cond* (patch-string
|
||||||
;; If PATCH-STRING is nil and this fails, `vc-diff-internal'
|
(diff-mode)
|
||||||
;; generated a nonsense diff -- not the user's fault.
|
(insert patch-string))
|
||||||
(funcall (if patch-string #'user-error #'error)
|
;; Some backends don't tolerate unregistered files
|
||||||
(ngettext "%d hunk does not reverse-apply to this working tree"
|
;; appearing in the fileset for a diff operation.
|
||||||
"%d hunks do not reverse-apply to this working tree"
|
((bind* (diff-fileset
|
||||||
failed)
|
`(,backend ,(cl-set-difference
|
||||||
failed))
|
(cadr fileset)
|
||||||
(let ((default-directory mirror-dir))
|
(alist-get 'unregistered by-state))))))
|
||||||
(when-let* ((failed (diff-apply-buffer)))
|
;; An empty files list makes `vc-diff-internal' diff the
|
||||||
(user-error (ngettext "%d hunk does not apply to `%s'"
|
;; whole of `default-directory'.
|
||||||
"%d hunks do not apply to `%s'"
|
((cadr diff-fileset)
|
||||||
failed)
|
(cl-letf ((display-buffer-overriding-action
|
||||||
failed directory)))
|
'(display-buffer-no-window (allow-no-window . t)))
|
||||||
(when move
|
;; Try to disable, e.g., Git's rename detection.
|
||||||
(diff-apply-buffer nil nil 'reverse))
|
((symbol-value (vc-make-backend-sym backend
|
||||||
(message "Changes %s to `%s'"
|
'diff-switches))
|
||||||
(if move "moved" "applied") directory)))
|
t))
|
||||||
|
(vc-diff-internal nil diff-fileset nil nil nil
|
||||||
|
(current-buffer))))
|
||||||
|
(t (require 'diff-mode)))
|
||||||
|
;; We'll handle any `added', `removed', `missing' and
|
||||||
|
;; `unregistered' files in FILESET by copying or moving whole
|
||||||
|
;; files, so remove any of them that show up in the diff
|
||||||
|
;; (only `added' and `removed' should actually show up).
|
||||||
|
(diff-kill-creations-deletions t)
|
||||||
|
(goto-char (point-min))
|
||||||
|
(if (not (setq non-empty-patch-p
|
||||||
|
(re-search-forward diff-hunk-header-re nil t)))
|
||||||
|
;; No hunks, so just sync WHOLE-FILES and skip over testing
|
||||||
|
;; reverse-application to the source working tree.
|
||||||
|
(let ((default-directory mirror-dir))
|
||||||
|
(vc-buffer-sync-fileset `(,backend ,whole-files) nil))
|
||||||
|
;; We cannot deal with renames, copies, and combinations of
|
||||||
|
;; renames and copies with ordinary changes detected by the VCS.
|
||||||
|
;; If we called `vc-diff-internal' just above then there shouldn't
|
||||||
|
;; be any, but check to make sure. And if PATCH-STRING is non-nil
|
||||||
|
;; then we definitely need to check there aren't any.
|
||||||
|
;;
|
||||||
|
;; In order to be able to support these kinds of things, then
|
||||||
|
;; rather than do it entirely ad hoc here, we probably want new
|
||||||
|
;; VC states representing renames and copies.
|
||||||
|
;; There is an old FIXME about this in `vc-state'. --spwhitton
|
||||||
|
(cl-loop initially
|
||||||
|
(goto-char (point-min))
|
||||||
|
(ignore-errors (diff-file-next))
|
||||||
|
for (name1 name2) = (diff-hunk-file-names)
|
||||||
|
for name1* = (or (diff-filename-drop-dir name1) name1)
|
||||||
|
and name2* = (or (diff-filename-drop-dir name2) name2)
|
||||||
|
unless (equal name1* name2*)
|
||||||
|
do (funcall (if patch-string #'user-error #'error)
|
||||||
|
(format "Cannot %s renames and/or copies"
|
||||||
|
(if move "move" "apply")))
|
||||||
|
until (eq (prog1 (point)
|
||||||
|
(ignore-errors (diff-file-next)))
|
||||||
|
(point)))
|
||||||
|
(let* ((default-directory mirror-dir)
|
||||||
|
(sync-fileset (diff-vc-deduce-fileset)))
|
||||||
|
(rplacd (last (cadr sync-fileset)) whole-files)
|
||||||
|
(vc-buffer-sync-fileset sync-fileset nil))
|
||||||
|
(when-let* (move
|
||||||
|
(failed (diff-apply-buffer nil nil 'reverse 'test)))
|
||||||
|
;; If PATCH-STRING is non-nil and this fails, the user called us
|
||||||
|
;; from a `diff-mode' buffer that doesn't reverse-apply; that's
|
||||||
|
;; a `user-error'.
|
||||||
|
;; If PATCH-STRING is nil and this fails, `vc-diff-internal'
|
||||||
|
;; generated a nonsense diff -- not the user's fault.
|
||||||
|
(funcall
|
||||||
|
(if patch-string #'user-error #'error)
|
||||||
|
(ngettext "%d hunk does not reverse-apply to this working tree"
|
||||||
|
"%d hunks do not reverse-apply to this working tree"
|
||||||
|
failed)
|
||||||
|
failed)))
|
||||||
|
(let ((default-directory mirror-dir)
|
||||||
|
(mirror-states (make-hash-table :test #'equal)))
|
||||||
|
(pcase-dolist (`(,file ,state . ,_)
|
||||||
|
(vc-dir-status-files mirror-dir nil backend))
|
||||||
|
(puthash file state mirror-states))
|
||||||
|
(dolist (copy copies)
|
||||||
|
(when (file-exists-p copy)
|
||||||
|
(user-error "`%s' already exists in `%s'"
|
||||||
|
copy mirror-dir)))
|
||||||
|
(dolist (deletion deletions)
|
||||||
|
(when (memq (gethash deletion mirror-states)
|
||||||
|
'(edited needs-merge unlocked-changes added
|
||||||
|
conflict unregistered))
|
||||||
|
(user-error "`%s' in `%s' has incompatible state `%s'"
|
||||||
|
deletion mirror-dir
|
||||||
|
(gethash deletion mirror-states))))
|
||||||
|
(when-let* (non-empty-patch-p
|
||||||
|
(failed (diff-apply-buffer)))
|
||||||
|
(user-error (ngettext "%d hunk does not apply to `%s'"
|
||||||
|
"%d hunks do not apply to `%s'"
|
||||||
|
failed)
|
||||||
|
failed directory))
|
||||||
|
;; For both `added' & `unregistered' files we leave them
|
||||||
|
;; unregistered in the target working tree, and for `removed' &
|
||||||
|
;; `missing' files we leave them missing. This means that if
|
||||||
|
;; the user wants to throw away their copied changes it's less
|
||||||
|
;; effort to do so. If the user does want to check in the
|
||||||
|
;; copied changes then VC-Dir will implicitly handle registering
|
||||||
|
;; the additions and deletions as part of `vc-checkin'.
|
||||||
|
(dolist (copy copies)
|
||||||
|
(copy-file (expand-file-name copy orig-dd) copy))
|
||||||
|
(mapc #'delete-file deletions)
|
||||||
|
(when vc-dir-buffers
|
||||||
|
(mapc #'vc-dir-resynch-file whole-files)))
|
||||||
|
(when move
|
||||||
|
(diff-apply-buffer nil nil 'reverse)
|
||||||
|
(mapc (lambda (f) (vc-call-backend backend 'unregister f))
|
||||||
|
(alist-get 'added by-state))
|
||||||
|
(mapc #'delete-file copies)
|
||||||
|
(when vc-dir-buffers
|
||||||
|
(mapc #'vc-dir-resynch-file copies))
|
||||||
|
(vc-revert-files backend deletions))
|
||||||
|
(message "Changes %s to `%s'"
|
||||||
|
(if move "moved" "applied") directory))))
|
||||||
|
|
||||||
;;;###autoload
|
;;;###autoload
|
||||||
(defun vc-kill-other-working-tree-buffers (backend)
|
(defun vc-kill-other-working-tree-buffers (backend)
|
||||||
|
|
|
||||||
|
|
@ -34,11 +34,20 @@
|
||||||
(ref-time1 '(17337 16613)) ;Monday, Jan 2, 2006, 3:04:05 PM
|
(ref-time1 '(17337 16613)) ;Monday, Jan 2, 2006, 3:04:05 PM
|
||||||
(ref-time2 '(22574 61591)) ;Friday, Nov 18, 2016, 12:14:15 PM
|
(ref-time2 '(22574 61591)) ;Friday, Nov 18, 2016, 12:14:15 PM
|
||||||
(ref-time3 '(21377 34956)) ;Sunday, May 25, 2014, 06:07:08 AM
|
(ref-time3 '(21377 34956)) ;Sunday, May 25, 2014, 06:07:08 AM
|
||||||
|
(time-stamp-active t) ;default, but user may have changed it
|
||||||
(time-stamp-time-zone t)) ;use UTC
|
(time-stamp-time-zone t)) ;use UTC
|
||||||
(cl-letf (((symbol-function 'time-stamp-conv-warn)
|
(cl-letf (((symbol-function 'time-stamp-conv-warn)
|
||||||
(lambda (old-format _new &optional _newer)
|
(lambda (old-format _new &optional _newer)
|
||||||
(ert-fail
|
(ert-fail
|
||||||
(format "Unexpected format warning for '%s'" old-format)))))
|
(format "Unexpected format warning for '%s'" old-format))))
|
||||||
|
((symbol-function 'message)
|
||||||
|
(lambda (format-string &rest args)
|
||||||
|
(ert-fail (format "Unexpected message: %s"
|
||||||
|
(apply #'format format-string args)))))
|
||||||
|
((symbol-function 'sit-for)
|
||||||
|
(lambda (&rest _args)
|
||||||
|
;; do not wait during tests
|
||||||
|
)))
|
||||||
;; Not all reference times are used in all tests;
|
;; Not all reference times are used in all tests;
|
||||||
;; suppress the byte compiler's "unused" warning.
|
;; suppress the byte compiler's "unused" warning.
|
||||||
(list ref-time1 ref-time2 ref-time3)
|
(list ref-time1 ref-time2 ref-time3)
|
||||||
|
|
@ -62,17 +71,32 @@
|
||||||
(lambda () ,name)))
|
(lambda () ,name)))
|
||||||
,@body))
|
,@body))
|
||||||
|
|
||||||
|
|
||||||
|
(defmacro time-stamp-test--count-function-calls (fn errmsg &rest forms)
|
||||||
|
"Return a form verifying that FN is called while FORMS are evaluated."
|
||||||
|
(declare (debug t) (indent 2))
|
||||||
|
(cl-with-gensyms (g-warning-count)
|
||||||
|
`(let ((,g-warning-count 0))
|
||||||
|
(cl-letf (((symbol-function ',fn)
|
||||||
|
(lambda (&rest _args)
|
||||||
|
(incf ,g-warning-count))))
|
||||||
|
,@forms
|
||||||
|
(unless (= ,g-warning-count 1)
|
||||||
|
(ert-fail (format "Should have warned about %s" ,errmsg)))))))
|
||||||
|
|
||||||
(defmacro time-stamp-should-warn (form)
|
(defmacro time-stamp-should-warn (form)
|
||||||
"Similar to `should' and also verify that FORM generates a format warning."
|
"Similar to `should' and also verify that FORM generates a format warning."
|
||||||
(declare (debug t))
|
(declare (debug t))
|
||||||
(cl-with-gensyms (g-warning-count)
|
`(time-stamp-test--count-function-calls
|
||||||
`(let ((,g-warning-count 0))
|
time-stamp-conv-warn (format "format: %S" ',form)
|
||||||
(cl-letf (((symbol-function 'time-stamp-conv-warn)
|
(should ,form)))
|
||||||
(lambda (_old _new &optional _newer)
|
|
||||||
(incf ,g-warning-count))))
|
(defmacro time-stamp-should-message (variable &rest body)
|
||||||
(should ,form)
|
"Output a message about VARIABLE if `message' is not called by BODY."
|
||||||
(unless (= ,g-warning-count 1)
|
(declare (indent 1) (debug t))
|
||||||
(ert-fail (format "Should have warned about format: %S" ',form)))))))
|
`(time-stamp-test--count-function-calls
|
||||||
|
message (format "variable %s" ',variable)
|
||||||
|
,@body))
|
||||||
|
|
||||||
;;; Tests:
|
;;; Tests:
|
||||||
|
|
||||||
|
|
@ -331,6 +355,31 @@
|
||||||
(time-stamp)
|
(time-stamp)
|
||||||
(should (equal (buffer-string) expected-2)))))))
|
(should (equal (buffer-string) expected-2)))))))
|
||||||
|
|
||||||
|
(ert-deftest time-stamp-custom-messages ()
|
||||||
|
"Test that various incorrect variable values warn and do not crash."
|
||||||
|
(with-time-stamp-test-env
|
||||||
|
(let ((time-stamp-line-limit 8.5))
|
||||||
|
(time-stamp-should-message time-stamp-line-limit
|
||||||
|
(time-stamp)))
|
||||||
|
(let ((time-stamp-count 1.5))
|
||||||
|
(time-stamp-should-message time-stamp-count
|
||||||
|
(time-stamp)))
|
||||||
|
(let ((time-stamp-start 17))
|
||||||
|
(time-stamp-should-message time-stamp-start
|
||||||
|
(time-stamp)))
|
||||||
|
(let ((time-stamp-end 17))
|
||||||
|
(time-stamp-should-message time-stamp-end
|
||||||
|
(time-stamp)))
|
||||||
|
(let ((time-stamp-active nil)
|
||||||
|
(buffer-original-contents "Time-stamp: <>"))
|
||||||
|
(with-temp-buffer
|
||||||
|
(time-stamp) ;with no template, no message
|
||||||
|
(insert buffer-original-contents)
|
||||||
|
(time-stamp-should-message time-stamp-active
|
||||||
|
(time-stamp))
|
||||||
|
(should (equal (buffer-string) buffer-original-contents))))
|
||||||
|
))
|
||||||
|
|
||||||
;;; Tests of time-stamp-string formatting
|
;;; Tests of time-stamp-string formatting
|
||||||
|
|
||||||
(eval-and-compile ;utility functions used by macros
|
(eval-and-compile ;utility functions used by macros
|
||||||
|
|
@ -1213,6 +1262,7 @@ Return non-nil if the definition is found."
|
||||||
;; eval: (put 'with-time-stamp-test-env 'lisp-indent-function 0)
|
;; eval: (put 'with-time-stamp-test-env 'lisp-indent-function 0)
|
||||||
;; eval: (put 'with-time-stamp-test-time 'lisp-indent-function 1)
|
;; eval: (put 'with-time-stamp-test-time 'lisp-indent-function 1)
|
||||||
;; eval: (put 'with-time-stamp-system-name 'lisp-indent-function 1)
|
;; eval: (put 'with-time-stamp-system-name 'lisp-indent-function 1)
|
||||||
|
;; eval: (put 'time-stamp-should-message 'lisp-indent-function 1)
|
||||||
;; eval: (put 'define-formatz-tests 'lisp-indent-function 1)
|
;; eval: (put 'define-formatz-tests 'lisp-indent-function 1)
|
||||||
;; End:
|
;; End:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
;; Copyright (C) 2014-2025 Free Software Foundation, Inc.
|
;; Copyright (C) 2014-2025 Free Software Foundation, Inc.
|
||||||
|
|
||||||
;; Author: Michael Albinus <michael.albinus@gmx.de>
|
;; Author: Michael Albinus <michael.albinus@gmx.de>
|
||||||
|
;; Author: Sean Whitton <spwhitton@spwhitton.name>
|
||||||
|
|
||||||
;; This file is part of GNU Emacs.
|
;; This file is part of GNU Emacs.
|
||||||
;;
|
;;
|
||||||
|
|
@ -902,6 +903,108 @@ This checks also `vc-backend' and `vc-responsible-backend'."
|
||||||
(ignore-errors
|
(ignore-errors
|
||||||
(run-hooks 'vc-test--cleanup-hook)))))))
|
(run-hooks 'vc-test--cleanup-hook)))))))
|
||||||
|
|
||||||
|
(defun vc-test--apply-to-other-working-tree (backend)
|
||||||
|
"Test `vc--apply-to-other-working-tree'."
|
||||||
|
(ert-with-temp-directory _tempdir
|
||||||
|
(let ((vc-handled-backends `(,backend))
|
||||||
|
(default-directory
|
||||||
|
(file-name-as-directory
|
||||||
|
(expand-file-name
|
||||||
|
(make-temp-name "vc-test") temporary-file-directory)))
|
||||||
|
vc-test--cleanup-hook)
|
||||||
|
(vc-test--with-author-identity backend
|
||||||
|
(unwind-protect
|
||||||
|
(let ((first (file-truename
|
||||||
|
(file-name-as-directory
|
||||||
|
(expand-file-name "first" default-directory))))
|
||||||
|
(second (file-truename
|
||||||
|
(file-name-as-directory
|
||||||
|
(expand-file-name "second" default-directory)))))
|
||||||
|
;; Cleanup.
|
||||||
|
(add-hook 'vc-test--cleanup-hook
|
||||||
|
(let ((dir default-directory))
|
||||||
|
(lambda ()
|
||||||
|
(delete-directory dir 'recursive))))
|
||||||
|
|
||||||
|
;; Set up the two working trees.
|
||||||
|
(make-directory first 'parents)
|
||||||
|
(let ((default-directory first)
|
||||||
|
(names '("foo" "bar" "baz")))
|
||||||
|
(vc-test--create-repo-function backend)
|
||||||
|
(dolist (str names)
|
||||||
|
(write-region (concat str "\n") nil str nil 'nomessage)
|
||||||
|
(vc-register `(,backend (,str))))
|
||||||
|
(vc-checkin names backend "Test files"))
|
||||||
|
;; For the purposes of this test just copying the tree is
|
||||||
|
;; enough. FIRST and SECOND don't have to actually share
|
||||||
|
;; a backing revisions store.
|
||||||
|
(copy-directory first (directory-file-name second))
|
||||||
|
|
||||||
|
;; Make modifications that we will try to move.
|
||||||
|
(let ((default-directory first))
|
||||||
|
(write-region "qux\n" nil "qux" nil 'nomessage)
|
||||||
|
(vc-register `(,backend ("qux")))
|
||||||
|
(write-region "quux\n" nil "quux" nil 'nomessage)
|
||||||
|
(cl-letf (((symbol-function 'y-or-n-p) #'always))
|
||||||
|
(vc-delete-file "bar"))
|
||||||
|
(delete-file "baz")
|
||||||
|
(write-region "foobar\n" nil "foo" nil 'nomessage)
|
||||||
|
(should (eq (vc-state "foo" backend) 'edited))
|
||||||
|
(should (eq (vc-state "baz" backend) 'missing))
|
||||||
|
(should (eq (vc-state "bar" backend) 'removed))
|
||||||
|
(should (eq (vc-state "qux" backend) 'added))
|
||||||
|
(should (eq (vc-state "quux" backend) 'unregistered)))
|
||||||
|
|
||||||
|
(cl-flet ((go ()
|
||||||
|
(let ((default-directory first)
|
||||||
|
(vc-no-confirm-moving-changes t))
|
||||||
|
(vc--apply-to-other-working-tree
|
||||||
|
second second `(,backend
|
||||||
|
("foo" "bar" "baz" "qux" "quux"))
|
||||||
|
nil t))))
|
||||||
|
(let ((default-directory second))
|
||||||
|
;; Set up a series of incompatibilities, one-by-one, and
|
||||||
|
;; try to move. In each case the problem should block the
|
||||||
|
;; move from proceeding.
|
||||||
|
|
||||||
|
;; User refuses to sync destination fileset.
|
||||||
|
(with-current-buffer (find-file-noselect "bar")
|
||||||
|
(set-buffer-modified-p t)
|
||||||
|
(cl-letf (((symbol-function 'y-or-n-p) #'ignore))
|
||||||
|
(should-error (go)))
|
||||||
|
(set-buffer-modified-p nil))
|
||||||
|
|
||||||
|
;; New file to be copied already exists.
|
||||||
|
(with-temp-file "qux")
|
||||||
|
(should-error (go))
|
||||||
|
(delete-file "qux")
|
||||||
|
|
||||||
|
;; File to be deleted has changes.
|
||||||
|
(write-region "foobar\n" nil "bar" nil 'nomessage)
|
||||||
|
(should-error (go))
|
||||||
|
(vc-revert-file "bar")
|
||||||
|
|
||||||
|
;; Finally, a move that should succeed. Check that
|
||||||
|
;; everything we expected to happen did happen.
|
||||||
|
(go)
|
||||||
|
(with-current-buffer (find-file-noselect "foo")
|
||||||
|
(should (equal (buffer-string) "foobar\n")))
|
||||||
|
(should-not (file-exists-p "bar"))
|
||||||
|
(should-not (file-exists-p "baz"))
|
||||||
|
(should (file-exists-p "qux"))
|
||||||
|
(should (file-exists-p "quux"))
|
||||||
|
(let ((default-directory first))
|
||||||
|
(with-current-buffer (find-file-noselect "foo")
|
||||||
|
(should (equal (buffer-string) "foo\n")))
|
||||||
|
(should (file-exists-p "bar"))
|
||||||
|
(should (file-exists-p "baz"))
|
||||||
|
(should-not (file-exists-p "qux"))
|
||||||
|
(should-not (file-exists-p "quux"))))))
|
||||||
|
|
||||||
|
;; Save exit.
|
||||||
|
(ignore-errors
|
||||||
|
(run-hooks 'vc-test--cleanup-hook)))))))
|
||||||
|
|
||||||
;; Create the test cases.
|
;; Create the test cases.
|
||||||
|
|
||||||
(defun vc-test--rcs-enabled ()
|
(defun vc-test--rcs-enabled ()
|
||||||
|
|
@ -1066,7 +1169,19 @@ This checks also `vc-backend' and `vc-responsible-backend'."
|
||||||
(vc-test--other-working-trees ',backend)))
|
(vc-test--other-working-trees ',backend)))
|
||||||
|
|
||||||
(ert-deftest
|
(ert-deftest
|
||||||
,(intern (format "vc-test-%s08-checkin-patch" backend-string)) ()
|
,(intern (format "vc-test-%s08-apply-to-other-working-tree" backend-string)) ()
|
||||||
|
,(format "Test `vc--apply-to-other-working-tree' with the %s backend."
|
||||||
|
backend-string)
|
||||||
|
(skip-when
|
||||||
|
(ert-test-skipped-p
|
||||||
|
(ert-test-most-recent-result
|
||||||
|
(ert-get-test
|
||||||
|
',(intern
|
||||||
|
(format "vc-test-%s07-other-working-trees" backend-string))))))
|
||||||
|
(vc-test--apply-to-other-working-tree ',backend))
|
||||||
|
|
||||||
|
(ert-deftest
|
||||||
|
,(intern (format "vc-test-%s09-checkin-patch" backend-string)) ()
|
||||||
,(format "Check preparing and checking in patches with the %s backend."
|
,(format "Check preparing and checking in patches with the %s backend."
|
||||||
backend-string)
|
backend-string)
|
||||||
(skip-unless
|
(skip-unless
|
||||||
|
|
|
||||||
|
|
@ -797,6 +797,8 @@ comparing the subr with a much slower Lisp implementation."
|
||||||
(should (= (ash (* 2 most-negative-fixnum) (* 2 most-negative-fixnum)) -1))
|
(should (= (ash (* 2 most-negative-fixnum) (* 2 most-negative-fixnum)) -1))
|
||||||
(should (= (ash (* 2 most-negative-fixnum) -1)
|
(should (= (ash (* 2 most-negative-fixnum) -1)
|
||||||
most-negative-fixnum))
|
most-negative-fixnum))
|
||||||
|
(should (= (ash 1 48) #x1000000000000))
|
||||||
|
(should (= (ash 1 72) #x1000000000000000000))
|
||||||
(with-suppressed-warnings ((suspicious lsh))
|
(with-suppressed-warnings ((suspicious lsh))
|
||||||
(should (= (lsh most-negative-fixnum 1)
|
(should (= (lsh most-negative-fixnum 1)
|
||||||
(* most-negative-fixnum 2)))
|
(* most-negative-fixnum 2)))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue