changeset 111679:33ed3cf8260b

Initial support for unified DVCS pull and merge. * lisp/vc/vc-bzr.el (vc-bzr-admin-branchconf, vc-bzr-history): New vars. (vc-bzr--branch-conf, vc-bzr-async-command, vc-bzr-pull) (vc-bzr-merge-branch): New functions, implementing merge-branch and pull operations. * lisp/vc/vc.el (vc-merge): Use vc-BACKEND-merge-branch if available. Accept optional prefix arg meaning to prompt for a command. (vc-update): Use vc-BACKEND-pull if available. Accept optional prefix arg meaning to prompt for a command. (vc-pull): Alias for vc-update.
author Chong Yidong <cyd@stupidchicken.com>
date Mon, 22 Nov 2010 20:15:08 -0500
parents 3b6c0c4ef2bb
children 2b1a10988f96
files lisp/ChangeLog lisp/vc/vc-bzr.el lisp/vc/vc.el
diffstat 3 files changed, 223 insertions(+), 79 deletions(-) [+]
line wrap: on
line diff
--- a/lisp/ChangeLog	Tue Nov 23 00:03:44 2010 +0000
+++ b/lisp/ChangeLog	Mon Nov 22 20:15:08 2010 -0500
@@ -1,3 +1,16 @@
+2010-11-23  Chong Yidong  <cyd@stupidchicken.com>
+
+	* vc/vc.el (vc-merge): Use vc-BACKEND-merge-branch if available.
+	Accept optional prefix arg meaning to prompt for a command.
+	(vc-update): Use vc-BACKEND-pull if available.  Accept optional
+	prefix arg meaning to prompt for a command.
+	(vc-pull): Alias for vc-update.
+
+	* vc/vc-bzr.el (vc-bzr-admin-branchconf, vc-bzr-history): New vars.
+	(vc-bzr--branch-conf, vc-bzr-async-command, vc-bzr-pull)
+	(vc-bzr-merge-branch): New functions, implementing merge-branch
+	and pull operations.
+
 2010-11-22  Stefan Monnier  <monnier@iro.umontreal.ca>
 
 	* Makefile.in: Fix up last merge.
--- a/lisp/vc/vc-bzr.el	Tue Nov 23 00:03:44 2010 +0000
+++ b/lisp/vc/vc-bzr.el	Mon Nov 22 20:15:08 2010 -0500
@@ -115,6 +115,8 @@
   (concat vc-bzr-admin-dirname "/branch/revision-history"))
 (defconst vc-bzr-admin-lastrev
   (concat vc-bzr-admin-dirname "/branch/last-revision"))
+(defconst vc-bzr-admin-branchconf
+  (concat vc-bzr-admin-dirname "/branch/branch.conf"))
 
 ;;;###autoload (defun vc-bzr-registered (file)
 ;;;###autoload   (if (vc-find-root file vc-bzr-admin-checkout-format-file)
