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.
This commit is contained in:
Chong Yidong 2010-11-22 20:15:08 -05:00
parent ef6a29070d
commit 2c3160c54e
3 changed files with 223 additions and 79 deletions

View file

@ -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.

View file

@ -115,6 +115,8 @@ Invoke the bzr command adding `BZR_PROGRESS_BAR=none' and
(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 @@ Invoke the bzr command adding `BZR_PROGRESS_BAR=none' and
(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 @@ Invoke the bzr command adding `BZR_PROGRESS_BAR=none' and
"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 @@ Invoke the bzr command adding `BZR_PROGRESS_BAR=none' and
(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

View file

@ -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 @@ The headers are reset to their non-expanded form."
'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 @@ depending on the underlying version-control system."
(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.