Make vc-annotate work through copies and renames.
[bpt/emacs.git] / lisp / vc.el
index fd95d86..2d5e325 100644 (file)
 ;; Arch, Subversion, Bzr, Git, Mercurial, Monotone and SCCS
 ;; (or its free replacement, CSSC).
 ;;
-;; Some features will not work with old RCS versions.  Where
-;; appropriate, VC finds out which version you have, and allows or
-;; disallows those features (stealing locks, for example, works only
-;; from 5.6.2 onwards).
-;; Even initial checkins will fail if your RCS version is so old that ci
-;; doesn't understand -t-; this has been known to happen to people running
-;; NExTSTEP 3.0.
-;;
-;; You can support the RCS -x option by customizing vc-rcs-master-templates.
-;;
-;; Proper function of the SCCS diff commands requires the shellscript vcdiff
-;; to be installed somewhere on Emacs's path for executables.
-;;
 ;; If your site uses the ChangeLog convention supported by Emacs, the
 ;; function `log-edit-comment-to-change-log' could prove a useful checkin hook,
 ;; although you might prefer to use C-c C-a (i.e. `log-edit-insert-changelog')
 ;;
 ;; HISTORY FUNCTIONS
 ;;
-;; * print-log (files &optional buffer)
+;; * print-log (files &optional buffer shortlog)
 ;;
 ;;   Insert the revision log for FILES into BUFFER, or the *vc* buffer
 ;;   if BUFFER is nil.  (Note: older versions of this function expected
 ;;   only a single file argument.)
+;;   If SHORTLOG is true insert a short version of the log.
 ;;
 ;; - log-view-mode ()
 ;;
 ;;   Invoked from a buffer in vc-annotate-mode, return the revision
 ;;   corresponding to the current line, or nil if there is no revision
 ;;   corresponding to the current line.
+;;   If the backend supports annotating through copies and renames,
+;;   and displays a file name and a revision, then return a cons
+;;   (REVISION . FILENAME).
 ;;
 ;; TAG SYSTEM
 ;;
 ;;   `revert' operations itself, without calling the backend system.  The
 ;;   default implementation always returns nil.
 ;;
+;; - root (file)
+;;   Return the root of the VC controlled hierarchy for file.
+;;
 ;; - repository-hostname (dirname)
 ;;
 ;;   Return the hostname that the backend will have to contact
 ;;
 ;;;; Default Behavior:
 ;;
-;; - do not default to RCS anymore when the current directory is not
-;;   controlled by any VCS and the user does C-x v v
-;;
 ;; - vc-responsible-backend should not return RCS if no backend
 ;;   declares itself responsible.
 ;;
@@ -814,11 +805,14 @@ The optional argument REGISTER means that a backend suitable for
 registration should be found.
 
 If REGISTER is nil, then if FILE is already registered, return the
-backend of FILE.  If FILE is not registered, or a directory, then the
+backend of FILE.  If FILE is not registered, then the
 first backend in `vc-handled-backends' that declares itself
 responsible for FILE is returned.  If no backend declares itself
 responsible, return the first backend.
 
+If REGISTER is non-nil and FILE is a directory, create a VC
+repository that can be used to register FILE.
+
 If REGISTER is non-nil, return the first responsible backend under
 which FILE is not yet registered.  If there is no such backend, return
 the first backend under which FILE is not yet registered, but could