@@ -129,6 +131,13 @@
       (let ((root (vc-find-root file vc-bzr-admin-checkout-format-file)))
 	(when root (vc-file-setprop file 'bzr-root root)))))
 
+(defun vc-bzr--branch-conf (file)
+  "Return the Bzr branch config for file FILE, as a string."
+  (with-temp-buffer
+    (insert-file-contents
+     (expand-file-name vc-bzr-admin-branchconf (vc-bzr-root file)))
+    (buffer-string)))
+
 (require 'sha1)                         ;For sha1-program
 
 (defun vc-bzr-sha1 (file)
@@ -228,6 +237,9 @@
   "added\\|ignored\\|kind changed\\|modified\\|removed\\|renamed\\|unknown"
   "Regexp matching file status words as reported in `bzr' output.")
 
+;; History of Bzr commands.
+(defvar vc-bzr-history nil)
+
 (defun vc-bzr-file-name-relative (filename)
   "Return file name FILENAME stripped of the initial Bzr repository path."
   (lexical-let*
@@ -236,6 +248,87 @@
     (when rootdir
          (file-relative-name filename* rootdir))))
 
+(defun vc-bzr-async-command (command args)
+  "Run Bzr COMMAND asynchronously with ARGS, displaying the result.
+Send the output to a buffer named \"*vc-bzr : NAME*\", where NAME
+is the root of the current Bzr branch.  Display the buffer in
+some window, but don't select it."
+  ;; TODO: set up hyperlinks.
+  (let* ((dir default-directory)
+	 (root (vc-bzr-root default-directory))
+	 (buffer (get-buffer-create (format "*vc-bzr : %s*" root))))
+    (with-current-buffer buffer
+      (setq default-directory root)
+      (goto-char (point-max))
+      (unless (eq (point) (point-min))
+	(insert "\n"))
+      (insert "Running \"" vc-bzr-program " " command)
+      (dolist (arg args)
+	(insert " " arg))
+      (insert "\"...\n")
+      ;; Run bzr in the original working directory.
+      (let ((default-directory dir))
+	(apply 'vc-bzr-command command t 'async nil args)))
+    (display-buffer buffer)))
+
+(defun vc-bzr-pull (prompt)
+  "Pull changes into the current Bzr branch.
+Normally, this runs \"bzr pull\".  However, if the branch is a
+bound branch, run \"bzr update\" instead.  If there is no default
+location from which to pull or update, or if PROMPT is non-nil,
+prompt for the Bzr command to run."
+  (let* ((vc-bzr-program vc-bzr-program)
+	 (branch-conf (vc-bzr--branch-conf default-directory))
+	 ;; Check whether the branch is bound.
+	 (bound (string-match "^bound\\s-*=\\s-*True" branch-conf))
+	 ;; If we need to do a "bzr pull", check for a parent.  If it
+	 ;; does not exist, bzr will need a pull location.
+	 (parent (unless bound
+		   (string-match
+		    "^parent_location\\s-*=\\s-*[^\n[:space:]]+"
+		    branch-conf)))
+	 (command (if bound "update" "pull"))
+	 args buf)
+    ;; If necessary, prompt for the exact command.
+    (when (or prompt (not (or bound parent)))
+      (setq args (split-string
+		  (read-shell-command
+		   "Run Bzr (like this): "
+		   (concat vc-bzr-program " " command)
+		   'vc-bzr-history)
+		  " " t))
+      (setq vc-bzr-program (car  args)
+	    command        (cadr args)
+	    args           (cddr args)))
+    (vc-bzr-async-command command args)))
+
+(defun vc-bzr-merge-branch (prompt)
+  "Merge another Bzr branch into the current one.
+If a default merge source is defined (i.e. an upstream branch or
+a previous merge source), this normally runs \"bzr merge --pull\".
+If optional PROMPT is non-nil or no default merge source is
+defined, prompt for the Bzr command to run."
+  (let* ((vc-bzr-program vc-bzr-program)
+	 (command "merge")
+	 (args '("--pull"))
+	 command-string args buf)
+    (when (or prompt
+	      ;; Prompt if there is no default merge source.
+	      (null
+	       (string-match
+		"^\\(parent_location\\|submit_branch\\)\\s-*=\\s-*[^\n[:space:]]+"
+		(vc-bzr--branch-conf default-directory))))
+      (setq args (split-string
+		  (read-shell-command
+		   "Run Bzr (like this): "
+		   (concat vc-bzr-program " " command " --pull")
+		   'vc-bzr-history)
+		  " " t))
+      (setq vc-bzr-program (car  args)
+	    command        (cadr args)
+	    args           (cddr args)))
+    (vc-bzr-async-command command args)))
+
 (defun vc-bzr-status (file)
   "Return FILE status according to Bzr.
 Return value is a cons (STATUS . WARNING), where WARNING is a
--- a/lisp/vc/vc.el	Tue Nov 23 00:03:44 2010 +0000
+++ b/lisp/vc/vc.el	Mon Nov 22 20:15:08 2010 -0500
@@ -100,7 +100,7 @@
 ;; In the list of functions below, each identifier needs to be prepended
 ;; with `vc-sys-'.  Some of the functions are mandatory (marked with a
 ;; `*'), others are optional (`-').
-;;
+
 ;; BACKEND PROPERTIES
 ;;
 ;; * revision-granularity
@@ -109,7 +109,7 @@
 ;;   that return 'file have per-file revision numbering; backends
 ;;   that return 'repository have per-repository revision numbering,
 ;;   so a revision level implicitly identifies a changeset
-;;
+
 ;; STATE-QUERYING FUNCTIONS
 ;;
 ;; * registered (file)
@@ -313,11 +313,24 @@
 ;;
 ;; - merge (file rev1 rev2)
 ;;
-;;   Merge the changes between REV1 and REV2 into the current working file.
+;;   Merge the changes between REV1 and REV2 into the current working file
+;;   (for non-distributed VCS).
+;;
+;; - merge-branch (prompt)
+;;
+;;   Merge another branch into the current one.  If PROMPT is non-nil,
+;;   or if necessary, prompt for a location to merge from.
 ;;
 ;; - merge-news (file)
 ;;
 ;;   Merge recent changes from the current branch into FILE.
+;;   (for non-distributed VCS).
+;;
+;; - pull (prompt)
+;;
+;;   Pull "upstream" changes into the current branch (for distributed
+;;   VCS).  If PROMPT is non-nil, or if necessary, prompt for a
+;;   location to pull from.
 ;;
 ;; - steal-lock (file &optional revision)
 ;;
@@ -335,7 +348,7 @@
 ;;
 ;;   Mark conflicts as resolved.  Some VC systems need to run a
 ;;   command to mark conflicts as resolved.
-;;
+
 ;; HISTORY FUNCTIONS
 ;;
 ;; * print-log (files buffer &optional shortlog start-revision limit)
@@ -440,7 +453,7 @@
 ;;   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
 ;;
 ;; - create-tag (dir name branchp)
@@ -461,7 +474,7 @@
 ;;   does a sanity check whether there aren't any uncommitted changes at
 ;;   or below DIR, and then performs a tree walk, using the `checkout'
 ;;   function to retrieve the corresponding revisions.
-;;
+
 ;; MISCELLANEOUS
 ;;
 ;; - make-version-backups-p (file)
@@ -1815,54 +1828,67 @@
                           'modify-change-comment files rev comment))))))
 
 ;;;###autoload
-(defun vc-merge ()
-  "Merge changes between two revisions into the current buffer's file.
-This asks for two revisions to merge from in the minibuffer.  If the
-first revision is a branch number, then merge all changes from that
-branch.  If the first revision is empty, merge news, i.e. recent changes
-from the current branch.
+(defun vc-merge (&optional arg)
+  "Perform a version control merge operation.
+On a distributed version control system, this runs a \"merge\"
+operation to incorporate changes from another branch onto the
+current branch, prompting for an argument list if required.
+Optional prefix ARG forces a prompt.
 
-See Info node `Merging'."
-  (interactive)
-  (vc-ensure-vc-buffer)
-  (vc-buffer-sync)
-  (let* ((file buffer-file-name)
-	 (backend (vc-backend file))
-	 (state (vc-state file))
-	 first-revision second-revision status)
+On a non-distributed version control system, this merges changes
+between two revisions into the current fileset.  This asks for
+two revisions to merge from in the minibuffer.  If the first
+revision is a branch number, then merge all changes from that
+branch.  If the first revision is empty, merge the most recent
+changes from the current branch."
+  (interactive "P")
+  (let* ((vc-fileset (vc-deduce-fileset t))
+	 (backend (car vc-fileset))
+	 (files (cadr vc-fileset)))
     (cond
-     ((stringp state)	;; Locking VCses only
-      (error "File is locked by %s" state))
-     ((not (vc-editable-p file))
-      (if (y-or-n-p
-	   "File must be checked out for merging.  Check out now? ")
-	  (vc-checkout file t)
-	(error "Merge aborted"))))
-    (setq first-revision
-	  (vc-read-revision
-           (concat "Branch or revision to merge from "
-                   "(default news on current branch): ")
-           (list file)
-           backend))
-    (if (string= first-revision "")
-        (setq status (vc-call-backend backend 'merge-news file))
-      (if (not (vc-find-backend-function backend 'merge))
-	  (error "Sorry, merging is not implemented for %s" backend)
-	(if (not (vc-branch-p first-revision))
-	    (setq second-revision
-		  (vc-read-revision
-                   "Second revision: "
-                   (list file) backend nil
-                   ;; FIXME: This is CVS/RCS/SCCS specific.
-                   (concat (vc-branch-part first-revision) ".")))
-	  ;; We want to merge an entire branch.  Set revisions
-	  ;; accordingly, so that vc-BACKEND-merge understands us.
-	  (setq second-revision first-revision)
-	  ;; first-revision must be the starting point of the branch
-	  (setq first-revision (vc-branch-part first-revision)))
-	(setq status (vc-call-backend backend 'merge file
-                                      first-revision second-revision))))
-    (vc-maybe-resolve-conflicts file status "WORKFILE" "MERGE SOURCE")))
+     ;; If a branch-merge operation is defined, use it.
+     ((vc-find-backend-function backend 'merge-branch)
+      (vc-call-backend backend 'merge-branch arg))
+     ;; Otherwise, do a per-file merge.
+     ((vc-find-backend-function backend 'merge)
+      (vc-buffer-sync)
+      (dolist (file files)
+	(let* ((state (vc-state file))
+	       first-revision second-revision status)
+	  (cond
+	   ((stringp state)	;; Locking VCses only
+	    (error "File %s is locked by %s" file state))
+	   ((not (vc-editable-p file))
+	    (vc-checkout file t)))
+	  (setq first-revision
+		(vc-read-revision
+		 (concat "Merge " file
+			 "from branch or revision "
+			 "(default news on current branch): ")
+		 (list file)
+		 backend))
+	  (cond
+	   ((string= first-revision "")
+	    (setq status (vc-call-backend backend 'merge-news file)))
+	   (t
+	    (if (not (vc-branch-p first-revision))
+		(setq second-revision
+		      (vc-read-revision
+		       "Second revision: "
+		       (list file) backend nil
+		       ;; FIXME: This is CVS/RCS/SCCS specific.
+		       (concat (vc-branch-part first-revision) ".")))
+	      ;; We want to merge an entire branch.  Set revisions
+	      ;; accordingly, so that vc-BACKEND-merge understands us.
+	      (setq second-revision first-revision)
+	      ;; first-revision must be the starting point of the branch
+	      (setq first-revision (vc-branch-part first-revision)))
+	    (setq status (vc-call-backend backend 'merge file
+					  first-revision second-revision))))
+	  (vc-maybe-resolve-conflicts file status "WORKFILE" "MERGE SOURCE"))))
+     (t
+      (error "Sorry, merging is not implemented for %s" backend)))))
+
 
 (defun vc-maybe-resolve-conflicts (file status &optional name-A name-B)
   (vc-resynch-buffer file t (not (buffer-modified-p)))
@@ -2274,35 +2300,47 @@
 (define-obsolete-function-alias 'vc-revert-buffer 'vc-revert "23.1")
 
 ;;;###autoload
-(defun vc-update ()
-  "Update the current fileset's files to their tip revisions.
-For each one that contains no changes, and is not locked, then this simply
-replaces the work file with the latest revision on its branch.  If the file
-contains changes, and the backend supports merging news, then any recent
-changes from the current branch are merged into the working file."
-  (interactive)
-  (let* ((vc-fileset (vc-deduce-fileset))
+(defun vc-update (&optional arg)
+  "Update the current fileset or branch.
+On a distributed version control system, this runs a \"pull\"
+operation to update the current branch, prompting for an argument
+list if required.  Optional prefix ARG forces a prompt.
+
+On a non-distributed version control system, update the current
+fileset to the tip revisions.  For each unchanged and unlocked
+file, this simply replaces the work file with the latest revision
+on its branch.  If the file contains changes, any changes in the
+tip revision are merged into the working file."
+  (interactive "P")
+  (let* ((vc-fileset (vc-deduce-fileset t))
 	 (backend (car vc-fileset))
 	 (files (cadr vc-fileset)))
-    (save-some-buffers          ; save buffers visiting files
-     nil (lambda ()
-           (and (buffer-modified-p)
-                (let ((file (buffer-file-name)))
-                  (and file (member file files))))))
-    (dolist (file files)
-      (if (vc-up-to-date-p file)
-	  (vc-checkout file nil t)
-	(if (eq (vc-checkout-model backend (list file)) 'locking)
-	    (if (eq (vc-state file) 'edited)
-		(error "%s"
-		       (substitute-command-keys
-			"File is locked--type \\[vc-revert] to discard changes"))
-	      (error "Unexpected file state (%s) -- type %s"
-		     (vc-state file)
-		     (substitute-command-keys
-		      "\\[vc-next-action] to correct")))
-          (vc-maybe-resolve-conflicts
-           file (vc-call-backend backend 'merge-news file)))))))
+    (cond
+     ;; If a pull operation is defined, use it.
+     ((vc-find-backend-function backend 'pull)
+      (vc-call-backend backend 'pull arg))
+     ;; If VCS has `merge-news' functionality (CVS and SVN), use it.
+     ((vc-find-backend-function backend 'merge-news)
+      (save-some-buffers ; save buffers visiting files
+       nil (lambda ()
+	     (and (buffer-modified-p)
+		  (let ((file (buffer-file-name)))
+		    (and file (member file files))))))
+      (dolist (file files)
+	(if (vc-up-to-date-p file)
+	    (vc-checkout file nil t)
+	  (vc-maybe-resolve-conflicts
+	   file (vc-call-backend backend 'merge-news file)))))
+     ;; For a locking VCS, check out each file.
+     ((eq (vc-checkout-model backend files) 'locking)
+      (dolist (file files)
+	(if (vc-up-to-date-p file)
+	    (vc-checkout file nil t))))
+     (t
+      (error "VC update is unsupported for `%s'" backend)))))
+
+;;;###autoload
+(defalias 'vc-pull 'vc-update)
 
 (defun vc-version-backup-file (file &optional rev)
   "Return name of backup file for revision REV of FILE.