emacs-elpa-diffs
[Top][All Lists]
Advanced

[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



reply via email to

[Prev in Thread] Current Thread [Next in Thread]