2

I am using Emacs master branch built --with-ns on a drive that is case insensitive -- aka Mac OS Extended (Journaled). I would like to use dired-do-rename to change a folder name from upper to lower-case or visa-versa. Emacs throws a file-error and does not change the name:

Move ‘/Volumes/STORAGE/PROGRAMS/HP_ScanJet_8290’ to ‘/Volumes/STORAGE/PROGRAMS/hp_scanjet_8290/HP_ScanJet_8290’ failed:

(file-error Renaming Invalid argument /Volumes/STORAGE/PROGRAMS/HP_ScanJet_8290 /Volumes/STORAGE/PROGRAMS/hp_scanjet_8290/HP_ScanJet_8290)

I am looking for some assistance, please, to redefine one or more of the dired functions to permit this operation to succeed. The solution can certainly use advice (since most of the forum participants seem to prefer that method), however, a new function that replaces one or more existing functions is also acceptable.


EDIT:  The following is the output from M-x debug-on-entry dired-do-create-files after evaluating (require 'dired-aux), and then trying to rename a folder [from CamelCase to all lower-case] using S-r in dired-mode -- aka dired-do-rename:

Debugger entered--entering a function:
  * dired-do-create-files(move dired-rename-file "Move" nil t "Rename")
    dired-do-rename(nil)
    funcall-interactively(dired-do-rename nil)
    call-interactively(dired-do-rename nil nil)
    command-execute(dired-do-rename)

EDIT:  This appears to be a bug affecting OSX users of Emacs and the appropriate thing to do (as Drew suggested) is file a bug report so that everyone can benefit from the fix -- instead of a fix that is only available on emacs.stackexchange.com. Here is the link to the report: http://debbugs.gnu.org/cgi/bugreport.cgi?bug=22300


EDIT:  The bug was summarily dismissed / closed in about 5 minutes flat with the following explanation:

Since this is not an Emacs bug, I'm closing this report, but here is what I do
that should work for you:

  1. Rename the file to 'x'.
  2. Rename 'x' to the correctly cased name.

Copy the name to the kill-ring before #1, so that you can paste it in when
asked by #2.

[@lawlist (the original poster)]:  The moral of the story is that this question remains open and any help to implement a proper bug fix for OSX users would be greatly appreciated.

  • Can you give a step-by-step recipe to repro the error? It looks like the first arg you gave is a directory and the second is a file in that directory (since the directory name differences are ignored by your OS). – Drew Jan 3 '16 at 19:58
  • Have you tried M-x debug-on-entry dired-do-create-files (after loading dired-aux.el, not *.elc)? – Drew Jan 3 '16 at 20:03
  • @Drew -- Thank you for the suggestion - I performed the test and edited the question with the output. It's basically just a Shift+r on a folder that contains the name HP_ScanJet_8290 and trying to change it to all lowercase. – lawlist Jan 3 '16 at 20:17
  • I meant see what the debugger says happens in dired-do-create-files (not leading up to it). It's not clear what it is (you are) trying to rename to what. And see what I said about what appear to be your renaming args - the directory and file name being the same, etc. – Drew Jan 3 '16 at 20:21
  • Perhaps my problem with debugging is that I am improperly using M-x debug-on-entry dired-do-create-files? I suppose I could open up dired-do-create-files and place a bunch of messages throughout and attempt to find out why the file-error is thrown, but that would be a lengthy process for later this evening . . . Basically (i.e., I guess), on a case-insensitive drive the file-error happens if the same name is used when renaming because dired cannot distinguish any difference between CamelCase / UPPERCASE / lowercase. – lawlist Jan 3 '16 at 20:35
1

Although the bug report is being handled by the Emacs team -- http://debbugs.gnu.org/cgi/bugreport.cgi?bug=22300 -- I am using the following workaround in the interim, which basically bypasses dired-create-files if the situation is as described in the original question above. The workaround also reverts the buffer after the file has been renamed, instead of the fancy cut/paste of just the affected line that dired-mode normally does.

[The workaround was inspired by the helpful comment of @wvxvw underneath the original question -- i.e., using mv to rename the file.]

(require 'dired-aux)

(require 'cl) ;; for `lexical-let*' to penetrate the `set-process-sentinel'.

(defun dired-do-create-files (op-symbol file-creator operation arg
                    &optional marker-char op1
                    how-to)
  "Create a new file for each marked file.
Prompt user for a target directory in which to create the new
  files.  The target may also be a non-directory file, if only
  one file is marked.  The initial suggestion for target is the
  Dired buffer's current directory (or, if `dired-dwim-target' is
  non-nil, the current directory of a neighboring Dired window).
OP-SYMBOL is the symbol for the operation.  Function `dired-mark-pop-up'
  will determine whether pop-ups are appropriate for this OP-SYMBOL.
FILE-CREATOR and OPERATION as in `dired-create-files'.
ARG as in `dired-get-marked-files'.
Optional arg MARKER-CHAR as in `dired-create-files'.
Optional arg OP1 is an alternate form for OPERATION if there is
  only one file.
Optional arg HOW-TO determines how to treat the target.
  If HOW-TO is nil, use `file-directory-p' to determine if the
   target is a directory.  If so, the marked file(s) are created
   inside that directory.  Otherwise, the target is a plain file;
   an error is raised unless there is exactly one marked file.
  If HOW-TO is t, target is always treated as a plain file.
  Otherwise, HOW-TO should be a function of one argument, TARGET.
   If its return value is nil, TARGET is regarded as a plain file.
   If it return value is a list, TARGET is a generalized
    directory (e.g. some sort of archive).  The first element of
    this list must be a function with at least four arguments:
      operation - as OPERATION above.
      rfn-list  - list of the relative names for the marked files.
      fn-list   - list of the absolute names for the marked files.
      target    - the name of the target itself.
      The rest of into-dir are optional arguments.
   For any other return value, TARGET is treated as a directory."
  (or op1 (setq op1 operation))
  (let* ((fn-list (dired-get-marked-files nil arg))
     (rfn-list (mapcar (function dired-make-relative) fn-list))
     (dired-one-file    ; fluid variable inside dired-create-files
      (and (consp fn-list) (null (cdr fn-list)) (car fn-list)))
     (target-dir (dired-dwim-target-directory))
     (default (and dired-one-file
               (expand-file-name (file-name-nondirectory (car fn-list))
                     target-dir)))
     (defaults (dired-dwim-target-defaults fn-list target-dir))
     (target (expand-file-name ; fluid variable inside dired-create-files
          (minibuffer-with-setup-hook
              (lambda ()
            (set (make-local-variable 'minibuffer-default-add-function) nil)
            (setq minibuffer-default defaults))
            (dired-mark-read-file-name
             (concat (if dired-one-file op1 operation) " %s to: ")
             target-dir op-symbol arg rfn-list default))))
     (into-dir (cond ((null how-to)
              ;; Allow DOS/Windows users to change the letter
              ;; case of a directory.  If we don't test these
              ;; conditions up front, file-directory-p below
              ;; will return t because the filesystem is
              ;; case-insensitive, and Emacs will try to move
              ;; foo -> foo/foo, which fails.
              (if (and (memq system-type '(ms-dos windows-nt cygwin))
                   (eq op-symbol 'move)
                   dired-one-file
                   (string= (downcase
                         (expand-file-name (car fn-list)))
                        (downcase
                         (expand-file-name target)))
                   (not (string=
                     (file-name-nondirectory (car fn-list))
                     (file-name-nondirectory target))))
                  nil
                (file-directory-p target)))
             ((eq how-to t) nil)
             (t (funcall how-to target)))))
    (if (and (consp into-dir) (functionp (car into-dir)))
    (apply (car into-dir) operation rfn-list fn-list target (cdr into-dir))
      (if (not (or dired-one-file into-dir))
      (error "Marked %s: target must be a directory: %s" operation target))
      ;; rename-file bombs when moving directories unless we do this:
      (or into-dir (setq target (directory-file-name target)))
;;; BEGIN modification by @lawlist
      (if
          (and
            (eq system-type 'darwin)
            dired-one-file
            (equal op1 "Rename")
            (equal operation "Move")
            (equal (downcase (expand-file-name dired-one-file)) (downcase (expand-file-name target))))
        ;; Penetrate the `set-process-sentinel' with `lexical-let'.
        (lexical-let* ((dired-one-file dired-one-file)
                       (target target))
          (set-process-sentinel
            (start-process "rename" nil "mv" dired-one-file target)
            (lambda (p e) (when (= 0 (process-exit-status p))
              (revert-buffer)
              (message "Renamed %s to %s" dired-one-file target)))))
      (dired-create-files
       file-creator operation fn-list
       (if into-dir         ; target is a directory
       ;; This function uses fluid variable target when called
       ;; inside dired-create-files:
       (function
        (lambda (from)
          (expand-file-name (file-name-nondirectory from) target)))
     (function (lambda (_from) target)))
       marker-char))
;;; END modification by @lawlist
  )))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.