changeset 5948:597d8402087d

Merge after backout
author Bryan O'Sullivan <bos@serpentine.com>
date Fri, 25 Jan 2008 16:04:46 -0800
parents 528c986f0162 (current diff) ee0dc0f3804b (diff)
children 4b8d568c65dd 92eb0a019bf2
files hgext/patchbomb.py templates/raw/header.tmpl
diffstat 82 files changed, 1619 insertions(+), 499 deletions(-) [+]
line wrap: on
line diff
--- a/contrib/churn.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/contrib/churn.py	Fri Jan 25 16:04:46 2008 -0800
@@ -114,11 +114,11 @@
         who, lines = __gather(ui, repo, node1, node2)
 
         # remap the owner if possible
-        if amap.has_key(who):
+        if who in amap:
             ui.note("using '%s' alias for '%s'\n" % (amap[who], who))
             who = amap[who]
 
-        if not stats.has_key(who):
+        if not who in stats:
             stats[who] = 0
         stats[who] += lines
 
--- a/contrib/zsh_completion	Fri Jan 25 16:04:32 2008 -0800
+++ b/contrib/zsh_completion	Fri Jan 25 16:04:46 2008 -0800
@@ -13,6 +13,9 @@
 # option) any later version.
 #
 
+emulate -LR zsh
+setopt extendedglob
+
 local curcontext="$curcontext" state line
 typeset -A _hg_cmd_globals
 
@@ -153,9 +156,9 @@
   typeset -a tags
   local tag rev
 
-  _hg_cmd tags 2> /dev/null | while read tag rev
+  _hg_cmd tags 2> /dev/null | while read tag
   do
