Function: vc-rename-file
vc-rename-file is an autoloaded, interactive and byte-compiled
function defined in vc.el.gz.
Signature
(vc-rename-file OLD NEW &optional OK-IF-ALREADY-EXISTS)
Documentation
Rename file OLD to NEW in both working tree and repository.
When called interactively, read OLD and NEW, defaulting OLD to the
current buffer's file name if it's under version control.
If NEW is a directory name, rename FILE to a like-named file under NEW.
For NEW to be recognized as a directory name, it should end in a slash.
Signal a file-already-exists error if a file NEW already exists unless
called from Lisp with optional argument OK-IF-ALREADY-EXISTS non-nil.
Probably introduced at or before Emacs version 31.1.
Key Bindings
Source Code
;; Defined in /usr/src/emacs/lisp/vc/vc.el.gz
;;;###autoload
(defun vc-rename-file (old new &optional ok-if-already-exists)
"Rename file OLD to NEW in both working tree and repository.
When called interactively, read OLD and NEW, defaulting OLD to the
current buffer's file name if it's under version control.
If NEW is a directory name, rename FILE to a like-named file under NEW.
For NEW to be recognized as a directory name, it should end in a slash.
Signal a `file-already-exists' error if a file NEW already exists unless
called from Lisp with optional argument OK-IF-ALREADY-EXISTS non-nil."
(interactive (list (read-file-name "VC rename file: " nil
(and (vc-backend buffer-file-name)
buffer-file-name)
t)
(read-file-name "Rename to: ")))
;; in CL I would have said (setq new (merge-pathnames new old))
(let ((old-base (file-name-nondirectory old)))
(when (and (not (string-empty-p old-base))
(string-empty-p (file-name-nondirectory new)))
(setq new (concat new old-base))))
(cl-callf expand-file-name old)
(cl-callf expand-file-name new)
(let ((oldbuf (get-file-buffer old))
(default-directory (file-name-directory old))
(dirp (file-directory-p old)))
(when (and oldbuf (buffer-modified-p oldbuf))
(error "Please save files before moving them"))
(when (get-file-buffer new)
(error "Already editing new file name"))
;; Handle OK-IF-ALREADY-EXISTS here because it's not part of the VC
;; backend `delete-file' API.
;; If NEW is a directory we'll fail to delete it, consistent with
;; `rename-file' whose OK-IF-ALREADY-EXISTS argument similarly can't
;; delete existing directories.
(when (file-exists-p new)
(if ok-if-already-exists (vc-delete-file new 'noconfirm)
(signal 'file-already-exists `("File exists" ,new))))
(unless dirp
(let ((state (vc-state old)))
(unless (memq state '(up-to-date edited added))
(error "Please %s files before moving them"
(if (stringp state) "check in" "update")))))
;; The rename commands for several VCS (at least Bzr, Git and
;; Mercurial) will fail if asked to move a directory containing
;; only untracked files. So skip calling into the backend if OLD
;; is a directory and we walk through the entirety of it without
;; finding any VC-managed files.
(unless (and dirp
(catch 'done
(vc-file-tree-walk old (lambda (_) (throw 'done nil)))
t))
(vc-call-backend (if dirp
(vc-responsible-backend old)
(vc-backend old))
'rename-file old new))
(vc-file-clearprops old)
(vc-file-clearprops new)
;; Move the actual file (unless the backend did it already)
(when (file-exists-p old) (rename-file old new))
;; ?? Renaming a file might change its contents due to keyword expansion.
;; We should really check out a new copy if the old copy was precisely equal
;; to some checked-in revision. However, testing for this is tricky....
(when oldbuf
(with-current-buffer oldbuf
(let ((buffer-read-only buffer-read-only))
(set-visited-file-name new))
(vc-mode-line new (vc-backend new))
(set-buffer-modified-p nil)))))