@@ -838,13 +832,47 @@ be registered."
        (if (not register)
            ;; if this is not for registration, the first backend must do
            (car vc-handled-backends)
-         ;; for registration, we need to find a new backend that
-         ;; could register FILE
-         (dolist (backend vc-handled-backends)
-           (and (not (vc-call-backend backend 'registered file))
-                (vc-call-backend backend 'could-register file)
-                (throw 'found backend)))
-         (error "No backend that could register")))))
+         (if (file-directory-p file)
+             (let* ((possible-backends
+                     (let (pos)
+                       (dolist (crt vc-handled-backends)
+                         (when (vc-find-backend-function crt 'create-repo)
+                           (push crt pos)))
+                       pos))
+                    (bk
+                     (intern
+                      ;; Read the VC backend from the user, only
+                      ;; complete with the backends that have the
+                      ;; 'create-repo method.
+                      (completing-read
+                       (format "%s is not in a version controlled directory.\nUse VC backend: " file)
+                       (mapcar 'symbol-name possible-backends) nil t)))
+                    (repo-dir
+                     (file-name-as-directory
+                      (let ((def-dir file))
+                        ;; read the directory where to create the
+                        ;; repository, make sure it's a parent of
+                        ;; file.
+                        (read-file-name
+                         (format "create %s repository in: " bk)
+                         default-directory nil t nil
+                         (lambda (arg)
+                           (and (file-directory-p arg)
+                                (vc-string-prefix-p (expand-file-name arg) def-dir))))))))
+               (let ((default-directory repo-dir))
+                 (vc-call-backend bk 'create-repo))
+               (throw 'found bk))
+
+           ;; FIXME: this case does not happen with the current code.
+           ;; Should we keep it?
+           ;;
+           ;; For registration, we need to find a new backend that
+           ;; could register FILE.
+           (dolist (backend vc-handled-backends)
+             (and (not (vc-call-backend backend 'registered file))
+                  (vc-call-backend backend 'could-register file)
+                  (throw 'found backend))))
+         (error "no backend that could register")))))
 
 (defun vc-expand-dirs (file-or-dir-list)
   "Expands directories in a file list specification.
@@ -857,32 +885,8 @@ Within directories, only files already under version control are noticed."
       (unless (file-directory-p node) (push node flattened)))
     (nreverse flattened)))
 
-(defun vc-derived-from-dir-mode (&optional buffer)
-  "Are we in a VC-directory buffer, or do we have one as an ancestor?"
-  (let ((buffer (or buffer (current-buffer))))
-    (cond ((derived-mode-p 'vc-dir-mode) t)
-         (vc-parent-buffer (vc-derived-from-dir-mode vc-parent-buffer))
-         (t nil))))
-
 (defvar vc-dir-backend)
 
-;; FIXME: this is not functional, commented out.
-;; (defun vc-deduce-fileset (&optional observer)
-;;   "Deduce a set of files and a backend to which to apply an operation and
-;; the common state of the fileset.  Return (BACKEND . FILESET)."
-;;   (let* ((selection (vc-dispatcher-selection-set observer))
-;;      (raw (car selection))          ;; Selection as user made it
-;;      (cooked (cdr selection))       ;; Files only
-;;          ;; FIXME: Store the backend in a buffer-local variable.
-;;          (backend (if (vc-derived-from-dir-mode (current-buffer))
-;;                   ;; FIXME: this should use vc-dir-backend from
-;;                   ;; the *vc-dir* buffer.
-;;                       (vc-responsible-backend default-directory)
-;;                     (assert (and (= 1 (length raw))
-;;                                  (not (file-directory-p (car raw)))))
-;;                     (vc-backend (car cooked)))))
-;;     (cons backend selection)))
-
 (declare-function vc-dir-current-file "vc-dir" ())
 (declare-function vc-dir-deduce-fileset "vc-dir" (&optional state-model-only-files))
 
@@ -929,15 +933,15 @@ current buffer."
      ((and allow-unregistered (not (vc-registered buffer-file-name)))
       (if state-model-only-files
          (list (vc-responsible-backend
-                (file-name-directory (buffer-file-name)))
+                (file-name-directory (buffer-file-name)) t)
                (list buffer-file-name)
                (list buffer-file-name)
                (when state-model-only-files 'unregistered)
                nil)
        (list (vc-responsible-backend
-              (file-name-directory (buffer-file-name)))
+              (file-name-directory (buffer-file-name)) t)
              (list buffer-file-name))))
-     (t (error "No fileset is available here.")))))
+     (t (error "No fileset is available here")))))
 
 (defun vc-ensure-vc-buffer ()
   "Make sure that the current buffer visits a version-controlled file."
@@ -1021,9 +1025,9 @@ merge in the changes into your working copy."
     ;; Do the right thing
     (cond
      ((eq state 'missing)
-      (error "Fileset files are missing, so cannot be operated on."))
+      (error "Fileset files are missing, so cannot be operated on"))
      ((eq state 'ignored)
-      (error "Fileset files are ignored by the version-control system."))
+      (error "Fileset files are ignored by the version-control system"))
      ((or (null state) (eq state 'unregistered))
       (vc-register nil vc-fileset))
      ;; Files are up-to-date, or need a merge and user specified a revision
@@ -1247,7 +1251,7 @@ first backend that could register the file is used."
   "Register the current file with a specified back end."
   (interactive "SBackend: ")
   (when (not (member backend vc-handled-backends))
-    (error "Unknown back end."))
+    (error "Unknown back end"))
   (let ((vc-handled-backends (list backend)))
     (call-interactively 'vc-register)))
 
@@ -1570,16 +1574,11 @@ returns t if the buffer had changes, nil otherwise."
        (list files rev1 rev2))))
   ;; All that was just so we could do argument completion!
   (when (and (not rev1) rev2)
-    (error "Not a valid revision range."))
+    (error "Not a valid revision range"))
   ;; Yes, it's painful to call (vc-deduce-fileset) again.  Alas, the
   ;; placement rules for (interactive) don't actually leave us a choice.
-  (vc-diff-internal t (vc-deduce-fileset) rev1 rev2 (interactive-p)))
-
-;; (defun vc-contains-version-controlled-file (dir)
-;;   "Return t if DIR contains a version-controlled file, nil otherwise."
-;;   (catch 'found
-;;     (mapc (lambda (f) (and (not (file-directory-p f)) (vc-backend f) (throw 'found 't))) (directory-files dir))
-;;     nil))
+  (vc-diff-internal t (vc-deduce-fileset t) rev1 rev2
+                   (called-interactively-p 'interactive)))
 
 ;;;###autoload
 (defun vc-diff (historic &optional not-urgent)
@@ -1594,7 +1593,36 @@ saving the buffer."
   (if historic
       (call-interactively 'vc-version-diff)
     (when buffer-file-name (vc-buffer-sync not-urgent))
-    (vc-diff-internal t (vc-deduce-fileset) nil nil (interactive-p))))
+    (vc-diff-internal t (vc-deduce-fileset t) nil nil
+                     (called-interactively-p 'interactive))))
+
+;;;###autoload
+(defun vc-root-diff (historic &optional not-urgent)
+  "Display diffs between file revisions.
+Normally this compares the currently selected fileset with their
+working revisions.  With a prefix argument HISTORIC, it reads two revision
+designators specifying which revisions to compare.
+
+The optional argument NOT-URGENT non-nil means it is ok to say no to
+saving the buffer."
+  (interactive (list current-prefix-arg t))
+  (if historic
+      ;; FIXME: this does not work right, `vc-version-diff' ends up
+      ;; calling `vc-deduce-fileset' to find the files to diff, and
+      ;; that's not what we want here, we want the diff for the VC root dir.
+      (call-interactively 'vc-version-diff)
+    (when buffer-file-name (vc-buffer-sync not-urgent))
+    (let ((backend
+          (cond ((derived-mode-p 'vc-dir-mode)  vc-dir-backend)
+                (vc-mode (vc-backend buffer-file-name))))
+         rootdir working-revision)
+      (unless backend
+       (error "Buffer is not version controlled"))
+      (setq rootdir (vc-call-backend backend 'root default-directory))
+      (setq working-revision (vc-working-revision rootdir))
+      (vc-diff-internal
+       t (list backend (list rootdir) working-revision) nil nil
+       (called-interactively-p 'interactive)))))
 
 ;;;###autoload
 (defun vc-revision-other-window (rev)
@@ -1822,23 +1850,43 @@ allowed and simply skipped)."
 
 ;; Miscellaneous other entry points
 
+;; FIXME: this should be a defcustom
+;; FIXME: maybe add another choice:
+;; `root-directory' (or somesuch), which would mean show a short log
+;; for the root directory.
+(defvar vc-log-short-style '(directory)
+  "Whether or not to show a short log.
+If it contains `directory' then if the fileset contains a directory show a short log.
+If it contains `file' then show short logs for files.
+Not all VC backends support short logs!")
+
 (defun vc-print-log-internal (backend files working-revision)
   ;; Don't switch to the output buffer before running the command,
   ;; so that any buffer-local settings in the vc-controlled
   ;; buffer can be accessed by the command.
-  (vc-call-backend backend 'print-log files "*vc-change-log*")
-  (pop-to-buffer "*vc-change-log*")
-  (vc-exec-after
-   `(let ((inhibit-read-only t))
-      (vc-call-backend ',backend 'log-view-mode)
-      (set (make-local-variable 'log-view-vc-backend) ',backend)
-      (set (make-local-variable 'log-view-vc-fileset) ',files)
-
-      (shrink-window-if-larger-than-buffer)
-      ;; move point to the log entry for the working revision
-      (vc-call-backend ',backend 'show-log-entry ',working-revision)
-      (setq vc-sentinel-movepoint (point))
-      (set-buffer-modified-p nil))))
+  (let ((dir-present nil)
+       (vc-short-log nil))
+    (dolist (file files)
+      (when (file-directory-p file)
+       (setq dir-present t)))
+    (setq vc-short-log
+         (not (null (if dir-present
+                        (memq 'directory vc-log-short-style)
+                      (memq 'file vc-log-short-style)))))
+    (vc-call-backend backend 'print-log files "*vc-change-log*" vc-short-log)
+    (pop-to-buffer "*vc-change-log*")
+    (vc-exec-after
+     `(let ((inhibit-read-only t)
+           (vc-short-log ,vc-short-log))
+       (vc-call-backend ',backend 'log-view-mode)
+       (set (make-local-variable 'log-view-vc-backend) ',backend)
+       (set (make-local-variable 'log-view-vc-fileset) ',files)
+
+       (shrink-window-if-larger-than-buffer)
+       ;; move point to the log entry for the working revision
+       (vc-call-backend ',backend 'show-log-entry ',working-revision)
+       (setq vc-sentinel-movepoint (point))
+       (set-buffer-modified-p nil)))))
 
 ;;;###autoload
 (defun vc-print-log (&optional working-revision)
@@ -1851,6 +1899,20 @@ If WORKING-REVISION is non-nil, leave the point at that revision."
         (working-revision (or working-revision (vc-working-revision (car files)))))
     (vc-print-log-internal backend files working-revision)))
 
+;;;###autoload
+(defun vc-print-root-log ()
+  "List the change log of for the current VC controlled tree in a window."
+  (interactive)
+  (let ((backend
+        (cond ((derived-mode-p 'vc-dir-mode)  vc-dir-backend)
+              (vc-mode (vc-backend buffer-file-name))))
+       rootdir working-revision)
+    (unless backend
+      (error "Buffer is not version controlled"))
+    (setq rootdir (vc-call-backend backend 'root default-directory))
+    (setq working-revision (vc-working-revision rootdir))
+    (vc-print-log-internal backend (list rootdir) working-revision)))
+
 ;;;###autoload
 (defun vc-revert ()
   "Revert working copies of the selected fileset to their repository contents.
@@ -1868,7 +1930,7 @@ to the working revision (except for keyword expansion)."
     (dolist (file files)
       (let ((buf (get-file-buffer file)))
        (when (and buf (buffer-modified-p buf))
-         (error "Please kill or save all modified buffers before reverting.")))
+         (error "Please kill or save all modified buffers before reverting")))
       (when (vc-up-to-date-p file)
        (unless (yes-or-no-p (format "%s seems up-to-date.  Revert anyway? " file))
          (error "Revert canceled"))))
@@ -1903,7 +1965,7 @@ depending on the underlying version-control system."
       (error "Rollback requires a singleton fileset or repository versioning"))
     ;; FIXME: latest-on-branch-p should take the fileset.
     (when (not (vc-call-backend backend 'latest-on-branch-p (car files)))
-      (error "Rollback is only possible at the tip revision."))
+      (error "Rollback is only possible at the tip revision"))
     ;; If any of the files is visited by the current buffer, make
     ;; sure buffer is saved.  If the user says `no', abort since
     ;; we cannot show the changes and ask for confirmation to
@@ -1912,9 +1974,9 @@ depending on the underlying version-control system."
       (vc-buffer-sync nil))
     (dolist (file files)
       (when (buffer-modified-p (get-file-buffer file))
-       (error "Please kill or save all modified buffers before rollback."))
+       (error "Please kill or save all modified buffers before rollback"))
       (when (not (vc-up-to-date-p file))
-       (error "Please revert all modified workfiles before rollback.")))
+       (error "Please revert all modified workfiles before rollback")))
     ;; Accumulate changes associated with the fileset
     (vc-setup-buffer "*vc-diff*")
     (not-modified)