-    tags+=($tag)
+    tags+=(${tag/ #    [0-9]#:*})
   done
   (( $#tags )) && _describe -t tags 'tags' tags
 }
@@ -674,13 +677,13 @@
 # MQ
 _hg_qseries() {
   typeset -a patches
-  patches=($(_hg_cmd qseries 2>/dev/null))
+  patches=(${(f)"$(_hg_cmd qseries 2>/dev/null)"})
   (( $#patches )) && _describe -t hg-patches 'patches' patches
 }
 
 _hg_qapplied() {
   typeset -a patches
-  patches=($(_hg_cmd qapplied 2>/dev/null))
+  patches=(${(f)"$(_hg_cmd qapplied 2>/dev/null)"})
   if (( $#patches ))
   then
     patches+=(qbase qtip)
@@ -690,7 +693,7 @@
 
 _hg_qunapplied() {
   typeset -a patches
-  patches=($(_hg_cmd qunapplied 2>/dev/null))
+  patches=(${(f)"$(_hg_cmd qunapplied 2>/dev/null)"})
   (( $#patches )) && _describe -t hg-unapplied-patches 'unapplied patches' patches
 }
 
@@ -730,6 +733,12 @@
   '*:unapplied patch:_hg_qunapplied'
 }
 
+_hg_cmd_qgoto() {
+  _arguments -s -w : $_hg_global_opts \
+  '(--force -f)'{-f,--force}'[overwrite any local changes]' \
+  ':patch:_hg_qseries'
+}
+
 _hg_cmd_qguard() {
   _arguments -s -w : $_hg_global_opts \
   '(--list -l)'{-l,--list}'[list all patches and guards]' \
--- a/hgext/convert/cvs.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/hgext/convert/cvs.py	Fri Jan 25 16:04:46 2008 -0800
@@ -53,11 +53,13 @@
             os.chdir(self.path)
             id = None
             state = 0
+            filerevids = {}
             for l in util.popen(cmd):
                 if state == 0: # header
                     if l.startswith("PatchSet"):
                         id = l[9:-2]
                         if maxrev and int(id) > maxrev:
+                            # ignore everything
                             state = 3
                     elif l.startswith("Date"):
                         date = util.parsedate(l[6:-1], ["%Y/%m/%d %H:%M:%S"])
@@ -68,7 +70,8 @@
                         self.lastbranch[branch] = id
                     elif l.startswith("Ancestor branch"):
                         ancestor = l[17:-1]
-                        self.parent[id] = self.lastbranch[ancestor]
+                        # figure out the parent later
+                        self.parent[id] = None
                     elif l.startswith("Author"):
                         author = self.recode(l[8:-1])
                     elif l.startswith("Tag:") or l.startswith("Tags:"):
@@ -77,23 +80,36 @@
                         if (len(t) > 1) or (t[0] and (t[0] != "(none)")):
                             self.tags.update(dict.fromkeys(t, id))
                     elif l.startswith("Log:"):
+                        # switch to gathering log
                         state = 1
                         log = ""
                 elif state == 1: # log
                     if l == "Members: \n":
+                        # switch to gathering members
                         files = {}
+                        oldrevs = []
                         log = self.recode(log[:-1])
                         state = 2
                     else:
+                        # gather log
                         log += l
-                elif state == 2:
-                    if l == "\n": #
+                elif state == 2: # members
+                    if l == "\n": # start of next entry
                         state = 0
                         p = [self.parent[id]]
                         if id == "1":
                             p = []
                         if branch == "HEAD":
                             branch = ""
+                        if branch and p[0] == None:
+                            latest = None
+                            # the last changeset that contains a base
+                            # file is our parent
+                            for r in oldrevs:
+                                latest = max(filerevids[r], latest)
+                            p = [latest]
+
+                        # add current commit to set
                         c = commit(author=author, date=date, parents=p,
                                    desc=log, branch=branch)
                         self.changeset[id] = c
@@ -102,9 +118,14 @@
                         colon = l.rfind(':')
                         file = l[1:colon]
                         rev = l[colon+1:-2]
-                        rev = rev.split("->")[1]
+                        oldrev, rev = rev.split("->")
                         files[file] = rev
+
+                        # save some information for identifying branch points
+                        oldrevs.append("%s:%s" % (oldrev, file))
+                        filerevids["%s:%s" % (rev, file)] = id
                 elif state == 3:
+                    # swallow all input
                     continue
 
             self.heads = self.lastbranch.values()
--- a/hgext/convert/hg.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/hgext/convert/hg.py	Fri Jan 25 16:04:46 2008 -0800
@@ -30,6 +30,8 @@
         if os.path.isdir(path) and len(os.listdir(path)) > 0:
             try:
                 self.repo = hg.repository(self.ui, path)
+                if not self.repo.local():
+                    raise NoRepo(_('%s is not a local Mercurial repo') % path)
             except hg.RepoError, err:
                 ui.print_exc()
                 raise NoRepo(err.args[0])
@@ -37,6 +39,8 @@
             try:
                 ui.status(_('initializing destination %s repository\n') % path)
                 self.repo = hg.repository(self.ui, path, create=True)
+                if not self.repo.local():
+                    raise NoRepo(_('%s is not a local Mercurial repo') % path)
                 self.created.append(path)
             except hg.RepoError, err:
                 ui.print_exc()
--- a/hgext/convert/subversion.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/hgext/convert/subversion.py	Fri Jan 25 16:04:46 2008 -0800
@@ -89,6 +89,9 @@
                        receiver)
     except SubversionException, (inst, num):
         pickle.dump(num, fp, protocol)
+    except IOError:
+        # Caller may interrupt the iteration
+        pickle.dump(None, fp, protocol)
     else:
         pickle.dump(None, fp, protocol)
     fp.close()
@@ -102,7 +105,53 @@
     args = decodeargs(sys.stdin.read())
     get_log_child(sys.stdout, *args)
 
+class logstream:
+    """Interruptible revision log iterator."""
+    def __init__(self, stdout):
+        self._stdout = stdout
+
+    def __iter__(self):
+        while True:
+            entry = pickle.load(self._stdout)
+            try:
+                orig_paths, revnum, author, date, message = entry
+            except:
+                if entry is None:
+                    break
+                raise SubversionException("child raised exception", entry)
+            yield entry
+
+    def close(self):
+        if self._stdout:
+            self._stdout.close()
+            self._stdout = None
+
+def get_log(url, paths, start, end, limit=0, discover_changed_paths=True,
+                strict_node_history=False):
+    args = [url, paths, start, end, limit, discover_changed_paths,
+            strict_node_history]
+    arg = encodeargs(args)
+    hgexe = util.hgexecutable()
+    cmd = '%s debugsvnlog' % util.shellquote(hgexe)
+    stdin, stdout = os.popen2(cmd, 'b')
+    stdin.write(arg)
+    stdin.close()
+    return logstream(stdout)
+
 # SVN conversion code stolen from bzr-svn and tailor
+#
+# Subversion looks like a versioned filesystem, branches structures
+# are defined by conventions and not enforced by the tool. First,
+# we define the potential branches (modules) as "trunk" and "branches"
+# children directories. Revisions are then identified by their
+# module and revision number (and a repository identifier).
+#
+# The revision graph is really a tree (or a forest). By default, a
+# revision parent is the previous revision in the same module. If the
+# module directory is copied/moved from another module then the
+# revision is the module root and its parent the source revision in
+# the parent module. A revision has at most one parent.
+#
 class svn_source(converter_source):
     def __init__(self, ui, url, rev=None):
         super(svn_source, self).__init__(ui, url, rev=rev)
@@ -133,7 +182,6 @@
             self.ctx = self.transport.client
             self.base = svn.ra.get_repos_root(self.ra)
             self.module = self.url[len(self.base):]
-            self.modulemap = {} # revision, module
             self.commits = {}
             self.paths = {}
             self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding)
@@ -257,45 +305,26 @@
             uuid, module, revnum = self.revsplit(rev)
             self.module = module
             self.reparent(module)
+            # We assume that:
+            # - requests for revisions after "stop" come from the
+            # revision graph backward traversal. Cache all of them
+            # down to stop, they will be used eventually.
+            # - requests for revisions before "stop" come to get
+            # isolated branches parents. Just fetch what is needed.
             stop = self.lastrevs.get(module, 0)
-            self._fetch_revisions(from_revnum=revnum, to_revnum=stop)
+            if revnum < stop:
+                stop = revnum + 1
+            self._fetch_revisions(revnum, stop)
         commit = self.commits[rev]
         # caller caches the result, so free it here to release memory
         del self.commits[rev]
         return commit
 
-    def get_log(self, paths, start, end, limit=0, discover_changed_paths=True,
-                strict_node_history=False):
-
-        def parent(fp):
-            while True:
-                entry = pickle.load(fp)
-                try:
-                    orig_paths, revnum, author, date, message = entry
-                except:
-                    if entry is None:
-                        break
-                    raise SubversionException("child raised exception", entry)
-                yield entry
-
-        args = [self.url, paths, start, end, limit, discover_changed_paths,
-                strict_node_history]
-        arg = encodeargs(args)
-        hgexe = util.hgexecutable()
-        cmd = '%s debugsvnlog' % util.shellquote(hgexe)
-        stdin, stdout = os.popen2(cmd, 'b')
-
-        stdin.write(arg)
-        stdin.close()
-
-        for p in parent(stdout):
-            yield p
-
     def gettags(self):
         tags = {}
         start = self.revnum(self.head)
         try:
-            for entry in self.get_log([self.tags], 0, start):
+            for entry in get_log(self.url, [self.tags], 0, start):
                 orig_paths, revnum, author, date, message = entry
                 for path in orig_paths:
                     if not path.startswith(self.tags+'/'):
@@ -400,13 +429,11 @@
         entries = []
         copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions.
         copies = {}
-        revnum = self.revnum(rev)
 
-        if revnum in self.modulemap:
-            new_module = self.modulemap[revnum]
-            if new_module != self.module:
-                self.module = new_module
-                self.reparent(self.module)
+        new_module, revnum = self.revsplit(rev)[1:]
+        if new_module != self.module:
+            self.module = new_module
+            self.reparent(self.module)
 
         for path, ent in paths:
             entrypath = get_entry_from_path(path, module=self.module)
@@ -432,12 +459,9 @@
 
                 # if a branch is created but entries are removed in the same
                 # changeset, get the right fromrev
-                if parents:
-                    uuid, old_module, fromrev = self.revsplit(parents[0])
-                else:
-                    fromrev = revnum - 1
-                    # might always need to be revnum - 1 in these 3 lines?
-                    old_module = self.modulemap.get(fromrev, self.module)
+                # parents cannot be empty here, you cannot remove things from
+                # a root revision.
+                uuid, old_module, fromrev = self.revsplit(parents[0])
 
                 basepath = old_module + "/" + get_entry_from_path(path, module=self.module)
                 entrypath = old_module + "/" + get_entry_from_path(path, module=self.module)
@@ -466,7 +490,12 @@
                     fromrev = froment.copyfrom_rev
                     self.ui.debug("Info: %s %s %s %s\n" % (frompath, froment, ent, entrypath))
 
-                fromkind = svn.ra.check_path(self.ra, entrypath, fromrev)
+                # We can avoid the reparent calls if the module has not changed
+                # but it probably does not worth the pain.
+                self.reparent('')
+                fromkind = svn.ra.check_path(self.ra, entrypath.strip('/'), fromrev)
+                self.reparent(self.module)
+                
                 if fromkind == svn.core.svn_node_file:   # a deleted file
                     entries.append(self.recode(entry))
                 elif fromkind == svn.core.svn_node_dir:
@@ -508,6 +537,9 @@
 
                 # If the directory just had a prop change,
                 # then we shouldn't need to look for its children.
+                if ent.action == 'M':
+                    continue
+
                 # Also this could create duplicate entries. Not sure
                 # whether this will matter. Maybe should make entries a set.
                 # print "Changed directory", revnum, path, ent.action, ent.copyfrom_path, ent.copyfrom_rev
@@ -566,25 +598,25 @@
                                 copies[self.recode(copyto_entry)] = self.recode(entry)
                                 # copy from quux splort/quuxfile
 
-        return (entries, copies)
+        return (util.unique(entries), copies)
 
-    def _fetch_revisions(self, from_revnum = 0, to_revnum = 347):
+    def _fetch_revisions(self, from_revnum, to_revnum):
+        if from_revnum < to_revnum:
+            from_revnum, to_revnum = to_revnum, from_revnum
+
         self.child_cset = None
         def parselogentry(orig_paths, revnum, author, date, message):
+            """Return the parsed commit object or None, and True if 
+            the revision is a branch root.
+            """
             self.ui.debug("parsing revision %d (%d changes)\n" %
                           (revnum, len(orig_paths)))
 
-            if revnum in self.modulemap:
-                new_module = self.modulemap[revnum]
-                if new_module != self.module:
-                    self.module = new_module
-                    self.reparent(self.module)
-
             rev = self.revid(revnum)
             # branch log might return entries for a parent we already have
-            if (rev in self.commits or
-                (revnum < self.lastrevs.get(self.module, 0))):
-                return
+
+            if (rev in self.commits or revnum < to_revnum):
+                return None, False
 
             parents = []
             # check whether this revision is the start of a branch
@@ -593,15 +625,12 @@
                 if ent.copyfrom_path:
                     # ent.copyfrom_rev may not be the actual last revision
                     prev = self.latest(ent.copyfrom_path, ent.copyfrom_rev)
-                    self.modulemap[prev] = ent.copyfrom_path
                     parents = [self.revid(prev, ent.copyfrom_path)]
                     self.ui.note('found parent of branch %s at %d: %s\n' % \
                                      (self.module, prev, ent.copyfrom_path))
                 else:
                     self.ui.debug("No copyfrom path, don't know what to do.\n")
 
-            self.modulemap[revnum] = self.module # track backwards in time
-
             orig_paths = orig_paths.items()
             orig_paths.sort()
             paths = []
@@ -612,14 +641,12 @@
                     continue
                 paths.append((path, ent))
 
-            self.paths[rev] = (paths, parents)
-
             # Example SVN datetime. Includes microseconds.
             # ISO-8601 conformant
             # '2007-01-04T17:35:00.902377Z'
             date = util.parsedate(date[:19] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
 
-            log = message and self.recode(message)
+            log = message and self.recode(message) or ''
             author = author and self.recode(author) or ''
             try:
                 branch = self.module.split("/")[-1]
@@ -636,23 +663,50 @@
                           rev=rev.encode('utf-8'))
 
             self.commits[rev] = cset
+            # The parents list is *shared* among self.paths and the
+            # commit object. Both will be updated below.
+            self.paths[rev] = (paths, cset.parents)
             if self.child_cset and not self.child_cset.parents:
-                self.child_cset.parents = [rev]
+                self.child_cset.parents[:] = [rev]
             self.child_cset = cset
+            return cset, len(parents) > 0
 
         self.ui.note('fetching revision log for "%s" from %d to %d\n' %
                      (self.module, from_revnum, to_revnum))
 
         try:
-            for entry in self.get_log([self.module], from_revnum, to_revnum):
-                orig_paths, revnum, author, date, message = entry
-                if self.is_blacklisted(revnum):
-                    self.ui.note('skipping blacklisted revision %d\n' % revnum)
-                    continue
-                if orig_paths is None:
-                    self.ui.debug('revision %d has no entries\n' % revnum)
-                    continue
-                parselogentry(orig_paths, revnum, author, date, message)
+            firstcset = None
+            stream = get_log(self.url, [self.module], from_revnum, to_revnum)
+            try:
+                for entry in stream:
+                    paths, revnum, author, date, message = entry
+                    if self.is_blacklisted(revnum):
+                        self.ui.note('skipping blacklisted revision %d\n' 
+                                     % revnum)
+                        continue
+                    if paths is None:
+                        self.ui.debug('revision %d has no entries\n' % revnum)
+                        continue
+                    cset, branched = parselogentry(paths, revnum, author, 
+                                                   date, message)
+                    if cset:
+                        firstcset = cset
+                    if branched:
+                        break
+            finally:
+                stream.close()
+
+            if firstcset and not firstcset.parents:
+                # The first revision of the sequence (the last fetched one)
+                # has invalid parents if not a branch root. Find the parent
+                # revision now, if any.
+                try:
+                    firstrevnum = self.revnum(firstcset.rev)
+                    if firstrevnum > 1:
+                        latest = self.latest(self.module, firstrevnum - 1)
+                        firstcset.parents.append(self.revid(latest))
+                except util.Abort:
+                    pass
         except SubversionException, (inst, num):
             if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
                 raise NoSuchRevision(branch=self,
@@ -664,9 +718,9 @@
         # TODO: ra.get_file transmits the whole file instead of diffs.
         mode = ''
         try:
-            revnum = self.revnum(rev)
-            if self.module != self.modulemap[revnum]:
-                self.module = self.modulemap[revnum]
+            new_module, revnum = self.revsplit(rev)[1:]
+            if self.module != new_module:
+                self.module = new_module
                 self.reparent(self.module)
             info = svn.ra.get_file(self.ra, file, revnum, io)
             if isinstance(info, list):
--- a/hgext/graphlog.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/hgext/graphlog.py	Fri Jan 25 16:04:46 2008 -0800
@@ -5,11 +5,12 @@
 # This software may be used and distributed according to the terms of
 # the GNU General Public License, incorporated herein by reference.
 
+import os
 import sys
 from mercurial.cmdutil import revrange, show_changeset
 from mercurial.i18n import _
 from mercurial.node import nullid, nullrev
-from mercurial.util import Abort
+from mercurial.util import Abort, canonpath
 
 def revision_grapher(repo, start_rev, stop_rev):
     """incremental revision grapher
@@ -63,6 +64,62 @@
         revs = next_revs
         curr_rev -= 1
 
+def filelog_grapher(repo, path, start_rev, stop_rev):
+    """incremental file log grapher
+
+    This generator function walks through the revision history of a
+    single file from revision start_rev to revision stop_rev (which must
+    be less than or equal to start_rev) and for each revision emits
+    tuples with the following elements:
+
+      - Current revision.
+      - Current node.
+      - Column of the current node in the set of ongoing edges.
+      - Edges; a list of (col, next_col) indicating the edges between
+        the current node and its parents.
+      - Number of columns (ongoing edges) in the current revision.
+      - The difference between the number of columns (ongoing edges)
+        in the next revision and the number of columns (ongoing edges)
+        in the current revision. That is: -1 means one column removed;
+        0 means no columns added or removed; 1 means one column added.
+    """
+
+    assert start_rev >= stop_rev
+    curr_rev = start_rev
+    revs = []
+    filerev = repo.file(path).count() - 1
+    while filerev >= 0:
+        fctx = repo.filectx(path, fileid=filerev)
+
+        # Compute revs and next_revs.
+        if filerev not in revs:
+            revs.append(filerev)
+        rev_index = revs.index(filerev)
+        next_revs = revs[:]
+
+        # Add parents to next_revs.
+        parents = [f.filerev() for f in fctx.parents()]
+        parents_to_add = []
+        for parent in parents:
+            if parent not in next_revs:
+                parents_to_add.append(parent)
+        parents_to_add.sort()
+        next_revs[rev_index:rev_index + 1] = parents_to_add
+
+        edges = []
+        for parent in parents:
+            edges.append((rev_index, next_revs.index(parent)))
+
+        changerev = fctx.linkrev()
+        if changerev <= start_rev:
+            node = repo.changelog.node(changerev)
+            n_columns_diff = len(next_revs) - len(revs)
+            yield (changerev, node, rev_index, edges, len(revs), n_columns_diff)
+        if changerev <= stop_rev:
+            break
+        revs = next_revs
+        filerev -= 1
+
 def get_rev_parents(repo, rev):
     return [x for x in repo.changelog.parentrevs(rev) if x != nullrev]
 
@@ -141,7 +198,7 @@
     else:
         return (repo.changelog.count() - 1, 0)
 
-def graphlog(ui, repo, **opts):
+def graphlog(ui, repo, path=None, **opts):
     """show revision history alongside an ASCII revision graph
 
     Print a revision history alongside a revision graph drawn with
@@ -157,7 +214,11 @@
     if start_rev == nullrev:
         return
     cs_printer = show_changeset(ui, repo, opts)
-    grapher = revision_grapher(repo, start_rev, stop_rev)
+    if path:
+        cpath = canonpath(repo.root, os.getcwd(), path)
+        grapher = filelog_grapher(repo, cpath, start_rev, stop_rev)
+    else:
+        grapher = revision_grapher(repo, start_rev, stop_rev)
     repo_parents = repo.dirstate.parents()
     prev_n_columns_diff = 0
     prev_node_index = 0
@@ -261,5 +322,5 @@
           ('r', 'rev', [], _('show the specified revision or range')),
           ('', 'style', '', _('display using template map file')),
           ('', 'template', '', _('display with template'))],
-         _('hg glog [OPTION]...')),
+         _('hg glog [OPTION]... [FILE]')),
 }
--- a/hgext/hgk.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/hgext/hgk.py	Fri Jan 25 16:04:46 2008 -0800
@@ -45,7 +45,7 @@
 # Revisions context menu will now display additional entries to fire
 # vdiff on hovered and selected revisions.
 
-import sys, os
+import os
 from mercurial import hg, fancyopts, commands, ui, util, patch, revlog
 
 def difftree(ui, repo, node1=None, node2=None, *files, **opts):
@@ -61,17 +61,14 @@
 
         for f in modified:
             # TODO get file permissions
-            print ":100664 100664 %s %s M\t%s\t%s" % (hg.short(mmap[f]),
-                                                      hg.short(mmap2[f]),
-                                                      f, f)
+            ui.write(":100664 100664 %s %s M\t%s\t%s\n" %
+                     (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.short(mmap2[f]),
-                                                      f, f)
+            ui.write(":000000 100664 %s %s N\t%s\t%s\n" %
+                     (empty, hg.short(mmap2[f]), f, f))
         for f in removed:
-            print ":100664 000000 %s %s D\t%s\t%s" % (hg.short(mmap[f]),
-                                                      empty,
-                                                      f, f)
+            ui.write(":100664 000000 %s %s D\t%s\t%s\n" %
+                     (hg.short(mmap[f]), empty, f, f))
     ##
 
     while True:
@@ -93,7 +90,7 @@
             node1 = repo.changelog.parents(node1)[0]
         if opts['patch']:
             if opts['pretty']:
-                catcommit(repo, node2, "")
+                catcommit(ui, repo, node2, "")
             patch.diff(repo, node1, node2,
                        files=files,
                        opts=patch.diffopts(ui, {'git': True}))
@@ -102,14 +99,14 @@
         if not opts['stdin']:
             break
 
-def catcommit(repo, n, prefix, ctx=None):
+def catcommit(ui, repo, n, prefix, ctx=None):
     nlprefix = '\n' + prefix;
     if ctx is None:
         ctx = repo.changectx(n)
     (p1, p2) = ctx.parents()
-    print "tree %s" % (hg.short(ctx.changeset()[0])) # use ctx.node() instead ??
-    if p1: print "parent %s" % (hg.short(p1.node()))
-    if p2: print "parent %s" % (hg.short(p2.node()))
+    ui.write("tree %s\n" % hg.short(ctx.changeset()[0])) # use ctx.node() instead ??
+    if p1: ui.write("parent %s\n" % hg.short(p1.node()))
+    if p2: ui.write("parent %s\n" % hg.short(p2.node()))
     date = ctx.date()
     description = ctx.description().replace("\0", "")
     lines = description.splitlines()
@@ -118,24 +115,24 @@
     else:
         committer = ctx.user()
 
-    print "author %s %s %s" % (ctx.user(), int(date[0]), date[1])
-    print "committer %s %s %s" % (committer, int(date[0]), date[1])
-    print "revision %d" % ctx.rev()
-    print "branch %s" % ctx.branch()
-    print ""
+    ui.write("author %s %s %s\n" % (ctx.user(), int(date[0]), date[1]))
+    ui.write("committer %s %s %s\n" % (committer, int(date[0]), date[1]))
+    ui.write("revision %d\n" % ctx.rev())
+    ui.write("branch %s\n\n" % ctx.branch())
+
     if prefix != "":
-        print "%s%s" % (prefix, description.replace('\n', nlprefix).strip())
+        ui.write("%s%s\n" % (prefix, description.replace('\n', nlprefix).strip()))
     else:
-        print description
+        ui.write(description + "\n")
     if prefix:
-        sys.stdout.write('\0')
+        ui.write('\0')
 
 def base(ui, repo, node1, node2):
     """Output common ancestor information"""
     node1 = repo.lookup(node1)
     node2 = repo.lookup(node2)
     n = repo.changelog.ancestor(node1, node2)
-    print hg.short(n)
+    ui.write(hg.short(n) + "\n")
 
 def catfile(ui, repo, type=None, r=None, **opts):
     """cat a specific revision"""
@@ -158,10 +155,10 @@
 
     while r:
         if type != "commit":
-            sys.stderr.write("aborting hg cat-file only understands commits\n")
-            sys.exit(1);
+            ui.warn("aborting hg cat-file only understands commits\n")
+            return 1;
         n = repo.lookup(r)
-        catcommit(repo, n, prefix)
+        catcommit(ui, repo, n, prefix)
         if opts['stdin']:
             try:
                 (type, r) = raw_input().split(' ');
@@ -175,7 +172,7 @@
 # telling you which commits are reachable from the supplied ones via
 # a bitmask based on arg position.
 # you can specify a commit to stop at by starting the sha1 with ^
-def revtree(args, repo, full="tree", maxnr=0, parents=False):
+def revtree(ui, args, repo, full="tree", maxnr=0, parents=False):
     def chlogwalk():
         count = repo.changelog.count()
         i = count
@@ -260,24 +257,24 @@
                 if pp[1] != hg.nullid:
                     parentstr += " " + hg.short(pp[1])
             if not full:
-                print hg.short(n) + parentstr
+                ui.write("%s%s\n" % (hg.short(n), parentstr))
             elif full == "commit":
-                print hg.short(n) + parentstr
-                catcommit(repo, n, '    ', ctx)
+                ui.write("%s%s\n" % (hg.short(n), parentstr))
+                catcommit(ui, repo, n, '    ', ctx)
             else:
                 (p1, p2) = repo.changelog.parents(n)
                 (h, h1, h2) = map(hg.short, (n, p1, p2))
                 (i1, i2) = map(repo.changelog.rev, (p1, p2))
 
                 date = ctx.date()[0]
-                print "%s %s:%s" % (date, h, mask),
+                ui.write("%s %s:%s" % (date, h, mask))
                 mask = is_reachable(want_sha1, reachable, p1)
                 if i1 != hg.nullrev and mask > 0:
-                    print "%s:%s " % (h1, mask),
+                    ui.write("%s:%s " % (h1, mask)),
                 mask = is_reachable(want_sha1, reachable, p2)
                 if i2 != hg.nullrev and mask > 0:
-                    print "%s:%s " % (h2, mask),
-                print ""
+                    ui.write("%s:%s " % (h2, mask))
+                ui.write("\n")
             if maxnr and count >= maxnr:
                 break
             count += 1
@@ -305,7 +302,7 @@
     else:
         full = None
     copy = [x for x in revs]
-    revtree(copy, repo, full, opts['max_count'], opts['parents'])
+    revtree(ui, copy, repo, full, opts['max_count'], opts['parents'])
 
 def config(ui, repo, **opts):
     """print extension options"""
--- a/hgext/keyword.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/hgext/keyword.py	Fri Jan 25 16:04:46 2008 -0800
@@ -69,12 +69,16 @@
 To force expansion after enabling it, or a configuration change, run
 "hg kwexpand".
 
+Also, when committing with the record extension or using mq's qrecord, be aware
+that keywords cannot be updated. Again, run "hg kwexpand" on the files in
+question to update keyword expansions after all changes have been checked in.
+
 Expansions spanning more than one line and incremental expansions,
 like CVS' $Log$, are not supported. A keyword template map
 "Log = {desc}" expands to the first line of the changeset description.
 '''
 
-from mercurial import commands, cmdutil, context, fancyopts, filelog
+from mercurial import commands, cmdutil, context, dispatch, filelog
 from mercurial import patch, localrepo, revlog, templater, util
 from mercurial.node import *
 from mercurial.i18n import _
@@ -86,6 +90,14 @@
     '''Returns hgdate in cvs-like UTC format.'''
     return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
 
+def _kwrestrict(cmd):
+    '''Returns True if cmd should trigger restricted expansion.
+    Keywords will only expanded when writing to working dir.
+    Crucial for mq as expanded keywords should not make it into patches.'''
+    return cmd in ('diff1', 'record',
+                   'qfold', 'qimport', 'qnew', 'qpush', 'qrefresh', 'qrecord')
+
+
 _kwtemplater = None
 
 class kwtemplater(object):
@@ -103,10 +115,11 @@
         'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
     }
 
-    def __init__(self, ui, repo, inc, exc):
+    def __init__(self, ui, repo, inc, exc, hgcmd):
         self.ui = ui
         self.repo = repo
         self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1]
+        self.hgcmd = hgcmd
         self.commitnode = None
         self.path = ''
 
@@ -144,7 +157,7 @@
 
     def expand(self, node, data):
         '''Returns data with keywords expanded.'''
-        if util.binary(data):
+        if util.binary(data) or _kwrestrict(self.hgcmd):
             return data
         return self.substitute(node, data, self.re_kw.sub)
 
@@ -230,7 +243,7 @@
     mf = ctx.manifest()
     if node is not None:   # commit
         _kwtemplater.commitnode = node
-        files = [f for f in ctx.files() if mf.has_key(f)]
+        files = [f for f in ctx.files() if f in mf]
         notify = ui.debug
     else:                  # kwexpand/kwshrink
         notify = ui.note
@@ -297,7 +310,7 @@
         kwmaps = kwtemplater.templates
         if ui.configitems('keywordmaps'):
             # override maps from optional rcfile
-            for k, v in kwmaps.items():
+            for k, v in kwmaps.iteritems():
                 ui.setconfig('keywordmaps', k, v)
     elif args:
         # simulate hgrc parsing
@@ -316,7 +329,7 @@
     demostatus('config using %s keyword template maps' % kwstatus)
     ui.write('[extensions]\n%s\n' % extension)
     demoitems('keyword', ui.configitems('keyword'))
-    demoitems('keywordmaps', kwmaps.items())
+    demoitems('keywordmaps', kwmaps.iteritems())
     keywords = '$' + '$\n$'.join(kwmaps.keys()) + '$\n'
     repo.wopener(fn, 'w').write(keywords)
     repo.add([fn])
@@ -395,22 +408,27 @@
     This is done for local repos only, and only if there are
     files configured at all for keyword substitution.'''
 
-    def kwbailout():
-        '''Obtains command via simplified cmdline parsing,
-        returns True if keyword expansion not needed.'''
-        nokwcommands = ('add', 'addremove', 'bundle', 'clone', 'copy',
-                        'export', 'grep', 'identify', 'incoming', 'init',
-                        'log', 'outgoing', 'push', 'remove', 'rename',
-                        'rollback', 'tip',
-                        'convert')
-        args = fancyopts.fancyopts(sys.argv[1:], commands.globalopts, {})
-        if args:
-            aliases, i = cmdutil.findcmd(ui, args[0], commands.table)
-            return aliases[0] in nokwcommands
+    if not repo.local():
+        return
 
-    if not repo.local() or kwbailout():
+    nokwcommands = ('add', 'addremove', 'bundle', 'clone', 'copy',
+                    'export', 'grep', 'identify', 'incoming', 'init',
+                    'log', 'outgoing', 'push', 'remove', 'rename',
+                    'rollback', 'tip',
+                    'convert')
+    hgcmd, func, args, opts, cmdopts = dispatch._parse(ui, sys.argv[1:])
+    if hgcmd in nokwcommands:
         return
 
+    if hgcmd == 'diff':
+        # only expand if comparing against working dir
+        node1, node2 = cmdutil.revpair(repo, cmdopts.get('rev'))
+        if node2 is not None:
+            return
+        # shrink if rev is not current node
+        if node1 is not None and node1 != repo.changectx().node():
+            hgcmd = 'diff1'
+
     inc, exc = [], ['.hgtags']
     for pat, opt in ui.configitems('keyword'):
         if opt != 'ignore':
@@ -421,7 +439,7 @@
         return
 
     global _kwtemplater
-    _kwtemplater = kwtemplater(ui, repo, inc, exc)
+    _kwtemplater = kwtemplater(ui, repo, inc, exc, hgcmd)
 
     class kwrepo(repo.__class__):
         def file(self, f, kwmatch=False):
@@ -431,6 +449,12 @@
                 return kwfilelog(self.sopener, f)
             return filelog.filelog(self.sopener, f)
 
+        def wread(self, filename):
+            data = super(kwrepo, self).wread(filename)
+            if _kwrestrict(hgcmd) and _kwtemplater.matcher(filename):
+                return _kwtemplater.shrink(data)
+            return data
+
         def commit(self, files=None, text='', user=None, date=None,
                    match=util.always, force=False, force_editor=False,
                    p1=None, p2=None, extra={}):
@@ -440,10 +464,10 @@
                 wlock = self.wlock()
                 lock = self.lock()
                 # store and postpone commit hooks
-                commithooks = []
+                commithooks = {}
                 for name, cmd in ui.configitems('hooks'):
                     if name.split('.', 1)[0] == 'commit':
-                        commithooks.append((name, cmd))
+                        commithooks[name] = cmd
                         ui.setconfig('hooks', name, None)
                 if commithooks:
                     # store parents for commit hook environment
@@ -464,7 +488,7 @@
                                           p1=p1, p2=p2, extra=extra)
 
                 # restore commit hooks
-                for name, cmd in commithooks:
+                for name, cmd in commithooks.iteritems():
                     ui.setconfig('hooks', name, cmd)
                 if node is not None:
                     _overwrite(ui, self, node=node)
--- a/hgext/mq.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/hgext/mq.py	Fri Jan 25 16:04:46 2008 -0800
@@ -224,7 +224,7 @@
         def write_list(items, path):
             fp = self.opener(path, 'w')
             for i in items:
-                print >> fp, i
+                fp.write("%s\n" % i)
             fp.close()
         if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
         if self.series_dirty: write_list(self.full_series, self.series_path)
@@ -1267,7 +1267,7 @@
             self.ui.warn("saved queue repository parents: %s %s\n" %
                          (hg.short(qpp[0]), hg.short(qpp[1])))
             if qupdate:
-                print "queue directory updating"
+                self.ui.status(_("queue directory updating\n"))
                 r = self.qrepo()
                 if not r:
                     self.ui.warn("Unable to load queue repository\n")
--- a/hgext/patchbomb.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/hgext/patchbomb.py	Fri Jan 25 16:04:46 2008 -0800
@@ -417,8 +417,7 @@
                 fp.close()
         elif opts.get('mbox'):
             ui.status('Writing ', m['Subject'], ' ...\n')
-            fp = open(opts.get('mbox'),
-                      m.has_key('In-Reply-To') and 'ab+' or 'wb+')
+            fp = open(opts.get('mbox'), 'In-Reply-To' in m and 'ab+' or 'wb+')
             date = util.datestr(date=start_time,
                                 format='%a %b %d %H:%M:%S %Y', timezone=False)
             fp.write('From %s %s\n' % (sender_addr, date))
--- a/hgext/record.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/hgext/record.py	Fri Jan 25 16:04:46 2008 -0800
@@ -364,10 +364,10 @@
     dorecord(ui, repo, record_committer, *pats, **opts)
 
 
-def qrecord(ui, repo, *pats, **opts):
-    '''interactively select changes for qrefresh
+def qrecord(ui, repo, patch, *pats, **opts):
+    '''interactively record a new patch
 
-    see 'hg help record' for more information and usage
+    see 'hg help qnew' & 'hg help record' for more information and usage
     '''
 
     try:
@@ -376,8 +376,10 @@
         raise util.Abort(_("'mq' extension not loaded"))
 
     def qrecord_committer(ui, repo, pats, opts):
-        mq.refresh(ui, repo, *pats, **opts)
+        mq.new(ui, repo, patch, *pats, **opts)
 
+    opts = opts.copy()
+    opts['force'] = True    # always 'qnew -f'
     dorecord(ui, repo, qrecord_committer, *pats, **opts)
 
 
@@ -513,10 +515,10 @@
     "qrecord":
         (qrecord,
 
-         # add qrefresh options
-         mq.cmdtable['^qrefresh'][1],
+         # add qnew options, except '--force'
+         [opt for opt in mq.cmdtable['qnew'][1] if opt[1] != 'force'],
 
-         _('hg qrecord [OPTION]... [FILE]...')),
+         _('hg qrecord [OPTION]... PATCH [FILE]...')),
     }
 
     cmdtable.update(qcmdtable)
--- a/mercurial/byterange.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/mercurial/byterange.py	Fri Jan 25 16:04:46 2008 -0800
@@ -233,7 +233,7 @@
             size = (lb - fb)
             fo = RangeableFileObject(fo, (fb, lb))
         headers = mimetools.Message(StringIO(
-            'Content-Type: %s\nContent-Length: %d\nLast-modified: %s\n' %
+            'Content-Type: %s\nContent-Length: %d\nLast-Modified: %s\n' %
             (mtype or 'text/plain', size, modified)))
         return urllib.addinfourl(fo, headers, 'file:'+file)
 
--- a/mercurial/changegroup.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/mercurial/changegroup.py	Fri Jan 25 16:04:46 2008 -0800
@@ -80,9 +80,13 @@
         # in case of sshrepo because we don't know the end of the stream
 
         # an empty chunkiter is the end of the changegroup
+        # a changegroup has at least 2 chunkiters (changelog and manifest).
+        # after that, an empty chunkiter is the end of the changegroup
         empty = False
-        while not empty:
+        count = 0
+        while not empty or count <= 2:
             empty = True
+            count += 1
             for chunk in chunkiter(cg):
                 empty = False
                 fh.write(z.compress(chunkheader(len(chunk))))
--- a/mercurial/cmdutil.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/mercurial/cmdutil.py	Fri Jan 25 16:04:46 2008 -0800
@@ -50,7 +50,7 @@
     """Return (aliases, command table entry) for command string."""
     choice = findpossible(ui, cmd, table)
 
-    if choice.has_key(cmd):
+    if cmd in choice:
         return choice[cmd]
 
     if len(choice) > 1:
--- a/mercurial/commands.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/mercurial/commands.py	Fri Jan 25 16:04:46 2008 -0800
@@ -1528,12 +1528,13 @@
                                        files=files)
                 finally:
                     files = patch.updatedir(ui, repo, files)
-                n = repo.commit(files, message, user, date)
-                if opts.get('exact'):
-                    if hex(n) != nodeid:
-                        repo.rollback()
-                        raise util.Abort(_('patch is damaged'
-                                           ' or loses information'))
+                if not opts.get('no_commit'):
+                    n = repo.commit(files, message, user, date)
+                    if opts.get('exact'):
+                        if hex(n) != nodeid:
+                            repo.rollback()
+                            raise util.Abort(_('patch is damaged'
+                                               ' or loses information'))
             finally:
                 os.unlink(tmpname)
     finally:
@@ -2896,6 +2897,7 @@
           ('b', 'base', '', _('base path')),
           ('f', 'force', None,
            _('skip check for outstanding uncommitted changes')),
+          ('', 'no-commit', None, _("don't commit, just update the working directory")),
           ('', 'exact', None,
            _('apply patch to the nodes from which it was generated')),
           ('', 'import-branch', None,
--- a/mercurial/demandimport.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/mercurial/demandimport.py	Fri Jan 25 16:04:46 2008 -0800
@@ -77,7 +77,7 @@
         self._load()
         setattr(self._module, attr, val)
 
-def _demandimport(name, globals=None, locals=None, fromlist=None):
+def _demandimport(name, globals=None, locals=None, fromlist=None, level=None):
     if not locals or name in ignore or fromlist == ('*',):
         # these cases we can't really delay
         return _origimport(name, globals, locals, fromlist)
@@ -95,6 +95,9 @@
                 return locals[base]
         return _demandmod(name, globals, locals)
     else:
+        if level is not None:
+            # from . import b,c,d or from .a import b,c,d
+            return _origimport(name, globals, locals, fromlist, level)
         # from a import b,c,d
         mod = _origimport(name, globals, locals)
         # recurse down the module chain
--- a/mercurial/dirstate.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/mercurial/dirstate.py	Fri Jan 25 16:04:46 2008 -0800
@@ -235,7 +235,7 @@
         self._changepath(f, 'n', True)
         s = os.lstat(self._join(f))
         self._map[f] = ('n', s.st_mode, s.st_size, s.st_mtime, 0)
-        if self._copymap.has_key(f):
+        if f in self._copymap:
             del self._copymap[f]
 
     def normallookup(self, f):
--- a/mercurial/dispatch.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/mercurial/dispatch.py	Fri Jan 25 16:04:46 2008 -0800
@@ -354,12 +354,12 @@
         d = lambda: func(ui, *args, **cmdoptions)
 
     # run pre-hook, and abort if it fails
-    ret = hook.hook(ui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs))
+    ret = hook.hook(lui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs))
     if ret:
         return ret
     ret = _runcommand(ui, options, cmd, d)
     # run post-hook, passing command result
-    hook.hook(ui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
+    hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
               result = ret)
     return ret
 
--- a/mercurial/fancyopts.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/mercurial/fancyopts.py	Fri Jan 25 16:04:46 2008 -0800
@@ -38,7 +38,6 @@
         if isinstance(default, list):
             state[name] = default[:]
         elif callable(default):
-            print "whoa", name, default
             state[name] = None
         else:
             state[name] = default
--- a/mercurial/filelog.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/mercurial/filelog.py	Fri Jan 25 16:04:46 2008 -0800
@@ -58,7 +58,7 @@
         if self.parents(node)[0] != nullid:
             return False
         m = self._readmeta(node)
-        if m and m.has_key("copy"):
+        if m and "copy" in m:
             return (m["copy"], bin(m["copyrev"]))
         return False
 
--- a/mercurial/hgweb/common.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/mercurial/hgweb/common.py	Fri Jan 25 16:04:46 2008 -0800
@@ -36,11 +36,11 @@
         return os.stat(store_path).st_mtime
 
 def staticfile(directory, fname, req):
-    """return a file inside directory with guessed content-type header
+    """return a file inside directory with guessed Content-Type header
 
     fname always uses '/' as directory separator and isn't allowed to
     contain unusual path components.
-    Content-type is guessed using the mimetypes module.
+    Content-Type is guessed using the mimetypes module.
     Return an empty string if fname is illegal or file not found.
 
     """
@@ -54,8 +54,10 @@
     try:
         os.stat(path)
         ct = mimetypes.guess_type(path)[0] or "text/plain"
-        req.header([('Content-type', ct),
-                    ('Content-length', str(os.path.getsize(path)))])
+        req.header([
+            ('Content-Type', ct),
+            ('Content-Length', str(os.path.getsize(path)))
+        ])
         return file(path, 'rb').read()
     except TypeError:
         raise ErrorResponse(500, 'illegal file name')
--- a/mercurial/hgweb/hgweb_mod.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/mercurial/hgweb/hgweb_mod.py	Fri Jan 25 16:04:46 2008 -0800
@@ -6,7 +6,7 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-import os, mimetypes, re, mimetools, cStringIO
+import os, mimetypes, re
 from mercurial.node import *
 from mercurial import mdiff, ui, hg, util, archival, patch, hook
 from mercurial import revlog, templater
@@ -153,7 +153,7 @@
         req.url = req.env['SCRIPT_NAME']
         if not req.url.endswith('/'):
             req.url += '/'
-        if req.env.has_key('REPO_NAME'):
+        if 'REPO_NAME' in req.env:
             req.url += req.env['REPO_NAME'] + '/'
 
         if req.env.get('PATH_INFO'):
@@ -206,12 +206,17 @@
                 method = getattr(protocol, cmd)
                 method(self, req)
             else:
+
                 tmpl = self.templater(req)
                 if cmd == '':
                     req.form['cmd'] = [tmpl.cache['default']]
                     cmd = req.form['cmd'][0]
-                method = getattr(webcommands, cmd)
-                method(self, req, tmpl)
+
+                if cmd == 'file' and 'raw' in req.form.get('style', []):
+                    webcommands.rawfile(self, req, tmpl)
+                else:
+                    getattr(webcommands, cmd)(self, req, tmpl)
+
                 del tmpl
 
         except revlog.LookupError, err:
@@ -248,17 +253,9 @@
         # some functions for the templater
 
         def header(**map):
-            header_file = cStringIO.StringIO(
-                ''.join(tmpl("header", encoding=self.encoding, **map)))
-            msg = mimetools.Message(header_file, 0)
-            req.header(msg.items())
-            yield header_file.read()
-
-        def rawfileheader(**map):
-            req.header([('Content-type', map['mimetype']),
-                        ('Content-disposition', 'filename=%s' % map['file']),
-                        ('Content-length', str(len(map['raw'])))])
-            yield ''
+            ctype = tmpl('mimetype', encoding=self.encoding)
+            req.httphdr(templater.stringify(ctype))
+            yield tmpl('header', encoding=self.encoding, **map)
 
         def footer(**map):
             yield tmpl("footer", **map)
@@ -268,7 +265,7 @@
 
         def sessionvars(**map):
             fields = []
-            if req.form.has_key('style'):
+            if 'style' in req.form:
                 style = req.form['style'][0]
                 if style != self.config('web', 'style', ''):
                     fields.append(('style', style))
@@ -281,7 +278,7 @@
         # figure out which style to use
 
         style = self.config("web", "style", "")
-        if req.form.has_key('style'):
+        if 'style' in req.form:
             style = req.form['style'][0]
         mapfile = style_map(self.templatepath, style)
 
@@ -300,7 +297,6 @@
                                              "header": header,
                                              "footer": footer,
                                              "motd": motd,
-                                             "rawfileheader": rawfileheader,
                                              "sessionvars": sessionvars
                                             })
         return tmpl
@@ -446,13 +442,13 @@
 
         changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
 
-        yield tmpl(shortlog and 'shortlog' or 'changelog',
-                   changenav=changenav,
-                   node=hex(cl.tip()),
-                   rev=pos, changesets=count,
-                   entries=lambda **x: changelist(limit=0,**x),
-                   latestentry=lambda **x: changelist(limit=1,**x),
-                   archives=self.archivelist("tip"))
+        return tmpl(shortlog and 'shortlog' or 'changelog',
+                    changenav=changenav,
+                    node=hex(cl.tip()),
+                    rev=pos, changesets=count,
+                    entries=lambda **x: changelist(limit=0,**x),
+                    latestentry=lambda **x: changelist(limit=1,**x),
+                    archives=self.archivelist("tip"))
 
     def search(self, tmpl, query):
 
@@ -505,11 +501,11 @@
         cl = self.repo.changelog
         parity = paritygen(self.stripecount)
 
-        yield tmpl('search',
-                   query=query,
-                   node=hex(cl.tip()),
-                   entries=changelist,
-                   archives=self.archivelist("tip"))
+        return tmpl('search',
+                    query=query,
+                    node=hex(cl.tip()),
+                    entries=changelist,
+                    archives=self.archivelist("tip"))
 
     def changeset(self, tmpl, ctx):
         n = ctx.node()
@@ -526,20 +522,20 @@
         def diff(**map):
             yield self.diff(tmpl, p1, n, None)
 
-        yield tmpl('changeset',
-                   diff=diff,
-                   rev=ctx.rev(),
-                   node=hex(n),
-                   parent=self.siblings(parents),
-                   child=self.siblings(ctx.children()),
-                   changesettag=self.showtag("changesettag",n),
-                   author=ctx.user(),
-                   desc=ctx.description(),
-                   date=ctx.date(),
-                   files=files,
-                   archives=self.archivelist(hex(n)),
-                   tags=self.nodetagsdict(n),
-                   branches=self.nodebranchdict(ctx))
+        return tmpl('changeset',
+                    diff=diff,
+                    rev=ctx.rev(),
+                    node=hex(n),
+                    parent=self.siblings(parents),
+                    child=self.siblings(ctx.children()),
+                    changesettag=self.showtag("changesettag",n),
+                    author=ctx.user(),
+                    desc=ctx.description(),
+                    date=ctx.date(),
+                    files=files,
+                    archives=self.archivelist(hex(n)),
+                    tags=self.nodetagsdict(n),
+                    branches=self.nodebranchdict(ctx))
 
     def filelog(self, tmpl, fctx):
         f = fctx.path()
@@ -578,9 +574,9 @@
 
         nodefunc = lambda x: fctx.filectx(fileid=x)
         nav = revnavgen(pos, pagelen, count, nodefunc)
-        yield tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
-                   entries=lambda **x: entries(limit=0, **x),
-                   latestentry=lambda **x: entries(limit=1, **x))
+        return tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
+                    entries=lambda **x: entries(limit=0, **x),
+                    latestentry=lambda **x: entries(limit=1, **x))
 
     def filerevision(self, tmpl, fctx):
         f = fctx.path()
@@ -602,21 +598,21 @@
                        "linenumber": "% 6d" % (l + 1),
                        "parity": parity.next()}
 
-        yield tmpl("filerevision",
-                   file=f,
-                   path=_up(f),
-                   text=lines(),
-                   raw=rawtext,
-                   mimetype=mt,
-                   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=fctx.manifest().flags(f))
+        return tmpl("filerevision",
+                    file=f,
+                    path=_up(f),
+                    text=lines(),
+                    raw=rawtext,
+                    mimetype=mt,
+                    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=fctx.manifest().flags(f))
 
     def fileannotate(self, tmpl, fctx):
         f = fctx.path()
@@ -640,19 +636,19 @@
                        "file": f.path(),
                        "line": l}
 
-        yield tmpl("fileannotate",
-                   file=f,
-                   annotate=annotate,
-                   path=_up(f),
-                   rev=fctx.rev(),
-                   node=hex(fctx.node()),
-                   author=fctx.user(),
-                   date=fctx.date(),
-                   desc=fctx.description(),
-                   rename=self.renamelink(fl, n),
-                   parent=self.siblings(fctx.parents()),
-                   child=self.siblings(fctx.children()),
-                   permissions=fctx.manifest().flags(f))
+        return tmpl("fileannotate",
+                    file=f,
+                    annotate=annotate,
+                    path=_up(f),
+                    rev=fctx.rev(),
+                    node=hex(fctx.node()),
+                    author=fctx.user(),
+                    date=fctx.date(),
+                    desc=fctx.description(),
+                    rename=self.renamelink(fl, n),
+                    parent=self.siblings(fctx.parents()),
+                    child=self.siblings(fctx.children()),
+                    permissions=fctx.manifest().flags(f))
 
     def manifest(self, tmpl, ctx, path):
         mf = ctx.manifest()
@@ -708,17 +704,17 @@
                        "path": "%s%s" % (abspath, f),
                        "basename": f[:-1]}
 
-        yield tmpl("manifest",
-                   rev=ctx.rev(),
-                   node=hex(node),
-                   path=abspath,
-                   up=_up(abspath),
-                   upparity=parity.next(),
-                   fentries=filelist,
-                   dentries=dirlist,
-                   archives=self.archivelist(hex(node)),
-                   tags=self.nodetagsdict(node),
-                   branches=self.nodebranchdict(ctx))
+        return tmpl("manifest",
+                    rev=ctx.rev(),
+                    node=hex(node),
+                    path=abspath,
+                    up=_up(abspath),
+                    upparity=parity.next(),
+                    fentries=filelist,
+                    dentries=dirlist,
+                    archives=self.archivelist(hex(node)),
+                    tags=self.nodetagsdict(node),
+                    branches=self.nodebranchdict(ctx))
 
     def tags(self, tmpl):
         i = self.repo.tagslist()
@@ -738,11 +734,11 @@
                        "date": self.repo.changectx(n).date(),
                        "node": hex(n)}
 
-        yield tmpl("tags",
-                   node=hex(self.repo.changelog.tip()),
-                   entries=lambda **x: entries(False,0, **x),
-                   entriesnotip=lambda **x: entries(True,0, **x),
-                   latestentry=lambda **x: entries(True,1, **x))
+        return tmpl("tags",
+                    node=hex(self.repo.changelog.tip()),
+                    entries=lambda **x: entries(False,0, **x),
+                    entriesnotip=lambda **x: entries(True,0, **x),
+                    latestentry=lambda **x: entries(True,1, **x))
 
     def summary(self, tmpl):
         i = self.repo.tagslist()
@@ -807,15 +803,15 @@
         start = max(0, count - self.maxchanges)
         end = min(count, start + self.maxchanges)
 
-        yield tmpl("summary",
-                   desc=self.config("web", "description", "unknown"),
-                   owner=get_contact(self.config) or "unknown",
-                   lastchange=cl.read(cl.tip())[2],
-                   tags=tagentries,
-                   branches=branches,
-                   shortlog=changelist,
-                   node=hex(cl.tip()),
-                   archives=self.archivelist("tip"))
+        return tmpl("summary",
+                    desc=self.config("web", "description", "unknown"),
+                    owner=get_contact(self.config) or "unknown",
+                    lastchange=cl.read(cl.tip())[2],
+                    tags=tagentries,
+                    branches=branches,
+                    shortlog=changelist,
+                    node=hex(cl.tip()),
+                    archives=self.archivelist("tip"))
 
     def filediff(self, tmpl, fctx):
         n = fctx.node()
@@ -826,13 +822,13 @@
         def diff(**map):
             yield self.diff(tmpl, p1, n, [path])
 
-        yield tmpl("filediff",
-                   file=path,
-                   node=hex(n),
-                   rev=fctx.rev(),
-                   parent=self.siblings(parents),
-                   child=self.siblings(fctx.children()),
-                   diff=diff)
+        return tmpl("filediff",
+                    file=path,
+                    node=hex(n),
+                    rev=fctx.rev(),
+                    parent=self.siblings(parents),
+                    child=self.siblings(fctx.children()),
+                    diff=diff)
 
     archive_specs = {
         'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
@@ -848,13 +844,15 @@
             arch_version = short(cnode)
         name = "%s-%s" % (reponame, arch_version)
         mimetype, artype, extension, encoding = self.archive_specs[type_]
-        headers = [('Content-type', mimetype),
-                   ('Content-disposition', 'attachment; filename=%s%s' %
-                    (name, extension))]
+        headers = [
+            ('Content-Type', mimetype),
+            ('Content-Disposition', 'attachment; filename=%s%s' %
+                (name, extension))
+        ]
         if encoding:
-            headers.append(('Content-encoding', encoding))
+            headers.append(('Content-Encoding', encoding))
         req.header(headers)
-        archival.archive(self.repo, req.out, cnode, artype, prefix=name)
+        archival.archive(self.repo, req, cnode, artype, prefix=name)
 
     # add tags to things
     # tags -> list of changesets corresponding to tags
@@ -865,9 +863,9 @@
         return util.canonpath(self.repo.root, '', path)
 
     def changectx(self, req):
-        if req.form.has_key('node'):
+        if 'node' in req.form:
             changeid = req.form['node'][0]
-        elif req.form.has_key('manifest'):
+        elif 'manifest' in req.form:
             changeid = req.form['manifest'][0]
         else:
             changeid = self.repo.changelog.count() - 1
@@ -883,7 +881,7 @@
 
     def filectx(self, req):
         path = self.cleanpath(req.form['file'][0])
-        if req.form.has_key('node'):
+        if 'node' in req.form:
             changeid = req.form['node'][0]
         else:
             changeid = req.form['filenode'][0]
--- a/mercurial/hgweb/hgwebdir_mod.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/mercurial/hgweb/hgwebdir_mod.py	Fri Jan 25 16:04:46 2008 -0800
@@ -6,7 +6,7 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-import os, mimetools, cStringIO
+import os
 from mercurial.i18n import gettext as _
 from mercurial import ui, hg, util, templater
 from common import ErrorResponse, get_mtime, staticfile, style_map, paritygen, \
@@ -143,7 +143,7 @@
         def entries(sortcolumn="", descending=False, subdir="", **map):
             def sessionvars(**map):
                 fields = []
-                if req.form.has_key('style'):
+                if 'style' in req.form:
                     style = req.form['style'][0]
                     if style != get('web', 'style', ''):
                         fields.append(('style', style))
@@ -214,7 +214,7 @@
 
         sortable = ["name", "description", "contact", "lastchange"]
         sortcolumn, descending = self.repos_sorted
-        if req.form.has_key('sort'):
+        if 'sort' in req.form:
             sortcolumn = req.form['sort'][0]
             descending = sortcolumn.startswith('-')
             if descending:
@@ -226,6 +226,7 @@
                  "%s%s" % ((not descending and column == sortcolumn)
                             and "-" or "", column))
                 for column in sortable]
+
         req.write(tmpl("index", entries=entries, subdir=subdir,
                        sortcolumn=sortcolumn, descending=descending,
                        **dict(sort)))
@@ -233,11 +234,9 @@
     def templater(self, req):
 
         def header(**map):
-            header_file = cStringIO.StringIO(
-                ''.join(tmpl("header", encoding=util._encoding, **map)))
-            msg = mimetools.Message(header_file, 0)
-            req.header(msg.items())
-            yield header_file.read()
+            ctype = tmpl('mimetype', encoding=util._encoding)
+            req.httphdr(templater.stringify(ctype))
+            yield tmpl('header', encoding=util._encoding, **map)
 
         def footer(**map):
             yield tmpl("footer", **map)
@@ -262,7 +261,7 @@
         style = self.style
         if style is None:
             style = config('web', 'style', '')
-        if req.form.has_key('style'):
+        if 'style' in req.form:
             style = req.form['style'][0]
         if self.stripecount is None:
             self.stripecount = int(config('web', 'stripes', 1))
--- a/mercurial/hgweb/protocol.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/mercurial/hgweb/protocol.py	Fri Jan 25 16:04:46 2008 -0800
@@ -28,7 +28,7 @@
 
 def branches(web, req):
     nodes = []
-    if req.form.has_key('nodes'):
+    if 'nodes' in req.form:
         nodes = map(bin, req.form['nodes'][0].split(" "))
     resp = cStringIO.StringIO()
     for b in web.repo.branches(nodes):
@@ -38,7 +38,7 @@
     req.write(resp)
 
 def between(web, req):
-    if req.form.has_key('pairs'):
+    if 'pairs' in req.form:
         pairs = [map(bin, p.split("-"))
                  for p in req.form['pairs'][0].split(" ")]
     resp = cStringIO.StringIO()
@@ -54,7 +54,7 @@
     if not web.allowpull:
         return
 
-    if req.form.has_key('roots'):
+    if 'roots' in req.form:
         nodes = map(bin, req.form['roots'][0].split(" "))
 
     z = zlib.compressobj()
@@ -74,9 +74,9 @@
     if not web.allowpull:
         return
 
-    if req.form.has_key('bases'):
+    if 'bases' in req.form:
         bases = [bin(x) for x in req.form['bases'][0].split(' ')]
-    if req.form.has_key('heads'):
+    if 'heads' in req.form:
         heads = [bin(x) for x in req.form['heads'][0].split(' ')]
 
     z = zlib.compressobj()
--- a/mercurial/hgweb/request.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/mercurial/hgweb/request.py	Fri Jan 25 16:04:46 2008 -0800
@@ -24,35 +24,43 @@
         self.run_once = wsgienv['wsgi.run_once']
         self.env = wsgienv
         self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
-        self.start_response = start_response
+        self._start_response = start_response
         self.headers = []
 
-    out = property(lambda self: self)
-
     def __iter__(self):
         return iter([])
 
     def read(self, count=-1):
         return self.inp.read(count)
 
+    def start_response(self, status):
+        if self._start_response is not None:
+            if not self.headers:
+                raise RuntimeError("request.write called before headers sent")
+
+            for k, v in self.headers:
+                if not isinstance(v, str):
+                    raise TypeError('header value must be string: %r' % v)
+
+            if isinstance(status, ErrorResponse):
+                status = statusmessage(status.code)
+            elif isinstance(status, int):
+                status = statusmessage(status)
+
+            self.server_write = self._start_response(status, self.headers)
+            self._start_response = None
+            self.headers = []
+
     def respond(self, status, *things):
+        if not things:
+            self.start_response(status)
         for thing in things:
             if hasattr(thing, "__iter__"):
                 for part in thing:
                     self.respond(status, part)
             else:
                 thing = str(thing)
-                if self.server_write is None:
-                    if not self.headers:
-                        raise RuntimeError("request.write called before headers sent (%s)." % thing)
-                    if isinstance(status, ErrorResponse):
-                        status = statusmessage(status.code)
-                    elif isinstance(status, int):
-                        status = statusmessage(status)
-                    self.server_write = self.start_response(status,
-                                                            self.headers)
-                    self.start_response = None
-                    self.headers = []
+                self.start_response(status)
                 try:
                     self.server_write(thing)
                 except socket.error, inst:
@@ -72,21 +80,23 @@
     def close(self):
         return None
 
-    def header(self, headers=[('Content-type','text/html')]):
+    def header(self, headers=[('Content-Type','text/html')]):
         self.headers.extend(headers)
 
     def httphdr(self, type, filename=None, length=0, headers={}):
         headers = headers.items()
-        headers.append(('Content-type', type))
+        headers.append(('Content-Type', type))
         if filename:
-            headers.append(('Content-disposition', 'attachment; filename=%s' %
+            headers.append(('Content-Disposition', 'inline; filename=%s' %
                             filename))
         if length:
-            headers.append(('Content-length', str(length)))
+            headers.append(('Content-Length', str(length)))
         self.header(headers)
 
 def wsgiapplication(app_maker):
+    '''For compatibility with old CGI scripts. A plain hgweb() or hgwebdir()
+    can and should now be used as a WSGI application.'''
     application = app_maker()
     def run_wsgi(env, respond):
-        application(env, respond)
+        return application(env, respond)
     return run_wsgi
--- a/mercurial/hgweb/webcommands.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/mercurial/hgweb/webcommands.py	Fri Jan 25 16:04:46 2008 -0800
@@ -5,16 +5,37 @@
 # 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 import revlog
-from common import staticfile
+import os, mimetypes
+from mercurial import revlog, util
+from common import staticfile, ErrorResponse
 
 def log(web, req, tmpl):
-    if req.form.has_key('file') and req.form['file'][0]:
+    if 'file' in req.form and req.form['file'][0]:
         filelog(web, req, tmpl)
     else:
         changelog(web, req, tmpl)
 
+def rawfile(web, req, tmpl):
+    path = web.cleanpath(req.form.get('file', [''])[0])
+    if not path:
+        req.write(web.manifest(tmpl, web.changectx(req), path))
+        return
+
+    try:
+        fctx = web.filectx(req)
+    except revlog.LookupError:
+        req.write(web.manifest(tmpl, web.changectx(req), path))
+        return
+
+    path = fctx.path()
+    text = fctx.data()
+    mt = mimetypes.guess_type(path)[0]
+    if mt is None or util.binary(text):
+        mt = mt or 'application/octet-stream'
+
+    req.httphdr(mt, path, len(text))
+    req.write(text)
+
 def file(web, req, tmpl):
     path = web.cleanpath(req.form.get('file', [''])[0])
     if path:
@@ -27,10 +48,10 @@
     req.write(web.manifest(tmpl, web.changectx(req), path))
 
 def changelog(web, req, tmpl, shortlog = False):
-    if req.form.has_key('node'):
+    if 'node' in req.form:
         ctx = web.changectx(req)
     else:
-        if req.form.has_key('rev'):
+        if 'rev' in req.form:
             hi = req.form['rev'][0]
         else:
             hi = web.repo.changelog.count() - 1
@@ -79,8 +100,7 @@
         web.archive(tmpl, req, req.form['node'][0], type_)
         return
 
-    req.respond(400, tmpl('error',
-                           error='Unsupported archive type: %s' % type_))
+    raise ErrorResponse(400, 'Unsupported archive type: %s' % type_)
 
 def static(web, req, tmpl):
     fname = req.form['file'][0]
--- a/mercurial/hook.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/mercurial/hook.py	Fri Jan 25 16:04:46 2008 -0800
@@ -71,7 +71,11 @@
 def _exthook(ui, repo, name, cmd, args, throw):
     ui.note(_("running hook %s: %s\n") % (name, cmd))
     env = dict([('HG_' + k.upper(), v) for k, v in args.iteritems()])
-    r = util.system(cmd, environ=env, cwd=repo.root)
+    if repo:
+        cwd = repo.root
+    else:
+        cwd = os.getcwd()
+    r = util.system(cmd, environ=env, cwd=cwd)
     if r:
         desc, r = util.explain_exit(r)
         if throw:
--- a/mercurial/httprepo.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/mercurial/httprepo.py	Fri Jan 25 16:04:46 2008 -0800
@@ -247,7 +247,7 @@
         # will take precedence if found, so drop them
         for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
             try:
-                if os.environ.has_key(env):
+                if env in os.environ:
                     del os.environ[env]
             except OSError:
                 pass
@@ -343,7 +343,7 @@
                 version = proto.split('-', 1)[1]
                 version_info = tuple([int(n) for n in version.split('.')])
             except ValueError:
-                raise repo.RepoError(_("'%s' sent a broken Content-type "
+                raise repo.RepoError(_("'%s' sent a broken Content-Type "
                                      "header (%s)") % (self._url, proto))
             if version_info > (0, 1):
                 raise repo.RepoError(_("'%s' uses newer protocol %s") %
@@ -428,7 +428,7 @@
             try:
                 rfp = self.do_cmd(
                     'unbundle', data=fp,
-                    headers={'content-type': 'application/octet-stream'},
+                    headers={'Content-Type': 'application/octet-stream'},
                     heads=' '.join(map(hex, heads)))
                 try:
                     ret = int(rfp.readline())
--- a/mercurial/keepalive.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/mercurial/keepalive.py	Fri Jan 25 16:04:46 2008 -0800
@@ -129,7 +129,7 @@
     def add(self, host, connection, ready):
         self._lock.acquire()
         try:
-            if not self._hostmap.has_key(host): self._hostmap[host] = []
+            if not host in self._hostmap: self._hostmap[host] = []
             self._hostmap[host].append(connection)
             self._connmap[connection] = host
             self._readymap[connection] = ready
@@ -159,7 +159,7 @@
         conn = None
         self._lock.acquire()
         try:
-            if self._hostmap.has_key(host):
+            if host in self._hostmap:
                 for c in self._hostmap[host]:
                     if self._readymap[c]:
                         self._readymap[c] = 0
--- a/mercurial/localrepo.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/mercurial/localrepo.py	Fri Jan 25 16:04:46 2008 -0800
@@ -687,6 +687,8 @@
                p1=None, p2=None, extra={}, empty_ok=False):
         wlock = lock = tr = None
         valid = 0 # don't save the dirstate if this isn't set
+        if files:
+            files = util.unique(files)
         try:
             commit = []
             remove = []
@@ -996,7 +998,7 @@
             mf2keys.sort()
             getnode = lambda fn: mf1.get(fn, nullid)
             for fn in mf2keys:
-                if mf1.has_key(fn):
+                if fn in mf1:
                     if (mf1.flags(fn) != mf2.flags(fn) or
                         (mf1[fn] != mf2[fn] and
                          (mf2[fn] != "" or fcmp(fn, getnode)))):
@@ -1510,7 +1512,7 @@
             for node in nodes:
                 self.ui.debug("%s\n" % hex(node))
 
-    def changegroupsubset(self, bases, heads, source):
+    def changegroupsubset(self, bases, heads, source, extranodes=None):
         """This function generates a changegroup consisting of all the nodes
         that are descendents of any of the bases, and ancestors of any of
         the heads.
@@ -1520,7 +1522,15 @@
         is non-trivial.
 
         Another wrinkle is doing the reverse, figuring out which changeset in
-        the changegroup a particular filenode or manifestnode belongs to."""
+        the changegroup a particular filenode or manifestnode belongs to.
+        
+        The caller can specify some nodes that must be included in the
+        changegroup using the extranodes argument.  It should be a dict
+        where the keys are the filenames (or 1 for the manifest), and the
+        values are lists of (node, linknode) tuples, where node is a wanted
+        node and linknode is the changelog node that should be transmitted as
+        the linkrev.
+        """
 
         self.hook('preoutgoing', throw=True, source=source)
 
@@ -1713,6 +1723,15 @@
                 return msngset[fnode]
             return lookup_filenode_link
 
+        # Add the nodes that were explicitly requested.
+        def add_extra_nodes(name, nodes):
+            if not extranodes or name not in extranodes:
+                return
+
+            for node, linknode in extranodes[name]:
+                if node not in nodes:
+                    nodes[node] = linknode
+
         # Now that we have all theses utility functions to help out and
         # logically divide up the task, generate the group.
         def gengroup():
@@ -1728,6 +1747,7 @@
             # The list of manifests has been collected by the generator
             # calling our functions back.
             prune_manifests()
+            add_extra_nodes(1, msng_mnfst_set)
             msng_mnfst_lst = msng_mnfst_set.keys()
             # Sort the manifestnodes by revision number.
             msng_mnfst_lst.sort(cmp_by_rev_func(mnfst))
@@ -1743,6 +1763,13 @@
             msng_mnfst_lst = None
             msng_mnfst_set.clear()
 
+            if extranodes:
+                for fname in extranodes:
+                    if isinstance(fname, int):
+                        continue
+                    add_extra_nodes(fname,
+                                    msng_filenode_set.setdefault(fname, {}))
+                    changedfiles[fname] = 1
             changedfiles = changedfiles.keys()
             changedfiles.sort()
             # Go through all our files in order sorted by name.
@@ -1752,7 +1779,7 @@
                     raise util.Abort(_("empty or missing revlog for %s") % fname)
                 # Toss out the filenodes that the recipient isn't really
                 # missing.
-                if msng_filenode_set.has_key(fname):
+                if fname in msng_filenode_set:
                     prune_filenodes(fname, filerevlog)
                     msng_filenode_lst = msng_filenode_set[fname].keys()
                 else:
@@ -1771,7 +1798,7 @@
                                              lookup_filenode_link_func(fname))
                     for chnk in group:
                         yield chnk
-                if msng_filenode_set.has_key(fname):
+                if fname in msng_filenode_set:
                     # Don't need this anymore, toss it to free memory.
                     del msng_filenode_set[fname]
             # Signal that no more groups are left.
@@ -1852,7 +1879,7 @@
 
         return util.chunkbuffer(gengroup())
 
-    def addchangegroup(self, source, srctype, url):
+    def addchangegroup(self, source, srctype, url, emptyok=False):
         """add changegroup to repo.
 
         return values:
@@ -1888,7 +1915,7 @@
             self.ui.status(_("adding changesets\n"))
             cor = cl.count() - 1
             chunkiter = changegroup.chunkiter(source)
-            if cl.addgroup(chunkiter, csmap, trp, 1) is None:
+            if cl.addgroup(chunkiter, csmap, trp, 1) is None and not emptyok:
                 raise util.Abort(_("received changelog group is empty"))
             cnr = cl.count() - 1
             changesets = cnr - cor
--- a/mercurial/patch.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/mercurial/patch.py	Fri Jan 25 16:04:46 2008 -0800
@@ -1372,7 +1372,8 @@
     try:
         p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
         try:
-            for line in patchlines: print >> p.tochild, line
+            for line in patchlines:
+                p.tochild.write(line + "\n")
             p.tochild.close()
             if p.wait(): return
             fp = os.fdopen(fd, 'r')
--- a/mercurial/repair.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/mercurial/repair.py	Fri Jan 25 16:04:46 2008 -0800
@@ -6,71 +6,86 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-import changegroup, revlog, os, commands
+import changegroup, os
+from node import *
 
-def strip(ui, repo, rev, backup="all"):
-    def limitheads(chlog, stop):
-        """return the list of all nodes that have no children"""
-        p = {}
-        h = []
-        stoprev = 0
-        if stop in chlog.nodemap:
-            stoprev = chlog.rev(stop)
+def _limitheads(cl, stoprev):
+    """return the list of all revs >= stoprev that have no children"""
+    seen = {}
+    heads = []
+
+    for r in xrange(cl.count() - 1, stoprev - 1, -1):
+        if r not in seen:
+            heads.append(r)
+        for p in cl.parentrevs(r):
+            seen[p] = 1
+    return heads
 
-        for r in xrange(chlog.count() - 1, -1, -1):
-            n = chlog.node(r)
-            if n not in p:
-                h.append(n)
-            if n == stop:
-                break
-            if r < stoprev:
-                break
-            for pn in chlog.parents(n):
-                p[pn] = 1
-        return h
+def _bundle(repo, bases, heads, node, suffix, extranodes=None):
+    """create a bundle with the specified revisions as a backup"""
+    cg = repo.changegroupsubset(bases, heads, 'strip', extranodes)
+    backupdir = repo.join("strip-backup")
+    if not os.path.isdir(backupdir):
+        os.mkdir(backupdir)
+    name = os.path.join(backupdir, "%s-%s" % (short(node), suffix))
+    repo.ui.warn("saving bundle to %s\n" % name)
+    return changegroup.writebundle(cg, name, "HG10BZ")
 
-    def bundle(repo, bases, heads, rev, suffix):
-        cg = repo.changegroupsubset(bases, heads, 'strip')
-        backupdir = repo.join("strip-backup")
-        if not os.path.isdir(backupdir):
-            os.mkdir(backupdir)
-        name = os.path.join(backupdir, "%s-%s" % (revlog.short(rev), suffix))
-        ui.warn("saving bundle to %s\n" % name)
-        return changegroup.writebundle(cg, name, "HG10BZ")
+def _collectfiles(repo, striprev):
+    """find out the filelogs affected by the strip"""
+    files = {}
+
+    for x in xrange(striprev, repo.changelog.count()):
+        for name in repo.changectx(x).files():
+            if name in files:
+                continue
+            files[name] = 1
+
+    files = files.keys()
+    files.sort()
+    return files
 
-    def stripall(revnum):
-        mm = repo.changectx(rev).manifest()
-        seen = {}
+def _collectextranodes(repo, files, link):
+    """return the nodes that have to be saved before the strip"""
+    def collectone(revlog):
+        extra = []
+        startrev = count = revlog.count()
+        # find the truncation point of the revlog
+        for i in xrange(0, count):
+            node = revlog.node(i)
+            lrev = revlog.linkrev(node)
+            if lrev >= link:
+                startrev = i + 1
+                break
+
+        # see if any revision after that point has a linkrev less than link
+        # (we have to manually save these guys)
+        for i in xrange(startrev, count):
+            node = revlog.node(i)
+            lrev = revlog.linkrev(node)
+            if lrev < link:
+                extra.append((node, cl.node(lrev)))
 
-        for x in xrange(revnum, repo.changelog.count()):
-            for f in repo.changectx(x).files():
-                if f in seen:
-                    continue
-                seen[f] = 1
-                if f in mm:
-                    filerev = mm[f]
-                else:
-                    filerev = 0
-                seen[f] = filerev
-        # we go in two steps here so the strip loop happens in a
-        # sensible order.  When stripping many files, this helps keep
-        # our disk access patterns under control.
-        seen_list = seen.keys()
-        seen_list.sort()
-        for f in seen_list:
-            ff = repo.file(f)
-            filerev = seen[f]
-            if filerev != 0:
-                if filerev in ff.nodemap:
-                    filerev = ff.rev(filerev)
-                else:
-                    filerev = 0
-            ff.strip(filerev, revnum)
+        return extra
 
-    chlog = repo.changelog
+    extranodes = {}
+    cl = repo.changelog
+    extra = collectone(repo.manifest)
+    if extra:
+        extranodes[1] = extra
+    for fname in files:
+        f = repo.file(fname)
+        extra = collectone(f)
+        if extra:
+            extranodes[fname] = extra
+
+    return extranodes
+
+def strip(ui, repo, node, backup="all"):
+    cl = repo.changelog
     # TODO delete the undo files, and handle undo of merge sets
-    pp = chlog.parents(rev)
-    revnum = chlog.rev(rev)
+    pp = cl.parents(node)
+    striprev = cl.rev(node)
 
     # save is a list of all the branches we are truncating away
     # that we actually want to keep.  changegroup will be used
@@ -78,7 +93,7 @@
     saveheads = []
     savebases = {}
 
-    heads = limitheads(chlog, rev)
+    heads = [cl.node(r) for r in _limitheads(cl, striprev)]
     seen = {}
 
     # search through all the heads, finding those where the revision
@@ -89,39 +104,48 @@
         n = h
         while True:
             seen[n] = 1
-            pp = chlog.parents(n)
-            if pp[1] != revlog.nullid:
+            pp = cl.parents(n)
+            if pp[1] != nullid:
                 for p in pp:
-                    if chlog.rev(p) > revnum and p not in seen:
+                    if cl.rev(p) > striprev and p not in seen:
                         heads.append(p)
-            if pp[0] == revlog.nullid:
+            if pp[0] == nullid:
                 break
-            if chlog.rev(pp[0]) < revnum:
+            if cl.rev(pp[0]) < striprev:
                 break
             n = pp[0]
-            if n == rev:
+            if n == node:
                 break
-        r = chlog.reachable(h, rev)
-        if rev not in r:
+        r = cl.reachable(h, node)
+        if node not in r:
             saveheads.append(h)
             for x in r:
-                if chlog.rev(x) > revnum:
+                if cl.rev(x) > striprev:
                     savebases[x] = 1
 
+    files = _collectfiles(repo, striprev)
+
+    extranodes = _collectextranodes(repo, files, striprev)
+
     # create a changegroup for all the branches we need to keep
     if backup == "all":
-        bundle(repo, [rev], chlog.heads(), rev, 'backup')
-    if saveheads:
-        chgrpfile = bundle(repo, savebases.keys(), saveheads, rev, 'temp')
-
-    stripall(revnum)
+        _bundle(repo, [node], cl.heads(), node, 'backup')
+    if saveheads or extranodes:
+        chgrpfile = _bundle(repo, savebases.keys(), saveheads, node, 'temp',
+                            extranodes)
 
-    change = chlog.read(rev)
-    chlog.strip(revnum, revnum)
-    repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
-    if saveheads:
+    cl.strip(striprev)
+    repo.manifest.strip(striprev)
+    for name in files:
+        f = repo.file(name)
+        f.strip(striprev)
+
+    if saveheads or extranodes:
         ui.status("adding branch\n")
-        commands.unbundle(ui, repo, "file:%s" % chgrpfile, update=False)
+        f = open(chgrpfile, "rb")
+        gen = changegroup.readbundle(f, chgrpfile)
+        repo.addchangegroup(gen, 'strip', 'bundle:' + chgrpfile, True)
+        f.close()
         if backup != "strip":
             os.unlink(chgrpfile)
 
--- a/mercurial/revlog.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/mercurial/revlog.py	Fri Jan 25 16:04:46 2008 -0800
@@ -1237,21 +1237,31 @@
 
         return node
 
-    def strip(self, rev, minlink):
-        if self.count() == 0 or rev >= self.count():
+    def strip(self, minlink):
+        """truncate the revlog on the first revision with a linkrev >= minlink
+
+        This function is called when we're stripping revision minlink and
+        its descendants from the repository.
+
+        We have to remove all revisions with linkrev >= minlink, because
+        the equivalent changelog revisions will be renumbered after the
+        strip.
+
+        So we truncate the revlog on the first of these revisions, and
+        trust that the caller has saved the revisions that shouldn't be
+        removed and that it'll readd them after this truncation.
+        """
+        if self.count() == 0:
             return
 
         if isinstance(self.index, lazyindex):
             self._loadindexmap()
 
-        # When stripping away a revision, we need to make sure it
-        # does not actually belong to an older changeset.
-        # The minlink parameter defines the oldest revision
-        # we're allowed to strip away.
-        while minlink > self.index[rev][4]:
-            rev += 1
-            if rev >= self.count():
-                return
+        for rev in xrange(0, self.count()):
+            if self.index[rev][4] >= minlink:
+                break
+        else:
+            return
 
         # first truncate the files on disk
         end = self.start(rev)
--- a/mercurial/sshrepo.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/mercurial/sshrepo.py	Fri Jan 25 16:04:46 2008 -0800
@@ -195,7 +195,7 @@
         r = self.pipei.read(l)
         if r:
             # remote may send "unsynced changes"
-            self.raise_(hg.RepoError(_("push failed: %s") % r))
+            self.raise_(repo.RepoError(_("push failed: %s") % r))
 
         self.readerr()
         l = int(self.pipei.readline())
--- a/mercurial/templater.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/mercurial/templater.py	Fri Jan 25 16:04:46 2008 -0800
@@ -82,7 +82,7 @@
         '''perform expansion.
         t is name of map element to expand.
         map is added elements to use during expansion.'''
-        if not self.cache.has_key(t):
+        if not t in self.cache:
             try:
                 self.cache[t] = file(self.map[t]).read()
             except IOError, inst:
--- a/mercurial/ui.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/mercurial/ui.py	Fri Jan 25 16:04:46 2008 -0800
@@ -204,7 +204,8 @@
                     pathsitems = items
                 for n, path in pathsitems:
                     if path and "://" not in path and not os.path.isabs(path):
-                        cdata.set("paths", n, os.path.join(root, path))
+                        cdata.set("paths", n,
+                                  os.path.normpath(os.path.join(root, path)))
 
         # update verbosity/interactive/report_untrusted settings
         if section is None or section == 'ui':
--- a/mercurial/util.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/mercurial/util.py	Fri Jan 25 16:04:46 2008 -0800
@@ -15,7 +15,7 @@
 from i18n import _
 import cStringIO, errno, getpass, popen2, re, shutil, sys, tempfile, strutil
 import os, stat, threading, time, calendar, ConfigParser, locale, glob, osutil
-import re, urlparse
+import urlparse
 
 try:
     set = set
--- a/mercurial/util_win32.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/mercurial/util_win32.py	Fri Jan 25 16:04:46 2008 -0800
@@ -147,9 +147,18 @@
                          self.win_strerror)
 
 def os_link(src, dst):
-    # NB will only succeed on NTFS
     try:
         win32file.CreateHardLink(dst, src)
+        # CreateHardLink sometimes succeeds on mapped drives but
+        # following nlinks() returns 1. Check it now and bail out.
+        if nlinks(src) < 2:
+            try:
+                win32file.DeleteFile(dst)
+            except:
+                pass
+            # Fake hardlinking error
+            raise WinOSError((18, 'CreateHardLink', 'The system cannot '
+                              'move the file to a different disk drive'))
     except pywintypes.error, details:
         raise WinOSError(details)
 
--- a/mercurial/version.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/mercurial/version.py	Fri Jan 25 16:04:46 2008 -0800
@@ -1,4 +1,4 @@
-# Copyright (C) 2005, 2006 by Intevation GmbH
+# Copyright (C) 2005, 2006, 2008 by Intevation GmbH
 # Author(s):
 # Thomas Arendsen Hein <thomas@intevation.de>
 #
@@ -10,7 +10,6 @@
 """
 
 import os
-import os.path
 import re
 import time
 import util
--- a/templates/atom/header.tmpl	Fri Jan 25 16:04:32 2008 -0800
+++ b/templates/atom/header.tmpl	Fri Jan 25 16:04:46 2008 -0800
@@ -1,4 +1,2 @@
-Content-type: application/atom+xml; charset={encoding}
-
 <?xml version="1.0" encoding="{encoding}"?>
 <feed xmlns="http://www.w3.org/2005/Atom">
\ No newline at end of file
--- a/templates/atom/map	Fri Jan 25 16:04:32 2008 -0800
+++ b/templates/atom/map	Fri Jan 25 16:04:46 2008 -0800
@@ -1,5 +1,6 @@
 default = 'changelog'
 feedupdated = '<updated>#date|rfc3339date#</updated>'
+mimetype = 'application/atom+xml; charset={encoding}'
 header = header.tmpl
 changelog = changelog.tmpl
 changelogentry = changelogentry.tmpl
--- a/templates/gitweb/header.tmpl	Fri Jan 25 16:04:32 2008 -0800
+++ b/templates/gitweb/header.tmpl	Fri Jan 25 16:04:46 2008 -0800
@@ -1,5 +1,3 @@
-Content-type: text/html; charset={encoding}
-
 <?xml version="1.0" encoding="{encoding}"?>
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
--- a/templates/gitweb/map	Fri Jan 25 16:04:32 2008 -0800
+++ b/templates/gitweb/map	Fri Jan 25 16:04:46 2008 -0800
@@ -1,4 +1,5 @@
 default = 'summary'
+mimetype = 'text/html; charset={encoding}'
 header = header.tmpl
 footer = footer.tmpl
 search = search.tmpl
--- a/templates/header.tmpl	Fri Jan 25 16:04:32 2008 -0800
+++ b/templates/header.tmpl	Fri Jan 25 16:04:46 2008 -0800
@@ -1,5 +1,3 @@
-Content-type: text/html; charset={encoding}
-
 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
 <html>
 <head>
--- a/templates/map	Fri Jan 25 16:04:32 2008 -0800
+++ b/templates/map	Fri Jan 25 16:04:46 2008 -0800
@@ -1,4 +1,5 @@
 default = 'shortlog'
+mimetype = 'text/html; charset={encoding}'
 header = header.tmpl
 footer = footer.tmpl
 search = search.tmpl
--- a/templates/old/header.tmpl	Fri Jan 25 16:04:32 2008 -0800
+++ b/templates/old/header.tmpl	Fri Jan 25 16:04:46 2008 -0800
@@ -1,5 +1,3 @@
-Content-type: text/html
-
 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
 <html>
 <head>
--- a/templates/old/map	Fri Jan 25 16:04:32 2008 -0800
+++ b/templates/old/map	Fri Jan 25 16:04:46 2008 -0800
@@ -1,4 +1,5 @@
 default = 'changelog'
+mimetype = 'text/html'
 header = header.tmpl
 footer = footer.tmpl
 search = search.tmpl
--- a/templates/raw/header.tmpl	Fri Jan 25 16:04:32 2008 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2 +0,0 @@
-Content-type: text/plain; charset={encoding}
-
--- a/templates/raw/map	Fri Jan 25 16:04:32 2008 -0800
+++ b/templates/raw/map	Fri Jan 25 16:04:46 2008 -0800
@@ -1,4 +1,5 @@
-header = header.tmpl
+mimetype = 'text/plain; charset={encoding}'
+header = ''
 footer = ''
 changeset = changeset.tmpl
 difflineplus = '#line#'
@@ -8,7 +9,6 @@
 changesetparent = '# Parent #node#'
 changesetchild = '# Child #node#'
 filenodelink = ''
-filerevision = '#rawfileheader##raw#'
 fileline = '#line#'
 diffblock = '#lines#'
 filediff = filediff.tmpl
--- a/templates/rss/header.tmpl	Fri Jan 25 16:04:32 2008 -0800
+++ b/templates/rss/header.tmpl	Fri Jan 25 16:04:46 2008 -0800
@@ -1,5 +1,3 @@
-Content-type: text/xml; charset={encoding}
-
 <?xml version="1.0" encoding="{encoding}"?>
 <rss version="2.0">
   <channel>
--- a/templates/rss/map	Fri Jan 25 16:04:32 2008 -0800
+++ b/templates/rss/map	Fri Jan 25 16:04:46 2008 -0800
@@ -1,4 +1,5 @@
 default = 'changelog'
+mimetype = 'text/xml; charset={encoding}'
 header = header.tmpl
 changelog = changelog.tmpl
 changelogentry = changelogentry.tmpl
--- a/tests/coverage.py	Fri Jan 25 16:04:32 2008 -0800
+++ b/tests/coverage.py	Fri Jan 25 16:04:46 2008 -0800
@@ -186,9 +186,9 @@
                 return 0
             # If this line is excluded, or suite_spots maps this line to
             # another line that is exlcuded, then we're excluded.
-            elif self.excluded.has_key(lineno) or \
-                 self.suite_spots.has_key(lineno) and \
-                 self.excluded.has_key(self.suite_spots[lineno][1]):
+            elif lineno in self.excluded or \
+                 lineno in self.suite_spots and \
+                 self.suite_spots[lineno][1] in self.excluded:
                 return 0
             # Otherwise, this is an executable line.
             else:
@@ -217,8 +217,8 @@
         lastprev = self.getLastLine(prevsuite)
         firstelse = self.getFirstLine(suite)
         for l in range(lastprev+1, firstelse):
-            if self.suite_spots.has_key(l):
-                self.doSuite(None, suite, exclude=self.excluded.has_key(l))
+            if l in self.suite_spots:
+                self.doSuite(None, suite, l in exclude=self.excluded)
                 break
         else:
             self.doSuite(None, suite)
@@ -353,9 +353,9 @@
         long_opts = optmap.values()
         options, args = getopt.getopt(argv, short_opts, long_opts)
         for o, a in options:
-            if optmap.has_key(o):
+            if o in optmap:
                 settings[optmap[o]] = 1
-            elif optmap.has_key(o + ':'):
+            elif o + ':' in optmap:
                 settings[optmap[o + ':']] = a
             elif o[2:] in long_opts:
                 settings[o[2:]] = 1
@@ -512,14 +512,14 @@
 
     def merge_data(self, new_data):
         for file_name, file_data in new_data.items():
-            if self.cexecuted.has_key(file_name):
+            if file_name in self.cexecuted:
                 self.merge_file_data(self.cexecuted[file_name], file_data)
             else:
                 self.cexecuted[file_name] = file_data
 
     def merge_file_data(self, cache_data, new_data):
         for line_number in new_data.keys():
-            if not cache_data.has_key(line_number):
+            if not line_number in cache_data:
                 cache_data[line_number] = new_data[line_number]
 
     # canonical_filename(filename).  Return a canonical filename for the
@@ -527,7 +527,7 @@
     # normalized case).  See [GDR 2001-12-04b, 3.3].
 
     def canonical_filename(self, filename):
-        if not self.canonical_filename_cache.has_key(filename):
+        if not filename in self.canonical_filename_cache:
             f = filename
             if os.path.isabs(f) and not os.path.exists(f):
                 f = os.path.basename(f)
@@ -550,7 +550,7 @@
                 # Can't do anything useful with exec'd strings, so skip them.
                 continue
             f = self.canonical_filename(filename)
-            if not self.cexecuted.has_key(f):
+            if not f in self.cexecuted:
                 self.cexecuted[f] = {}
             self.cexecuted[f][lineno] = 1
         self.c = {}
@@ -575,7 +575,7 @@
     # statements that cross lines.
 
     def analyze_morf(self, morf):
-        if self.analysis_cache.has_key(morf):
+        if morf in self.analysis_cache:
             return self.analysis_cache[morf]
         filename = self.morf_filename(morf)
         ext = os.path.splitext(filename)[1]
@@ -752,13 +752,13 @@
     def analysis2(self, morf):
         filename, statements, excluded, line_map = self.analyze_morf(morf)
         self.canonicalize_filenames()
-        if not self.cexecuted.has_key(filename):
+        if not filename in self.cexecuted:
             self.cexecuted[filename] = {}
         missing = []
         for line in statements:
             lines = line_map.get(line, [line, line])
             for l in range(lines[0], lines[1]+1):
-                if self.cexecuted[filename].has_key(l):
+                if l in self.cexecuted[filename]:
                     break
             else:
                 missing.append(line)
--- a/tests/test-archive	Fri Jan 25 16:04:32 2008 -0800
+++ b/tests/test-archive	Fri Jan 25 16:04:46 2008 -0800
@@ -13,7 +13,7 @@
 echo "[web]" >> .hg/hgrc
 echo "name = test-archive" >> .hg/hgrc
 echo "allow_archive = gz bz2, zip" >> .hg/hgrc
-hg serve -p $HGPORT -d --pid-file=hg.pid
+hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
 cat hg.pid >> $DAEMON_PIDS
 
 TIP=`hg id -v | cut -f1 -d' '`
@@ -69,8 +69,12 @@
     echo 'rev-0.tar created'
 fi
 
+echo % server errors
+cat errors.log
+
 echo '% empty repo'
 hg init ../empty
 cd ../empty
 hg archive ../test-empty
+
 exit 0
--- a/tests/test-archive.out	Fri Jan 25 16:04:32 2008 -0800
+++ b/tests/test-archive.out	Fri Jan 25 16:04:46 2008 -0800
@@ -39,5 +39,6 @@
 test-TIP/baz/bletch
 test-TIP/foo
 rev-0.tar created
+% server errors
 % empty repo
 abort: repository has no revisions
--- a/tests/test-convert-cvs	Fri Jan 25 16:04:32 2008 -0800
+++ b/tests/test-convert-cvs	Fri Jan 25 16:04:46 2008 -0800
@@ -71,3 +71,25 @@
 cat src-hg/b/c
 hg -R src-filemap log --template '#rev# #desc# files: #files#\n'
 
+echo % commit branch
+cd src
+cvs -q update -r1.1 b/c
+cvs -q tag -b branch
+cvs -q update -r branch
+echo d >> b/c
+cvs -q commit -mci2 . | grep '<--' |\
+    sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
+cd ..
+
+echo % convert again
+hg convert src src-hg | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
+cat src-hg/a
+cat src-hg/b/c
+
+echo % convert again with --filemap
+hg convert --filemap filemap src src-filemap | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
+cat src-hg/b/c
+hg -R src-filemap log --template '#rev# #desc# files: #files#\n'
+
+echo "graphlog = " >> $HGRCPATH
+hg -R src-hg glog --template '#rev# (#branches#) #desc# files: #files#\n'
--- a/tests/test-convert-cvs.out	Fri Jan 25 16:04:32 2008 -0800
+++ b/tests/test-convert-cvs.out	Fri Jan 25 16:04:46 2008 -0800
@@ -67,3 +67,43 @@
 2 update tags files: .hgtags
 1 ci0 files: b/c
 0 Initial revision files: b/c
+% commit branch
+U b/c
+T a
+T b/c
+checking in src/b/c,v
+% convert again
+connecting to cvsrepo
+scanning source...
+sorting...
+converting...
+0 ci2
+a
+a
+c
+d
+% convert again with --filemap
+connecting to cvsrepo
+scanning source...
+sorting...
+converting...
+0 ci2
+c
+d
+4 ci2 files: b/c
+3 ci1 files: b/c
+2 update tags files: .hgtags
+1 ci0 files: b/c
+0 Initial revision files: b/c
+o  5 (branch) ci2 files: b/c
+|
+| o  4 () ci1 files: a b/c
+| |
+| o  3 () update tags files: .hgtags
+| |
+| o  2 () ci0 files: b/c
+|/
+| o  1 (INITIAL) import files:
+|/
+o  0 () Initial revision files: a b/c
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-convert-svn-branches	Fri Jan 25 16:04:46 2008 -0800
@@ -0,0 +1,89 @@
+#!/bin/sh
+
+"$TESTDIR/hghave" svn svn-bindings || exit 80
+
+fix_path()
+{
+    tr '\\' /
+}
+
+echo "[extensions]" >> $HGRCPATH
+echo "convert = " >> $HGRCPATH
+echo "hgext.graphlog =" >> $HGRCPATH
+
+svnadmin create svn-repo
+
+svnpath=`pwd | fix_path`
+# SVN wants all paths to start with a slash. Unfortunately,
+# Windows ones don't. Handle that.
+expr $svnpath : "\/" > /dev/null
+if [ $? -ne 0 ]; then
+    svnpath='/'$svnpath
+fi
+
+echo % initial svn import
+mkdir projA
+cd projA
+mkdir trunk
+mkdir branches
+mkdir tags
+cd ..
+
+svnurl=file://$svnpath/svn-repo/projA
+svn import -m "init projA" projA $svnurl | fix_path
+
+echo % update svn repository
+svn co $svnurl A | fix_path
+cd A
+echo hello > trunk/letter.txt
+echo hey > trunk/letter2.txt
+echo ho > trunk/letter3.txt
+svn add trunk/letter.txt trunk/letter2.txt trunk/letter3.txt
+svn ci -m hello
+
+echo % branch to old letters
+svn copy trunk branches/old
+svn rm branches/old/letter3.txt
+svn ci -m "branch trunk, remove letter3"
+svn up
+
+echo % update trunk
+echo "what can I say ?" >> trunk/letter.txt
+svn ci -m "change letter"
+
+echo % update old branch
+echo "what's up ?" >> branches/old/letter2.txt
+svn ci -m "change letter2"
+
+echo % create a cross-branch revision
+svn move -m "move letter2" trunk/letter2.txt \
+    branches/old/letter3.txt
+echo "I am fine" >> branches/old/letter3.txt
+svn ci -m "move and update letter3.txt"
+
+echo % update old branch again
+echo "bye" >> branches/old/letter2.txt
+svn ci -m "change letter2 again"
+
+echo % update trunk again
+echo "how are you ?" >> trunk/letter.txt
+svn ci -m "last change to letter"
+cd ..
+
+echo % convert trunk and branches
+hg convert --datesort $svnurl A-hg
+
+echo % branch again from a converted revision
+cd A
+svn copy -r 1 $svnurl/trunk branches/old2
+svn ci -m "branch trunk@1 into old2"
+cd ..
+
+echo % convert again
+hg convert --datesort $svnurl A-hg
+
+cd A-hg
+hg glog --template '#rev# #desc|firstline# files: #files#\n'
+hg branches | sed 's/:.*/:/'
+hg tags -q
+cd ..
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-convert-svn-branches.out	Fri Jan 25 16:04:46 2008 -0800
@@ -0,0 +1,101 @@
+% initial svn import
+Adding         projA/trunk
+Adding         projA/branches
+Adding         projA/tags
+
+Committed revision 1.
+% update svn repository
+A    A/trunk
+A    A/branches
+A    A/tags
+Checked out revision 1.
+A         trunk/letter.txt
+A         trunk/letter2.txt
+A         trunk/letter3.txt
+Adding         trunk/letter.txt
+Adding         trunk/letter2.txt
+Adding         trunk/letter3.txt
+Transmitting file data ...
+Committed revision 2.
+% branch to old letters
+A         branches/old
+D         branches/old/letter3.txt
+Adding         branches/old
+Adding         branches/old/letter.txt
+Adding         branches/old/letter2.txt
+Deleting       branches/old/letter3.txt
+
+Committed revision 3.
+At revision 3.
+% update trunk
+Sending        trunk/letter.txt
+Transmitting file data .
+Committed revision 4.
+% update old branch
+Sending        branches/old/letter2.txt
+Transmitting file data .
+Committed revision 5.
+% create a cross-branch revision
+A         branches/old/letter3.txt
+D         trunk/letter2.txt
+Adding         branches/old/letter3.txt
+Deleting       trunk/letter2.txt
+Transmitting file data .
+Committed revision 6.
+% update old branch again
+Sending        branches/old/letter2.txt
+Transmitting file data .
+Committed revision 7.
+% update trunk again
+Sending        trunk/letter.txt
+Transmitting file data .
+Committed revision 8.
+% convert trunk and branches
+initializing destination A-hg repository
+scanning source...
+sorting...
+converting...
+8 init projA
+7 hello
+6 branch trunk, remove letter3
+5 change letter
+4 change letter2
+3 move and update letter3.txt
+2 move and update letter3.txt
+1 change letter2 again
+0 last change to letter
+% branch again from a converted revision
+Checked out revision 1.
+A         branches/old2
+Adding         branches/old2
+
+Committed revision 9.
+% convert again
+scanning source...
+sorting...
+converting...
+0 branch trunk@1 into old2
+o  9 branch trunk@1 into old2 files:
+|
+| o  8 last change to letter files: letter.txt
+| |
+| | o  7 change letter2 again files: letter2.txt
+| | |
+| o |  6 move and update letter3.txt files: letter2.txt
+| | |
+| | o  5 move and update letter3.txt files: letter3.txt
+| | |
+| | o  4 change letter2 files: letter2.txt
+| | |
+| o |  3 change letter files: letter.txt
+| | |
++---o  2 branch trunk, remove letter3 files: letter.txt letter2.txt
+| |
+| o  1 hello files: letter.txt letter2.txt letter3.txt
+|/
+o  0 init projA files:
+
+old2                           9:
+default                        8:
+old                            7:
+tip
--- a/tests/test-convert-svn-source	Fri Jan 25 16:04:32 2008 -0800
+++ b/tests/test-convert-svn-source	Fri Jan 25 16:04:46 2008 -0800
@@ -103,13 +103,16 @@
 echo % update svn repository again
 cd A
 echo "see second letter" >> letter.txt
-echo "nice to meet you" > letter2.txt
-svn add letter2.txt
+# Put it in a subdirectory to test duplicate file records
+# from svn source (issue 714)
+mkdir todo
+echo "nice to meet you" > todo/letter2.txt
+svn add todo
 svn ci -m "second letter"
 
 svn copy -m "tag v0.2" $svnurl/trunk $svnurl/tags/v0.2
 
-echo "blah-blah-blah" >> letter2.txt
+echo "blah-blah-blah" >> todo/letter2.txt
 svn ci -m "work in progress"
 cd ..
 
--- a/tests/test-convert-svn-source.out	Fri Jan 25 16:04:32 2008 -0800
+++ b/tests/test-convert-svn-source.out	Fri Jan 25 16:04:46 2008 -0800
@@ -82,14 +82,16 @@
 0 nice day
 updating tags
 % update svn repository again
-A         letter2.txt
+A         todo
+A         todo/letter2.txt
 Sending        letter.txt
-Adding         letter2.txt
+Adding         todo
+Adding         todo/letter2.txt
 Transmitting file data ..
 Committed revision 9.
 
 Committed revision 10.
-Sending        letter2.txt
+Sending        todo/letter2.txt
 Transmitting file data .
 Committed revision 11.
 % test incremental conversion
@@ -101,9 +103,9 @@
 updating tags
 o  7 update tags files: .hgtags
 |
-o  6 work in progress files: letter2.txt
+o  6 work in progress files: todo/letter2.txt
 |
-o  5 second letter files: letter.txt letter2.txt
+o  5 second letter files: letter.txt todo/letter2.txt
 |
 o  4 update tags files: .hgtags
 |
--- a/tests/test-glog	Fri Jan 25 16:04:32 2008 -0800
+++ b/tests/test-glog	Fri Jan 25 16:04:46 2008 -0800
@@ -139,5 +139,8 @@
 echo % glog
 hg glog
 
+echo % file glog
+hg glog 5
+
 echo % unused arguments
-hg glog -q foo || echo failed
+hg glog -q foo bar || echo failed
--- a/tests/test-glog.out	Fri Jan 25 16:04:32 2008 -0800
+++ b/tests/test-glog.out	Fri Jan 25 16:04:46 2008 -0800
@@ -307,9 +307,17 @@
    date:        Thu Jan 01 00:00:00 1970 +0000
    summary:     (0) root
 
+% file glog
+o  changeset:   5:3589c3c477ab
+   parent:      3:02173ffbf857
+   parent:      4:e2cad8233c77
+   user:        test
+   date:        Thu Jan 01 00:00:05 1970 +0000
+   summary:     (5) expand
+
 % unused arguments
 hg glog: invalid arguments
-hg glog [OPTION]...
+hg glog [OPTION]... [FILE]
 
 show revision history alongside an ASCII revision graph
 failed
--- a/tests/test-hgweb	Fri Jan 25 16:04:32 2008 -0800
+++ b/tests/test-hgweb	Fri Jan 25 16:04:46 2008 -0800
@@ -7,7 +7,7 @@
 echo foo > da/foo
 echo foo > foo
 hg ci -Ambase -d '0 0'
-hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log
+hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
 cat hg.pid >> $DAEMON_PIDS
 echo % manifest
 ("$TESTDIR/get-with-headers.py" localhost:$HGPORT '/file/tip/?style=raw')
@@ -37,3 +37,6 @@
 
 echo % static file
 "$TESTDIR/get-with-headers.py" localhost:$HGPORT '/static/style-gitweb.css'
+
+echo % errors
+cat errors.log
--- a/tests/test-hgweb-commands	Fri Jan 25 16:04:32 2008 -0800
+++ b/tests/test-hgweb-commands	Fri Jan 25 16:04:46 2008 -0800
@@ -27,6 +27,7 @@
 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/file/1/foo/?style=raw'
 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/annotate/1/foo/?style=raw'
 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/file/1/?style=raw'
+"$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/file/1/foo' | sed "s/[0-9]* years/many years/"
 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/filediff/1/foo/?style=raw'
 
 echo % Overviews
Binary file tests/test-hgweb-commands.out has changed
--- a/tests/test-hgweb-no-request-uri.out	Fri Jan 25 16:04:32 2008 -0800
+++ b/tests/test-hgweb-no-request-uri.out	Fri Jan 25 16:04:46 2008 -0800
@@ -7,7 +7,7 @@
 ---- HEADERS
 200 Script output follows
 ---- DATA
-[('content-type', 'application/atom+xml; charset=ascii')]
+[('Content-Type', 'application/atom+xml; charset=ascii')]
 <?xml version="1.0" encoding="ascii"?>
 <feed xmlns="http://www.w3.org/2005/Atom">
  <!-- Changelog -->
@@ -41,7 +41,7 @@
 ---- HEADERS
 200 Script output follows
 ---- DATA
-[('content-type', 'text/plain; charset=ascii')]
+[('Content-Type', 'text/plain; charset=ascii')]
 
 -rw-r--r-- 4 bar
 
@@ -52,7 +52,7 @@
 ---- HEADERS
 200 Script output follows
 ---- DATA
-[('content-type', 'text/plain; charset=ascii')]
+[('Content-Type', 'text/plain; charset=ascii')]
 
 /repo/
 
@@ -62,7 +62,7 @@
 ---- HEADERS
 200 Script output follows
 ---- DATA
-[('content-type', 'text/plain; charset=ascii')]
+[('Content-Type', 'text/plain; charset=ascii')]
 
 -rw-r--r-- 4 bar
 
--- a/tests/test-hgweb.out	Fri Jan 25 16:04:32 2008 -0800
+++ b/tests/test-hgweb.out	Fri Jan 25 16:04:46 2008 -0800
@@ -136,3 +136,4 @@
 	background-color: #aaffaa;
 	border-color: #ccffcc #00cc33 #00cc33 #ccffcc;
 }
+% errors
--- a/tests/test-import	Fri Jan 25 16:04:32 2008 -0800
+++ b/tests/test-import	Fri Jan 25 16:04:46 2008 -0800
@@ -32,6 +32,13 @@
 hg --cwd b import -mpatch ../tip.patch
 rm -r b
 
+echo % import of plain diff should be ok with --no-commit
+hg clone -r0 a b
+hg --cwd a diff -r0:1 > tip.patch
+hg --cwd b import --no-commit ../tip.patch
+hg --cwd b diff --nodates
+rm -r b
+
 echo % hg -R repo import
 # put the clone in a subdir - having a directory named "a"
 # used to hide a bug.
--- a/tests/test-import.out	Fri Jan 25 16:04:32 2008 -0800
+++ b/tests/test-import.out	Fri Jan 25 16:04:46 2008 -0800
@@ -31,6 +31,20 @@
 added 1 changesets with 2 changes to 2 files
 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 applying ../tip.patch
+% import of plain diff should be ok with --no-commit
+requesting all changes
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 2 changes to 2 files
+2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+applying ../tip.patch
+diff -r 80971e65b431 a
+--- a/a
++++ b/a
+@@ -1,1 +1,2 @@
+ line 1
++line 2
 % hg -R repo import
 requesting all changes
 adding changesets
--- a/tests/test-journal-exists	Fri Jan 25 16:04:32 2008 -0800
+++ b/tests/test-journal-exists	Fri Jan 25 16:04:46 2008 -0800
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/bin/sh
 
 hg init
 echo a > a
--- a/tests/test-keyword	Fri Jan 25 16:04:32 2008 -0800
+++ b/tests/test-keyword	Fri Jan 25 16:04:46 2008 -0800
@@ -3,6 +3,7 @@
 cat <<EOF >> $HGRCPATH
 [extensions]
 hgext.keyword =
+hgext.mq =
 [keyword]
 * =
 b = ignore
@@ -88,9 +89,19 @@
 hg -v kwexpand
 echo % compare changenodes in a c
 cat a c
-echo % rollback and remove c
-hg rollback
-rm c
+
+echo % qimport
+hg qimport -r tip -n mqtest.diff
+echo % keywords should not be expanded in patch
+cat .hg/patches/mqtest.diff
+echo % qpop
+hg qpop
+echo % qgoto - should imply qpush
+hg qgoto mqtest.diff
+echo % cat
+cat c
+echo % qpop and move on
+hg qpop
 
 echo % copy
 hg cp a c
--- a/tests/test-keyword.out	Fri Jan 25 16:04:32 2008 -0800
+++ b/tests/test-keyword.out	Fri Jan 25 16:04:46 2008 -0800
@@ -36,6 +36,10 @@
 To force expansion after enabling it, or a configuration change, run
 "hg kwexpand".
 
+Also, when committing with the record extension or using mq's qrecord, be aware
+that keywords cannot be updated. Again, run "hg kwexpand" on the files in
+question to update keyword expansions after all changes have been checked in.
+
 Expansions spanning more than one line and incremental expansions,
 like CVS' $Log$, are not supported. A keyword template map
 "Log = {desc}" expands to the first line of the changeset description.
@@ -158,8 +162,31 @@
 xxx $
 $Id: c,v ba4426d1938e 1970/01/01 00:00:01 user $
 tests for different changenodes
-% rollback and remove c
-rolling back last transaction
+% qimport
+% keywords should not be expanded in patch
+# HG changeset patch
+# User User Name <user@example.com>
+# Date 1 0
+# Node ID ba4426d1938ec9673e03ab274d88c44e24618f7f
+# Parent  f782df5f9602483b4e51c31a12315f353bba380c
+cndiff
+
+diff -r f782df5f9602 -r ba4426d1938e c
+--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
++++ b/c	Thu Jan 01 00:00:01 1970 +0000
+@@ -0,0 +1,2 @@
++$Id$
++tests for different changenodes
+% qpop
+Patch queue now empty
+% qgoto - should imply qpush
+applying mqtest.diff
+Now at: mqtest.diff
+% cat
+$Id: c,v ba4426d1938e 1970/01/01 00:00:01 user $
+tests for different changenodes
+% qpop and move on
+Patch queue now empty
 % copy
 % kwfiles added
 a
@@ -184,7 +211,7 @@
 diff -r f782df5f9602 c
 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
 @@ -0,0 +1,3 @@
-+expand $Id: c,v 0ba462c0f077 1970/01/01 00:00:01 user $
++expand $Id$
 +do not process $Id:
 +xxx $
 % rollback
@@ -266,7 +293,7 @@
 added 1 changesets with 3 changes to 3 files
 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 % incoming
-comparing with test-keyword/Test-a/../Test
+comparing with test-keyword/Test
 searching for changes
 changeset:   1:0729690beff6
 tag:         tip
--- a/tests/test-mq.out	Fri Jan 25 16:04:32 2008 -0800
+++ b/tests/test-mq.out	Fri Jan 25 16:04:46 2008 -0800
@@ -310,7 +310,6 @@
 adding manifests
 adding file changes
 added 1 changesets with 1 changes to 1 files
-(run 'hg update' to get a working copy)
 Patch queue now empty
 applying bar
 Now at: bar
@@ -344,7 +343,6 @@
 adding manifests
 adding file changes
 added 1 changesets with 1 changes to 1 files
-(run 'hg update' to get a working copy)
 Patch queue now empty
 applying bar
 Now at: bar
@@ -419,7 +417,6 @@
 adding manifests
 adding file changes
 added 1 changesets with 1 changes to 1 files
-(run 'hg update' to get a working copy)
 changeset:   1:20cbbe65cff7
 tag:         tip
 user:        test
--- a/tests/test-no-symlinks.out	Fri Jan 25 16:04:32 2008 -0800
+++ b/tests/test-no-symlinks.out	Fri Jan 25 16:04:46 2008 -0800
@@ -6,6 +6,7 @@
 a
 d/b
 % bundle
+2 changesets found
 pulling from ../symlinks.hg
 requesting all changes
 adding changesets
--- a/tests/test-non-interactive-wsgi.out	Fri Jan 25 16:04:32 2008 -0800
+++ b/tests/test-non-interactive-wsgi.out	Fri Jan 25 16:04:46 2008 -0800
@@ -7,6 +7,6 @@
 ---- HEADERS
 200 Script output follows
 ---- DATA
-[('content-type', 'text/html; charset=ascii')]
+[('Content-Type', 'text/html; charset=ascii')]
 ---- ERRORS
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-paths	Fri Jan 25 16:04:46 2008 -0800
@@ -0,0 +1,11 @@
+#!/bin/sh
+base=`pwd`
+hg init a
+hg clone a b
+cd a
+echo '[paths]' >> .hg/hgrc
+echo 'dupe = ../b' >> .hg/hgrc
+hg in dupe | sed "s!$base!<base>!g"
+cd ..
+hg -R a in dupe | sed "s!$base!<base>!g"
+true
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-paths.out	Fri Jan 25 16:04:46 2008 -0800
@@ -0,0 +1,5 @@
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+comparing with <base>/b
+no changes found
+comparing with <base>/b
+no changes found
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-qrecord	Fri Jan 25 16:04:46 2008 -0800
@@ -0,0 +1,100 @@
+#!/bin/sh
+
+echo "[ui]" >> $HGRCPATH
+echo "interactive=true" >> $HGRCPATH
+echo "[extensions]"     >> $HGRCPATH
+echo "record="          >> $HGRCPATH
+
+echo "% help (no mq, so no qrecord)"
+
+hg help qrecord
+
+echo "mq="              >> $HGRCPATH
+
+echo "% help (mq present)"
+
+hg help qrecord
+
+hg init a
+cd a
+
+echo % base commit
+
+cat > 1.txt <<EOF
+1
+2
+3
+4
+5
+EOF
+cat > 2.txt <<EOF
+a
+b
+c
+d
+e
+f
+EOF
+mkdir dir
+cat > dir/a.txt <<EOF
+hello world
+
+someone
+up
+there
+loves
+me
+EOF
+
+hg add 1.txt 2.txt dir/a.txt
+hg commit -d '0 0' -m 'initial checkin'
+
+echo % changing files 
+
+sed -e 's/2/2 2/;s/4/4 4/' 1.txt > 1.txt.new
+sed -e 's/b/b b/' 2.txt > 2.txt.new
+sed -e 's/hello world/hello world!/' dir/a.txt > dir/a.txt.new
+
+mv -f 1.txt.new 1.txt
+mv -f 2.txt.new 2.txt
+mv -f dir/a.txt.new dir/a.txt
+
+echo % whole diff
+
+hg diff --nodates
+
+echo % qrecord a.patch
+
+hg qrecord -d '0 0' -m aaa a.patch <<EOF
+y
+y
+n
+y
+y
+n
+EOF
+
+echo
+echo % "after qrecord a.patch 'tip'"
+hg tip -p
+echo
+echo % "after qrecord a.patch 'diff'"
+hg diff --nodates
+
+echo % qrecord b.patch
+hg qrecord -d '0 0' -m bbb b.patch <<EOF
+y
+y
+y
+y
+EOF
+
+echo
+echo % "after qrecord b.patch 'tip'"
+hg tip -p
+echo
+echo % "after qrecord b.patch 'diff'"
+hg diff --nodates
+
+echo
+echo % --- end ---
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-qrecord.out	Fri Jan 25 16:04:46 2008 -0800
@@ -0,0 +1,207 @@
+% help (no mq, so no qrecord)
+hg: unknown command 'qrecord'
+Mercurial Distributed SCM
+
+basic commands:
+
+ add        add the specified files on the next commit
+ annotate   show changeset information per file line
+ clone      make a copy of an existing repository
+ commit     commit the specified files or all outstanding changes
+ diff       diff repository (or selected files)
+ export     dump the header and diffs for one or more changesets
+ init       create a new repository in the given directory
+ log        show revision history of entire repository or files
+ merge      merge working directory with another revision
+ parents    show the parents of the working dir or revision
+ pull       pull changes from the specified source
+ push       push changes to the specified destination
+ remove     remove the specified files on the next commit
+ serve      export the repository via HTTP
+ status     show changed files in the working directory
+ update     update working directory
+
+use "hg help" for the full list of commands or "hg -v" for details
+% help (mq present)
+hg qrecord [OPTION]... PATCH [FILE]...
+
+interactively record a new patch
+
+    see 'hg help qnew' & 'hg help record' for more information and usage
+
+options:
+
+ -e --edit         edit commit message
+ -g --git          use git extended diff format
+ -I --include      include names matching the given patterns
+ -X --exclude      exclude names matching the given patterns
+ -m --message      use <text> as commit message
+ -l --logfile      read commit message from <file>
+ -U --currentuser  add "From: <current user>" to patch
+ -u --user         add "From: <given user>" to patch
+ -D --currentdate  add "Date: <current date>" to patch
+ -d --date         add "Date: <given date>" to patch
+
+use "hg -v help qrecord" to show global options
+% base commit
+% changing files
+% whole diff
+diff -r 1057167b20ef 1.txt
+--- a/1.txt
++++ b/1.txt
+@@ -1,5 +1,5 @@
+ 1
+-2
++2 2
+ 3
+-4
++4 4
+ 5
+diff -r 1057167b20ef 2.txt
+--- a/2.txt
++++ b/2.txt
+@@ -1,5 +1,5 @@
+ a
+-b
++b b
+ c
+ d
+ e
+diff -r 1057167b20ef dir/a.txt
+--- a/dir/a.txt
++++ b/dir/a.txt
+@@ -1,4 +1,4 @@
+-hello world
++hello world!
+ 
+ someone
+ up
+% qrecord a.patch
+diff --git a/1.txt b/1.txt
+2 hunks, 4 lines changed
+examine changes to '1.txt'? [Ynsfdaq?]  @@ -1,3 +1,3 @@
+ 1
+-2
++2 2
+ 3
+record this change to '1.txt'? [Ynsfdaq?]  @@ -3,3 +3,3 @@
+ 3
+-4
++4 4
+ 5
+record this change to '1.txt'? [Ynsfdaq?]  diff --git a/2.txt b/2.txt
+1 hunks, 2 lines changed
+examine changes to '2.txt'? [Ynsfdaq?]  @@ -1,5 +1,5 @@
+ a
+-b
++b b
+ c
+ d
+ e
+record this change to '2.txt'? [Ynsfdaq?]  diff --git a/dir/a.txt b/dir/a.txt
+1 hunks, 2 lines changed
+examine changes to 'dir/a.txt'? [Ynsfdaq?]  
+% after qrecord a.patch 'tip'
+changeset:   1:5d1ca63427ee
+tag:         qtip
+tag:         tip
+tag:         a.patch
+tag:         qbase
+user:        test
+date:        Thu Jan 01 00:00:00 1970 +0000
+summary:     aaa
+
+diff -r 1057167b20ef -r 5d1ca63427ee 1.txt
+--- a/1.txt	Thu Jan 01 00:00:00 1970 +0000
++++ b/1.txt	Thu Jan 01 00:00:00 1970 +0000
+@@ -1,5 +1,5 @@
+ 1
+-2
++2 2
+ 3
+ 4
+ 5
+diff -r 1057167b20ef -r 5d1ca63427ee 2.txt
+--- a/2.txt	Thu Jan 01 00:00:00 1970 +0000
++++ b/2.txt	Thu Jan 01 00:00:00 1970 +0000
+@@ -1,5 +1,5 @@
+ a
+-b
++b b
+ c
+ d
+ e
+
+
+% after qrecord a.patch 'diff'
+diff -r 5d1ca63427ee 1.txt
+--- a/1.txt
++++ b/1.txt
+@@ -1,5 +1,5 @@
+ 1
+ 2 2
+ 3
+-4
++4 4
+ 5
+diff -r 5d1ca63427ee dir/a.txt
+--- a/dir/a.txt
++++ b/dir/a.txt
+@@ -1,4 +1,4 @@
+-hello world
++hello world!
+ 
+ someone
+ up
+% qrecord b.patch
+diff --git a/1.txt b/1.txt
+1 hunks, 2 lines changed
+examine changes to '1.txt'? [Ynsfdaq?]  @@ -1,5 +1,5 @@
+ 1
+ 2 2
+ 3
+-4
++4 4
+ 5
+record this change to '1.txt'? [Ynsfdaq?]  diff --git a/dir/a.txt b/dir/a.txt
+1 hunks, 2 lines changed
+examine changes to 'dir/a.txt'? [Ynsfdaq?]  @@ -1,4 +1,4 @@
+-hello world
++hello world!
+ 
+ someone
+ up
+record this change to 'dir/a.txt'? [Ynsfdaq?]  
+% after qrecord b.patch 'tip'
+changeset:   2:b056198bf878
+tag:         qtip
+tag:         tip
+tag:         b.patch
+user:        test
+date:        Thu Jan 01 00:00:00 1970 +0000
+summary:     bbb
+
+diff -r 5d1ca63427ee -r b056198bf878 1.txt
+--- a/1.txt	Thu Jan 01 00:00:00 1970 +0000
++++ b/1.txt	Thu Jan 01 00:00:00 1970 +0000
+@@ -1,5 +1,5 @@
+ 1
+ 2 2
+ 3
+-4
++4 4
+ 5
+diff -r 5d1ca63427ee -r b056198bf878 dir/a.txt
+--- a/dir/a.txt	Thu Jan 01 00:00:00 1970 +0000
++++ b/dir/a.txt	Thu Jan 01 00:00:00 1970 +0000
+@@ -1,4 +1,4 @@
+-hello world
++hello world!
+ 
+ someone
+ up
+
+
+% after qrecord b.patch 'diff'
+
+% --- end ---
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-strip-cross	Fri Jan 25 16:04:46 2008 -0800
@@ -0,0 +1,68 @@
+#!/bin/sh
+
+# test stripping of filelogs where the linkrev doesn't always increase
+
+echo '[extensions]' >> $HGRCPATH
+echo 'hgext.mq =' >> $HGRCPATH
+
+hg init orig
+cd orig
+
+hidefilename()
+{
+    sed -e 's/saving bundle to .*strip-backup/saving bundle to strip-backup/'
+}
+
+commit()
+{
+    hg up -qC null
+    count=1
+    for i in "$@"; do
+	for f in $i; do
+	    echo $count > $f
+	done
+	count=`expr $count + 1`
+    done
+    hg commit -qAm "$*"
+}
+
+# 2 1 0 2 0 1 2
+commit '201 210'
+
+commit '102 120' '210'
+
+commit '021'
+
+commit '201' '021 120'
+
+commit '012 021' '102 201' '120 210'
+
+commit 'manifest-file'
+
+commit '102 120' '012 210' '021 201'
+
+commit '201 210' '021 120' '012 102'
+
+HGUSER=another-user; export HGUSER
+commit 'manifest-file'
+
+commit '012' 'manifest-file'
+
+cd ..
+hg clone -q -U -r -1 -r -2 -r -3 -r -4 -r -6 orig crossed
+
+for i in crossed/.hg/store/00manifest.i crossed/.hg/store/data/*.i; do
+    echo $i
+    hg debugindex $i
+    echo
+done
+
+for i in 0 1 2 3 4; do
+    hg clone -q -U --pull crossed $i
+    echo "% Trying to strip revision $i"
+    hg --cwd $i strip $i 2>&1 | hidefilename
+    echo "% Verifying"
+    hg --cwd $i verify
+    echo
+done
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-strip-cross.out	Fri Jan 25 16:04:46 2008 -0800
@@ -0,0 +1,118 @@
+crossed/.hg/store/00manifest.i
+   rev    offset  length   base linkrev nodeid       p1           p2
+     0         0     112      0       0 6f105cbb914d 000000000000 000000000000
+     1       112      56      1       3 1b55917b3699 000000000000 000000000000
+     2       168     123      1       1 8f3d04e263e5 000000000000 000000000000
+     3       291     122      1       2 f0ef8726ac4f 000000000000 000000000000
+     4       413      87      4       4 0b76e38b4070 000000000000 000000000000
+
+crossed/.hg/store/data/012.i
+   rev    offset  length   base linkrev nodeid       p1           p2
+     0         0       3      0       0 b8e02f643373 000000000000 000000000000
+     1         3       3      1       1 5d9299349fc0 000000000000 000000000000
+     2         6       3      2       2 2661d26c6496 000000000000 000000000000
+
+crossed/.hg/store/data/021.i
+   rev    offset  length   base linkrev nodeid       p1           p2
+     0         0       3      0       0 b8e02f643373 000000000000 000000000000
+     1         3       3      1       2 5d9299349fc0 000000000000 000000000000
+     2         6       3      2       1 2661d26c6496 000000000000 000000000000
+
+crossed/.hg/store/data/102.i
+   rev    offset  length   base linkrev nodeid       p1           p2
+     0         0       3      0       1 b8e02f643373 000000000000 000000000000
+     1         3       3      1       0 5d9299349fc0 000000000000 000000000000
+     2         6       3      2       2 2661d26c6496 000000000000 000000000000
+
+crossed/.hg/store/data/120.i
+   rev    offset  length   base linkrev nodeid       p1           p2
+     0         0       3      0       1 b8e02f643373 000000000000 000000000000
+     1         3       3      1       2 5d9299349fc0 000000000000 000000000000
+     2         6       3      2       0 2661d26c6496 000000000000 000000000000
+
+crossed/.hg/store/data/201.i
+   rev    offset  length   base linkrev nodeid       p1           p2
+     0         0       3      0       2 b8e02f643373 000000000000 000000000000
+     1         3       3      1       0 5d9299349fc0 000000000000 000000000000
+     2         6       3      2       1 2661d26c6496 000000000000 000000000000
+
+crossed/.hg/store/data/210.i
+   rev    offset  length   base linkrev nodeid       p1           p2
+     0         0       3      0       2 b8e02f643373 000000000000 000000000000
+     1         3       3      1       1 5d9299349fc0 000000000000 000000000000
+     2         6       3      2       0 2661d26c6496 000000000000 000000000000
+
+crossed/.hg/store/data/manifest-file.i
+   rev    offset  length   base linkrev nodeid       p1           p2
+     0         0       3      0       3 b8e02f643373 000000000000 000000000000
+     1         3       3      1       4 5d9299349fc0 000000000000 000000000000
+
+% Trying to strip revision 0
+saving bundle to strip-backup/cbb8c2f0a2e3-backup
+saving bundle to strip-backup/cbb8c2f0a2e3-temp
+adding branch
+adding changesets
+adding manifests
+adding file changes
+added 4 changesets with 15 changes to 7 files (+3 heads)
+% Verifying
+checking changesets
+checking manifests
+crosschecking files in changesets and manifests
+checking files
+7 files, 4 changesets, 15 total revisions
+
+% Trying to strip revision 1
+saving bundle to strip-backup/124ecc0cbec9-backup
+saving bundle to strip-backup/124ecc0cbec9-temp
+adding branch
+adding changesets
+adding manifests
+adding file changes
+added 3 changesets with 12 changes to 7 files (+3 heads)
+% Verifying
+checking changesets
+checking manifests
+crosschecking files in changesets and manifests
+checking files
+7 files, 4 changesets, 14 total revisions
+
+% Trying to strip revision 2
+saving bundle to strip-backup/f6439b304a1a-backup
+saving bundle to strip-backup/f6439b304a1a-temp
+adding branch
+adding changesets
+adding manifests
+adding file changes
+added 2 changesets with 8 changes to 6 files (+2 heads)
+% Verifying
+checking changesets
+checking manifests
+crosschecking files in changesets and manifests
+checking files
+7 files, 4 changesets, 14 total revisions
+
+% Trying to strip revision 3
+saving bundle to strip-backup/6e54ec5db740-backup
+saving bundle to strip-backup/6e54ec5db740-temp
+adding branch
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 1 changes to 2 files (+1 heads)
+% Verifying
+checking changesets
+checking manifests
+crosschecking files in changesets and manifests
+checking files
+7 files, 4 changesets, 19 total revisions
+
+% Trying to strip revision 4
+saving bundle to strip-backup/9147ea23c156-backup
+% Verifying
+checking changesets
+checking manifests
+crosschecking files in changesets and manifests
+checking files
+7 files, 4 changesets, 19 total revisions
+
--- a/tests/test-webraw.out	Fri Jan 25 16:04:32 2008 -0800
+++ b/tests/test-webraw.out	Fri Jan 25 16:04:46 2008 -0800
@@ -1,7 +1,7 @@
 200 Script output follows
 content-type: text/plain
 content-length: 157
-content-disposition: filename=sometext.txt
+content-disposition: inline; filename=sometext.txt
 
 This is just some random text
 that will go inside the file and take a few lines.