changeset 3460:afb106e08cda

merge with mpm
author Josef "Jeff" Sipek <jeffpc@josefsipek.net>
date Sat, 21 Oct 2006 15:22:08 -0400
parents a75d609ded05 (current diff) dba3cadef789 (diff)
children ad4b5ef8a051
files templates/changelog-gitweb.tmpl templates/changelog-rss.tmpl templates/changelogentry-gitweb.tmpl templates/changelogentry-rss.tmpl templates/changeset-gitweb.tmpl templates/changeset-raw.tmpl templates/error-gitweb.tmpl templates/fileannotate-gitweb.tmpl templates/fileannotate-raw.tmpl templates/filediff-raw.tmpl templates/filelog-gitweb.tmpl templates/filelog-rss.tmpl templates/filelogentry-rss.tmpl templates/filerevision-gitweb.tmpl templates/footer-gitweb.tmpl templates/gitweb/changelog.tmpl templates/gitweb/changeset.tmpl templates/gitweb/error.tmpl templates/gitweb/fileannotate.tmpl templates/gitweb/filelog.tmpl templates/gitweb/filerevision.tmpl templates/gitweb/manifest.tmpl templates/gitweb/map templates/gitweb/search.tmpl templates/gitweb/shortlog.tmpl templates/gitweb/summary.tmpl templates/gitweb/tags.tmpl templates/header-gitweb.tmpl templates/header-raw.tmpl templates/header-rss.tmpl templates/manifest-gitweb.tmpl templates/map-gitweb templates/map-raw templates/map-rss templates/old/changelog.tmpl templates/old/changelogentry.tmpl templates/old/changeset.tmpl templates/old/fileannotate.tmpl templates/old/filediff.tmpl templates/old/filelog.tmpl templates/old/filelogentry.tmpl templates/old/filerevision.tmpl templates/old/manifest.tmpl templates/old/map templates/old/search.tmpl templates/old/shortlog.tmpl templates/old/tags.tmpl templates/raw/map templates/rss/filelogentry.tmpl templates/rss/map templates/search-gitweb.tmpl templates/shortlog-gitweb.tmpl templates/summary-gitweb.tmpl templates/tagentry-rss.tmpl templates/tags-gitweb.tmpl templates/tags-rss.tmpl
diffstat 302 files changed, 11665 insertions(+), 3562 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Sat Sep 02 22:58:02 2006 -0400
+++ b/.hgignore	Sat Oct 21 15:22:08 2006 -0400
@@ -21,6 +21,7 @@
 MANIFEST
 patches
 mercurial/__version__.py
+.DS_Store
 
 syntax: regexp
 ^\.pc/
--- a/CONTRIBUTORS	Sat Sep 02 22:58:02 2006 -0400
+++ b/CONTRIBUTORS	Sat Oct 21 15:22:08 2006 -0400
@@ -4,6 +4,7 @@
 Muli Ben-Yehuda <mulix at mulix.org>
 Mikael Berthe <mikael at lilotux.net>
 Benoit Boissinot <bboissin at gmail.com>
+Brendan Cully <brendan at kublai.com>
 Vincent Danjean <vdanjean.ml at free.fr>
 Jake Edge <jake at edge2.net>
 Michael Fetterman <michael.fetterman at intel.com>
--- a/MANIFEST.in	Sat Sep 02 22:58:02 2006 -0400
+++ b/MANIFEST.in	Sat Oct 21 15:22:08 2006 -0400
@@ -2,13 +2,11 @@
 recursive-include mercurial *.py
 include hgweb.cgi hgwebdir.cgi
 include hgeditor rewrite-log
-include tests/README tests/*.py tests/test-*[a-z0-9] tests/*.out
+include tests/README tests/*.py tests/test-*[a-z0-9] tests/*.out tests/*.bin
 prune tests/*.err
 include *.txt
-include templates/map templates/map-*[a-z0-9]
-include templates/*.tmpl
-include templates/static/*
 include doc/README doc/Makefile doc/gendoc.py doc/*.txt doc/*.html doc/*.[0-9]
+recursive-include templates *
 recursive-include contrib *
 recursive-include hgext *
 include README
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/churn.py	Sat Oct 21 15:22:08 2006 -0400
@@ -0,0 +1,179 @@
+# churn.py - create a graph showing who changed the most lines
+#
+# Copyright 2006 Josef "Jeff" Sipek <jeffpc@josefsipek.net>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+#
+#
+# Aliases map file format is simple one alias per line in the following
+# format:
+#
+# <alias email> <actual email>
+
+from mercurial.demandload import *
+from mercurial.i18n import gettext as _
+demandload(globals(), 'time sys signal os')
+demandload(globals(), 'mercurial:hg,mdiff,fancyopts,cmdutil,ui,util,templater,node')
+
+def __gather(ui, repo, node1, node2):
+    def dirtywork(f, mmap1, mmap2):
+        lines = 0
+
+        to = mmap1 and repo.file(f).read(mmap1[f]) or None
+        tn = mmap2 and repo.file(f).read(mmap2[f]) or None
+
+        diff = mdiff.unidiff(to, "", tn, "", f).split("\n")
+
+        for line in diff:
+            if not line:
+                continue # skip EOF
+            if line.startswith(" "):
+                continue # context line
+            if line.startswith("--- ") or line.startswith("+++ "):
+                continue # begining of diff
+            if line.startswith("@@ "):
+                continue # info line
+
+            # changed lines
+            lines += 1
+
+        return lines
+
+    ##
+
+    lines = 0
+
+    changes = repo.status(node1, node2, None, util.always)[:5]
+
+    modified, added, removed, deleted, unknown = changes
+
+    who = repo.changelog.read(node2)[1]
+    who = templater.email(who) # get the email of the person
+
+    mmap1 = repo.manifest.read(repo.changelog.read(node1)[0])
+    mmap2 = repo.manifest.read(repo.changelog.read(node2)[0])
+    for f in modified:
+        lines += dirtywork(f, mmap1, mmap2)
+
+    for f in added:
+        lines += dirtywork(f, None, mmap2)
+
+    for f in removed:
+        lines += dirtywork(f, mmap1, None)
+
+    for f in deleted:
+        lines += dirtywork(f, mmap1, mmap2)
+
+    for f in unknown:
+        lines += dirtywork(f, mmap1, mmap2)
+
+    return (who, lines)
+
+def gather_stats(ui, repo, amap, revs=None, progress=False):
+    stats = {}
+
+    cl    = repo.changelog
+
+    if not revs:
+        revs = range(0, cl.count())
+
+    nr_revs = len(revs)
+    cur_rev = 0
+
+    for rev in revs:
+        cur_rev += 1 # next revision
+
+        node2    = cl.node(rev)
+        node1    = cl.parents(node2)[0]
+
+        if cl.parents(node2)[1] != node.nullid:
+            ui.note(_('Revision %d is a merge, ignoring...\n') % (rev,))
+            continue
+
+        who, lines = __gather(ui, repo, node1, node2)
+
+        # remap the owner if possible
+        if amap.has_key(who):
+            ui.note("using '%s' alias for '%s'\n" % (amap[who], who))
+            who = amap[who]
+
+        if not stats.has_key(who):
+            stats[who] = 0
+        stats[who] += lines
+
+        ui.note("rev %d: %d lines by %s\n" % (rev, lines, who))
+
+        if progress:
+            if int(100.0*(cur_rev - 1)/nr_revs) < int(100.0*cur_rev/nr_revs):
+                ui.write("%d%%.." % (int(100.0*cur_rev/nr_revs),))
+                sys.stdout.flush()
+
+    if progress:
+        ui.write("done\n")
+        sys.stdout.flush()
+
+    return stats
+
+def churn(ui, repo, **opts):
+    "Graphs the number of lines changed"
+
+    def pad(s, l):
+        if len(s) < l:
+            return s + " " * (l-len(s))
+        return s[0:l]
+
+    def graph(n, maximum, width, char):
+        n = int(n * width / float(maximum))
+
+        return char * (n)
+
+    def get_aliases(f):
+        aliases = {}
+
+        for l in f.readlines():
+            l = l.strip()
+            alias, actual = l.split(" ")
+            aliases[alias] = actual
+
+        return aliases
+
+    amap = {}
+    aliases = opts.get('aliases')
+    if aliases:
+        try:
+            f = open(aliases,"r")
+        except OSError, e:
+            print "Error: " + e
+            return
+
+        amap = get_aliases(f)
+        f.close()
+
+    revs = [int(r) for r in cmdutil.revrange(ui, repo, opts['rev'])]
+    revs.sort()
+    stats = gather_stats(ui, repo, amap, revs, opts.get('progress'))
+
+    # make a list of tuples (name, lines) and sort it in descending order
+    ordered = stats.items()
+    ordered.sort(lambda x, y: cmp(y[1], x[1]))
+
+    maximum = ordered[0][1]
+
+    ui.note("Assuming 80 character terminal\n")
+    width = 80 - 1
+
+    for i in ordered:
+        person = i[0]
+        lines = i[1]
+        print "%s %6d %s" % (pad(person, 20), lines,
+                graph(lines, maximum, width - 20 - 1 - 6 - 2 - 2, '*'))
+
+cmdtable = {
+    "churn":
+    (churn,
+     [('r', 'rev', [], _('limit statistics to the specified revisions')),
+      ('', 'aliases', '', _('file with email aliases')),
+      ('', 'progress', None, _('show progress'))],
+    'hg churn [-r revision range] [-a file] [--progress]'),
+}
--- a/contrib/hgdiff	Sat Sep 02 22:58:02 2006 -0400
+++ b/contrib/hgdiff	Sat Oct 21 15:22:08 2006 -0400
@@ -5,9 +5,9 @@
 import re
 from optparse import OptionParser
 from mercurial.bdiff import bdiff, blocks
-from mercurial.mdiff import bunidiff
+from mercurial.mdiff import bunidiff, diffopts
 
-VERSION="0.2"
+VERSION="0.3"
 usage = "usage: %prog [options] file1 file2"
 parser = OptionParser(usage=usage)
 
@@ -57,9 +57,10 @@
         if options.difflib:
             l = difflib.unified_diff(l1, l2, file1, file2)
         else:
-            l = bunidiff(t1, t2, l1, l2, file1, file2, context=options.context,
-                     showfunc=options.show_c_function,
-                     ignorews=options.ignore_all_space)
+            l = bunidiff(t1, t2, l1, l2, file1, file2,
+                         diffopts(context=options.context,
+                                  showfunc=options.show_c_function,
+                                  ignorews=options.ignore_all_space))
     for x in l:
         if x[-1] != '\n':
             x += "\n\ No newline at end of file\n"
--- a/contrib/hgk	Sat Sep 02 22:58:02 2006 -0400
+++ b/contrib/hgk	Sat Oct 21 15:22:08 2006 -0400
@@ -30,15 +30,29 @@
     set startmsecs [clock clicks -milliseconds]
     set nextupdate [expr $startmsecs + 100]
     set ncmupdate 1
+    set limit 0
+    set revargs {}
+    for {set i 0} {$i < [llength $rargs]} {incr i} {
+	set opt [lindex $rargs $i]
+	if {$opt == "--limit"} {
+	    incr i
+	    set limit [lindex $rargs $i]
+	} else {
+	    lappend revargs $opt
+	}
+    }
     if [catch {
-	set parse_args [concat --default HEAD $rargs]
+	set parse_args [concat --default HEAD $revargs]
 	set parsed_args [split [eval exec hg debug-rev-parse $parse_args] "\n"]
-    }] {
+    } err] {
 	# if git-rev-parse failed for some reason...
 	if {$rargs == {}} {
-	    set rargs HEAD
+	    set revargs HEAD
 	}
-	set parsed_args $rargs
+	set parsed_args $revargs
+    }
+    if {$limit > 0} {
+	set parsed_args [concat -n $limit $parsed_args]
     }
     if [catch {
 	set commfd [open "|hg debug-rev-list --header --topo-order --parents $parsed_args" r]
@@ -100,7 +114,7 @@
 	    set ids [string range $cmit 0 [expr {$j - 1}]]
 	    set ok 1
 	    foreach id $ids {
-		if {![regexp {^[0-9a-f]{40}$} $id]} {
+		if {![regexp {^[0-9a-f]{12}$} $id]} {
 		    set ok 0
 		    break
 		}
@@ -176,6 +190,7 @@
     set audate {}
     set comname {}
     set comdate {}
+    set rev {}
     if {![info exists nchildren($id)]} {
 	set children($id) {}
 	set nchildren($id) 0
@@ -209,6 +224,8 @@
 		    set x [expr {[llength $line] - 2}]
 		    set comdate [lindex $line $x]
 		    set comname [join [lrange $line 1 [expr {$x - 1}]]]
+		} elseif {$tag == "revision"} {
+		    set rev [lindex $line 1]
 		}
 	    }
 	} else {
@@ -233,7 +250,7 @@
 	set comdate [clock format $comdate -format "%Y-%m-%d %H:%M:%S"]
     }
     set commitinfo($id) [list $headline $auname $audate \
-			     $comname $comdate $comment]
+			     $comname $comdate $comment $rev]
 }
 
 proc readrefs {} {
@@ -261,7 +278,7 @@
 	catch {
 	    set fd [open $f r]
 	    set line [read $fd 40]
-	    if {[regexp {^[0-9a-f]{40}} $line id]} {
+	    if {[regexp {^[0-9a-f]{12}} $line id]} {
 		set name "$dname[file tail $f]"
 		set otherrefids($name) $id
 		lappend idotherrefs($id) $name
@@ -1743,7 +1760,7 @@
 	}
 	return
     }
-    if {![regexp {^[0-9a-f]{40}} $line id]} {
+    if {![regexp {^[0-9a-f]{12}} $line id]} {
 	error_popup "Can't parse git-diff-tree output: $line"
 	stopfindproc
 	return
@@ -1856,7 +1873,7 @@
 	}
 	return
     }
-    if {[regexp {^([0-9a-f]{40}) \(from ([0-9a-f]{40})\)} $line match id p]} {
+    if {[regexp {^([0-9a-f]{12}) \(from ([0-9a-f]{12})\)} $line match id p]} {
 	# start of a new string of diffs
 	donefilediff
 	set fdiffids [list $id $p]
@@ -2002,8 +2019,9 @@
     set l "..."
     if {[info exists commitinfo($p)]} {
 	set l [lindex $commitinfo($p) 0]
+	set r [lindex $commitinfo($p) 6]
     }
-    return "$p ($l)"
+    return "$r:$p ($l)"
 }
 
 # append some text to the ctext widget, and make any SHA1 ID
@@ -2014,7 +2032,7 @@
     set start [$ctext index "end - 1c"]
     $ctext insert end $text
     $ctext insert end "\n"
-    set links [regexp -indices -all -inline {[0-9a-f]{40}} $text]
+    set links [regexp -indices -all -inline {[0-9a-f]{12}} $text]
     foreach l $links {
 	set s [lindex $l 0]
 	set e [lindex $l 1]
@@ -2107,6 +2125,7 @@
     $ctext mark set fmark.0 0.0
     $ctext mark gravity fmark.0 left
     set info $commitinfo($id)
+    $ctext insert end "Revision: [lindex $info 6]\n"
     $ctext insert end "Author: [lindex $info 1]  [lindex $info 2]\n"
     $ctext insert end "Committer: [lindex $info 3]  [lindex $info 4]\n"
     if {[info exists idtags($id)]} {
--- a/contrib/macosx/Readme.html	Sat Sep 02 22:58:02 2006 -0400
+++ b/contrib/macosx/Readme.html	Sat Oct 21 15:22:08 2006 -0400
@@ -25,7 +25,7 @@
 <p class="p2"><br></p>
 <p class="p1"><b>After you install</b></p>
 <p class="p2"><br></p>
-<p class="p3">This package installs the <span class="s2">hg</span> executable in <span class="s2">/usr/local/bin</span>. This directory may not be in your shell's search path. Don't forget to check.</p>
+<p class="p3">This package installs the <span class="s2">hg</span> executable in <span class="s2">/Library/Frameworks/Python.framework/Versions/Current/bin</span>. This directory may not be in your shell's search path. The MacPython installer will have created an entry in <span class="s2">.profile</span> for it but if your shell doesn't use <span class="s2">.profile</span> you'll need configure it yourself or create a symlink from a directory already in your path.</p>
 <p class="p2"><br></p>
 <p class="p1"><b>Reporting problems</b></p>
 <p class="p2"><br></p>
--- a/contrib/mercurial.el	Sat Sep 02 22:58:02 2006 -0400
+++ b/contrib/mercurial.el	Sat Oct 21 15:22:08 2006 -0400
@@ -1,6 +1,6 @@
 ;;; mercurial.el --- Emacs support for the Mercurial distributed SCM
 
-;; Copyright (C) 2005 Bryan O'Sullivan
+;; Copyright (C) 2005, 2006 Bryan O'Sullivan
 
 ;; Author: Bryan O'Sullivan <bos@serpentine.com>
 
@@ -289,7 +289,7 @@
 
 (defsubst hg-chomp (str)
   "Strip trailing newlines from a string."
-  (hg-replace-in-string str "[\r\n]+\'" ""))
+  (hg-replace-in-string str "[\r\n]+\\'" ""))
 
 (defun hg-run-command (command &rest args)
   "Run the shell command COMMAND, returning (EXIT-CODE . COMMAND-OUTPUT).
@@ -502,6 +502,43 @@
 			     (or default "tip")))
 	rev))))
 
+(defun hg-parents-for-mode-line (root)
+  "Format the parents of the working directory for the mode line."
+  (let ((parents (split-string (hg-chomp
+				(hg-run0 "--cwd" root "parents" "--template"
+					 "{rev}\n")) "\n")))
+    (mapconcat 'identity parents "+")))
+
+(defun hg-buffers-visiting-repo (&optional path)
+  "Return a list of buffers visiting the repository containing PATH."
+  (let ((root-name (hg-root (or path (buffer-file-name))))
+	bufs)
+    (save-excursion
+      (dolist (buf (buffer-list) bufs)
+	(set-buffer buf)
+	(let ((name (buffer-file-name)))
+	  (when (and hg-status name (equal (hg-root name) root-name))
+	    (setq bufs (cons buf bufs))))))))
+
+(defun hg-update-mode-lines (path)
+  "Update the mode lines of all buffers visiting the same repository as PATH."
+  (let* ((root (hg-root path))
+	 (parents (hg-parents-for-mode-line root)))
+    (save-excursion
+      (dolist (info (hg-path-status
+		     root
+		     (mapcar
+		      (function
+		       (lambda (buf)
+			 (substring (buffer-file-name buf) (length root))))
+		      (hg-buffers-visiting-repo root))))
+	(let* ((name (car info))
+	       (status (cdr info))
+	       (buf (find-buffer-visiting (concat root name))))
+	  (when buf
+	    (set-buffer buf)
+	    (hg-mode-line-internal status parents)))))))
+  
 (defmacro hg-do-across-repo (path &rest body)
   (let ((root-name (gensym "root-"))
 	(buf-name (gensym "buf-")))
@@ -548,13 +585,31 @@
 			    '(("M " . modified)
 			      ("A " . added)
 			      ("R " . removed)
+			      ("! " . deleted)
 			      ("? " . nil)))))
 	  (if state
 	      (cdr state)
 	    'normal)))))
 
-(defun hg-tip ()
-  (split-string (hg-chomp (hg-run0 "-q" "tip")) ":"))
+(defun hg-path-status (root paths)
+  "Return status of PATHS in repo ROOT as an alist.
+Each entry is a pair (FILE-NAME . STATUS)."
+  (let ((s (apply 'hg-run "--cwd" root "status" "-marduc" paths))
+	result)
+    (dolist (entry (split-string (hg-chomp (cdr s)) "\n") (nreverse result))
+      (let (state name)
+	(if (equal (substring entry 1 2) " ")
+	    (setq state (cdr (assoc (substring entry 0 2)
+				    '(("M " . modified)
+				      ("A " . added)
+				      ("R " . removed)
+				      ("! " . deleted)
+				      ("C " . normal)
+				      ("I " . ignored)
+				      ("? " . nil))))
+		  name (substring entry 2))
+	  (setq name (substring entry 0 (search ": " entry :from-end t))))
+	(setq result (cons (cons name state) result))))))
 
 (defmacro hg-view-output (args &rest body)
   "Execute BODY in a clean buffer, then quickly display that buffer.
@@ -589,7 +644,7 @@
 
 (put 'hg-view-output 'lisp-indent-function 1)
 
-;;; Context save and restore across revert.
+;;; Context save and restore across revert and other operations.
 
 (defun hg-position-context (pos)
   "Return information to help find the given position again."
@@ -631,22 +686,28 @@
 
 ;;; Hooks.
 
+(defun hg-mode-line-internal (status parents)
+  (setq hg-status status
+	hg-mode (and status (concat " Hg:"
+				    parents
+				    (cdr (assq status
+					       '((normal . "")
+						 (removed . "r")
+						 (added . "a")
+						 (deleted . "!")
+						 (modified . "m"))))))))
+  
 (defun hg-mode-line (&optional force)
   "Update the modeline with the current status of a file.
 An update occurs if optional argument FORCE is non-nil,
 hg-update-modeline is non-nil, or we have not yet checked the state of
 the file."
-  (when (and (hg-root) (or force hg-update-modeline (not hg-mode)))
-    (let ((status (hg-file-status buffer-file-name)))
-      (setq hg-status status
-	    hg-mode (and status (concat " Hg:"
-					(car (hg-tip))
-					(cdr (assq status
-						   '((normal . "")
-						     (removed . "r")
-						     (added . "a")
-						     (modified . "m")))))))
-      status)))
+  (let ((root (hg-root)))
+    (when (and root (or force hg-update-modeline (not hg-mode)))
+      (let ((status (hg-file-status buffer-file-name))
+	    (parents (hg-parents-for-mode-line root)))
+	(hg-mode-line-internal status parents)
+	status))))
 
 (defun hg-mode (&optional toggle)
   "Minor mode for Mercurial distributed SCM integration.
@@ -722,7 +783,14 @@
       (if (not hg-root-dir)
 	  (error "error: %s: directory is not part of a Mercurial repository."
 		 default-directory)
-	(cd (hg-root))))))
+	(cd hg-root-dir)))))
+
+(defun hg-fix-paths ()
+  "Fix paths reported by some Mercurial commands."
+  (save-excursion
+    (goto-char (point-min))
+    (while (re-search-forward " \\.\\.." nil t)
+      (replace-match " " nil nil))))
 
 (defun hg-add (path)
   "Add PATH to the Mercurial repository on the next commit.
@@ -732,9 +800,8 @@
 	(update (equal buffer-file-name path)))
     (hg-view-output (hg-output-buffer-name)
       (apply 'call-process (hg-binary) nil t nil (list "add" path))
-      ;; "hg add" shows pathes relative NOT TO ROOT BUT TO REPOSITORY
-      (replace-regexp " \\.\\.." " " nil 0 (buffer-size))
-      (goto-char 0)
+      (hg-fix-paths)
+      (goto-char (point-min))
       (cd (hg-root path)))
     (when update
       (unless vc-make-backup-files
@@ -820,8 +887,7 @@
       (let ((buf hg-prev-buffer))
 	(kill-buffer nil)
 	(switch-to-buffer buf))
-      (hg-do-across-repo root
-	(hg-mode-line)))))
+      (hg-update-mode-lines root))))
 
 (defun hg-commit-mode ()
   "Mode for describing a commit of changes to a Mercurial repository.
@@ -973,8 +1039,8 @@
     (hg-view-output (hg-output-buffer-name)
       (apply 'call-process (hg-binary) nil t nil (list "forget" path))
       ;; "hg forget" shows pathes relative NOT TO ROOT BUT TO REPOSITORY
-      (replace-regexp " \\.\\.." " " nil 0 (buffer-size))
-      (goto-char 0)
+      (hg-fix-paths)
+      (goto-char (point-min))
       (cd (hg-root path)))
     (when update
       (with-current-buffer buf
@@ -1148,6 +1214,21 @@
 	root)
     hg-root))
 
+(defun hg-cwd (&optional path)
+  "Return the current directory of PATH within the repository."
+  (do ((stack nil (cons (file-name-nondirectory
+			 (directory-file-name dir))
+			stack))
+       (prev nil dir)
+       (dir (file-name-directory (or path buffer-file-name
+				     (expand-file-name default-directory)))
+	    (file-name-directory (directory-file-name dir))))
+      ((equal prev dir))
+    (when (file-directory-p (concat dir ".hg"))
+      (let ((cwd (mapconcat 'identity stack "/")))
+	(unless (equal cwd "")
+	  (return (file-name-as-directory cwd)))))))
+
 (defun hg-status (path)
   "Print revision control status of a file or directory.
 With prefix argument, prompt for the path to give status for.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/mq.el	Sat Oct 21 15:22:08 2006 -0400
@@ -0,0 +1,327 @@
+;;; mq.el --- Emacs support for Mercurial Queues
+
+;; Copyright (C) 2006 Bryan O'Sullivan
+
+;; Author: Bryan O'Sullivan <bos@serpentine.com>
+
+;; mq.el is free software; you can redistribute it and/or modify it
+;; under the terms of version 2 of the GNU General Public License as
+;; published by the Free Software Foundation.
+
+;; mq.el is distributed in the hope that it will be useful, but
+;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+;; General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with mq.el, GNU Emacs, or XEmacs; see the file COPYING (`C-h
+;; C-l').  If not, write to the Free Software Foundation, Inc., 59
+;; Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+(require 'mercurial)
+
+
+(defcustom mq-mode-hook nil
+  "Hook run when a buffer enters mq-mode."
+  :type 'sexp
+  :group 'mercurial)
+
+(defcustom mq-global-prefix "\C-cq"
+  "The global prefix for Mercurial Queues keymap bindings."
+  :type 'sexp
+  :group 'mercurial)
+
+(defcustom mq-edit-mode-hook nil
+  "Hook run after a buffer is populated to edit a patch description."
+  :type 'sexp
+  :group 'mercurial)
+
+
+;;; Internal variables.
+
+(defvar mq-mode nil
+  "Is this file managed by MQ?")
+(make-variable-buffer-local 'mq-mode)
+(put 'mq-mode 'permanent-local t)
+
+(defvar mq-patch-history nil)
+
+(defvar mq-top-patch '(nil))
+
+(defvar mq-prev-buffer nil)
+(make-variable-buffer-local 'mq-prev-buffer)
+(put 'mq-prev-buffer 'permanent-local t)
+
+
+;;; Global keymap.
+
+(defvar mq-global-map (make-sparse-keymap))
+(fset 'mq-global-map mq-global-map)
+(global-set-key mq-global-prefix 'mq-global-map)
+(define-key mq-global-map "." 'mq-push)
+(define-key mq-global-map ">" 'mq-push-all)
+(define-key mq-global-map "," 'mq-pop)
+(define-key mq-global-map "<" 'mq-pop-all)
+(define-key mq-global-map "r" 'mq-refresh)
+(define-key mq-global-map "e" 'mq-refresh-edit)
+(define-key mq-global-map "n" 'mq-next)
+(define-key mq-global-map "p" 'mq-previous)
+(define-key mq-global-map "t" 'mq-top)
+
+(add-minor-mode 'mq-mode 'mq-mode)
+
+
+;;; Refresh edit mode keymap.
+
+(defvar mq-edit-mode-map (make-sparse-keymap))
+(define-key mq-edit-mode-map "\C-c\C-c" 'mq-edit-finish)
+(define-key mq-edit-mode-map "\C-c\C-k" 'mq-edit-kill)
+
+
+;;; Helper functions.
+
+(defun mq-read-patch-name (&optional source prompt)
+  "Read a patch name to use with a command.
+May return nil, meaning \"use the default\"."
+  (let ((patches (split-string
+		  (hg-chomp (hg-run0 (or source "qseries"))) "\n")))
+    (when current-prefix-arg
+      (completing-read (format "Patch%s: " (or prompt ""))
+		       (map 'list 'cons patches patches)
+		       nil
+		       nil
+		       nil
+		       'mq-patch-history))))
+
+(defun mq-refresh-buffers (root)
+  (save-excursion
+    (dolist (buf (hg-buffers-visiting-repo root))
+      (when (not (verify-visited-file-modtime buf))
+	(set-buffer buf)
+	(let ((ctx (hg-buffer-context)))
+	  (message "Refreshing %s..." (buffer-name))
+	  (revert-buffer t t t)
+	  (hg-restore-context ctx)
+	  (message "Refreshing %s...done" (buffer-name))))))
+  (hg-update-mode-lines root)
+  (mq-update-mode-lines root))
+
+(defun mq-last-line ()
+  (goto-char (point-max))
+  (beginning-of-line)
+  (when (looking-at "^$")
+    (forward-line -1))
+  (let ((bol (point)))
+    (end-of-line)
+    (let ((line (buffer-substring bol (point))))
+      (when (> (length line) 0)
+	line))))
+  
+(defun mq-push (&optional patch)
+  "Push patches until PATCH is reached.
+If PATCH is nil, push at most one patch."
+  (interactive (list (mq-read-patch-name "qunapplied" " to push")))
+  (let ((root (hg-root))
+	(prev-buf (current-buffer))
+	last-line ok)
+    (unless root
+      (error "Cannot push outside a repository!"))
+    (hg-sync-buffers root)
+    (let ((buf-name (format "MQ: Push %s" (or patch "next patch"))))
+      (kill-buffer (get-buffer-create buf-name))
+      (split-window-vertically)
+      (other-window 1)
+      (switch-to-buffer (get-buffer-create buf-name))
+      (cd root)
+      (message "Pushing...")
+      (setq ok (= 0 (apply 'call-process (hg-binary) nil t t "qpush"
+			   (if patch (list patch))))
+	    last-line (mq-last-line))
+      (let ((lines (count-lines (point-min) (point-max))))
+	(if (and (equal lines 2) (string-match "Now at:" last-line))
+	    (progn
+	      (kill-buffer (current-buffer))
+	      (delete-window))
+	  (hg-view-mode prev-buf))))
+    (mq-refresh-buffers root)
+    (sit-for 0)
+    (when last-line
+      (if ok
+	  (message "Pushing... %s" last-line)
+	(error "Pushing... %s" last-line)))))
+  
+(defun mq-push-all ()
+  "Push patches until all are applied."
+  (interactive)
+  (mq-push "-a"))
+
+(defun mq-pop (&optional patch)
+  "Pop patches until PATCH is reached.
+If PATCH is nil, pop at most one patch."
+  (interactive (list (mq-read-patch-name "qapplied" " to pop to")))
+  (let ((root (hg-root))
+	last-line ok)
+    (unless root
+      (error "Cannot pop outside a repository!"))
+    (hg-sync-buffers root)
+    (set-buffer (generate-new-buffer "qpop"))
+    (cd root)
+    (message "Popping...")
+    (setq ok (= 0 (apply 'call-process (hg-binary) nil t t "qpop"
+			 (if patch (list patch))))
+	  last-line (mq-last-line))
+    (kill-buffer (current-buffer))
+    (mq-refresh-buffers root)
+    (sit-for 0)
+    (when last-line
+      (if ok
+	  (message "Popping... %s" last-line)
+	(error "Popping... %s" last-line)))))
+  
+(defun mq-pop-all ()
+  "Push patches until none are applied."
+  (interactive)
+  (mq-pop "-a"))
+
+(defun mq-refresh-internal (root &rest args)
+  (hg-sync-buffers root)
+  (let ((patch (mq-patch-info "qtop")))
+    (message "Refreshing %s..." patch)
+    (let ((ret (apply 'hg-run "qrefresh" args)))
+      (if (equal (car ret) 0)
+	  (message "Refreshing %s... done." patch)
+	(error "Refreshing %s... %s" patch (hg-chomp (cdr ret)))))))
+
+(defun mq-refresh ()
+  "Refresh the topmost applied patch."
+  (interactive)
+  (let ((root (hg-root)))
+    (unless root
+      (error "Cannot refresh outside of a repository!"))
+  (mq-refresh-internal root)))
+
+(defun mq-patch-info (cmd &optional msg)
+  (let* ((ret (hg-run cmd))
+	 (info (hg-chomp (cdr ret))))
+    (if (equal (car ret) 0)
+	(if msg
+	    (message "%s patch: %s" msg info)
+	  info)
+      (error "%s" info))))
+
+(defun mq-top ()
+  "Print the name of the topmost applied patch."
+  (interactive)
+  (mq-patch-info "qtop" "Top"))
+
+(defun mq-next ()
+  "Print the name of the next patch to be pushed."
+  (interactive)
+  (mq-patch-info "qnext" "Next"))
+
+(defun mq-previous ()
+  "Print the name of the first patch below the topmost applied patch.
+This would become the active patch if popped to."
+  (interactive)
+  (mq-patch-info "qprev" "Previous"))
+
+(defun mq-edit-finish ()
+  "Finish editing the description of this patch, and refresh the patch."
+  (interactive)
+  (unless (equal (mq-patch-info "qtop") mq-top)
+    (error "Topmost patch has changed!"))
+  (hg-sync-buffers hg-root)
+  (mq-refresh-internal hg-root "-m" (buffer-substring (point-min) (point-max)))
+  (let ((buf mq-prev-buffer))
+    (kill-buffer nil)
+    (switch-to-buffer buf)))
+  
+(defun mq-edit-kill ()
+  "Kill the edit currently being prepared."
+  (interactive)
+  (when (or (not (buffer-modified-p)) (y-or-n-p "Really kill this edit? "))
+    (let ((buf mq-prev-buffer))
+      (kill-buffer nil)
+      (switch-to-buffer buf))))
+
+(defun mq-get-top (root)
+  (let ((entry (assoc root mq-top-patch)))
+    (if entry
+        (cdr entry))))
+
+(defun mq-set-top (root patch)
+  (let ((entry (assoc root mq-top-patch)))
+    (if entry
+        (if patch
+            (setcdr entry patch)
+          (setq mq-top-patch (delq entry mq-top-patch)))
+      (setq mq-top-patch (cons (cons root patch) mq-top-patch)))))
+
+(defun mq-update-mode-lines (root)
+  (let ((cwd default-directory))
+    (cd root)
+    (condition-case nil
+        (mq-set-top root (mq-patch-info "qtop"))
+      (error (mq-set-top root nil)))
+    (cd cwd))
+  (let ((patch (mq-get-top root)))
+    (save-excursion
+      (dolist (buf (hg-buffers-visiting-repo root))
+        (set-buffer buf)
+        (if mq-mode
+            (setq mq-mode (or (and patch (concat " MQ:" patch)) " MQ")))))))
+	
+(defun mq-mode (&optional arg)
+  "Minor mode for Mercurial repositories with an MQ patch queue"
+  (interactive "i")
+  (cond ((hg-root)
+         (setq mq-mode (if (null arg) (not mq-mode)
+                         arg))
+         (mq-update-mode-lines (hg-root))))
+  (run-hooks 'mq-mode-hook))
+
+(defun mq-edit-mode ()
+  "Mode for editing the description of a patch.
+
+Key bindings
+------------
+\\[mq-edit-finish]	use this description
+\\[mq-edit-kill]	abandon this description"
+  (interactive)
+  (use-local-map mq-edit-mode-map)
+  (set-syntax-table text-mode-syntax-table)
+  (setq local-abbrev-table text-mode-abbrev-table
+	major-mode 'mq-edit-mode
+	mode-name "MQ-Edit")
+  (set-buffer-modified-p nil)
+  (setq buffer-undo-list nil)
+  (run-hooks 'text-mode-hook 'mq-edit-mode-hook))
+  
+(defun mq-refresh-edit ()
+  "Refresh the topmost applied patch, editing the patch description."
+  (interactive)
+  (while mq-prev-buffer
+    (set-buffer mq-prev-buffer))
+  (let ((root (hg-root))
+	(prev-buffer (current-buffer))
+	(patch (mq-patch-info "qtop")))
+    (hg-sync-buffers root)
+    (let ((buf-name (format "*MQ: Edit description of %s*" patch)))
+      (switch-to-buffer (get-buffer-create buf-name))
+      (when (= (point-min) (point-max))
+	(set (make-local-variable 'hg-root) root)
+	(set (make-local-variable 'mq-top) patch)
+	(setq mq-prev-buffer prev-buffer)
+	(insert (hg-run0 "qheader"))
+	(goto-char (point-min)))
+      (mq-edit-mode)
+      (cd root)))
+  (message "Type `C-c C-c' to finish editing and refresh the patch."))
+
+
+(provide 'mq)
+
+
+;;; Local Variables:
+;;; prompt-to-byte-compile: nil
+;;; end:
--- a/contrib/win32/ReadMe.html	Sat Sep 02 22:58:02 2006 -0400
+++ b/contrib/win32/ReadMe.html	Sat Oct 21 15:22:08 2006 -0400
@@ -46,7 +46,7 @@
       other Mercurial commands should work fine for you.</p>
 
     <h1>Configuration notes</h1>
-    <p>The default editor for commit messages is 'vi'. You can set the EDITOR
+    <p>The default editor for commit messages is 'notepad'. You can set the EDITOR
     (or HGEDITOR) environment variable to specify your preference or set it in
     mercurial.ini:</p>
     <pre>
--- a/contrib/win32/mercurial.ini	Sat Sep 02 22:58:02 2006 -0400
+++ b/contrib/win32/mercurial.ini	Sat Oct 21 15:22:08 2006 -0400
@@ -3,6 +3,9 @@
 ; USERNAME is your Windows user name:
 ;   C:\Documents and Settings\USERNAME\Mercurial.ini
 
+[ui] 
+editor = notepad
+
 ; By default, we try to encode and decode all files that do not
 ; contain ASCII NUL characters.  What this means is that we try to set
 ; line endings to Windows style on update, and to Unix style on
--- a/doc/Makefile	Sat Sep 02 22:58:02 2006 -0400
+++ b/doc/Makefile	Sat Oct 21 15:22:08 2006 -0400
@@ -28,7 +28,7 @@
 
 install: man
 	for i in $(MAN) ; do \
-	  subdir=`echo $$i | sed -n 's/.\+\(\.[0-9]\)$$/man\1/p'` ; \
+	  subdir=`echo $$i | sed -n 's/..*\.\([0-9]\)$$/man\1/p'` ; \
 	  mkdir -p $(MANDIR)/$$subdir ; \
 	  $(INSTALL) $$i $(MANDIR)/$$subdir ; \
 	done
--- a/doc/hg.1.txt	Sat Sep 02 22:58:02 2006 -0400
+++ b/doc/hg.1.txt	Sat Oct 21 15:22:08 2006 -0400
@@ -127,6 +127,42 @@
     A range acts as a closed interval.  This means that a range of 3:5
     gives 3, 4 and 5.  Similarly, a range of 4:2 gives 4, 3, and 2.
 
+DATE FORMATS
+------------
+
+    Some commands (backout, commit, tag) allow the user to specify a date.
+    Possible formats for dates are:
+
+YYYY-mm-dd \HH:MM[:SS] [(+|-)NNNN]::
+    This is a subset of ISO 8601, allowing just the recommended notations
+    for date and time. The last part represents the timezone; if omitted,
+    local time is assumed. Examples:
+
+    "2005-08-22 03:27 -0700"
+
+    "2006-04-19 21:39:51"
+
+aaa bbb dd HH:MM:SS YYYY [(+|-)NNNN]::
+    This is the date format used by the C library. Here, aaa stands for
+    abbreviated weekday name and bbb for abbreviated month name. The last
+    part represents the timezone; if omitted, local time is assumed.
+    Examples:
+
+    "Mon Aug 22 03:27:00 2005 -0700"
+
+    "Wed Apr 19 21:39:51 2006"
+
+unixtime offset::
+    This is the internal representation format for dates. unixtime is
+    the number of seconds since the epoch (1970-01-01 00:00 UTC). offset
+    is the offset of the local timezone, in seconds west of UTC (negative
+    if the timezone is east of UTC).
+    Examples:
+
+    "1124706420 25200" (2005-08-22 03:27:00 -0700)
+
+    "1145475591 -7200" (2006-04-19 21:39:51 +0200)
+
 ENVIRONMENT VARIABLES
 ---------------------
 
@@ -193,6 +229,10 @@
     global /etc/mercurial/hgrc configuration.  See hgrc(5) for details of
     the contents and format of these files.
 
+Some commands (e.g. revert) produce backup files ending in .orig, if
+the .orig file already exists and is not tracked by Mercurial, it
+will be overwritten.
+
 BUGS
 ----
 Probably lots, please post them to the mailing list (See Resources below)
@@ -216,6 +256,6 @@
 
 COPYING
 -------
-Copyright \(C) 2005 Matt Mackall.
+Copyright \(C) 2005, 2006 Matt Mackall.
 Free use of this software is granted under the terms of the GNU General
 Public License (GPL).
--- a/doc/hgmerge.1.txt	Sat Sep 02 22:58:02 2006 -0400
+++ b/doc/hgmerge.1.txt	Sat Oct 21 15:22:08 2006 -0400
@@ -30,6 +30,6 @@
 
 COPYING
 -------
-Copyright \(C) 2005 Matt Mackall.
+Copyright \(C) 2005, 2006 Matt Mackall.
 Free use of this software is granted under the terms of the GNU General
 Public License (GPL).
--- a/doc/hgrc.5.txt	Sat Sep 02 22:58:02 2006 -0400
+++ b/doc/hgrc.5.txt	Sat Oct 21 15:22:08 2006 -0400
@@ -133,6 +133,21 @@
     # them to the working dir
     **.txt = tempfile: unix2dos -n INFILE OUTFILE
 
+defaults::
+  Use the [defaults] section to define command defaults, i.e. the 
+  default options/arguments to pass to the specified commands.
+  
+  The following example makes 'hg log' run in verbose mode, and
+  'hg status' show only the modified files, by default.
+  
+    [defaults]
+    log = -v
+    status = -m
+  
+  The actual commands, instead of their aliases, must be used when 
+  defining command defaults. The command defaults will also be
+  applied to the aliases of the commands defined.
+
 email::
   Settings for extensions that send email messages.
   from;;
@@ -306,7 +321,7 @@
 smtp::
   Configuration for extensions that need to send email messages.
   host;;
-    Optional.  Host name of mail server.  Default: "mail".
+    Host name of mail server, e.g. "mail.example.com".
   port;;
     Optional.  Port to connect to on mail server.  Default: 25.
   tls;;
@@ -377,6 +392,9 @@
     remote command to use for clone/push/pull operations. Default is 'hg'.
   ssh;;
     command to use for SSH connections. Default is 'ssh'.
+  strict;;
+    Require exact command names, instead of allowing unambiguous
+    abbreviations.  True or False.  Default is False.
   timeout;;
     The timeout used when a lock is held (in seconds), a negative value
     means no timeout. Default is 600.
--- a/doc/ja/hg.1.ja.txt	Sat Sep 02 22:58:02 2006 -0400
+++ b/doc/ja/hg.1.ja.txt	Sat Oct 21 15:22:08 2006 -0400
@@ -862,6 +862,6 @@
 
 著作権情報
 -----
-Copyright (C) 2005 Matt Mackall.
+Copyright (C) 2005, 2006 Matt Mackall.
 このソフトウェアの自由な使用は GNU 一般公有使用許諾 (GPL) のもとで
 認められます。
--- a/doc/ja/hgmerge.1.ja.txt	Sat Sep 02 22:58:02 2006 -0400
+++ b/doc/ja/hgmerge.1.ja.txt	Sat Oct 21 15:22:08 2006 -0400
@@ -32,6 +32,6 @@
 
 著作権情報
 ----
-Copyright (C) 2005 Matt Mackall.
+Copyright (C) 2005, 2006 Matt Mackall.
 このソフトウェアの自由な使用は GNU 一般公有使用許諾 (GPL) のもとで
 認められます。
--- a/hgeditor	Sat Sep 02 22:58:02 2006 -0400
+++ b/hgeditor	Sat Oct 21 15:22:08 2006 -0400
@@ -41,13 +41,15 @@
 
 cat "$1" > "$HGTMP/msg"
 
-CHECKSUM=`md5sum "$HGTMP/msg"`
+MD5=$(which md5sum 2>/dev/null) || \
+    MD5=$(which md5 2>/dev/null) 
+[ -x "${MD5}" ] && CHECKSUM=`${MD5} "$HGTMP/msg"`
 if [ -s "$HGTMP/diff" ]; then
     $EDITOR "$HGTMP/msg" "$HGTMP/diff" || exit $?
 else
     $EDITOR "$HGTMP/msg" || exit $?
 fi
-echo "$CHECKSUM" | md5sum -c >/dev/null 2>&1 && exit 13
+[ -x "${MD5}" ] && (echo "$CHECKSUM" | ${MD5} -c >/dev/null 2>&1 && exit 13)
 
 mv "$HGTMP/msg" "$1"
 
--- a/hgext/acl.py	Sat Sep 02 22:58:02 2006 -0400
+++ b/hgext/acl.py	Sat Oct 21 15:22:08 2006 -0400
@@ -60,8 +60,8 @@
             return None, False
 
         thisuser = self.getuser()
-        pats = [pat for pat, user in self.ui.configitems(key)
-                if user == thisuser]
+        pats = [pat for pat, users in self.ui.configitems(key)
+                if thisuser in users.replace(',', ' ').split()]
         self.ui.debug(_('acl: %s enabled, %d entries for user %s\n') %
                       (key, len(pats), thisuser))
         if pats:
@@ -80,7 +80,7 @@
         self.user = getpass.getuser()
         cfg = self.ui.config('acl', 'config')
         if cfg:
-            self.ui.readconfig(cfg)
+            self.ui.readsections(cfg, 'acl.allow', 'acl.deny')
         self.allow, self.allowable = self.buildmatch('acl.allow')
         self.deny, self.deniable = self.buildmatch('acl.deny')
 
--- a/hgext/bugzilla.py	Sat Sep 02 22:58:02 2006 -0400
+++ b/hgext/bugzilla.py	Sat Oct 21 15:22:08 2006 -0400
@@ -74,7 +74,7 @@
         timeout = int(self.ui.config('bugzilla', 'timeout', 5))
         usermap = self.ui.config('bugzilla', 'usermap')
         if usermap:
-            self.ui.readconfig(usermap)
+            self.ui.readsections(usermap, 'usermap')
         self.ui.note(_('connecting to %s:%s as %s, password %s\n') %
                      (host, db, user, '*' * len(passwd)))
         self.conn = MySQLdb.connect(host=host, user=user, passwd=passwd,
--- a/hgext/extdiff.py	Sat Sep 02 22:58:02 2006 -0400
+++ b/hgext/extdiff.py	Sat Oct 21 15:22:08 2006 -0400
@@ -5,37 +5,55 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 #
-# allow to use external programs to compare revisions, or revision
-# with working dir. program is called with two arguments: paths to
-# directories containing snapshots of files to compare.
+# The `extdiff' Mercurial extension allows you to use external programs
+# to compare revisions, or revision with working dir.  The external diff
+# programs are called with a configurable set of options and two
+# non-option arguments: paths to directories containing snapshots of
+# files to compare.
 #
-# to enable:
+# To enable this extension:
 #
 #   [extensions]
 #   hgext.extdiff =
 #
-# also allows to configure new diff commands, so you do not need to
-# type "hg extdiff -p kdiff3" always.
+# The `extdiff' extension also allows to configure new diff commands, so
+# you do not need to type "hg extdiff -p kdiff3" always.
 #
 #   [extdiff]
+#   # add new command that runs GNU diff(1) in 'context diff' mode
+#   cmd.cdiff = gdiff
+#   opts.cdiff = -Nprc5
+
 #   # add new command called vdiff, runs kdiff3
 #   cmd.vdiff = kdiff3
+
 #   # add new command called meld, runs meld (no need to name twice)
 #   cmd.meld =
+
 #   # add new command called vimdiff, runs gvimdiff with DirDiff plugin
 #   #(see http://www.vim.org/scripts/script.php?script_id=102)
-#   cmd.vimdiff = LC_ALL=C gvim -f '+bdel 1 2' '+ execute "DirDiff ".argv(0)." ".argv(1)'
+#   # Non english user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
+#   # your .vimrc
+#   cmd.vimdiff = gvim
+#   opts.vimdiff = -f '+next' '+execute "DirDiff" argv(0) argv(1)'
 #
-# you can use -I/-X and list of file or directory names like normal
-# "hg diff" command. extdiff makes snapshots of only needed files, so
-# compare program will be fast.
+# Each custom diff commands can have two parts: a `cmd' and an `opts'
+# part.  The cmd.xxx option defines the name of an executable program
+# that will be run, and opts.xxx defines a set of command-line options
+# which will be inserted to the command between the program name and
+# the files/directories to diff (i.e. the cdiff example above).
+#
+# You can use -I/-X and list of file or directory names like normal
+# "hg diff" command.  The `extdiff' extension makes snapshots of only
+# needed files, so running the external diff program will actually be
+# pretty fast (at least faster than having to compare the entire tree).
 
 from mercurial.demandload import demandload
 from mercurial.i18n import gettext as _
 from mercurial.node import *
-demandload(globals(), 'mercurial:commands,util os shutil tempfile')
+demandload(globals(), 'mercurial:cmdutil,util os shutil tempfile')
 
-def dodiff(ui, repo, diffcmd, pats, opts):
+def dodiff(ui, repo, diffcmd, diffopts, pats, opts):
     def snapshot_node(files, node):
         '''snapshot files as of some revision'''
         changes = repo.changelog.read(node)
@@ -47,6 +65,9 @@
             ui.write_err(_('making snapshot of %d files from rev %s\n') %
                          (len(files), short(node)))
         for fn in files:
+            if not fn in mf:
+                # skipping new file after a merge ?
+                continue
             wfn = util.pconvert(fn)
             ui.note('  %s\n' % wfn)
             dest = os.path.join(base, wfn)
@@ -78,10 +99,10 @@
                 fp.write(chunk)
         return dirname
 
-    node1, node2 = commands.revpair(ui, repo, opts['rev'])
-    files, matchfn, anypats = commands.matchpats(repo, pats, opts)
-    modified, added, removed, deleted, unknown = repo.changes(
-        node1, node2, files, match=matchfn)
+    node1, node2 = cmdutil.revpair(ui, repo, opts['rev'])
+    files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
+    modified, added, removed, deleted, unknown = repo.status(
+        node1, node2, files, match=matchfn)[:5]
     if not (modified or added or removed):
         return 0
 
@@ -92,9 +113,11 @@
             dir2 = snapshot_node(modified + added, node2)
         else:
             dir2 = snapshot_wdir(modified + added)
-        util.system('%s %s "%s" "%s"' %
-                    (diffcmd, ' '.join(opts['option']), dir1, dir2),
-                    cwd=tmproot)
+        cmdline = ('%s %s %s %s' %
+                   (util.shellquote(diffcmd), ' '.join(diffopts),
+                    util.shellquote(dir1), util.shellquote(dir2)))
+        ui.debug('running %r in %s\n' % (cmdline, tmproot))
+        util.system(cmdline, cwd=tmproot)
         return 1
     finally:
         ui.note(_('cleaning up temp directory\n'))
@@ -104,7 +127,9 @@
     '''use external program to diff repository (or selected files)
 
     Show differences between revisions for the specified files, using
-    an external program.  The default program used is "diff -Npru".
+    an external program.  The default program used is diff, with
+    default options "-Npru".
+
     To select a different program, use the -p option.  The program
     will be passed the names of two directories to compare.  To pass
     additional options to the program, use the -o option.  These will
@@ -115,7 +140,12 @@
     specified then that revision is compared to the working
     directory, and, when no revisions are specified, the
     working directory files are compared to its parent.'''
-    return dodiff(ui, repo, opts['program'] or 'diff -Npru', pats, opts)
+    program = opts['program'] or 'diff'
+    if opts['program']:
+        option = opts['option']
+    else:
+        option = opts['option'] or ['-Npru']
+    return dodiff(ui, repo, program, option, pats, opts)
 
 cmdtable = {
     "extdiff":
@@ -133,21 +163,25 @@
         if not cmd.startswith('cmd.'): continue
         cmd = cmd[4:]
         if not path: path = cmd
-        def save(cmd, path):
+        diffopts = ui.config('extdiff', 'opts.' + cmd, '')
+        diffopts = diffopts and [diffopts] or []
+        def save(cmd, path, diffopts):
             '''use closure to save diff command to use'''
             def mydiff(ui, repo, *pats, **opts):
-                return dodiff(ui, repo, path, pats, opts)
-            mydiff.__doc__ = '''use %s to diff repository (or selected files)
+                return dodiff(ui, repo, path, diffopts, pats, opts)
+            mydiff.__doc__ = '''use %(path)r to diff repository (or selected files)
 
             Show differences between revisions for the specified
-            files, using the %s program.
+            files, using the %(path)r program.
 
             When two revision arguments are given, then changes are
             shown between those revisions. If only one revision is
             specified then that revision is compared to the working
             directory, and, when no revisions are specified, the
-            working directory files are compared to its parent.''' % (cmd, cmd)
+            working directory files are compared to its parent.''' % {
+                'path': path,
+                }
             return mydiff
-        cmdtable[cmd] = (save(cmd, path),
+        cmdtable[cmd] = (save(cmd, path, diffopts),
                          cmdtable['extdiff'][1][1:],
                          _('hg %s [OPT]... [FILE]...') % cmd)
--- a/hgext/fetch.py	Sat Sep 02 22:58:02 2006 -0400
+++ b/hgext/fetch.py	Sat Oct 21 15:22:08 2006 -0400
@@ -24,13 +24,13 @@
         if modheads == 0:
             return 0
         if modheads == 1:
-            return hg.update(repo, repo.changelog.tip(), wlock=wlock)
+            return hg.clean(repo, repo.changelog.tip(), wlock=wlock)
         newheads = repo.heads(parent)
         newchildren = [n for n in repo.heads(parent) if n != parent]
         newparent = parent
         if newchildren:
             newparent = newchildren[0]
-            hg.update(repo, newparent, wlock=wlock)
+            hg.clean(repo, newparent, wlock=wlock)
         newheads = [n for n in repo.heads() if n != newparent]
         err = False
         if newheads:
@@ -63,7 +63,7 @@
             revs = [other.lookup(rev) for rev in opts['rev']]
         modheads = repo.pull(other, heads=revs, lock=lock)
         return postincoming(other, modheads)
-        
+
     parent, p2 = repo.dirstate.parents()
     if parent != repo.changelog.tip():
         raise util.Abort(_('working dir not at tip '
--- a/hgext/gpg.py	Sat Sep 02 22:58:02 2006 -0400
+++ b/hgext/gpg.py	Sat Oct 21 15:22:08 2006 -0400
@@ -221,7 +221,7 @@
         repo.opener("localsigs", "ab").write(sigmessage)
         return
 
-    for x in repo.changes():
+    for x in repo.status()[:5]:
         if ".hgsigs" in x and not opts["force"]:
             raise util.Abort(_("working copy of .hgsigs is changed "
                                "(please commit .hgsigs manually "
--- a/hgext/hbisect.py	Sat Sep 02 22:58:02 2006 -0400
+++ b/hgext/hbisect.py	Sat Oct 21 15:22:08 2006 -0400
@@ -23,10 +23,10 @@
     return parents.pop()
 
 def check_clean(ui, repo):
-        modified, added, removed, deleted, unknown = repo.changes()
-        if modified or added or removed:
-            ui.warn("Repository is not clean, please commit or revert\n")
-            sys.exit(1)
+    modified, added, removed, deleted, unknown = repo.status()[:5]
+    if modified or added or removed:
+        ui.warn("Repository is not clean, please commit or revert\n")
+        sys.exit(1)
 
 class bisect(object):
     """dichotomic search in the DAG of changesets"""
--- a/hgext/hgk.py	Sat Sep 02 22:58:02 2006 -0400
+++ b/hgext/hgk.py	Sat Oct 21 15:22:08 2006 -0400
@@ -1,95 +1,45 @@
 # Minimal support for git commands on an hg repository
 #
-# Copyright 2005 Chris Mason <mason@suse.com>
+# Copyright 2005, 2006 Chris Mason <mason@suse.com>
 #
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-import time, sys, signal, os
-from mercurial import hg, mdiff, fancyopts, commands, ui, util
-
-def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
-           changes=None, text=False):
-    def date(c):
-        return time.asctime(time.gmtime(c[2][0]))
-
-    if not changes:
-        changes = repo.changes(node1, node2, files, match=match)
-    modified, added, removed, deleted, unknown = changes
-    if files:
-        modified, added, removed = map(lambda x: filterfiles(files, x),
-                                       (modified, added, removed))
-
-    if not modified and not added and not removed:
-        return
+from mercurial.demandload import *
+demandload(globals(), 'time sys signal os')
+demandload(globals(), 'mercurial:hg,fancyopts,commands,ui,util,patch,revlog')
 
-    if node2:
-        change = repo.changelog.read(node2)
-        mmap2 = repo.manifest.read(change[0])
-        date2 = date(change)
-        def read(f):
-            return repo.file(f).read(mmap2[f])
-    else:
-        date2 = time.asctime()
-        if not node1:
-            node1 = repo.dirstate.parents()[0]
-        def read(f):
-            return repo.wfile(f).read()
-
-    change = repo.changelog.read(node1)
-    mmap = repo.manifest.read(change[0])
-    date1 = date(change)
-
-    for f in modified:
-        to = None
-        if f in mmap:
-            to = repo.file(f).read(mmap[f])
-        tn = read(f)
-        fp.write("diff --git a/%s b/%s\n" % (f, f))
-        fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text))
-    for f in added:
-        to = None
-        tn = read(f)
-        fp.write("diff --git /dev/null b/%s\n" % (f))
-        fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text))
-    for f in removed:
-        to = repo.file(f).read(mmap[f])
-        tn = None
-        fp.write("diff --git a/%s /dev/null\n" % (f))
-        fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text))
-
-def difftree(ui, repo, node1=None, node2=None, **opts):
+def difftree(ui, repo, node1=None, node2=None, *files, **opts):
     """diff trees from two commits"""
-    def __difftree(repo, node1, node2):
-        def date(c):
-            return time.asctime(time.gmtime(c[2][0]))
-
+    def __difftree(repo, node1, node2, files=[]):
         if node2:
             change = repo.changelog.read(node2)
             mmap2 = repo.manifest.read(change[0])
-            modified, added, removed, deleted, unknown = repo.changes(node1, node2)
-            def read(f): return repo.file(f).read(mmap2[f])
-            date2 = date(change)
+            status = repo.status(node1, node2, files=files)[:5]
+            modified, added, removed, deleted, unknown = status
         else:
-            date2 = time.asctime()
-            modified, added, removed, deleted, unknown = repo.changes(node1)
+            status = repo.status(node1, files=files)[:5]
+            modified, added, removed, deleted, unknown = status
             if not node1:
                 node1 = repo.dirstate.parents()[0]
-            def read(f): return file(os.path.join(repo.root, f)).read()
 
         change = repo.changelog.read(node1)
         mmap = repo.manifest.read(change[0])
-        date1 = date(change)
-        empty = "0" * 40;
+        empty = hg.short(hg.nullid)
 
         for f in modified:
             # TODO get file permissions
-            print ":100664 100664 %s %s M\t%s\t%s" % (hg.hex(mmap[f]),
-                                                      hg.hex(mmap2[f]), f, f)
+            print ":100664 100664 %s %s M\t%s\t%s" % (hg.short(mmap[f]),
+                                                      hg.short(mmap2[f]),
+                                                      f, f)
         for f in added:
-            print ":000000 100664 %s %s N\t%s\t%s" % (empty, hg.hex(mmap2[f]), f, f)
+            print ":000000 100664 %s %s N\t%s\t%s" % (empty,
+                                                      hg.short(mmap2[f]),
+                                                      f, f)
         for f in removed:
-            print ":100664 000000 %s %s D\t%s\t%s" % (hg.hex(mmap[f]), empty, f, f)
+            print ":100664 000000 %s %s D\t%s\t%s" % (hg.short(mmap[f]),
+                                                      empty,
+                                                      f, f)
     ##
 
     while True:
@@ -112,20 +62,22 @@
         if opts['patch']:
             if opts['pretty']:
                 catcommit(repo, node2, "")
-            dodiff(sys.stdout, ui, repo, node1, node2)
+            patch.diff(repo, node1, node2,
+                       files=files,
+                       opts=patch.diffopts(ui, {'git': True}))
         else:
-            __difftree(repo, node1, node2)
+            __difftree(repo, node1, node2, files=files)
         if not opts['stdin']:
             break
 
 def catcommit(repo, n, prefix, changes=None):
     nlprefix = '\n' + prefix;
     (p1, p2) = repo.changelog.parents(n)
-    (h, h1, h2) = map(hg.hex, (n, p1, p2))
+    (h, h1, h2) = map(hg.short, (n, p1, p2))
     (i1, i2) = map(repo.changelog.rev, (p1, p2))
     if not changes:
         changes = repo.changelog.read(n)
-    print "tree %s" % (hg.hex(changes[0]))
+    print "tree %s" % (hg.short(changes[0]))
     if i1 != -1: print "parent %s" % (h1)
     if i2 != -1: print "parent %s" % (h2)
     date_ar = changes[2]
@@ -138,6 +90,7 @@
 
     print "author %s %s %s" % (changes[1], date, date_ar[1])
     print "committer %s %s %s" % (committer, date, date_ar[1])
+    print "revision %d" % repo.changelog.rev(n)
     print ""
     if prefix != "":
         print "%s%s" % (prefix, changes[4].replace('\n', nlprefix).strip())
@@ -151,7 +104,7 @@
     node1 = repo.lookup(node1)
     node2 = repo.lookup(node2)
     n = repo.changelog.ancestor(node1, node2)
-    print hg.hex(n)
+    print hg.short(n)
 
 def catfile(ui, repo, type=None, r=None, **opts):
     """cat a specific revision"""
@@ -264,7 +217,6 @@
 
     # walk the repository looking for commits that are in our
     # reachability graph
-    #for i in range(repo.changelog.count()-1, -1, -1):
     for i, changes in chlogwalk():
         n = repo.changelog.node(i)
         mask = is_reachable(want_sha1, reachable, n)
@@ -273,17 +225,17 @@
             if parents:
                 pp = repo.changelog.parents(n)
                 if pp[0] != hg.nullid:
-                    parentstr += " " + hg.hex(pp[0])
+                    parentstr += " " + hg.short(pp[0])
                 if pp[1] != hg.nullid:
-                    parentstr += " " + hg.hex(pp[1])
+                    parentstr += " " + hg.short(pp[1])
             if not full:
-                print hg.hex(n) + parentstr
-            elif full is "commit":
-                print hg.hex(n) + parentstr
+                print hg.short(n) + parentstr
+            elif full == "commit":
+                print hg.short(n) + parentstr
                 catcommit(repo, n, '    ', changes)
             else:
                 (p1, p2) = repo.changelog.parents(n)
-                (h, h1, h2) = map(hg.hex, (n, p1, p2))
+                (h, h1, h2) = map(hg.short, (n, p1, p2))
                 (i1, i2) = map(repo.changelog.rev, (p1, p2))
 
                 date = changes[2][0]
@@ -299,6 +251,19 @@
                 break
             count += 1
 
+def revparse(ui, repo, *revs, **opts):
+    """Parse given revisions"""
+    def revstr(rev):
+        if rev == 'HEAD':
+            rev = 'tip'
+        return revlog.hex(repo.lookup(rev))
+
+    for r in revs:
+        revrange = r.split(':', 1)
+        ui.write('%s\n' % revstr(revrange[0]))
+        if len(revrange) == 2:
+            ui.write('^%s\n' % revstr(revrange[1]))
+
 # git rev-list tries to order things by date, and has the ability to stop
 # at a given commit without walking the whole repo.  TODO add the stop
 # parameter
@@ -311,23 +276,31 @@
     copy = [x for x in revs]
     revtree(copy, repo, full, opts['max_count'], opts['parents'])
 
-def view(ui, repo, *etc):
+def view(ui, repo, *etc, **opts):
     "start interactive history viewer"
     os.chdir(repo.root)
-    os.system(ui.config("hgk", "path", "hgk") + " " + " ".join(etc))
+    optstr = ' '.join(['--%s %s' % (k, v) for k, v in opts.iteritems() if v])
+    cmd = ui.config("hgk", "path", "hgk") + " %s %s" % (optstr, " ".join(etc))
+    ui.debug("running %s\n" % cmd)
+    os.system(cmd)
 
 cmdtable = {
-    "view": (view, [], 'hg view'),
+    "view": (view,
+             [('l', 'limit', '', 'limit number of changes displayed')],
+             'hg view [-l LIMIT] [REVRANGE]'),
     "debug-diff-tree": (difftree, [('p', 'patch', None, 'generate patch'),
                             ('r', 'recursive', None, 'recursive'),
                             ('P', 'pretty', None, 'pretty'),
                             ('s', 'stdin', None, 'stdin'),
                             ('C', 'copy', None, 'detect copies'),
                             ('S', 'search', "", 'search')],
-                            "hg git-diff-tree [options] node1 node2"),
+                            "hg git-diff-tree [options] node1 node2 [files...]"),
     "debug-cat-file": (catfile, [('s', 'stdin', None, 'stdin')],
                  "hg debug-cat-file [options] type file"),
     "debug-merge-base": (base, [], "hg debug-merge-base node node"),
+    'debug-rev-parse': (revparse,
+                        [('', 'default', '', 'ignored')],
+                        "hg debug-rev-parse rev"),
     "debug-rev-list": (revlist, [('H', 'header', None, 'header'),
                            ('t', 'topo-order', None, 'topo-order'),
                            ('p', 'parents', None, 'parents'),
--- a/hgext/mq.py	Sat Sep 02 22:58:02 2006 -0400
+++ b/hgext/mq.py	Sat Oct 21 15:22:08 2006 -0400
@@ -1,7 +1,6 @@
-
 # queue.py - patch queues for mercurial
 #
-# Copyright 2005 Chris Mason <mason@suse.com>
+# Copyright 2005, 2006 Chris Mason <mason@suse.com>
 #
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
@@ -31,16 +30,17 @@
 '''
 
 from mercurial.demandload import *
+from mercurial.i18n import gettext as _
+from mercurial import commands
 demandload(globals(), "os sys re struct traceback errno bz2")
-from mercurial.i18n import gettext as _
-from mercurial import ui, hg, revlog, commands, util
+demandload(globals(), "mercurial:cmdutil,hg,patch,revlog,ui,util")
 
 commands.norepo += " qclone qversion"
 
 class statusentry:
     def __init__(self, rev, name=None):
         if not name:
-            fields = rev.split(':')
+            fields = rev.split(':', 1)
             if len(fields) == 2:
                 self.rev, self.name = fields
             else:
@@ -66,6 +66,7 @@
         self.guards_path = "guards"
         self.active_guards = None
         self.guards_dirty = False
+        self._diffopts = None
 
         if os.path.exists(self.join(self.series_path)):
             self.full_series = self.opener(self.series_path).read().splitlines()
@@ -75,6 +76,11 @@
             lines = self.opener(self.status_path).read().splitlines()
             self.applied = [statusentry(l) for l in lines]
 
+    def diffopts(self):
+        if self._diffopts is None:
+            self._diffopts = patch.diffopts(self.ui)
+        return self._diffopts
+
     def join(self, *p):
         return os.path.join(self.path, *p)
 
@@ -108,6 +114,9 @@
                 comment = l[h:]
             patch = patch.strip()
             if patch:
+                if patch in self.series:
+                    raise util.Abort(_('%s appears more than once in %s') %
+                                     (patch, self.join(self.series_path)))
                 self.series.append(patch)
                 self.series_guards.append(self.guard_re.findall(comment))
 
@@ -121,7 +130,7 @@
         for c in bad_chars:
             if c in guard:
                 return _('invalid character in guard %r: %r') % (guard, c)
-        
+
     def set_active(self, guards):
         for guard in guards:
             bad = self.check_guard(guard)
@@ -163,7 +172,7 @@
         self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
         self.parse_series()
         self.series_dirty = True
-        
+
     def pushable(self, idx):
         if isinstance(idx, str):
             idx = self.series.index(idx)
@@ -176,11 +185,11 @@
         if exactneg:
             return False, exactneg[0]
         pos = [g for g in patchguards if g[0] == '+']
-        nonpos = [g for g in pos if g[1:] not in guards]
+        exactpos = [g for g in pos if g[1:] in guards]
         if pos:
-            if not nonpos:
-                return True, ''
-            return False, nonpos
+            if exactpos:
+                return True, exactpos[0]
+            return False, pos
         return True, ''
 
     def explain_pushable(self, idx, all_patches=False):
@@ -247,6 +256,9 @@
 
         for line in file(pf):
             line = line.rstrip()
+            if line.startswith('diff --git'):
+                diffstart = 2
+                break
             if diffstart:
                 if line.startswith('+++ '):
                     diffstart = 2
@@ -292,6 +304,13 @@
             message.insert(0, subject)
         return (message, comments, user, date, diffstart > 1)
 
+    def printdiff(self, repo, node1, node2=None, files=None,
+                  fp=None, changes=None, opts={}):
+        fns, matchfn, anypats = cmdutil.matchpats(repo, files, opts)
+
+        patch.diff(repo, node1, node2, fns, match=matchfn,
+                   fp=fp, changes=changes, opts=self.diffopts())
+
     def mergeone(self, repo, mergeq, head, patch, rev, wlock):
         # first try just applying the patch
         (err, n) = self.apply(repo, [ patch ], update_status=False,
@@ -325,7 +344,7 @@
         if comments:
             comments = "\n".join(comments) + '\n\n'
             patchf.write(comments)
-        commands.dodiff(patchf, self.ui, repo, head, n)
+        self.printdiff(repo, head, n, fp=patchf)
         patchf.close()
         return (0, n)
 
@@ -390,39 +409,15 @@
         '''Apply patchfile  to the working directory.
         patchfile: file name of patch'''
         try:
-            pp = util.find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
-            f = os.popen("%s -d %s -p1 --no-backup-if-mismatch < %s" %
-                         (pp, util.shellquote(repo.root), util.shellquote(patchfile)))
-        except:
-            self.ui.warn("patch failed, unable to continue (try -v)\n")
-            return (None, [], False)
-        files = []
-        fuzz = False
-        for l in f:
-            l = l.rstrip('\r\n');
-            if self.ui.verbose:
-                self.ui.warn(l + "\n")
-            if l[:14] == 'patching file ':
-                pf = os.path.normpath(util.parse_patch_output(l))
-                if pf not in files:
-                    files.append(pf)
-                printed_file = False
-                file_str = l
-            elif l.find('with fuzz') >= 0:
-                if not printed_file:
-                    self.ui.warn(file_str + '\n')
-                    printed_file = True
-                self.ui.warn(l + '\n')
-                fuzz = True
-            elif l.find('saving rejects to file') >= 0:
-                self.ui.warn(l + '\n')
-            elif l.find('FAILED') >= 0:
-                if not printed_file:
-                    self.ui.warn(file_str + '\n')
-                    printed_file = True
-                self.ui.warn(l + '\n')
+            (files, fuzz) = patch.patch(patchfile, self.ui, strip=1,
+                                        cwd=repo.root)
+        except Exception, inst:
+            self.ui.note(str(inst) + '\n')
+            if not self.ui.verbose:
+                self.ui.warn("patch failed, unable to continue (try -v)\n")
+            return (False, [], False)
 
-        return (not f.close(), files, fuzz)
+        return (True, files, fuzz)
 
     def apply(self, repo, series, list=False, update_status=True,
               strict=False, patchdir=None, merge=None, wlock=None):
@@ -435,43 +430,37 @@
         lock = repo.lock()
         tr = repo.transaction()
         n = None
-        for patch in series:
-            pushable, reason = self.pushable(patch)
+        for patchname in series:
+            pushable, reason = self.pushable(patchname)
             if not pushable:
-                self.explain_pushable(patch, all_patches=True)
+                self.explain_pushable(patchname, all_patches=True)
                 continue
-            self.ui.warn("applying %s\n" % patch)
-            pf = os.path.join(patchdir, patch)
+            self.ui.warn("applying %s\n" % patchname)
+            pf = os.path.join(patchdir, patchname)
 
             try:
-                message, comments, user, date, patchfound = self.readheaders(patch)
+                message, comments, user, date, patchfound = self.readheaders(patchname)
             except:
-                self.ui.warn("Unable to read %s\n" % pf)
+                self.ui.warn("Unable to read %s\n" % patchname)
                 err = 1
                 break
 
             if not message:
-                message = "imported patch %s\n" % patch
+                message = "imported patch %s\n" % patchname
             else:
                 if list:
-                    message.append("\nimported patch %s" % patch)
+                    message.append("\nimported patch %s" % patchname)
                 message = '\n'.join(message)
 
             (patcherr, files, fuzz) = self.patch(repo, pf)
             patcherr = not patcherr
 
-            if merge and len(files) > 0:
+            if merge and files:
                 # Mark as merged and update dirstate parent info
-                repo.dirstate.update(repo.dirstate.filterfiles(files), 'm')
+                repo.dirstate.update(repo.dirstate.filterfiles(files.keys()), 'm')
                 p1, p2 = repo.dirstate.parents()
                 repo.dirstate.setparents(p1, merge)
-            if len(files) > 0:
-                cwd = repo.getcwd()
-                cfiles = files
-                if cwd:
-                    cfiles = [util.pathto(cwd, f) for f in files]
-                commands.addremove_lock(self.ui, repo, cfiles,
-                                        opts={}, wlock=wlock)
+            files = patch.updatedir(self.ui, repo, files, wlock=wlock)
             n = repo.commit(files, message, user, date, force=1, lock=lock,
                             wlock=wlock)
 
@@ -479,11 +468,11 @@
                 raise util.Abort(_("repo commit failed"))
 
             if update_status:
-                self.applied.append(statusentry(revlog.hex(n), patch))
+                self.applied.append(statusentry(revlog.hex(n), patchname))
 
             if patcherr:
                 if not patchfound:
-                    self.ui.warn("patch %s is empty\n" % patch)
+                    self.ui.warn("patch %s is empty\n" % patchname)
                     err = 0
                 else:
                     self.ui.warn("patch failed, rejects left in working dir\n")
@@ -497,21 +486,51 @@
         tr.close()
         return (err, n)
 
-    def delete(self, repo, patch, force=False):
-        patch = self.lookup(patch, strict=True)
-        info = self.isapplied(patch)
-        if info:
-            raise util.Abort(_("cannot delete applied patch %s") % patch)
-        if patch not in self.series:
-            raise util.Abort(_("patch %s not in series file") % patch)
-        if force:
+    def delete(self, repo, patches, opts):
+        realpatches = []
+        for patch in patches:
+            patch = self.lookup(patch, strict=True)
+            info = self.isapplied(patch)
+            if info:
+                raise util.Abort(_("cannot delete applied patch %s") % patch)
+            if patch not in self.series:
+                raise util.Abort(_("patch %s not in series file") % patch)
+            realpatches.append(patch)
+
+        appliedbase = 0
+        if opts.get('rev'):
+            if not self.applied:
+                raise util.Abort(_('no patches applied'))
+            revs = [int(r) for r in cmdutil.revrange(ui, repo, opts['rev'])]
+            if len(revs) > 1 and revs[0] > revs[1]:
+                revs.reverse()
+            for rev in revs:
+                if appliedbase >= len(self.applied):
+                    raise util.Abort(_("revision %d is not managed") % rev)
+
+                base = revlog.bin(self.applied[appliedbase].rev)
+                node = repo.changelog.node(rev)
+                if node != base:
+                    raise util.Abort(_("cannot delete revision %d above "
+                                       "applied patches") % rev)
+                realpatches.append(self.applied[appliedbase].name)
+                appliedbase += 1
+
+        if not opts.get('keep'):
             r = self.qrepo()
             if r:
-                r.remove([patch], True)
+                r.remove(realpatches, True)
             else:
-                os.unlink(self.join(patch))
-        i = self.find_series(patch)
-        del self.full_series[i]
+                for p in realpatches:
+                    os.unlink(self.join(p))
+
+        if appliedbase:
+            del self.applied[:appliedbase]
+            self.applied_dirty = 1
+        indices = [self.find_series(p) for p in realpatches]
+        indices.sort()
+        for i in indices[-1::-1]:
+            del self.full_series[i]
         self.parse_series()
         self.series_dirty = 1
 
@@ -523,19 +542,20 @@
                 raise util.Abort(_("queue top not at same revision as working directory"))
             return top
         return None
-    def check_localchanges(self, repo):
-        (c, a, r, d, u) = repo.changes(None, None)
-        if c or a or d or r:
-            raise util.Abort(_("local changes found, refresh first"))
+    def check_localchanges(self, repo, force=False, refresh=True):
+        m, a, r, d = repo.status()[:4]
+        if m or a or r or d:
+            if not force:
+                if refresh:
+                    raise util.Abort(_("local changes found, refresh first"))
+                else:
+                    raise util.Abort(_("local changes found"))
+        return m, a, r, d
     def new(self, repo, patch, msg=None, force=None):
         if os.path.exists(self.join(patch)):
             raise util.Abort(_('patch "%s" already exists') % patch)
-        commitfiles = []
-        (c, a, r, d, u) = repo.changes(None, None)
-        if c or a or d or r:
-            if not force:
-                raise util.Abort(_("local changes found, refresh first"))
-            commitfiles = c + a + r
+        m, a, r, d = self.check_localchanges(repo, force)
+        commitfiles = m + a + r
         self.check_toppatch(repo)
         wlock = repo.wlock()
         insert = self.full_series_end()
@@ -561,7 +581,7 @@
         r = self.qrepo()
         if r: r.add([patch])
         if commitfiles:
-            self.refresh(repo, msg=None, short=True)
+            self.refresh(repo, short=True)
 
     def strip(self, repo, rev, update=True, backup="all", wlock=None):
         def limitheads(chlog, stop):
@@ -649,9 +669,7 @@
         revnum = chlog.rev(rev)
 
         if update:
-            (c, a, r, d, u) = repo.changes(None, None)
-            if c or a or d or r:
-                raise util.Abort(_("local changes found"))
+            self.check_localchanges(repo, refresh=False)
             urev = self.qparents(repo, rev)
             hg.clean(repo, urev, wlock=wlock)
             repo.dirstate.write()
@@ -702,8 +720,8 @@
         stripall(rev, revnum)
 
         change = chlog.read(rev)
+        chlog.strip(revnum, revnum)
         repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
-        chlog.strip(revnum, revnum)
         if saveheads:
             self.ui.status("adding branch\n")
             commands.unbundle(self.ui, repo, chgrpfile, update=False)
@@ -718,13 +736,15 @@
                 return (i, a.rev, a.name)
         return None
 
-    # if the exact patch name does not exist, we try a few 
+    # if the exact patch name does not exist, we try a few
     # variations.  If strict is passed, we try only #1
     #
     # 1) a number to indicate an offset in the series file
     # 2) a unique substring of the patch name was given
     # 3) patchname[-+]num to indicate an offset in the series file
     def lookup(self, patch, strict=False):
+        patch = patch and str(patch)
+
         def partial_name(s):
             if s in self.series:
                 return s
@@ -763,25 +783,25 @@
                 # return any partial match made above
                 if res:
                     return res
-                minus = patch.rsplit('-', 1)
-                if len(minus) > 1:
-                    res = partial_name(minus[0])
+                minus = patch.rfind('-')
+                if minus >= 0:
+                    res = partial_name(patch[:minus])
                     if res:
                         i = self.series.index(res)
                         try:
-                            off = int(minus[1] or 1)
+                            off = int(patch[minus+1:] or 1)
                         except(ValueError, OverflowError):
                             pass
                         else:
                             if i - off >= 0:
                                 return self.series[i - off]
-                plus = patch.rsplit('+', 1)
-                if len(plus) > 1:
-                    res = partial_name(plus[0])
+                plus = patch.rfind('+')
+                if plus >= 0:
+                    res = partial_name(patch[:plus])
                     if res:
                         i = self.series.index(res)
                         try:
-                            off = int(plus[1] or 1)
+                            off = int(patch[plus+1:] or 1)
                         except(ValueError, OverflowError):
                             pass
                         else:
@@ -795,11 +815,9 @@
             wlock = repo.wlock()
         patch = self.lookup(patch)
         if patch and self.isapplied(patch):
-            self.ui.warn(_("patch %s is already applied\n") % patch)
-            sys.exit(1)
+            raise util.Abort(_("patch %s is already applied") % patch)
         if self.series_end() == len(self.series):
-            self.ui.warn(_("patch series fully applied\n"))
-            sys.exit(1)
+            raise util.Abort(_("patch series fully applied"))
         if not force:
             self.check_localchanges(repo)
 
@@ -849,8 +867,7 @@
             if not info:
                 raise util.Abort(_("patch %s is not applied") % patch)
         if len(self.applied) == 0:
-            self.ui.warn(_("no patches applied\n"))
-            sys.exit(1)
+            raise util.Abort(_("no patches applied"))
 
         if not update:
             parents = repo.dirstate.parents()
@@ -887,15 +904,15 @@
             qp = self.qparents(repo, rev)
             changes = repo.changelog.read(qp)
             mmap = repo.manifest.read(changes[0])
-            (c, a, r, d, u) = repo.changes(qp, top)
+            m, a, r, d, u = repo.status(qp, top)[:5]
             if d:
                 raise util.Abort("deletions found between repo revs")
-            for f in c:
+            for f in m:
                 getfile(f, mmap[f])
             for f in r:
                 getfile(f, mmap[f])
-                util.set_exec(repo.wjoin(f), mmap.execf[f])
-            repo.dirstate.update(c + r, 'n')
+                util.set_exec(repo.wjoin(f), mmap.execf(f))
+            repo.dirstate.update(m + r, 'n')
             for f in a:
                 try: os.unlink(repo.wjoin(f))
                 except: raise
@@ -911,28 +928,28 @@
         else:
             self.ui.write("Patch queue now empty\n")
 
-    def diff(self, repo, files):
+    def diff(self, repo, pats, opts):
         top = self.check_toppatch(repo)
         if not top:
             self.ui.write("No patches applied\n")
             return
         qp = self.qparents(repo, top)
-        commands.dodiff(sys.stdout, self.ui, repo, qp, None, files)
+        self.printdiff(repo, qp, files=pats, opts=opts)
 
-    def refresh(self, repo, msg=None, short=False):
+    def refresh(self, repo, pats=None, **opts):
         if len(self.applied) == 0:
             self.ui.write("No patches applied\n")
-            return
+            return 1
         wlock = repo.wlock()
         self.check_toppatch(repo)
-        (top, patch) = (self.applied[-1].rev, self.applied[-1].name)
+        (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
         top = revlog.bin(top)
         cparents = repo.changelog.parents(top)
         patchparent = self.qparents(repo, top)
-        message, comments, user, date, patchfound = self.readheaders(patch)
+        message, comments, user, date, patchfound = self.readheaders(patchfn)
 
-        patchf = self.opener(patch, "w")
-        msg = msg.rstrip()
+        patchf = self.opener(patchfn, "w")
+        msg = opts.get('msg', '').rstrip()
         if msg:
             if comments:
                 # Remove existing message.
@@ -946,6 +963,7 @@
             comments = "\n".join(comments) + '\n\n'
             patchf.write(comments)
 
+        fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
         tip = repo.changelog.tip()
         if top == tip:
             # if the top of our patch queue is also the tip, there is an
@@ -958,30 +976,30 @@
             # patch already
             #
             # this should really read:
-            #(cc, dd, aa, aa2, uu) = repo.changes(tip, patchparent)
+            #   mm, dd, aa, aa2, uu = repo.status(tip, patchparent)[:5]
             # but we do it backwards to take advantage of manifest/chlog
-            # caching against the next repo.changes call
+            # caching against the next repo.status call
             #
-            (cc, aa, dd, aa2, uu) = repo.changes(patchparent, tip)
-            if short:
-                filelist = cc + aa + dd
+            mm, aa, dd, aa2, uu = repo.status(patchparent, tip)[:5]
+            if opts.get('short'):
+                filelist = mm + aa + dd
             else:
                 filelist = None
-            (c, a, r, d, u) = repo.changes(None, None, filelist)
+            m, a, r, d, u = repo.status(files=filelist)[:5]
 
             # we might end up with files that were added between tip and
             # the dirstate parent, but then changed in the local dirstate.
             # in this case, we want them to only show up in the added section
-            for x in c:
+            for x in m:
                 if x not in aa:
-                    cc.append(x)
+                    mm.append(x)
             # we might end up with files added by the local dirstate that
             # were deleted by the patch.  In this case, they should only
             # show up in the changed section.
             for x in a:
                 if x in dd:
                     del dd[dd.index(x)]
-                    cc.append(x)
+                    mm.append(x)
                 else:
                     aa.append(x)
             # make sure any files deleted in the local dirstate
@@ -992,28 +1010,42 @@
                     del aa[aa.index(x)]
                     forget.append(x)
                     continue
-                elif x in cc:
-                    del cc[cc.index(x)]
+                elif x in mm:
+                    del mm[mm.index(x)]
                 dd.append(x)
 
-            c = list(util.unique(cc))
+            m = list(util.unique(mm))
             r = list(util.unique(dd))
             a = list(util.unique(aa))
-            filelist = list(util.unique(c + r + a ))
-            commands.dodiff(patchf, self.ui, repo, patchparent, None,
-                            filelist, changes=(c, a, r, [], u))
+            filelist = filter(matchfn, util.unique(m + r + a))
+            if opts.get('git'):
+                self.diffopts().git = True
+            patch.diff(repo, patchparent, files=filelist, match=matchfn,
+                       fp=patchf, changes=(m, a, r, [], u),
+                       opts=self.diffopts())
             patchf.close()
 
             changes = repo.changelog.read(tip)
             repo.dirstate.setparents(*cparents)
+            copies = [(f, repo.dirstate.copied(f)) for f in a]
             repo.dirstate.update(a, 'a')
+            for dst, src in copies:
+                repo.dirstate.copy(src, dst)
             repo.dirstate.update(r, 'r')
-            repo.dirstate.update(c, 'n')
+            # if the patch excludes a modified file, mark that file with mtime=0
+            # so status can see it.
+            mm = []
+            for i in range(len(m)-1, -1, -1):
+                if not matchfn(m[i]):
+                    mm.append(m[i])
+                    del m[i]
+            repo.dirstate.update(m, 'n')
+            repo.dirstate.update(mm, 'n', st_mtime=0)
             repo.dirstate.forget(forget)
 
             if not msg:
                 if not message:
-                    message = "patch queue: %s\n" % patch
+                    message = "patch queue: %s\n" % patchfn
                 else:
                     message = "\n".join(message)
             else:
@@ -1021,10 +1053,10 @@
 
             self.strip(repo, top, update=False, backup='strip', wlock=wlock)
             n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock)
-            self.applied[-1] = statusentry(revlog.hex(n), patch)
+            self.applied[-1] = statusentry(revlog.hex(n), patchfn)
             self.applied_dirty = 1
         else:
-            commands.dodiff(patchf, self.ui, repo, patchparent, None)
+            self.printdiff(repo, patchparent, fp=patchf)
             patchf.close()
             self.pop(repo, force=True, wlock=wlock)
             self.push(repo, force=True, wlock=wlock)
@@ -1051,25 +1083,38 @@
             self.explain_pushable(i)
         return unapplied
 
-    def qseries(self, repo, missing=None, summary=False):
-        start = self.series_end(all_patches=True)
+    def qseries(self, repo, missing=None, start=0, length=0, status=None,
+                summary=False):
+        def displayname(patchname):
+            if summary:
+                msg = self.readheaders(patchname)[0]
+                msg = msg and ': ' + msg[0] or ': '
+            else:
+                msg = ''
+            return '%s%s' % (patchname, msg)
+
+        def pname(i):
+            if status == 'A':
+                return self.applied[i].name
+            else:
+                return self.series[i]
+
+        unapplied = self.series_end(all_patches=True)
+        if not length:
+            length = len(self.series) - start
         if not missing:
-            for i in range(len(self.series)):
-                patch = self.series[i]
+            for i in range(start, start+length):
+                pfx = ''
+                patch = pname(i)
                 if self.ui.verbose:
-                    if i < start:
+                    if i < unapplied:
                         status = 'A'
                     elif self.pushable(i)[0]:
                         status = 'U'
                     else:
                         status = 'G'
-                    self.ui.write('%d %s ' % (i, status))
-                if summary:
-                    msg = self.readheaders(patch)[0]
-                    msg = msg and ': ' + msg[0] or ': '
-                else:
-                    msg = ''
-                self.ui.write('%s%s\n' % (patch, msg))
+                    pfx = '%d %s ' % (i, status)
+                self.ui.write('%s%s\n' % (pfx, displayname(patch)))
         else:
             msng_list = []
             for root, dirs, files in os.walk(self.path):
@@ -1082,9 +1127,8 @@
                         msng_list.append(fl)
             msng_list.sort()
             for x in msng_list:
-                if self.ui.verbose:
-                    self.ui.write("D ")
-                self.ui.write("%s\n" % x)
+                pfx = self.ui.verbose and ('D ') or ''
+                self.ui.write("%s%s\n" % (pfx, displayname(x)))
 
     def issaveline(self, l):
         if l.name == '.hg.patches.save.line':
@@ -1116,7 +1160,8 @@
                 file_ = se.name
                 if se.rev:
                     applied.append(se)
-                series.append(file_)
+                else:
+                    series.append(file_)
         if datastart == None:
             self.ui.warn("No saved patch data found\n")
             return 1
@@ -1207,116 +1252,174 @@
             return next(end + 1)
         return next(end)
 
-    def qapplied(self, repo, patch=None):
-        if patch and patch not in self.series:
-            raise util.Abort(_("patch %s is not in series file") % patch)
-        if not patch:
-            end = len(self.applied)
-        else:
-            end = self.series.index(patch) + 1
-        for x in xrange(end):
-            p = self.appliedname(x)
-            self.ui.write("%s\n" % p)
-
     def appliedname(self, index):
         pname = self.applied[index].name
         if not self.ui.verbose:
             p = pname
         else:
-            p = str(self.series.index(pname)) + " " + p
+            p = str(self.series.index(pname)) + " " + pname
         return p
 
-    def top(self, repo):
-        if len(self.applied):
-            p = self.appliedname(-1)
-            self.ui.write(p + '\n')
-        else:
-            self.ui.write("No patches applied\n")
+    def qimport(self, repo, files, patchname=None, rev=None, existing=None,
+                force=None):
+        def checkseries(patchname):
+            if patchname in self.series:
+                raise util.Abort(_('patch %s is already in the series file')
+                                 % patchname)
+        def checkfile(patchname):
+            if not force and os.path.exists(self.join(patchname)):
+                raise util.Abort(_('patch "%s" already exists')
+                                 % patchname)
 
-    def next(self, repo):
-        end = self.series_end()
-        if end == len(self.series):
-            self.ui.write("All patches applied\n")
-        else:
-            p = self.series[end]
-            if self.ui.verbose:
-                self.ui.write("%d " % self.series.index(p))
-            self.ui.write(p + '\n')
-
-    def prev(self, repo):
-        if len(self.applied) > 1:
-            p = self.appliedname(-2)
-            self.ui.write(p + '\n')
-        elif len(self.applied) == 1:
-            self.ui.write("Only one patch applied\n")
-        else:
-            self.ui.write("No patches applied\n")
-
-    def qimport(self, repo, files, patch=None, existing=None, force=None):
-        if len(files) > 1 and patch:
+        if rev:
+            if files:
+                raise util.Abort(_('option "-r" not valid when importing '
+                                   'files'))
+            rev = [int(r) for r in cmdutil.revrange(self.ui, repo, rev)]
+            rev.sort(lambda x, y: cmp(y, x))
+        if (len(files) > 1 or len(rev) > 1) and patchname:
             raise util.Abort(_('option "-n" not valid when importing multiple '
-                               'files'))
+                               'patches'))
         i = 0
         added = []
+        if rev:
+            # If mq patches are applied, we can only import revisions
+            # that form a linear path to qbase.
+            # Otherwise, they should form a linear path to a head.
+            heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
+            if len(heads) > 1:
+                raise util.Abort(_('revision %d is the root of more than one '
+                                   'branch') % rev[-1])
+            if self.applied:
+                base = revlog.hex(repo.changelog.node(rev[0]))
+                if base in [n.rev for n in self.applied]:
+                    raise util.Abort(_('revision %d is already managed')
+                                     % rev[0])
+                if heads != [revlog.bin(self.applied[-1].rev)]:
+                    raise util.Abort(_('revision %d is not the parent of '
+                                       'the queue') % rev[0])
+                base = repo.changelog.rev(revlog.bin(self.applied[0].rev))
+                lastparent = repo.changelog.parentrevs(base)[0]
+            else:
+                if heads != [repo.changelog.node(rev[0])]:
+                    raise util.Abort(_('revision %d has unmanaged children')
+                                     % rev[0])
+                lastparent = None
+
+            for r in rev:
+                p1, p2 = repo.changelog.parentrevs(r)
+                n = repo.changelog.node(r)
+                if p2 != -1:
+                    raise util.Abort(_('cannot import merge revision %d') % r)
+                if lastparent and lastparent != r:
+                    raise util.Abort(_('revision %d is not the parent of %d')
+                                     % (r, lastparent))
+                lastparent = p1
+
+                if not patchname:
+                    patchname = '%d.diff' % r
+                checkseries(patchname)
+                checkfile(patchname)
+                self.full_series.insert(0, patchname)
+
+                patchf = self.opener(patchname, "w")
+                patch.export(repo, [n], fp=patchf, opts=self.diffopts())
+                patchf.close()
+
+                se = statusentry(revlog.hex(n), patchname)
+                self.applied.insert(0, se)
+
+                added.append(patchname)
+                patchname = None
+            self.parse_series()
+            self.applied_dirty = 1
+
         for filename in files:
             if existing:
-                if not patch:
-                    patch = filename
-                if not os.path.isfile(self.join(patch)):
-                    raise util.Abort(_("patch %s does not exist") % patch)
+                if not patchname:
+                    patchname = filename
+                if not os.path.isfile(self.join(patchname)):
+                    raise util.Abort(_("patch %s does not exist") % patchname)
             else:
                 try:
                     text = file(filename).read()
                 except IOError:
-                    raise util.Abort(_("unable to read %s") % patch)
-                if not patch:
-                    patch = os.path.split(filename)[1]
-                if not force and os.path.exists(self.join(patch)):
-                    raise util.Abort(_('patch "%s" already exists') % patch)
-                patchf = self.opener(patch, "w")
+                    raise util.Abort(_("unable to read %s") % patchname)
+                if not patchname:
+                    patchname = os.path.basename(filename)
+                checkfile(patchname)
+                patchf = self.opener(patchname, "w")
                 patchf.write(text)
-            if patch in self.series:
-                raise util.Abort(_('patch %s is already in the series file')
-                                 % patch)
+            checkseries(patchname)
             index = self.full_series_end() + i
-            self.full_series[index:index] = [patch]
+            self.full_series[index:index] = [patchname]
             self.parse_series()
-            self.ui.warn("adding %s to series file\n" % patch)
+            self.ui.warn("adding %s to series file\n" % patchname)
             i += 1
-            added.append(patch)
-            patch = None
+            added.append(patchname)
+            patchname = None
         self.series_dirty = 1
         qrepo = self.qrepo()
         if qrepo:
             qrepo.add(added)
 
-def delete(ui, repo, patch, **opts):
-    """remove a patch from the series file
+def delete(ui, repo, *patches, **opts):
+    """remove patches from queue
 
-    The patch must not be applied.
-    With -f, deletes the patch file as well as the series entry."""
+    With --rev, mq will stop managing the named revisions. The
+    patches must be applied and at the base of the stack. This option
+    is useful when the patches have been applied upstream.
+
+    Otherwise, the patches must not be applied.
+
+    With --keep, the patch files are preserved in the patch directory."""
     q = repo.mq
-    q.delete(repo, patch, force=opts.get('force'))
+    q.delete(repo, patches, opts)
     q.save_dirty()
     return 0
 
 def applied(ui, repo, patch=None, **opts):
     """print the patches already applied"""
-    repo.mq.qapplied(repo, patch)
-    return 0
+    q = repo.mq
+    if patch:
+        if patch not in q.series:
+            raise util.Abort(_("patch %s is not in series file") % patch)
+        end = q.series.index(patch) + 1
+    else:
+        end = len(q.applied)
+    if not end:
+        return
+
+    return q.qseries(repo, length=end, status='A', summary=opts.get('summary'))
 
 def unapplied(ui, repo, patch=None, **opts):
     """print the patches not yet applied"""
-    for i, p in repo.mq.unapplied(repo, patch):
-        if ui.verbose:
-            ui.write("%d " % i)
-        ui.write("%s\n" % p)
+    q = repo.mq
+    if patch:
+        if patch not in q.series:
+            raise util.Abort(_("patch %s is not in series file") % patch)
+        start = q.series.index(patch) + 1
+    else:
+        start = q.series_end()
+    q.qseries(repo, start=start, summary=opts.get('summary'))
 
 def qimport(ui, repo, *filename, **opts):
-    """import a patch"""
+    """import a patch
+
+    The patch will have the same name as its source file unless you
+    give it a new one with --name.
+
+    You can register an existing patch inside the patch directory
+    with the --existing flag.
+
+    With --force, an existing patch of the same name will be overwritten.
+
+    An existing changeset may be placed under mq control with --rev
+    (e.g. qimport --rev tip -n patch will place tip under mq control).
+    """
     q = repo.mq
-    q.qimport(repo, filename, patch=opts['name'],
-              existing=opts['existing'], force=opts['force'])
+    q.qimport(repo, filename, patchname=opts['name'],
+              existing=opts['existing'], force=opts['force'], rev=opts['rev'])
     q.save_dirty()
     return 0
 
@@ -1397,18 +1500,36 @@
 
 def top(ui, repo, **opts):
     """print the name of the current patch"""
-    repo.mq.top(repo)
-    return 0
+    q = repo.mq
+    t = len(q.applied)
+    if t:
+        return q.qseries(repo, start=t-1, length=1, status='A',
+                         summary=opts.get('summary'))
+    else:
+        ui.write("No patches applied\n")
+        return 1
 
 def next(ui, repo, **opts):
     """print the name of the next patch"""
-    repo.mq.next(repo)
-    return 0
+    q = repo.mq
+    end = q.series_end()
+    if end == len(q.series):
+        ui.write("All patches applied\n")
+        return 1
+    return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
 
 def prev(ui, repo, **opts):
     """print the name of the previous patch"""
-    repo.mq.prev(repo)
-    return 0
+    q = repo.mq
+    l = len(q.applied)
+    if l == 1:
+        ui.write("Only one patch applied\n")
+        return 1
+    if not l:
+        ui.write("No patches applied\n")
+        return 1
+    return q.qseries(repo, start=l-2, length=1, status='A',
+                     summary=opts.get('summary'))
 
 def new(ui, repo, patch, **opts):
     """create a new patch
@@ -1418,17 +1539,24 @@
     changes unless -f is specified, in which case the patch will
     be initialised with them.
 
-    -m or -l set the patch header as well as the commit message.
-    If neither is specified, the patch header is empty and the
+    -e, -m or -l set the patch header as well as the commit message.
+    If none is specified, the patch header is empty and the
     commit message is 'New patch: PATCH'"""
     q = repo.mq
     message = commands.logmessage(opts)
+    if opts['edit']:
+        message = ui.edit(message, ui.username())
     q.new(repo, patch, msg=message, force=opts['force'])
     q.save_dirty()
     return 0
 
-def refresh(ui, repo, **opts):
-    """update the current patch"""
+def refresh(ui, repo, *pats, **opts):
+    """update the current patch
+
+    If any file patterns are provided, the refreshed patch will contain only
+    the modifications that match those patterns; the remaining modifications
+    will remain in the working directory.
+    """
     q = repo.mq
     message = commands.logmessage(opts)
     if opts['edit']:
@@ -1437,14 +1565,13 @@
         patch = q.applied[-1].name
         (message, comment, user, date, hasdiff) = q.readheaders(patch)
         message = ui.edit('\n'.join(message), user or ui.username())
-    q.refresh(repo, msg=message, short=opts['short'])
+    ret = q.refresh(repo, pats, msg=message, **opts)
     q.save_dirty()
-    return 0
+    return ret
 
-def diff(ui, repo, *files, **opts):
+def diff(ui, repo, *pats, **opts):
     """diff of the current patch"""
-    # deep in the dirstate code, the walkhelper method wants a list, not a tuple
-    repo.mq.diff(repo, list(files))
+    repo.mq.diff(repo, pats, opts)
     return 0
 
 def fold(ui, repo, *files, **opts):
@@ -1454,7 +1581,7 @@
     applied to the current patch in the order given. If all the
     patches apply successfully, the current patch will be refreshed
     with the new cumulative patch, and the folded patches will
-    be deleted. With -f/--force, the folded patch files will
+    be deleted. With -k/--keep, the folded patch files will not
     be removed afterwards.
 
     The header for each folded patch will be concatenated with
@@ -1465,7 +1592,7 @@
     if not files:
         raise util.Abort(_('qfold requires at least one patch name'))
     if not q.check_toppatch(repo):
-        raise util.Abort(_('No patches applied\n'))
+        raise util.Abort(_('No patches applied'))
 
     message = commands.logmessage(opts)
     if opts['edit']:
@@ -1476,20 +1603,21 @@
     patches = []
     messages = []
     for f in files:
-        patch = q.lookup(f)
-        if patch in patches or patch == parent:
-            ui.warn(_('Skipping already folded patch %s') % patch)
-        if q.isapplied(patch):
-            raise util.Abort(_('qfold cannot fold already applied patch %s') % patch)
-        patches.append(patch)
+        p = q.lookup(f)
+        if p in patches or p == parent:
+            ui.warn(_('Skipping already folded patch %s') % p)
+        if q.isapplied(p):
+            raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
+        patches.append(p)
 
-    for patch in patches:
+    for p in patches:
         if not message:
-            messages.append(q.readheaders(patch)[0])
-        pf = q.join(patch)
+            messages.append(q.readheaders(p)[0])
+        pf = q.join(p)
         (patchsuccess, files, fuzz) = q.patch(repo, pf)
         if not patchsuccess:
-            raise util.Abort(_('Error folding patch %s') % patch)
+            raise util.Abort(_('Error folding patch %s') % p)
+        patch.updatedir(ui, repo, files)
 
     if not message:
         message, comments, user = q.readheaders(parent)[0:3]
@@ -1502,30 +1630,27 @@
         message = ui.edit(message, user or ui.username())
 
     q.refresh(repo, msg=message)
-
-    for patch in patches:
-        q.delete(repo, patch, force=opts['force'])
-
+    q.delete(repo, patches, opts)
     q.save_dirty()
 
 def guard(ui, repo, *args, **opts):
     '''set or print guards for a patch
 
-    guards control whether a patch can be pushed.  a patch with no
-    guards is aways pushed.  a patch with posative guard ("+foo") is
-    pushed only if qselect command enables guard "foo".  a patch with
-    nagative guard ("-foo") is never pushed if qselect command enables
-    guard "foo".
+    Guards control whether a patch can be pushed. A patch with no
+    guards is always pushed. A patch with a positive guard ("+foo") is
+    pushed only if the qselect command has activated it. A patch with
+    a negative guard ("-foo") is never pushed if the qselect command
+    has activated it.
 
-    with no arguments, default is to print current active guards.
-    with arguments, set active guards for patch.
+    With no arguments, print the currently active guards.
+    With arguments, set guards for the named patch.
 
-    to set nagative guard "-foo" on topmost patch ("--" is needed so
-    hg will not interpret "-foo" as argument):
+    To set a negative guard "-foo" on topmost patch ("--" is needed so
+    hg will not interpret "-foo" as an option):
       hg qguard -- -foo
 
-    to set guards on other patch:
-      hg qguard other.patch +2.6.17 -stable    
+    To set guards on another patch:
+      hg qguard other.patch +2.6.17 -stable
     '''
     def status(idx):
         guards = q.series_guards[idx] or ['unguarded']
@@ -1562,7 +1687,7 @@
     else:
         if not q.applied:
             ui.write('No patches applied\n')
-            return
+            return 1
         patch = q.lookup('qtip')
     message = repo.mq.readheaders(patch)[0]
 
@@ -1639,13 +1764,6 @@
         name = patch
         patch = None
 
-    if name in q.series:
-        raise util.Abort(_('A patch named %s already exists in the series file') % name)
-
-    absdest = q.join(name)
-    if os.path.exists(absdest):
-        raise util.Abort(_('%s already exists') % absdest)
-    
     if patch:
         patch = q.lookup(patch)
     else:
@@ -1653,6 +1771,15 @@
             ui.write(_('No patches applied\n'))
             return
         patch = q.lookup('qtip')
+    absdest = q.join(name)
+    if os.path.isdir(absdest):
+        name = os.path.join(name, os.path.basename(patch))
+        absdest = q.join(name)
+    if os.path.exists(absdest):
+        raise util.Abort(_('%s already exists') % absdest)
+
+    if name in q.series:
+        raise util.Abort(_('A patch named %s already exists in the series file') % name)
 
     if ui.verbose:
         ui.write('Renaming %s to %s\n' % (patch, name))
@@ -1724,44 +1851,64 @@
         backup = 'strip'
     elif opts['nobackup']:
         backup = 'none'
-    repo.mq.strip(repo, rev, backup=backup)
+    update = repo.dirstate.parents()[0] != revlog.nullid
+    repo.mq.strip(repo, rev, backup=backup, update=update)
     return 0
 
 def select(ui, repo, *args, **opts):
     '''set or print guarded patches to push
 
-    use qguard command to set or print guards on patch.  then use
-    qselect to tell mq which guards to use.  example:
+    Use the qguard command to set or print guards on patch, then use
+    qselect to tell mq which guards to use. A patch will be pushed if it
+    has no guards or any positive guards match the currently selected guard,
+    but will not be pushed if any negative guards match the current guard.
+    For example:
 
-        qguard foo.patch -stable    (nagative guard)
-        qguard bar.patch +stable    (posative guard)
+        qguard foo.patch -stable    (negative guard)
+        qguard bar.patch +stable    (positive guard)
         qselect stable
 
-    this sets "stable" guard.  mq will skip foo.patch (because it has
-    nagative match) but push bar.patch (because it has posative
-    match).  patch is pushed only if all posative guards match and no
-    nagative guards match.
+    This activates the "stable" guard. mq will skip foo.patch (because
+    it has a negative match) but push bar.patch (because it
+    has a positive match).
+
+    With no arguments, prints the currently active guards.
+    With one argument, sets the active guard.
 
-    with no arguments, default is to print current active guards.
-    with arguments, set active guards as given.
-    
-    use -n/--none to deactivate guards (no other arguments needed).
-    when no guards active, patches with posative guards are skipped,
-    patches with nagative guards are pushed.
+    Use -n/--none to deactivate guards (no other arguments needed).
+    When no guards are active, patches with positive guards are skipped
+    and patches with negative guards are pushed.
 
-    use -s/--series to print list of all guards in series file (no
-    other arguments needed).  use -v for more information.'''
+    qselect can change the guards on applied patches. It does not pop
+    guarded patches by default. Use --pop to pop back to the last applied
+    patch that is not guarded. Use --reapply (which implies --pop) to push
+    back to the current patch afterwards, but skip guarded patches.
+
+    Use -s/--series to print a list of all guards in the series file (no
+    other arguments needed). Use -v for more information.'''
 
     q = repo.mq
     guards = q.active()
     if args or opts['none']:
+        old_unapplied = q.unapplied(repo)
+        old_guarded = [i for i in xrange(len(q.applied)) if
+                       not q.pushable(i)[0]]
         q.set_active(args)
         q.save_dirty()
         if not args:
             ui.status(_('guards deactivated\n'))
-        if q.series:
-            ui.status(_('%d of %d unapplied patches active\n') %
-                      (len(q.unapplied(repo)), len(q.series)))
+        if not opts['pop'] and not opts['reapply']:
+            unapplied = q.unapplied(repo)
+            guarded = [i for i in xrange(len(q.applied))
+                       if not q.pushable(i)[0]]
+            if len(unapplied) != len(old_unapplied):
+                ui.status(_('number of unguarded, unapplied patches has '
+                            'changed from %d to %d\n') %
+                          (len(old_unapplied), len(unapplied)))
+            if len(guarded) != len(old_guarded):
+                ui.status(_('number of guarded, applied patches has changed '
+                            'from %d to %d\n') %
+                          (len(old_guarded), len(guarded)))
     elif opts['series']:
         guards = {}
         noguards = 0
@@ -1789,9 +1936,51 @@
                 ui.write(g, '\n')
         else:
             ui.write(_('no active guards\n'))
+    reapply = opts['reapply'] and q.applied and q.appliedname(-1)
+    popped = False
+    if opts['pop'] or opts['reapply']:
+        for i in xrange(len(q.applied)):
+            pushable, reason = q.pushable(i)
+            if not pushable:
+                ui.status(_('popping guarded patches\n'))
+                popped = True
+                if i == 0:
+                    q.pop(repo, all=True)
+                else:
+                    q.pop(repo, i-1)
+                break
+    if popped:
+        try:
+            if reapply:
+                ui.status(_('reapplying unguarded patches\n'))
+                q.push(repo, reapply)
+        finally:
+            q.save_dirty()
 
 def reposetup(ui, repo):
     class mqrepo(repo.__class__):
+        def abort_if_wdir_patched(self, errmsg, force=False):
+            if self.mq.applied and not force:
+                parent = revlog.hex(self.dirstate.parents()[0])
+                if parent in [s.rev for s in self.mq.applied]:
+                    raise util.Abort(errmsg)
+
+        def commit(self, *args, **opts):
+            if len(args) >= 6:
+                force = args[5]
+            else:
+                force = opts.get('force')
+            self.abort_if_wdir_patched(
+                _('cannot commit over an applied mq patch'),
+                force)
+
+            return super(mqrepo, self).commit(*args, **opts)
+
+        def push(self, remote, force=False, revs=None):
+            if self.mq.applied and not force:
+                raise util.Abort(_('source has mq patches applied'))
+            return super(mqrepo, self).push(remote, force, revs)
+
         def tags(self):
             if self.tagscache:
                 return self.tagscache
@@ -1813,11 +2002,14 @@
 
             return tagscache
 
-    repo.__class__ = mqrepo
-    repo.mq = queue(ui, repo.join(""))
+    if repo.local():
+        repo.__class__ = mqrepo
+        repo.mq = queue(ui, repo.join(""))
+
+seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
 
 cmdtable = {
-    "qapplied": (applied, [], 'hg qapplied [PATCH]'),
+    "qapplied": (applied, [] + seriesopts, 'hg qapplied [-s] [PATCH]'),
     "qclone": (clone,
                [('', 'pull', None, _('use pull protocol to copy metadata')),
                 ('U', 'noupdate', None, _('do not update the new working directories')),
@@ -1832,15 +2024,19 @@
         (commit,
          commands.table["^commit|ci"][1],
          'hg qcommit [OPTION]... [FILE]...'),
-    "^qdiff": (diff, [], 'hg qdiff [FILE]...'),
-    "qdelete":
+    "^qdiff": (diff,
+               [('I', 'include', [], _('include names matching the given patterns')),
+                ('X', 'exclude', [], _('exclude names matching the given patterns'))],
+               'hg qdiff [-I] [-X] [FILE]...'),
+    "qdelete|qremove|qrm":
         (delete,
-         [('f', 'force', None, _('delete patch file'))],
-          'hg qdelete [-f] PATCH'),
+         [('k', 'keep', None, _('keep patch file')),
+          ('r', 'rev', [], _('stop managing a revision'))],
+          'hg qdelete [-k] [-r REV]... PATCH...'),
     'qfold':
         (fold,
          [('e', 'edit', None, _('edit patch header')),
-          ('f', 'force', None, _('delete folded patch files')),
+          ('k', 'keep', None, _('keep folded patch files')),
           ('m', 'message', '', _('set patch header to <text>')),
           ('l', 'logfile', '', _('set patch header to contents of <file>'))],
          'hg qfold [-e] [-m <text>] [-l <file] PATCH...'),
@@ -1853,20 +2049,22 @@
         (qimport,
          [('e', 'existing', None, 'import file in patch dir'),
           ('n', 'name', '', 'patch file name'),
-          ('f', 'force', None, 'overwrite existing files')],
-         'hg qimport [-e] [-n NAME] [-f] FILE...'),
+          ('f', 'force', None, 'overwrite existing files'),
+          ('r', 'rev', [], 'place existing revisions under mq control')],
+         'hg qimport [-e] [-n NAME] [-f] [-r REV]... FILE...'),
     "^qinit":
         (init,
          [('c', 'create-repo', None, 'create queue repository')],
          'hg qinit [-c]'),
     "qnew":
         (new,
-         [('m', 'message', '', _('use <text> as commit message')),
+         [('e', 'edit', None, _('edit commit message')),
+          ('m', 'message', '', _('use <text> as commit message')),
           ('l', 'logfile', '', _('read the commit message from <file>')),
           ('f', 'force', None, _('import uncommitted changes into patch'))],
-         'hg qnew [-m TEXT] [-l FILE] [-f] PATCH'),
-    "qnext": (next, [], 'hg qnext'),
-    "qprev": (prev, [], 'hg qprev'),
+         'hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH'),
+    "qnext": (next, [] + seriesopts, 'hg qnext [-s]'),
+    "qprev": (prev, [] + seriesopts, 'hg qprev [-s]'),
     "^qpop":
         (pop,
          [('a', 'all', None, 'pop all patches'),
@@ -1886,8 +2084,11 @@
          [('e', 'edit', None, _('edit commit message')),
           ('m', 'message', '', _('change commit message with <text>')),
           ('l', 'logfile', '', _('change commit message with <file> content')),
-          ('s', 'short', None, 'short refresh')],
-         'hg qrefresh [-e] [-m TEXT] [-l FILE] [-s]'),
+          ('g', 'git', None, _('use git extended diff format')),
+          ('s', 'short', None, 'short refresh'),
+          ('I', 'include', [], _('include names matching the given patterns')),
+          ('X', 'exclude', [], _('exclude names matching the given patterns'))],
+         'hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] FILES...'),
     'qrename|qmv':
         (rename, [], 'hg qrename PATCH1 [PATCH2]'),
     "qrestore":
@@ -1906,20 +2107,21 @@
          'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
     "qselect": (select,
                 [('n', 'none', None, _('disable all guards')),
-                 ('s', 'series', None, _('list all guards in series file'))],
-                'hg qselect [GUARDS]'),
+                 ('s', 'series', None, _('list all guards in series file')),
+                 ('', 'pop', None,
+                  _('pop to before first guarded applied patch')),
+                 ('', 'reapply', None, _('pop, then reapply patches'))],
+                'hg qselect [OPTION...] [GUARD...]'),
     "qseries":
         (series,
-         [('m', 'missing', None, 'print patches not in series'),
-          ('s', 'summary', None, _('print first line of patch header'))],
-         'hg qseries [-m]'),
+         [('m', 'missing', None, 'print patches not in series')] + seriesopts,
+         'hg qseries [-ms]'),
     "^strip":
         (strip,
          [('f', 'force', None, 'force multi-head removal'),
           ('b', 'backup', None, 'bundle unrelated changesets'),
           ('n', 'nobackup', None, 'no backups')],
          'hg strip [-f] [-b] [-n] REV'),
-    "qtop": (top, [], 'hg qtop'),
-    "qunapplied": (unapplied, [], 'hg qunapplied [PATCH]'),
+    "qtop": (top, [] + seriesopts, 'hg qtop [-s]'),
+    "qunapplied": (unapplied, [] + seriesopts, 'hg qunapplied [-s] [PATCH]'),
 }
-
--- a/hgext/notify.py	Sat Sep 02 22:58:02 2006 -0400
+++ b/hgext/notify.py	Sat Oct 21 15:22:08 2006 -0400
@@ -40,6 +40,7 @@
 #   changegroup = ...      # template when run as changegroup hook
 #   maxdiff = 300          # max lines of diffs to include (0=none, -1=all)
 #   maxsubject = 67        # truncate subject line longer than this
+#   diffstat = True        # add a diffstat before the diff content
 #   sources = serve        # notify if source of incoming changes in this list
 #                          # (serve == ssh or http, push, pull, bundle)
 #   [email]
@@ -67,8 +68,8 @@
 from mercurial.demandload import *
 from mercurial.i18n import gettext as _
 from mercurial.node import *
-demandload(globals(), 'email.Parser mercurial:commands,templater,util')
-demandload(globals(), 'fnmatch socket time')
+demandload(globals(), 'mercurial:commands,patch,templater,util,mail')
+demandload(globals(), 'email.Parser fnmatch socket time')
 
 # template for single changeset can include email headers.
 single_template = '''
@@ -101,7 +102,7 @@
         self.ui = ui
         cfg = self.ui.config('notify', 'config')
         if cfg:
-            self.ui.readconfig(cfg)
+            self.ui.readsections(cfg, 'usersubs', 'reposubs')
         self.repo = repo
         self.stripcount = int(self.ui.config('notify', 'strip', 0))
         self.root = self.strip(self.repo.root)
@@ -229,8 +230,8 @@
         else:
             self.ui.status(_('notify: sending %d subscribers %d changes\n') %
                              (len(self.subs), count))
-            mail = self.ui.sendmail()
-            mail.sendmail(templater.email(msg['From']), self.subs, msgtext)
+            mail.sendmail(self.ui, templater.email(msg['From']),
+                          self.subs, msgtext)
 
     def diff(self, node, ref):
         maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
@@ -238,8 +239,11 @@
             return
         fp = templater.stringio()
         prev = self.repo.changelog.parents(node)[0]
-        commands.dodiff(fp, self.ui, self.repo, prev, ref)
+        patch.diff(self.repo, prev, ref, fp=fp)
         difflines = fp.getvalue().splitlines(1)
+        if self.ui.configbool('notify', 'diffstat', True):
+            s = patch.diffstat(difflines)
+            self.sio.write('\ndiffstat:\n\n' + s)
         if maxdiff > 0 and len(difflines) > maxdiff:
             self.sio.write(_('\ndiffs (truncated from %d to %d lines):\n\n') %
                            (len(difflines), maxdiff))
--- a/hgext/patchbomb.py	Sat Sep 02 22:58:02 2006 -0400
+++ b/hgext/patchbomb.py	Sat Oct 21 15:22:08 2006 -0400
@@ -23,26 +23,49 @@
 # the changeset summary, so you can be sure you are sending the right
 # changes.
 #
-# It is best to run this script with the "-n" (test only) flag before
-# firing it up "for real", in which case it will use your pager to
-# display each of the messages that it would send.
+# To enable this extension:
 #
-# The "-m" (mbox) option will create an mbox file instead of sending
-# the messages directly. This can be reviewed e.g. with "mutt -R -f mbox",
-# and finally sent with "formail -s sendmail -bm -t < mbox".
+#   [extensions]
+#   hgext.patchbomb =
 #
 # To configure other defaults, add a section like this to your hgrc
 # file:
 #
-# [email]
-# from = My Name <my@email>
-# to = recipient1, recipient2, ...
-# cc = cc1, cc2, ...
-# bcc = bcc1, bcc2, ...
+#   [email]
+#   from = My Name <my@email>
+#   to = recipient1, recipient2, ...
+#   cc = cc1, cc2, ...
+#   bcc = bcc1, bcc2, ...
+#
+# Then you can use the "hg email" command to mail a series of changesets
+# as a patchbomb.
+#
+# To avoid sending patches prematurely, it is a good idea to first run
+# the "email" command with the "-n" option (test only).  You will be
+# prompted for an email recipient address, a subject an an introductory
+# message describing the patches of your patchbomb.  Then when all is
+# done, your pager will be fired up once for each patchbomb message, so
+# you can verify everything is alright.
+#
+# The "-m" (mbox) option is also very useful.  Instead of previewing
+# each patchbomb message in a pager or sending the messages directly,
+# it will create a UNIX mailbox file with the patch emails.  This
+# mailbox file can be previewed with any mail user agent which supports
+# UNIX mbox files, i.e. with mutt:
+#
+#   % mutt -R -f mbox
+#
+# When you are previewing the patchbomb messages, you can use `formail'
+# (a utility that is commonly installed as part of the procmail package),
+# to send each message out:
+#
+#  % formail -s sendmail -bm -t < mbox
+#
+# That should be all.  Now your patchbomb is on its way out.
 
 from mercurial.demandload import *
 demandload(globals(), '''email.MIMEMultipart email.MIMEText email.Utils
-                         mercurial:commands,hg,ui
+                         mercurial:cmdutil,commands,hg,mail,ui,patch
                          os errno popen2 socket sys tempfile time''')
 from mercurial.i18n import gettext as _
 from mercurial.node import *
@@ -53,27 +76,6 @@
     import readline
 except ImportError: pass
 
-def diffstat(patch):
-    fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
-    try:
-        p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
-        try:
-            for line in patch: print >> p.tochild, line
-            p.tochild.close()
-            if p.wait(): return
-            fp = os.fdopen(fd, 'r')
-            stat = []
-            for line in fp: stat.append(line.lstrip())
-            last = stat.pop()
-            stat.insert(0, last)
-            stat = ''.join(stat)
-            if stat.startswith('0 files'): raise ValueError
-            return stat
-        except: raise
-    finally:
-        try: os.unlink(name)
-        except: pass
-
 def patchbomb(ui, repo, *revs, **opts):
     '''send changesets as a series of patch emails
 
@@ -100,8 +102,8 @@
         if not prompt(s, default = 'y', rest = '? ').lower().startswith('y'):
             raise ValueError
 
-    def cdiffstat(summary, patch):
-        s = diffstat(patch)
+    def cdiffstat(summary, patchlines):
+        s = patch.diffstat(patchlines)
         if s:
             if summary:
                 ui.write(summary, '\n')
@@ -117,7 +119,9 @@
             if line.startswith('#'):
                 if line.startswith('# Node ID'): node = line.split()[-1]
                 continue
-            if line.startswith('diff -r'): break
+            if (line.startswith('diff -r')
+                or line.startswith('diff --git')):
+                break
             desc.append(line)
         if not node: raise ValueError
 
@@ -142,10 +146,10 @@
             if patchname:
                 patchname = patchname[0]
             elif total > 1:
-                patchname = commands.make_filename(repo, '%b-%n.patch',
+                patchname = cmdutil.make_filename(repo, '%b-%n.patch',
                                                    binnode, idx, total)
             else:
-                patchname = commands.make_filename(repo, '%b.patch', binnode)
+                patchname = cmdutil.make_filename(repo, '%b.patch', binnode)
             p['Content-Disposition'] = 'inline; filename=' + patchname
             msg.attach(p)
         else:
@@ -154,7 +158,8 @@
         if total == 1:
             subj = '[PATCH] ' + desc[0].strip()
         else:
-            subj = '[PATCH %d of %d] %s' % (idx, total, desc[0].strip())
+            tlen = len(str(total))
+            subj = '[PATCH %0*d of %d] %s' % (tlen, idx, total, desc[0].strip())
         if subj.endswith('.'): subj = subj[:-1]
         msg['Subject'] = subj
         msg['X-Mercurial-Node'] = node
@@ -182,7 +187,8 @@
 
     commands.export(ui, repo, *revs, **{'output': exportee(patches),
                                         'switch_parent': False,
-                                        'text': None})
+                                        'text': None,
+                                        'git': opts.get('git')})
 
     jumbo = []
     msgs = []
@@ -212,10 +218,14 @@
     if len(patches) > 1:
         ui.write(_('\nWrite the introductory message for the patch series.\n\n'))
 
-        subj = '[PATCH 0 of %d] %s' % (
+        tlen = len(str(len(patches)))
+
+        subj = '[PATCH %0*d of %d] %s' % (
+            tlen, 0,
             len(patches),
             opts['subject'] or
-            prompt('Subject:', rest = ' [PATCH 0 of %d] ' % len(patches)))
+            prompt('Subject:', rest = ' [PATCH %0*d of %d] ' % (tlen, 0,
+                len(patches))))
 
         ui.write(_('Finish with ^D or a dot on a line by itself.\n\n'))
 
@@ -241,7 +251,7 @@
     ui.write('\n')
 
     if not opts['test'] and not opts['mbox']:
-        mail = ui.sendmail()
+        mailer = mail.connect(ui)
     parent = None
 
     # Calculate UTC offset
@@ -290,7 +300,7 @@
             ui.status('Sending ', m['Subject'], ' ...\n')
             # Exim does not remove the Bcc field
             del m['Bcc']
-            mail.sendmail(sender, to + bcc + cc, m.as_string(0))
+            mailer.sendmail(sender, to + bcc + cc, m.as_string(0))
 
 cmdtable = {
     'email':
@@ -299,6 +309,7 @@
       ('', 'bcc', [], 'email addresses of blind copy recipients'),
       ('c', 'cc', [], 'email addresses of copy recipients'),
       ('d', 'diffstat', None, 'add diffstat output to messages'),
+      ('g', 'git', None, _('use git extended diff format')),
       ('f', 'from', '', 'email address of sender'),
       ('', 'plain', None, 'omit hg patch header'),
       ('n', 'test', None, 'print messages that would be sent'),
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/ancestor.py	Sat Oct 21 15:22:08 2006 -0400
@@ -0,0 +1,83 @@
+# ancestor.py - generic DAG ancestor algorithm for mercurial
+#
+# Copyright 2006 Matt Mackall <mpm@selenic.com>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+import heapq
+
+def ancestor(a, b, pfunc):
+    """
+    return the least common ancestor of nodes a and b or None if there
+    is no such ancestor.
+
+    pfunc must return a list of parent vertices
+    """
+
+    if a == b:
+        return a
+
+    # find depth from root of all ancestors
+    visit = [a, b]
+    depth = {}
+    while visit:
+        vertex = visit[-1]
+        pl = pfunc(vertex)
+        if not pl:
+            depth[vertex] = 0
+            visit.pop()
+        else:
+            for p in pl:
+                if p == a or p == b: # did we find a or b as a parent?
+                    return p # we're done
+                if p not in depth:
+                    visit.append(p)
+            if visit[-1] == vertex:
+                depth[vertex] = min([depth[p] for p in pl]) - 1
+                visit.pop()
+
+    # traverse ancestors in order of decreasing distance from root
+    def ancestors(vertex):
+        h = [(depth[vertex], vertex)]
+        seen = {}
+        while h:
+            d, n = heapq.heappop(h)
+            if n not in seen:
+                seen[n] = 1
+                yield (d, n)
+                for p in pfunc(n):
+                    heapq.heappush(h, (depth[p], p))
+
+    def generations(vertex):
+        sg, s = None, {}
+        for g,v in ancestors(vertex):
+            if g != sg:
+                if sg:
+                    yield sg, s
+                sg, s = g, {v:1}
+            else:
+                s[v] = 1
+        yield sg, s
+
+    x = generations(a)
+    y = generations(b)
+    gx = x.next()
+    gy = y.next()
+
+    # increment each ancestor list until it is closer to root than
+    # the other, or they match
+    try:
+        while 1:
+            if gx[0] == gy[0]:
+                for v in gx[1]:
+                    if v in gy[1]:
+                        return v
+                gy = y.next()
+                gx = x.next()
+            elif gx[0] > gy[0]:
+                gy = y.next()
+            else:
+                gx = x.next()
+    except StopIteration:
+        return None
--- a/mercurial/archival.py	Sat Sep 02 22:58:02 2006 -0400
+++ b/mercurial/archival.py	Sat Oct 21 15:22:08 2006 -0400
@@ -163,11 +163,12 @@
     change = repo.changelog.read(node)
     mn = change[0]
     archiver = archivers[kind](dest, prefix, mtime or change[2][0])
-    mf = repo.manifest.read(mn).items()
-    mf.sort()
+    m = repo.manifest.read(mn)
+    items = m.items()
+    items.sort()
     write('.hg_archival.txt', 0644,
           'repo: %s\nnode: %s\n' % (hex(repo.changelog.node(0)), hex(node)))
-    for filename, filenode in mf:
-        write(filename, mf.execf(filename) and 0755 or 0644,
+    for filename, filenode in items:
+        write(filename, m.execf(filename) and 0755 or 0644,
               repo.file(filename).read(filenode))
     archiver.done()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/base85.c	Sat Oct 21 15:22:08 2006 -0400
@@ -0,0 +1,155 @@
+/*
+ base85 codec
+
+ Copyright 2006 Brendan Cully <brendan@kublai.com>
+
+ This software may be used and distributed according to the terms of
+ the GNU General Public License, incorporated herein by reference.
+
+ Largely based on git's implementation
+*/
+
+#include <Python.h>
+
+static const char b85chars[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+	"abcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~";
+static char b85dec[256];
+
+static void
+b85prep(void)
+{
+	int i;
+
+	memset(b85dec, 0, sizeof(b85dec));
+	for (i = 0; i < sizeof(b85chars); i++)
+		b85dec[(int)(b85chars[i])] = i + 1;
+}
+
+static PyObject *
+b85encode(PyObject *self, PyObject *args)
+{
+	const unsigned char *text;
+	PyObject *out;
+	char *dst;
+	int len, olen, i;
+	unsigned int acc, val, ch;
+        int pad = 0;
+
+	if (!PyArg_ParseTuple(args, "s#|i", &text, &len, &pad))
+		return NULL;
+
+        if (pad)
+                olen = ((len + 3) / 4 * 5) - 3;
+        else {
+                olen = len % 4;
+                if (olen)
+                        olen++;
+                olen += len / 4 * 5;
+        }
+	if (!(out = PyString_FromStringAndSize(NULL, olen + 3)))
+		return NULL;
+
+	dst = PyString_AS_STRING(out);
+
+	while (len) {
+		acc = 0;
+		for (i = 24; i >= 0; i -= 8) {
+			ch = *text++;
+			acc |= ch << i;
+			if (--len == 0)
+				break;
+		}
+		for (i = 4; i >= 0; i--) {
+			val = acc % 85;
+			acc /= 85;
+			dst[i] = b85chars[val];
+		}
+		dst += 5;
+	}
+
+        if (!pad)
+                _PyString_Resize(&out, olen);
+
+	return out;
+}
+
+static PyObject *
+b85decode(PyObject *self, PyObject *args)
+{
+	PyObject *out;
+	const char *text;
+	char *dst;
+	int len, i, j, olen, c, cap;
+	unsigned int acc;
+
+	if (!PyArg_ParseTuple(args, "s#", &text, &len))
+		return NULL;
+
+	olen = len / 5 * 4;
+	i = len % 5;
+	if (i)
+		olen += i - 1;
+	if (!(out = PyString_FromStringAndSize(NULL, olen)))
+		return NULL;
+
+	dst = PyString_AS_STRING(out);
+
+	i = 0;
+	while (i < len)
+	{
+		acc = 0;
+		cap = len - i - 1;
+		if (cap > 4)
+			cap = 4;
+		for (j = 0; j < cap; i++, j++)
+		{
+			c = b85dec[(int)*text++] - 1;
+			if (c < 0)
+				return PyErr_Format(PyExc_ValueError, "Bad base85 character at position %d", i);
+			acc = acc * 85 + c;
+		}
+		if (i++ < len)
+		{
+			c = b85dec[(int)*text++] - 1;
+			if (c < 0)
+				return PyErr_Format(PyExc_ValueError, "Bad base85 character at position %d", i);
+			/* overflow detection: 0xffffffff == "|NsC0",
+			 * "|NsC" == 0x03030303 */
+			if (acc > 0x03030303 || (acc *= 85) > 0xffffffff - c)
+				return PyErr_Format(PyExc_ValueError, "Bad base85 sequence at position %d", i);
+			acc += c;
+		}
+
+		cap = olen < 4 ? olen : 4;
+		olen -= cap;
+		for (j = 0; j < 4 - cap; j++)
+			acc *= 85;
+		if (cap && cap < 4)
+			acc += 0xffffff >> (cap - 1) * 8;
+		for (j = 0; j < cap; j++)
+		{
+			acc = (acc << 8) | (acc >> 24);
+			*dst++ = acc;
+		}
+	}
+
+	return out;
+}
+
+static char base85_doc[] = "Base85 Data Encoding";
+
+static PyMethodDef methods[] = {
+	{"b85encode", b85encode, METH_VARARGS,
+         "Encode text in base85.\n\n"
+         "If the second parameter is true, pad the result to a multiple of "
+         "five characters.\n"},
+	{"b85decode", b85decode, METH_VARARGS, "Decode base85 text.\n"},
+	{NULL, NULL}
+};
+
+PyMODINIT_FUNC initbase85(void)
+{
+	Py_InitModule3("base85", methods, base85_doc);
+
+	b85prep();
+}
--- a/mercurial/bdiff.c	Sat Sep 02 22:58:02 2006 -0400
+++ b/mercurial/bdiff.c	Sat Oct 21 15:22:08 2006 -0400
@@ -1,7 +1,7 @@
 /*
  bdiff.c - efficient binary diff extension for Mercurial
 
- Copyright 2005 Matt Mackall <mpm@selenic.com>
+ Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
 
  This software may be used and distributed according to the terms of
  the GNU General Public License, incorporated herein by reference.
@@ -300,18 +300,19 @@
 
 static PyObject *bdiff(PyObject *self, PyObject *args)
 {
-	PyObject *sa, *sb, *result = NULL;
+	char *sa, *sb;
+	PyObject *result = NULL;
 	struct line *al, *bl;
 	struct hunklist l = {NULL, NULL};
 	struct hunk *h;
 	char encode[12], *rb;
-	int an, bn, len = 0, la = 0, lb = 0;
+	int an, bn, len = 0, la, lb;
 
-	if (!PyArg_ParseTuple(args, "SS:bdiff", &sa, &sb))
+	if (!PyArg_ParseTuple(args, "s#s#:bdiff", &sa, &la, &sb, &lb))
 		return NULL;
 
-	an = splitlines(PyString_AsString(sa), PyString_Size(sa), &al);
-	bn = splitlines(PyString_AsString(sb), PyString_Size(sb), &bl);
+	an = splitlines(sa, la, &al);
+	bn = splitlines(sb, lb, &bl);
 	if (!al || !bl)
 		goto nomem;
 
@@ -320,6 +321,7 @@
 		goto nomem;
 
 	/* calculate length of output */
+	la = lb = 0;
 	for (h = l.base; h != l.head; h++) {
 		if (h->a1 != la || h->b1 != lb)
 			len += 12 + bl[h->b1].l - bl[lb].l;
--- a/mercurial/bundlerepo.py	Sat Sep 02 22:58:02 2006 -0400
+++ b/mercurial/bundlerepo.py	Sat Oct 21 15:22:08 2006 -0400
@@ -233,10 +233,12 @@
         self.bundlefile.close()
 
     def __del__(self):
-        if not self.bundlefile.closed:
-            self.bundlefile.close()
-        if self.tempfile is not None:
-            os.unlink(self.tempfile)
+        bundlefile = getattr(self, 'bundlefile', None)
+        if bundlefile and not bundlefile.closed:
+            bundlefile.close()
+        tempfile = getattr(self, 'tempfile', None)
+        if tempfile is not None:
+            os.unlink(tempfile)
 
 def instance(ui, path, create):
     if create:
--- a/mercurial/changelog.py	Sat Sep 02 22:58:02 2006 -0400
+++ b/mercurial/changelog.py	Sat Oct 21 15:22:08 2006 -0400
@@ -1,6 +1,6 @@
 # changelog.py - changelog class for mercurial
 #
-# Copyright 2005 Matt Mackall <mpm@selenic.com>
+# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
 #
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
@@ -10,38 +10,91 @@
 from demandload import demandload
 demandload(globals(), "os time util")
 
+def _string_escape(text):
+    """
+    >>> d = {'nl': chr(10), 'bs': chr(92), 'cr': chr(13), 'nul': chr(0)}
+    >>> s = "ab%(nl)scd%(bs)s%(bs)sn%(nul)sab%(cr)scd%(bs)s%(nl)s" % d
+    >>> s
+    'ab\\ncd\\\\\\\\n\\x00ab\\rcd\\\\\\n'
+    >>> res = _string_escape(s)
+    >>> s == _string_unescape(res)
+    True
+    """
+    # subset of the string_escape codec
+    text = text.replace('\\', '\\\\').replace('\n', '\\n').replace('\r', '\\r')
+    return text.replace('\0', '\\0')
+
+def _string_unescape(text):
+    return text.decode('string_escape')
+
 class changelog(revlog):
     def __init__(self, opener, defversion=REVLOGV0):
         revlog.__init__(self, opener, "00changelog.i", "00changelog.d",
                         defversion)
 
+    def decode_extra(self, text):
+        extra = {}
+        for l in text.split('\0'):
+            if not l:
+                continue
+            k, v = _string_unescape(l).split(':', 1)
+            extra[k] = v
+        return extra
+
+    def encode_extra(self, d):
+        items = [_string_escape(":".join(t)) for t in d.iteritems()]
+        return "\0".join(items)
+
     def extract(self, text):
+        """
+        format used:
+        nodeid\n        : manifest node in ascii
+        user\n          : user, no \n or \r allowed
+        time tz extra\n : date (time is int or float, timezone is int)
+                        : extra is metadatas, encoded and separated by '\0'
+                        : older versions ignore it
+        files\n\n       : files modified by the cset, no \n or \r allowed
+        (.*)            : comment (free text, ideally utf-8)
+
+        changelog v0 doesn't use extra
+        """
         if not text:
-            return (nullid, "", (0, 0), [], "")
+            return (nullid, "", (0, 0), [], "", {})
         last = text.index("\n\n")
         desc = text[last + 2:]
-        l = text[:last].splitlines()
+        l = text[:last].split('\n')
         manifest = bin(l[0])
         user = l[1]
-        date = l[2].split(' ')
-        time = float(date.pop(0))
-        try:
-            # various tools did silly things with the time zone field.
-            timezone = int(date[0])
-        except:
-            timezone = 0
+
+        extra_data = l[2].split(' ', 2)
+        if len(extra_data) != 3:
+            time = float(extra_data.pop(0))
+            try:
+                # various tools did silly things with the time zone field.
+                timezone = int(extra_data[0])
+            except:
+                timezone = 0
+            extra = {}
+        else:
+            time, timezone, extra = extra_data
+            time, timezone = float(time), int(timezone)
+            extra = self.decode_extra(extra)
         files = l[3:]
-        return (manifest, user, (time, timezone), files, desc)
+        return (manifest, user, (time, timezone), files, desc, extra)
 
     def read(self, node):
         return self.extract(self.revision(node))
 
     def add(self, manifest, list, desc, transaction, p1=None, p2=None,
-                  user=None, date=None):
+                  user=None, date=None, extra={}):
+
         if date:
             parseddate = "%d %d" % util.parsedate(date)
         else:
             parseddate = "%d %d" % util.makedate()
+        if extra:
+            extra = self.encode_extra(extra)
+            parseddate = "%s %s" % (parseddate, extra)
         list.sort()
         l = [hex(manifest), user, parseddate] + list + ["", desc]
         text = "\n".join(l)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/cmdutil.py	Sat Oct 21 15:22:08 2006 -0400
@@ -0,0 +1,215 @@
+# cmdutil.py - help for command processing in mercurial
+#
+# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+from demandload import demandload
+from node import *
+from i18n import gettext as _
+demandload(globals(), 'mdiff util')
+demandload(globals(), 'os sys')
+
+revrangesep = ':'
+
+def revfix(repo, val, defval):
+    '''turn user-level id of changeset into rev number.
+    user-level id can be tag, changeset, rev number, or negative rev
+    number relative to number of revs (-1 is tip, etc).'''
+    if not val:
+        return defval
+    try:
+        num = int(val)
+        if str(num) != val:
+            raise ValueError
+        if num < 0:
+            num += repo.changelog.count()
+        if num < 0:
+            num = 0
+        elif num >= repo.changelog.count():
+            raise ValueError
+    except ValueError:
+        try:
+            num = repo.changelog.rev(repo.lookup(val))
+        except KeyError:
+            raise util.Abort(_('invalid revision identifier %s') % val)
+    return num
+
+def revpair(ui, repo, revs):
+    '''return pair of nodes, given list of revisions. second item can
+    be None, meaning use working dir.'''
+    if not revs:
+        return repo.dirstate.parents()[0], None
+    end = None
+    if len(revs) == 1:
+        start = revs[0]
+        if revrangesep in start:
+            start, end = start.split(revrangesep, 1)
+            start = revfix(repo, start, 0)
+            end = revfix(repo, end, repo.changelog.count() - 1)
+        else:
+            start = revfix(repo, start, None)
+    elif len(revs) == 2:
+        if revrangesep in revs[0] or revrangesep in revs[1]:
+            raise util.Abort(_('too many revisions specified'))
+        start = revfix(repo, revs[0], None)
+        end = revfix(repo, revs[1], None)
+    else:
+        raise util.Abort(_('too many revisions specified'))
+    if end is not None: end = repo.lookup(str(end))
+    return repo.lookup(str(start)), end
+
+def revrange(ui, repo, revs):
+    """Yield revision as strings from a list of revision specifications."""
+    seen = {}
+    for spec in revs:
+        if revrangesep in spec:
+            start, end = spec.split(revrangesep, 1)
+            start = revfix(repo, start, 0)
+            end = revfix(repo, end, repo.changelog.count() - 1)
+            step = start > end and -1 or 1
+            for rev in xrange(start, end+step, step):
+                if rev in seen:
+                    continue
+                seen[rev] = 1
+                yield str(rev)
+        else:
+            rev = revfix(repo, spec, None)
+            if rev in seen:
+                continue
+            seen[rev] = 1
+            yield str(rev)
+
+def make_filename(repo, pat, node,
+                  total=None, seqno=None, revwidth=None, pathname=None):
+    node_expander = {
+        'H': lambda: hex(node),
+        'R': lambda: str(repo.changelog.rev(node)),
+        'h': lambda: short(node),
+        }
+    expander = {
+        '%': lambda: '%',
+        'b': lambda: os.path.basename(repo.root),
+        }
+
+    try:
+        if node:
+            expander.update(node_expander)
+        if node and revwidth is not None:
+            expander['r'] = (lambda:
+                    str(repo.changelog.rev(node)).zfill(revwidth))
+        if total is not None:
+            expander['N'] = lambda: str(total)
+        if seqno is not None:
+            expander['n'] = lambda: str(seqno)
+        if total is not None and seqno is not None:
+            expander['n'] = lambda:str(seqno).zfill(len(str(total)))
+        if pathname is not None:
+            expander['s'] = lambda: os.path.basename(pathname)
+            expander['d'] = lambda: os.path.dirname(pathname) or '.'
+            expander['p'] = lambda: pathname
+
+        newname = []
+        patlen = len(pat)
+        i = 0
+        while i < patlen:
+            c = pat[i]
+            if c == '%':
+                i += 1
+                c = pat[i]
+                c = expander[c]()
+            newname.append(c)
+            i += 1
+        return ''.join(newname)
+    except KeyError, inst:
+        raise util.Abort(_("invalid format spec '%%%s' in output file name") %
+                         inst.args[0])
+
+def make_file(repo, pat, node=None,
+              total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
+    if not pat or pat == '-':
+        return 'w' in mode and sys.stdout or sys.stdin
+    if hasattr(pat, 'write') and 'w' in mode:
+        return pat
+    if hasattr(pat, 'read') and 'r' in mode:
+        return pat
+    return open(make_filename(repo, pat, node, total, seqno, revwidth,
+                              pathname),
+                mode)
+
+def matchpats(repo, pats=[], opts={}, head=''):
+    cwd = repo.getcwd()
+    if not pats and cwd:
+        opts['include'] = [os.path.join(cwd, i)
+                           for i in opts.get('include', [])]
+        opts['exclude'] = [os.path.join(cwd, x)
+                           for x in opts.get('exclude', [])]
+        cwd = ''
+    return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'),
+                           opts.get('exclude'), head)
+
+def makewalk(repo, pats=[], opts={}, node=None, head='', badmatch=None):
+    files, matchfn, anypats = matchpats(repo, pats, opts, head)
+    exact = dict(zip(files, files))
+    def walk():
+        for src, fn in repo.walk(node=node, files=files, match=matchfn,
+                                 badmatch=badmatch):
+            yield src, fn, util.pathto(repo.getcwd(), fn), fn in exact
+    return files, matchfn, walk()
+
+def walk(repo, pats=[], opts={}, node=None, head='', badmatch=None):
+    files, matchfn, results = makewalk(repo, pats, opts, node, head, badmatch)
+    for r in results:
+        yield r
+
+def findrenames(repo, added=None, removed=None, threshold=0.5):
+    if added is None or removed is None:
+        added, removed = repo.status()[1:3]
+    changes = repo.changelog.read(repo.dirstate.parents()[0])
+    mf = repo.manifest.read(changes[0])
+    for a in added:
+        aa = repo.wread(a)
+        bestscore, bestname = None, None
+        for r in removed:
+            rr = repo.file(r).read(mf[r])
+            delta = mdiff.textdiff(aa, rr)
+            if len(delta) < len(aa):
+                myscore = 1.0 - (float(len(delta)) / len(aa))
+                if bestscore is None or myscore > bestscore:
+                    bestscore, bestname = myscore, r
+        if bestname and bestscore >= threshold:
+            yield bestname, a, bestscore
+
+def addremove(repo, pats=[], opts={}, wlock=None, dry_run=None,
+              similarity=None):
+    if dry_run is None:
+        dry_run = opts.get('dry_run')
+    if similarity is None:
+        similarity = float(opts.get('similarity') or 0)
+    add, remove = [], []
+    mapping = {}
+    for src, abs, rel, exact in walk(repo, pats, opts):
+        if src == 'f' and repo.dirstate.state(abs) == '?':
+            add.append(abs)
+            mapping[abs] = rel, exact
+            if repo.ui.verbose or not exact:
+                repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
+        if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
+            remove.append(abs)
+            mapping[abs] = rel, exact
+            if repo.ui.verbose or not exact:
+                repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
+    if not dry_run:
+        repo.add(add, wlock=wlock)
+        repo.remove(remove, wlock=wlock)
+    if similarity > 0:
+        for old, new, score in findrenames(repo, add, remove, similarity):
+            oldrel, oldexact = mapping[old]
+            newrel, newexact = mapping[new]
+            if repo.ui.verbose or not oldexact or not newexact:
+                repo.ui.status(_('recording removal of %s as rename to %s '
+                                 '(%d%% similar)\n') %
+                               (oldrel, newrel, score * 100))
+            if not dry_run:
+                repo.copy(old, new, wlock=wlock)
--- a/mercurial/commands.py	Sat Sep 02 22:58:02 2006 -0400
+++ b/mercurial/commands.py	Sat Oct 21 15:22:08 2006 -0400
@@ -1,6 +1,6 @@
 # commands.py - command processing for mercurial
 #
-# Copyright 2005 Matt Mackall <mpm@selenic.com>
+# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
 #
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
@@ -8,12 +8,12 @@
 from demandload import demandload
 from node import *
 from i18n import gettext as _
-demandload(globals(), "os re sys signal shutil imp urllib pdb")
+demandload(globals(), "os re sys signal shutil imp urllib pdb shlex")
 demandload(globals(), "fancyopts ui hg util lock revlog templater bundlerepo")
-demandload(globals(), "fnmatch mdiff random signal tempfile time")
+demandload(globals(), "fnmatch difflib patch random signal tempfile time")
 demandload(globals(), "traceback errno socket version struct atexit sets bz2")
-demandload(globals(), "archival cStringIO changegroup email.Parser")
-demandload(globals(), "hgweb.server sshserver")
+demandload(globals(), "archival cStringIO changegroup")
+demandload(globals(), "cmdutil hgweb.server sshserver")
 
 class UnknownCommand(Exception):
     """Exception raised if command is not in the command table."""
@@ -21,19 +21,10 @@
     """Exception raised if command shortcut matches more than one command."""
 
 def bail_if_changed(repo):
-    modified, added, removed, deleted, unknown = repo.changes()
+    modified, added, removed, deleted = repo.status()[:4]
     if modified or added or removed or deleted:
         raise util.Abort(_("outstanding uncommitted changes"))
 
-def filterfiles(filters, files):
-    l = [x for x in files if x in filters]
-
-    for t in filters:
-        if t and t[-1] != "/":
-            t += "/"
-        l += [x for x in files if x.startswith(t)]
-    return l
-
 def relpath(repo, args):
     cwd = repo.getcwd()
     if cwd:
@@ -59,29 +50,6 @@
                              (logfile, inst.strerror))
     return message
 
-def matchpats(repo, pats=[], opts={}, head=''):
-    cwd = repo.getcwd()
-    if not pats and cwd:
-        opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
-        opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
-        cwd = ''
-    return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'),
-                           opts.get('exclude'), head)
-
-def makewalk(repo, pats, opts, node=None, head='', badmatch=None):
-    files, matchfn, anypats = matchpats(repo, pats, opts, head)
-    exact = dict(zip(files, files))
-    def walk():
-        for src, fn in repo.walk(node=node, files=files, match=matchfn,
-                                 badmatch=badmatch):
-            yield src, fn, util.pathto(repo.getcwd(), fn), fn in exact
-    return files, matchfn, walk()
-
-def walk(repo, pats, opts, node=None, head='', badmatch=None):
-    files, matchfn, results = makewalk(repo, pats, opts, node, head, badmatch)
-    for r in results:
-        yield r
-
 def walkchangerevs(ui, repo, pats, opts):
     '''Iterate over files and the revs they changed in.
 
@@ -124,23 +92,17 @@
                     windowsize *= 2
 
 
-    files, matchfn, anypats = matchpats(repo, pats, opts)
+    files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
     follow = opts.get('follow') or opts.get('follow_first')
 
     if repo.changelog.count() == 0:
         return [], False, matchfn
 
     if follow:
-        p = repo.dirstate.parents()[0]
-        if p == nullid:
-            ui.warn(_('No working directory revision; defaulting to tip\n'))
-            start = 'tip'
-        else:
-            start = repo.changelog.rev(p)
-        defrange = '%s:0' % start
+        defrange = '%s:0' % repo.changectx().rev()
     else:
         defrange = 'tip:0'
-    revs = map(int, revrange(ui, repo, opts['rev'] or [defrange]))
+    revs = map(int, cmdutil.revrange(ui, repo, opts['rev'] or [defrange]))
     wanted = {}
     slowpath = anypats
     fncache = {}
@@ -215,49 +177,59 @@
                 fncache[rev] = matches
                 wanted[rev] = 1
 
-    def iterate():
-        class followfilter:
-            def __init__(self, onlyfirst=False):
-                self.startrev = -1
-                self.roots = []
-                self.onlyfirst = onlyfirst
-
-            def match(self, rev):
-                def realparents(rev):
-                    if self.onlyfirst:
-                        return repo.changelog.parentrevs(rev)[0:1]
-                    else:
-                        return filter(lambda x: x != -1, repo.changelog.parentrevs(rev))
-
-                if self.startrev == -1:
-                    self.startrev = rev
+    class followfilter:
+        def __init__(self, onlyfirst=False):
+            self.startrev = -1
+            self.roots = []
+            self.onlyfirst = onlyfirst
+
+        def match(self, rev):
+            def realparents(rev):
+                if self.onlyfirst:
+                    return repo.changelog.parentrevs(rev)[0:1]
+                else:
+                    return filter(lambda x: x != -1, repo.changelog.parentrevs(rev))
+
+            if self.startrev == -1:
+                self.startrev = rev
+                return True
+
+            if rev > self.startrev:
+                # forward: all descendants
+                if not self.roots:
+                    self.roots.append(self.startrev)
+                for parent in realparents(rev):
+                    if parent in self.roots:
+                        self.roots.append(rev)
+                        return True
+            else:
+                # backwards: all parents
+                if not self.roots:
+                    self.roots.extend(realparents(self.startrev))
+                if rev in self.roots:
+                    self.roots.remove(rev)
+                    self.roots.extend(realparents(rev))
                     return True
 
-                if rev > self.startrev:
-                    # forward: all descendants
-                    if not self.roots:
-                        self.roots.append(self.startrev)
-                    for parent in realparents(rev):
-                        if parent in self.roots:
-                            self.roots.append(rev)
-                            return True
-                else:
-                    # backwards: all parents
-                    if not self.roots:
-                        self.roots.extend(realparents(self.startrev))
-                    if rev in self.roots:
-                        self.roots.remove(rev)
-                        self.roots.extend(realparents(rev))
-                        return True
-
-                return False
-
+            return False
+
+    # it might be worthwhile to do this in the iterator if the rev range
+    # is descending and the prune args are all within that range
+    for rev in opts.get('prune', ()):
+        rev = repo.changelog.rev(repo.lookup(rev))
+        ff = followfilter()
+        stop = min(revs[0], revs[-1])
+        for x in range(rev, stop-1, -1):
+            if ff.match(x) and wanted.has_key(x):
+                del wanted[x]
+
+    def iterate():
         if follow and not files:
             ff = followfilter(onlyfirst=opts.get('follow_first'))
             def want(rev):
-                if rev not in wanted:
-                    return False
-                return ff.match(rev)
+                if ff.match(rev) and rev in wanted:
+                    return True
+                return False
         else:
             def want(rev):
                 return rev in wanted
@@ -274,133 +246,6 @@
                 yield 'iter', rev, None
     return iterate(), getchange, matchfn
 
-revrangesep = ':'
-
-def revfix(repo, val, defval):
-    '''turn user-level id of changeset into rev number.
-    user-level id can be tag, changeset, rev number, or negative rev
-    number relative to number of revs (-1 is tip, etc).'''
-    if not val:
-        return defval
-    try:
-        num = int(val)
-        if str(num) != val:
-            raise ValueError
-        if num < 0:
-            num += repo.changelog.count()
-        if num < 0:
-            num = 0
-        elif num >= repo.changelog.count():
-            raise ValueError
-    except ValueError:
-        try:
-            num = repo.changelog.rev(repo.lookup(val))
-        except KeyError:
-            raise util.Abort(_('invalid revision identifier %s'), val)
-    return num
-
-def revpair(ui, repo, revs):
-    '''return pair of nodes, given list of revisions. second item can
-    be None, meaning use working dir.'''
-    if not revs:
-        return repo.dirstate.parents()[0], None
-    end = None
-    if len(revs) == 1:
-        start = revs[0]
-        if revrangesep in start:
-            start, end = start.split(revrangesep, 1)
-            start = revfix(repo, start, 0)
-            end = revfix(repo, end, repo.changelog.count() - 1)
-        else:
-            start = revfix(repo, start, None)
-    elif len(revs) == 2:
-        if revrangesep in revs[0] or revrangesep in revs[1]:
-            raise util.Abort(_('too many revisions specified'))
-        start = revfix(repo, revs[0], None)
-        end = revfix(repo, revs[1], None)
-    else:
-        raise util.Abort(_('too many revisions specified'))
-    if end is not None: end = repo.lookup(str(end))
-    return repo.lookup(str(start)), end
-
-def revrange(ui, repo, revs):
-    """Yield revision as strings from a list of revision specifications."""
-    seen = {}
-    for spec in revs:
-        if revrangesep in spec:
-            start, end = spec.split(revrangesep, 1)
-            start = revfix(repo, start, 0)
-            end = revfix(repo, end, repo.changelog.count() - 1)
-            step = start > end and -1 or 1
-            for rev in xrange(start, end+step, step):
-                if rev in seen:
-                    continue
-                seen[rev] = 1
-                yield str(rev)
-        else:
-            rev = revfix(repo, spec, None)
-            if rev in seen:
-                continue
-            seen[rev] = 1
-            yield str(rev)
-
-def make_filename(repo, pat, node,
-                  total=None, seqno=None, revwidth=None, pathname=None):
-    node_expander = {
-        'H': lambda: hex(node),
-        'R': lambda: str(repo.changelog.rev(node)),
-        'h': lambda: short(node),
-        }
-    expander = {
-        '%': lambda: '%',
-        'b': lambda: os.path.basename(repo.root),
-        }
-
-    try:
-        if node:
-            expander.update(node_expander)
-        if node and revwidth is not None:
-            expander['r'] = (lambda:
-                    str(repo.changelog.rev(node)).zfill(revwidth))
-        if total is not None:
-            expander['N'] = lambda: str(total)
-        if seqno is not None:
-            expander['n'] = lambda: str(seqno)
-        if total is not None and seqno is not None:
-            expander['n'] = lambda:str(seqno).zfill(len(str(total)))
-        if pathname is not None:
-            expander['s'] = lambda: os.path.basename(pathname)
-            expander['d'] = lambda: os.path.dirname(pathname) or '.'
-            expander['p'] = lambda: pathname
-
-        newname = []
-        patlen = len(pat)
-        i = 0
-        while i < patlen:
-            c = pat[i]
-            if c == '%':
-                i += 1
-                c = pat[i]
-                c = expander[c]()
-            newname.append(c)
-            i += 1
-        return ''.join(newname)
-    except KeyError, inst:
-        raise util.Abort(_("invalid format spec '%%%s' in output file name"),
-                    inst.args[0])
-
-def make_file(repo, pat, node=None,
-              total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
-    if not pat or pat == '-':
-        return 'w' in mode and sys.stdout or sys.stdin
-    if hasattr(pat, 'write') and 'w' in mode:
-        return pat
-    if hasattr(pat, 'read') and 'r' in mode:
-        return pat
-    return open(make_filename(repo, pat, node, total, seqno, revwidth,
-                              pathname),
-                mode)
-
 def write_bundle(cg, filename=None, compress=True):
     """Write a bundle file and return its filename.
 
@@ -420,7 +265,7 @@
     try:
         if filename:
             if os.path.exists(filename):
-                raise util.Abort(_("file '%s' already exists"), filename)
+                raise util.Abort(_("file '%s' already exists") % filename)
             fh = open(filename, "wb")
         else:
             fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
@@ -453,74 +298,6 @@
         if cleanup is not None:
             os.unlink(cleanup)
 
-def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
-           changes=None, text=False, opts={}):
-    if not node1:
-        node1 = repo.dirstate.parents()[0]
-    # reading the data for node1 early allows it to play nicely
-    # with repo.changes and the revlog cache.
-    change = repo.changelog.read(node1)
-    mmap = repo.manifest.read(change[0])
-    date1 = util.datestr(change[2])
-
-    if not changes:
-        changes = repo.changes(node1, node2, files, match=match)
-    modified, added, removed, deleted, unknown = changes
-    if files:
-        modified, added, removed = map(lambda x: filterfiles(files, x),
-                                       (modified, added, removed))
-
-    if not modified and not added and not removed:
-        return
-
-    if node2:
-        change = repo.changelog.read(node2)
-        mmap2 = repo.manifest.read(change[0])
-        _date2 = util.datestr(change[2])
-        def date2(f):
-            return _date2
-        def read(f):
-            return repo.file(f).read(mmap2[f])
-    else:
-        tz = util.makedate()[1]
-        _date2 = util.datestr()
-        def date2(f):
-            try:
-                return util.datestr((os.lstat(repo.wjoin(f)).st_mtime, tz))
-            except OSError, err:
-                if err.errno != errno.ENOENT: raise
-                return _date2
-        def read(f):
-            return repo.wread(f)
-
-    if ui.quiet:
-        r = None
-    else:
-        hexfunc = ui.verbose and hex or short
-        r = [hexfunc(node) for node in [node1, node2] if node]
-
-    diffopts = ui.diffopts()
-    showfunc = opts.get('show_function') or diffopts['showfunc']
-    ignorews = opts.get('ignore_all_space') or diffopts['ignorews']
-    ignorewsamount = opts.get('ignore_space_change') or \
-                     diffopts['ignorewsamount']
-    ignoreblanklines = opts.get('ignore_blank_lines') or \
-                     diffopts['ignoreblanklines']
-
-    all = modified + added + removed
-    all.sort()
-    for f in all:
-        to = None
-        tn = None
-        if f in mmap:
-            to = repo.file(f).read(mmap[f])
-        if f not in removed:
-            tn = read(f)
-        fp.write(mdiff.unidiff(to, date1, tn, date2(f), f, r, text=text,
-                               showfunc=showfunc, ignorews=ignorews,
-                               ignorewsamount=ignorewsamount,
-                               ignoreblanklines=ignoreblanklines))
-
 def trimuser(ui, name, rev, revcache):
     """trim the name of the user who committed a change"""
     user = revcache.get(rev)
@@ -535,7 +312,7 @@
         self.ui = ui
         self.repo = repo
 
-    def show(self, rev=0, changenode=None, brinfo=None):
+    def show(self, rev=0, changenode=None, brinfo=None, copies=None):
         '''show a single changeset or file revision'''
         log = self.repo.changelog
         if changenode is None:
@@ -549,19 +326,20 @@
 
         changes = log.read(changenode)
         date = util.datestr(changes[2])
-
-        parents = [(log.rev(p), self.ui.verbose and hex(p) or short(p))
-                   for p in log.parents(changenode)
+        branch = changes[5].get("branch")
+
+        hexfunc = self.ui.debugflag and hex or short
+
+        parents = [(log.rev(p), hexfunc(p)) for p in log.parents(changenode)
                    if self.ui.debugflag or p != nullid]
         if (not self.ui.debugflag and len(parents) == 1 and
             parents[0][0] == rev-1):
             parents = []
 
-        if self.ui.verbose:
-            self.ui.write(_("changeset:   %d:%s\n") % (rev, hex(changenode)))
-        else:
-            self.ui.write(_("changeset:   %d:%s\n") % (rev, short(changenode)))
-
+        self.ui.write(_("changeset:   %d:%s\n") % (rev, hexfunc(changenode)))
+
+        if branch:
+            self.ui.status(_("branch:      %s\n") % branch)
         for tag in self.repo.nodetags(changenode):
             self.ui.status(_("tag:         %s\n") % tag)
         for parent in parents:
@@ -577,13 +355,16 @@
         self.ui.status(_("date:        %s\n") % date)
 
         if self.ui.debugflag:
-            files = self.repo.changes(log.parents(changenode)[0], changenode)
+            files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
             for key, value in zip([_("files:"), _("files+:"), _("files-:")],
                                   files):
                 if value:
                     self.ui.note("%-12s %s\n" % (key, " ".join(value)))
         else:
             self.ui.note(_("files:       %s\n") % " ".join(changes[3]))
+        if copies:
+            copies = ['%s (%s)' % c for c in copies]
+            self.ui.note(_("copies:      %s\n") % ' '.join(copies))
 
         description = changes[4].strip()
         if description:
@@ -597,21 +378,36 @@
         self.ui.status("\n")
 
 def show_changeset(ui, repo, opts):
-    '''show one changeset.  uses template or regular display.  caller
-    can pass in 'style' and 'template' options in opts.'''
-
+    """show one changeset using template or regular display.
+
+    Display format will be the first non-empty hit of:
+    1. option 'template'
+    2. option 'style'
+    3. [ui] setting 'logtemplate'
+    4. [ui] setting 'style'
+    If all of these values are either the unset or the empty string,
+    regular display via changeset_printer() is done.
+    """
+    # options
     tmpl = opts.get('template')
+    mapfile = None
     if tmpl:
         tmpl = templater.parsestring(tmpl, quoted=False)
     else:
-        tmpl = ui.config('ui', 'logtemplate')
-        if tmpl: tmpl = templater.parsestring(tmpl)
-    mapfile = opts.get('style') or ui.config('ui', 'style')
+        mapfile = opts.get('style')
+        # ui settings
+        if not mapfile:
+            tmpl = ui.config('ui', 'logtemplate')
+            if tmpl:
+                tmpl = templater.parsestring(tmpl)
+            else:
+                mapfile = ui.config('ui', 'style')
+
     if tmpl or mapfile:
         if mapfile:
-            if not os.path.isfile(mapfile):
-                mapname = templater.templatepath('map-cmdline.' + mapfile)
-                if not mapname: mapname = templater.templatepath(mapfile)
+            if not os.path.split(mapfile)[0]:
+                mapname = (templater.templatepath('map-cmdline.' + mapfile)
+                           or templater.templatepath(mapfile))
                 if mapname: mapfile = mapname
         try:
             t = templater.changeset_templater(ui, repo, mapfile)
@@ -633,7 +429,7 @@
     ui.write(_("Mercurial Distributed SCM (version %s)\n")
              % version.get_version())
     ui.status(_(
-        "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
+        "\nCopyright (C) 2005, 2006 Matt Mackall <mpm@selenic.com>\n"
         "This is free software; see the source for copying conditions. "
         "There is NO\nwarranty; "
         "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
@@ -654,7 +450,7 @@
         if with_version:
             show_version(ui)
             ui.write('\n')
-        aliases, i = findcmd(name)
+        aliases, i = findcmd(ui, name)
         # synopsis
         ui.write("%s\n\n" % i[2])
 
@@ -787,7 +583,7 @@
     """
 
     names = []
-    for src, abs, rel, exact in walk(repo, pats, opts):
+    for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
         if exact:
             if ui.verbose:
                 ui.status(_('adding %s\n') % rel)
@@ -799,35 +595,23 @@
         repo.add(names)
 
 def addremove(ui, repo, *pats, **opts):
-    """add all new files, delete all missing files (DEPRECATED)
-
-    (DEPRECATED)
+    """add all new files, delete all missing files
+
     Add all new files and remove all missing files from the repository.
 
     New files are ignored if they match any of the patterns in .hgignore. As
     with add, these changes take effect at the next commit.
 
-    This command is now deprecated and will be removed in a future
-    release. Please use add and remove --after instead.
+    Use the -s option to detect renamed files.  With a parameter > 0,
+    this compares every removed file with every added file and records
+    those similar enough as renames.  This option takes a percentage
+    between 0 (disabled) and 100 (files must be identical) as its
+    parameter.  Detecting renamed files this way can be expensive.
     """
-    ui.warn(_('(the addremove command is deprecated; use add and remove '
-              '--after instead)\n'))
-    return addremove_lock(ui, repo, pats, opts)
-
-def addremove_lock(ui, repo, pats, opts, wlock=None):
-    add, remove = [], []
-    for src, abs, rel, exact in walk(repo, pats, opts):
-        if src == 'f' and repo.dirstate.state(abs) == '?':
-            add.append(abs)
-            if ui.verbose or not exact:
-                ui.status(_('adding %s\n') % ((pats and rel) or abs))
-        if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
-            remove.append(abs)
-            if ui.verbose or not exact:
-                ui.status(_('removing %s\n') % ((pats and rel) or abs))
-    if not opts.get('dry_run'):
-        repo.add(add, wlock=wlock)
-        repo.remove(remove, wlock=wlock)
+    sim = float(opts.get('similarity') or 0)
+    if sim < 0 or sim > 100:
+        raise util.Abort(_('similarity must be between 0 and 100'))
+    return cmdutil.addremove(repo, pats, opts, similarity=sim/100.)
 
 def annotate(ui, repo, *pats, **opts):
     """show changeset information per file line
@@ -841,42 +625,29 @@
     detects as binary. With -a, annotate will generate an annotation
     anyway, probably with undesirable results.
     """
-    def getnode(rev):
-        return short(repo.changelog.node(rev))
-
-    ucache = {}
-    def getname(rev):
-        try:
-            return ucache[rev]
-        except:
-            u = trimuser(ui, repo.changectx(rev).user(), rev, ucache)
-            ucache[rev] = u
-            return u
-
-    dcache = {}
-    def getdate(rev):
-        datestr = dcache.get(rev)
-        if datestr is None:
-            datestr = dcache[rev] = util.datestr(repo.changectx(rev).date())
-        return datestr
+    getdate = util.cachefunc(lambda x: util.datestr(x.date()))
 
     if not pats:
         raise util.Abort(_('at least one file name or pattern required'))
 
-    opmap = [['user', getname], ['number', str], ['changeset', getnode],
-             ['date', getdate]]
-    if not opts['user'] and not opts['changeset'] and not opts['date']:
+    opmap = [['user', lambda x: ui.shortuser(x.user())],
+             ['number', lambda x: str(x.rev())],
+             ['changeset', lambda x: short(x.node())],
+             ['date', getdate], ['follow', lambda x: x.path()]]
+    if (not opts['user'] and not opts['changeset'] and not opts['date']
+        and not opts['follow']):
         opts['number'] = 1
 
-    ctx = repo.changectx(opts['rev'] or repo.dirstate.parents()[0])
-
-    for src, abs, rel, exact in walk(repo, pats, opts, node=ctx.node()):
+    ctx = repo.changectx(opts['rev'])
+
+    for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
+                                             node=ctx.node()):
         fctx = ctx.filectx(abs)
         if not opts['text'] and util.binary(fctx.data()):
             ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
             continue
 
-        lines = fctx.annotate()
+        lines = fctx.annotate(follow=opts.get('follow'))
         pieces = []
 
         for o, f in opmap:
@@ -914,18 +685,11 @@
     The default is the basename of the archive, with suffixes removed.
     '''
 
-    if opts['rev']:
-        node = repo.lookup(opts['rev'])
-    else:
-        node, p2 = repo.dirstate.parents()
-        if p2 != nullid:
-            raise util.Abort(_('uncommitted merge - please provide a '
-                               'specific revision'))
-
-    dest = make_filename(repo, dest, node)
+    node = repo.changectx(opts['rev']).node()
+    dest = cmdutil.make_filename(repo, dest, node)
     if os.path.realpath(dest) == repo.root:
         raise util.Abort(_('repository root cannot be destination'))
-    dummy, matchfn, dummy = matchpats(repo, [], opts)
+    dummy, matchfn, dummy = cmdutil.matchpats(repo, [], opts)
     kind = opts.get('type') or 'files'
     prefix = opts['prefix']
     if dest == '-':
@@ -933,7 +697,7 @@
             raise util.Abort(_('cannot archive plain files to stdout'))
         dest = sys.stdout
         if not prefix: prefix = os.path.basename(repo.root) + '-%h'
-    prefix = make_filename(repo, prefix, node)
+    prefix = cmdutil.make_filename(repo, prefix, node)
     archival.archive(repo, dest, node, kind, not opts['no_decode'],
                      matchfn, prefix)
 
@@ -978,6 +742,7 @@
         parent = p1
     hg.clean(repo, node, show_stats=False)
     revert_opts = opts.copy()
+    revert_opts['all'] = True
     revert_opts['rev'] = hex(parent)
     revert(ui, repo, **revert_opts)
     commit_opts = opts.copy()
@@ -1004,9 +769,12 @@
 def bundle(ui, repo, fname, dest=None, **opts):
     """create a changegroup file
 
-    Generate a compressed changegroup file collecting all changesets
+    Generate a compressed changegroup file collecting changesets.
     not found in the other repository.
 
+    If no destination repository is specified the destination is
+    assumed to have all the node specified by --base.
+
     This file can then be transferred using conventional means and
     applied to another repository with the unbundle command. This is
     useful when native push and pull are not available or when
@@ -1016,17 +784,51 @@
     Unlike import/export, this exactly preserves all changeset
     contents including permissions, rename data, and revision history.
     """
-    dest = ui.expandpath(dest or 'default-push', dest or 'default')
-    other = hg.repository(ui, dest)
-    o = repo.findoutgoing(other, force=opts['force'])
-    cg = repo.changegroup(o, 'bundle')
+    revs = opts.get('rev') or None
+    if revs:
+        revs = [repo.lookup(rev) for rev in revs]
+    base = opts.get('base')
+    if base:
+        if dest:
+            raise util.Abort(_("--base is incompatible with specifiying "
+                               "a destination"))
+        base = [repo.lookup(rev) for rev in base]
+        # create the right base
+        # XXX: nodesbetween / changegroup* should be "fixed" instead
+        o = []
+        has_set = sets.Set(base)
+        for n in base:
+            has_set.update(repo.changelog.reachable(n))
+        if revs:
+            visit = list(revs)
+        else:
+            visit = repo.changelog.heads()
+        while visit:
+            n = visit.pop(0)
+            parents = [p for p in repo.changelog.parents(n)
+                       if p != nullid and p not in has_set]
+            if len(parents) == 0:
+                o.insert(0, n)
+            else:
+                visit.extend(parents)
+    else:
+        setremoteconfig(ui, opts)
+        dest = ui.expandpath(dest or 'default-push', dest or 'default')
+        other = hg.repository(ui, dest)
+        o = repo.findoutgoing(other, force=opts['force'])
+
+    if revs:
+        cg = repo.changegroupsubset(o, revs, 'bundle')
+    else:
+        cg = repo.changegroup(o, 'bundle')
     write_bundle(cg, fname)
 
 def cat(ui, repo, file1, *pats, **opts):
     """output the latest or given revisions of files
 
     Print the specified files as they were at the given revision.
-    If no revision is given then the tip is used.
+    If no revision is given then working dir parent is used, or tip
+    if no revision is checked out.
 
     Output may be to a file, in which case the name of the file is
     given using a format string.  The formatting rules are the same as
@@ -1036,9 +838,10 @@
     %d   dirname of file being printed, or '.' if in repo root
     %p   root-relative path name of file being printed
     """
-    ctx = repo.changectx(opts['rev'] or "-1")
-    for src, abs, rel, exact in walk(repo, (file1,) + pats, opts, ctx.node()):
-        fp = make_file(repo, opts['output'], ctx.node(), pathname=abs)
+    ctx = repo.changectx(opts['rev'])
+    for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
+                                             ctx.node()):
+        fp = cmdutil.make_file(repo, opts['output'], ctx.node(), pathname=abs)
         fp.write(ctx.filectx(abs).data())
 
 def clone(ui, source, dest=None, **opts):
@@ -1100,11 +903,10 @@
     message = logmessage(opts)
 
     if opts['addremove']:
-        addremove_lock(ui, repo, pats, opts)
-    fns, match, anypats = matchpats(repo, pats, opts)
+        cmdutil.addremove(repo, pats, opts)
+    fns, match, anypats = cmdutil.matchpats(repo, pats, opts)
     if pats:
-        modified, added, removed, deleted, unknown = (
-            repo.changes(files=fns, match=match))
+        modified, added, removed = repo.status(files=fns, match=match)[:3]
         files = modified + added + removed
     else:
         files = []
@@ -1259,7 +1061,7 @@
     copylist = []
     for pat in pats:
         srcs = []
-        for tag, abssrc, relsrc, exact in walk(repo, [pat], opts):
+        for tag, abssrc, relsrc, exact in cmdutil.walk(repo, [pat], opts):
             origsrc = okaytocopy(abssrc, relsrc, exact)
             if origsrc:
                 srcs.append((origsrc, abssrc, relsrc, exact))
@@ -1311,7 +1113,7 @@
         options = []
         otables = [globalopts]
         if cmd:
-            aliases, entry = findcmd(cmd)
+            aliases, entry = findcmd(ui, cmd)
             otables.append(entry[1])
         for t in otables:
             for o in t:
@@ -1321,7 +1123,7 @@
         ui.write("%s\n" % "\n".join(options))
         return
 
-    clist = findpossible(cmd).keys()
+    clist = findpossible(ui, cmd).keys()
     clist.sort()
     ui.write("%s\n" % "\n".join(clist))
 
@@ -1370,7 +1172,7 @@
         error = _(".hg/dirstate inconsistent with current parent's manifest")
         raise util.Abort(error)
 
-def debugconfig(ui, repo, *values):
+def showconfig(ui, repo, *values):
     """show combined config settings from all hgrc files
 
     With no args, print names and values of all config items.
@@ -1418,8 +1220,8 @@
                  % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
                     time.strftime("%x %X",
                                   time.localtime(dc[file_][3])), file_))
-    for f in repo.dirstate.copies:
-        ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copies[f], f))
+    for f in repo.dirstate.copies():
+        ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
 
 def debugdata(ui, file_, rev):
     """dump the contents of an data file revision"""
@@ -1428,7 +1230,7 @@
     try:
         ui.write(r.revision(r.lookup(rev)))
     except KeyError:
-        raise util.Abort(_('invalid revision identifier %s'), rev)
+        raise util.Abort(_('invalid revision identifier %s') % rev)
 
 def debugindex(ui, file_):
     """dump the contents of an index file"""
@@ -1476,7 +1278,7 @@
 
 def debugwalk(ui, repo, *pats, **opts):
     """show how files match on given patterns"""
-    items = list(walk(repo, pats, opts))
+    items = list(cmdutil.walk(repo, pats, opts))
     if not items:
         return
     fmt = '%%s  %%-%ds  %%-%ds  %%s' % (
@@ -1503,39 +1305,12 @@
     it detects as binary. With -a, diff will generate a diff anyway,
     probably with undesirable results.
     """
-    node1, node2 = revpair(ui, repo, opts['rev'])
-
-    fns, matchfn, anypats = matchpats(repo, pats, opts)
-
-    dodiff(sys.stdout, ui, repo, node1, node2, fns, match=matchfn,
-           text=opts['text'], opts=opts)
-
-def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
-    node = repo.lookup(changeset)
-    parents = [p for p in repo.changelog.parents(node) if p != nullid]
-    if opts['switch_parent']:
-        parents.reverse()
-    prev = (parents and parents[0]) or nullid
-    change = repo.changelog.read(node)
-
-    fp = make_file(repo, opts['output'], node, total=total, seqno=seqno,
-                   revwidth=revwidth)
-    if fp != sys.stdout:
-        ui.note("%s\n" % fp.name)
-
-    fp.write("# HG changeset patch\n")
-    fp.write("# User %s\n" % change[1])
-    fp.write("# Date %d %d\n" % change[2])
-    fp.write("# Node ID %s\n" % hex(node))
-    fp.write("# Parent  %s\n" % hex(prev))
-    if len(parents) > 1:
-        fp.write("# Parent  %s\n" % hex(parents[1]))
-    fp.write(change[4].rstrip())
-    fp.write("\n\n")
-
-    dodiff(fp, ui, repo, prev, node, text=opts['text'])
-    if fp != sys.stdout:
-        fp.close()
+    node1, node2 = cmdutil.revpair(ui, repo, opts['rev'])
+
+    fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
+
+    patch.diff(repo, node1, node2, fns, match=matchfn,
+               opts=patch.diffopts(ui, opts))
 
 def export(ui, repo, *changesets, **opts):
     """dump the header and diffs for one or more changesets
@@ -1566,15 +1341,14 @@
     """
     if not changesets:
         raise util.Abort(_("export requires at least one changeset"))
-    seqno = 0
-    revs = list(revrange(ui, repo, changesets))
-    total = len(revs)
-    revwidth = max(map(len, revs))
-    msg = len(revs) > 1 and _("Exporting patches:\n") or _("Exporting patch:\n")
-    ui.note(msg)
-    for cset in revs:
-        seqno += 1
-        doexport(ui, repo, cset, seqno, total, revwidth, opts)
+    revs = list(cmdutil.revrange(ui, repo, changesets))
+    if len(revs) > 1:
+        ui.note(_('exporting patches:\n'))
+    else:
+        ui.note(_('exporting patch:\n'))
+    patch.export(repo, map(repo.lookup, revs), template=opts['output'],
+                 switch_parent=opts['switch_parent'],
+                 opts=patch.diffopts(ui, opts))
 
 def forget(ui, repo, *pats, **opts):
     """don't add the specified files on the next commit (DEPRECATED)
@@ -1587,7 +1361,7 @@
     """
     ui.warn(_("(the forget command is deprecated; use revert instead)\n"))
     forget = []
-    for src, abs, rel, exact in walk(repo, pats, opts):
+    for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
         if repo.dirstate.state(abs) == 'a':
             forget.append(abs)
             if ui.verbose or not exact:
@@ -1644,42 +1418,56 @@
             self.linenum = linenum
             self.colstart = colstart
             self.colend = colend
+
         def __eq__(self, other):
             return self.line == other.line
-        def __hash__(self):
-            return hash(self.line)
 
     matches = {}
+    copies = {}
     def grepbody(fn, rev, body):
-        matches[rev].setdefault(fn, {})
+        matches[rev].setdefault(fn, [])
         m = matches[rev][fn]
         for lnum, cstart, cend, line in matchlines(body):
             s = linestate(line, lnum, cstart, cend)
-            m[s] = s
-
-    # FIXME: prev isn't used, why ?
+            m.append(s)
+
+    def difflinestates(a, b):
+        sm = difflib.SequenceMatcher(None, a, b)
+        for tag, alo, ahi, blo, bhi in sm.get_opcodes():
+            if tag == 'insert':
+                for i in range(blo, bhi):
+                    yield ('+', b[i])
+            elif tag == 'delete':
+                for i in range(alo, ahi):
+                    yield ('-', a[i])
+            elif tag == 'replace':
+                for i in range(alo, ahi):
+                    yield ('-', a[i])
+                for i in range(blo, bhi):
+                    yield ('+', b[i])
+
     prev = {}
     ucache = {}
     def display(fn, rev, states, prevstates):
-        diff = list(sets.Set(states).symmetric_difference(sets.Set(prevstates)))
-        diff.sort(lambda x, y: cmp(x.linenum, y.linenum))
         counts = {'-': 0, '+': 0}
         filerevmatches = {}
-        for l in diff:
+        if incrementing or not opts['all']:
+            a, b = prevstates, states
+        else:
+            a, b = states, prevstates
+        for change, l in difflinestates(a, b):
             if incrementing or not opts['all']:
-                change = ((l in prevstates) and '-') or '+'
                 r = rev
             else:
-                change = ((l in states) and '-') or '+'
                 r = prev[fn]
-            cols = [fn, str(rev)]
+            cols = [fn, str(r)]
             if opts['line_number']:
                 cols.append(str(l.linenum))
             if opts['all']:
                 cols.append(change)
             if opts['user']:
-                cols.append(trimuser(ui, getchange(rev)[1], rev,
-                                                  ucache))
+                cols.append(trimuser(ui, getchange(r)[1], rev,
+                                     ucache))
             if opts['files_with_matches']:
                 c = (fn, rev)
                 if c in filerevmatches:
@@ -1696,6 +1484,7 @@
     changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
     count = 0
     incrementing = False
+    follow = opts.get('follow')
     for st, rev, fns in changeiter:
         if st == 'window':
             incrementing = rev
@@ -1710,20 +1499,31 @@
                 fstate.setdefault(fn, {})
                 try:
                     grepbody(fn, rev, getfile(fn).read(mf[fn]))
+                    if follow:
+                        copied = getfile(fn).renamed(mf[fn])
+                        if copied:
+                            copies.setdefault(rev, {})[fn] = copied[0]
                 except KeyError:
                     pass
         elif st == 'iter':
             states = matches[rev].items()
             states.sort()
             for fn, m in states:
+                copy = copies.get(rev, {}).get(fn)
                 if fn in skip:
+                    if copy:
+                        skip[copy] = True
                     continue
                 if incrementing or not opts['all'] or fstate[fn]:
                     pos, neg = display(fn, rev, m, fstate[fn])
                     count += pos + neg
                     if pos and not opts['all']:
                         skip[fn] = True
+                        if copy:
+                            skip[copy] = True
                 fstate[fn] = m
+                if copy:
+                    fstate[copy] = m
                 prev[fn] = rev
 
     if not incrementing:
@@ -1732,7 +1532,8 @@
         for fn, state in fstate:
             if fn in skip:
                 continue
-            display(fn, rev, {}, state)
+            if fn not in copies.get(prev[fn], {}):
+                display(fn, rev, {}, state)
     return (count == 0 and 1) or 0
 
 def heads(ui, repo, **opts):
@@ -1769,13 +1570,18 @@
         ui.write(_("unknown\n"))
         return
 
-    hexfunc = ui.verbose and hex or short
-    modified, added, removed, deleted, unknown = repo.changes()
+    hexfunc = ui.debugflag and hex or short
+    modified, added, removed, deleted = repo.status()[:4]
     output = ["%s%s" %
               ('+'.join([hexfunc(parent) for parent in parents]),
               (modified or added or removed or deleted) and "+" or "")]
 
     if not ui.quiet:
+
+        branch = repo.workingctx().branch()
+        if branch:
+            output.append("(%s)" % branch)
+
         # multiple tags for a single parent separated by '/'
         parenttags = ['/'.join(tags)
                       for tags in map(repo.nodetags, parents) if tags]
@@ -1814,81 +1620,23 @@
     d = opts["base"]
     strip = opts["strip"]
 
-    mailre = re.compile(r'(?:From |[\w-]+:)')
-
-    # attempt to detect the start of a patch
-    # (this heuristic is borrowed from quilt)
-    diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
-                        'retrieving revision [0-9]+(\.[0-9]+)*$|' +
-                        '(---|\*\*\*)[ \t])', re.MULTILINE)
-
-    for patch in patches:
-        pf = os.path.join(d, patch)
-
-        message = None
-        user = None
-        date = None
-        hgpatch = False
-
-        p = email.Parser.Parser()
+    wlock = repo.wlock()
+    lock = repo.lock()
+
+    for p in patches:
+        pf = os.path.join(d, p)
+
         if pf == '-':
-            msg = p.parse(sys.stdin)
             ui.status(_("applying patch from stdin\n"))
+            tmpname, message, user, date = patch.extract(ui, sys.stdin)
         else:
-            msg = p.parse(file(pf))
-            ui.status(_("applying %s\n") % patch)
-
-        fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
-        tmpfp = os.fdopen(fd, 'w')
+            ui.status(_("applying %s\n") % p)
+            tmpname, message, user, date = patch.extract(ui, file(pf))
+
+        if tmpname is None:
+            raise util.Abort(_('no diffs found'))
+
         try:
-            message = msg['Subject']
-            if message:
-                message = message.replace('\n\t', ' ')
-                ui.debug('Subject: %s\n' % message)
-            user = msg['From']
-            if user:
-                ui.debug('From: %s\n' % user)
-            diffs_seen = 0
-            ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
-            for part in msg.walk():
-                content_type = part.get_content_type()
-                ui.debug('Content-Type: %s\n' % content_type)
-                if content_type not in ok_types:
-                    continue
-                payload = part.get_payload(decode=True)
-                m = diffre.search(payload)
-                if m:
-                    ui.debug(_('found patch at byte %d\n') % m.start(0))
-                    diffs_seen += 1
-                    hgpatch = False
-                    fp = cStringIO.StringIO()
-                    if message:
-                        fp.write(message)
-                        fp.write('\n')
-                    for line in payload[:m.start(0)].splitlines():
-                        if line.startswith('# HG changeset patch'):
-                            ui.debug(_('patch generated by hg export\n'))
-                            hgpatch = True
-                            # drop earlier commit message content
-                            fp.seek(0)
-                            fp.truncate()
-                        elif hgpatch:
-                            if line.startswith('# User '):
-                                user = line[7:]
-                                ui.debug('From: %s\n' % user)
-                            elif line.startswith("# Date "):
-                                date = line[7:]
-                        if not line.startswith('# '):
-                            fp.write(line)
-                            fp.write('\n')
-                    message = fp.getvalue()
-                    if tmpfp:
-                        tmpfp.write(payload)
-                        if not payload.endswith('\n'):
-                            tmpfp.write('\n')
-                elif not diffs_seen and message and content_type == 'text/plain':
-                    message += '\n' + payload
-
             if opts['message']:
                 # pickup the cmdline msg
                 message = opts['message']
@@ -1900,18 +1648,9 @@
                 message = None
             ui.debug(_('message:\n%s\n') % message)
 
-            tmpfp.close()
-            if not diffs_seen:
-                raise util.Abort(_('no diffs found'))
-
-            files = util.patch(strip, tmpname, ui, cwd=repo.root)
-            if len(files) > 0:
-                cfiles = files
-                cwd = repo.getcwd()
-                if cwd:
-                    cfiles = [util.pathto(cwd, f) for f in files]
-                addremove_lock(ui, repo, cfiles, {})
-            repo.commit(files, message, user, date)
+            files, fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root)
+            files = patch.updatedir(ui, repo, files, wlock=wlock)
+            repo.commit(files, message, user, date, wlock=wlock, lock=lock)
         finally:
             os.unlink(tmpname)
 
@@ -1964,7 +1703,7 @@
             displayer.show(changenode=n)
             if opts['patch']:
                 prev = (parents and parents[0]) or nullid
-                dodiff(ui, ui, other, prev, n)
+                patch.diff(other, prev, n, fp=repo.ui)
                 ui.write("\n")
     finally:
         if hasattr(other, 'close'):
@@ -2012,8 +1751,8 @@
     else:
         node = None
 
-    for src, abs, rel, exact in walk(repo, pats, opts, node=node,
-                                     head='(?:.*/|)'):
+    for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
+                                             head='(?:.*/|)'):
         if not node and repo.dirstate.state(abs) == '?':
             continue
         if opts['fullpath']:
@@ -2081,6 +1820,43 @@
         limit = sys.maxint
     count = 0
 
+    if opts['copies'] and opts['rev']:
+        endrev = max([int(i)
+                      for i in cmdutil.revrange(ui, repo, opts['rev'])]) + 1
+    else:
+        endrev = repo.changelog.count()
+    rcache = {}
+    ncache = {}
+    dcache = []
+    def getrenamed(fn, rev, man):
+        '''looks up all renames for a file (up to endrev) the first
+        time the file is given. It indexes on the changerev and only
+        parses the manifest if linkrev != changerev.
+        Returns rename info for fn at changerev rev.'''
+        if fn not in rcache:
+            rcache[fn] = {}
+            ncache[fn] = {}
+            fl = repo.file(fn)
+            for i in xrange(fl.count()):
+                node = fl.node(i)
+                lr = fl.linkrev(node)
+                renamed = fl.renamed(node)
+                rcache[fn][lr] = renamed
+                if renamed:
+                    ncache[fn][node] = renamed
+                if lr >= endrev:
+                    break
+        if rev in rcache[fn]:
+            return rcache[fn][rev]
+        mr = repo.manifest.rev(man)
+        if repo.manifest.parentrevs(mr) != (mr - 1, -1):
+            return ncache[fn].get(repo.manifest.find(man, fn)[0])
+        if not dcache or dcache[0] != man:
+            dcache[:] = [man, repo.manifest.readdelta(man)]
+        if fn in dcache[1]:
+            return ncache[fn].get(dcache[1][fn])
+        return None
+
     displayer = show_changeset(ui, repo, opts)
     for st, rev, fns in changeiter:
         if st == 'window':
@@ -2112,10 +1888,17 @@
             if opts['branches']:
                 br = repo.branchlookup([repo.changelog.node(rev)])
 
-            displayer.show(rev, brinfo=br)
+            copies = []
+            if opts.get('copies') and rev:
+                mf = getchange(rev)[0]
+                for fn in getchange(rev)[3]:
+                    rename = getrenamed(fn, rev, mf)
+                    if rename:
+                        copies.append((fn, rename[0]))
+            displayer.show(rev, brinfo=br, copies=copies)
             if opts['patch']:
                 prev = (parents and parents[0]) or nullid
-                dodiff(du, du, repo, prev, changenode, match=matchfn)
+                patch.diff(repo, prev, changenode, match=matchfn, fp=du)
                 du.write("\n\n")
         elif st == 'iter':
             if count == limit: break
@@ -2160,9 +1943,29 @@
     requested revision. Files that changed between either parent are
     marked as changed for the next commit and a commit must be
     performed before any further updates are allowed.
+
+    If no revision is specified, the working directory's parent is a
+    head revision, and the repository contains exactly one other head,
+    the other head is merged with by default.  Otherwise, an explicit
+    revision to merge with must be provided.
     """
 
-    node = _lookup(repo, node, branch)
+    if node or branch:
+        node = _lookup(repo, node, branch)
+    else:
+        heads = repo.heads()
+        if len(heads) > 2:
+            raise util.Abort(_('repo has %d heads - '
+                               'please merge with an explicit rev') %
+                             len(heads))
+        if len(heads) == 1:
+            raise util.Abort(_('there is nothing to merge - '
+                               'use "hg update" instead'))
+        parent = repo.dirstate.parents()[0]
+        if parent not in heads:
+            raise util.Abort(_('working dir not at a head rev - '
+                               'use "hg update" or merge with an explicit rev'))
+        node = parent == heads[0] and heads[-1] or heads[0]
     return hg.merge(repo, node, force=force)
 
 def outgoing(ui, repo, dest=None, **opts):
@@ -2196,7 +1999,7 @@
         displayer.show(changenode=n)
         if opts['patch']:
             prev = (parents and parents[0]) or nullid
-            dodiff(ui, ui, repo, prev, n)
+            patch.diff(repo, prev, n)
             ui.write("\n")
 
 def parents(ui, repo, file_=None, rev=None, branches=None, **opts):
@@ -2302,10 +2105,12 @@
     other = hg.repository(ui, source)
     ui.status(_('pulling from %s\n') % (source))
     revs = None
-    if opts['rev'] and not other.local():
-        raise util.Abort(_("pull -r doesn't work for remote repositories yet"))
-    elif opts['rev']:
-        revs = [other.lookup(rev) for rev in opts['rev']]
+    if opts['rev']:
+        if 'lookup' in other.capabilities:
+            revs = [other.lookup(rev) for rev in opts['rev']]
+        else:
+            error = _("Other repository doesn't support revision lookup, so a rev cannot be specified.")
+            raise util.Abort(error)
     modheads = repo.pull(other, heads=revs, force=opts['force'])
     return postincoming(ui, repo, modheads, opts['update'])
 
@@ -2409,12 +2214,12 @@
     names = []
     if not opts['after'] and not pats:
         raise util.Abort(_('no files specified'))
-    files, matchfn, anypats = matchpats(repo, pats, opts)
+    files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
     exact = dict.fromkeys(files)
-    mardu = map(dict.fromkeys, repo.changes(files=files, match=matchfn))
+    mardu = map(dict.fromkeys, repo.status(files=files, match=matchfn))[:5]
     modified, added, removed, deleted, unknown = mardu
     remove, forget = [], []
-    for src, abs, rel, exact in walk(repo, pats, opts):
+    for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
         reason = None
         if abs not in deleted and opts['after']:
             reason = _('is still present')
@@ -2479,8 +2284,8 @@
     Modified files are saved with a .orig suffix before reverting.
     To disable these backups, use --no-backup.
 
-    Using the -r option, revert the given files or directories to
-    their contents as of a specific revision.  This can be helpful to"roll
+    Using the -r option, revert the given files or directories to their
+    contents as of a specific revision. This can be helpful to "roll
     back" some or all of a change that should not have been committed.
 
     Revert modifies the working directory.  It does not commit any
@@ -2494,16 +2299,18 @@
 
     If names are given, all files matching the names are reverted.
 
-    If no arguments are given, all files in the repository are reverted.
+    If no arguments are given, no files are reverted.
     """
+
+    if not pats and not opts['all']:
+        raise util.Abort(_('no files or directories specified; '
+                           'use --all to revert the whole repo'))
+
     parent, p2 = repo.dirstate.parents()
-    if opts['rev']:
-        node = repo.lookup(opts['rev'])
-    elif p2 != nullid:
-        raise util.Abort(_('working dir has two parents; '
-                           'you must specify the revision to revert to'))
-    else:
-        node = parent
+    if not opts['rev'] and p2 != nullid:
+        raise util.Abort(_('uncommitted merge - please provide a '
+                           'specific revision'))
+    node = repo.changectx(opts['rev']).node()
     mf = repo.manifest.read(repo.changelog.read(node)[0])
     if node == parent:
         pmf = mf
@@ -2521,20 +2328,21 @@
 
     # walk dirstate.
 
-    for src, abs, rel, exact in walk(repo, pats, opts, badmatch=mf.has_key):
+    for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
+                                             badmatch=mf.has_key):
         names[abs] = (rel, exact)
         if src == 'b':
             target_only[abs] = True
 
     # walk target manifest.
 
-    for src, abs, rel, exact in walk(repo, pats, opts, node=node,
-                                     badmatch=names.has_key):
+    for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
+                                             badmatch=names.has_key):
         if abs in names: continue
         names[abs] = (rel, exact)
         target_only[abs] = True
 
-    changes = repo.changes(match=names.has_key, wlock=wlock)
+    changes = repo.status(match=names.has_key, wlock=wlock)[:5]
     modified, added, removed, deleted, unknown = map(dict.fromkeys, changes)
 
     revert = ([], _('reverting %s\n'))
@@ -2659,7 +2467,8 @@
 
     if opts["stdio"]:
         if repo is None:
-            raise hg.RepoError(_('no repo found'))
+            raise hg.RepoError(_("There is no Mercurial repository here"
+                                 " (.hg not found)"))
         s = sshserver.sshserver(ui, repo)
         s.serve_forever()
 
@@ -2667,10 +2476,11 @@
                " accesslog errorlog webdir_conf")
     for o in optlist.split():
         if opts[o]:
-            ui.setconfig("web", o, opts[o])
+            ui.setconfig("web", o, str(opts[o]))
 
     if repo is None and not ui.config("web", "webdir_conf"):
-        raise hg.RepoError(_('no repo found'))
+        raise hg.RepoError(_("There is no Mercurial repository here"
+                             " (.hg not found)"))
 
     if opts['daemon'] and not opts['daemon_pipefds']:
         rfd, wfd = os.pipe()
@@ -2685,7 +2495,7 @@
     try:
         httpd = hgweb.server.create_server(ui, repo)
     except socket.error, inst:
-        raise util.Abort(_('cannot start server: ') + inst.args[1])
+        raise util.Abort(_('cannot start server: %s') % inst.args[1])
 
     if ui.verbose:
         addr, port = httpd.socket.getsockname()
@@ -2740,8 +2550,8 @@
     """
 
     all = opts['all']
-    
-    files, matchfn, anypats = matchpats(repo, pats, opts)
+
+    files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
     cwd = (pats and repo.getcwd()) or ''
     modified, added, removed, deleted, unknown, ignored, clean = [
         [util.pathto(cwd, x) for x in n]
@@ -2770,9 +2580,10 @@
 
         for f in changes:
             ui.write(format % f)
-            if ((all or opts.get('copies')) and not opts.get('no_status')
-                and opt == 'added' and repo.dirstate.copies.has_key(f)):
-                ui.write('  %s%s' % (repo.dirstate.copies[f], end))
+            if ((all or opts.get('copies')) and not opts.get('no_status')):
+                copied = repo.dirstate.copied(f)
+                if copied:
+                    ui.write('  %s%s' % (copied, end))
 
 def tag(ui, repo, name, rev_=None, **opts):
     """add a tag for the current tip or a given revision
@@ -2800,18 +2611,16 @@
             raise util.Abort(_("use only one form to specify the revision"))
     if opts['rev']:
         rev_ = opts['rev']
-    if rev_:
-        r = hex(repo.lookup(rev_))
-    else:
-        p1, p2 = repo.dirstate.parents()
-        if p1 == nullid:
-            raise util.Abort(_('no revision to tag'))
-        if p2 != nullid:
-            raise util.Abort(_('outstanding uncommitted merges'))
-        r = hex(p1)
-
-    repo.tag(name, r, opts['local'], opts['message'], opts['user'],
-             opts['date'])
+    if not rev_ and repo.dirstate.parents()[1] != nullid:
+        raise util.Abort(_('uncommitted merge - please provide a '
+                           'specific revision'))
+    r = repo.changectx(rev_).node()
+
+    message = opts['message']
+    if not message:
+        message = _('Added tag %s for changeset %s') % (name, short(r))
+
+    repo.tag(name, r, message, opts['local'], opts['user'], opts['date'])
 
 def tags(ui, repo):
     """list repository tags
@@ -2823,9 +2632,10 @@
 
     l = repo.tagslist()
     l.reverse()
+    hexfunc = ui.debugflag and hex or short
     for t, n in l:
         try:
-            r = "%5d:%s" % (repo.changelog.rev(n), hex(n))
+            r = "%5d:%s" % (repo.changelog.rev(n), hexfunc(n))
         except KeyError:
             r = "    ?:?"
         if ui.quiet:
@@ -2844,7 +2654,7 @@
         br = repo.branchlookup([n])
     show_changeset(ui, repo, opts).show(changenode=n, brinfo=br)
     if opts['patch']:
-        dodiff(ui, ui, repo, repo.changelog.parents(n)[0], n)
+        patch.diff(repo, repo.changelog.parents(n)[0], n)
 
 def unbundle(ui, repo, fname, **opts):
     """apply a changegroup file
@@ -2931,7 +2741,7 @@
             repo.ui.warn(_("Using head %s for branch %s\n")
                          % (short(node), branch))
         else:
-            raise util.Abort(_("branch %s not found\n") % (branch))
+            raise util.Abort(_("branch %s not found") % branch)
     else:
         node = node and repo.lookup(node) or repo.changelog.tip()
     return node
@@ -2950,29 +2760,59 @@
 
 # Command options and aliases are listed here, alphabetically
 
+globalopts = [
+    ('R', 'repository', '',
+     _('repository root directory or symbolic path name')),
+    ('', 'cwd', '', _('change working directory')),
+    ('y', 'noninteractive', None,
+     _('do not prompt, assume \'yes\' for any required answers')),
+    ('q', 'quiet', None, _('suppress output')),
+    ('v', 'verbose', None, _('enable additional output')),
+    ('', 'config', [], _('set/override config option')),
+    ('', 'debug', None, _('enable debugging output')),
+    ('', 'debugger', None, _('start debugger')),
+    ('', 'lsprof', None, _('print improved command execution profile')),
+    ('', 'traceback', None, _('print traceback on exception')),
+    ('', 'time', None, _('time how long the command takes')),
+    ('', 'profile', None, _('print command execution profile')),
+    ('', 'version', None, _('output version information and exit')),
+    ('h', 'help', None, _('display help and exit')),
+]
+
+dryrunopts = [('n', 'dry-run', None,
+               _('do not perform actions, just print output'))]
+
+remoteopts = [
+    ('e', 'ssh', '', _('specify ssh command to use')),
+    ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
+]
+
+walkopts = [
+    ('I', 'include', [], _('include names matching the given patterns')),
+    ('X', 'exclude', [], _('exclude names matching the given patterns')),
+]
+
 table = {
     "^add":
         (add,
-         [('I', 'include', [], _('include names matching the given patterns')),
-          ('X', 'exclude', [], _('exclude names matching the given patterns')),
-          ('n', 'dry-run', None, _('do not perform actions, just print output'))],
+         walkopts + dryrunopts,
          _('hg add [OPTION]... [FILE]...')),
-    "debugaddremove|addremove":
+    "addremove":
         (addremove,
-         [('I', 'include', [], _('include names matching the given patterns')),
-          ('X', 'exclude', [], _('exclude names matching the given patterns')),
-          ('n', 'dry-run', None, _('do not perform actions, just print output'))],
+         [('s', 'similarity', '',
+           _('guess renamed files by similarity (0<=s<=100)')),
+         ] + walkopts + dryrunopts,
          _('hg addremove [OPTION]... [FILE]...')),
     "^annotate":
         (annotate,
          [('r', 'rev', '', _('annotate the specified revision')),
+          ('f', 'follow', None, _('follow file copies and renames')),
           ('a', 'text', None, _('treat all files as text')),
           ('u', 'user', None, _('list the author')),
           ('d', 'date', None, _('list the date')),
           ('n', 'number', None, _('list the revision number (default)')),
           ('c', 'changeset', None, _('list the changeset')),
-          ('I', 'include', [], _('include names matching the given patterns')),
-          ('X', 'exclude', [], _('exclude names matching the given patterns'))],
+         ] + walkopts,
          _('hg annotate [-r REV] [-a] [-u] [-d] [-n] [-c] FILE...')),
     "archive":
         (archive,
@@ -2980,8 +2820,7 @@
           ('p', 'prefix', '', _('directory prefix for files in archive')),
           ('r', 'rev', '', _('revision to distribute')),
           ('t', 'type', '', _('type of distribution to create')),
-          ('I', 'include', [], _('include names matching the given patterns')),
-          ('X', 'exclude', [], _('exclude names matching the given patterns'))],
+         ] + walkopts,
          _('hg archive [OPTION]... DEST')),
     "backout":
         (backout,
@@ -2992,20 +2831,23 @@
           ('d', 'date', '', _('record datecode as commit date')),
           ('', 'parent', '', _('parent to choose when backing out merge')),
           ('u', 'user', '', _('record user as committer')),
-          ('I', 'include', [], _('include names matching the given patterns')),
-          ('X', 'exclude', [], _('exclude names matching the given patterns'))],
+         ] + walkopts,
          _('hg backout [OPTION]... REV')),
     "bundle":
         (bundle,
          [('f', 'force', None,
-           _('run even when remote repository is unrelated'))],
-         _('hg bundle FILE DEST')),
+           _('run even when remote repository is unrelated')),
+          ('r', 'rev', [],
+           _('a changeset you would like to bundle')),
+          ('', 'base', [],
+           _('a base changeset to specify instead of a destination')),
+         ] + remoteopts,
+         _('hg bundle [--base REV]... [--rev REV]... FILE [DEST]')),
     "cat":
         (cat,
          [('o', 'output', '', _('print output to file with formatted name')),
           ('r', 'rev', '', _('print the given revision')),
-          ('I', 'include', [], _('include names matching the given patterns')),
-          ('X', 'exclude', [], _('exclude names matching the given patterns'))],
+         ] + walkopts,
          _('hg cat [OPTION]... FILE...')),
     "^clone":
         (clone,
@@ -3015,9 +2857,7 @@
           ('', 'pull', None, _('use pull protocol to copy metadata')),
           ('', 'uncompressed', None,
            _('use uncompressed transfer (fast over LAN)')),
-          ('e', 'ssh', '', _('specify ssh command to use')),
-          ('', 'remotecmd', '',
-           _('specify hg command to run on the remote side'))],
+         ] + remoteopts,
          _('hg clone [OPTION]... SOURCE [DEST]')),
     "^commit|ci":
         (commit,
@@ -3027,17 +2867,14 @@
           ('l', 'logfile', '', _('read the commit message from <file>')),
           ('d', 'date', '', _('record datecode as commit date')),
           ('u', 'user', '', _('record user as commiter')),
-          ('I', 'include', [], _('include names matching the given patterns')),
-          ('X', 'exclude', [], _('exclude names matching the given patterns'))],
+         ] + walkopts,
          _('hg commit [OPTION]... [FILE]...')),
     "copy|cp":
         (copy,
          [('A', 'after', None, _('record a copy that has already occurred')),
           ('f', 'force', None,
            _('forcibly copy over an existing managed file')),
-          ('I', 'include', [], _('include names matching the given patterns')),
-          ('X', 'exclude', [], _('exclude names matching the given patterns')),
-          ('n', 'dry-run', None, _('do not perform actions, just print output'))],
+         ] + walkopts + dryrunopts,
          _('hg copy [OPTION]... [SOURCE]... DEST')),
     "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
     "debugcomplete":
@@ -3049,7 +2886,6 @@
          [('r', 'rev', '', _('revision to rebuild to'))],
          _('debugrebuildstate [-r REV] [REV]')),
     "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
-    "debugconfig": (debugconfig, [], _('debugconfig [NAME]...')),
     "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
     "debugstate": (debugstate, [], _('debugstate')),
     "debugdata": (debugdata, [], _('debugdata FILE REV')),
@@ -3057,48 +2893,46 @@
     "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')),
     "debugrename": (debugrename, [], _('debugrename FILE [REV]')),
     "debugwalk":
-        (debugwalk,
-         [('I', 'include', [], _('include names matching the given patterns')),
-          ('X', 'exclude', [], _('exclude names matching the given patterns'))],
-         _('debugwalk [OPTION]... [FILE]...')),
+        (debugwalk, walkopts, _('debugwalk [OPTION]... [FILE]...')),
     "^diff":
         (diff,
          [('r', 'rev', [], _('revision')),
           ('a', 'text', None, _('treat all files as text')),
           ('p', 'show-function', None,
            _('show which function each change is in')),
+          ('g', 'git', None, _('use git extended diff format')),
+          ('', 'nodates', None, _("don't include dates in diff headers")),
           ('w', 'ignore-all-space', None,
            _('ignore white space when comparing lines')),
           ('b', 'ignore-space-change', None,
            _('ignore changes in the amount of white space')),
           ('B', 'ignore-blank-lines', None,
            _('ignore changes whose lines are all blank')),
-          ('I', 'include', [], _('include names matching the given patterns')),
-          ('X', 'exclude', [], _('exclude names matching the given patterns'))],
+         ] + walkopts,
          _('hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...')),
     "^export":
         (export,
          [('o', 'output', '', _('print output to file with formatted name')),
           ('a', 'text', None, _('treat all files as text')),
+          ('g', 'git', None, _('use git extended diff format')),
+          ('', 'nodates', None, _("don't include dates in diff headers")),
           ('', 'switch-parent', None, _('diff against the second parent'))],
          _('hg export [-a] [-o OUTFILESPEC] REV...')),
     "debugforget|forget":
-        (forget,
-         [('I', 'include', [], _('include names matching the given patterns')),
-          ('X', 'exclude', [], _('exclude names matching the given patterns'))],
-         _('hg forget [OPTION]... FILE...')),
+        (forget, walkopts, _('hg forget [OPTION]... FILE...')),
     "grep":
         (grep,
          [('0', 'print0', None, _('end fields with NUL')),
           ('', 'all', None, _('print all revisions that match')),
+          ('f', 'follow', None,
+           _('follow changeset history, or file history across copies and renames')),
           ('i', 'ignore-case', None, _('ignore case when matching')),
           ('l', 'files-with-matches', None,
            _('print only filenames and revs that match')),
           ('n', 'line-number', None, _('print matching line numbers')),
           ('r', 'rev', [], _('search in given revision range')),
           ('u', 'user', None, _('print user who committed change')),
-          ('I', 'include', [], _('include names matching the given patterns')),
-          ('X', 'exclude', [], _('exclude names matching the given patterns'))],
+         ] + walkopts,
          _('hg grep [OPTION]... PATTERN [FILE]...')),
     "heads":
         (heads,
@@ -3127,19 +2961,13 @@
           ('n', 'newest-first', None, _('show newest record first')),
           ('', 'bundle', '', _('file to store the bundles into')),
           ('p', 'patch', None, _('show patch')),
-          ('r', 'rev', [], _('a specific revision you would like to pull')),
+          ('r', 'rev', [], _('a specific revision up to which you would like to pull')),
           ('', 'template', '', _('display with template')),
-          ('e', 'ssh', '', _('specify ssh command to use')),
-          ('', 'remotecmd', '',
-           _('specify hg command to run on the remote side'))],
+         ] + remoteopts,
          _('hg incoming [-p] [-n] [-M] [-r REV]...'
            ' [--bundle FILENAME] [SOURCE]')),
     "^init":
-        (init,
-         [('e', 'ssh', '', _('specify ssh command to use')),
-          ('', 'remotecmd', '',
-           _('specify hg command to run on the remote side'))],
-         _('hg init [-e FILE] [--remotecmd FILE] [DEST]')),
+        (init, remoteopts, _('hg init [-e FILE] [--remotecmd FILE] [DEST]')),
     "locate":
         (locate,
          [('r', 'rev', '', _('search the repository as it stood at rev')),
@@ -3147,8 +2975,7 @@
            _('end filenames with NUL, for use with xargs')),
           ('f', 'fullpath', None,
            _('print complete paths from the filesystem root')),
-          ('I', 'include', [], _('include names matching the given patterns')),
-          ('X', 'exclude', [], _('exclude names matching the given patterns'))],
+         ] + walkopts,
          _('hg locate [OPTION]... [PATTERN]...')),
     "^log|history":
         (log,
@@ -3157,6 +2984,7 @@
            _('follow changeset history, or file history across copies and renames')),
           ('', 'follow-first', None,
            _('only follow the first parent of merge changesets')),
+          ('C', 'copies', None, _('show copied files')),
           ('k', 'keyword', [], _('search for a keyword')),
           ('l', 'limit', '', _('limit number of changes displayed')),
           ('r', 'rev', [], _('show the specified revision or range')),
@@ -3164,9 +2992,9 @@
           ('', 'style', '', _('display using template map file')),
           ('m', 'only-merges', None, _('show only merges')),
           ('p', 'patch', None, _('show patch')),
+          ('P', 'prune', [], _('do not display revision or any of its ancestors')),
           ('', 'template', '', _('display with template')),
-          ('I', 'include', [], _('include names matching the given patterns')),
-          ('X', 'exclude', [], _('exclude names matching the given patterns'))],
+         ] + walkopts,
          _('hg log [OPTION]... [FILE]')),
     "manifest": (manifest, [], _('hg manifest [REV]')),
     "merge":
@@ -3183,9 +3011,7 @@
           ('r', 'rev', [], _('a specific revision you would like to push')),
           ('n', 'newest-first', None, _('show newest record first')),
           ('', 'template', '', _('display with template')),
-          ('e', 'ssh', '', _('specify ssh command to use')),
-          ('', 'remotecmd', '',
-           _('specify hg command to run on the remote side'))],
+         ] + remoteopts,
          _('hg outgoing [-M] [-p] [-n] [-r REV]... [DEST]')),
     "^parents":
         (parents,
@@ -3198,21 +3024,17 @@
     "^pull":
         (pull,
          [('u', 'update', None,
-           _('update the working directory to tip after pull')),
-          ('e', 'ssh', '', _('specify ssh command to use')),
+           _('update to new tip if changesets were pulled')),
           ('f', 'force', None,
            _('run even when remote repository is unrelated')),
-          ('r', 'rev', [], _('a specific revision you would like to pull')),
-          ('', 'remotecmd', '',
-           _('specify hg command to run on the remote side'))],
+          ('r', 'rev', [], _('a specific revision up to which you would like to pull')),
+         ] + remoteopts,
          _('hg pull [-u] [-r REV]... [-e FILE] [--remotecmd FILE] [SOURCE]')),
     "^push":
         (push,
          [('f', 'force', None, _('force push')),
-          ('e', 'ssh', '', _('specify ssh command to use')),
           ('r', 'rev', [], _('a specific revision you would like to push')),
-          ('', 'remotecmd', '',
-           _('specify hg command to run on the remote side'))],
+         ] + remoteopts,
          _('hg push [-f] [-r REV]... [-e FILE] [--remotecmd FILE] [DEST]')),
     "debugrawcommit|rawcommit":
         (rawcommit,
@@ -3228,28 +3050,25 @@
         (remove,
          [('A', 'after', None, _('record remove that has already occurred')),
           ('f', 'force', None, _('remove file even if modified')),
-          ('I', 'include', [], _('include names matching the given patterns')),
-          ('X', 'exclude', [], _('exclude names matching the given patterns'))],
+         ] + walkopts,
          _('hg remove [OPTION]... FILE...')),
     "rename|mv":
         (rename,
          [('A', 'after', None, _('record a rename that has already occurred')),
           ('f', 'force', None,
            _('forcibly copy over an existing managed file')),
-          ('I', 'include', [], _('include names matching the given patterns')),
-          ('X', 'exclude', [], _('exclude names matching the given patterns')),
-          ('n', 'dry-run', None, _('do not perform actions, just print output'))],
+         ] + walkopts + dryrunopts,
          _('hg rename [OPTION]... SOURCE... DEST')),
     "^revert":
         (revert,
-         [('r', 'rev', '', _('revision to revert to')),
+         [('a', 'all', None, _('revert all changes when no arguments given')),
+          ('r', 'rev', '', _('revision to revert to')),
           ('', 'no-backup', None, _('do not save backup copies of files')),
-          ('I', 'include', [], _('include names matching given patterns')),
-          ('X', 'exclude', [], _('exclude names matching given patterns')),
-          ('n', 'dry-run', None, _('do not perform actions, just print output'))],
+         ] + walkopts + dryrunopts,
          _('hg revert [-r REV] [NAME]...')),
     "rollback": (rollback, [], _('hg rollback')),
     "root": (root, [], _('hg root')),
+    "showconfig|debugconfig": (showconfig, [], _('showconfig [NAME]...')),
     "^serve":
         (serve,
          [('A', 'accesslog', '', _('name of access log file to write to')),
@@ -3282,8 +3101,7 @@
           ('C', 'copies', None, _('show source of copied files')),
           ('0', 'print0', None,
            _('end filenames with NUL, for use with xargs')),
-          ('I', 'include', [], _('include names matching the given patterns')),
-          ('X', 'exclude', [], _('exclude names matching the given patterns'))],
+         ] + walkopts,
          _('hg status [OPTION]... [FILE]...')),
     "tag":
         (tag,
@@ -3304,7 +3122,7 @@
     "unbundle":
         (unbundle,
          [('u', 'update', None,
-           _('update the working directory to tip after unbundle'))],
+           _('update to new tip if changesets were unbundled'))],
          _('hg unbundle [-u] FILE')),
     "debugundo|undo": (undo, [], _('hg undo')),
     "^update|up|checkout|co":
@@ -3318,30 +3136,11 @@
     "version": (show_version, [], _('hg version')),
 }
 
-globalopts = [
-    ('R', 'repository', '',
-     _('repository root directory or symbolic path name')),
-    ('', 'cwd', '', _('change working directory')),
-    ('y', 'noninteractive', None,
-     _('do not prompt, assume \'yes\' for any required answers')),
-    ('q', 'quiet', None, _('suppress output')),
-    ('v', 'verbose', None, _('enable additional output')),
-    ('', 'config', [], _('set/override config option')),
-    ('', 'debug', None, _('enable debugging output')),
-    ('', 'debugger', None, _('start debugger')),
-    ('', 'lsprof', None, _('print improved command execution profile')),
-    ('', 'traceback', None, _('print traceback on exception')),
-    ('', 'time', None, _('time how long the command takes')),
-    ('', 'profile', None, _('print command execution profile')),
-    ('', 'version', None, _('output version information and exit')),
-    ('h', 'help', None, _('display help and exit')),
-]
-
 norepo = ("clone init version help debugancestor debugcomplete debugdata"
           " debugindex debugindexdot")
-optionalrepo = ("paths serve debugconfig")
-
-def findpossible(cmd):
+optionalrepo = ("paths serve showconfig")
+
+def findpossible(ui, cmd):
     """
     Return cmd -> (aliases, command table entry)
     for each matching command.
@@ -3354,13 +3153,13 @@
         found = None
         if cmd in aliases:
             found = cmd
-        else:
+        elif not ui.config("ui", "strict"):
             for a in aliases:
                 if a.startswith(cmd):
                     found = a
                     break
         if found is not None:
-            if aliases[0].startswith("debug"):
+            if aliases[0].startswith("debug") or found.startswith("debug"):
                 debugchoice[found] = (aliases, table[e])
             else:
                 choice[found] = (aliases, table[e])
@@ -3370,9 +3169,9 @@
 
     return choice
 
-def findcmd(cmd):
+def findcmd(ui, cmd):
     """Return (aliases, command table entry) for command string."""
-    choice = findpossible(cmd)
+    choice = findpossible(ui, cmd)
 
     if choice.has_key(cmd):
         return choice[cmd]
@@ -3407,11 +3206,11 @@
 
     if args:
         cmd, args = args[0], args[1:]
-        aliases, i = findcmd(cmd)
+        aliases, i = findcmd(ui, cmd)
         cmd = aliases[0]
         defaults = ui.config("defaults", cmd)
         if defaults:
-            args = defaults.split() + args
+            args = shlex.split(defaults) + args
         c = list(i[1])
     else:
         cmd = None
@@ -3446,18 +3245,11 @@
                 return sys.modules[v]
         raise KeyError(name)
 
-def dispatch(args):
-    for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
-        num = getattr(signal, name, None)
-        if num: signal.signal(num, catchterm)
-
-    try:
-        u = ui.ui(traceback='--traceback' in sys.argv[1:])
-    except util.Abort, inst:
-        sys.stderr.write(_("abort: %s\n") % inst)
-        return -1
-
-    for ext_name, load_from_name in u.extensions():
+def load_extensions(ui):
+    added = []
+    for ext_name, load_from_name in ui.extensions():
+        if ext_name in external:
+            continue
         try:
             if load_from_name:
                 # the module will be loaded in sys.modules
@@ -3477,24 +3269,53 @@
                 except ImportError:
                     mod = importh(ext_name)
             external[ext_name] = mod.__name__
+            added.append((mod, ext_name))
         except (util.SignalInterrupt, KeyboardInterrupt):
             raise
         except Exception, inst:
-            u.warn(_("*** failed to import extension %s: %s\n") % (ext_name, inst))
-            if u.print_exc():
+            ui.warn(_("*** failed to import extension %s: %s\n") %
+                    (ext_name, inst))
+            if ui.print_exc():
                 return 1
 
-    for name in external.itervalues():
-        mod = sys.modules[name]
+    for mod, name in added:
         uisetup = getattr(mod, 'uisetup', None)
         if uisetup:
-            uisetup(u)
+            uisetup(ui)
         cmdtable = getattr(mod, 'cmdtable', {})
         for t in cmdtable:
             if t in table:
-                u.warn(_("module %s overrides %s\n") % (name, t))
+                ui.warn(_("module %s overrides %s\n") % (name, t))
         table.update(cmdtable)
 
+def parseconfig(config):
+    """parse the --config options from the command line"""
+    parsed = []
+    for cfg in config:
+        try:
+            name, value = cfg.split('=', 1)
+            section, name = name.split('.', 1)
+            if not section or not name:
+                raise IndexError
+            parsed.append((section, name, value))
+        except (IndexError, ValueError):
+            raise util.Abort(_('malformed --config option: %s') % cfg)
+    return parsed
+
+def dispatch(args):
+    for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
+        num = getattr(signal, name, None)
+        if num: signal.signal(num, catchterm)
+
+    try:
+        u = ui.ui(traceback='--traceback' in sys.argv[1:])
+    except util.Abort, inst:
+        sys.stderr.write(_("abort: %s\n") % inst)
+        return -1
+
+    load_extensions(u)
+    u.addreadhook(load_extensions)
+
     try:
         cmd, func, args, options, cmdoptions = parse(u, args)
         if options["time"]:
@@ -3510,10 +3331,6 @@
                     (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
             atexit.register(print_time)
 
-        u.updateopts(options["verbose"], options["debug"], options["quiet"],
-                     not options["noninteractive"], options["traceback"],
-                     options["config"])
-
         # enter the debugger before command execution
         if options['debugger']:
             pdb.set_trace()
@@ -3526,8 +3343,14 @@
                     raise util.Abort('%s: %s' %
                                      (options['cwd'], inst.strerror))
 
+            u.updateopts(options["verbose"], options["debug"], options["quiet"],
+                         not options["noninteractive"], options["traceback"],
+                         parseconfig(options["config"]))
+
             path = u.expandpath(options["repository"]) or ""
             repo = path and hg.repository(u, path=path) or None
+            if repo and not repo.local():
+                raise util.Abort(_("repository '%s' is not local") % path)
 
             if options['help']:
                 return help_(u, cmd, options['version'])
@@ -3545,6 +3368,7 @@
                         mod = sys.modules[name]
                         if hasattr(mod, 'reposetup'):
                             mod.reposetup(u, repo)
+                            hg.repo_setup_hooks.append(mod.reposetup)
                 except hg.RepoError:
                     if cmd not in optionalrepo.split():
                         raise
@@ -3552,11 +3376,6 @@
             else:
                 d = lambda: func(u, *args, **cmdoptions)
 
-            # reupdate the options, repo/.hg/hgrc may have changed them
-            u.updateopts(options["verbose"], options["debug"], options["quiet"],
-                         not options["noninteractive"], options["traceback"],
-                         options["config"])
-
             try:
                 if options['profile']:
                     import hotshot, hotshot.stats
@@ -3628,7 +3447,7 @@
         u.warn(_("abort: could not lock %s: %s\n") %
                (inst.desc or inst.filename, inst.strerror))
     except revlog.RevlogError, inst:
-        u.warn(_("abort: "), inst, "!\n")
+        u.warn(_("abort: %s!\n") % inst)
     except util.SignalInterrupt:
         u.warn(_("killed!\n"))
     except KeyboardInterrupt:
@@ -3650,18 +3469,18 @@
                 u.warn(_("broken pipe\n"))
         elif getattr(inst, "strerror", None):
             if getattr(inst, "filename", None):
-                u.warn(_("abort: %s - %s\n") % (inst.strerror, inst.filename))
+                u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
             else:
                 u.warn(_("abort: %s\n") % inst.strerror)
         else:
             raise
     except OSError, inst:
-        if hasattr(inst, "filename"):
+        if getattr(inst, "filename", None):
             u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
         else:
             u.warn(_("abort: %s\n") % inst.strerror)
     except util.Abort, inst:
-        u.warn(_('abort: '), inst.args[0] % inst.args[1:], '\n')
+        u.warn(_("abort: %s\n") % inst)
     except TypeError, inst:
         # was this an argument error?
         tb = traceback.extract_tb(sys.exc_info()[2])
--- a/mercurial/context.py	Sat Sep 02 22:58:02 2006 -0400
+++ b/mercurial/context.py	Sat Oct 21 15:22:08 2006 -0400
@@ -1,40 +1,70 @@
 # context.py - changeset and file context objects for mercurial
 #
-# Copyright 2005 Matt Mackall <mpm@selenic.com>
+# Copyright 2006 Matt Mackall <mpm@selenic.com>
 #
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
+from node import *
+from i18n import gettext as _
+from demandload import demandload
+demandload(globals(), "ancestor bdiff repo revlog util os")
+
 class changectx(object):
     """A changecontext object makes access to data related to a particular
     changeset convenient."""
-    def __init__(self, repo, changeid):
+    def __init__(self, repo, changeid=None):
         """changeid is a revision number, node, or tag"""
         self._repo = repo
 
+        if not changeid and changeid != 0:
+            p1, p2 = self._repo.dirstate.parents()
+            self._rev = self._repo.changelog.rev(p1)
+            if self._rev == -1:
+                changeid = 'tip'
+            else:
+                self._node = p1
+                return
+
         self._node = self._repo.lookup(changeid)
         self._rev = self._repo.changelog.rev(self._node)
 
-    def changeset(self):
-        try:
-            return self._changeset
-        except AttributeError:
+    def __str__(self):
+        return short(self.node())
+
+    def __repr__(self):
+        return "<changectx %s>" % str(self)
+
+    def __eq__(self, other):
+        return self._rev == other._rev
+
+    def __nonzero__(self):
+        return self._rev != -1
+
+    def __getattr__(self, name):
+        if name == '_changeset':
             self._changeset = self._repo.changelog.read(self.node())
             return self._changeset
-
-    def manifest(self):
-        try:
+        elif name == '_manifest':
+            self._manifest = self._repo.manifest.read(self._changeset[0])
             return self._manifest
-        except AttributeError:
-            self._manifest = self._repo.manifest.read(self.changeset()[0])
-            return self._manifest
+        elif name == '_manifestdelta':
+            md = self._repo.manifest.readdelta(self._changeset[0])
+            self._manifestdelta = md
+            return self._manifestdelta
+        else:
+            raise AttributeError, name
+
+    def changeset(self): return self._changeset
+    def manifest(self): return self._manifest
 
     def rev(self): return self._rev
     def node(self): return self._node
-    def user(self): return self.changeset()[1]
-    def date(self): return self.changeset()[2]
-    def changedfiles(self): return self.changeset()[3]
-    def description(self): return self.changeset()[4]
+    def user(self): return self._changeset[1]
+    def date(self): return self._changeset[2]
+    def files(self): return self._changeset[3]
+    def description(self): return self._changeset[4]
+    def branch(self): return self._changeset[5].get("branch", "")
 
     def parents(self):
         """return contexts for each parent changeset"""
@@ -47,14 +77,25 @@
         return [ changectx(self._repo, x) for x in c ]
 
     def filenode(self, path):
-        node, flag = self._repo.manifest.find(self.changeset()[0], path)
+        if '_manifest' in self.__dict__:
+            try:
+                return self._manifest[path]
+            except KeyError:
+                raise repo.LookupError(_("'%s' not found in manifest") % path)
+        if '_manifestdelta' in self.__dict__ or path in self.files():
+            if path in self._manifestdelta:
+                return self._manifestdelta[path]
+        node, flag = self._repo.manifest.find(self._changeset[0], path)
+        if not node:
+            raise repo.LookupError(_("'%s' not found in manifest") % path)
+
         return node
 
     def filectx(self, path, fileid=None):
         """get a file context from this changeset"""
         if fileid is None:
             fileid = self.filenode(path)
-        return filectx(self._repo, path, fileid=fileid)
+        return filectx(self._repo, path, fileid=fileid, changectx=self)
 
     def filectxs(self):
         """generate a file context for each file in this changeset's
@@ -65,62 +106,389 @@
         for f in m:
             yield self.filectx(f, fileid=mf[f])
 
+    def ancestor(self, c2):
+        """
+        return the ancestor context of self and c2
+        """
+        n = self._repo.changelog.ancestor(self._node, c2._node)
+        return changectx(self._repo, n)
+
 class filectx(object):
     """A filecontext object makes access to data related to a particular
        filerevision convenient."""
-    def __init__(self, repo, path, changeid=None, fileid=None):
+    def __init__(self, repo, path, changeid=None, fileid=None,
+                 filelog=None, changectx=None):
         """changeid can be a changeset revision, node, or tag.
            fileid can be a file revision or node."""
         self._repo = repo
         self._path = path
 
-        assert changeid or fileid
+        assert changeid is not None or fileid is not None
 
-        if not fileid:
-            # if given a changeset id, go ahead and look up the file
+        if filelog:
+            self._filelog = filelog
+        if changectx:
+            self._changectx = changectx
+            self._changeid = changectx.node()
+
+        if fileid is None:
             self._changeid = changeid
-            self._changectx = self.changectx()
-            self._filelog = self._repo.file(self._path)
-            self._filenode = self._changectx.filenode(self._path)
         else:
-            # else be lazy
-            self._filelog = self._repo.file(self._path)
-            self._filenode = self._filelog.lookup(fileid)
-            self._changeid = self._filelog.linkrev(self._filenode)
-        self._filerev = self._filelog.rev(self._filenode)
+            self._fileid = fileid
 
-    def changectx(self):
-        try:
-            return self._changectx
-        except AttributeError:
+    def __getattr__(self, name):
+        if name == '_changectx':
             self._changectx = changectx(self._repo, self._changeid)
             return self._changectx
+        elif name == '_filelog':
+            self._filelog = self._repo.file(self._path)
+            return self._filelog
+        elif name == '_changeid':
+            self._changeid = self._filelog.linkrev(self._filenode)
+            return self._changeid
+        elif name == '_filenode':
+            try:
+                if '_fileid' in self.__dict__:
+                    self._filenode = self._filelog.lookup(self._fileid)
+                else:
+                    self._filenode = self._changectx.filenode(self._path)
+            except revlog.RevlogError, inst:
+                raise repo.LookupError(str(inst))
+            return self._filenode
+        elif name == '_filerev':
+            self._filerev = self._filelog.rev(self._filenode)
+            return self._filerev
+        else:
+            raise AttributeError, name
+
+    def __nonzero__(self):
+        return self._filerev != nullid
+
+    def __str__(self):
+        return "%s@%s" % (self.path(), short(self.node()))
+
+    def __repr__(self):
+        return "<filectx %s>" % str(self)
+
+    def __eq__(self, other):
+        return self._path == other._path and self._changeid == other._changeid
+
+    def filectx(self, fileid):
+        '''opens an arbitrary revision of the file without
+        opening a new filelog'''
+        return filectx(self._repo, self._path, fileid=fileid,
+                       filelog=self._filelog)
 
     def filerev(self): return self._filerev
     def filenode(self): return self._filenode
     def filelog(self): return self._filelog
 
-    def rev(self): return self.changectx().rev()
-    def node(self): return self.changectx().node()
-    def user(self): return self.changectx().user()
-    def date(self): return self.changectx().date()
-    def files(self): return self.changectx().files()
-    def description(self): return self.changectx().description()
-    def manifest(self): return self.changectx().manifest()
+    def rev(self):
+        if '_changectx' in self.__dict__:
+            return self._changectx.rev()
+        return self._filelog.linkrev(self._filenode)
+
+    def node(self): return self._changectx.node()
+    def user(self): return self._changectx.user()
+    def date(self): return self._changectx.date()
+    def files(self): return self._changectx.files()
+    def description(self): return self._changectx.description()
+    def branch(self): return self._changectx.branch()
+    def manifest(self): return self._changectx.manifest()
+    def changectx(self): return self._changectx
 
     def data(self): return self._filelog.read(self._filenode)
-    def metadata(self): return self._filelog.readmeta(self._filenode)
     def renamed(self): return self._filelog.renamed(self._filenode)
+    def path(self): return self._path
+    def size(self): return self._filelog.size(self._filerev)
+
+    def cmp(self, text): return self._filelog.cmp(self._filenode, text)
 
     def parents(self):
-        # need to fix for renames
-        p = self._filelog.parents(self._filenode)
-        return [ filectx(self._repo, self._path, fileid=x) for x in p ]
+        p = self._path
+        fl = self._filelog
+        pl = [ (p, n, fl) for n in self._filelog.parents(self._filenode) ]
+
+        r = self.renamed()
+        if r:
+            pl[0] = (r[0], r[1], None)
+
+        return [ filectx(self._repo, p, fileid=n, filelog=l)
+                 for p,n,l in pl if n != nullid ]
 
     def children(self):
         # hard for renames
         c = self._filelog.children(self._filenode)
-        return [ filectx(self._repo, self._path, fileid=x) for x in c ]
+        return [ filectx(self._repo, self._path, fileid=x,
+                         filelog=self._filelog) for x in c ]
+
+    def annotate(self, follow=False):
+        '''returns a list of tuples of (ctx, line) for each line
+        in the file, where ctx is the filectx of the node where
+        that line was last changed'''
+
+        def decorate(text, rev):
+            return ([rev] * len(text.splitlines()), text)
+
+        def pair(parent, child):
+            for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
+                child[0][b1:b2] = parent[0][a1:a2]
+            return child
+
+        getlog = util.cachefunc(lambda x: self._repo.file(x))
+        def getctx(path, fileid):
+            log = path == self._path and self._filelog or getlog(path)
+            return filectx(self._repo, path, fileid=fileid, filelog=log)
+        getctx = util.cachefunc(getctx)
+
+        def parents(f):
+            # we want to reuse filectx objects as much as possible
+            p = f._path
+            if f._filerev is None: # working dir
+                pl = [ (n.path(), n.filerev()) for n in f.parents() ]
+            else:
+                pl = [ (p, n) for n in f._filelog.parentrevs(f._filerev) ]
+
+            if follow:
+                r = f.renamed()
+                if r:
+                    pl[0] = (r[0], getlog(r[0]).rev(r[1]))
+
+            return [ getctx(p, n) for p, n in pl if n != -1 ]
+
+        # use linkrev to find the first changeset where self appeared
+        if self.rev() != self._filelog.linkrev(self._filenode):
+            base = self.filectx(self.filerev())
+        else:
+            base = self
+
+        # find all ancestors
+        needed = {base: 1}
+        visit = [base]
+        files = [base._path]
+        while visit:
+            f = visit.pop(0)
+            for p in parents(f):
+                if p not in needed:
+                    needed[p] = 1
+                    visit.append(p)
+                    if p._path not in files:
+                        files.append(p._path)
+                else:
+                    # count how many times we'll use this
+                    needed[p] += 1
+
+        # sort by revision (per file) which is a topological order
+        visit = []
+        files.reverse()
+        for f in files:
+            fn = [(n._filerev, n) for n in needed.keys() if n._path == f]
+            fn.sort()
+            visit.extend(fn)
+        hist = {}
+
+        for r, f in visit:
+            curr = decorate(f.data(), f)
+            for p in parents(f):
+                if p != nullid:
+                    curr = pair(hist[p], curr)
+                    # trim the history of unneeded revs
+                    needed[p] -= 1
+                    if not needed[p]:
+                        del hist[p]
+            hist[f] = curr
+
+        return zip(hist[f][0], hist[f][1].splitlines(1))
+
+    def ancestor(self, fc2):
+        """
+        find the common ancestor file context, if any, of self, and fc2
+        """
+
+        acache = {}
+
+        # prime the ancestor cache for the working directory
+        for c in (self, fc2):
+            if c._filerev == None:
+                pl = [ (n.path(), n.filenode()) for n in c.parents() ]
+                acache[(c._path, None)] = pl
+
+        flcache = {self._path:self._filelog, fc2._path:fc2._filelog}
+        def parents(vertex):
+            if vertex in acache:
+                return acache[vertex]
+            f, n = vertex
+            if f not in flcache:
+                flcache[f] = self._repo.file(f)
+            fl = flcache[f]
+            pl = [ (f,p) for p in fl.parents(n) if p != nullid ]
+            re = fl.renamed(n)
+            if re:
+                pl.append(re)
+            acache[vertex]=pl
+            return pl
+
+        a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
+        v = ancestor.ancestor(a, b, parents)
+        if v:
+            f,n = v
+            return filectx(self._repo, f, fileid=n, filelog=flcache[f])
+
+        return None
+
+class workingctx(changectx):
+    """A workingctx object makes access to data related to
+    the current working directory convenient."""
+    def __init__(self, repo):
+        self._repo = repo
+        self._rev = None
+        self._node = None
+
+    def __str__(self):
+        return str(self._parents[0]) + "+"
+
+    def __nonzero__(self):
+        return True
 
-    def annotate(self):
-        return self._filelog.annotate(self._filenode)
+    def __getattr__(self, name):
+        if name == '_parents':
+            self._parents = self._repo.parents()
+            return self._parents
+        if name == '_status':
+            self._status = self._repo.status()
+            return self._status
+        if name == '_manifest':
+            self._buildmanifest()
+            return self._manifest
+        else:
+            raise AttributeError, name
+
+    def _buildmanifest(self):
+        """generate a manifest corresponding to the working directory"""
+
+        man = self._parents[0].manifest().copy()
+        copied = self._repo.dirstate.copies()
+        modified, added, removed, deleted, unknown = self._status[:5]
+        for i,l in (("a", added), ("m", modified), ("u", unknown)):
+            for f in l:
+                man[f] = man.get(copied.get(f, f), nullid) + i
+                man.set(f, util.is_exec(self._repo.wjoin(f), man.execf(f)))
+
+        for f in deleted + removed:
+            if f in man:
+                del man[f]
+
+        self._manifest = man
+
+    def manifest(self): return self._manifest
+
+    def user(self): return self._repo.ui.username()
+    def date(self): return util.makedate()
+    def description(self): return ""
+    def files(self):
+        f = self.modified() + self.added() + self.removed()
+        f.sort()
+        return f
+
+    def modified(self): return self._status[0]
+    def added(self): return self._status[1]
+    def removed(self): return self._status[2]
+    def deleted(self): return self._status[3]
+    def unknown(self): return self._status[4]
+    def clean(self): return self._status[5]
+    def branch(self):
+        try:
+            return self._repo.opener("branch").read().strip()
+        except IOError:
+            return ""
+
+    def parents(self):
+        """return contexts for each parent changeset"""
+        return self._parents
+
+    def children(self):
+        return []
+
+    def filectx(self, path):
+        """get a file context from the working directory"""
+        return workingfilectx(self._repo, path, workingctx=self)
+
+    def ancestor(self, c2):
+        """return the ancestor context of self and c2"""
+        return self._parents[0].ancestor(c2) # punt on two parents for now
+
+class workingfilectx(filectx):
+    """A workingfilectx object makes access to data related to a particular
+       file in the working directory convenient."""
+    def __init__(self, repo, path, filelog=None, workingctx=None):
+        """changeid can be a changeset revision, node, or tag.
+           fileid can be a file revision or node."""
+        self._repo = repo
+        self._path = path
+        self._changeid = None
+        self._filerev = self._filenode = None
+
+        if filelog:
+            self._filelog = filelog
+        if workingctx:
+            self._changectx = workingctx
+
+    def __getattr__(self, name):
+        if name == '_changectx':
+            self._changectx = workingctx(repo)
+            return self._changectx
+        elif name == '_repopath':
+            self._repopath = (self._repo.dirstate.copied(self._path)
+                              or self._path)
+            return self._repopath
+        elif name == '_filelog':
+            self._filelog = self._repo.file(self._repopath)
+            return self._filelog
+        else:
+            raise AttributeError, name
+
+    def __nonzero__(self):
+        return True
+
+    def __str__(self):
+        return "%s@%s" % (self.path(), self._changectx)
+
+    def filectx(self, fileid):
+        '''opens an arbitrary revision of the file without
+        opening a new filelog'''
+        return filectx(self._repo, self._repopath, fileid=fileid,
+                       filelog=self._filelog)
+
+    def rev(self):
+        if '_changectx' in self.__dict__:
+            return self._changectx.rev()
+        return self._filelog.linkrev(self._filenode)
+
+    def data(self): return self._repo.wread(self._path)
+    def renamed(self):
+        rp = self._repopath
+        if rp == self._path:
+            return None
+        return rp, self._workingctx._parents._manifest.get(rp, nullid)
+
+    def parents(self):
+        '''return parent filectxs, following copies if necessary'''
+        p = self._path
+        rp = self._repopath
+        pcl = self._changectx._parents
+        fl = self._filelog
+        pl = [ (rp, pcl[0]._manifest.get(rp, nullid), fl) ]
+        if len(pcl) > 1:
+            if rp != p:
+                fl = None
+            pl.append((p, pcl[1]._manifest.get(p, nullid), fl))
+
+        return [ filectx(self._repo, p, fileid=n, filelog=l)
+                 for p,n,l in pl if n != nullid ]
+
+    def children(self):
+        return []
+
+    def size(self): return os.stat(self._repo.wjoin(self._path)).st_size
+
+    def cmp(self, text): return self._repo.wread(self._path) == text
--- a/mercurial/demandload.py	Sat Sep 02 22:58:02 2006 -0400
+++ b/mercurial/demandload.py	Sat Oct 21 15:22:08 2006 -0400
@@ -109,9 +109,9 @@
             mod = mod[:col]
         else:
             fromlist = []
-        as = None
+        as_ = None
         if '@' in mod:
-            mod, as = mod.split("@")
+            mod, as_ = mod.split("@")
         importer = _importer(scope, mod, fromlist)
         if fromlist:
             for name in fromlist:
@@ -130,6 +130,6 @@
                     continue
             else:
                 basemod = mod
-            if not as:
-                as = basemod
-            scope[as] = _replacer(importer, as)
+            if not as_:
+                as_ = basemod
+            scope[as_] = _replacer(importer, as_)
--- a/mercurial/dirstate.py	Sat Sep 02 22:58:02 2006 -0400
+++ b/mercurial/dirstate.py	Sat Oct 21 15:22:08 2006 -0400
@@ -1,7 +1,7 @@
 """
 dirstate.py - working directory tracking for mercurial
 
-Copyright 2005 Matt Mackall <mpm@selenic.com>
+Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
 
 This software may be used and distributed according to the terms
 of the GNU General Public License, incorporated herein by reference.
@@ -10,7 +10,7 @@
 from node import *
 from i18n import gettext as _
 from demandload import *
-demandload(globals(), "struct os time bisect stat util re errno")
+demandload(globals(), "struct os time bisect stat strutil util re errno")
 
 class dirstate(object):
     format = ">cllll"
@@ -22,7 +22,8 @@
         self.ui = ui
         self.map = None
         self.pl = None
-        self.copies = {}
+        self.dirs = None
+        self.copymap = {}
         self.ignorefunc = None
         self.blockignore = False
 
@@ -159,7 +160,7 @@
 
         # deref fields so they will be local in loop
         map = self.map
-        copies = self.copies
+        copymap = self.copymap
         format = self.format
         unpack = struct.unpack
 
@@ -175,7 +176,7 @@
             f = st[pos:newpos]
             if '\0' in f:
                 f, c = f.split('\0')
-                copies[f] = c
+                copymap[f] = c
             map[f] = e[:4]
             pos = newpos
 
@@ -192,10 +193,45 @@
     def copy(self, source, dest):
         self.lazyread()
         self.markdirty()
-        self.copies[dest] = source
+        self.copymap[dest] = source
 
     def copied(self, file):
-        return self.copies.get(file, None)
+        return self.copymap.get(file, None)
+
+    def copies(self):
+        return self.copymap
+
+    def initdirs(self):
+        if self.dirs is None:
+            self.dirs = {}
+            for f in self.map:
+                self.updatedirs(f, 1)
+
+    def updatedirs(self, path, delta):
+        if self.dirs is not None:
+            for c in strutil.findall(path, '/'):
+                pc = path[:c]
+                self.dirs.setdefault(pc, 0)
+                self.dirs[pc] += delta
+
+    def checkshadows(self, files):
+        def prefixes(f):
+            for c in strutil.rfindall(f, '/'):
+                yield f[:c]
+        self.lazyread()
+        self.initdirs()
+        seendirs = {}
+        for f in files:
+            if self.dirs.get(f):
+                raise util.Abort(_('directory named %r already in dirstate') %
+                                 f)
+            for d in prefixes(f):
+                if d in seendirs:
+                    break
+                if d in self.map:
+                    raise util.Abort(_('file named %r already in dirstate') %
+                                     d)
+                seendirs[d] = True
 
     def update(self, files, state, **kw):
         ''' current states:
@@ -207,31 +243,40 @@
         if not files: return
         self.lazyread()
         self.markdirty()
+        if state == "a":
+            self.initdirs()
+            self.checkshadows(files)
         for f in files:
             if state == "r":
                 self.map[f] = ('r', 0, 0, 0)
+                self.updatedirs(f, -1)
             else:
+                if state == "a":
+                    self.updatedirs(f, 1)
                 s = os.lstat(self.wjoin(f))
                 st_size = kw.get('st_size', s.st_size)
                 st_mtime = kw.get('st_mtime', s.st_mtime)
                 self.map[f] = (state, s.st_mode, st_size, st_mtime)
-            if self.copies.has_key(f):
-                del self.copies[f]
+            if self.copymap.has_key(f):
+                del self.copymap[f]
 
     def forget(self, files):
         if not files: return
         self.lazyread()
         self.markdirty()
+        self.initdirs()
         for f in files:
             try:
                 del self.map[f]
+                self.updatedirs(f, -1)
             except KeyError:
                 self.ui.warn(_("not in dirstate: %s!\n") % f)
                 pass
 
     def clear(self):
         self.map = {}
-        self.copies = {}
+        self.copymap = {}
+        self.dirs = None
         self.markdirty()
 
     def rebuild(self, parent, files):
@@ -476,7 +521,7 @@
                 if size >= 0 and (size != st.st_size
                                   or (mode ^ st.st_mode) & 0100):
                     modified.append(fn)
-                elif time != st.st_mtime:
+                elif time != int(st.st_mtime):
                     lookup.append(fn)
                 elif list_clean:
                     clean.append(fn)
--- a/mercurial/filelog.py	Sat Sep 02 22:58:02 2006 -0400
+++ b/mercurial/filelog.py	Sat Oct 21 15:22:08 2006 -0400
@@ -1,13 +1,13 @@
 # filelog.py - file history class for mercurial
 #
-# Copyright 2005 Matt Mackall <mpm@selenic.com>
+# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
 #
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
 from revlog import *
 from demandload import *
-demandload(globals(), "bdiff os")
+demandload(globals(), "os")
 
 class filelog(revlog):
     def __init__(self, opener, path, defversion=REVLOG_DEFAULT_VERSION):
@@ -37,7 +37,7 @@
         s = t.index('\1\n', 2)
         return t[s+2:]
 
-    def readmeta(self, node):
+    def _readmeta(self, node):
         t = self.revision(node)
         if not t.startswith('\1\n'):
             return {}
@@ -60,48 +60,27 @@
     def renamed(self, node):
         if self.parents(node)[0] != nullid:
             return False
-        m = self.readmeta(node)
+        m = self._readmeta(node)
         if m and m.has_key("copy"):
             return (m["copy"], bin(m["copyrev"]))
         return False
 
-    def annotate(self, node):
-
-        def decorate(text, rev):
-            return ([rev] * len(text.splitlines()), text)
+    def size(self, rev):
+        """return the size of a given revision"""
 
-        def pair(parent, child):
-            for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
-                child[0][b1:b2] = parent[0][a1:a2]
-            return child
+        # for revisions with renames, we have to go the slow way
+        node = self.node(rev)
+        if self.renamed(node):
+            return len(self.read(node))
+
+        return revlog.size(self, rev)
 
-        # find all ancestors
-        needed = {node:1}
-        visit = [node]
-        while visit:
-            n = visit.pop(0)
-            for p in self.parents(n):
-                if p not in needed:
-                    needed[p] = 1
-                    visit.append(p)
-                else:
-                    # count how many times we'll use this
-                    needed[p] += 1
+    def cmp(self, node, text):
+        """compare text with a given file revision"""
 
-        # sort by revision which is a topological order
-        visit = [ (self.rev(n), n) for n in needed.keys() ]
-        visit.sort()
-        hist = {}
+        # for renames, we have to go the slow way
+        if self.renamed(node):
+            t2 = self.read(node)
+            return t2 != text
 
-        for r,n in visit:
-            curr = decorate(self.read(n), self.linkrev(n))
-            for p in self.parents(n):
-                if p != nullid:
-                    curr = pair(hist[p], curr)
-                    # trim the history of unneeded revs
-                    needed[p] -= 1
-                    if not needed[p]:
-                        del hist[p]
-            hist[n] = curr
-
-        return zip(hist[n][0], hist[n][1].splitlines(1))
+        return revlog.cmp(self, node, text)
--- a/mercurial/hg.py	Sat Sep 02 22:58:02 2006 -0400
+++ b/mercurial/hg.py	Sat Oct 21 15:22:08 2006 -0400
@@ -1,6 +1,7 @@
 # hg.py - repository classes for mercurial
 #
-# Copyright 2005 Matt Mackall <mpm@selenic.com>
+# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
+# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
 #
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
@@ -13,7 +14,7 @@
 demandload(globals(), "errno lock os shutil util merge@_merge verify@_verify")
 
 def _local(path):
-    return (os.path.isfile(path and util.drop_scheme('file', path)) and
+    return (os.path.isfile(util.drop_scheme('file', path)) and
             bundlerepo or localrepo)
 
 schemes = {
@@ -48,9 +49,14 @@
             return False
     return repo.local()
 
-def repository(ui, path=None, create=False):
+repo_setup_hooks = []
+
+def repository(ui, path='', create=False):
     """return a repository object for the specified path"""
-    return _lookup(path).instance(ui, path, create)
+    repo = _lookup(path).instance(ui, path, create)
+    for hook in repo_setup_hooks:
+        hook(ui, repo)
+    return repo
 
 def defaultdest(source):
     '''return default destination of clone if none is given'''
@@ -109,7 +115,7 @@
     source = localpath(source)
 
     if os.path.exists(dest):
-        raise util.Abort(_("destination '%s' already exists"), dest)
+        raise util.Abort(_("destination '%s' already exists") % dest)
 
     class DirCleanup(object):
         def __init__(self, dir_):
@@ -121,12 +127,7 @@
             if self.dir_:
                 self.rmtree(self.dir_, True)
 
-    dest_repo = None
-    try:
-        dest_repo = repository(ui, dest)
-        raise util.Abort(_("destination '%s' already exists." % dest))
-    except RepoError:
-        dest_repo = repository(ui, dest, create=True)
+    dest_repo = repository(ui, dest, create=True)
 
     dest_path = None
     dir_cleanup = None
@@ -175,9 +176,10 @@
     else:
         revs = None
         if rev:
-            if not src_repo.local():
-                raise util.Abort(_("clone by revision not supported yet "
-                                   "for remote repositories"))
+            if 'lookup' not in src_repo.capabilities:
+                raise util.Abort(_("src repository does not support revision "
+                                   "lookup and so doesn't support clone by "
+                                   "revision"))
             revs = [src_repo.lookup(r) for r in rev]
 
         if dest_repo.local():
@@ -200,30 +202,55 @@
             dest_lock.release()
 
         if update:
-            _merge.update(dest_repo, dest_repo.changelog.tip())
+            _update(dest_repo, dest_repo.changelog.tip())
     if dir_cleanup:
         dir_cleanup.close()
 
     return src_repo, dest_repo
 
+def _showstats(repo, stats):
+    stats = ((stats[0], _("updated")),
+             (stats[1], _("merged")),
+             (stats[2], _("removed")),
+             (stats[3], _("unresolved")))
+    note = ", ".join([_("%d files %s") % s for s in stats])
+    repo.ui.status("%s\n" % note)
+
+def _update(repo, node): return update(repo, node)
+
 def update(repo, node):
     """update the working directory to node, merging linear changes"""
-    return _merge.update(repo, node)
+    stats = _merge.update(repo, node, False, False, None, None)
+    _showstats(repo, stats)
+    if stats[3]:
+        repo.ui.status(_("There are unresolved merges with"
+                         " locally modified files.\n"))
+    return stats[3]
 
 def clean(repo, node, wlock=None, show_stats=True):
     """forcibly switch the working directory to node, clobbering changes"""
-    return _merge.update(repo, node, force=True, wlock=wlock,
-                         show_stats=show_stats)
+    stats = _merge.update(repo, node, False, True, None, wlock)
+    if show_stats: _showstats(repo, stats)
+    return stats[3]
 
 def merge(repo, node, force=None, remind=True, wlock=None):
     """branch merge with node, resolving changes"""
-    return _merge.update(repo, node, branchmerge=True, force=force,
-                         remind=remind, wlock=wlock)
+    stats = _merge.update(repo, node, True, force, False, wlock)
+    _showstats(repo, stats)
+    if stats[3]:
+        pl = repo.parents()
+        repo.ui.status(_("There are unresolved merges,"
+                         " you can redo the full merge using:\n"
+                         "  hg update -C %s\n"
+                         "  hg merge %s\n"
+                         % (pl[0].rev(), pl[1].rev())))
+    elif remind:
+        repo.ui.status(_("(branch merge, don't forget to commit)\n"))
+    return stats[3]
 
 def revert(repo, node, choose, wlock):
     """revert changes to revision in node without updating dirstate"""
-    return _merge.update(repo, node, force=True, partial=choose,
-                         show_stats=False, wlock=wlock)
+    return _merge.update(repo, node, False, True, choose, wlock)[3]
 
 def verify(repo):
     """verify the consistency of a repository"""
--- a/mercurial/hgweb/common.py	Sat Sep 02 22:58:02 2006 -0400
+++ b/mercurial/hgweb/common.py	Sat Oct 21 15:22:08 2006 -0400
@@ -1,7 +1,7 @@
 # hgweb/common.py - Utility functions needed by hgweb_mod and hgwebdir_mod
 #
 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
-# Copyright 2005 Matt Mackall <mpm@selenic.com>
+# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
 #
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
@@ -38,7 +38,24 @@
         ct = mimetypes.guess_type(path)[0] or "text/plain"
         req.header([('Content-type', ct),
                     ('Content-length', os.path.getsize(path))])
-        return file(path).read()
+        return file(path, 'rb').read()
     except (TypeError, OSError):
         # illegal fname or unreadable file
         return ""
+
+def style_map(templatepath, style):
+    """Return path to mapfile for a given style.
+
+    Searches mapfile in the following locations:
+    1. templatepath/style/map
+    2. templatepath/map-style
+    3. templatepath/map
+    """
+    locations = style and [os.path.join(style, "map"), "map-"+style] or []
+    locations.append("map")
+    for location in locations:
+        mapfile = os.path.join(templatepath, location)
+        if os.path.isfile(mapfile):
+            return mapfile
+    raise RuntimeError("No hgweb templates found in %r" % templatepath)
+
--- a/mercurial/hgweb/hgweb_mod.py	Sat Sep 02 22:58:02 2006 -0400
+++ b/mercurial/hgweb/hgweb_mod.py	Sat Oct 21 15:22:08 2006 -0400
@@ -1,7 +1,7 @@
 # hgweb/hgweb_mod.py - Web interface for a repository.
 #
 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
-# Copyright 2005 Matt Mackall <mpm@selenic.com>
+# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
 #
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
@@ -11,9 +11,10 @@
 import mimetypes
 from mercurial.demandload import demandload
 demandload(globals(), "re zlib ConfigParser mimetools cStringIO sys tempfile")
-demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,streamclone")
-demandload(globals(), "mercurial:templater")
-demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile")
+demandload(globals(), 'urllib')
+demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,streamclone,patch")
+demandload(globals(), "mercurial:revlog,templater")
+demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile,style_map")
 from mercurial.node import *
 from mercurial.i18n import gettext as _
 
@@ -27,6 +28,44 @@
         return "/"
     return up + "/"
 
+def revnavgen(pos, pagelen, limit, nodefunc):
+    def seq(factor, limit=None):
+        if limit:
+            yield limit
+            if limit >= 20 and limit <= 40:
+                yield 50
+        else:
+            yield 1 * factor
+            yield 3 * factor
+        for f in seq(factor * 10):
+            yield f
+
+    def nav(**map):
+        l = []
+        last = 0
+        for f in seq(1, pagelen):
+            if f < pagelen or f <= last:
+                continue
+            if f > limit:
+                break
+            last = f
+            if pos + f < limit:
+                l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
+            if pos - f >= 0:
+                l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
+
+        try:
+            yield {"label": "(0)", "node": hex(nodefunc('0').node())}
+
+            for label, node in l:
+                yield {"label": label, "node": node}
+
+            yield {"label": "tip", "node": "tip"}
+        except hg.RepoError:
+            pass
+
+    return nav
+
 class hgweb(object):
     def __init__(self, repo, name=None):
         if type(repo) == type(""):
@@ -54,15 +93,9 @@
 
     def archivelist(self, nodeid):
         allowed = self.repo.ui.configlist("web", "allow_archive")
-        for i in self.archives:
+        for i, spec in self.archive_specs.iteritems():
             if i in allowed or self.repo.ui.configbool("web", "allow" + i):
-                yield {"type" : i, "node" : nodeid, "url": ""}
-
-    def listfiles(self, files, mf):
-        for f in files[:self.maxfiles]:
-            yield self.t("filenodelink", node=hex(mf[f]), file=f)
-        if len(files) > self.maxfiles:
-            yield self.t("fileellipses")
+                yield {"type" : i, "extension" : spec[2], "node" : nodeid}
 
     def listfilediffs(self, files, changeset):
         for f in files[:self.maxfiles]:
@@ -70,14 +103,16 @@
         if len(files) > self.maxfiles:
             yield self.t("fileellipses")
 
-    def siblings(self, siblings=[], rev=None, hiderev=None, **args):
-        if not rev:
-            rev = lambda x: ""
-        siblings = [s for s in siblings if s != nullid]
-        if len(siblings) == 1 and rev(siblings[0]) == hiderev:
+    def siblings(self, siblings=[], hiderev=None, **args):
+        siblings = [s for s in siblings if s.node() != nullid]
+        if len(siblings) == 1 and siblings[0].rev() == hiderev:
             return
         for s in siblings:
-            yield dict(node=hex(s), rev=rev(s), **args)
+            d = {'node': hex(s.node()), 'rev': s.rev()}
+            if hasattr(s, 'path'):
+                d['file'] = s.path()
+            d.update(args)
+            yield d
 
     def renamelink(self, fl, node):
         r = fl.renamed(node)
@@ -129,95 +164,47 @@
         date1 = util.datestr(change1[2])
         date2 = util.datestr(change2[2])
 
-        modified, added, removed, deleted, unknown = r.changes(node1, node2)
+        modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
         if files:
             modified, added, removed = map(lambda x: filterfiles(files, x),
                                            (modified, added, removed))
 
-        diffopts = self.repo.ui.diffopts()
-        showfunc = diffopts['showfunc']
-        ignorews = diffopts['ignorews']
-        ignorewsamount = diffopts['ignorewsamount']
-        ignoreblanklines = diffopts['ignoreblanklines']
+        diffopts = patch.diffopts(self.repo.ui)
         for f in modified:
             to = r.file(f).read(mmap1[f])
             tn = r.file(f).read(mmap2[f])
             yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
-                            showfunc=showfunc, ignorews=ignorews,
-                            ignorewsamount=ignorewsamount,
-                            ignoreblanklines=ignoreblanklines), f, tn)
+                                          opts=diffopts), f, tn)
         for f in added:
             to = None
             tn = r.file(f).read(mmap2[f])
             yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
-                            showfunc=showfunc, ignorews=ignorews,
-                            ignorewsamount=ignorewsamount,
-                            ignoreblanklines=ignoreblanklines), f, tn)
+                                          opts=diffopts), f, tn)
         for f in removed:
             to = r.file(f).read(mmap1[f])
             tn = None
             yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
-                            showfunc=showfunc, ignorews=ignorews,
-                            ignorewsamount=ignorewsamount,
-                            ignoreblanklines=ignoreblanklines), f, tn)
-
-    def changelog(self, pos, shortlog=False):
-        def changenav(**map):
-            def seq(factor, maxchanges=None):
-                if maxchanges:
-                    yield maxchanges
-                    if maxchanges >= 20 and maxchanges <= 40:
-                        yield 50
-                else:
-                    yield 1 * factor
-                    yield 3 * factor
-                for f in seq(factor * 10):
-                    yield f
+                                          opts=diffopts), f, tn)
 
-            l = []
-            last = 0
-            maxchanges = shortlog and self.maxshortchanges or self.maxchanges
-            for f in seq(1, maxchanges):
-                if f < maxchanges or f <= last:
-                    continue
-                if f > count:
-                    break
-                last = f
-                r = "%d" % f
-                if pos + f < count:
-                    l.append(("+" + r, pos + f))
-                if pos - f >= 0:
-                    l.insert(0, ("-" + r, pos - f))
-
-            yield {"rev": 0, "label": "(0)"}
-
-            for label, rev in l:
-                yield {"label": label, "rev": rev}
-
-            yield {"label": "tip", "rev": "tip"}
-
+    def changelog(self, ctx, shortlog=False):
         def changelist(**map):
             parity = (start - end) & 1
             cl = self.repo.changelog
             l = [] # build a list in forward order for efficiency
             for i in range(start, end):
-                n = cl.node(i)
-                changes = cl.read(n)
-                hn = hex(n)
+                ctx = self.repo.changectx(i)
+                n = ctx.node()
 
                 l.insert(0, {"parity": parity,
-                             "author": changes[1],
-                             "parent": self.siblings(cl.parents(n), cl.rev,
-                                                     cl.rev(n) - 1),
-                             "child": self.siblings(cl.children(n), cl.rev,
-                                                    cl.rev(n) + 1),
+                             "author": ctx.user(),
+                             "parent": self.siblings(ctx.parents(), i - 1),
+                             "child": self.siblings(ctx.children(), i + 1),
                              "changelogtag": self.showtag("changelogtag",n),
-                             "manifest": hex(changes[0]),
-                             "desc": changes[4],
-                             "date": changes[2],
-                             "files": self.listfilediffs(changes[3], n),
+                             "desc": ctx.description(),
+                             "date": ctx.date(),
+                             "files": self.listfilediffs(ctx.files(), n),
                              "rev": i,
-                             "node": hn})
+                             "node": hex(n)})
                 parity = 1 - parity
 
             for e in l:
@@ -225,15 +212,17 @@
 
         maxchanges = shortlog and self.maxshortchanges or self.maxchanges
         cl = self.repo.changelog
-        mf = cl.read(cl.tip())[0]
         count = cl.count()
+        pos = ctx.rev()
         start = max(0, pos - maxchanges + 1)
         end = min(count, start + maxchanges)
         pos = end - 1
 
+        changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
+
         yield self.t(shortlog and 'shortlog' or 'changelog',
                      changenav=changenav,
-                     manifest=hex(mf),
+                     node=hex(cl.tip()),
                      rev=pos, changesets=count, entries=changelist,
                      archives=self.archivelist("tip"))
 
@@ -248,127 +237,120 @@
                 for i in range(cl.count() - 1, 0, -100):
                     l = []
                     for j in range(max(0, i - 100), i):
-                        n = cl.node(j)
-                        changes = cl.read(n)
-                        l.append((n, j, changes))
+                        ctx = self.repo.changectx(j)
+                        l.append(ctx)
                     l.reverse()
                     for e in l:
                         yield e
 
-            for n, i, changes in revgen():
+            for ctx in revgen():
                 miss = 0
                 for q in qw:
-                    if not (q in changes[1].lower() or
-                            q in changes[4].lower() or
-                            q in " ".join(changes[3][:20]).lower()):
+                    if not (q in ctx.user().lower() or
+                            q in ctx.description().lower() or
+                            q in " ".join(ctx.files()[:20]).lower()):
                         miss = 1
                         break
                 if miss:
                     continue
 
                 count += 1
-                hn = hex(n)
+                n = ctx.node()
 
                 yield self.t('searchentry',
                              parity=self.stripes(count),
-                             author=changes[1],
-                             parent=self.siblings(cl.parents(n), cl.rev),
-                             child=self.siblings(cl.children(n), cl.rev),
+                             author=ctx.user(),
+                             parent=self.siblings(ctx.parents()),
+                             child=self.siblings(ctx.children()),
                              changelogtag=self.showtag("changelogtag",n),
-                             manifest=hex(changes[0]),
-                             desc=changes[4],
-                             date=changes[2],
-                             files=self.listfilediffs(changes[3], n),
-                             rev=i,
-                             node=hn)
+                             desc=ctx.description(),
+                             date=ctx.date(),
+                             files=self.listfilediffs(ctx.files(), n),
+                             rev=ctx.rev(),
+                             node=hex(n))
 
                 if count >= self.maxchanges:
                     break
 
         cl = self.repo.changelog
-        mf = cl.read(cl.tip())[0]
 
         yield self.t('search',
                      query=query,
-                     manifest=hex(mf),
+                     node=hex(cl.tip()),
                      entries=changelist)
 
-    def changeset(self, nodeid):
-        cl = self.repo.changelog
-        n = self.repo.lookup(nodeid)
-        nodeid = hex(n)
-        changes = cl.read(n)
-        p1 = cl.parents(n)[0]
+    def changeset(self, ctx):
+        n = ctx.node()
+        parents = ctx.parents()
+        p1 = parents[0].node()
 
         files = []
-        mf = self.repo.manifest.read(changes[0])
-        for f in changes[3]:
+        parity = 0
+        for f in ctx.files():
             files.append(self.t("filenodelink",
-                                filenode=hex(mf.get(f, nullid)), file=f))
+                                node=hex(n), file=f,
+                                parity=parity))
+            parity = 1 - parity
 
         def diff(**map):
             yield self.diff(p1, n, None)
 
         yield self.t('changeset',
                      diff=diff,
-                     rev=cl.rev(n),
-                     node=nodeid,
-                     parent=self.siblings(cl.parents(n), cl.rev),
-                     child=self.siblings(cl.children(n), cl.rev),
+                     rev=ctx.rev(),
+                     node=hex(n),
+                     parent=self.siblings(parents),
+                     child=self.siblings(ctx.children()),
                      changesettag=self.showtag("changesettag",n),
-                     manifest=hex(changes[0]),
-                     author=changes[1],
-                     desc=changes[4],
-                     date=changes[2],
+                     author=ctx.user(),
+                     desc=ctx.description(),
+                     date=ctx.date(),
                      files=files,
-                     archives=self.archivelist(nodeid))
+                     archives=self.archivelist(hex(n)))
 
-    def filelog(self, f, filenode):
-        cl = self.repo.changelog
-        fl = self.repo.file(f)
-        filenode = hex(fl.lookup(filenode))
+    def filelog(self, fctx):
+        f = fctx.path()
+        fl = fctx.filelog()
         count = fl.count()
+        pagelen = self.maxshortchanges
+        pos = fctx.filerev()
+        start = max(0, pos - pagelen + 1) 
+        end = min(count, start + pagelen)
+        pos = end - 1
 
         def entries(**map):
             l = []
             parity = (count - 1) & 1
 
-            for i in range(count):
+            for i in range(start, end):
+                ctx = fctx.filectx(i)
                 n = fl.node(i)
-                lr = fl.linkrev(n)
-                cn = cl.node(lr)
-                cs = cl.read(cl.node(lr))
 
                 l.insert(0, {"parity": parity,
-                             "filenode": hex(n),
                              "filerev": i,
                              "file": f,
-                             "node": hex(cn),
-                             "author": cs[1],
-                             "date": cs[2],
+                             "node": hex(ctx.node()),
+                             "author": ctx.user(),
+                             "date": ctx.date(),
                              "rename": self.renamelink(fl, n),
-                             "parent": self.siblings(fl.parents(n),
-                                                     fl.rev, file=f),
-                             "child": self.siblings(fl.children(n),
-                                                    fl.rev, file=f),
-                             "desc": cs[4]})
+                             "parent": self.siblings(fctx.parents()),
+                             "child": self.siblings(fctx.children()),
+                             "desc": ctx.description()})
                 parity = 1 - parity
 
             for e in l:
                 yield e
 
-        yield self.t("filelog", file=f, filenode=filenode, entries=entries)
+        nodefunc = lambda x: fctx.filectx(fileid=x)
+        nav = revnavgen(pos, pagelen, count, nodefunc)
+        yield self.t("filelog", file=f, node=hex(fctx.node()), nav=nav,
+                     entries=entries)
 
-    def filerevision(self, f, node):
-        fl = self.repo.file(f)
-        n = fl.lookup(node)
-        node = hex(n)
-        text = fl.read(n)
-        changerev = fl.linkrev(n)
-        cl = self.repo.changelog
-        cn = cl.node(changerev)
-        cs = cl.read(cn)
-        mfn = cs[0]
+    def filerevision(self, fctx):
+        f = fctx.path()
+        text = fctx.data()
+        fl = fctx.filelog()
+        n = fctx.filenode()
 
         mt = mimetypes.guess_type(f)[0]
         rawtext = text
@@ -385,83 +367,60 @@
 
         yield self.t("filerevision",
                      file=f,
-                     filenode=node,
                      path=_up(f),
                      text=lines(),
                      raw=rawtext,
                      mimetype=mt,
-                     rev=changerev,
-                     node=hex(cn),
-                     manifest=hex(mfn),
-                     author=cs[1],
-                     date=cs[2],
-                     parent=self.siblings(fl.parents(n), fl.rev, file=f),
-                     child=self.siblings(fl.children(n), fl.rev, file=f),
+                     rev=fctx.rev(),
+                     node=hex(fctx.node()),
+                     author=fctx.user(),
+                     date=fctx.date(),
+                     desc=fctx.description(),
+                     parent=self.siblings(fctx.parents()),
+                     child=self.siblings(fctx.children()),
                      rename=self.renamelink(fl, n),
-                     permissions=self.repo.manifest.read(mfn).execf[f])
+                     permissions=fctx.manifest().execf(f))
 
-    def fileannotate(self, f, node):
-        bcache = {}
-        ncache = {}
-        fl = self.repo.file(f)
-        n = fl.lookup(node)
-        node = hex(n)
-        changerev = fl.linkrev(n)
-
-        cl = self.repo.changelog
-        cn = cl.node(changerev)
-        cs = cl.read(cn)
-        mfn = cs[0]
+    def fileannotate(self, fctx):
+        f = fctx.path()
+        n = fctx.filenode()
+        fl = fctx.filelog()
 
         def annotate(**map):
             parity = 0
             last = None
-            for r, l in fl.annotate(n):
-                try:
-                    cnode = ncache[r]
-                except KeyError:
-                    cnode = ncache[r] = self.repo.changelog.node(r)
+            for f, l in fctx.annotate(follow=True):
+                fnode = f.filenode()
+                name = self.repo.ui.shortuser(f.user())
 
-                try:
-                    name = bcache[r]
-                except KeyError:
-                    cl = self.repo.changelog.read(cnode)
-                    bcache[r] = name = self.repo.ui.shortuser(cl[1])
-
-                if last != cnode:
+                if last != fnode:
                     parity = 1 - parity
-                    last = cnode
+                    last = fnode
 
                 yield {"parity": parity,
-                       "node": hex(cnode),
-                       "rev": r,
+                       "node": hex(f.node()),
+                       "rev": f.rev(),
                        "author": name,
-                       "file": f,
+                       "file": f.path(),
                        "line": l}
 
         yield self.t("fileannotate",
                      file=f,
-                     filenode=node,
                      annotate=annotate,
                      path=_up(f),
-                     rev=changerev,
-                     node=hex(cn),
-                     manifest=hex(mfn),
-                     author=cs[1],
-                     date=cs[2],
+                     rev=fctx.rev(),
+                     node=hex(fctx.node()),
+                     author=fctx.user(),
+                     date=fctx.date(),
+                     desc=fctx.description(),
                      rename=self.renamelink(fl, n),
-                     parent=self.siblings(fl.parents(n), fl.rev, file=f),
-                     child=self.siblings(fl.children(n), fl.rev, file=f),
-                     permissions=self.repo.manifest.read(mfn).execf[f])
+                     parent=self.siblings(fctx.parents()),
+                     child=self.siblings(fctx.children()),
+                     permissions=fctx.manifest().execf(f))
 
-    def manifest(self, mnode, path):
-        man = self.repo.manifest
-        mn = man.lookup(mnode)
-        mnode = hex(mn)
-        mf = man.read(mn)
-        rev = man.rev(mn)
-        changerev = man.linkrev(mn)
-        node = self.repo.changelog.node(changerev)
+    def manifest(self, ctx, path):
+        mf = ctx.manifest()
+        node = ctx.node()
 
         files = {}
 
@@ -491,11 +450,10 @@
                     continue
 
                 yield {"file": full,
-                       "manifest": mnode,
-                       "filenode": hex(fnode),
                        "parity": self.stripes(parity),
                        "basename": f,
-                       "permissions": mf.execf[full]}
+                       "size": ctx.filectx(full).size(),
+                       "permissions": mf.execf(full)}
                 parity += 1
 
         def dirlist(**map):
@@ -509,13 +467,11 @@
 
                 yield {"parity": self.stripes(parity),
                        "path": os.path.join(path, f),
-                       "manifest": mnode,
                        "basename": f[:-1]}
                 parity += 1
 
         yield self.t("manifest",
-                     manifest=mnode,
-                     rev=rev,
+                     rev=ctx.rev(),
                      node=hex(node),
                      path=path,
                      up=_up(path),
@@ -525,7 +481,6 @@
 
     def tags(self):
         cl = self.repo.changelog
-        mf = cl.read(cl.tip())[0]
 
         i = self.repo.tagslist()
         i.reverse()
@@ -536,19 +491,17 @@
                 if notip and k == "tip": continue
                 yield {"parity": self.stripes(parity),
                        "tag": k,
-                       "tagmanifest": hex(cl.read(n)[0]),
                        "date": cl.read(n)[2],
                        "node": hex(n)}
                 parity += 1
 
         yield self.t("tags",
-                     manifest=hex(mf),
+                     node=hex(self.repo.changelog.tip()),
                      entries=lambda **x: entries(False, **x),
                      entriesnotip=lambda **x: entries(True, **x))
 
     def summary(self):
         cl = self.repo.changelog
-        mf = cl.read(cl.tip())[0]
 
         i = self.repo.tagslist()
         i.reverse()
@@ -565,15 +518,13 @@
                     break;
 
                 c = cl.read(n)
-                m = c[0]
                 t = c[2]
 
                 yield self.t("tagentry",
                              parity = self.stripes(parity),
                              tag = k,
                              node = hex(n),
-                             date = t,
-                             tagmanifest = hex(m))
+                             date = t)
                 parity += 1
 
         def changelist(**map):
@@ -590,7 +541,6 @@
                     'shortlogentry',
                     parity = parity,
                     author = changes[1],
-                    manifest = hex(changes[0]),
                     desc = changes[4],
                     date = t,
                     rev = i,
@@ -599,8 +549,6 @@
 
             yield l
 
-        cl = self.repo.changelog
-        mf = cl.read(cl.tip())[0]
         count = cl.count()
         start = max(0, count - self.maxchanges)
         end = min(count, start + self.maxchanges)
@@ -610,30 +558,27 @@
                  owner = (self.repo.ui.config("ui", "username") or # preferred
                           self.repo.ui.config("web", "contact") or # deprecated
                           self.repo.ui.config("web", "author", "unknown")), # also
-                 lastchange = (0, 0), # FIXME
-                 manifest = hex(mf),
+                 lastchange = cl.read(cl.tip())[2],
                  tags = tagentries,
                  shortlog = changelist,
+                 node = hex(cl.tip()),
                  archives=self.archivelist("tip"))
 
-    def filediff(self, file, changeset):
-        cl = self.repo.changelog
-        n = self.repo.lookup(changeset)
-        changeset = hex(n)
-        p1 = cl.parents(n)[0]
-        cs = cl.read(n)
-        mf = self.repo.manifest.read(cs[0])
+    def filediff(self, fctx):
+        n = fctx.node()
+        path = fctx.path()
+        parents = fctx.parents()
+        p1 = parents and parents[0].node() or nullid
 
         def diff(**map):
-            yield self.diff(p1, n, [file])
+            yield self.diff(p1, n, [path])
 
         yield self.t("filediff",
-                     file=file,
-                     filenode=hex(mf.get(file, nullid)),
-                     node=changeset,
-                     rev=self.repo.changelog.rev(n),
-                     parent=self.siblings(cl.parents(n), cl.rev),
-                     child=self.siblings(cl.children(n), cl.rev),
+                     file=path,
+                     node=hex(n),
+                     rev=fctx.rev(),
+                     parent=self.siblings(parents),
+                     child=self.siblings(fctx.children()),
                      diff=diff)
 
     archive_specs = {
@@ -659,10 +604,7 @@
     # find tag, changeset, file
 
     def cleanpath(self, path):
-        p = util.normpath(path)
-        if p[:2] == "..":
-            raise Exception("suspicious path")
-        return p
+        return util.canonpath(self.repo.root, '', path)
 
     def run(self):
         if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
@@ -715,36 +657,113 @@
                         form[name] = value
                     del form[k]
 
+        def rewrite_request(req):
+            '''translate new web interface to traditional format'''
+
+            def spliturl(req):
+                def firstitem(query):
+                    return query.split('&', 1)[0].split(';', 1)[0]
+
+                def normurl(url):
+                    inner = '/'.join([x for x in url.split('/') if x])
+                    tl = len(url) > 1 and url.endswith('/') and '/' or ''
+
+                    return '%s%s%s' % (url.startswith('/') and '/' or '',
+                                       inner, tl)
+
+                root = normurl(req.env.get('REQUEST_URI', '').split('?', 1)[0])
+                pi = normurl(req.env.get('PATH_INFO', ''))
+                if pi:
+                    # strip leading /
+                    pi = pi[1:]
+                    if pi:
+                        root = root[:-len(pi)]
+                    if req.env.has_key('REPO_NAME'):
+                        rn = req.env['REPO_NAME'] + '/'
+                        root += rn
+                        query = pi[len(rn):]
+                    else:
+                        query = pi
+                else:
+                    root += '?'
+                    query = firstitem(req.env['QUERY_STRING'])
+
+                return (root, query)
+
+            req.url, query = spliturl(req)
+
+            if req.form.has_key('cmd'):
+                # old style
+                return
+
+            args = query.split('/', 2)
+            if not args or not args[0]:
+                return
+
+            cmd = args.pop(0)
+            style = cmd.rfind('-')
+            if style != -1:
+                req.form['style'] = [cmd[:style]]
+                cmd = cmd[style+1:]
+            # avoid accepting e.g. style parameter as command
+            if hasattr(self, 'do_' + cmd):
+                req.form['cmd'] = [cmd]
+
+            if args and args[0]:
+                node = args.pop(0)
+                req.form['node'] = [node]
+            if args:
+                req.form['file'] = args
+
+            if cmd == 'static':
+                req.form['file'] = req.form['node']
+            elif cmd == 'archive':
+                fn = req.form['node'][0]
+                for type_, spec in self.archive_specs.iteritems():
+                    ext = spec[2]
+                    if fn.endswith(ext):
+                        req.form['node'] = [fn[:-len(ext)]]
+                        req.form['type'] = [type_]
+
+        def sessionvars(**map):
+            fields = []
+            if req.form.has_key('style'):
+                style = req.form['style'][0]
+                if style != self.repo.ui.config('web', 'style', ''):
+                    fields.append(('style', style))
+
+            separator = req.url[-1] == '?' and ';' or '?'
+            for name, value in fields:
+                yield dict(name=name, value=value, separator=separator)
+                separator = ';'
+
         self.refresh()
 
         expand_form(req.form)
+        rewrite_request(req)
 
-        m = os.path.join(self.templatepath, "map")
         style = self.repo.ui.config("web", "style", "")
         if req.form.has_key('style'):
             style = req.form['style'][0]
-        if style:
-            b = os.path.basename("map-" + style)
-            p = os.path.join(self.templatepath, b)
-            if os.path.isfile(p):
-                m = p
+        mapfile = style_map(self.templatepath, style)
 
         port = req.env["SERVER_PORT"]
         port = port != "80" and (":" + port) or ""
-        uri = req.env["REQUEST_URI"]
-        if "?" in uri:
-            uri = uri.split("?")[0]
-        url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
+        urlbase = 'http://%s%s' % (req.env['SERVER_NAME'], port)
+
         if not self.reponame:
             self.reponame = (self.repo.ui.config("web", "name")
-                             or uri.strip('/') or self.repo.root)
+                             or req.env.get('REPO_NAME')
+                             or req.url.strip('/') or self.repo.root)
 
-        self.t = templater.templater(m, templater.common_filters,
-                                     defaults={"url": url,
+        self.t = templater.templater(mapfile, templater.common_filters,
+                                     defaults={"url": req.url,
+                                               "urlbase": urlbase,
                                                "repo": self.reponame,
                                                "header": header,
                                                "footer": footer,
                                                "rawfileheader": rawfileheader,
+                                               "sessionvars": sessionvars
                                                })
 
         if not req.form.has_key('cmd'):
@@ -754,9 +773,43 @@
 
         method = getattr(self, 'do_' + cmd, None)
         if method:
-            method(req)
+            try:
+                method(req)
+            except (hg.RepoError, revlog.RevlogError), inst:
+                req.write(self.t("error", error=str(inst)))
+        else:
+            req.write(self.t("error", error='No such method: ' + cmd))
+
+    def changectx(self, req):
+        if req.form.has_key('node'):
+            changeid = req.form['node'][0]
+        elif req.form.has_key('manifest'):
+            changeid = req.form['manifest'][0]
         else:
-            req.write(self.t("error"))
+            changeid = self.repo.changelog.count() - 1
+
+        try:
+            ctx = self.repo.changectx(changeid)
+        except hg.RepoError:
+            man = self.repo.manifest
+            mn = man.lookup(changeid)
+            ctx = self.repo.changectx(man.linkrev(mn))
+
+        return ctx
+
+    def filectx(self, req):
+        path = self.cleanpath(req.form['file'][0])
+        if req.form.has_key('node'):
+            changeid = req.form['node'][0]
+        else:
+            changeid = req.form['filenode'][0]
+        try:
+            ctx = self.repo.changectx(changeid)
+            fctx = ctx.filectx(path)
+        except hg.RepoError:
+            fctx = self.repo.filectx(path, fileid=changeid)
+
+        return fctx
 
     def stripes(self, parity):
         "make horizontal stripes for easier reading"
@@ -765,35 +818,54 @@
         else:
             return 0
 
-    def do_changelog(self, req):
-        hi = self.repo.changelog.count() - 1
-        if req.form.has_key('rev'):
-            hi = req.form['rev'][0]
+    def do_log(self, req):
+        if req.form.has_key('file') and req.form['file'][0]:
+            self.do_filelog(req)
+        else:
+            self.do_changelog(req)
+
+    def do_rev(self, req):
+        self.do_changeset(req)
+
+    def do_file(self, req):
+        path = req.form.get('file', [''])[0]
+        if path:
             try:
-                hi = self.repo.changelog.rev(self.repo.lookup(hi))
+                req.write(self.filerevision(self.filectx(req)))
+                return
+            except hg.RepoError:
+                pass
+            path = self.cleanpath(path)
+
+        req.write(self.manifest(self.changectx(req), '/' + path))
+
+    def do_diff(self, req):
+        self.do_filediff(req)
+
+    def do_changelog(self, req, shortlog = False):
+        if req.form.has_key('node'):
+            ctx = self.changectx(req)
+        else:
+            if req.form.has_key('rev'):
+                hi = req.form['rev'][0]
+            else:
+                hi = self.repo.changelog.count() - 1
+            try:
+                ctx = self.repo.changectx(hi)
             except hg.RepoError:
                 req.write(self.search(hi)) # XXX redirect to 404 page?
                 return
 
-        req.write(self.changelog(hi))
+        req.write(self.changelog(ctx, shortlog = shortlog))
 
     def do_shortlog(self, req):
-        hi = self.repo.changelog.count() - 1
-        if req.form.has_key('rev'):
-            hi = req.form['rev'][0]
-            try:
-                hi = self.repo.changelog.rev(self.repo.lookup(hi))
-            except hg.RepoError:
-                req.write(self.search(hi)) # XXX redirect to 404 page?
-                return
-
-        req.write(self.changelog(hi, shortlog = True))
+        self.do_changelog(req, shortlog = True)
 
     def do_changeset(self, req):
-        req.write(self.changeset(req.form['node'][0]))
+        req.write(self.changeset(self.changectx(req)))
 
     def do_manifest(self, req):
-        req.write(self.manifest(req.form['manifest'][0],
+        req.write(self.manifest(self.changectx(req),
                                 self.cleanpath(req.form['path'][0])))
 
     def do_tags(self, req):
@@ -803,20 +875,24 @@
         req.write(self.summary())
 
     def do_filediff(self, req):
-        req.write(self.filediff(self.cleanpath(req.form['file'][0]),
-                                req.form['node'][0]))
-
-    def do_file(self, req):
-        req.write(self.filerevision(self.cleanpath(req.form['file'][0]),
-                                    req.form['filenode'][0]))
+        req.write(self.filediff(self.filectx(req)))
 
     def do_annotate(self, req):
-        req.write(self.fileannotate(self.cleanpath(req.form['file'][0]),
-                                    req.form['filenode'][0]))
+        req.write(self.fileannotate(self.filectx(req)))
 
     def do_filelog(self, req):
-        req.write(self.filelog(self.cleanpath(req.form['file'][0]),
-                               req.form['filenode'][0]))
+        req.write(self.filelog(self.filectx(req)))
+
+    def do_lookup(self, req):
+        try:
+            r = hex(self.repo.lookup(req.form['key'][0]))
+            success = 1
+        except Exception,inst:
+            r = str(inst)
+            success = 0
+        resp = "%s %s\n" % (success, r)
+        req.httphdr("application/mercurial-0.1", length=len(resp))
+        req.write(resp)
 
     def do_heads(self, req):
         resp = " ".join(map(hex, self.repo.heads())) + "\n"
@@ -835,7 +911,6 @@
         req.write(resp)
 
     def do_between(self, req):
-        nodes = []
         if req.form.has_key('pairs'):
             pairs = [map(bin, p.split("-"))
                      for p in req.form['pairs'][0].split(" ")]
@@ -865,6 +940,28 @@
 
         req.write(z.flush())
 
+    def do_changegroupsubset(self, req):
+        req.httphdr("application/mercurial-0.1")
+        bases = []
+        heads = []
+        if not self.allowpull:
+            return
+
+        if req.form.has_key('bases'):
+            bases = [bin(x) for x in req.form['bases'][0].split(' ')]
+        if req.form.has_key('heads'):
+            heads = [bin(x) for x in req.form['heads'][0].split(' ')]
+
+        z = zlib.compressobj()
+        f = self.repo.changegroupsubset(bases, heads, 'serve')
+        while 1:
+            chunk = f.read(4096)
+            if not chunk:
+                break
+            req.write(z.compress(chunk))
+
+        req.write(z.flush())
+
     def do_archive(self, req):
         changeset = self.repo.lookup(req.form['node'][0])
         type_ = req.form['type'][0]
@@ -885,7 +982,7 @@
                   or self.t("error", error="%r not found" % fname))
 
     def do_capabilities(self, req):
-        caps = ['unbundle']
+        caps = ['unbundle', 'lookup', 'changegroupsubset']
         if self.repo.ui.configbool('server', 'uncompressed'):
             caps.append('stream=%d' % self.repo.revlogversion)
         resp = ' '.join(caps)
--- a/mercurial/hgweb/hgwebdir_mod.py	Sat Sep 02 22:58:02 2006 -0400
+++ b/mercurial/hgweb/hgwebdir_mod.py	Sat Oct 21 15:22:08 2006 -0400
@@ -1,17 +1,17 @@
 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
 #
 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
-# Copyright 2005 Matt Mackall <mpm@selenic.com>
+# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
 #
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
 import os
 from mercurial.demandload import demandload
-demandload(globals(), "ConfigParser mimetools cStringIO")
+demandload(globals(), "mimetools cStringIO")
 demandload(globals(), "mercurial:ui,hg,util,templater")
 demandload(globals(), "mercurial.hgweb.hgweb_mod:hgweb")
-demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile")
+demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile,style_map")
 from mercurial.i18n import gettext as _
 
 # This is a stopgap
@@ -21,6 +21,7 @@
             return [(name.strip(os.sep), path) for name, path in items]
 
         self.motd = ""
+        self.style = ""
         self.repos_sorted = ('name', False)
         if isinstance(config, (list, tuple)):
             self.repos = cleannames(config)
@@ -29,11 +30,14 @@
             self.repos = cleannames(config.items())
             self.repos.sort()
         else:
-            cp = ConfigParser.SafeConfigParser()
+            cp = util.configparser()
             cp.read(config)
             self.repos = []
-            if cp.has_section('web') and cp.has_option('web', 'motd'):
-                self.motd = cp.get('web', 'motd')
+            if cp.has_section('web'):
+                if cp.has_option('web', 'motd'):
+                    self.motd = cp.get('web', 'motd')
+                if cp.has_option('web', 'style'):
+                    self.style = cp.get('web', 'style')
             if cp.has_section('paths'):
                 self.repos.extend(cleannames(cp.items('paths')))
             if cp.has_section('collections'):
@@ -65,18 +69,39 @@
         def footer(**map):
             yield tmpl("footer", motd=self.motd, **map)
 
-        m = os.path.join(templater.templatepath(), "map")
-        tmpl = templater.templater(m, templater.common_filters,
+        url = req.env['REQUEST_URI'].split('?')[0]
+        if not url.endswith('/'):
+            url += '/'
+
+        style = self.style
+        if req.form.has_key('style'):
+            style = req.form['style'][0]
+        mapfile = style_map(templater.templatepath(), style)
+        tmpl = templater.templater(mapfile, templater.common_filters,
                                    defaults={"header": header,
-                                             "footer": footer})
+                                             "footer": footer,
+                                             "url": url})
 
         def archivelist(ui, nodeid, url):
             allowed = ui.configlist("web", "allow_archive")
-            for i in ['zip', 'gz', 'bz2']:
-                if i in allowed or ui.configbool("web", "allow" + i):
-                    yield {"type" : i, "node": nodeid, "url": url}
+            for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
+                if i[0] in allowed or ui.configbool("web", "allow" + i[0]):
+                    yield {"type" : i[0], "extension": i[1],
+                           "node": nodeid, "url": url}
 
         def entries(sortcolumn="", descending=False, **map):
+            def sessionvars(**map):
+                fields = []
+                if req.form.has_key('style'):
+                    style = req.form['style'][0]
+                    if style != get('web', 'style', ''):
+                        fields.append(('style', style))
+
+                separator = url[-1] == '?' and ';' or '?'
+                for name, value in fields:
+                    yield dict(name=name, value=value, separator=separator)
+                    separator = ';'
+
             rows = []
             parity = 0
             for name, path in self.repos:
@@ -88,7 +113,7 @@
                 get = u.config
 
                 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
-                       .replace("//", "/"))
+                       .replace("//", "/")) + '/'
 
                 # update time with local timezone
                 try:
@@ -110,6 +135,7 @@
                            description_sort=description.upper() or "unknown",
                            lastchange=d,
                            lastchange_sort=d[1]-d[0],
+                           sessionvars=sessionvars,
                            archives=archivelist(u, "tip", url))
                 if (not sortcolumn
                     or (sortcolumn, descending) == self.repos_sorted):
@@ -129,9 +155,22 @@
                     yield row
 
         virtual = req.env.get("PATH_INFO", "").strip('/')
-        if virtual:
-            real = dict(self.repos).get(virtual)
+        if virtual.startswith('static/'):
+            static = os.path.join(templater.templatepath(), 'static')
+            fname = virtual[7:]
+            req.write(staticfile(static, fname, req) or
+                      tmpl('error', error='%r not found' % fname))
+        elif virtual:
+            while virtual:
+                real = dict(self.repos).get(virtual)
+                if real:
+                    break
+                up = virtual.rfind('/')
+                if up < 0:
+                    break
+                virtual = virtual[:up]
             if real:
+                req.env['REPO_NAME'] = virtual
                 try:
                     hgweb(real).run_wsgi(req)
                 except IOError, inst:
--- a/mercurial/hgweb/request.py	Sat Sep 02 22:58:02 2006 -0400
+++ b/mercurial/hgweb/request.py	Sat Oct 21 15:22:08 2006 -0400
@@ -1,7 +1,7 @@
 # hgweb/request.py - An http request from either CGI or the standalone server.
 #
 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
-# Copyright 2005 Matt Mackall <mpm@selenic.com>
+# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
 #
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
--- a/mercurial/hgweb/server.py	Sat Sep 02 22:58:02 2006 -0400
+++ b/mercurial/hgweb/server.py	Sat Oct 21 15:22:08 2006 -0400
@@ -1,7 +1,7 @@
 # hgweb/server.py - The standalone hg web server.
 #
 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
-# Copyright 2005 Matt Mackall <mpm@selenic.com>
+# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
 #
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
@@ -30,9 +30,9 @@
         self.handler = handler
     def flush(self):
         pass
-    def write(str):
+    def write(self, str):
         self.writelines(str.split('\n'))
-    def writelines(seq):
+    def writelines(self, seq):
         for msg in seq:
             self.handler.log_error("HG error:  %s", msg)
 
@@ -71,7 +71,7 @@
         env['REQUEST_METHOD'] = self.command
         env['SERVER_NAME'] = self.server.server_name
         env['SERVER_PORT'] = str(self.server.server_port)
-        env['REQUEST_URI'] = "/"
+        env['REQUEST_URI'] = self.path
         env['PATH_INFO'] = path_info
         if query:
             env['QUERY_STRING'] = query
@@ -207,7 +207,8 @@
                 hgwebobj = self.repoviewmaker(repo.__class__(repo.ui,
                                                              repo.origroot))
             else:
-                raise hg.RepoError(_('no repo found'))
+                raise hg.RepoError(_("There is no Mercurial repository here"
+                                     " (.hg not found)"))
             return hgwebobj
 
     class IPv6HTTPServer(MercurialHTTPServer):
--- a/mercurial/httprangereader.py	Sat Sep 02 22:58:02 2006 -0400
+++ b/mercurial/httprangereader.py	Sat Oct 21 15:22:08 2006 -0400
@@ -1,6 +1,6 @@
 # httprangereader.py - just what it says
 #
-# Copyright 2005 Matt Mackall <mpm@selenic.com>
+# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
 #
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
--- a/mercurial/httprepo.py	Sat Sep 02 22:58:02 2006 -0400
+++ b/mercurial/httprepo.py	Sat Oct 21 15:22:08 2006 -0400
@@ -1,6 +1,7 @@
 # httprepo.py - HTTP repository proxy classes for mercurial
 #
-# Copyright 2005 Matt Mackall <mpm@selenic.com>
+# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
+# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
 #
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
@@ -130,7 +131,7 @@
         self.ui = ui
 
         proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
-        proxyauthinfo = None
+        # XXX proxyauthinfo = None
         handler = httphandler()
 
         if proxyurl:
@@ -164,7 +165,8 @@
                                                proxyuser, proxypasswd or ''),
                     proxypath, proxyquery, proxyfrag))
                 handler = urllib2.ProxyHandler({scheme: proxyurl})
-                ui.debug(_('proxying through %s\n') % proxyurl)
+                ui.debug(_('proxying through http://%s:%s\n') %
+                          (proxyhost, proxyport))
 
         # urllib2 takes proxy values from the environment and those
         # will take precedence if found, so drop them
@@ -228,6 +230,9 @@
             self.ui.debug(_('http error while sending %s command\n') % cmd)
             self.ui.print_exc()
             raise IOError(None, inst)
+        except IndexError:
+            # this only happens with Python 2.3, later versions raise URLError
+            raise util.Abort(_('http error, possibly caused by proxy setting'))
         try:
             proto = resp.getheader('content-type')
         except AttributeError:
@@ -256,6 +261,13 @@
             # if using keepalive, allow connection to be reused
             fp.close()
 
+    def lookup(self, key):
+        d = self.do_cmd("lookup", key = key).read()
+        success, data = d[:-1].split(' ', 1)
+        if int(success):
+            return bin(data)
+        raise hg.RepoError(data)
+
     def heads(self):
         d = self.do_read("heads")
         try:
@@ -287,7 +299,6 @@
     def changegroup(self, nodes, kind):
         n = " ".join(map(hex, nodes))
         f = self.do_cmd("changegroup", roots=n)
-        bytes = 0
 
         def zgenerator(f):
             zd = zlib.decompressobj()
@@ -300,6 +311,22 @@
 
         return util.chunkbuffer(zgenerator(util.filechunkiter(f)))
 
+    def changegroupsubset(self, bases, heads, source):
+        baselst = " ".join([hex(n) for n in bases])
+        headlst = " ".join([hex(n) for n in heads])
+        f = self.do_cmd("changegroupsubset", bases=baselst, heads=headlst)
+
+        def zgenerator(f):
+            zd = zlib.decompressobj()
+            try:
+                for chnk in f:
+                    yield zd.decompress(chnk)
+            except httplib.HTTPException:
+                raise IOError(None, _('connection ended unexpectedly'))
+            yield zd.flush()
+
+        return util.chunkbuffer(zgenerator(util.filechunkiter(f)))
+
     def unbundle(self, cg, heads, source):
         # have to stream bundle to a temp file because we do not have
         # http 1.1 chunked transfer.
@@ -324,7 +351,7 @@
                     rfp.close()
             except socket.error, err:
                 if err[0] in (errno.ECONNRESET, errno.EPIPE):
-                    raise util.Abort(_('push failed: %s'), err[1])
+                    raise util.Abort(_('push failed: %s') % err[1])
                 raise util.Abort(err[1])
         finally:
             fp.close()
--- a/mercurial/i18n.py	Sat Sep 02 22:58:02 2006 -0400
+++ b/mercurial/i18n.py	Sat Oct 21 15:22:08 2006 -0400
@@ -1,7 +1,7 @@
 """
 i18n.py - internationalization support for mercurial
 
-Copyright 2005 Matt Mackall <mpm@selenic.com>
+Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
 
 This software may be used and distributed according to the terms
 of the GNU General Public License, incorporated herein by reference.
--- a/mercurial/localrepo.py	Sat Sep 02 22:58:02 2006 -0400
+++ b/mercurial/localrepo.py	Sat Oct 21 15:22:08 2006 -0400
@@ -1,6 +1,6 @@
 # localrepo.py - read/write repository class for mercurial
 #
-# Copyright 2005 Matt Mackall <mpm@selenic.com>
+# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
 #
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
@@ -15,7 +15,7 @@
 demandload(globals(), "os revlog time util")
 
 class localrepository(repo.repository):
-    capabilities = ()
+    capabilities = ('lookup', 'changegroupsubset')
 
     def __del__(self):
         self.transhandle = None
@@ -27,12 +27,21 @@
                 oldp = p
                 p = os.path.dirname(p)
                 if p == oldp:
-                    raise repo.RepoError(_("no repo found"))
+                    raise repo.RepoError(_("There is no Mercurial repository"
+                                           " here (.hg not found)"))
             path = p
         self.path = os.path.join(path, ".hg")
 
-        if not create and not os.path.isdir(self.path):
-            raise repo.RepoError(_("repository %s not found") % path)
+        if not os.path.isdir(self.path):
+            if create:
+                if not os.path.exists(path):
+                    os.mkdir(path)
+                os.mkdir(self.path)
+                os.mkdir(self.join("data"))
+            else:
+                raise repo.RepoError(_("repository %s not found") % path)
+        elif create:
+            raise repo.RepoError(_("repository %s already exists") % path)
 
         self.root = os.path.abspath(path)
         self.origroot = path
@@ -45,7 +54,7 @@
         except IOError:
             pass
 
-        v = self.ui.revlogopts
+        v = self.ui.configrevlog()
         self.revlogversion = int(v.get('format', revlog.REVLOG_DEFAULT_FORMAT))
         self.revlogv1 = self.revlogversion != revlog.REVLOGV0
         fl = v.get('flags', None)
@@ -70,17 +79,12 @@
         self.revlogversion = v
 
         self.tagscache = None
+        self.branchcache = None
         self.nodetagscache = None
         self.encodepats = None
         self.decodepats = None
         self.transhandle = None
 
-        if create:
-            if not os.path.exists(path):
-                os.mkdir(path)
-            os.mkdir(self.path)
-            os.mkdir(self.join("data"))
-
         self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root)
 
     def url(self):
@@ -131,7 +135,7 @@
             except Exception, exc:
                 if isinstance(exc, util.Abort):
                     self.ui.warn(_('error: %s hook failed: %s\n') %
-                                 (hname, exc.args[0] % exc.args[1:]))
+                                 (hname, exc.args[0]))
                 else:
                     self.ui.warn(_('error: %s hook raised an exception: '
                                    '%s\n') % (hname, exc))
@@ -169,7 +173,7 @@
 
     tag_disallowed = ':\r\n'
 
-    def tag(self, name, node, local=False, message=None, user=None, date=None):
+    def tag(self, name, node, message, local, user, date):
         '''tag a revision with a symbolic name.
 
         if local is True, the tag is stored in a per-repository file.
@@ -191,27 +195,24 @@
             if c in name:
                 raise util.Abort(_('%r cannot be used in a tag name') % c)
 
-        self.hook('pretag', throw=True, node=node, tag=name, local=local)
+        self.hook('pretag', throw=True, node=hex(node), tag=name, local=local)
 
         if local:
-            self.opener('localtags', 'a').write('%s %s\n' % (node, name))
-            self.hook('tag', node=node, tag=name, local=local)
+            self.opener('localtags', 'a').write('%s %s\n' % (hex(node), name))
+            self.hook('tag', node=hex(node), tag=name, local=local)
             return
 
-        for x in self.changes():
+        for x in self.status()[:5]:
             if '.hgtags' in x:
                 raise util.Abort(_('working copy of .hgtags is changed '
                                    '(please commit .hgtags manually)'))
 
-        self.wfile('.hgtags', 'ab').write('%s %s\n' % (node, name))
+        self.wfile('.hgtags', 'ab').write('%s %s\n' % (hex(node), name))
         if self.dirstate.state('.hgtags') == '?':
             self.add(['.hgtags'])
 
-        if not message:
-            message = _('Added tag %s for changeset %s') % (name, node)
-
         self.commit(['.hgtags'], message, user, date)
-        self.hook('tag', node=node, tag=name, local=local)
+        self.hook('tag', node=hex(node), tag=name, local=local)
 
     def tags(self):
         '''return a mapping of tag to node'''
@@ -288,18 +289,64 @@
                 self.nodetagscache.setdefault(n, []).append(t)
         return self.nodetagscache.get(node, [])
 
+    def branchtags(self):
+        if self.branchcache != None:
+            return self.branchcache
+
+        self.branchcache = {} # avoid recursion in changectx
+
+        try:
+            f = self.opener("branches.cache")
+            last, lrev = f.readline().rstrip().split(" ", 1)
+            last, lrev = bin(last), int(lrev)
+            if (lrev < self.changelog.count() and
+                self.changelog.node(lrev) == last): # sanity check
+                for l in f:
+                    node, label = l.rstrip().split(" ", 1)
+                    self.branchcache[label] = bin(node)
+            else: # invalidate the cache
+                last, lrev = nullid, -1
+            f.close()
+        except IOError:
+            last, lrev = nullid, -1
+
+        tip = self.changelog.count() - 1
+        if lrev != tip:
+            for r in xrange(lrev + 1, tip + 1):
+                c = self.changectx(r)
+                b = c.branch()
+                if b:
+                    self.branchcache[b] = c.node()
+            self._writebranchcache()
+
+        return self.branchcache
+
+    def _writebranchcache(self):
+        try:
+            f = self.opener("branches.cache", "w")
+            t = self.changelog.tip()
+            f.write("%s %s\n" % (hex(t), self.changelog.count() - 1))
+            for label, node in self.branchcache.iteritems():
+                f.write("%s %s\n" % (hex(node), label))
+        except IOError:
+            pass
+
     def lookup(self, key):
-        try:
+        if key == '.':
+            key = self.dirstate.parents()[0]
+            if key == nullid:
+                raise repo.RepoError(_("no revision checked out"))
+        n = self.changelog._match(key)
+        if n:
+            return n
+        if key in self.tags():
             return self.tags()[key]
-        except KeyError:
-            if key == '.':
-                key = self.dirstate.parents()[0]
-                if key == nullid:
-                    raise repo.RepoError(_("no revision checked out"))
-            try:
-                return self.changelog.lookup(key)
-            except:
-                raise repo.RepoError(_("unknown revision '%s'") % key)
+        if key in self.branchtags():
+            return self.branchtags()[key]
+        n = self.changelog._partialmatch(key)
+        if n:
+            return n
+        raise repo.RepoError(_("unknown revision '%s'") % key)
 
     def dev(self):
         return os.lstat(self.path).st_dev
@@ -318,9 +365,25 @@
             f = f[1:]
         return filelog.filelog(self.opener, f, self.revlogversion)
 
-    def changectx(self, changeid):
+    def changectx(self, changeid=None):
         return context.changectx(self, changeid)
 
+    def workingctx(self):
+        return context.workingctx(self)
+
+    def parents(self, changeid=None):
+        '''
+        get list of changectxs for parents of changeid or working directory
+        '''
+        if changeid is None:
+            pl = self.dirstate.parents()
+        else:
+            n = self.changelog.lookup(changeid)
+            pl = self.changelog.parents(n)
+        if pl[1] == nullid:
+            return [self.changectx(pl[0])]
+        return [self.changectx(pl[0]), self.changectx(pl[1])]
+
     def filectx(self, path, changeid=None, fileid=None):
         """changeid can be a changeset revision, node, or tag.
            fileid can be a file revision or node."""
@@ -445,24 +508,45 @@
                             self.wreload,
                             desc=_('working directory of %s') % self.origroot)
 
-    def checkfilemerge(self, filename, text, filelog, manifest1, manifest2):
-        "determine whether a new filenode is needed"
-        fp1 = manifest1.get(filename, nullid)
-        fp2 = manifest2.get(filename, nullid)
+    def filecommit(self, fn, manifest1, manifest2, linkrev, transaction, changelist):
+        """
+        commit an individual file as part of a larger transaction
+        """
+
+        t = self.wread(fn)
+        fl = self.file(fn)
+        fp1 = manifest1.get(fn, nullid)
+        fp2 = manifest2.get(fn, nullid)
 
-        if fp2 != nullid:
+        meta = {}
+        cp = self.dirstate.copied(fn)
+        if cp:
+            meta["copy"] = cp
+            if not manifest2: # not a branch merge
+                meta["copyrev"] = hex(manifest1.get(cp, nullid))
+                fp2 = nullid
+            elif fp2 != nullid: # copied on remote side
+                meta["copyrev"] = hex(manifest1.get(cp, nullid))
+            else: # copied on local side, reversed
+                meta["copyrev"] = hex(manifest2.get(cp))
+                fp2 = nullid
+            self.ui.debug(_(" %s: copy %s:%s\n") %
+                          (fn, cp, meta["copyrev"]))
+            fp1 = nullid
+        elif fp2 != nullid:
             # is one parent an ancestor of the other?
-            fpa = filelog.ancestor(fp1, fp2)
+            fpa = fl.ancestor(fp1, fp2)
             if fpa == fp1:
                 fp1, fp2 = fp2, nullid
             elif fpa == fp2:
                 fp2 = nullid
 
             # is the file unmodified from the parent? report existing entry
-            if fp2 == nullid and text == filelog.read(fp1):
-                return (fp1, None, None)
+            if fp2 == nullid and not fl.cmp(fp1, t):
+                return fp1
 
-        return (None, fp1, fp2)
+        changelist.append(fn)
+        return fl.add(t, meta, transaction, linkrev, fp1, fp2)
 
     def rawcommit(self, files, text, user, date, p1=None, p2=None, wlock=None):
         orig_parent = self.dirstate.parents()[0] or nullid
@@ -473,6 +557,7 @@
         m1 = self.manifest.read(c1[0]).copy()
         m2 = self.manifest.read(c2[0])
         changed = []
+        removed = []
 
         if orig_parent == p1:
             update_dirstate = 1
@@ -486,32 +571,22 @@
         linkrev = self.changelog.count()
         for f in files:
             try:
-                t = self.wread(f)
+                m1[f] = self.filecommit(f, m1, m2, linkrev, tr, changed)
                 m1.set(f, util.is_exec(self.wjoin(f), m1.execf(f)))
-                r = self.file(f)
-
-                (entry, fp1, fp2) = self.checkfilemerge(f, t, r, m1, m2)
-                if entry:
-                    m1[f] = entry
-                    continue
-
-                m1[f] = r.add(t, {}, tr, linkrev, fp1, fp2)
-                changed.append(f)
-                if update_dirstate:
-                    self.dirstate.update([f], "n")
             except IOError:
                 try:
                     del m1[f]
-                    del m1[f]
                     if update_dirstate:
                         self.dirstate.forget([f])
+                    removed.append(f)
                 except:
                     # deleted from p2?
                     pass
 
         mnode = self.manifest.add(m1, tr, linkrev, c1[0], c2[0])
         user = user or self.ui.username()
-        n = self.changelog.add(mnode, changed, text, tr, p1, p2, user, date)
+        n = self.changelog.add(mnode, changed + removed, text,
+                               tr, p1, p2, user, date)
         tr.close()
         if update_dirstate:
             self.dirstate.setparents(n, nullid)
@@ -533,7 +608,7 @@
                 else:
                     self.ui.warn(_("%s not tracked!\n") % f)
         else:
-            modified, added, removed, deleted, unknown = self.changes(match=match)
+            modified, added, removed, deleted, unknown = self.status(match=match)[:5]
             commit = modified + added
             remove = removed
 
@@ -543,7 +618,11 @@
         m1 = self.manifest.read(c1[0]).copy()
         m2 = self.manifest.read(c2[0])
 
-        if not commit and not remove and not force and p2 == nullid:
+        branchname = self.workingctx().branch()
+        oldname = c1[5].get("branch", "")
+
+        if not commit and not remove and not force and p2 == nullid and \
+               branchname == oldname:
             self.ui.status(_("nothing changed\n"))
             return None
 
@@ -566,39 +645,18 @@
         for f in commit:
             self.ui.note(f + "\n")
             try:
+                new[f] = self.filecommit(f, m1, m2, linkrev, tr, changed)
                 m1.set(f, util.is_exec(self.wjoin(f), m1.execf(f)))
-                t = self.wread(f)
             except IOError:
                 self.ui.warn(_("trouble committing %s!\n") % f)
                 raise
 
-            r = self.file(f)
-
-            meta = {}
-            cp = self.dirstate.copied(f)
-            if cp:
-                meta["copy"] = cp
-                meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
-                self.ui.debug(_(" %s: copy %s:%s\n") % (f, cp, meta["copyrev"]))
-                fp1, fp2 = nullid, nullid
-            else:
-                entry, fp1, fp2 = self.checkfilemerge(f, t, r, m1, m2)
-                if entry:
-                    new[f] = entry
-                    continue
-
-            new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
-            # remember what we've added so that we can later calculate
-            # the files to pull from a set of changesets
-            changed.append(f)
-
         # update manifest
         m1.update(new)
         for f in remove:
             if f in m1:
                 del m1[f]
-        mn = self.manifest.add(m1, tr, linkrev, c1[0], c2[0],
-                               (new, remove))
+        mn = self.manifest.add(m1, tr, linkrev, c1[0], c2[0], (new, remove))
 
         # add changeset
         new = new.keys()
@@ -629,7 +687,11 @@
         if not lines:
             return None
         text = '\n'.join(lines)
-        n = self.changelog.add(mn, changed + remove, text, tr, p1, p2, user, date)
+        extra = {}
+        if branchname:
+            extra["branch"] = branchname
+        n = self.changelog.add(mn, changed + remove, text, tr, p1, p2,
+                               user, date, extra)
         self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
                   parent2=xp2)
         tr.close()
@@ -645,7 +707,11 @@
         if node:
             fdict = dict.fromkeys(files)
             for fn in self.manifest.read(self.changelog.read(node)[0]):
-                fdict.pop(fn, None)
+                for ffn in fdict:
+                    # match if the file is the exact name or a directory
+                    if ffn == fn or fn.startswith("%s/" % ffn):
+                        del fdict[ffn]
+                        break
                 if match(fn):
                     yield 'm', fn
             for fn in fdict:
@@ -669,12 +735,11 @@
 
         def fcmp(fn, mf):
             t1 = self.wread(fn)
-            t2 = self.file(fn).read(mf.get(fn, nullid))
-            return cmp(t1, t2)
+            return self.file(fn).cmp(mf.get(fn, nullid), t1)
 
         def mfmatches(node):
             change = self.changelog.read(node)
-            mf = dict(self.manifest.read(change[0]))
+            mf = self.manifest.read(change[0]).copy()
             for fn in mf.keys():
                 if not match(fn):
                     del mf[fn]
@@ -712,14 +777,18 @@
                     for f in lookup:
                         if fcmp(f, mf2):
                             modified.append(f)
-                        elif wlock is not None:
-                            self.dirstate.update([f], "n")
+                        else:
+                            clean.append(f)
+                            if wlock is not None:
+                                self.dirstate.update([f], "n")
             else:
                 # we are comparing working dir against non-parent
                 # generate a pseudo-manifest for the working dir
+                # XXX: create it in dirstate.py ?
                 mf2 = mfmatches(self.dirstate.parents()[0])
                 for f in lookup + modified + added:
                     mf2[f] = ""
+                    mf2.set(f, execf=util.is_exec(self.wjoin(f), mf2.execf(f)))
                 for f in removed:
                     if f in mf2:
                         del mf2[f]
@@ -737,7 +806,8 @@
             mf2keys.sort()
             for fn in mf2keys:
                 if mf1.has_key(fn):
-                    if mf1[fn] != mf2[fn] and (mf2[fn] != "" or fcmp(fn, mf1)):
+                    if mf1.flags(fn) != mf2.flags(fn) or \
+                       (mf1[fn] != mf2[fn] and (mf2[fn] != "" or fcmp(fn, mf1))):
                         modified.append(fn)
                     elif list_clean:
                         clean.append(fn)
@@ -752,16 +822,6 @@
             l.sort()
         return (modified, added, removed, deleted, unknown, ignored, clean)
 
-    def changes(self, node1=None, node2=None, files=[], match=util.always,
-                wlock=None, list_ignored=False, list_clean=False):
-        '''DEPRECATED - use status instead'''
-        marduit = self.status(node1, node2, files, match, wlock,
-                              list_ignored, list_clean)
-        if list_ignored:
-            return marduit[:-1]
-        else:
-            return marduit[:-2]
-
     def add(self, list, wlock=None):
         if not wlock:
             wlock = self.wlock()
@@ -1115,7 +1175,7 @@
             else:
                 raise util.Abort(_("repository is unrelated"))
 
-        self.ui.note(_("found new changesets starting at ") +
+        self.ui.debug(_("found new changesets starting at ") +
                      " ".join([short(f) for f in fetch]) + "\n")
 
         self.ui.debug(_("%d total queries\n") % reqcnt)
@@ -1188,6 +1248,8 @@
             if heads is None:
                 cg = remote.changegroup(fetch, 'pull')
             else:
+                if 'changegroupsubset' not in remote.capabilities:
+                    raise util.Abort(_("Partial pull cannot be done because other repository doesn't support changegroupsubset."))
                 cg = remote.changegroupsubset(fetch, heads, 'pull')
             return self.addchangegroup(cg, 'pull', remote.url())
         finally:
@@ -1753,6 +1815,6 @@
 
 def instance(ui, path, create):
     return localrepository(ui, util.drop_scheme('file', path), create)
-    
+
 def islocal(path):
     return True
--- a/mercurial/lock.py	Sat Sep 02 22:58:02 2006 -0400
+++ b/mercurial/lock.py	Sat Oct 21 15:22:08 2006 -0400
@@ -1,6 +1,6 @@
 # lock.py - simple locking scheme for mercurial
 #
-# Copyright 2005 Matt Mackall <mpm@selenic.com>
+# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
 #
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/mail.py	Sat Oct 21 15:22:08 2006 -0400
@@ -0,0 +1,68 @@
+# mail.py - mail sending bits for mercurial
+#
+# Copyright 2006 Matt Mackall <mpm@selenic.com>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+from i18n import gettext as _
+from demandload import *
+demandload(globals(), "os re smtplib templater util")
+
+def _smtp(ui):
+    '''send mail using smtp.'''
+
+    local_hostname = ui.config('smtp', 'local_hostname')
+    s = smtplib.SMTP(local_hostname=local_hostname)
+    mailhost = ui.config('smtp', 'host')
+    if not mailhost:
+        raise util.Abort(_('no [smtp]host in hgrc - cannot send mail'))
+    mailport = int(ui.config('smtp', 'port', 25))
+    ui.note(_('sending mail: smtp host %s, port %s\n') %
+            (mailhost, mailport))
+    s.connect(host=mailhost, port=mailport)
+    if ui.configbool('smtp', 'tls'):
+        ui.note(_('(using tls)\n'))
+        s.ehlo()
+        s.starttls()
+        s.ehlo()
+    username = ui.config('smtp', 'username')
+    password = ui.config('smtp', 'password')
+    if username and password:
+        ui.note(_('(authenticating to mail server as %s)\n') %
+                  (username))
+        s.login(username, password)
+    return s
+
+class _sendmail(object):
+    '''send mail using sendmail.'''
+
+    def __init__(self, ui, program):
+        self.ui = ui
+        self.program = program
+
+    def sendmail(self, sender, recipients, msg):
+        cmdline = '%s -f %s %s' % (
+            self.program, templater.email(sender),
+            ' '.join(map(templater.email, recipients)))
+        self.ui.note(_('sending mail: %s\n') % cmdline)
+        fp = os.popen(cmdline, 'w')
+        fp.write(msg)
+        ret = fp.close()
+        if ret:
+            raise util.Abort('%s %s' % (
+                os.path.basename(self.program.split(None, 1)[0]),
+                util.explain_exit(ret)[0]))
+
+def connect(ui):
+    '''make a mail connection. object returned has one method, sendmail.
+    call as sendmail(sender, list-of-recipients, msg).'''
+
+    method = ui.config('email', 'method', 'smtp')
+    if method == 'smtp':
+        return _smtp(ui)
+
+    return _sendmail(ui, method)
+
+def sendmail(ui, sender, recipients, msg):
+    return connect(ui).sendmail(sender, recipients, msg)
--- a/mercurial/manifest.py	Sat Sep 02 22:58:02 2006 -0400
+++ b/mercurial/manifest.py	Sat Oct 21 15:22:08 2006 -0400
@@ -1,6 +1,6 @@
 # manifest.py - manifest revision class for mercurial
 #
-# Copyright 2005 Matt Mackall <mpm@selenic.com>
+# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
 #
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
@@ -9,9 +9,12 @@
 from i18n import gettext as _
 from demandload import *
 demandload(globals(), "array bisect struct")
+demandload(globals(), "mdiff")
 
 class manifestdict(dict):
-    def __init__(self, mapping={}, flags={}):
+    def __init__(self, mapping=None, flags=None):
+        if mapping is None: mapping = {}
+        if flags is None: flags = {}
         dict.__init__(self, mapping)
         self._flags = flags
     def flags(self, f):
@@ -27,8 +30,9 @@
         fl = entry[40:-1]
         if fl: self._flags[f] = fl
     def set(self, f, execf=False, linkf=False):
-        if execf: self._flags[f] = "x"
-        if linkf: self._flags[f] = "x"
+        if linkf: self._flags[f] = "l"
+        elif execf: self._flags[f] = "x"
+        else: self._flags[f] = ""
     def copy(self):
         return manifestdict(dict.copy(self), dict.copy(self._flags))
 
@@ -39,23 +43,29 @@
         revlog.__init__(self, opener, "00manifest.i", "00manifest.d",
                         defversion)
 
+    def parselines(self, lines):
+        for l in lines.splitlines(1):
+            yield l.split('\0')
+
+    def readdelta(self, node):
+        delta = mdiff.patchtext(self.delta(node))
+        deltamap = manifestdict()
+        for f, n in self.parselines(delta):
+            deltamap.rawset(f, n)
+        return deltamap
+
     def read(self, node):
         if node == nullid: return manifestdict() # don't upset local cache
         if self.mapcache and self.mapcache[0] == node:
             return self.mapcache[1]
         text = self.revision(node)
         self.listcache = array.array('c', text)
-        lines = text.splitlines(1)
         mapping = manifestdict()
-        for l in lines:
-            (f, n) = l.split('\0')
+        for f, n in self.parselines(text):
             mapping.rawset(f, n)
         self.mapcache = (node, mapping)
         return mapping
 
-    def diff(self, a, b):
-        return mdiff.textdiff(str(a), str(b))
-
     def _search(self, m, s, lo=0, hi=None):
         '''return a tuple (start, end) that says where to find s within m.
 
@@ -169,7 +179,7 @@
                 if start == end and w[1] == 1:
                     # item we want to delete was not found, error out
                     raise AssertionError(
-                            _("failed to remove %s from manifest\n") % f)
+                            _("failed to remove %s from manifest") % f)
                 if dstart != None and dstart <= start and dend >= start:
                     if dend < end:
                         dend = end
--- a/mercurial/mdiff.py	Sat Sep 02 22:58:02 2006 -0400
+++ b/mercurial/mdiff.py	Sat Oct 21 15:22:08 2006 -0400
@@ -1,6 +1,6 @@
 # mdiff.py - diff and patch routines for mercurial
 #
-# Copyright 2005 Matt Mackall <mpm@selenic.com>
+# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
 #
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
@@ -19,44 +19,73 @@
             lines[-1] = lines[-1][:-1]
     return lines
 
-def unidiff(a, ad, b, bd, fn, r=None, text=False,
-            showfunc=False, ignorews=False, ignorewsamount=False,
-            ignoreblanklines=False):
+class diffopts(object):
+    '''context is the number of context lines
+    text treats all files as text
+    showfunc enables diff -p output
+    git enables the git extended patch format
+    nodates removes dates from diff headers
+    ignorews ignores all whitespace changes in the diff
+    ignorewsamount ignores changes in the amount of whitespace
+    ignoreblanklines ignores changes whose lines are all blank'''
+
+    defaults = {
+        'context': 3,
+        'text': False,
+        'showfunc': True,
+        'git': False,
+        'nodates': False,
+        'ignorews': False,
+        'ignorewsamount': False,
+        'ignoreblanklines': False,
+        }
+
+    __slots__ = defaults.keys()
+
+    def __init__(self, **opts):
+        for k in self.__slots__:
+            v = opts.get(k)
+            if v is None:
+                v = self.defaults[k]
+            setattr(self, k, v)
+
+defaultopts = diffopts()
+
+def unidiff(a, ad, b, bd, fn, r=None, opts=defaultopts):
+    def datetag(date):
+        return (opts.git or opts.nodates) and '\n' or '\t%s\n' % date
 
     if not a and not b: return ""
     epoch = util.datestr((0, 0))
 
-    if not text and (util.binary(a) or util.binary(b)):
+    if not opts.text and (util.binary(a) or util.binary(b)):
         l = ['Binary file %s has changed\n' % fn]
     elif not a:
         b = splitnewlines(b)
         if a is None:
-            l1 = "--- %s\t%s\n" % ("/dev/null", epoch)
+            l1 = '--- /dev/null%s' % datetag(epoch)
         else:
-            l1 = "--- %s\t%s\n" % ("a/" + fn, ad)
-        l2 = "+++ %s\t%s\n" % ("b/" + fn, bd)
+            l1 = "--- %s%s" % ("a/" + fn, datetag(ad))
+        l2 = "+++ %s%s" % ("b/" + fn, datetag(bd))
         l3 = "@@ -0,0 +1,%d @@\n" % len(b)
         l = [l1, l2, l3] + ["+" + e for e in b]
     elif not b:
         a = splitnewlines(a)
-        l1 = "--- %s\t%s\n" % ("a/" + fn, ad)
+        l1 = "--- %s%s" % ("a/" + fn, datetag(ad))
         if b is None:
-            l2 = "+++ %s\t%s\n" % ("/dev/null", epoch)
+            l2 = '+++ /dev/null%s' % datetag(epoch)
         else:
-            l2 = "+++ %s\t%s\n" % ("b/" + fn, bd)
+            l2 = "+++ %s%s" % ("b/" + fn, datetag(bd))
         l3 = "@@ -1,%d +0,0 @@\n" % len(a)
         l = [l1, l2, l3] + ["-" + e for e in a]
     else:
         al = splitnewlines(a)
         bl = splitnewlines(b)
-        l = list(bunidiff(a, b, al, bl, "a/" + fn, "b/" + fn,
-                          showfunc=showfunc, ignorews=ignorews,
-                          ignorewsamount=ignorewsamount,
-                          ignoreblanklines=ignoreblanklines))
+        l = list(bunidiff(a, b, al, bl, "a/" + fn, "b/" + fn, opts=opts))
         if not l: return ""
         # difflib uses a space, rather than a tab
-        l[0] = "%s\t%s\n" % (l[0][:-2], ad)
-        l[1] = "%s\t%s\n" % (l[1][:-2], bd)
+        l[0] = "%s%s" % (l[0][:-2], datetag(ad))
+        l[1] = "%s%s" % (l[1][:-2], datetag(bd))
 
     for ln in xrange(len(l)):
         if l[ln][-1] != '\n':
@@ -72,21 +101,15 @@
 # t1 and t2 are the text to be diffed
 # l1 and l2 are the text broken up into lines
 # header1 and header2 are the filenames for the diff output
-# context is the number of context lines
-# showfunc enables diff -p output
-# ignorews ignores all whitespace changes in the diff
-# ignorewsamount ignores changes in the amount of whitespace
-# ignoreblanklines ignores changes whose lines are all blank
-def bunidiff(t1, t2, l1, l2, header1, header2, context=3, showfunc=False,
-             ignorews=False, ignorewsamount=False, ignoreblanklines=False):
+def bunidiff(t1, t2, l1, l2, header1, header2, opts=defaultopts):
     def contextend(l, len):
-        ret = l + context
+        ret = l + opts.context
         if ret > len:
             ret = len
         return ret
 
     def contextstart(l):
-        ret = l - context
+        ret = l - opts.context
         if ret < 0:
             return 0
         return ret
@@ -101,7 +124,7 @@
         blen = b2 - bstart + aend - a2
 
         func = ""
-        if showfunc:
+        if opts.showfunc:
             # walk backwards from the start of the context
             # to find a line starting with an alphanumeric char.
             for x in xrange(astart, -1, -1):
@@ -119,14 +142,14 @@
 
     header = [ "--- %s\t\n" % header1, "+++ %s\t\n" % header2 ]
 
-    if showfunc:
+    if opts.showfunc:
         funcre = re.compile('\w')
-    if ignorewsamount:
+    if opts.ignorewsamount:
         wsamountre = re.compile('[ \t]+')
         wsappendedre = re.compile(' \n')
-    if ignoreblanklines:
+    if opts.ignoreblanklines:
         wsblanklinesre = re.compile('\n')
-    if ignorews:
+    if opts.ignorews:
         wsre = re.compile('[ \t]')
 
     # bdiff.blocks gives us the matching sequences in the files.  The loop
@@ -159,13 +182,13 @@
         if not old and not new:
             continue
 
-        if ignoreblanklines:
+        if opts.ignoreblanklines:
             wsold = wsblanklinesre.sub('', "".join(old))
             wsnew = wsblanklinesre.sub('', "".join(new))
             if wsold == wsnew:
                 continue
 
-        if ignorewsamount:
+        if opts.ignorewsamount:
             wsold = wsamountre.sub(' ', "".join(old))
             wsold = wsappendedre.sub('\n', wsold)
             wsnew = wsamountre.sub(' ', "".join(new))
@@ -173,7 +196,7 @@
             if wsold == wsnew:
                 continue
 
-        if ignorews:
+        if opts.ignorews:
             wsold = wsre.sub('', "".join(old))
             wsnew = wsre.sub('', "".join(new))
             if wsold == wsnew:
@@ -184,7 +207,7 @@
         prev = None
         if hunk:
             # join with the previous hunk if it falls inside the context
-            if astart < hunk[1] + context + 1:
+            if astart < hunk[1] + opts.context + 1:
                 prev = hunk
                 astart = hunk[1]
                 bstart = hunk[3]
--- a/mercurial/merge.py	Sat Sep 02 22:58:02 2006 -0400
+++ b/mercurial/merge.py	Sat Oct 21 15:22:08 2006 -0400
@@ -8,327 +8,411 @@
 from node import *
 from i18n import gettext as _
 from demandload import *
-demandload(globals(), "util os tempfile")
+demandload(globals(), "errno util os tempfile")
+
+def filemerge(repo, fw, fo, wctx, mctx):
+    """perform a 3-way merge in the working directory
 
-def merge3(repo, fn, my, other, p1, p2):
-    """perform a 3-way merge in the working directory"""
+    fw = filename in the working directory
+    fo = filename in other parent
+    wctx, mctx = working and merge changecontexts
+    """
 
-    def temp(prefix, node):
-        pre = "%s~%s." % (os.path.basename(fn), prefix)
+    def temp(prefix, ctx):
+        pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
         (fd, name) = tempfile.mkstemp(prefix=pre)
         f = os.fdopen(fd, "wb")
-        repo.wwrite(fn, fl.read(node), f)
+        repo.wwrite(ctx.path(), ctx.data(), f)
         f.close()
         return name
 
-    fl = repo.file(fn)
-    base = fl.ancestor(my, other)
-    a = repo.wjoin(fn)
-    b = temp("base", base)
-    c = temp("other", other)
+    fcm = wctx.filectx(fw)
+    fco = mctx.filectx(fo)
+
+    if not fco.cmp(fcm.data()): # files identical?
+        return None
 
-    repo.ui.note(_("resolving %s\n") % fn)
-    repo.ui.debug(_("file %s: my %s other %s ancestor %s\n") %
-                          (fn, short(my), short(other), short(base)))
+    fca = fcm.ancestor(fco)
+    if not fca:
+        fca = repo.filectx(fw, fileid=-1)
+    a = repo.wjoin(fw)
+    b = temp("base", fca)
+    c = temp("other", fco)
+
+    if fw != fo:
+        repo.ui.status(_("merging %s and %s\n") % (fw, fo))
+    else:
+        repo.ui.status(_("merging %s\n") % fw)
+
+    repo.ui.debug(_("my %s other %s ancestor %s\n") % (fcm, fco, fca))
 
     cmd = (os.environ.get("HGMERGE") or repo.ui.config("ui", "merge")
            or "hgmerge")
     r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=repo.root,
-                    environ={'HG_FILE': fn,
-                             'HG_MY_NODE': p1,
-                             'HG_OTHER_NODE': p2,
-                             'HG_FILE_MY_NODE': hex(my),
-                             'HG_FILE_OTHER_NODE': hex(other),
-                             'HG_FILE_BASE_NODE': hex(base)})
+                    environ={'HG_FILE': fw,
+                             'HG_MY_NODE': str(wctx.parents()[0]),
+                             'HG_OTHER_NODE': str(mctx)})
     if r:
-        repo.ui.warn(_("merging %s failed!\n") % fn)
+        repo.ui.warn(_("merging %s failed!\n") % fw)
 
     os.unlink(b)
     os.unlink(c)
     return r
 
-def update(repo, node, branchmerge=False, force=False, partial=None,
-           wlock=None, show_stats=True, remind=True):
+def checkunknown(wctx, mctx):
+    "check for collisions between unknown files and files in mctx"
+    man = mctx.manifest()
+    for f in wctx.unknown():
+        if f in man:
+            if mctx.filectx(f).cmp(wctx.filectx(f).data()):
+                raise util.Abort(_("'%s' already exists in the working"
+                                   " dir and differs from remote") % f)
 
-    overwrite = force and not branchmerge
-    forcemerge = force and branchmerge
-
-    if not wlock:
-        wlock = repo.wlock()
+def forgetremoved(wctx, mctx):
+    """
+    Forget removed files
 
-    ### check phase
+    If we're jumping between revisions (as opposed to merging), and if
+    neither the working directory nor the target rev has the file,
+    then we need to remove it from the dirstate, to prevent the
+    dirstate from listing the file when it is no longer in the
+    manifest.
+    """
 
-    pl = repo.dirstate.parents()
-    if not overwrite and pl[1] != nullid:
-        raise util.Abort(_("outstanding uncommitted merges"))
+    action = []
+    man = mctx.manifest()
+    for f in wctx.deleted() + wctx.removed():
+        if f not in man:
+            action.append((f, "f"))
 
-    p1, p2 = pl[0], node
-    pa = repo.changelog.ancestor(p1, p2)
+    return action
+
+def nonoverlap(d1, d2, d3):
+    "Return list of elements in d1 not in d2 or d3"
 
-    # is there a linear path from p1 to p2?
-    linear_path = (pa == p1 or pa == p2)
-    if branchmerge and linear_path:
-        raise util.Abort(_("there is nothing to merge, just use "
-                           "'hg update' or look at 'hg heads'"))
+    l = []
+    for d in d1:
+        if d not in d3 and d not in d2:
+            l.append(d)
 
-    if not overwrite and not linear_path and not branchmerge:
-        raise util.Abort(_("update spans branches, use 'hg merge' "
-                           "or 'hg update -C' to lose changes"))
+    l.sort()
+    return l
+
+def findold(fctx, limit):
+    "find files that path was copied from, back to linkrev limit"
 
-    modified, added, removed, deleted, unknown = repo.changes()
-    if branchmerge and not forcemerge:
-        if modified or added or removed:
-            raise util.Abort(_("outstanding uncommitted changes"))
+    old = {}
+    orig = fctx.path()
+    visit = [fctx]
+    while visit:
+        fc = visit.pop()
+        if fc.rev() < limit:
+            continue
+        if fc.path() != orig and fc.path() not in old:
+            old[fc.path()] = 1
+        visit += fc.parents()
 
-    m1n = repo.changelog.read(p1)[0]
-    m2n = repo.changelog.read(p2)[0]
-    man = repo.manifest.ancestor(m1n, m2n)
-    m1 = repo.manifest.read(m1n)
-    m2 = repo.manifest.read(m2n).copy()
-    ma = repo.manifest.read(man)
+    old = old.keys()
+    old.sort()
+    return old
+
+def findcopies(repo, m1, m2, ma, limit):
+    """
+    Find moves and copies between m1 and m2 back to limit linkrev
+    """
+
+    if not repo.ui.configbool("merge", "followcopies", True):
+        return {}
 
-    if not forcemerge and not overwrite:
-        for f in unknown:
-            if f in m2:
-                t1 = repo.wread(f)
-                t2 = repo.file(f).read(m2[f])
-                if cmp(t1, t2) != 0:
-                    raise util.Abort(_("'%s' already exists in the working"
-                                       " dir and differs from remote") % f)
+    # avoid silly behavior for update from empty dir
+    if not m1:
+        return {}
+
+    dcopies = repo.dirstate.copies()
+    copy = {}
+    match = {}
+    u1 = nonoverlap(m1, m2, ma)
+    u2 = nonoverlap(m2, m1, ma)
+    ctx = util.cachefunc(lambda f,n: repo.filectx(f, fileid=n[:20]))
 
-    # resolve the manifest to determine which files
-    # we care about merging
-    repo.ui.note(_("resolving manifests\n"))
-    repo.ui.debug(_(" overwrite %s branchmerge %s partial %s linear %s\n") %
-                  (overwrite, branchmerge, partial and True or False, linear_path))
-    repo.ui.debug(_(" ancestor %s local %s remote %s\n") %
-                  (short(man), short(m1n), short(m2n)))
+    def checkpair(c, f2, man):
+        ''' check if an apparent pair actually matches '''
+        c2 = ctx(f2, man[f2])
+        ca = c.ancestor(c2)
+        if ca and ca.path() == c.path() or ca.path() == c2.path():
+            copy[c.path()] = f2
+            copy[f2] = c.path()
+
+    for f in u1:
+        c = ctx(dcopies.get(f, f), m1[f])
+        for of in findold(c, limit):
+            if of in m2:
+                checkpair(c, of, m2)
+            else:
+                match.setdefault(of, []).append(f)
+
+    for f in u2:
+        c = ctx(f, m2[f])
+        for of in findold(c, limit):
+            if of in m1:
+                checkpair(c, of, m1)
+            elif of in match: