[Top][All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Updating *vc-dir* marks from *VC-log*

From: Bob Rogers
Subject: Updating *vc-dir* marks from *VC-log*
Date: Sat, 20 Nov 2010 19:58:00 -0500

   Perhaps I'm slow, but I only just noticed that it is possible to work
on multiple VC commits for the same working copy in parallel, just by
renaming *VC-log* buffers.  This is cool; it perfectly suits my working
style.  I would like to write it up for the Emacs manual so that others
can find it.

   But it lacks something.  I often start a commit, then realize that
I've left out a file or two, or maybe included one that ought to be
committed separately.  When doing one commit at a time, this is a simple
matter of returning to vc-dir, updating the set, and doing "C-x v v"
again.  With multiple pending commits, what is needed is a way to return
to vc-dir and "swap in" the log buffer fileset.  The patch below adds a
new log-edit-visit-files-in-vc-dir command to do this.  I have
tentatively bound it to "C-c @" in log-edit-mode, which I hope is
sufficiently mnemonic for "mark".

   The only problem is that "C-x v v" doesn't know to which of several
log buffers it should return from *vc-dir*; it always picks *VC-log*,
creating a new one if necessary.  I could hack vc-checkin, but one would
still need to be able to specify when to go back to the relevant log
buffer, and when to create a new one.


                                        -- Bob Rogers

diff --git a/lisp/vc/log-edit.el b/lisp/vc/log-edit.el
index 27290ee..726a074 100644
--- a/lisp/vc/log-edit.el
+++ b/lisp/vc/log-edit.el
@@ -57,6 +57,7 @@
     ("\C-c\C-a" . log-edit-insert-changelog)
     ("\C-c\C-d" . log-edit-show-diff)
     ("\C-c\C-f" . log-edit-show-files)
+    ("\C-c@"    . log-edit-visit-files-in-vc-dir)
     ("\M-n"    . log-edit-next-comment)
     ("\M-p"    . log-edit-previous-comment)
     ("\M-r"    . log-edit-comment-search-backward)
@@ -533,6 +534,40 @@ If you want to abort the commit, simply delete the buffer."
+(defun log-edit-visit-files-in-vc-dir ()
+  "Switch to vc-dir, marking the list of files to be committed."
+  (interactive)
+  (let ((files (mapcar 'expand-file-name (log-edit-files)))
+       (buffers nil))
+    ;; Find the set of possible vc-dir buffers.
+    (let ((tail files))
+      (while tail
+       (let* ((buf-and-node (vc-dir-find-buffer-for-file (car tail)))
+              (buffer (car buf-and-node)))
+         (message "got %S for %S" buf-and-node (car tail))
+         (if (and buffer
+                  (not (member buffer buffers)))
+             (setq buffers (cons buffer buffers))))
+       (setq tail (cdr tail))))
+    (cond ((null buffers)
+           ;; Totally failed, so offer to start vc-dir.
+           (call-interactively 'vc-dir))
+         ((not (null (cdr buffers)))
+           ;; [not sure what to do here.  -- rgr, 20-Nov-10.]
+           (error "Oops; got buffers %S" buffers))
+         (t
+           ;; Single-buffer case.
+           (switch-to-buffer-other-window (car buffers))
+           ;; [should we go to one of these files?  should it be
+           ;; the first, or the last?  -- rgr, 20-Nov-10.]
+           (let* ((n-files (length files))
+                  (n-marked (vc-dir-set-marked-files files))
+                  (plural (if (= n-files 1) "" "s")))
+             (if (= n-files n-marked)
+                 (message "Marked %d file%s." n-files plural)
+                 (message "Marked %d out of %d file%s."
+                          n-marked n-files plural)))))))
 (defun log-edit-insert-cvs-template ()
   "Insert the template specified by the CVS administrator, if any.
 This simply uses the local CVS/Template file."
diff --git a/lisp/vc/vc-dir.el b/lisp/vc/vc-dir.el
index 4397251..556c612 100644
--- a/lisp/vc/vc-dir.el
+++ b/lisp/vc/vc-dir.el
@@ -714,6 +714,31 @@ that share the same state."
   (interactive "e")
   (vc-dir-at-event e (vc-dir-mark-unmark 'vc-dir-toggle-mark-file)))
+(defun vc-dir-set-marked-files (files)
+  ;; Given a list of files, mark each file that appears in the list,
+  ;; and unmark all the others.  No mention is made if a file appears
+  ;; in the list that does not appear in the vc-dir buffer, but the
+  ;; number of files actually marked is returned.
+  (let ((crt (ewoc-nth vc-ewoc 0)) (n-files-marked 0))
+    (while crt
+      (let* ((file (ewoc-data crt))
+            (new-mark-state
+              (cond ((vc-dir-fileinfo->directory file)
+                      ;; Always unmark the directories.
+                      nil)
+                    ((let ((nodefile (vc-dir-fileinfo->name file)))
+                       ;; (message "[checking %S]" nodefile)
+                       (member (expand-file-name nodefile) files))
+                      t))))
+       (unless (eq (not new-mark-state)
+                   (not (vc-dir-fileinfo->marked file)))
+         (setf (vc-dir-fileinfo->marked file) new-mark-state)
+         (ewoc-invalidate vc-ewoc crt)
+         (when new-mark-state
+           (incf n-files-marked))))
+      (setq crt (ewoc-next vc-ewoc crt)))
+    n-files-marked))
 (defun vc-dir-delete-file ()
   "Delete the marked files, or the current file if no marks."
@@ -886,6 +911,45 @@ If it is a file, return the corresponding cons for the 
file itself."
       (vc-dir-update fileentries (current-buffer)))))
+(defun vc-dir-find-buffer-for-file (file)
+  ;; Look for a vc-dir buffer that includes file, or one that might
+  ;; contain file if it were visible.  Returns nil if no such buffer
+  ;; was found, or a list of (buffer node) where node might be nil if
+  ;; file is not visible.
+  (message "checking %S for file %S" vc-dir-buffers file)
+  (let ((vc-dir-buffer nil) (vc-dir-default-directory-len 0)
+       (vc-dir-node nil) (tail vc-dir-buffers))
+    (while tail
+      (if (buffer-live-p (car tail))
+         (with-current-buffer (car tail)
+           ;; Search for a node for file.
+           (let ((node (ewoc-nth vc-ewoc 0))
+                 (dd-len (length default-directory)))
+             (while node
+               (let ((nodefile (vc-dir-fileinfo->name (ewoc-data node))))
+                 (if (string-equal (expand-file-name nodefile)
+                                   file)
+                     ;; Success.
+                     (setq vc-dir-buffer (current-buffer)
+                           vc-dir-node node
+                           tail nil node nil)))
+               (setq node (ewoc-next vc-ewoc node)))
+             ;; If not found, but the directory is a prefix of
+             ;; file, then remember the buffer as a fallback.
+             (if (and (null vc-dir-node)
+                      (> (length file) dd-len)
+                      (string-equal (substring file 0 dd-len)
+                                    default-directory)
+                      ;; When we have multiple candidates, pick the one
+                      ;; deeper in the directory hierarchy.
+                      (or (null vc-dir-buffer)
+                          (> dd-len vc-dir-default-directory-len)))
+                 (setq vc-dir-buffer (current-buffer)
+                       vc-dir-default-directory-len dd-len)))))
+      (setq tail (cdr tail)))
+    (and vc-dir-buffer
+        (list vc-dir-buffer vc-dir-node))))
 (defun vc-dir-resynch-file (&optional fname)
   "Update the entries for FNAME in any directory buffers that list it."
   (let ((file (or fname (expand-file-name buffer-file-name)))

reply via email to

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