[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] externals/denote bbdf36036b 3/5: Make rename operations avoid dup
From: |
ELPA Syncer |
Subject: |
[elpa] externals/denote bbdf36036b 3/5: Make rename operations avoid duplicate identifiers |
Date: |
Fri, 14 Jul 2023 06:58:11 -0400 (EDT) |
branch: externals/denote
commit bbdf36036b683247e2aa8e046709a52e30d791c2
Author: Protesilaos Stavrou <info@protesilaos.com>
Commit: Protesilaos Stavrou <info@protesilaos.com>
Make rename operations avoid duplicate identifiers
Before, it was possible for Denote to create duplicate identifiers
while renaming non-Denote files that had the same modification date.
Files can have the same modification date when they are processed by a
program, such as with Git or the 'touch' command.
What Denote now does is check for the presence of the given identifier
and produce a new one that is unique. Depending on the command, the
check is done against the 'denote-directory' or the set of marked
Dired files.
The command 'denote-dired-rename-marked-files' gains a new optional
parameter, which is interactively passed by the double prefix
argument ('C-u C-u' with default key bindings). I am not sure this is
the best interface, as we may prefer to always run the check for
duplicate identifiers. The reason I am doing it this way with the
prefix argument is to avoid an unconditional potentially expensive
operation while renaming a large set of files.
---
README.org | 18 ++++++++++++++
denote.el | 80 +++++++++++++++++++++++++++++++++++++++++++++++++-------------
2 files changed, 82 insertions(+), 16 deletions(-)
diff --git a/README.org b/README.org
index 24f17e5d33..605f89525c 100644
--- a/README.org
+++ b/README.org
@@ -1073,6 +1073,16 @@ following:
[ The optional =SKIP-FRONT-MATTER-PROMPT= is added to
~denote-dired-rename-marked-files~ as part of {{{development-version}}}. ]
+With optional =ENSURE-UNIQUE-IDS= as a double prefix argument,
+process the file identifiers of the marked files to ensure there
+is no duplicate among them. When renaming files in Dired, it is
+possible to produce duplicate identifiers. This can happen when
+multiple files share the same modification time, which can be
+casually done with the ~touch~ command, ~git~, and others.
+
+[ The optional =ENSURE-UNIQUE-IDS= is added to
+ ~denote-dired-rename-marked-files~ as part of {{{development-version}}}. ]
+
** Rename a single file based on its front matter
:PROPERTIES:
:CUSTOM_ID: h:3ab08ff4-81fa-4d24-99cb-79f97c13a373
@@ -3584,6 +3594,14 @@ might change them without further notice.
- As a fallback, derive an identifier from the current time.
+ [ The =FILES= argument is part of {{{development-version}}}. ]
+
+ With optional =FILES= as a list of file names, test that the
+ identifier is unique among them.
+
+ With optional =FILES= as non-nil, test that the identifier is unique
+ among all files and buffers in variable ~denote-directory~.
+
To only return an existing identifier, refer to the function
~denote-retrieve-filename-identifier~.
diff --git a/denote.el b/denote.el
index d14c0c08ea..c952491435 100644
--- a/denote.el
+++ b/denote.el
@@ -1315,7 +1315,7 @@ the function `denote-retrieve-or-create-file-identifier'."
'denote-retrieve-filename-identifier
"1.0.0")
-(defun denote-retrieve-or-create-file-identifier (file &optional date)
+(defun denote-retrieve-or-create-file-identifier (file &optional date files)
"Return FILE identifier, generating one if appropriate.
The conditions are as follows:
@@ -1331,14 +1331,29 @@ The conditions are as follows:
- As a fallback, derive an identifier from the current time.
+With optional FILES as a list of file names, test that the
+identifier is unique among them.
+
+With optional FILES as non-nil, test that the identifier is
+unique among all files and buffers in variable
+`denote-directory'.
+
To only return an existing identifier, refer to the function
`denote-retrieve-filename-identifier'."
- (cond
- ((string-match denote-id-regexp file)
- (substring file (match-beginning 0) (match-end 0)))
- (date (denote-prompt-for-date-return-id))
- ((denote--file-attributes-time file))
- (t (format-time-string denote-id-format))))
+ (let ((id
+ (cond
+ ((string-match denote-id-regexp file)
+ (substring file (match-beginning 0) (match-end 0)))
+ (date (denote-prompt-for-date-return-id))
+ ((denote--file-attributes-time file))
+ (t (format-time-string denote-id-format)))))
+ (cond
+ ((and files (listp files))
+ (denote--return-new-identifier-if-duplicate id files))
+ (files
+ (denote--return-new-identifier-if-duplicate id))
+ (t
+ id))))
(define-obsolete-function-alias
'denote--file-name-id
@@ -1617,12 +1632,29 @@ where the former does not read dates without a time
component."
;; otherwise the identifier is always unique (we trust that no-one
;; writes multiple notes within fractions of a second). Though the
;; `denote' command does call `denote-barf-duplicate-id'.
-(defun denote--id-exists-p (identifier)
- "Return non-nil if IDENTIFIER already exists."
+(defun denote--id-exists-p (identifier &optional files)
+ "Return non-nil if IDENTIFIER already exists.
+With optional FILES, check for IDENTIFIER among them. Else refer
+to files or buffers in the variable `denote-directory'."
(seq-some
(lambda (file)
(string-prefix-p identifier (file-name-nondirectory file)))
- (append (denote-directory-files) (denote--buffer-file-names))))
+ (or files
+ (append (denote-directory-files) (denote--buffer-file-names)))))
+
+(defun denote--increment-identifier (identifier)
+ "Increment IDENTIFIER.
+Preserve the date component and append to it the current time."
+ (let* ((datetime (split-string identifier "T"))
+ (date (car datetime)))
+ (concat date "T" (format-time-string "%H%M%S"))))
+
+(defun denote--return-new-identifier-if-duplicate (identifier &optional files)
+ "Return new unique identifier if IDENTIFIER already exists.
+The meaning of FILES is the same as in `denote--id-exists-p'."
+ (while (denote--id-exists-p identifier files)
+ (setq identifier (denote--increment-identifier identifier)))
+ identifier)
(defun denote-barf-duplicate-id (identifier)
"Throw a `user-error' if IDENTIFIER already exists."
@@ -2285,7 +2317,7 @@ files)."
(denote-keywords-prompt)
current-prefix-arg)))
(let* ((dir (file-name-directory file))
- (id (denote-retrieve-or-create-file-identifier file date))
+ (id (denote-retrieve-or-create-file-identifier file date :unique))
(signature (denote-retrieve-filename-signature file))
(extension (file-name-extension file t))
(file-type (denote-filetype-heuristics file))
@@ -2342,7 +2374,7 @@ of the file. This needs to be done manually."
(denote--add-front-matter new-name title keywords id new-file-type)))))
;;;###autoload
-(defun denote-dired-rename-marked-files (&optional skip-front-matter-prompt)
+(defun denote-dired-rename-marked-files (&optional skip-front-matter-prompt
ensure-unique-ids)
"Rename marked files in Dired to Denote file name.
The operation does the following:
@@ -2370,15 +2402,28 @@ The operation does the following:
saved with `save-some-buffers' (read its doc string). The
addition of front matter takes place only if the given file has
the appropriate file type extension (per the user option
- `denote-file-type')."
+ `denote-file-type').
+
+With optional ENSURE-UNIQUE-IDS as a double prefix argument,
+process the file identifiers of the marked files to ensure there
+is no duplicate among them. When renaming files in Dired, it is
+possible to produce duplicate identifiers. This can happen when
+multiple files share the same modification time, which can be
+casually done with the `touch' command, `git', and others."
(interactive "P" dired-mode)
+ (when current-prefix-arg
+ (if (>= (car current-prefix-arg) 16)
+ (setq skip-front-matter-prompt t
+ ensure-unique-ids t)
+ (setq skip-front-matter-prompt t
+ ensure-unique-ids nil)))
(if-let ((marks (dired-get-marked-files)))
(let ((keywords (denote-keywords-prompt)))
(when (or skip-front-matter-prompt
(yes-or-no-p "Add front matter if necessary (buffers are not
saved)?"))
(dolist (file marks)
(let* ((dir (file-name-directory file))
- (id (denote-retrieve-or-create-file-identifier file))
+ (id (denote-retrieve-or-create-file-identifier file nil
(when ensure-unique-ids marks)))
(signature (denote-retrieve-filename-signature file))
(file-type (denote-filetype-heuristics file))
(title (denote--retrieve-title-or-filename file file-type))
@@ -2389,7 +2434,10 @@ The operation does the following:
(when (denote-file-is-writable-and-supported-p new-name)
(if (denote--edit-front-matter-p new-name file-type)
(denote-rewrite-keywords new-name keywords file-type)
- (denote--add-front-matter new-name title keywords id
file-type)))))
+ (denote--add-front-matter new-name title keywords id
file-type)))
+ (when ensure-unique-ids
+ (setq marks (delete file marks))
+ (push new-name marks))))
(revert-buffer)))
(user-error "No marked files; aborting")))
@@ -2530,7 +2578,7 @@ relevant front matter."
(when (denote-file-is-writable-and-supported-p file)
(denote--add-front-matter
file title keywords
- (denote-retrieve-or-create-file-identifier file)
+ (denote-retrieve-or-create-file-identifier file nil :unique)
(denote-filetype-heuristics file))))
;;;; The Denote faces