changeset 4445:30e7aa755efd

Merge with crew-stable.
author Patrick Mezard <pmezard@gmail.com>
date Sat, 19 May 2007 22:51:43 +0200
parents af8db3b42a4a (diff) 2d32e3ae01a7 (current diff)
children 1b75e0eff532
files mercurial/dirstate.py
diffstat 181 files changed, 6106 insertions(+), 2743 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile
+++ b/Makefile
@@ -67,10 +67,10 @@ dist-notests:	doc MANIFEST
 	TAR_OPTIONS="--owner=root --group=root --mode=u+w,go-w,a+rX-s" $(PYTHON) setup.py -q sdist
 
 tests:
-	cd tests && $(PYTHON) run-tests.py
+	cd tests && $(PYTHON) run-tests.py $(TESTFLAGS)
 
 test-%:
-	cd tests && $(PYTHON) run-tests.py $@
+	cd tests && $(PYTHON) run-tests.py $(TESTFLAGS) $@
 
 
 .PHONY: help all local build doc clean install install-bin install-doc \
--- a/README
+++ b/README
@@ -1,99 +1,10 @@
-MERCURIAL QUICK-START
-
-Setting up Mercurial:
-
- Note: some distributions fails to include bits of distutils by
- default, you'll need python-dev to install. You'll also need a C
- compiler and a 3-way merge tool like merge, tkdiff, or kdiff3.
-
- First, unpack the source:
-
- $ tar xvzf mercurial-<ver>.tar.gz
- $ cd mercurial-<ver>
-
- When installing, change python to python2.3 or python2.4 if 2.2 is the
- default on your system.
-
- To install system-wide:
-
- $ python setup.py install --force
-
- To install in your home directory (~/bin and ~/lib, actually), run:
-
- $ python setup.py install --home=${HOME} --force
- $ export PYTHONPATH=${HOME}/lib/python  # (or lib64/ on some systems)
- $ export PATH=${HOME}/bin:$PATH         # add these to your .bashrc
-
- And finally:
-
- $ hg debuginstall                       # run some basic tests
- $ hg                                    # show help
-
- If you get complaints about missing modules, you probably haven't set
- PYTHONPATH correctly.
-
-Setting up a Mercurial project:
-
- $ hg init project     # creates project directory
- $ cd project
-                       # copy files in, edit them
- $ hg add              # add all unknown files
- $ hg commit           # commit all changes, edit changelog entry
-
- Mercurial will look for a file named .hgignore in the root of your
- repository which contains a set of regular expressions to ignore in
- file paths.
-
-Branching and merging:
+Basic install:
 
- $ hg clone project project-work    # create a new branch
- $ cd project-work
- $ <make changes>
- $ hg commit
- $ cd ../project
- $ hg pull ../project-work   # pull changesets from project-work
- $ hg merge                  # merge the new tip from project-work into
-                             # our working directory
- $ hg commit                 # commit the result of the merge
-
-Importing patches:
-
- Simple:
- $ patch < ../p/foo.patch
- $ hg commit -A
-
- Fast:
- $ cat ../p/patchlist | xargs hg import -p1 -b ../p
-
-Exporting a patch:
-
- (make changes)
- $ hg commit
- $ hg export tip > foo.patch    # export latest change
+ $ make            # see install targets
+ $ make install    # do a system-wide install
+ $ hg debuginstall # sanity-check setup
+ $ hg              # see help
 
-Network support:
-
- # pull from the primary Mercurial repo
- foo$ hg clone http://selenic.com/hg/
- foo$ cd hg
-
- # make your current repo available via http://server:8000/
- foo$ hg serve
-
- # pushing and pulling changes to/from a remote repo with SSH
- foo$ hg push ssh://user@example.com/my/repository
- foo$ hg pull ssh://user@example.com//home/somebody/his/repository
+See http://www.selenic.com/mercurial/ for detailed installation
+instructions, platform-specific notes, and Mercurial user information.
 
- # merge changes from a remote machine (e.g. running 'hg serve')
- bar$ hg pull http://foo:8000/
- bar$ hg merge   # merge changes into your working directory
- bar$ hg commit  # commit merge in to your local repository
-
- # Set up a CGI server on your webserver
- foo$ cp hgweb.cgi ~/public_html/hg/index.cgi
- foo$ emacs ~/public_html/hg/index.cgi # adjust the defaults
-
-For more info:
-
- Documentation in doc/
- Mercurial website at http://selenic.com/mercurial
--- a/contrib/bash_completion
+++ b/contrib/bash_completion
@@ -145,6 +145,7 @@ shopt -s extglob
     # global options
     case "$prev" in
 	-R|--repository)
+	    _hg_paths
 	    _hg_repos
 	    return
 	;;
@@ -477,3 +478,25 @@ complete -o bashdefault -o default -F _h
 {
     _hg_tags
 }
+
+
+# transplant
+_hg_cmd_transplant()
+{
+    case "$prev" in
+	-s|--source)
+	    _hg_paths
+	    _hg_repos
+	    return
+	    ;;
+	--filter)
+	    # standard filename completion
+	    return
+	    ;;
+    esac
+
+    # all other transplant options values and command parameters are revisions
+    _hg_tags
+    return
+}
+
--- a/contrib/churn.py
+++ b/contrib/churn.py
@@ -11,10 +11,9 @@
 #
 # <alias email> <actual email>
 
-from mercurial.demandload import *
+import sys
 from mercurial.i18n import gettext as _
-demandload(globals(), 'time sys signal os')
-demandload(globals(), 'mercurial:hg,mdiff,fancyopts,cmdutil,ui,util,templater,node')
+from mercurial import hg, mdiff, cmdutil, ui, util, templater, node
 
 def __gather(ui, repo, node1, node2):
     def dirtywork(f, mmap1, mmap2):
--- a/contrib/convert-repo
+++ b/contrib/convert-repo
@@ -3,29 +3,49 @@
 # This is a generalized framework for converting between SCM
 # repository formats.
 #
-# In its current form, it's hardcoded to convert incrementally between
-# git and Mercurial.
-#
 # To use, run:
 #
-# convert-repo <git-dir> <hg-dir> <mapfile>
+# convert-repo <source> [<dest> [<mapfile>]]
 #
-# (don't forget to create the <hg-dir> repository beforehand)
+# Currently accepted source formats: git, cvs
+# Currently accepted destination formats: hg
 #
-# The <mapfile> is a simple text file that maps a git commit hash to
-# the hash in Mercurial for that version, like so:
+# If destination isn't given, a new Mercurial repo named <src>-hg will
+# be created. If <mapfile> isn't given, it will be put in a default
+# location (<dest>/.hg/shamap by default)
 #
-# <git hash> <mercurial hash>
+# The <mapfile> is a simple text file that maps each source commit ID to
+# the destination ID for that revision, like so:
+#
+# <source ID> <destination ID>
 #
 # If the file doesn't exist, it's automatically created.  It's updated
 # on each commit copied, so convert-repo can be interrupted and can
 # be run repeatedly to copy new commits.
 
-import sys, os, zlib, sha, time
-
+import sys, os, zlib, sha, time, re, locale, socket
 os.environ["HGENCODING"] = "utf-8"
+from mercurial import hg, ui, util, fancyopts
 
-from mercurial import hg, ui, util
+class Abort(Exception): pass
+class NoRepo(Exception): pass
+
+class commit:
+    def __init__(self, **parts):
+        for x in "author date desc parents".split():
+            if not x in parts:
+                abort("commit missing field %s\n" % x)
+        self.__dict__.update(parts)
+
+quiet = 0
+def status(msg):
+    if not quiet: sys.stdout.write(str(msg))
+
+def warn(msg):
+    sys.stderr.write(str(msg))
+
+def abort(msg):
+    raise Abort(msg)
 
 def recode(s):
     try:
@@ -36,9 +56,245 @@ def recode(s):
         except:
             return s.decode("utf-8", "replace").encode("utf-8")
 
+# CVS conversion code inspired by hg-cvs-import and git-cvsimport
+class convert_cvs:
+    def __init__(self, path):
+        self.path = path
+        cvs = os.path.join(path, "CVS")
+        if not os.path.exists(cvs):
+            raise NoRepo("couldn't open CVS repo %s" % path)
+
+        self.changeset = {}
+        self.files = {}
+        self.tags = {}
+        self.lastbranch = {}
+        self.parent = {}
+        self.socket = None
+        self.cvsroot = file(os.path.join(cvs, "Root")).read()[:-1]
+        self.cvsrepo = file(os.path.join(cvs, "Repository")).read()[:-1]
+        self.encoding = locale.getpreferredencoding()
+        self._parse()
+        self._connect()
+
+    def _parse(self):
+        if self.changeset:
+            return
+
+        d = os.getcwd()
+        try:
+            os.chdir(self.path)
+            id = None
+            state = 0
+            for l in os.popen("cvsps -A -u --cvs-direct -q"):
+                if state == 0: # header
+                    if l.startswith("PatchSet"):
+                        id = l[9:-2]
+                    elif l.startswith("Date"):
+                        date = util.parsedate(l[6:-1], ["%Y/%m/%d %H:%M:%S"])
+                        date = util.datestr(date)
+                    elif l.startswith("Branch"):
+                        branch = l[8:-1]
+                        self.parent[id] = self.lastbranch.get(branch,'bad')
+                        self.lastbranch[branch] = id
+                    elif l.startswith("Ancestor branch"):
+                        ancestor = l[17:-1]
+                        self.parent[id] = self.lastbranch[ancestor]
+                    elif l.startswith("Author"):
+                        author = self.recode(l[8:-1])
+                    elif l.startswith("Tag: "):
+                        t = l[5:-1].rstrip()
+                        if t != "(none)":
+                            self.tags[t] = id
+                    elif l.startswith("Log:"):
+                        state = 1
+                        log = ""
+                elif state == 1: # log
+                    if l == "Members: \n":
+                        files = {}
+                        log = self.recode(log[:-1])
+                        if log.isspace():
+                            log = "*** empty log message ***\n"
+                        state = 2
+                    else:
+                        log += l
+                elif state == 2:
+                    if l == "\n": #
+                        state = 0
+                        p = [self.parent[id]]
+                        if id == "1":
+                            p = []
+                        c = commit(author=author, date=date, parents=p,
+                                   desc=log, branch=branch)
+                        self.changeset[id] = c
+                        self.files[id] = files
+                    else:
+                        file,rev = l[1:-2].rsplit(':',1)
+                        rev = rev.split("->")[1]
+                        files[file] = rev
+
+            self.heads = self.lastbranch.values()
+        finally:
+            os.chdir(d)
+
+    def _connect(self):
+        root = self.cvsroot
+        conntype = None
+        user, host = None, None
+        cmd = ['cvs', 'server']
+
+        status("connecting to %s\n" % root)
+
+        if root.startswith(":pserver:"):
+            root = root[9:]
+            m = re.match(r'(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?(.*)', root)
+            if m:
+                conntype = "pserver"
+                user, passw, serv, port, root = m.groups()
+                if not user:
+                    user = "anonymous"
+                rr = ":pserver:" + user + "@" + serv + ":" +  root
+                if port:
+                    rr2, port = "-", int(port)
+                else:
+                    rr2, port = rr, 2401
+                rr += str(port)
+
+                if not passw:
+                    passw = "A"
+                    pf = open(os.path.join(os.environ["HOME"], ".cvspass"))
+                    for l in pf:
+                        # :pserver:cvs@mea.tmt.tele.fi:/cvsroot/zmailer Ah<Z
+                        m = re.match(r'(/\d+\s+/)?(.*)', l)
+                        l = m.group(2)
+                        w, p = l.split(' ', 1)
+                        if w in [rr, rr2]:
+                            passw = p
+                            break
+                    pf.close()
+
+                sck = socket.socket()
+                sck.connect((serv, port))
+                sck.send("\n".join(["BEGIN AUTH REQUEST", root, user, passw, "END AUTH REQUEST", ""]))
+                if sck.recv(128) != "I LOVE YOU\n":
+                    raise NoRepo("CVS pserver authentication failed")
+
+                self.writep = self.readp = sck.makefile('r+')
+
+        if not conntype and root.startswith(":local:"):
+            conntype = "local"
+            root = root[7:]
+
+        if not conntype:
+            # :ext:user@host/home/user/path/to/cvsroot
+            if root.startswith(":ext:"):
+                root = root[5:]
+            m = re.match(r'(?:([^@:/]+)@)?([^:/]+):?(.*)', root)
+            if not m:
+                conntype = "local"
+            else:
+                conntype = "rsh"
+                user, host, root = m.group(1), m.group(2), m.group(3)
+
+        if conntype != "pserver":
+            if conntype == "rsh": 
+                rsh = os.environ.get("CVS_RSH" or "rsh")
+                if user:
+                    cmd = [rsh, '-l', user, host] + cmd
+                else:
+                    cmd = [rsh, host] + cmd
+
+            self.writep, self.readp = os.popen2(cmd)
+
+        self.realroot = root
+
+        self.writep.write("Root %s\n" % root)
+        self.writep.write("Valid-responses ok error Valid-requests Mode"
+                          " M Mbinary E Checked-in Created Updated"
+                          " Merged Removed\n")
+        self.writep.write("valid-requests\n")
+        self.writep.flush()
+        r = self.readp.readline()
+        if not r.startswith("Valid-requests"):
+            abort("server sucks\n")
+        if "UseUnchanged" in r:
+            self.writep.write("UseUnchanged\n")
+            self.writep.flush()
+            r = self.readp.readline()
+
+    def getheads(self):
+        return self.heads
+
+    def _getfile(self, name, rev):
+        if rev.endswith("(DEAD)"):
+            raise IOError
+
+        args = ("-N -P -kk -r %s --" % rev).split()
+        args.append(os.path.join(self.cvsrepo, name))
+        for x in args:
+            self.writep.write("Argument %s\n" % x)
+        self.writep.write("Directory .\n%s\nco\n" % self.realroot)
+        self.writep.flush()
+
+        data = ""
+        while 1:
+            line = self.readp.readline()
+            if line.startswith("Created ") or line.startswith("Updated "):
+                self.readp.readline() # path
+                self.readp.readline() # entries
+                mode = self.readp.readline()[:-1]
+                count = int(self.readp.readline()[:-1])
+                data = self.readp.read(count)
+            elif line.startswith(" "):
+                data += line[1:]
+            elif line.startswith("M "):
+                pass
+            elif line.startswith("Mbinary "):
+                count = int(self.readp.readline()[:-1])
+                data = self.readp.read(count)
+            else:
+                if line == "ok\n":
+                    return (data, "x" in mode and "x" or "")
+                elif line.startswith("E "):
+                    warn("cvs server: %s\n" % line[2:])
+                elif line.startswith("Remove"):
+                    l = self.readp.readline()
+                    l = self.readp.readline()
+                    if l != "ok\n":
+                        abort("unknown CVS response: %s\n" % l)
+                else:
+                    abort("unknown CVS response: %s\n" % line)
+
+    def getfile(self, file, rev):
+        data, mode = self._getfile(file, rev)
+        self.modecache[(file, rev)] = mode
+        return data
+
+    def getmode(self, file, rev):
+        return self.modecache[(file, rev)]
+
+    def getchanges(self, rev):
+        self.modecache = {}
+        files = self.files[rev]
+        cl = files.items()
+        cl.sort()
+        return cl
+
+    def recode(self, text):
+        return text.decode(self.encoding, "replace").encode("utf-8")
+
+    def getcommit(self, rev):
+        return self.changeset[rev]
+
+    def gettags(self):
+        return self.tags
+
 class convert_git:
     def __init__(self, path):
+        if os.path.isdir(path + "/.git"):
+            path += "/.git"
         self.path = path
+        if not os.path.exists(path + "/objects"):
+            raise NoRepo("couldn't open GIT repo %s" % path)
 
     def getheads(self):
         fh = os.popen("GIT_DIR=%s git-rev-parse --verify HEAD" % self.path)
@@ -52,7 +308,11 @@ class convert_git:
     def getfile(self, name, rev):
         return self.catfile(rev, "blob")
 
+    def getmode(self, name, rev):
+        return self.modecache[(name, rev)]
+
     def getchanges(self, version):
+        self.modecache = {}
         fh = os.popen("GIT_DIR=%s git-diff-tree --root -m -r %s" % (self.path, version))
         changes = []
         for l in fh:
@@ -61,7 +321,9 @@ class convert_git:
             m = m.split()
             h = m[3]
             p = (m[1] == "100755")
-            changes.append((f, h, p))
+            s = (m[1] == "120000")
+            self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
+            changes.append((f, h))
         return changes
 
     def getcommit(self, version):
@@ -92,37 +354,47 @@ class convert_git:
         tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
         tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
         date = tm + " " + str(tz)
-        return (parents, author, date, message)
+
+        c = commit(parents=parents, date=date, author=author, desc=message)
+        return c
 
     def gettags(self):
         tags = {}
-        for f in os.listdir(self.path + "/refs/tags"):
-            try:
-                h = file(self.path + "/refs/tags/" + f).read().strip()
-                c = self.catfile(h, "tag") # read the commit hash
-                h = c.splitlines()[0].split()[1]
-                tags[f] = h
-            except:
-                pass
+        fh = os.popen('git-ls-remote --tags "%s" 2>/dev/null' % self.path)
+        prefix = 'refs/tags/'
+        for line in fh:
+            line = line.strip()
+            if not line.endswith("^{}"):
+                continue
+            node, tag = line.split(None, 1)
+            if not tag.startswith(prefix):
+                continue
+            tag = tag[len(prefix):-3]
+            tags[tag] = node
+
         return tags
 
 class convert_mercurial:
     def __init__(self, path):
         self.path = path
         u = ui.ui()
-        self.repo = hg.repository(u, path)
+        try:
+            self.repo = hg.repository(u, path)
+        except:
+            raise NoRepo("could open hg repo %s" % path)
+
+    def mapfile(self):
+        return os.path.join(self.path, ".hg", "shamap")
 
     def getheads(self):
         h = self.repo.changelog.heads()
         return [ hg.hex(x) for x in h ]
 
     def putfile(self, f, e, data):
-        self.repo.wfile(f, "w").write(data)
+        self.repo.wwrite(f, data, e)
         if self.repo.dirstate.state(f) == '?':
             self.repo.dirstate.update([f], "a")
 
-        util.set_exec(self.repo.wjoin(f), e)
-
     def delfile(self, f):
         try:
             os.unlink(self.repo.wjoin(f))
@@ -130,7 +402,7 @@ class convert_mercurial:
         except:
             pass
 
-    def putcommit(self, files, parents, author, dest, text):
+    def putcommit(self, files, parents, commit):
         seen = {}
         pl = []
         for p in parents:
@@ -143,11 +415,18 @@ class convert_mercurial:
         if len(parents) < 2: parents.append("0" * 40)
         p2 = parents.pop(0)
 
+        text = commit.desc
+        extra = {}
+        try:
+            extra["branch"] = commit.branch
+        except AttributeError:
+            pass
+
         while parents:
             p1 = p2
             p2 = parents.pop(0)
-            self.repo.rawcommit(files, text, author, dest,
-                                hg.bin(p1), hg.bin(p2))
+            a = self.repo.rawcommit(files, text, commit.author, commit.date,
+                                    hg.bin(p1), hg.bin(p2), extra=extra)
             text = "(octopus merge fixup)\n"
             p2 = hg.hex(self.repo.changelog.tip())
 
@@ -170,7 +449,7 @@ class convert_mercurial:
         newlines.sort()
 
         if newlines != oldlines:
-            #print "updating tags"
+            status("updating tags\n")
             f = self.repo.wfile(".hgtags", "w")
             f.write("".join(newlines))
             f.close()
@@ -180,11 +459,25 @@ class convert_mercurial:
                                 date, self.repo.changelog.tip(), hg.nullid)
             return hg.hex(self.repo.changelog.tip())
 
+converters = [convert_cvs, convert_git, convert_mercurial]
+
+def converter(path):
+    if not os.path.isdir(path):
+        abort("%s: not a directory\n" % path)
+    for c in converters:
+        try:
+            return c(path)
+        except NoRepo:
+            pass
+    abort("%s: unknown repository type\n" % path)
+
 class convert:
-    def __init__(self, source, dest, mapfile):
+    def __init__(self, source, dest, mapfile, opts):
+
         self.source = source
         self.dest = dest
         self.mapfile = mapfile
+        self.opts = opts
         self.commitcache = {}
 
         self.map = {}
@@ -204,7 +497,7 @@ class convert:
             if n in known or n in self.map: continue
             known[n] = 1
             self.commitcache[n] = self.source.getcommit(n)
-            cp = self.commitcache[n][0]
+            cp = self.commitcache[n].parents
             for p in cp:
                 parents.setdefault(n, []).append(p)
                 visit.append(p)
@@ -247,42 +540,60 @@ class convert:
             if not dep:
                 # all n's parents are in the list
                 removed[n] = 1
-                s.append(n)
+                if n not in self.map:
+                    s.append(n)
                 if n in children:
                     for c in children[n]:
                         visit.insert(0, c)
 
+        if opts.get('datesort'):
+            depth = {}
+            for n in s:
+                depth[n] = 0
+                pl = [p for p in self.commitcache[n].parents if p not in self.map]
+                if pl:
+                    depth[n] = max([depth[p] for p in pl]) + 1
+
+            s = [(depth[n], self.commitcache[n].date, n) for n in s]
+            s.sort()
+            s = [e[2] for e in s]
+
         return s
 
     def copy(self, rev):
-        p, a, d, t = self.commitcache[rev]
+        c = self.commitcache[rev]
         files = self.source.getchanges(rev)
 
-        for f,v,e in files:
+        for f,v in files:
             try:
                 data = self.source.getfile(f, v)
             except IOError, inst:
                 self.dest.delfile(f)
             else:
+                e = self.source.getmode(f, v)
                 self.dest.putfile(f, e, data)
 
-        r = [self.map[v] for v in p]
-        f = [f for f,v,e in files]
-        self.map[rev] = self.dest.putcommit(f, r, a, d, t)
+        r = [self.map[v] for v in c.parents]
+        f = [f for f,v in files]
+        self.map[rev] = self.dest.putcommit(f, r, c)
         file(self.mapfile, "a").write("%s %s\n" % (rev, self.map[rev]))
 
     def convert(self):
+        status("scanning source...\n")
         heads = self.source.getheads()
         parents = self.walktree(heads)
+        status("sorting...\n")
         t = self.toposort(parents)
-        t = [n for n in t if n not in self.map]
         num = len(t)
         c = None
 
+        status("converting...\n")
         for c in t:
             num -= 1
-            desc = self.commitcache[c][3].splitlines()[0]
-            #print num, desc
+            desc = self.commitcache[c].desc
+            if "\n" in desc:
+                desc = desc.splitlines()[0]
+            status("%d %s\n" % (num, desc))
             self.copy(c)
 
         tags = self.source.gettags()
@@ -299,9 +610,41 @@ class convert:
             if nrev:
                 file(self.mapfile, "a").write("%s %s\n" % (c, nrev))
 
-gitpath, hgpath, mapfile = sys.argv[1:]
-if os.path.isdir(gitpath + "/.git"):
-    gitpath += "/.git"
+def command(src, dest=None, mapfile=None, **opts):
+    srcc = converter(src)
+    if not hasattr(srcc, "getcommit"):
+        abort("%s: can't read from this repo type\n" % src)
+
+    if not dest:
+        dest = src + "-hg"
+        status("assuming destination %s\n" % dest)
+        if not os.path.isdir(dest):
+            status("creating repository %s\n" % dest)
+            os.system("hg init " + dest)
+    destc = converter(dest)
+    if not hasattr(destc, "putcommit"):
+        abort("%s: can't write to this repo type\n" % src)
 
-c = convert(convert_git(gitpath), convert_mercurial(hgpath), mapfile)
-c.convert()
+    if not mapfile:
+        try:
+            mapfile = destc.mapfile()
+        except:
+            mapfile = os.path.join(destc, "map")
+
+    c = convert(srcc, destc, mapfile, opts)
+    c.convert()
+
+options = [('q', 'quiet', None, 'suppress output'),
+           ('', 'datesort', None, 'try to sort changesets by date')]
+opts = {}
+args = fancyopts.fancyopts(sys.argv[1:], options, opts)
+
+if opts['quiet']:
+    quiet = 1
+
+try:
+    command(*args, **opts)
+except Abort, inst:
+    warn(inst)
+except KeyboardInterrupt:
+    status("interrupted\n")
new file mode 100755
--- /dev/null
+++ b/contrib/hg-relink
@@ -0,0 +1,128 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2007 Brendan Cully <brendan@kublai.com>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+import os, sys
+
+class ConfigError(Exception): pass
+
+def usage():
+    print """relink <source> <destination>
+    Recreate hard links between source and destination repositories"""
+
+class Config:
+    def __init__(self, args):
+        if len(args) != 3:
+            raise ConfigError("wrong number of arguments")
+        self.src = os.path.abspath(args[1])
+        self.dst = os.path.abspath(args[2])
+        for d in (self.src, self.dst):
+            if not os.path.exists(os.path.join(d, '.hg')):
+                raise ConfigError("%s: not a mercurial repository" % d)
+
+def collect(src):
+    seplen = len(os.path.sep)
+    candidates = []
+    for dirpath, dirnames, filenames in os.walk(src):
+        relpath = dirpath[len(src) + seplen:]
+        for filename in filenames:
+            if not filename.endswith('.i'):
+                continue
+            st = os.stat(os.path.join(dirpath, filename))
+            candidates.append((os.path.join(relpath, filename), st))
+
+    return candidates
+
+def prune(candidates, dst):
+    def getdatafile(path):
+        if not path.endswith('.i'):
+            return None, None
+        df = path[:-1] + 'd'
+        try:
+            st = os.stat(df)
+        except OSError:
+            return None, None
+        return df, st
+
+    def linkfilter(dst, st):
+        try:
+            ts = os.stat(dst)
+        except OSError:
+            # Destination doesn't have this file?
+            return False
+        if st.st_ino == ts.st_ino:
+            return False
+        if st.st_dev != ts.st_dev:
+            # No point in continuing
+            raise Exception('Source and destination are on different devices')
+        if st.st_size != ts.st_size:
+            # TODO: compare revlog heads
+            return False
+        return st
+
+    targets = []
+    for fn, st in candidates:
+        tgt = os.path.join(dst, fn)
+        ts = linkfilter(tgt, st)
+        if not ts:
+            continue
+        targets.append((fn, ts.st_size))
+        df, ts = getdatafile(tgt)
+        if df:
+            targets.append((fn[:-1] + 'd', ts.st_size))
+
+    return targets
+
+def relink(src, dst, files):
+    def relinkfile(src, dst):
+        bak = dst + '.bak'
+        os.rename(dst, bak)
+        try:
+            os.link(src, dst)
+        except OSError:
+            os.rename(bak, dst)
+            raise
+        os.remove(bak)
+
+    CHUNKLEN = 65536
+    relinked = 0
+    savedbytes = 0
+
+    for f, sz in files:
+        source = os.path.join(src, f)
+        tgt = os.path.join(dst, f)
+        sfp = file(source)
+        dfp = file(tgt)
+        sin = sfp.read(CHUNKLEN)
+        while sin:
+            din = dfp.read(CHUNKLEN)
+            if sin != din:
+                break
+            sin = sfp.read(CHUNKLEN)
+        if sin:
+            continue
+        try:
+            relinkfile(source, tgt)
+            print 'Relinked %s' % f
+            relinked += 1
+            savedbytes += sz
+        except OSError, inst:
+            print '%s: %s' % (tgt, str(inst))
+
+    print 'Relinked %d files (%d bytes reclaimed)' % (relinked, savedbytes)
+
+try:
+    cfg = Config(sys.argv)
+except ConfigError, inst:
+    print str(inst)
+    usage()
+    sys.exit(1)
+
+src = os.path.join(cfg.src, '.hg')
+dst = os.path.join(cfg.dst, '.hg')
+candidates = collect(src)
+targets = prune(candidates, dst)
+relink(src, dst, targets)
--- a/contrib/hgk
+++ b/contrib/hgk
@@ -43,7 +43,9 @@ proc getcommits {rargs} {
     }
     if [catch {
 	set parse_args [concat --default HEAD $revargs]
-	set parsed_args [split [eval exec hg debug-rev-parse $parse_args] "\n"]
+	set parse_temp [eval exec hg debug-rev-parse $parse_args]
+	regsub -all "\r\n" $parse_temp "\n" parse_temp
+	set parsed_args [split $parse_temp "\n"]
     } err] {
 	# if git-rev-parse failed for some reason...
 	if {$rargs == {}} {
@@ -108,6 +110,7 @@ to allow selection of commits to be disp
 	    set leftover {}
 	}
 	set start [expr {$i + 1}]
+	regsub -all "\r\n" $cmit "\n" cmit
 	set j [string first "\n" $cmit]
 	set ok 0
 	if {$j >= 0} {
@@ -209,6 +212,7 @@ proc parsecommit {id contents listed old
 	    incr ncleft($p)
 	}
     }
+    regsub -all "\r\n" $contents "\n" contents
     foreach line [split $contents "\n"] {
 	if {$inhdr} {
 	    set line [split $line]
@@ -257,7 +261,8 @@ proc readrefs {} {
     global tagids idtags headids idheads tagcontents
 
     set tags [exec hg tags]
-    set lines [split $tags '\n']
+    regsub -all "\r\n" $tags "\n" tags
+    set lines [split $tags "\n"]
     foreach f $lines {
 	set f [regexp -all -inline {\S+} $f]
 	set direct [lindex $f 0]
@@ -2856,6 +2861,7 @@ proc getblobdiffline {bdf ids} {
     if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
 	return
     }
+    regsub -all "\r" $line "" line
     $ctext conf -state normal
     if {[regexp {^diff --git a/(.*) b/(.*)} $line match fname newname]} {
 	# start of a new file
@@ -2914,7 +2920,7 @@ proc getblobdiffline {bdf ids} {
 	} elseif {$diffinhdr || $x == "\\"} {
 	    # e.g. "\ No newline at end of file"
 	    $ctext insert end "$line\n" filesep
-	} else {
+	} elseif  {$line != ""} {
 	    # Something else we don't recognize
 	    if {$curdifftag != "Comments"} {
 		$ctext insert end "\n"
new file mode 100644
--- /dev/null
+++ b/contrib/hgwebdir.fcgi
@@ -0,0 +1,47 @@
+#!/usr/bin/env python
+#
+# An example CGI script to export multiple hgweb repos, edit as necessary
+
+# send python tracebacks to the browser if an error occurs:
+import cgitb
+cgitb.enable()
+
+# adjust python path if not a system-wide install:
+#import sys
+#sys.path.insert(0, "/path/to/python/lib")
+
+# If you'd like to serve pages with UTF-8 instead of your default
+# locale charset, you can do so by uncommenting the following lines.
+# Note that this will cause your .hgrc files to be interpreted in
+# UTF-8 and all your repo files to be displayed using UTF-8.
+#
+#import os
+#os.environ["HGENCODING"] = "UTF-8"
+
+from mercurial.hgweb.hgwebdir_mod import hgwebdir
+from mercurial.hgweb.request import wsgiapplication
+from flup.server.fcgi import WSGIServer
+
+# The config file looks like this.  You can have paths to individual
+# repos, collections of repos in a directory tree, or both.
+#
+# [paths]
+# virtual/path = /real/path
+# virtual/path = /real/path
+#
+# [collections]
+# /prefix/to/strip/off = /root/of/tree/full/of/repos
+#
+# collections example: say directory tree /foo contains repos /foo/bar,
+# /foo/quux/baz.  Give this config section:
+#   [collections]
+#   /foo = /foo
+# Then repos will list as bar and quux/baz.
+#
+# Alternatively you can pass a list of ('virtual/path', '/real/path') tuples
+# or use a dictionary with entries like 'virtual/path': '/real/path'
+
+def make_web_app():
+    return hgwebdir("hgweb.config")
+
+WSGIServer(wsgiapplication(make_web_app)).run()
--- a/contrib/mercurial.el
+++ b/contrib/mercurial.el
@@ -43,22 +43,28 @@
 
 ;;; Code:
 
-(require 'advice)
-(require 'cl)
+(eval-when-compile (require 'cl))
 (require 'diff-mode)
 (require 'easymenu)
 (require 'executable)
 (require 'vc)
 
+(defmacro hg-feature-cond (&rest clauses)
+  "Test CLAUSES for feature at compile time.
+Each clause is (FEATURE BODY...)."
+  (dolist (x clauses)
+    (let ((feature (car x))
+	  (body (cdr x)))
+      (when (or (eq feature t)
+		(featurep feature))
+	(return (cons 'progn body))))))
+
 
 ;;; XEmacs has view-less, while GNU Emacs has view.  Joy.
 
-(condition-case nil
-    (require 'view-less)
-  (error nil))
-(condition-case nil
-    (require 'view)
-  (error nil))
+(hg-feature-cond
+ (xemacs (require 'view-less))
+ (t (require 'view)))
 
 
 ;;; Variables accessible through the custom system.
@@ -147,9 +153,6 @@ repository-related commands."
 
 ;;; Other variables.
 
-(defconst hg-running-xemacs (string-match "XEmacs" emacs-version)
-  "Is mercurial.el running under XEmacs?")
-
 (defvar hg-mode nil
   "Is this file managed by Mercurial?")
 (make-variable-buffer-local 'hg-mode)
@@ -167,12 +170,21 @@ repository-related commands."
 (make-variable-buffer-local 'hg-root)
 (put 'hg-root 'permanent-local t)
 
+(defvar hg-view-mode nil)
+(make-variable-buffer-local 'hg-view-mode)
+(put 'hg-view-mode 'permanent-local t)
+
+(defvar hg-view-file-name nil)
+(make-variable-buffer-local 'hg-view-file-name)
+(put 'hg-view-file-name 'permanent-local t)
+
 (defvar hg-output-buffer-name "*Hg*"
   "The name to use for Mercurial output buffers.")
 
 (defvar hg-file-history nil)
 (defvar hg-repo-history nil)
 (defvar hg-rev-history nil)
+(defvar hg-repo-completion-table nil)	; shut up warnings
 
 
 ;;; Random constants.
@@ -183,85 +195,96 @@ repository-related commands."
 (defconst hg-commit-message-end
   "--- Files in bold will be committed.  Click to toggle selection. ---\n")
 
+(defconst hg-state-alist
+  '((?M . modified)
+    (?A . added)
+    (?R . removed)
+    (?! . deleted)
+    (?C . normal)
+    (?I . ignored)
+    (?? . nil)))
 
 ;;; hg-mode keymap.
 
-(defvar hg-mode-map (make-sparse-keymap))
-(define-key hg-mode-map "\C-xv" 'hg-prefix-map)
-
 (defvar hg-prefix-map
-  (let ((map (copy-keymap vc-prefix-map)))
-    (if (functionp 'set-keymap-name)
-      (set-keymap-name map 'hg-prefix-map)); XEmacs
+  (let ((map (make-sparse-keymap)))
+    (hg-feature-cond (xemacs (set-keymap-name map 'hg-prefix-map))) ; XEmacs
+    (set-keymap-parent map vc-prefix-map)
+    (define-key map "=" 'hg-diff)
+    (define-key map "c" 'hg-undo)
+    (define-key map "g" 'hg-annotate)
+    (define-key map "i" 'hg-add)
+    (define-key map "l" 'hg-log)
+    (define-key map "n" 'hg-commit-start)
+    ;; (define-key map "r" 'hg-update)
+    (define-key map "u" 'hg-revert-buffer)
+    (define-key map "~" 'hg-version-other-window)
     map)
   "This keymap overrides some default vc-mode bindings.")
-(fset 'hg-prefix-map hg-prefix-map)
-(define-key hg-prefix-map "=" 'hg-diff)
-(define-key hg-prefix-map "c" 'hg-undo)
-(define-key hg-prefix-map "g" 'hg-annotate)
-(define-key hg-prefix-map "l" 'hg-log)
-(define-key hg-prefix-map "n" 'hg-commit-start)
-;; (define-key hg-prefix-map "r" 'hg-update)
-(define-key hg-prefix-map "u" 'hg-revert-buffer)
-(define-key hg-prefix-map "~" 'hg-version-other-window)
+
+(defvar hg-mode-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map "\C-xv" hg-prefix-map)
+    map))
 
 (add-minor-mode 'hg-mode 'hg-mode hg-mode-map)
 
 
 ;;; Global keymap.
 
-(global-set-key "\C-xvi" 'hg-add)
+(defvar hg-global-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map "," 'hg-incoming)
+    (define-key map "." 'hg-outgoing)
+    (define-key map "<" 'hg-pull)
+    (define-key map "=" 'hg-diff-repo)
+    (define-key map ">" 'hg-push)
+    (define-key map "?" 'hg-help-overview)
+    (define-key map "A" 'hg-addremove)
+    (define-key map "U" 'hg-revert)
+    (define-key map "a" 'hg-add)
+    (define-key map "c" 'hg-commit-start)
+    (define-key map "f" 'hg-forget)
+    (define-key map "h" 'hg-help-overview)
+    (define-key map "i" 'hg-init)
+    (define-key map "l" 'hg-log-repo)
+    (define-key map "r" 'hg-root)
+    (define-key map "s" 'hg-status)
+    (define-key map "u" 'hg-update)
+    map))
 
-(defvar hg-global-map (make-sparse-keymap))
-(fset 'hg-global-map hg-global-map)
-(global-set-key hg-global-prefix 'hg-global-map)
-(define-key hg-global-map "," 'hg-incoming)
-(define-key hg-global-map "." 'hg-outgoing)
-(define-key hg-global-map "<" 'hg-pull)
-(define-key hg-global-map "=" 'hg-diff-repo)
-(define-key hg-global-map ">" 'hg-push)
-(define-key hg-global-map "?" 'hg-help-overview)
-(define-key hg-global-map "A" 'hg-addremove)
-(define-key hg-global-map "U" 'hg-revert)
-(define-key hg-global-map "a" 'hg-add)
-(define-key hg-global-map "c" 'hg-commit-start)
-(define-key hg-global-map "f" 'hg-forget)
-(define-key hg-global-map "h" 'hg-help-overview)
-(define-key hg-global-map "i" 'hg-init)
-(define-key hg-global-map "l" 'hg-log-repo)
-(define-key hg-global-map "r" 'hg-root)
-(define-key hg-global-map "s" 'hg-status)
-(define-key hg-global-map "u" 'hg-update)
-
+(global-set-key hg-global-prefix hg-global-map)
 
 ;;; View mode keymap.
 
 (defvar hg-view-mode-map
-  (let ((map (copy-keymap (if (boundp 'view-minor-mode-map)
-			      view-minor-mode-map
-			    view-mode-map))))
-    (if (functionp 'set-keymap-name)
-      (set-keymap-name map 'hg-view-mode-map)); XEmacs
+  (let ((map (make-sparse-keymap)))
+    (hg-feature-cond (xemacs (set-keymap-name map 'hg-view-mode-map))) ; XEmacs
+    (define-key map (hg-feature-cond (xemacs [button2])
+				     (t [mouse-2]))
+      'hg-buffer-mouse-clicked)
     map))
-(fset 'hg-view-mode-map hg-view-mode-map)
-(define-key hg-view-mode-map
-  (if hg-running-xemacs [button2] [mouse-2])
-  'hg-buffer-mouse-clicked)
+
+(add-minor-mode 'hg-view-mode "" hg-view-mode-map)
 
 
 ;;; Commit mode keymaps.
 
-(defvar hg-commit-mode-map (make-sparse-keymap))
-(define-key hg-commit-mode-map "\C-c\C-c" 'hg-commit-finish)
-(define-key hg-commit-mode-map "\C-c\C-k" 'hg-commit-kill)
-(define-key hg-commit-mode-map "\C-xv=" 'hg-diff-repo)
+(defvar hg-commit-mode-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map "\C-c\C-c" 'hg-commit-finish)
+    (define-key map "\C-c\C-k" 'hg-commit-kill)
+    (define-key map "\C-xv=" 'hg-diff-repo)
+    map))
 
-(defvar hg-commit-mode-file-map (make-sparse-keymap))
-(define-key hg-commit-mode-file-map
-  (if hg-running-xemacs [button2] [mouse-2])
-  'hg-commit-mouse-clicked)
-(define-key hg-commit-mode-file-map " " 'hg-commit-toggle-file)
-(define-key hg-commit-mode-file-map "\r" 'hg-commit-toggle-file)
+(defvar hg-commit-mode-file-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map (hg-feature-cond (xemacs [button2])
+				     (t [mouse-2]))
+      'hg-commit-mouse-clicked)
+    (define-key map " " 'hg-commit-toggle-file)
+    (define-key map "\r" 'hg-commit-toggle-file)
+    map))
 
 
 ;;; Convenience functions.
@@ -278,9 +301,9 @@ replacement.
 
 This function bridges yet another pointless impedance gap between
 XEmacs and GNU Emacs."
-  (if (fboundp 'replace-in-string)
-      (replace-in-string str regexp newtext literal)
-    (replace-regexp-in-string regexp newtext str nil literal)))
+  (hg-feature-cond
+   (xemacs (replace-in-string str regexp newtext literal))
+   (t (replace-regexp-in-string regexp newtext str nil literal))))
 
 (defsubst hg-strip (str)
   "Strip leading and trailing blank lines from a string."
@@ -317,6 +340,18 @@ If the command does not exit with a zero
 	       (car res))
       (cdr res))))
 
+(defmacro hg-do-across-repo (path &rest body)
+  (let ((root-name (make-symbol "root-"))
+	(buf-name (make-symbol "buf-")))
+    `(let ((,root-name (hg-root ,path)))
+       (save-excursion
+	 (dolist (,buf-name (buffer-list))
+	   (set-buffer ,buf-name)
+	   (when (and hg-status (equal (hg-root buffer-file-name) ,root-name))
+	     ,@body))))))
+
+(put 'hg-do-across-repo 'lisp-indent-function 1)
+
 (defun hg-sync-buffers (path)
   "Sync buffers visiting PATH with their on-disk copies.
 If PATH is not being visited, but is under the repository root, sync
@@ -332,29 +367,23 @@ all buffers visiting files in the reposi
   "Use the properties of a character to do something sensible."
   (interactive "d")
   (let ((rev (get-char-property pnt 'rev))
-	(file (get-char-property pnt 'file))
-	(date (get-char-property pnt 'date))
-	(user (get-char-property pnt 'user))
-	(host (get-char-property pnt 'host))
-	(prev-buf (current-buffer)))
+	(file (get-char-property pnt 'file)))
     (cond
      (file
       (find-file-other-window file))
      (rev
-      (hg-diff hg-view-file-name rev rev prev-buf))
+      (hg-diff hg-view-file-name rev rev))
      ((message "I don't know how to do that yet")))))
 
 (defsubst hg-event-point (event)
   "Return the character position of the mouse event EVENT."
-  (if hg-running-xemacs
-      (event-point event)
-    (posn-point (event-start event))))
+  (hg-feature-cond (xemacs (event-point event))
+		   (t (posn-point (event-start event)))))
 
 (defsubst hg-event-window (event)
   "Return the window over which mouse event EVENT occurred."
-  (if hg-running-xemacs
-      (event-window event)
-    (posn-window (event-start event))))
+  (hg-feature-cond (xemacs (event-window event))
+		   (t (posn-window (event-start event)))))
 
 (defun hg-buffer-mouse-clicked (event)
   "Translate the mouse clicks in a HG log buffer to character events.
@@ -365,15 +394,10 @@ Handle frickin' frackin' gratuitous even
   (select-window (hg-event-window event))
   (hg-buffer-commands (hg-event-point event)))
 
-(unless (fboundp 'view-minor-mode)
-  (defun view-minor-mode (prev-buffer exit-func)
-    (view-mode)))
-
 (defsubst hg-abbrev-file-name (file)
   "Portable wrapper around abbreviate-file-name."
-  (if hg-running-xemacs
-      (abbreviate-file-name file t)
-    (abbreviate-file-name file)))
+  (hg-feature-cond (xemacs (abbreviate-file-name file t))
+		   (t (abbreviate-file-name file))))
 
 (defun hg-read-file-name (&optional prompt default)
   "Read a file or directory name, or a pattern, to use with a command."
@@ -391,9 +415,9 @@ Handle frickin' frackin' gratuitous even
                         (and path (file-name-directory path))
                         nil nil
                         (and path (file-name-nondirectory path))
-                        (if hg-running-xemacs
-                            (cons (quote 'hg-file-history) nil)
-                          nil))))
+                        (hg-feature-cond
+			 (xemacs (cons (quote 'hg-file-history) nil))
+			 (t nil)))))
         path))))
 
 (defun hg-read-number (&optional prompt default)
@@ -465,7 +489,10 @@ directory names from the file system.  W
 	    (dolist (path (hg-config-section "paths" (hg-read-config)))
 	      (setq hg-repo-completion-table
 		    (cons (cons (car path) t) hg-repo-completion-table))
-	      (unless (hg-string-starts-with directory-sep-char (cdr path))
+	      (unless (hg-string-starts-with (hg-feature-cond
+					      (xemacs directory-sep-char)
+					      (t ?/))
+					     (cdr path))
 		(setq hg-repo-completion-table
 		      (cons (cons (cdr path) t) hg-repo-completion-table))))
 	    (completing-read (format "Repository%s: " (or prompt ""))
@@ -486,8 +513,8 @@ directory names from the file system.  W
       (if current-prefix-arg
 	  (let ((revs (split-string
 		       (hg-chomp
-			(hg-run0 "-q" "log" "-r"
-				 (format "-%d:tip" hg-rev-completion-limit)))
+			(hg-run0 "-q" "log" "-l"
+				 (format "%d" hg-rev-completion-limit)))
 		       "[\n:]")))
 	    (dolist (line (split-string (hg-chomp (hg-run0 "tags")) "\n"))
 	      (setq revs (cons (car (split-string line "\\s-")) revs)))
@@ -539,18 +566,6 @@ directory names from the file system.  W
 	    (set-buffer buf)
 	    (hg-mode-line-internal status parents)))))))
   
-(defmacro hg-do-across-repo (path &rest body)
-  (let ((root-name (gensym "root-"))
-	(buf-name (gensym "buf-")))
-    `(let ((,root-name (hg-root ,path)))
-       (save-excursion
-	 (dolist (,buf-name (buffer-list))
-	   (set-buffer ,buf-name)
-	   (when (and hg-status (equal (hg-root buffer-file-name) ,root-name))
-	     ,@body))))))
-
-(put 'hg-do-across-repo 'lisp-indent-function 1)
-
 
 ;;; View mode bits.
 
@@ -568,12 +583,13 @@ current frame."
   (goto-char (point-min))
   (set-buffer-modified-p nil)
   (toggle-read-only t)
-  (view-minor-mode prev-buffer 'hg-exit-view-mode)
-  (use-local-map hg-view-mode-map)
+  (hg-feature-cond (xemacs (view-minor-mode prev-buffer 'hg-exit-view-mode))
+		   (t (view-mode-enter nil 'hg-exit-view-mode)))
+  (setq hg-view-mode t)
   (setq truncate-lines t)
   (when file-name
-    (set (make-local-variable 'hg-view-file-name)
-	 (hg-abbrev-file-name file-name))))
+    (setq hg-view-file-name 
+	  (hg-abbrev-file-name file-name))))
 
 (defun hg-file-status (file)
   "Return status of FILE, or nil if FILE does not exist or is unmanaged."
@@ -581,12 +597,9 @@ current frame."
 	 (exit (car s))
 	 (output (cdr s)))
     (if (= exit 0)
-	(let ((state (assoc (substring output 0 (min (length output) 2))
-			    '(("M " . modified)
-			      ("A " . added)
-			      ("R " . removed)
-			      ("! " . deleted)
-			      ("? " . nil)))))
+	(let ((state (and (>= (length output) 2)
+			  (= (aref output 1) ? )
+			  (assq (aref output 0) hg-state-alist))))
 	  (if state
 	      (cdr state)
 	    'normal)))))
@@ -598,17 +611,11 @@ Each entry is a pair (FILE-NAME . STATUS
 	result)
     (dolist (entry (split-string (hg-chomp (cdr s)) "\n") (nreverse result))
       (let (state name)
-	(if (equal (substring entry 1 2) " ")
-	    (setq state (cdr (assoc (substring entry 0 2)
-				    '(("M " . modified)
-				      ("A " . added)
-				      ("R " . removed)
-				      ("! " . deleted)
-				      ("C " . normal)
-				      ("I " . ignored)
-				      ("? " . nil))))
-		  name (substring entry 2))
-	  (setq name (substring entry 0 (search ": " entry :from-end t))))
+	(cond ((= (aref entry 1) ? )
+	       (setq state (assq (aref entry 0) hg-state-alist)
+		     name (substring entry 2)))
+	      ((string-match "\\(.*\\): " entry)
+	       (setq name (match-string 1 entry))))
 	(setq result (cons (cons name state) result))))))
 
 (defmacro hg-view-output (args &rest body)
@@ -618,7 +625,7 @@ minibuffer.  Otherwise, the buffer is di
 ARGS is of the form (BUFFER-NAME &optional FILE), where BUFFER-NAME is
 the name of the buffer to create, and FILE is the name of the file
 being viewed."
-  (let ((prev-buf (gensym "prev-buf-"))
+  (let ((prev-buf (make-symbol "prev-buf-"))
 	(v-b-name (car args))
 	(v-m-rest (cdr args)))
     `(let ((view-buf-name ,v-b-name)
@@ -717,8 +724,8 @@ you're already familiar with VC, the sam
 will generally work.
 
 Below is a list of many common SCM tasks.  In the list, `G/L\'
-indicates whether a key binding is global (G) to a repository or local
-(L) to a file.  Many commands take a prefix argument.
+indicates whether a key binding is global (G) to a repository or
+local (L) to a file.  Many commands take a prefix argument.
 
 SCM Task                              G/L  Key Binding  Command Name
 --------                              ---  -----------  ------------
@@ -750,8 +757,9 @@ Push changes                          G 
   (run-hooks 'hg-mode-hook))
 
 (defun hg-find-file-hook ()
-  (when (hg-mode-line)
-    (hg-mode)))
+  (ignore-errors
+    (when (hg-mode-line)
+      (hg-mode))))
 
 (add-hook 'find-file-hooks 'hg-find-file-hook)
 
deleted file mode 100644
--- a/contrib/purge/README
+++ /dev/null
@@ -1,60 +0,0 @@
-What is "hg purge"?
-===================
-"purge" is a simple extension for the Mercurial source control management
-system (http://www.selenic.com/mercurial).
-This extension adds a "purge" command to "hg" that removes files not known
-to Mercurial, this is useful to test local and uncommitted changes in the
-otherwise clean source tree.
-
-This means that Mercurial will delete:
- - Unknown files: files marked with "?" by "hg status"
- - Ignored files: files usually ignored by Mercurial because they match a
-   pattern in a ".hgignore" file
- - Empty directories: infact Mercurial ignores directories unless they
-   contain files under source control managment
-But it will leave untouched:
- - Unmodified files tracked by Mercurial
- - Modified files tracked by Mercurial
- - New files added to the repository (with "hg add")
-
-Be careful with "hg purge", you could irreversibly delete some files you
-forgot to add to the repository. If you only want to print the list of
-files that this program would delete use:
-  hg purge --print
-
-To get the most recent version of "hg purge" visit its home page:
-  http://www.barisione.org/apps.html#hg-purge
-
-This program was inspired by the "cvspurge" script contained in CVS utilities
-(http://www.red-bean.com/cvsutils/).
-
-
-How to install
-==============
-The purge extension is distributed with Mercurial, to activate it you need to
-put these lines in your ~/.hgrc:
-
-  [extensions]
-  hgext.purge=
-
-For more information on the configuration files see the man page for "hgrc":
-  man 5 hgrc
-
-
-How to use "hg purge"
-====================
-For help on the usage of "hg purge" use:
-  hg help purge
-
-
-License
-=======
-Copyright (C) 2006 - Marco Barisione <marco@barisione.org>
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-A copy of the GNU General Public License is distributed along
-with Mercurial in the file COPYING.
--- a/contrib/sample.hgrc
+++ b/contrib/sample.hgrc
@@ -43,6 +43,11 @@
 
 # hgext.gpg =
 
+### graphlog - ASCII graph log
+### hg help glog
+
+# hgext.graphlog =
+
 ### hgk - GUI repository browser
 ### hg help view
 
new file mode 100755
--- /dev/null
+++ b/contrib/simplemerge
@@ -0,0 +1,562 @@
+#!/usr/bin/env python
+# Copyright (C) 2004, 2005 Canonical Ltd
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+
+# mbp: "you know that thing where cvs gives you conflict markers?"
+# s: "i hate that."
+
+from mercurial import demandimport
+demandimport.enable()
+
+from mercurial import util, mdiff, fancyopts
+from mercurial.i18n import _
+
+
+class CantReprocessAndShowBase(Exception):
+    pass
+    
+
+def warn(message):
+    sys.stdout.flush()
+    sys.stderr.write(message)
+    sys.stderr.flush()
+
+
+def intersect(ra, rb):
+    """Given two ranges return the range where they intersect or None.
+
+    >>> intersect((0, 10), (0, 6))
+    (0, 6)
+    >>> intersect((0, 10), (5, 15))
+    (5, 10)
+    >>> intersect((0, 10), (10, 15))
+    >>> intersect((0, 9), (10, 15))
+    >>> intersect((0, 9), (7, 15))
+    (7, 9)
+    """
+    assert ra[0] <= ra[1]
+    assert rb[0] <= rb[1]
+    
+    sa = max(ra[0], rb[0])
+    sb = min(ra[1], rb[1])
+    if sa < sb:
+        return sa, sb
+    else:
+        return None
+
+
+def compare_range(a, astart, aend, b, bstart, bend):
+    """Compare a[astart:aend] == b[bstart:bend], without slicing.
+    """
+    if (aend-astart) != (bend-bstart):
+        return False
+    for ia, ib in zip(xrange(astart, aend), xrange(bstart, bend)):
+        if a[ia] != b[ib]:
+            return False
+    else:
+        return True
+        
+
+
+
+class Merge3Text(object):
+    """3-way merge of texts.
+
+    Given strings BASE, OTHER, THIS, tries to produce a combined text
+    incorporating the changes from both BASE->OTHER and BASE->THIS."""
+    def __init__(self, basetext, atext, btext, base=None, a=None, b=None):
+        self.basetext = basetext
+        self.atext = atext
+        self.btext = btext
+        if base is None:
+            base = mdiff.splitnewlines(basetext)
+        if a is None:
+            a = mdiff.splitnewlines(atext)
+        if b is None:
+            b = mdiff.splitnewlines(btext)
+        self.base = base
+        self.a = a
+        self.b = b
+
+
+
+    def merge_lines(self,
+                    name_a=None,
+                    name_b=None,
+                    name_base=None,
+                    start_marker='<<<<<<<',
+                    mid_marker='=======',
+                    end_marker='>>>>>>>',
+                    base_marker=None,
+                    reprocess=False):
+        """Return merge in cvs-like form.
+        """
+        self.conflicts = False
+        newline = '\n'
+        if len(self.a) > 0:
+            if self.a[0].endswith('\r\n'):
+                newline = '\r\n'
+            elif self.a[0].endswith('\r'):
+                newline = '\r'
+        if base_marker and reprocess:
+            raise CantReprocessAndShowBase()
+        if name_a:
+            start_marker = start_marker + ' ' + name_a
+        if name_b:
+            end_marker = end_marker + ' ' + name_b
+        if name_base and base_marker:
+            base_marker = base_marker + ' ' + name_base
+        merge_regions = self.merge_regions()
+        if reprocess is True:
+            merge_regions = self.reprocess_merge_regions(merge_regions)
+        for t in merge_regions:
+            what = t[0]
+            if what == 'unchanged':
+                for i in range(t[1], t[2]):
+                    yield self.base[i]
+            elif what == 'a' or what == 'same':
+                for i in range(t[1], t[2]):
+                    yield self.a[i]
+            elif what == 'b':
+                for i in range(t[1], t[2]):
+                    yield self.b[i]
+            elif what == 'conflict':
+                self.conflicts = True
+                yield start_marker + newline
+                for i in range(t[3], t[4]):
+                    yield self.a[i]
+                if base_marker is not None:
+                    yield base_marker + newline
+                    for i in range(t[1], t[2]):
+                        yield self.base[i]
+                yield mid_marker + newline
+                for i in range(t[5], t[6]):
+                    yield self.b[i]
+                yield end_marker + newline
+            else:
+                raise ValueError(what)
+        
+        
+
+
+
+    def merge_annotated(self):
+        """Return merge with conflicts, showing origin of lines.
+
+        Most useful for debugging merge.        
+        """
+        for t in self.merge_regions():
+            what = t[0]
+            if what == 'unchanged':
+                for i in range(t[1], t[2]):
+                    yield 'u | ' + self.base[i]
+            elif what == 'a' or what == 'same':
+                for i in range(t[1], t[2]):
+                    yield what[0] + ' | ' + self.a[i]
+            elif what == 'b':
+                for i in range(t[1], t[2]):
+                    yield 'b | ' + self.b[i]
+            elif what == 'conflict':
+                yield '<<<<\n'
+                for i in range(t[3], t[4]):
+                    yield 'A | ' + self.a[i]
+                yield '----\n'
+                for i in range(t[5], t[6]):
+                    yield 'B | ' + self.b[i]
+                yield '>>>>\n'
+            else:
+                raise ValueError(what)
+        
+        
+
+
+
+    def merge_groups(self):
+        """Yield sequence of line groups.  Each one is a tuple:
+
+        'unchanged', lines
+             Lines unchanged from base
+
+        'a', lines
+             Lines taken from a
+
+        'same', lines
+             Lines taken from a (and equal to b)
+
+        'b', lines
+             Lines taken from b
+
+        'conflict', base_lines, a_lines, b_lines
+             Lines from base were changed to either a or b and conflict.
+        """
+        for t in self.merge_regions():
+            what = t[0]
+            if what == 'unchanged':
+                yield what, self.base[t[1]:t[2]]
+            elif what == 'a' or what == 'same':
+                yield what, self.a[t[1]:t[2]]
+            elif what == 'b':
+                yield what, self.b[t[1]:t[2]]
+            elif what == 'conflict':
+                yield (what,
+                       self.base[t[1]:t[2]],
+                       self.a[t[3]:t[4]],
+                       self.b[t[5]:t[6]])
+            else:
+                raise ValueError(what)
+
+
+    def merge_regions(self):
+        """Return sequences of matching and conflicting regions.
+
+        This returns tuples, where the first value says what kind we
+        have:
+
+        'unchanged', start, end
+             Take a region of base[start:end]
+
+        'same', astart, aend
+             b and a are different from base but give the same result
+
+        'a', start, end
+             Non-clashing insertion from a[start:end]
+
+        Method is as follows:
+
+        The two sequences align only on regions which match the base
+        and both descendents.  These are found by doing a two-way diff
+        of each one against the base, and then finding the
+        intersections between those regions.  These "sync regions"
+        are by definition unchanged in both and easily dealt with.
+
+        The regions in between can be in any of three cases:
+        conflicted, or changed on only one side.
+        """
+
+        # section a[0:ia] has been disposed of, etc
+        iz = ia = ib = 0
+        
+        for zmatch, zend, amatch, aend, bmatch, bend in self.find_sync_regions():
+            #print 'match base [%d:%d]' % (zmatch, zend)
+            
+            matchlen = zend - zmatch
+            assert matchlen >= 0
+            assert matchlen == (aend - amatch)
+            assert matchlen == (bend - bmatch)
+            
+            len_a = amatch - ia
+            len_b = bmatch - ib
+            len_base = zmatch - iz
+            assert len_a >= 0
+            assert len_b >= 0
+            assert len_base >= 0
+
+            #print 'unmatched a=%d, b=%d' % (len_a, len_b)
+
+            if len_a or len_b:
+                # try to avoid actually slicing the lists
+                equal_a = compare_range(self.a, ia, amatch,
+                                        self.base, iz, zmatch)
+                equal_b = compare_range(self.b, ib, bmatch,
+                                        self.base, iz, zmatch)
+                same = compare_range(self.a, ia, amatch,
+                                     self.b, ib, bmatch)
+
+                if same:
+                    yield 'same', ia, amatch
+                elif equal_a and not equal_b:
+                    yield 'b', ib, bmatch
+                elif equal_b and not equal_a:
+                    yield 'a', ia, amatch
+                elif not equal_a and not equal_b:
+                    yield 'conflict', iz, zmatch, ia, amatch, ib, bmatch
+                else:
+                    raise AssertionError("can't handle a=b=base but unmatched")
+
+                ia = amatch
+                ib = bmatch
+            iz = zmatch
+
+            # if the same part of the base was deleted on both sides
+            # that's OK, we can just skip it.
+
+                
+            if matchlen > 0:
+                assert ia == amatch
+                assert ib == bmatch
+                assert iz == zmatch
+                
+                yield 'unchanged', zmatch, zend
+                iz = zend
+                ia = aend
+                ib = bend
+    
+
+    def reprocess_merge_regions(self, merge_regions):
+        """Where there are conflict regions, remove the agreed lines.
+
+        Lines where both A and B have made the same changes are 
+        eliminated.
+        """
+        for region in merge_regions:
+            if region[0] != "conflict":
+                yield region
+                continue
+            type, iz, zmatch, ia, amatch, ib, bmatch = region
+            a_region = self.a[ia:amatch]
+            b_region = self.b[ib:bmatch]
+            matches = mdiff.get_matching_blocks(''.join(a_region),
+                                                ''.join(b_region))
+            next_a = ia
+            next_b = ib
+            for region_ia, region_ib, region_len in matches[:-1]:
+                region_ia += ia
+                region_ib += ib
+                reg = self.mismatch_region(next_a, region_ia, next_b,
+                                           region_ib)
+                if reg is not None:
+                    yield reg
+                yield 'same', region_ia, region_len+region_ia
+                next_a = region_ia + region_len
+                next_b = region_ib + region_len
+            reg = self.mismatch_region(next_a, amatch, next_b, bmatch)
+            if reg is not None:
+                yield reg
+
+
+    def mismatch_region(next_a, region_ia,  next_b, region_ib):
+        if next_a < region_ia or next_b < region_ib:
+            return 'conflict', None, None, next_a, region_ia, next_b, region_ib
+    mismatch_region = staticmethod(mismatch_region)
+            
+
+    def find_sync_regions(self):
+        """Return a list of sync regions, where both descendents match the base.
+
+        Generates a list of (base1, base2, a1, a2, b1, b2).  There is
+        always a zero-length sync region at the end of all the files.
+        """
+
+        ia = ib = 0
+        amatches = mdiff.get_matching_blocks(self.basetext, self.atext)
+        bmatches = mdiff.get_matching_blocks(self.basetext, self.btext)
+        len_a = len(amatches)
+        len_b = len(bmatches)
+
+        sl = []
+
+        while ia < len_a and ib < len_b:
+            abase, amatch, alen = amatches[ia]
+            bbase, bmatch, blen = bmatches[ib]
+
+            # there is an unconflicted block at i; how long does it
+            # extend?  until whichever one ends earlier.
+            i = intersect((abase, abase+alen), (bbase, bbase+blen))
+            if i:
+                intbase = i[0]
+                intend = i[1]
+                intlen = intend - intbase
+
+                # found a match of base[i[0], i[1]]; this may be less than
+                # the region that matches in either one
+                assert intlen <= alen
+                assert intlen <= blen
+                assert abase <= intbase
+                assert bbase <= intbase
+
+                asub = amatch + (intbase - abase)
+                bsub = bmatch + (intbase - bbase)
+                aend = asub + intlen
+                bend = bsub + intlen
+
+                assert self.base[intbase:intend] == self.a[asub:aend], \
+                       (self.base[intbase:intend], self.a[asub:aend])
+
+                assert self.base[intbase:intend] == self.b[bsub:bend]
+
+                sl.append((intbase, intend,
+                           asub, aend,
+                           bsub, bend))
+
+            # advance whichever one ends first in the base text
+            if (abase + alen) < (bbase + blen):
+                ia += 1
+            else:
+                ib += 1
+            
+        intbase = len(self.base)
+        abase = len(self.a)
+        bbase = len(self.b)
+        sl.append((intbase, intbase, abase, abase, bbase, bbase))
+
+        return sl
+
+
+
+    def find_unconflicted(self):
+        """Return a list of ranges in base that are not conflicted."""
+        am = mdiff.get_matching_blocks(self.basetext, self.atext)
+        bm = mdiff.get_matching_blocks(self.basetext, self.btext)
+
+        unc = []
+
+        while am and bm:
+            # there is an unconflicted block at i; how long does it
+            # extend?  until whichever one ends earlier.
+            a1 = am[0][0]
+            a2 = a1 + am[0][2]
+            b1 = bm[0][0]
+            b2 = b1 + bm[0][2]
+            i = intersect((a1, a2), (b1, b2))
+            if i:
+                unc.append(i)
+
+            if a2 < b2:
+                del am[0]
+            else:
+                del bm[0]
+                
+        return unc
+
+
+# bzr compatible interface, for the tests
+class Merge3(Merge3Text):
+    """3-way merge of texts.
+
+    Given BASE, OTHER, THIS, tries to produce a combined text
+    incorporating the changes from both BASE->OTHER and BASE->THIS.
+    All three will typically be sequences of lines."""
+    def __init__(self, base, a, b):
+        basetext = '\n'.join([i.strip('\n') for i in base] + [''])
+        atext = '\n'.join([i.strip('\n') for i in a] + [''])
+        btext = '\n'.join([i.strip('\n') for i in b] + [''])
+        if util.binary(basetext) or util.binary(atext) or util.binary(btext):
+            raise util.Abort(_("don't know how to merge binary files"))
+        Merge3Text.__init__(self, basetext, atext, btext, base, a, b)
+
+
+def simplemerge(local, base, other, **opts):
+    def readfile(filename):
+        f = open(filename, "rb")
+        text = f.read()
+        f.close()
+        if util.binary(text):
+            msg = _("%s looks like a binary file.") % filename
+            if not opts.get('text'):
+                raise util.Abort(msg)
+            elif not opts.get('quiet'):
+                warn(_('warning: %s\n') % msg)
+        return text
+
+    name_a = local
+    name_b = other
+    labels = opts.get('label', [])
+    if labels:
+        name_a = labels.pop(0)
+    if labels:
+        name_b = labels.pop(0)
+    if labels:
+        raise util.Abort(_("can only specify two labels."))
+
+    localtext = readfile(local)
+    basetext = readfile(base)
+    othertext = readfile(other)
+
+    orig = local
+    local = os.path.realpath(local)
+    if not opts.get('print'):
+        opener = util.opener(os.path.dirname(local))
+        out = opener(os.path.basename(local), "w", atomictemp=True)
+    else:
+        out = sys.stdout
+
+    reprocess = not opts.get('no_minimal')
+
+    m3 = Merge3Text(basetext, localtext, othertext)
+    for line in m3.merge_lines(name_a=name_a, name_b=name_b,
+                               reprocess=reprocess):
+        out.write(line)
+
+    if not opts.get('print'):
+        out.rename()
+
+    if m3.conflicts:
+        if not opts.get('quiet'):
+            warn(_("warning: conflicts during merge.\n"))
+        return 1
+
+options = [('L', 'label', [], _('labels to use on conflict markers')),
+           ('a', 'text', None, _('treat all files as text')),
+           ('p', 'print', None,
+            _('print results instead of overwriting LOCAL')),
+           ('', 'no-minimal', None,
+            _('do not try to minimize conflict regions')),
+           ('h', 'help', None, _('display help and exit')),
+           ('q', 'quiet', None, _('suppress output'))]
+
+usage = _('''simplemerge [OPTS] LOCAL BASE OTHER
+
+    Simple three-way file merge utility with a minimal feature set.
+    
+    Apply to LOCAL the changes necessary to go from BASE to OTHER.
+    
+    By default, LOCAL is overwritten with the results of this operation.
+''')
+
+def showhelp():
+    sys.stdout.write(usage)
+    sys.stdout.write('\noptions:\n')
+
+    out_opts = []
+    for shortopt, longopt, default, desc in options:
+        out_opts.append(('%2s%s' % (shortopt and '-%s' % shortopt,
+                                    longopt and ' --%s' % longopt),
+                         '%s' % desc))
+    opts_len = max([len(opt[0]) for opt in out_opts])
+    for first, second in out_opts:
+        sys.stdout.write(' %-*s  %s\n' % (opts_len, first, second))
+
+class ParseError(Exception):
+    """Exception raised on errors in parsing the command line."""
+
+def main(argv):
+    try:
+        opts = {}
+        try:
+            args = fancyopts.fancyopts(argv[1:], options, opts)
+        except fancyopts.getopt.GetoptError, e:
+            raise ParseError(e)
+        if opts['help']:
+            showhelp()
+            return 0
+        if len(args) != 3:
+                raise ParseError(_('wrong number of arguments'))
+        return simplemerge(*args, **opts)
+    except ParseError, e:
+        sys.stdout.write("%s: %s\n" % (sys.argv[0], e))
+        showhelp()
+        return 1
+    except util.Abort, e:
+        sys.stderr.write("abort: %s\n" % e)
+        return 255
+    except KeyboardInterrupt:
+        return 255
+
+if __name__ == '__main__':
+    import sys
+    import os
+    sys.exit(main(sys.argv))
--- a/contrib/win32/ReadMe.html
+++ b/contrib/win32/ReadMe.html
@@ -46,14 +46,21 @@ hg
       other Mercurial commands should work fine for you.</p>
 
     <h1>Configuration notes</h1>
-    <p>The default editor for commit messages is 'notepad'. You can set the EDITOR
+	<h4>Default editor</h4>
+	The default editor for commit messages is 'notepad'. You can set the EDITOR
     (or HGEDITOR) environment variable to specify your preference or set it in
-    mercurial.ini:</p>
+    mercurial.ini:
     <pre>
 [ui]
 editor = whatever
 </pre>
 
+	<h4>Configuring a Merge program</h4>
+	It should be emphasized that Mercurial by itself doesn't attempt to do a 
+	Merge at the file level, neither does it make any attempt to Resolve the conflicts.
+
+    By default, Mercurial will use the merge program defined by the HGMERGE environment 
+    variable, or uses the one defined in the mercurial.ini file. (see <a href="http://www.selenic.com/mercurial/wiki/index.cgi/MergeProgram">MergeProgram</a> on the Mercurial Wiki for more information)
 
     <h1>Reporting problems</h1>
 
--- a/contrib/win32/mercurial.iss
+++ b/contrib/win32/mercurial.iss
@@ -2,7 +2,7 @@
 ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
 
 [Setup]
-AppCopyright=Copyright 2005, 2006 Matt Mackall and others
+AppCopyright=Copyright 2005-2007 Matt Mackall and others
 AppName=Mercurial
 AppVerName=Mercurial snapshot
 InfoAfterFile=contrib/win32/postinstall.txt
@@ -18,7 +18,7 @@ OutputBaseFilename=Mercurial-snapshot
 DefaultDirName={sd}\Mercurial
 SourceDir=C:\hg\hg-release
 VersionInfoDescription=Mercurial distributed SCM
-VersionInfoCopyright=Copyright 2005, 2006 Matt Mackall and others
+VersionInfoCopyright=Copyright 2005-2007 Matt Mackall and others
 VersionInfoCompany=Matt Mackall and others
 InternalCompressLevel=max
 SolidCompression=true
--- a/doc/Makefile
+++ b/doc/Makefile
@@ -2,7 +2,7 @@ SOURCES=$(wildcard *.[0-9].txt)
 MAN=$(SOURCES:%.txt=%)
 HTML=$(SOURCES:%.txt=%.html)
 PREFIX=/usr/local
-MANDIR=$(PREFIX)/man
+MANDIR=$(PREFIX)/share/man
 INSTALL=install -c
 
 all: man html
@@ -36,8 +36,8 @@ MANIFEST: man html
 install: man
 	for i in $(MAN) ; do \
 	  subdir=`echo $$i | sed -n 's/..*\.\([0-9]\)$$/man\1/p'` ; \
-	  mkdir -p $(MANDIR)/$$subdir ; \
-	  $(INSTALL) $$i $(MANDIR)/$$subdir ; \
+	  mkdir -p $(DESTDIR)/$(MANDIR)/$$subdir ; \
+	  $(INSTALL) $$i $(DESTDIR)/$(MANDIR)/$$subdir ; \
 	done
 
 clean:
--- a/doc/hgrc.5.txt
+++ b/doc/hgrc.5.txt
@@ -215,6 +215,15 @@ extensions::
     # (this extension will get loaded from the file specified)
     myfeature = ~/.hgext/myfeature.py
 
+format::
+
+  usestore;;
+    Enable or disable the "store" repository format which improves
+    compatibility with systems that fold case or otherwise mangle
+    filenames. Enabled by default. Disabling this option will allow
+    you to store longer filenames in some situations at the expense of
+    compatibility.
+
 hooks::
   Commands or Python functions that get automatically executed by
   various actions such as starting or finishing a commit. Multiple
@@ -423,6 +432,9 @@ ui::
   merge;;
     The conflict resolution program to use during a manual merge.
     Default is "hgmerge".
+  patch;;
+    command to use to apply patches. Look for 'gpatch' or 'patch' in PATH if
+    unset.
   quiet;;
     Reduce the amount of output printed.  True or False.  Default is False.
   remotecmd;;
@@ -507,6 +519,11 @@ web::
   push_ssl;;
     Whether to require that inbound pushes be transported over SSL to
     prevent password sniffing.  Default is true.
+  staticurl;;
+    Base URL to use for static files. If unset, static files (e.g.
+    the hgicon.png favicon) will be served by the CGI script itself.
+    Use this setting to serve them directly with the HTTP server.
+    Example: "http://hgserver/static/"
   stripes;;
     How many lines a "zebra stripe" should span in multiline output.
     Default is 1; set to 0 to disable.
--- a/hg
+++ b/hg
@@ -7,6 +7,5 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-from mercurial import commands
-
-commands.run()
+import mercurial.commands
+mercurial.commands.run()
--- a/hgext/acl.py
+++ b/hgext/acl.py
@@ -45,10 +45,10 @@
 #   glob pattern = user4, user5
 #   ** = user6
 
-from mercurial.demandload import *
-from mercurial.i18n import gettext as _
+from mercurial.i18n import _
 from mercurial.node import *
-demandload(globals(), 'getpass mercurial:util')
+from mercurial import util
+import getpass
 
 class checker(object):
     '''acl checker.'''
@@ -91,7 +91,7 @@ class checker(object):
 
     def check(self, node):
         '''return if access allowed, raise exception if not.'''
-        files = self.repo.changelog.read(node)[3]
+        files = self.repo.changectx(node).files()
         if self.deniable:
             for f in files:
                 if self.deny(f):
--- a/hgext/bugzilla.py
+++ b/hgext/bugzilla.py
@@ -52,10 +52,10 @@
 #   [usermap]
 #   committer_email = bugzilla_user_name
 
-from mercurial.demandload import *
-from mercurial.i18n import gettext as _
+from mercurial.i18n import _
 from mercurial.node import *
-demandload(globals(), 'mercurial:cmdutil,templater,util os re time')
+from mercurial import cmdutil, templater, util
+import os, re, time
 
 MySQLdb = None
 
@@ -222,7 +222,7 @@ class bugzilla(object):
     _bug_re = None
     _split_re = None
 
-    def find_bug_ids(self, node, desc):
+    def find_bug_ids(self, ctx):
         '''find valid bug ids that are referred to in changeset
         comments and that do not already have references to this
         changeset.'''
@@ -235,7 +235,7 @@ class bugzilla(object):
         start = 0
         ids = {}
         while True:
-            m = bugzilla._bug_re.search(desc, start)
+            m = bugzilla._bug_re.search(ctx.description(), start)
             if not m:
                 break
             start = m.end()
@@ -246,10 +246,10 @@ class bugzilla(object):
         if ids:
             ids = self.filter_real_bug_ids(ids)
         if ids:
-            ids = self.filter_unknown_bug_ids(node, ids)
+            ids = self.filter_unknown_bug_ids(ctx.node(), ids)
         return ids
 
-    def update(self, bugid, node, changes):
+    def update(self, bugid, ctx):
         '''update bugzilla bug with reference to changeset.'''
 
         def webroot(root):
@@ -268,7 +268,7 @@ class bugzilla(object):
         mapfile = self.ui.config('bugzilla', 'style')
         tmpl = self.ui.config('bugzilla', 'template')
         t = cmdutil.changeset_templater(self.ui, self.repo,
-                                        False, None, mapfile, False)
+                                        False, mapfile, False)
         if not mapfile and not tmpl:
             tmpl = _('changeset {node|short} in repo {root} refers '
                      'to bug {bug}.\ndetails:\n\t{desc|tabindent}')
@@ -276,13 +276,13 @@ class bugzilla(object):
             tmpl = templater.parsestring(tmpl, quoted=False)
             t.use_template(tmpl)
         self.ui.pushbuffer()
-        t.show(changenode=node, changes=changes,
+        t.show(changenode=ctx.node(), changes=ctx.changeset(),
                bug=str(bugid),
                hgweb=self.ui.config('web', 'baseurl'),
                root=self.repo.root,
                webroot=webroot(self.repo.root))
         data = self.ui.popbuffer()
-        self.add_comment(bugid, data, templater.email(changes[1]))
+        self.add_comment(bugid, data, templater.email(ctx.user()))
 
 def hook(ui, repo, hooktype, node=None, **kwargs):
     '''add comment to bugzilla for each changeset that refers to a
@@ -300,12 +300,11 @@ def hook(ui, repo, hooktype, node=None, 
                          hooktype)
     try:
         bz = bugzilla(ui, repo)
-        bin_node = bin(node)
-        changes = repo.changelog.read(bin_node)
-        ids = bz.find_bug_ids(bin_node, changes[4])
+        ctx = repo.changectx(node)
+        ids = bz.find_bug_ids(ctx)
         if ids:
             for id in ids:
-                bz.update(id, bin_node, changes)
+                bz.update(id, ctx)
             bz.notify(ids)
     except MySQLdb.MySQLError, err:
         raise util.Abort(_('database error: %s') % err[1])
--- a/hgext/extdiff.py
+++ b/hgext/extdiff.py
@@ -48,16 +48,15 @@
 # needed files, so running the external diff program will actually be
 # pretty fast (at least faster than having to compare the entire tree).
 
-from mercurial.demandload import demandload
-from mercurial.i18n import gettext as _
+from mercurial.i18n import _
 from mercurial.node import *
-demandload(globals(), 'mercurial:cmdutil,util os shutil tempfile')
+from mercurial import cmdutil, util
+import os, shutil, tempfile
 
 def dodiff(ui, repo, diffcmd, diffopts, pats, opts):
     def snapshot_node(files, node):
         '''snapshot files as of some revision'''
-        changes = repo.changelog.read(node)
-        mf = repo.manifest.read(changes[0])
+        mf = repo.changectx(node).manifest()
         dirname = os.path.basename(repo.root)
         if dirname == "":
             dirname = "root"
@@ -77,7 +76,8 @@ def dodiff(ui, repo, diffcmd, diffopts, 
             destdir = os.path.dirname(dest)
             if not os.path.isdir(destdir):
                 os.makedirs(destdir)
-            repo.wwrite(wfn, repo.file(fn).read(mf[fn]), open(dest, 'wb'))
+            data = repo.wwritedata(wfn, repo.file(wfn).read(mf[wfn]))
+            open(dest, 'wb').write(data)
         return dirname
 
     def snapshot_wdir(files):
--- a/hgext/fetch.py
+++ b/hgext/fetch.py
@@ -5,10 +5,9 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-from mercurial.demandload import *
-from mercurial.i18n import gettext as _
+from mercurial.i18n import _
 from mercurial.node import *
-demandload(globals(), 'mercurial:commands,hg,node,util')
+from mercurial import commands, hg, node, util
 
 def fetch(ui, repo, source='default', **opts):
     '''Pull changes from a remote repository, merge new changes if needed.
--- a/hgext/gpg.py
+++ b/hgext/gpg.py
@@ -8,7 +8,7 @@
 import os, tempfile, binascii
 from mercurial import util
 from mercurial import node as hgnode
-from mercurial.i18n import gettext as _
+from mercurial.i18n import _
 
 class gpg:
     def __init__(self, path, key=None):
new file mode 100644
--- /dev/null
+++ b/hgext/graphlog.py
@@ -0,0 +1,266 @@
+# ASCII graph log extension for Mercurial
+#
+# Copyright 2007 Joel Rosdahl <joel@rosdahl.net>
+# 
+# This software may be used and distributed according to the terms of
+# the GNU General Public License, incorporated herein by reference.
+
+import sys
+from mercurial.cmdutil import revrange, show_changeset
+from mercurial.i18n import _
+from mercurial.node import nullid, nullrev
+from mercurial.util import Abort
+
+def revision_grapher(repo, start_rev, stop_rev):
+    """incremental revision grapher
+
+    This generator function walks through the revision history 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 = []
+    while curr_rev >= stop_rev:
+        node = repo.changelog.node(curr_rev)
+
+        # Compute revs and next_revs.
+        if curr_rev not in revs:
+            # New head.
+            revs.append(curr_rev)
+        rev_index = revs.index(curr_rev)
+        next_revs = revs[:]
+
+        # Add parents to next_revs.
+        parents = get_rev_parents(repo, curr_rev)
+        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)))
+
+        n_columns_diff = len(next_revs) - len(revs)
+        yield (curr_rev, node, rev_index, edges, len(revs), n_columns_diff)
+
+        revs = next_revs
+        curr_rev -= 1
+
+def get_rev_parents(repo, rev):
+    return [x for x in repo.changelog.parentrevs(rev) if x != nullrev]
+
+def fix_long_right_edges(edges):
+    for (i, (start, end)) in enumerate(edges):
+        if end > start:
+            edges[i] = (start, end + 1)
+
+def draw_edges(edges, nodeline, interline):
+    for (start, end) in edges:
+        if start == end + 1:
+            interline[2 * end + 1] = "/"
+        elif start == end - 1:
+            interline[2 * start + 1] = "\\"
+        elif start == end:
+            interline[2 * start] = "|"
+        else:
+            nodeline[2 * end] = "+"
+            if start > end:
+                (start, end) = (end,start)
+            for i in range(2 * start + 1, 2 * end):
+                if nodeline[i] != "+":
+                    nodeline[i] = "-"
+
+def format_line(line, level, logstr):
+    text = "%-*s %s" % (2 * level, "".join(line), logstr)
+    return "%s\n" % text.rstrip()
+
+def get_nodeline_edges_tail(
+        node_index, p_node_index, n_columns, n_columns_diff, p_diff, fix_tail):
+    if fix_tail and n_columns_diff == p_diff and n_columns_diff != 0:
+        # Still going in the same non-vertical direction.
+        if n_columns_diff == -1:
+            start = max(node_index + 1, p_node_index)
+            tail = ["|", " "] * (start - node_index - 1)
+            tail.extend(["/", " "] * (n_columns - start))
+            return tail
+        else:
+            return ["\\", " "] * (n_columns - node_index - 1)
+    else:
+        return ["|", " "] * (n_columns - node_index - 1)
+
+def get_padding_line(ni, n_columns, edges):
+    line = []
+    line.extend(["|", " "] * ni)
+    if (ni, ni - 1) in edges or (ni, ni) in edges:
+        # (ni, ni - 1)      (ni, ni)
+        # | | | |           | | | |
+        # +---o |           | o---+
+        # | | c |           | c | |
+        # | |/ /            | |/ /
+        # | | |             | | |
+        c = "|"
+    else:
+        c = " "
+    line.extend([c, " "])
+    line.extend(["|", " "] * (n_columns - ni - 1))
+    return line
+
+def get_limit(limit_opt):
+    if limit_opt:
+        try:
+            limit = int(limit_opt)
+        except ValueError:
+            raise Abort(_("limit must be a positive integer"))
+        if limit <= 0:
+            raise Abort(_("limit must be positive"))
+    else:
+        limit = sys.maxint
+    return limit
+
+def get_revs(repo, rev_opt):
+    if rev_opt:
+        revs = revrange(repo, rev_opt)
+        return (max(revs), min(revs))
+    else:
+        return (repo.changelog.count() - 1, 0)
+
+def graphlog(ui, repo, *args, **opts):
+    """show revision history alongside an ASCII revision graph
+
+    Print a revision history alongside a revision graph drawn with
+    ASCII characters.
+
+    Nodes printed as an @ character are parents of the working
+    directory.
+    """
+
+    limit = get_limit(opts["limit"])
+    (start_rev, stop_rev) = get_revs(repo, opts["rev"])
+    stop_rev = max(stop_rev, start_rev - limit + 1)
+    if start_rev == nullrev:
+        return
+    cs_printer = show_changeset(ui, repo, opts)
+    grapher = revision_grapher(repo, start_rev, stop_rev)
+    repo_parents = repo.dirstate.parents()
+    prev_n_columns_diff = 0
+    prev_node_index = 0
+
+    for (rev, node, node_index, edges, n_columns, n_columns_diff) in grapher:
+        # log_strings is the list of all log strings to draw alongside
+        # the graph.
+        ui.pushbuffer()
+        cs_printer.show(rev, node)
+        log_strings = ui.popbuffer().split("\n")[:-1]
+
+        if n_columns_diff == -1:
+            # Transform
+            #
+            #     | | |        | | |
+            #     o | |  into  o---+
+            #     |X /         |/ /
+            #     | |          | |
+            fix_long_right_edges(edges)
+
+        # add_padding_line says whether to rewrite
+        #
+        #     | | | |        | | | |
+        #     | o---+  into  | o---+
+        #     |  / /         |   | |  # <--- padding line
+        #     o | |          |  / /
+        #                    o | |
+        add_padding_line = \
+            len(log_strings) > 2 and \
+            n_columns_diff == -1 and \
+            [x for (x, y) in edges if x + 1 < y]
+
+        # fix_nodeline_tail says whether to rewrite
+        #
+        #     | | o | |        | | o | |
+        #     | | |/ /         | | |/ /
+        #     | o | |    into  | o / /   # <--- fixed nodeline tail
+        #     | |/ /           | |/ /
+        #     o | |            o | |
+        fix_nodeline_tail = len(log_strings) <= 2 and not add_padding_line
+
+        # nodeline is the line containing the node character (@ or o).
+        nodeline = ["|", " "] * node_index
+        if node in repo_parents:
+            node_ch = "@"
+        else:
+            node_ch = "o"
+        nodeline.extend([node_ch, " "])
+
+        nodeline.extend(
+            get_nodeline_edges_tail(
+                node_index, prev_node_index, n_columns, n_columns_diff,
+                prev_n_columns_diff, fix_nodeline_tail))
+
+        # shift_interline is the line containing the non-vertical
+        # edges between this entry and the next.
+        shift_interline = ["|", " "] * node_index
+        if n_columns_diff == -1:
+            n_spaces = 1
+            edge_ch = "/"
+        elif n_columns_diff == 0:
+            n_spaces = 2
+            edge_ch = "|"
+        else:
+            n_spaces = 3
+            edge_ch = "\\"
+        shift_interline.extend(n_spaces * [" "])
+        shift_interline.extend([edge_ch, " "] * (n_columns - node_index - 1))
+
+        # Draw edges from the current node to its parents.
+        draw_edges(edges, nodeline, shift_interline)
+
+        # lines is the list of all graph lines to print.
+        lines = [nodeline]
+        if add_padding_line:
+            lines.append(get_padding_line(node_index, n_columns, edges))
+        lines.append(shift_interline)
+
+        # Make sure that there are as many graph lines as there are
+        # log strings.
+        while len(log_strings) < len(lines):
+            log_strings.append("")
+        if len(lines) < len(log_strings):
+            extra_interline = ["|", " "] * (n_columns + n_columns_diff)
+            while len(lines) < len(log_strings):
+                lines.append(extra_interline)
+
+        # Print lines.
+        indentation_level = max(n_columns, n_columns + n_columns_diff)
+        for (line, logstr) in zip(lines, log_strings):
+            ui.write(format_line(line, indentation_level, logstr))
+
+        # ...and start over.
+        prev_node_index = node_index
+        prev_n_columns_diff = n_columns_diff
+
+cmdtable = {
+    "glog":
+    (graphlog,
+     [("l", "limit", "", _("limit number of changes displayed")),
+      ("p", "patch", False, _("show patch")),
+      ("r", "rev", [], _("show the specified revision or range")),
+      ("", "style", "", _("display using template map file")),
+      ("", "template", "", _("display with template"))],
+     "hg glog [OPTIONS]"),
+}
--- a/hgext/hbisect.py
+++ b/hgext/hbisect.py
@@ -6,9 +6,9 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-from mercurial.i18n import gettext as _
-from mercurial.demandload import demandload
-demandload(globals(), "os sys sets mercurial:hg,util,commands,cmdutil")
+from mercurial.i18n import _
+from mercurial import hg, util, commands, cmdutil
+import os, sys, sets
 
 versionstr = "0.0.3"
 
@@ -252,8 +252,21 @@ def test(ui, repo, rev):
     return 0
 
 def bisect_run(ui, repo, cmd=None, *args):
-    """bisect extension: dichotomic search in the DAG of changesets
-for subcommands see "hg bisect help\"
+    """Dichotomic search in the DAG of changesets
+
+This extension helps to find changesets which cause problems.
+To use, mark the earliest changeset you know introduces the problem
+as bad, then mark the latest changeset which is free from the problem
+as good. Bisect will update your working directory to a revision for
+testing. Once you have performed tests, mark the working directory
+as bad or good and bisect will either update to another candidate
+changeset or announce that it has found the bad revision.
+
+Note: bisect expects bad revisions to be descendants of good revisions.
+If you are looking for the point at which a problem was fixed, then make
+the problem-free state "bad" and the problematic state "good."
+
+For subcommands see "hg bisect help\"
     """
     def help_(cmd=None, *args):
         """show help for a given bisect subcommand or all subcommands"""
--- a/hgext/hgk.py
+++ b/hgext/hgk.py
@@ -5,26 +5,18 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-from mercurial.demandload import *
-demandload(globals(), 'time sys signal os')
-demandload(globals(), 'mercurial:hg,fancyopts,commands,ui,util,patch,revlog')
+import sys, os
+from mercurial import hg, fancyopts, commands, ui, util, patch, revlog
 
 def difftree(ui, repo, node1=None, node2=None, *files, **opts):
     """diff trees from two commits"""
     def __difftree(repo, node1, node2, files=[]):
-        if node2:
-            change = repo.changelog.read(node2)
-            mmap2 = repo.manifest.read(change[0])
-            status = repo.status(node1, node2, files=files)[:5]
-            modified, added, removed, deleted, unknown = status
-        else:
-            status = repo.status(node1, files=files)[:5]
-            modified, added, removed, deleted, unknown = status
-            if not node1:
-                node1 = repo.dirstate.parents()[0]
+        assert node2 is not None
+        mmap = repo.changectx(node1).manifest()
+        mmap2 = repo.changectx(node2).manifest()
+        status = repo.status(node1, node2, files=files)[:5]
+        modified, added, removed, deleted, unknown = status
 
-        change = repo.changelog.read(node1)
-        mmap = repo.manifest.read(change[0])
         empty = hg.short(hg.nullid)
 
         for f in modified:
@@ -70,32 +62,30 @@ def difftree(ui, repo, node1=None, node2
         if not opts['stdin']:
             break
 
-def catcommit(repo, n, prefix, changes=None):
+def catcommit(repo, n, prefix, ctx=None):
     nlprefix = '\n' + prefix;
-    (p1, p2) = repo.changelog.parents(n)
-    (h, h1, h2) = map(hg.short, (n, p1, p2))
-    (i1, i2) = map(repo.changelog.rev, (p1, p2))
-    if not changes:
-        changes = repo.changelog.read(n)
-    print "tree %s" % (hg.short(changes[0]))
-    if i1 != hg.nullrev: print "parent %s" % (h1)
-    if i2 != hg.nullrev: print "parent %s" % (h2)
-    date_ar = changes[2]
-    date = int(float(date_ar[0]))
-    lines = changes[4].splitlines()
+    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()))
+    date = ctx.date()
+    description = ctx.description().replace("\0", "")
+    lines = description.splitlines()
     if lines and lines[-1].startswith('committer:'):
         committer = lines[-1].split(': ')[1].rstrip()
     else:
-        committer = changes[1]
+        committer = ctx.user()
 
-    print "author %s %s %s" % (changes[1], date, date_ar[1])
-    print "committer %s %s %s" % (committer, date, date_ar[1])
-    print "revision %d" % repo.changelog.rev(n)
+    print "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 ""
     if prefix != "":
-        print "%s%s" % (prefix, changes[4].replace('\n', nlprefix).strip())
+        print "%s%s" % (prefix, description.replace('\n', nlprefix).strip())
     else:
-        print changes[4]
+        print description
     if prefix:
         sys.stdout.write('\0')
 
@@ -146,8 +136,7 @@ def catfile(ui, repo, type=None, r=None,
 # you can specify a commit to stop at by starting the sha1 with ^
 def revtree(args, repo, full="tree", maxnr=0, parents=False):
     def chlogwalk():
-        ch = repo.changelog
-        count = ch.count()
+        count = repo.changelog.count()
         i = count
         l = [0] * 100
         chunk = 100
@@ -163,7 +152,8 @@ def revtree(args, repo, full="tree", max
                     l[chunk - x:] = [0] * (chunk - x)
                     break
                 if full != None:
-                    l[x] = ch.read(ch.node(i + x))
+                    l[x] = repo.changectx(i + x)
+                    l[x].changeset() # force reading
                 else:
                     l[x] = 1
             for x in xrange(chunk-1, -1, -1):
@@ -217,7 +207,7 @@ def revtree(args, repo, full="tree", max
 
     # walk the repository looking for commits that are in our
     # reachability graph
-    for i, changes in chlogwalk():
+    for i, ctx in chlogwalk():
         n = repo.changelog.node(i)
         mask = is_reachable(want_sha1, reachable, n)
         if mask:
@@ -232,13 +222,13 @@ def revtree(args, repo, full="tree", max
                 print hg.short(n) + parentstr
             elif full == "commit":
                 print hg.short(n) + parentstr
-                catcommit(repo, n, '    ', changes)
+                catcommit(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 = changes[2][0]
+                date = ctx.date()[0]
                 print "%s %s:%s" % (date, h, mask),
                 mask = is_reachable(want_sha1, reachable, p1)
                 if i1 != hg.nullrev and mask > 0:
--- a/hgext/mq.py
+++ b/hgext/mq.py
@@ -29,14 +29,16 @@ remove patch from applied stack         
 refresh contents of top applied patch     qrefresh
 '''
 
-from mercurial.demandload import *
-from mercurial.i18n import gettext as _
-from mercurial import commands
-demandload(globals(), "os sys re struct traceback errno bz2")
-demandload(globals(), "mercurial:cmdutil,hg,patch,revlog,util,changegroup")
+from mercurial.i18n import _
+from mercurial import commands, cmdutil, hg, patch, revlog, util, changegroup
+import os, sys, re, errno
 
 commands.norepo += " qclone qversion"
 
+# Patch names looks like unix-file names.
+# They must be joinable with queue directory and result in the patch path.
+normname = util.normpath
+
 class statusentry:
     def __init__(self, rev, name=None):
         if not name:
@@ -304,6 +306,15 @@ class queue:
             message.insert(0, subject)
         return (message, comments, user, date, diffstart > 1)
 
+    def removeundo(self, repo):
+        undo = repo.sjoin('undo')
+        if not os.path.exists(undo):
+            return
+        try:
+            os.unlink(undo)
+        except OSError, inst:
+            self.ui.warn('error removing undo: %s\n' % str(inst))
+
     def printdiff(self, repo, node1, node2=None, files=None,
                   fp=None, changes=None, opts={}):
         fns, matchfn, anypats = cmdutil.matchpats(repo, files, opts)
@@ -328,11 +339,12 @@ class queue:
         hg.clean(repo, head, wlock=wlock)
         self.strip(repo, n, update=False, backup='strip', wlock=wlock)
 
-        c = repo.changelog.read(rev)
+        ctx = repo.changectx(rev)
         ret = hg.merge(repo, rev, wlock=wlock)
         if ret:
             raise util.Abort(_("update returned %d") % ret)
-        n = repo.commit(None, c[4], c[1], force=1, wlock=wlock)
+        n = repo.commit(None, ctx.description(), ctx.user(),
+                        force=1, wlock=wlock)
         if n == None:
             raise util.Abort(_("repo commit failed"))
         try:
@@ -346,6 +358,7 @@ class queue:
             patchf.write(comments)
         self.printdiff(repo, head, n, fp=patchf)
         patchf.close()
+        self.removeundo(repo)
         return (0, n)
 
     def qparents(self, repo, rev=None):
@@ -378,6 +391,7 @@ class queue:
             pname = ".hg.patches.merge.marker"
             n = repo.commit(None, '[mq]: merge marker', user=None, force=1,
                             wlock=wlock)
+            self.removeundo(repo)
             self.applied.append(statusentry(revlog.hex(n), pname))
             self.applied_dirty = 1
 
@@ -403,6 +417,7 @@ class queue:
                 self.applied_dirty = 1
             if err:
                 return (err, head)
+        self.save_dirty()
         return (0, head)
 
     def patch(self, repo, patchfile):
@@ -512,6 +527,7 @@ class queue:
                 self.ui.warn("fuzz found when applying patch, stopping\n")
                 err = 1
                 break
+        self.removeundo(repo)
         return (err, n)
 
     def delete(self, repo, patches, opts):
@@ -610,6 +626,7 @@ class queue:
         if r: r.add([patch])
         if commitfiles:
             self.refresh(repo, short=True)
+        self.removeundo(repo)
 
     def strip(self, repo, rev, update=True, backup="all", wlock=None):
         def limitheads(chlog, stop):
@@ -641,15 +658,12 @@ class queue:
             self.ui.warn("saving bundle to %s\n" % name)
             return changegroup.writebundle(cg, name, "HG10BZ")
 
-        def stripall(rev, revnum):
-            cl = repo.changelog
-            c = cl.read(rev)
-            mm = repo.manifest.read(c[0])
+        def stripall(revnum):
+            mm = repo.changectx(rev).manifest()
             seen = {}
 
-            for x in xrange(revnum, cl.count()):
-                c = cl.read(cl.node(x))
-                for f in c[3]:
+            for x in xrange(revnum, repo.changelog.count()):
+                for f in repo.changectx(x).files():
                     if f in seen:
                         continue
                     seen[f] = 1
@@ -731,11 +745,12 @@ class queue:
             backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip')
             chgrpfile = bundle(backupch)
 
-        stripall(rev, revnum)
+        stripall(revnum)
 
         change = chlog.read(rev)
         chlog.strip(revnum, revnum)
         repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
+        self.removeundo(repo)
         if saveheads:
             self.ui.status("adding branch\n")
             commands.unbundle(self.ui, repo, "file:%s" % chgrpfile,
@@ -829,10 +844,29 @@ class queue:
         if not wlock:
             wlock = repo.wlock()
         patch = self.lookup(patch)
-        if patch and self.isapplied(patch):
-            raise util.Abort(_("patch %s is already applied") % patch)
+        # Suppose our series file is: A B C and the current 'top' patch is B.
+        # qpush C should be performed (moving forward)
+        # qpush B is a NOP (no change)
+        # qpush A is an error (can't go backwards with qpush)
+        if patch:
+            info = self.isapplied(patch)
+            if info:
+                if info[0] < len(self.applied) - 1:
+                    raise util.Abort(_("cannot push to a previous patch: %s") %
+                                     patch)
+                if info[0] < len(self.series) - 1:
+                    self.ui.warn(_('qpush: %s is already at the top\n') % patch)
+                else:
+                    self.ui.warn(_('all patches are currently applied\n'))
+                return
+
+        # Following the above example, starting at 'top' of B:
+        #  qpush should be performed (pushes C), but a subsequent qpush without
+        #  an argument is an error (nothing to apply). This allows a loop
+        #  of "...while hg qpush..." to work as it detects an error when done
         if self.series_end() == len(self.series):
-            raise util.Abort(_("patch series fully applied"))
+            self.ui.warn(_('patch series already fully applied\n'))
+            return 1
         if not force:
             self.check_localchanges(repo)
 
@@ -877,14 +911,7 @@ class queue:
             wlock=None):
         def getfile(f, rev):
             t = repo.file(f).read(rev)
-            try:
-                repo.wfile(f, "w").write(t)
-            except IOError:
-                try:
-                    os.makedirs(os.path.dirname(repo.wjoin(f)))
-                except OSError, err:
-                    if err.errno != errno.EEXIST: raise
-                repo.wfile(f, "w").write(t)
+            repo.wfile(f, "w").write(t)
 
         if not wlock:
             wlock = repo.wlock()
@@ -896,8 +923,12 @@ class queue:
             info = self.isapplied(patch)
             if not info:
                 raise util.Abort(_("patch %s is not applied") % patch)
+
         if len(self.applied) == 0:
-            raise util.Abort(_("no patches applied"))
+            # Allow qpop -a to work repeatedly,
+            # but not qpop without an argument
+            self.ui.warn(_("no patches applied\n"))
+            return not all
 
         if not update:
             parents = repo.dirstate.parents()
@@ -989,8 +1020,11 @@ class queue:
             if comments:
                 # Remove existing message.
                 ci = 0
+                subj = None
                 for mi in xrange(len(message)):
-                    while message[mi] != comments[ci]:
+                    if comments[ci].lower().startswith('subject: '):
+                        subj = comments[ci][9:]
+                    while message[mi] != comments[ci] and message[mi] != subj:
                         ci += 1
                     del comments[ci]
             comments.append(msg)
@@ -1096,7 +1130,7 @@ class queue:
                     mm.append(m[i])
                     del m[i]
             repo.dirstate.update(m, 'n')
-            repo.dirstate.update(mm, 'n', st_mtime=0)
+            repo.dirstate.update(mm, 'n', st_mtime=-1, st_size=-1)
             repo.dirstate.forget(forget)
 
             if not msg:
@@ -1112,6 +1146,7 @@ class queue:
                             force=1, wlock=wlock)
             self.applied[-1] = statusentry(revlog.hex(n), patchfn)
             self.applied_dirty = 1
+            self.removeundo(repo)
         else:
             self.printdiff(repo, patchparent, fp=patchf)
             patchf.close()
@@ -1132,9 +1167,13 @@ class queue:
             self.push(repo, force=True, wlock=wlock)
 
     def init(self, repo, create=False):
-        if os.path.isdir(self.path):
+        if not create and os.path.isdir(self.path):
             raise util.Abort(_("patch queue directory already exists"))
-        os.mkdir(self.path)
+        try:
+            os.mkdir(self.path)
+        except OSError, inst:
+            if inst.errno != errno.EEXIST or not create:
+                raise
         if create:
             return self.qrepo(create=True)
 
@@ -1287,6 +1326,7 @@ class queue:
             return 1
         self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
         self.applied_dirty = 1
+        self.removeundo(repo)
 
     def full_series_end(self):
         if len(self.applied) > 0:
@@ -1391,7 +1431,7 @@ class queue:
                 lastparent = p1
 
                 if not patchname:
-                    patchname = '%d.diff' % r
+                    patchname = normname('%d.diff' % r)
                 checkseries(patchname)
                 checkfile(patchname)
                 self.full_series.insert(0, patchname)
@@ -1413,7 +1453,7 @@ class queue:
                 if filename == '-':
                     raise util.Abort(_('-e is incompatible with import from -'))
                 if not patchname:
-                    patchname = filename
+                    patchname = normname(filename)
                 if not os.path.isfile(self.join(patchname)):
                     raise util.Abort(_("patch %s does not exist") % patchname)
             else:
@@ -1427,7 +1467,7 @@ class queue:
                 except IOError:
                     raise util.Abort(_("unable to read %s") % patchname)
                 if not patchname:
-                    patchname = os.path.basename(filename)
+                    patchname = normname(os.path.basename(filename))
                 checkfile(patchname)
                 patchf = self.opener(patchname, "w")
                 patchf.write(text)
@@ -1515,13 +1555,16 @@ def init(ui, repo, **opts):
     r = q.init(repo, create=opts['create_repo'])
     q.save_dirty()
     if r:
-        fp = r.wopener('.hgignore', 'w')
-        print >> fp, 'syntax: glob'
-        print >> fp, 'status'
-        print >> fp, 'guards'
-        fp.close()
-        r.wopener('series', 'w').close()
+        if not os.path.exists(r.wjoin('.hgignore')):
+            fp = r.wopener('.hgignore', 'w')
+            fp.write('syntax: glob\n')
+            fp.write('status\n')
+            fp.write('guards\n')
+            fp.close()
+        if not os.path.exists(r.wjoin('series')):
+            r.wopener('series', 'w').close()
         r.add(['.hgignore', 'series'])
+        commands.add(ui, r)
     return 0
 
 def clone(ui, source, dest=None, **opts):
@@ -1640,6 +1683,9 @@ def refresh(ui, repo, *pats, **opts):
     If any file patterns are provided, the refreshed patch will contain only
     the modifications that match those patterns; the remaining modifications
     will remain in the working directory.
+
+    hg add/remove/copy/rename work as usual, though you might want to use
+    git-style patches (--git or [diff] git=1) to track copies and renames.
     """
     q = repo.mq
     message = commands.logmessage(opts)
@@ -1717,6 +1763,17 @@ def fold(ui, repo, *files, **opts):
     q.delete(repo, patches, opts)
     q.save_dirty()
 
+def goto(ui, repo, patch, **opts):
+    '''push or pop patches until named patch is at top of stack'''
+    q = repo.mq
+    patch = q.lookup(patch)
+    if q.isapplied(patch):
+        ret = q.pop(repo, patch, force=opts['force'])
+    else:
+        ret = q.push(repo, patch, force=opts['force'])
+    q.save_dirty()
+    return ret
+
 def guard(ui, repo, *args, **opts):
     '''set or print guards for a patch
 
@@ -1811,7 +1868,8 @@ def push(ui, repo, patch=None, **opts):
 
     if opts['all']:
         if not q.series:
-            raise util.Abort(_('no patches in series'))
+            ui.warn(_('no patches in series\n'))
+            return 0
         patch = q.series[-1]
     if opts['merge']:
         if opts['name']:
@@ -1836,9 +1894,10 @@ def pop(ui, repo, patch=None, **opts):
         localupdate = False
     else:
         q = repo.mq
-    q.pop(repo, patch, force=opts['force'], update=localupdate, all=opts['all'])
+    ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
+                all=opts['all'])
     q.save_dirty()
-    return 0
+    return ret
 
 def rename(ui, repo, patch, name=None, **opts):
     """rename a patch
@@ -1861,7 +1920,7 @@ def rename(ui, repo, patch, name=None, *
         patch = q.lookup('qtip')
     absdest = q.join(name)
     if os.path.isdir(absdest):
-        name = os.path.join(name, os.path.basename(patch))
+        name = normname(os.path.join(name, os.path.basename(patch)))
         absdest = q.join(name)
     if os.path.exists(absdest):
         raise util.Abort(_('%s already exists') % absdest)
@@ -2066,7 +2125,7 @@ def reposetup(ui, repo):
             return super(mqrepo, self).commit(*args, **opts)
 
         def push(self, remote, force=False, revs=None):
-            if self.mq.applied and not force:
+            if self.mq.applied and not force and not revs:
                 raise util.Abort(_('source has mq patches applied'))
             return super(mqrepo, self).push(remote, force, revs)
 
@@ -2080,14 +2139,15 @@ def reposetup(ui, repo):
             if not q.applied:
                 return tagscache
 
-            mqtags = [(patch.rev, patch.name) for patch in q.applied]
+            mqtags = [(revlog.bin(patch.rev), patch.name) for patch in q.applied]
             mqtags.append((mqtags[-1][0], 'qtip'))
             mqtags.append((mqtags[0][0], 'qbase'))
+            mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
             for patch in mqtags:
                 if patch[1] in tagscache:
                     self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
                 else:
-                    tagscache[patch[1]] = revlog.bin(patch[0])
+                    tagscache[patch[1]] = patch[0]
 
             return tagscache
 
@@ -2154,6 +2214,8 @@ cmdtable = {
           ('k', 'keep', None, _('keep folded patch files'))
           ] + commands.commitopts,
          'hg qfold [-e] [-m <text>] [-l <file] PATCH...'),
+    'qgoto': (goto, [('f', 'force', None, _('overwrite any local changes'))],
+              'hg qgoto [OPT]... PATCH'),
     'qguard': (guard, [('l', 'list', None, _('list all patches and guards')),
                        ('n', 'none', None, _('drop all guards'))],
                'hg qguard [PATCH] [+GUARD]... [-GUARD]...'),
--- a/hgext/notify.py
+++ b/hgext/notify.py
@@ -65,11 +65,10 @@
 # if you like, you can put notify config file in repo that users can
 # push changes to, they can manage their own subscriptions.
 
-from mercurial.demandload import *
-from mercurial.i18n import gettext as _
+from mercurial.i18n import _
 from mercurial.node import *
-demandload(globals(), 'mercurial:patch,cmdutil,templater,util,mail')
-demandload(globals(), 'email.Parser fnmatch socket time')
+from mercurial import patch, cmdutil, templater, util, mail
+import email.Parser, fnmatch, socket, time
 
 # template for single changeset can include email headers.
 single_template = '''
@@ -113,7 +112,7 @@ class notifier(object):
         template = (self.ui.config('notify', hooktype) or
                     self.ui.config('notify', 'template'))
         self.t = cmdutil.changeset_templater(self.ui, self.repo,
-                                             False, None, mapfile, False)
+                                             False, mapfile, False)
         if not mapfile and not template:
             template = deftemplates.get(hooktype) or single_template
         if template:
--- a/hgext/patchbomb.py
+++ b/hgext/patchbomb.py
@@ -63,11 +63,11 @@
 #
 # That should be all.  Now your patchbomb is on its way out.
 
-from mercurial.demandload import *
-demandload(globals(), '''email.MIMEMultipart email.MIMEText email.Utils
-                         mercurial:cmdutil,commands,hg,mail,ui,patch,util
-                         os errno popen2 socket sys tempfile''')
-from mercurial.i18n import gettext as _
+import os, errno, socket, tempfile
+import email.MIMEMultipart, email.MIMEText, email.MIMEBase
+import email.Utils, email.Encoders
+from mercurial import cmdutil, commands, hg, mail, ui, patch, util
+from mercurial.i18n import _
 from mercurial.node import *
 
 try:
@@ -77,17 +77,48 @@ try:
 except ImportError: pass
 
 def patchbomb(ui, repo, *revs, **opts):
-    '''send changesets as a series of patch emails
+    '''send changesets by email
 
-    The series starts with a "[PATCH 0 of N]" introduction, which
-    describes the series as a whole.
+    By default, diffs are sent in the format generated by hg export,
+    one per message.  The series starts with a "[PATCH 0 of N]"
+    introduction, which describes the series as a whole.
 
     Each patch email has a Subject line of "[PATCH M of N] ...", using
     the first line of the changeset description as the subject text.
     The message contains two or three body parts.  First, the rest of
     the changeset description.  Next, (optionally) if the diffstat
     program is installed, the result of running diffstat on the patch.
-    Finally, the patch itself, as generated by "hg export".'''
+    Finally, the patch itself, as generated by "hg export".
+
+    With --outgoing, emails will be generated for patches not
+    found in the destination repository (or only those which are
+    ancestors of the specified revisions if any are provided)
+
+    With --bundle, changesets are selected as for --outgoing,
+    but a single email containing a binary Mercurial bundle as an
+    attachment will be sent.
+
+    Examples:
+
+    hg email -r 3000          # send patch 3000 only
+    hg email -r 3000 -r 3001  # send patches 3000 and 3001
+    hg email -r 3000:3005     # send patches 3000 through 3005
+    hg email 3000             # send patch 3000 (deprecated)
+
+    hg email -o               # send all patches not in default
+    hg email -o DEST          # send all patches not in DEST
+    hg email -o -r 3000       # send all ancestors of 3000 not in default
+    hg email -o -r 3000 DEST  # send all ancestors of 3000 not in DEST
+
+    hg email -b               # send bundle of all patches not in default
+    hg email -b DEST          # send bundle of all patches not in DEST
+    hg email -b -r 3000       # bundle of all ancestors of 3000 not in default
+    hg email -b -r 3000 DEST  # bundle of all ancestors of 3000 not in DEST
+
+    Before using this command, you will need to enable email in your hgrc.
+    See the [email] section in hgrc(5) for details.
+    '''
+
     def prompt(prompt, default = None, rest = ': ', empty_ok = False):
         if default: prompt += ' [%s]' % default
         prompt += rest
@@ -166,40 +197,59 @@ def patchbomb(ui, repo, *revs, **opts):
         msg['X-Mercurial-Node'] = node
         return msg
 
+    def outgoing(dest, revs):
+        '''Return the revisions present locally but not in dest'''
+        dest = ui.expandpath(dest or 'default-push', dest or 'default')
+        revs = [repo.lookup(rev) for rev in revs]
+        other = hg.repository(ui, dest)
+        ui.status(_('comparing with %s\n') % dest)
+        o = repo.findoutgoing(other)
+        if not o:
+            ui.status(_("no changes found\n"))
+            return []
+        o = repo.changelog.nodesbetween(o, revs or None)[0]
+        return [str(repo.changelog.rev(r)) for r in o]
+
+    def getbundle(dest):
+        tmpdir = tempfile.mkdtemp(prefix='hg-email-bundle-')
+        tmpfn = os.path.join(tmpdir, 'bundle')
+        try:
+            commands.bundle(ui, repo, tmpfn, dest, **opts)
+            return open(tmpfn).read()
+        finally:
+            try:
+                os.unlink(tmpfn)
+            except:
+                pass
+            os.rmdir(tmpdir)
+
+    # option handling
+    commands.setremoteconfig(ui, opts)
+    if opts.get('outgoint') and opts.get('bundle'):
+        raise util.Abort(_("--outgoing mode always on with --bundle; do not re-specify --outgoing"))
+
+    if opts.get('outgoing') or opts.get('bundle'):
+        if len(revs) > 1:
+            raise util.Abort(_("too many destinations"))
+        dest = revs and revs[0] or None
+        revs = []
+
+    if opts.get('rev'):
+        if revs:
+            raise util.Abort(_('use only one form to specify the revision'))
+        revs = opts.get('rev')
+
+    if opts.get('outgoing'):
+        revs = outgoing(dest, opts.get('rev'))
+    if opts.get('bundle'):
+        opts['revs'] = revs
+
+    # start
     start_time = util.makedate()
 
     def genmsgid(id):
         return '<%s.%s@%s>' % (id[:20], int(start_time[0]), socket.getfqdn())
 
-    patches = []
-
-    class exportee:
-        def __init__(self, container):
-            self.lines = []
-            self.container = container
-            self.name = 'email'
-
-        def write(self, data):
-            self.lines.append(data)
-
-        def close(self):
-            self.container.append(''.join(self.lines).split('\n'))
-            self.lines = []
-
-    commands.export(ui, repo, *revs, **{'output': exportee(patches),
-                                        'switch_parent': False,
-                                        'text': None,
-                                        'git': opts.get('git')})
-
-    jumbo = []
-    msgs = []
-
-    ui.write(_('This patch series consists of %d patches.\n\n') % len(patches))
-
-    for p, i in zip(patches, xrange(len(patches))):
-        jumbo.extend(p)
-        msgs.append(makepatch(p, i + 1, len(patches)))
-
     sender = (opts['from'] or ui.config('email', 'from') or
               ui.config('patchbomb', 'from') or
               prompt('From', ui.username()))
@@ -209,6 +259,7 @@ def patchbomb(ui, repo, *revs, **opts):
                               ui.config('patchbomb', opt) or
                               prompt(prpt, default = default)).split(',')
         return [a.strip() for a in addrs if a.strip()]
+
     to = getaddrs('to', 'To')
     cc = getaddrs('cc', 'Cc', '')
 
@@ -216,38 +267,82 @@ def patchbomb(ui, repo, *revs, **opts):
                           ui.config('patchbomb', 'bcc') or '').split(',')
     bcc = [a.strip() for a in bcc if a.strip()]
 
-    if len(patches) > 1:
-        ui.write(_('\nWrite the introductory message for the patch series.\n\n'))
+    def getexportmsgs():
+        patches = []
 
-        tlen = len(str(len(patches)))
+        class exportee:
+            def __init__(self, container):
+                self.lines = []
+                self.container = container
+                self.name = 'email'
+
+            def write(self, data):
+                self.lines.append(data)
+
+            def close(self):
+                self.container.append(''.join(self.lines).split('\n'))
+                self.lines = []
 
-        subj = '[PATCH %0*d of %d] %s' % (
-            tlen, 0,
-            len(patches),
-            opts['subject'] or
-            prompt('Subject:', rest = ' [PATCH %0*d of %d] ' % (tlen, 0,
-                len(patches))))
+        commands.export(ui, repo, *revs, **{'output': exportee(patches),
+                                            'switch_parent': False,
+                                            'text': None,
+                                            'git': opts.get('git')})
+
+        jumbo = []
+        msgs = []
 
-        ui.write(_('Finish with ^D or a dot on a line by itself.\n\n'))
+        ui.write(_('This patch series consists of %d patches.\n\n') % len(patches))
 
-        body = []
+        for p, i in zip(patches, xrange(len(patches))):
+            jumbo.extend(p)
+            msgs.append(makepatch(p, i + 1, len(patches)))
+
+        if len(patches) > 1:
+            tlen = len(str(len(patches)))
 
-        while True:
-            try: l = raw_input()
-            except EOFError: break
-            if l == '.': break
-            body.append(l)
+            subj = '[PATCH %0*d of %d] %s' % (
+                tlen, 0,
+                len(patches),
+                opts['subject'] or
+                prompt('Subject:', rest = ' [PATCH %0*d of %d] ' % (tlen, 0,
+                    len(patches))))
+
+            body = ''
+            if opts['diffstat']:
+                d = cdiffstat(_('Final summary:\n'), jumbo)
+                if d: body = '\n' + d
+
+            ui.write(_('\nWrite the introductory message for the patch series.\n\n'))
+            body = ui.edit(body, sender)
+
+            msg = email.MIMEText.MIMEText(body)
+            msg['Subject'] = subj
+
+            msgs.insert(0, msg)
+        return msgs
 
-        if opts['diffstat']:
-            d = cdiffstat(_('Final summary:\n'), jumbo)
-            if d: body.append('\n' + d)
+    def getbundlemsgs(bundle):
+        subj = opts['subject'] or \
+                prompt('Subject:', default='A bundle for your repository')
+        ui.write(_('\nWrite the introductory message for the bundle.\n\n'))
+        body = ui.edit('', sender)
 
-        body = '\n'.join(body) + '\n'
+        msg = email.MIMEMultipart.MIMEMultipart()
+        if body:
+            msg.attach(email.MIMEText.MIMEText(body, 'plain'))
+        datapart = email.MIMEBase.MIMEBase('application', 'x-mercurial-bundle')
+        datapart.set_payload(bundle)
+        datapart.add_header('Content-Disposition', 'attachment',
+                            filename='bundle.hg')
+        email.Encoders.encode_base64(datapart)
+        msg.attach(datapart)
+        msg['Subject'] = subj
+        return [msg]
 
-        msg = email.MIMEText.MIMEText(body)
-        msg['Subject'] = subj
-
-        msgs.insert(0, msg)
+    if opts.get('bundle'):
+        msgs = getbundlemsgs(getbundle(dest))
+    else:
+        msgs = getexportmsgs()
 
     ui.write('\n')
 
@@ -310,7 +405,14 @@ cmdtable = {
       ('', 'plain', None, 'omit hg patch header'),
       ('n', 'test', None, 'print messages that would be sent'),
       ('m', 'mbox', '', 'write messages to mbox file instead of sending them'),
+      ('o', 'outgoing', None, _('send changes not found in the target repository')),
+      ('b', 'bundle', None, _('send changes not in target as a binary bundle')),
+      ('r', 'rev', [], _('a revision to send')),
       ('s', 'subject', '', 'subject of first message (intro or single patch)'),
-      ('t', 'to', [], 'email addresses of recipients')],
-     "hg email [OPTION]... [REV]...")
+      ('t', 'to', [], 'email addresses of recipients'),
+      ('', 'force', None, _('run even when remote repository is unrelated (with -b)')),
+      ('', 'base', [],
+          _('a base changeset to specify instead of a destination (with -b)'))]
+      + commands.remoteopts,
+     "hg email [OPTION]... [DEST]...")
     }
rename from contrib/purge/purge.py
rename to hgext/purge.py
--- a/contrib/purge/purge.py
+++ b/hgext/purge.py
@@ -3,6 +3,16 @@
 # This is a small extension for Mercurial (http://www.selenic.com/mercurial)
 # that removes files not known to mercurial
 #
+# This program was inspired by the "cvspurge" script contained in CVS utilities
+# (http://www.red-bean.com/cvsutils/).
+#
+# To enable the "purge" extension put these lines in your ~/.hgrc:
+#  [extensions]
+#  hgext.purge =
+#
+# For help on the usage of "hg purge" use:
+#  hg help purge
+#
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation; either version 2 of the License, or
@@ -18,118 +28,89 @@
 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 
 from mercurial import hg, util
+from mercurial.i18n import _
 import os
 
-def _(s):
-    return s
-
-class Purge(object):
-    def __init__(self, act=True, abort_on_err=False, eol='\n'):
-        self._repo = None
-        self._ui = None
-        self._hg_root = None
-        self._act = act
-        self._abort_on_err = abort_on_err
-        self._eol = eol
-
-    def purge(self, ui, repo, dirs=None):
-        self._repo = repo
-        self._ui = ui
-        self._hg_root = self._split_path(repo.root)
-
-        if not dirs:
-            dirs = [repo.root]
-
-        for path in dirs:
-            path = os.path.abspath(path)
-            for root, dirs, files in os.walk(path, topdown=False):
-                if '.hg' in self._split_path(root):
-                    # Skip files in the .hg directory.
-                    # Note that if the repository is in a directory
-                    # called .hg this command does not work.
-                    continue
-                for name in files:
-                    self._remove_file(os.path.join(root, name))
-                if not os.listdir(root):
-                    # Remove this directory if it is empty.
-                    self._remove_dir(root)
-
-        self._repo = None
-        self._ui = None
-        self._hg_root = None
-
-    def _error(self, msg):
-        if self._abort_on_err:
+def dopurge(ui, repo, dirs=None, act=True, abort_on_err=False, eol='\n',
+            force=False):
+    def error(msg):
+        if abort_on_err:
             raise util.Abort(msg)
         else:
-            self._ui.warn(_('warning: %s\n') % msg)
+            ui.warn(_('warning: %s\n') % msg)
 
-    def _remove_file(self, name):
-        relative_name = self._relative_name(name)
-        # dirstate.state() requires a path relative to the root
-        # directory.
-        if self._repo.dirstate.state(relative_name) != '?':
-            return
-        self._ui.note(_('Removing file %s\n') % name)
-        if self._act:
+    def remove(remove_func, name):
+        if act:
             try:
-                os.remove(name)
+                remove_func(os.path.join(repo.root, name))
             except OSError, e:
-                self._error(_('%s cannot be removed') % name)
+                error(_('%s cannot be removed') % name)
         else:
-            self._ui.write('%s%s' % (name, self._eol))
+            ui.write('%s%s' % (name, eol))
 
-    def _remove_dir(self, name):
-        self._ui.note(_('Removing directory %s\n') % name)
-        if self._act:
-            try:
-                os.rmdir(name)
-            except OSError, e:
-                self._error(_('%s cannot be removed') % name)
-        else:
-            self._ui.write('%s%s' % (name, self._eol))
+    directories = []
+    files = []
+    missing = []
+    roots, match, anypats = util.cmdmatcher(repo.root, repo.getcwd(), dirs)
+    for src, f, st in repo.dirstate.statwalk(files=roots, match=match,
+                                             ignored=True, directories=True):
+        if src == 'd':
+            directories.append(f)
+        elif src == 'm':
+            missing.append(f)
+        elif src == 'f' and f not in repo.dirstate:
+            files.append(f)
+
+    _check_missing(ui, repo, missing, force)
+
+    directories.sort()
+
+    for f in files:
+        if f not in repo.dirstate:
+            ui.note(_('Removing file %s\n') % f)
+            remove(os.remove, f)
 
-    def _relative_name(self, path):
-        '''
-        Returns "path" but relative to the root directory of the
-        repository and with '\\' replaced with '/'.
-        This is needed because this is the format required by
-        self._repo.dirstate.state().
-        '''
-        splitted_path = self._split_path(path)[len(self._hg_root):]
-        # Even on Windows self._repo.dirstate.state() wants '/'.
-        return self._join_path(splitted_path).replace('\\', '/')
+    for f in directories[::-1]:
+        if not os.listdir(repo.wjoin(f)):
+            ui.note(_('Removing directory %s\n') % f)
+            remove(os.rmdir, f)
+
+def _check_missing(ui, repo, missing, force=False):
+    """Abort if there is the chance of having problems with name-mangling fs
 
-    def _split_path(self, path):
-        '''
-        Returns a list of the single files/directories in "path".
-        For instance:
-          '/home/user/test' -> ['/', 'home', 'user', 'test']
-          'C:\\Mercurial'   -> ['C:\\', 'Mercurial']
-        '''
-        ret = []
-        while True:
-            head, tail = os.path.split(path)
-            if tail:
-                ret.append(tail)
-            if head == path:
-                ret.append(head)
-                break
-            path = head
-        ret.reverse()
-        return ret
+    In a name mangling filesystem (e.g. a case insensitive one)
+    dirstate.walk() can yield filenames different from the ones
+    stored in the dirstate. This already confuses the status and
+    add commands, but with purge this may cause data loss.
+    
+    To prevent this, _check_missing will abort if there are missing
+    files. The force option will let the user skip the check if he 
+    knows it is safe.
+    
+    Even with the force option this function will check if any of the 
+    missing files is still available in the working dir: if so there
+    may be some problem with the underlying filesystem, so it
+    aborts unconditionally."""
+
+    found = [f for f in missing if util.lexists(repo.wjoin(f))]
 
-    def _join_path(self, splitted_path):
-        '''
-        Joins a list returned by _split_path().
-        '''
-        ret = ''
-        for part in splitted_path:
-            if ret:
-                ret = os.path.join(ret, part)
-            else:
-                ret = part
-        return ret
+    if found:
+        if not ui.quiet:
+            ui.warn(_("The following tracked files weren't listed by the "
+                      "filesystem, but could still be found:\n"))
+            for f in found:
+                ui.warn("%s\n" % f)
+            if util.checkfolding(repo.path):
+                ui.warn(_("This is probably due to a case-insensitive "
+                          "filesystem\n"))
+        raise util.Abort(_("purging on name mangling filesystems is not "
+                           "yet fully supported"))
+
+    if missing and not force:
+        raise util.Abort(_("there are missing files in the working dir and "
+                           "purge still has problems with them due to name "
+                           "mangling filesystems. "
+                           "Use --force if you know what you are doing"))
 
 
 def purge(ui, repo, *dirs, **opts):
@@ -162,14 +143,15 @@ def purge(ui, repo, *dirs, **opts):
     if eol == '\0':
         # --print0 implies --print
         act = False
-    p = Purge(act, abort_on_err, eol)
-    p.purge(ui, repo, dirs)
+    force = bool(opts['force'])
+    dopurge(ui, repo, dirs, act, abort_on_err, eol, force)
 
 
 cmdtable = {
     'purge':
         (purge,
          [('a', 'abort-on-err', None, _('abort if an error occurs')),
+          ('f', 'force', None, _('purge even when missing files are detected')),
           ('p', 'print', None, _('print the file names instead of deleting them')),
           ('0', 'print0', None, _('end filenames with NUL, for use with xargs'
                                   ' (implies -p)'))],
--- a/hgext/transplant.py
+++ b/hgext/transplant.py
@@ -5,11 +5,10 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-from mercurial.demandload import *
-from mercurial.i18n import gettext as _
-demandload(globals(), 'os tempfile')
-demandload(globals(), 'mercurial:bundlerepo,cmdutil,commands,hg,merge,patch')
-demandload(globals(), 'mercurial:revlog,util')
+from mercurial.i18n import _
+import os, tempfile
+from mercurial import bundlerepo, changegroup, cmdutil, commands, hg, merge
+from mercurial import patch, revlog, util
 
 '''patch transplanting tool
 
@@ -120,7 +119,8 @@ class transplanter:
                     if pulls:
                         if source != repo:
                             repo.pull(source, heads=pulls, lock=lock)
-                        merge.update(repo, pulls[-1], wlock=wlock)
+                        merge.update(repo, pulls[-1], False, False, None,
+                                     wlock=wlock)
                         p1, p2 = repo.dirstate.parents()
                         pulls = []
 
@@ -151,10 +151,10 @@ class transplanter:
                                           log=opts.get('log'),
                                           filter=opts.get('filter'),
                                           lock=lock, wlock=wlock)
-                        if domerge:
+                        if n and domerge:
                             self.ui.status(_('%s merged at %s\n') % (revstr,
                                       revlog.short(n)))
-                        else:
+                        elif n:
                             self.ui.status(_('%s transplanted to %s\n') % (revlog.short(node),
                                                                        revlog.short(n)))
                     finally:
@@ -162,7 +162,7 @@ class transplanter:
                             os.unlink(patchfile)
             if pulls:
                 repo.pull(source, heads=pulls, lock=lock)
-                merge.update(repo, pulls[-1], wlock=wlock)
+                merge.update(repo, pulls[-1], False, False, None, wlock=wlock)
         finally:
             self.saveseries(revmap, merges)
             self.transplants.write()
@@ -217,7 +217,7 @@ class transplanter:
                                        files=files)
                     if not files:
                         self.ui.warn(_('%s: empty changeset') % revlog.hex(node))
-                        return
+                        return None
                 finally:
                     files = patch.updatedir(self.ui, repo, files, wlock=wlock)
             except Exception, inst:
@@ -473,7 +473,7 @@ def transplant(ui, repo, *revs, **opts):
         bundle = None
         if not source.local():
             cg = source.changegroup(incoming, 'incoming')
-            bundle = commands.write_bundle(cg, compress=False)
+            bundle = changegroup.writebundle(cg, None, 'HG10UN')
             source = bundlerepo.bundlerepository(ui, repo.root, bundle)
 
         return (source, incoming, bundle)
@@ -575,6 +575,7 @@ def transplant(ui, repo, *revs, **opts):
         tp.apply(repo, source, revmap, merges, opts)
     finally:
         if bundle:
+            source.close()
             os.unlink(bundle)
 
 cmdtable = {
@@ -588,5 +589,5 @@ cmdtable = {
           ('', 'log', None, _('append transplant info to log message')),
           ('c', 'continue', None, _('continue last transplant session after repair')),
           ('', 'filter', '', _('filter changesets through FILTER'))],
-         _('hg transplant [-s REPOSITORY] [-b BRANCH] [-p REV] [-m REV] [-n] REV...'))
+         _('hg transplant [-s REPOSITORY] [-b BRANCH [-a]] [-p REV] [-m REV] [REV]...'))
 }
deleted file mode 100644
--- a/mercurial/appendfile.py
+++ /dev/null
@@ -1,162 +0,0 @@
-# appendfile.py - special classes to make repo updates atomic
-#
-# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
-#
-# This software may be used and distributed according to the terms
-# of the GNU General Public License, incorporated herein by reference.
-
-from demandload import *
-demandload(globals(), "cStringIO changelog errno manifest os tempfile util")
-
-# writes to metadata files are ordered.  reads: changelog, manifest,
-# normal files.  writes: normal files, manifest, changelog.
-
-# manifest contains pointers to offsets in normal files.  changelog
-# contains pointers to offsets in manifest.  if reader reads old
-# changelog while manifest or normal files are written, it has no
-# pointers into new parts of those files that are maybe not consistent
-# yet, so will not read them.
-
-# localrepo.addchangegroup thinks it writes changelog first, then
-# manifest, then normal files (this is order they are available, and
-# needed for computing linkrev fields), but uses appendfile to hide
-# updates from readers.  data not written to manifest or changelog
-# until all normal files updated.  write manifest first, then
-# changelog.
-
-# with this write ordering, readers cannot see inconsistent view of
-# repo during update.
-
-class appendfile(object):
-    '''implement enough of file protocol to append to revlog file.
-    appended data is written to temp file.  reads and seeks span real
-    file and temp file.  readers cannot see appended data until
-    writedata called.'''
-
-    def __init__(self, fp, tmpname):
-        if tmpname:
-            self.tmpname = tmpname
-            self.tmpfp = util.posixfile(self.tmpname, 'ab+')
-        else:
-            fd, self.tmpname = tempfile.mkstemp(prefix="hg-appendfile-")
-            os.close(fd)
-            self.tmpfp = util.posixfile(self.tmpname, 'ab+')
-        self.realfp = fp
-        self.offset = fp.tell()
-        # real file is not written by anyone else. cache its size so
-        # seek and read can be fast.
-        self.realsize = util.fstat(fp).st_size
-        self.name = fp.name
-
-    def end(self):
-        self.tmpfp.flush() # make sure the stat is correct
-        return self.realsize + util.fstat(self.tmpfp).st_size
-
-    def tell(self):
-        return self.offset
-
-    def flush(self):
-        self.tmpfp.flush()
-
-    def close(self):
-        self.realfp.close()
-        self.tmpfp.close()
-
-    def seek(self, offset, whence=0):
-        '''virtual file offset spans real file and temp file.'''
-        if whence == 0:
-            self.offset = offset
-        elif whence == 1:
-            self.offset += offset
-        elif whence == 2:
-            self.offset = self.end() + offset
-
-        if self.offset < self.realsize:
-            self.realfp.seek(self.offset)
-        else:
-            self.tmpfp.seek(self.offset - self.realsize)
-
-    def read(self, count=-1):
-        '''only trick here is reads that span real file and temp file.'''
-        fp = cStringIO.StringIO()
-        old_offset = self.offset
-        if self.offset < self.realsize:
-            s = self.realfp.read(count)
-            fp.write(s)
-            self.offset += len(s)
-            if count > 0:
-                count -= len(s)
-        if count != 0:
-            if old_offset != self.offset:
-                self.tmpfp.seek(self.offset - self.realsize)
-            s = self.tmpfp.read(count)
-            fp.write(s)
-            self.offset += len(s)
-        return fp.getvalue()
-
-    def write(self, s):
-        '''append to temp file.'''
-        self.tmpfp.seek(0, 2)
-        self.tmpfp.write(s)
-        # all writes are appends, so offset must go to end of file.
-        self.offset = self.realsize + self.tmpfp.tell()
-
-class appendopener(object):
-    '''special opener for files that only read or append.'''
-
-    def __init__(self, opener):
-        self.realopener = opener
-        # key: file name, value: appendfile name
-        self.tmpnames = {}
-
-    def __call__(self, name, mode='r'):
-        '''open file.'''
-
-        assert mode in 'ra+'
-        try:
-            realfp = self.realopener(name, 'r')
-        except IOError, err:
-            if err.errno != errno.ENOENT: raise
-            realfp = self.realopener(name, 'w+')
-        tmpname = self.tmpnames.get(name)
-        fp = appendfile(realfp, tmpname)
-        if tmpname is None:
-            self.tmpnames[name] = fp.tmpname
-        return fp
-
-    def writedata(self):
-        '''copy data from temp files to real files.'''
-        # write .d file before .i file.
-        tmpnames = self.tmpnames.items()
-        tmpnames.sort()
-        for name, tmpname in tmpnames:
-            ifp = open(tmpname, 'rb')
-            ofp = self.realopener(name, 'a')
-            for chunk in util.filechunkiter(ifp):
-                ofp.write(chunk)
-            ifp.close()
-            os.unlink(tmpname)
-            del self.tmpnames[name]
-            ofp.close()
-
-    def cleanup(self):
-        '''delete temp files (this discards unwritten data!)'''
-        for tmpname in self.tmpnames.values():
-            os.unlink(tmpname)
-
-# files for changelog and manifest are in different appendopeners, so
-# not mixed up together.
-
-class appendchangelog(changelog.changelog, appendopener):
-    def __init__(self, opener, version):
-        appendopener.__init__(self, opener)
-        changelog.changelog.__init__(self, self, version)
-    def checkinlinesize(self, fp, tr):
-        return
-
-class appendmanifest(manifest.manifest, appendopener):
-    def __init__(self, opener, version):
-        appendopener.__init__(self, opener)
-        manifest.manifest.__init__(self, self, version)
-    def checkinlinesize(self, fp, tr):
-        return
--- a/mercurial/archival.py
+++ b/mercurial/archival.py
@@ -5,10 +5,9 @@
 # This software may be used and distributed according to the terms of
 # the GNU General Public License, incorporated herein by reference.
 
-from demandload import *
-from i18n import gettext as _
+from i18n import _
 from node import *
-demandload(globals(), 'cStringIO os stat tarfile time util zipfile')
+import cStringIO, os, stat, tarfile, time, util, zipfile
 
 def tidyprefix(dest, prefix, suffixes):
     '''choose prefix to use for names in archive.  make sure prefix is
@@ -156,15 +155,12 @@ def archive(repo, dest, node, kind, deco
     def write(name, mode, data):
         if matchfn and not matchfn(name): return
         if decode:
-            fp = cStringIO.StringIO()
-            repo.wwrite(name, data, fp)
-            data = fp.getvalue()
+            data = repo.wwritedata(name, data)
         archiver.addfile(name, mode, data)
 
-    change = repo.changelog.read(node)
-    mn = change[0]
-    archiver = archivers[kind](dest, prefix, mtime or change[2][0])
-    m = repo.manifest.read(mn)
+    ctx = repo.changectx(node)
+    archiver = archivers[kind](dest, prefix, mtime or ctx.date()[0])
+    m = ctx.manifest()
     items = m.items()
     items.sort()
     write('.hg_archival.txt', 0644,
--- a/mercurial/bdiff.c
+++ b/mercurial/bdiff.c
@@ -33,7 +33,11 @@ static uint32_t htonl(uint32_t x)
 }
 #else
 #include <sys/types.h>
+#ifdef __BEOS__
+#include <ByteOrder.h>
+#else
 #include <arpa/inet.h>
+#endif
 #include <inttypes.h>
 #endif
 
--- a/mercurial/bundlerepo.py
+++ b/mercurial/bundlerepo.py
@@ -11,14 +11,13 @@ of the GNU General Public License, incor
 """
 
 from node import *
-from i18n import gettext as _
-from demandload import demandload
-demandload(globals(), "changegroup util os struct bz2 tempfile")
+from i18n import _
+import changegroup, util, os, struct, bz2, tempfile
 
 import localrepo, changelog, manifest, filelog, revlog
 
 class bundlerevlog(revlog.revlog):
-    def __init__(self, opener, indexfile, datafile, bundlefile,
+    def __init__(self, opener, indexfile, bundlefile,
                  linkmapper=None):
         # How it works:
         # to retrieve a revision, we need to know the offset of
@@ -29,7 +28,7 @@ class bundlerevlog(revlog.revlog):
         # len(index[r]). If the tuple is bigger than 7, it is a bundle
         # (it is bigger since we store the node to which the delta is)
         #
-        revlog.revlog.__init__(self, opener, indexfile, datafile)
+        revlog.revlog.__init__(self, opener, indexfile)
         self.bundlefile = bundlefile
         self.basemap = {}
         def chunkpositer():
@@ -50,7 +49,7 @@ class bundlerevlog(revlog.revlog):
                 continue
             for p in (p1, p2):
                 if not p in self.nodemap:
-                    raise revlog.RevlogError(_("unknown parent %s") % short(p1))
+                    raise revlog.LookupError(_("unknown parent %s") % short(p1))
             if linkmapper is None:
                 link = n
             else:
@@ -141,20 +140,19 @@ class bundlerevlog(revlog.revlog):
 class bundlechangelog(bundlerevlog, changelog.changelog):
     def __init__(self, opener, bundlefile):
         changelog.changelog.__init__(self, opener)
-        bundlerevlog.__init__(self, opener, self.indexfile, self.datafile,
-                              bundlefile)
+        bundlerevlog.__init__(self, opener, self.indexfile, bundlefile)
 
 class bundlemanifest(bundlerevlog, manifest.manifest):
     def __init__(self, opener, bundlefile, linkmapper):
         manifest.manifest.__init__(self, opener)
-        bundlerevlog.__init__(self, opener, self.indexfile, self.datafile,
-                              bundlefile, linkmapper)
+        bundlerevlog.__init__(self, opener, self.indexfile, bundlefile,
+                              linkmapper)
 
 class bundlefilelog(bundlerevlog, filelog.filelog):
     def __init__(self, opener, path, bundlefile, linkmapper):
         filelog.filelog.__init__(self, opener, path)
-        bundlerevlog.__init__(self, opener, self.indexfile, self.datafile,
-                              bundlefile, linkmapper)
+        bundlerevlog.__init__(self, opener, self.indexfile, bundlefile,
+                              linkmapper)
 
 class bundlerepository(localrepo.localrepository):
     def __init__(self, ui, path, bundlename):
--- a/mercurial/changegroup.py
+++ b/mercurial/changegroup.py
@@ -6,9 +6,9 @@ changegroup.py - Mercurial changegroup m
 This software may be used and distributed according to the terms
 of the GNU General Public License, incorporated herein by reference.
 """
-from i18n import gettext as _
-from demandload import *
-demandload(globals(), "struct os bz2 zlib util tempfile")
+
+from i18n import _
+import struct, os, bz2, zlib, util, tempfile
 
 def getchunk(source):
     """get a chunk from a changegroup"""
@@ -67,8 +67,6 @@ def writebundle(cg, filename, bundletype
     cleanup = None
     try:
         if filename:
-            if os.path.exists(filename):
-                raise util.Abort(_("file '%s' already exists") % filename)
             fh = open(filename, "wb")
         else:
             fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
--- a/mercurial/changelog.py
+++ b/mercurial/changelog.py
@@ -6,9 +6,8 @@
 # of the GNU General Public License, incorporated herein by reference.
 
 from revlog import *
-from i18n import gettext as _
-from demandload import demandload
-demandload(globals(), "os time util")
+from i18n import _
+import os, time, util
 
 def _string_escape(text):
     """
@@ -27,10 +26,100 @@ def _string_escape(text):
 def _string_unescape(text):
     return text.decode('string_escape')
 
+class appender:
+    '''the changelog index must be update last on disk, so we use this class
+    to delay writes to it'''
+    def __init__(self, fp, buf):
+        self.data = buf
+        self.fp = fp
+        self.offset = fp.tell()
+        self.size = util.fstat(fp).st_size
+
+    def end(self):
+        return self.size + len("".join(self.data))
+    def tell(self):
+        return self.offset
+    def flush(self):
+        pass
+    def close(self):
+        close(self.fp)
+
+    def seek(self, offset, whence=0):
+        '''virtual file offset spans real file and data'''
+        if whence == 0:
+            self.offset = offset
+        elif whence == 1:
+            self.offset += offset
+        elif whence == 2:
+            self.offset = self.end() + offset
+        if self.offset < self.size:
+            self.fp.seek(self.offset)
+
+    def read(self, count=-1):
+        '''only trick here is reads that span real file and data'''
+        ret = ""
+        old_offset = self.offset
+        if self.offset < self.size:
+            s = self.fp.read(count)
+            ret = s
+            self.offset += len(s)
+            if count > 0:
+                count -= len(s)
+        if count != 0:
+            doff = self.offset - self.size
+            self.data.insert(0, "".join(self.data))
+            del self.data[1:]
+            s = self.data[0][doff:doff+count]
+            self.offset += len(s)
+            ret += s
+        return ret
+
+    def write(self, s):
+        self.data.append(s)
+        self.offset += len(s)
+
 class changelog(revlog):
-    def __init__(self, opener, defversion=REVLOGV0):
-        revlog.__init__(self, opener, "00changelog.i", "00changelog.d",
-                        defversion)
+    def __init__(self, opener):
+        revlog.__init__(self, opener, "00changelog.i")
+
+    def delayupdate(self):
+        "delay visibility of index updates to other readers"
+        self._realopener = self.opener
+        self.opener = self._delayopener
+        self._delaycount = self.count()
+        self._delaybuf = []
+        self._delayname = None
+
+    def finalize(self, tr):
+        "finalize index updates"
+        self.opener = self._realopener
+        # move redirected index data back into place
+        if self._delayname:
+            util.rename(self._delayname + ".a", self._delayname)
+        elif self._delaybuf:
+            fp = self.opener(self.indexfile, 'a')
+            fp.write("".join(self._delaybuf))
+            fp.close()
+            del self._delaybuf
+        # split when we're done
+        self.checkinlinesize(tr)
+
+    def _delayopener(self, name, mode='r'):
+        fp = self._realopener(name, mode)
+        # only divert the index
+        if not name == self.indexfile:
+            return fp
+        # if we're doing an initial clone, divert to another file
+        if self._delaycount == 0:
+            self._delayname = fp.name
+            return self._realopener(name + ".a", mode)
+        # otherwise, divert to memory
+        return appender(fp, self._delaybuf)
+
+    def checkinlinesize(self, tr, fp=None):
+        if self.opener == self._delayopener:
+            return
+        return revlog.checkinlinesize(self, tr, fp)
 
     def decode_extra(self, text):
         extra = {}
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -5,11 +5,9 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-from demandload import demandload
 from node import *
-from i18n import gettext as _
-demandload(globals(), 'os sys')
-demandload(globals(), 'mdiff util templater patch')
+from i18n import _
+import os, sys, mdiff, bdiff, util, templater, patch
 
 revrangesep = ':'
 
@@ -127,42 +125,45 @@ def make_file(repo, pat, node=None,
                               pathname),
                 mode)
 
-def matchpats(repo, pats=[], opts={}, head='', globbed=False):
+def matchpats(repo, pats=[], opts={}, globbed=False, default=None):
     cwd = repo.getcwd()
-    if not pats and cwd:
-        opts['include'] = [os.path.join(cwd, i)
-                           for i in opts.get('include', [])]
-        opts['exclude'] = [os.path.join(cwd, x)
-                           for x in opts.get('exclude', [])]
-        cwd = ''
-    return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'),
-                           opts.get('exclude'), head, globbed=globbed)
+    return util.cmdmatcher(repo.root, cwd, pats or [], opts.get('include'),
+                           opts.get('exclude'), globbed=globbed,
+                           default=default)
 
-def walk(repo, pats=[], opts={}, node=None, head='', badmatch=None,
-         globbed=False):
-    files, matchfn, anypats = matchpats(repo, pats, opts, head,
-                                        globbed=globbed)
+def walk(repo, pats=[], opts={}, node=None, badmatch=None, globbed=False,
+         default=None):
+    files, matchfn, anypats = matchpats(repo, pats, opts, globbed=globbed,
+                                        default=default)
     exact = dict.fromkeys(files)
     for src, fn in repo.walk(node=node, files=files, match=matchfn,
                              badmatch=badmatch):
         yield src, fn, util.pathto(repo.root, repo.getcwd(), fn), fn in exact
 
 def findrenames(repo, added=None, removed=None, threshold=0.5):
+    '''find renamed files -- yields (before, after, score) tuples'''
     if added is None or removed is None:
         added, removed = repo.status()[1:3]
-    changes = repo.changelog.read(repo.dirstate.parents()[0])
-    mf = repo.manifest.read(changes[0])
+    ctx = repo.changectx()
     for a in added:
         aa = repo.wread(a)
-        bestscore, bestname = None, None
+        bestname, bestscore = None, threshold
         for r in removed:
-            rr = repo.file(r).read(mf[r])
-            delta = mdiff.textdiff(aa, rr)
-            if len(delta) < len(aa):
-                myscore = 1.0 - (float(len(delta)) / len(aa))
-                if bestscore is None or myscore > bestscore:
-                    bestscore, bestname = myscore, r
-        if bestname and bestscore >= threshold:
+            rr = ctx.filectx(r).data()
+
+            # bdiff.blocks() returns blocks of matching lines
+            # count the number of bytes in each
+            equal = 0
+            alines = mdiff.splitnewlines(aa)
+            matches = bdiff.blocks(aa, rr)
+            for x1,x2,y1,y2 in matches:
+                for line in alines[x1:x2]:
+                    equal += len(line)
+
+            myscore = equal*2.0 / (len(aa)+len(rr))
+            if myscore >= bestscore:
+                bestname, bestscore = r, myscore
+        if bestname:
             yield bestname, a, bestscore
 
 def addremove(repo, pats=[], opts={}, wlock=None, dry_run=None,
@@ -179,7 +180,8 @@ def addremove(repo, pats=[], opts={}, wl
             mapping[abs] = rel, exact
             if repo.ui.verbose or not exact:
                 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
-        if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
+        islink = os.path.islink(rel)
+        if repo.dirstate.state(abs) != 'r' and not islink and not os.path.exists(rel):
             remove.append(abs)
             mapping[abs] = rel, exact
             if repo.ui.verbose or not exact:
@@ -198,15 +200,58 @@ def addremove(repo, pats=[], opts={}, wl
             if not dry_run:
                 repo.copy(old, new, wlock=wlock)
 
+def service(opts, parentfn=None, initfn=None, runfn=None):
+    '''Run a command as a service.'''
+
+    if opts['daemon'] and not opts['daemon_pipefds']:
+        rfd, wfd = os.pipe()
+        args = sys.argv[:]
+        args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
+        pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
+                         args[0], args)
+        os.close(wfd)
+        os.read(rfd, 1)
+        if parentfn:
+            return parentfn(pid)
+        else:
+            os._exit(0)
+
+    if initfn:
+        initfn()
+
+    if opts['pid_file']:
+        fp = open(opts['pid_file'], 'w')
+        fp.write(str(os.getpid()) + '\n')
+        fp.close()
+
+    if opts['daemon_pipefds']:
+        rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
+        os.close(rfd)
+        try:
+            os.setsid()
+        except AttributeError:
+            pass
+        os.write(wfd, 'y')
+        os.close(wfd)
+        sys.stdout.flush()
+        sys.stderr.flush()
+        fd = os.open(util.nulldev, os.O_RDWR)
+        if fd != 0: os.dup2(fd, 0)
+        if fd != 1: os.dup2(fd, 1)
+        if fd != 2: os.dup2(fd, 2)
+        if fd not in (0, 1, 2): os.close(fd)
+
+    if runfn:
+        return runfn()
+
 class changeset_printer(object):
     '''show changeset information when templating not requested.'''
 
-    def __init__(self, ui, repo, patch, brinfo, buffered):
+    def __init__(self, ui, repo, patch, buffered):
         self.ui = ui
         self.repo = repo
         self.buffered = buffered
         self.patch = patch
-        self.brinfo = brinfo
         self.header = {}
         self.hunk = {}
         self.lastheader = None
@@ -271,11 +316,6 @@ class changeset_printer(object):
         for parent in parents:
             self.ui.write(_("parent:      %d:%s\n") % parent)
 
-        if self.brinfo:
-            br = self.repo.branchlookup([changenode])
-            if br:
-                self.ui.write(_("branch:      %s\n") % " ".join(br[changenode]))
-
         if self.ui.debugflag:
             self.ui.write(_("manifest:    %d:%s\n") %
                           (self.repo.manifest.rev(changes[0]), hex(changes[0])))
@@ -323,8 +363,8 @@ class changeset_printer(object):
 class changeset_templater(changeset_printer):
     '''format changeset information.'''
 
-    def __init__(self, ui, repo, patch, brinfo, mapfile, buffered):
-        changeset_printer.__init__(self, ui, repo, patch, brinfo, buffered)
+    def __init__(self, ui, repo, patch, mapfile, buffered):
+        changeset_printer.__init__(self, ui, repo, patch, buffered)
         filters = templater.common_filters.copy()
         filters['formatnode'] = (ui.debugflag and (lambda x: x)
                                  or (lambda x: x[:12]))
@@ -414,12 +454,6 @@ class changeset_templater(changeset_prin
             if branch != 'default':
                 branch = util.tolocal(branch)
                 return showlist('branch', [branch], plural='branches', **args)
-            # add old style branches if requested
-            if self.brinfo:
-                br = self.repo.branchlookup([changenode])
-                if changenode in br:
-                    return showlist('branch', br[changenode],
-                                    plural='branches', **args)
 
         def showparents(**args):
             parents = [[('rev', log.rev(p)), ('node', hex(p))]
@@ -533,11 +567,6 @@ def show_changeset(ui, repo, opts, buffe
     if opts.get('patch'):
         patch = matchfn or util.always
 
-    br = None
-    if opts.get('branches'):
-        ui.warn(_("the --branches option is deprecated, "
-                  "please use 'hg branches' instead\n"))
-        br = True
     tmpl = opts.get('template')
     mapfile = None
     if tmpl:
@@ -559,12 +588,12 @@ def show_changeset(ui, repo, opts, buffe
                            or templater.templatepath(mapfile))
                 if mapname: mapfile = mapname
         try:
-            t = changeset_templater(ui, repo, patch, br, mapfile, buffered)
+            t = changeset_templater(ui, repo, patch, mapfile, buffered)
         except SyntaxError, inst:
             raise util.Abort(inst.args[0])
         if tmpl: t.use_template(tmpl)
         return t
-    return changeset_printer(ui, repo, patch, br, buffered)
+    return changeset_printer(ui, repo, patch, buffered)
 
 def finddate(ui, repo, date):
     """Find the tipmost changeset that matches the given date spec"""
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -5,14 +5,14 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-from demandload import demandload
+import demandimport; demandimport.enable()
 from node import *
-from i18n import gettext as _
-demandload(globals(), "bisect os re sys signal imp urllib pdb shlex stat")
-demandload(globals(), "fancyopts ui hg util lock revlog bundlerepo")
-demandload(globals(), "difflib patch time help mdiff tempfile")
-demandload(globals(), "traceback errno version atexit socket")
-demandload(globals(), "archival changegroup cmdutil hgweb.server sshserver")
+from i18n import _
+import bisect, os, re, sys, signal, imp, urllib, pdb, shlex, stat
+import fancyopts, ui, hg, util, lock, revlog, bundlerepo
+import difflib, patch, time, help, mdiff, tempfile
+import traceback, errno, version, atexit, socket
+import archival, changegroup, cmdutil, hgweb.server, sshserver
 
 class UnknownCommand(Exception):
     """Exception raised if command is not in the command table."""
@@ -240,22 +240,28 @@ def backout(ui, repo, rev, **opts):
     if op1 != node:
         if opts['merge']:
             ui.status(_('merging with changeset %s\n') % nice(op1))
-            n = _lookup(repo, hex(op1))
-            hg.merge(repo, n)
+            hg.merge(repo, hex(op1))
         else:
             ui.status(_('the backout changeset is a new head - '
                         'do not forget to merge\n'))
             ui.status(_('(use "backout --merge" '
                         'if you want to auto-merge)\n'))
 
-def branch(ui, repo, label=None):
+def branch(ui, repo, label=None, **opts):
     """set or show the current branch name
 
     With <name>, set the current branch name. Otherwise, show the
     current branch name.
+
+    Unless --force is specified, branch will not let you set a
+    branch name that shadows an existing branch.
     """
 
     if label:
+        if not opts.get('force') and label in repo.branchtags():
+            if label not in [p.branch() for p in repo.workingctx().parents()]:
+                raise util.Abort(_('a branch of the same name already exists'
+                                   ' (use --force to override)'))
         repo.dirstate.setbranch(util.fromlocal(label))
     else:
         ui.write("%s\n" % util.tolocal(repo.dirstate.branch()))
@@ -424,6 +430,8 @@ def commit(ui, repo, *pats, **opts):
         files = modified + added + removed
         slist = None
         for f in fns:
+            if f == '.':
+                continue
             if f not in files:
                 rf = repo.wjoin(f)
                 if f in unknown:
@@ -495,7 +503,7 @@ def docopy(ui, repo, pats, opts, wlock):
                      util.localpath(prevsrc)))
             return
         if (not opts['after'] and os.path.exists(reltarget) or
-            opts['after'] and repo.dirstate.state(abstarget) not in '?r'):
+            opts['after'] and repo.dirstate.state(abstarget) not in '?ar'):
             if not opts['force']:
                 ui.warn(_('%s: not overwriting - file exists\n') %
                         reltarget)
@@ -519,7 +527,7 @@ def docopy(ui, repo, pats, opts, wlock):
                     restore = False
                 finally:
                     if restore:
-                        repo.remove([abstarget], wlock)
+                        repo.remove([abstarget], wlock=wlock)
             except IOError, inst:
                 if inst.errno == errno.ENOENT:
                     ui.warn(_('%s: deleted in working copy\n') % relsrc)
@@ -658,7 +666,7 @@ def copy(ui, repo, *pats, **opts):
 
 def debugancestor(ui, index, rev1, rev2):
     """find the ancestor revision of two revisions in a given index"""
-    r = revlog.revlog(util.opener(os.getcwd(), audit=False), index, "", 0)
+    r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
     a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
     ui.write("%d:%s\n" % (r.rev(a), hex(a)))
 
@@ -683,15 +691,12 @@ def debugcomplete(ui, cmd='', **opts):
     clist.sort()
     ui.write("%s\n" % "\n".join(clist))
 
-def debugrebuildstate(ui, repo, rev=None):
+def debugrebuildstate(ui, repo, rev=""):
     """rebuild the dirstate as it would look like for the given revision"""
-    if not rev:
+    if rev == "":
         rev = repo.changelog.tip()
-    else:
-        rev = repo.lookup(rev)
-    change = repo.changelog.read(rev)
-    n = change[0]
-    files = repo.manifest.read(n)
+    ctx = repo.changectx(rev)
+    files = ctx.manifest()
     wlock = repo.wlock()
     repo.dirstate.rebuild(rev, files)
 
@@ -702,10 +707,8 @@ def debugcheckstate(ui, repo):
     dc = repo.dirstate.map
     keys = dc.keys()
     keys.sort()
-    m1n = repo.changelog.read(parent1)[0]
-    m2n = repo.changelog.read(parent2)[0]
-    m1 = repo.manifest.read(m1n)
-    m2 = repo.manifest.read(m2n)
+    m1 = repo.changectx(parent1).manifest()
+    m2 = repo.changectx(parent2).manifest()
     errors = 0
     for f in dc:
         state = repo.dirstate.state(f)
@@ -791,9 +794,8 @@ def debugstate(ui, repo):
         ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
 
 def debugdata(ui, file_, rev):
-    """dump the contents of an data file revision"""
-    r = revlog.revlog(util.opener(os.getcwd(), audit=False),
-                      file_[:-2] + ".i", file_, 0)
+    """dump the contents of a data file revision"""
+    r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
     try:
         ui.write(r.revision(r.lookup(rev)))
     except KeyError:
@@ -813,7 +815,7 @@ def debugdate(ui, date, range=None, **op
 
 def debugindex(ui, file_):
     """dump the contents of an index file"""
-    r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0)
+    r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
     ui.write("   rev    offset  length   base linkrev" +
              " nodeid       p1           p2\n")
     for i in xrange(r.count()):
@@ -825,7 +827,7 @@ def debugindex(ui, file_):
 
 def debugindexdot(ui, file_):
     """dump an index DAG as a .dot file"""
-    r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0)
+    r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
     ui.write("digraph G {\n")
     for i in xrange(r.count()):
         node = r.node(i)
@@ -879,8 +881,10 @@ def debuginstall(ui):
     # patch
     ui.status(_("Checking patch...\n"))
     path = os.environ.get('PATH', '')
-    patcher = util.find_in_path('gpatch', path,
-                                util.find_in_path('patch', path, None))
+    patcher = ui.config('ui', 'patch')
+    if not patcher:
+        patcher = util.find_in_path('gpatch', path,
+                                    util.find_in_path('patch', path, None))
     if not patcher:
         ui.write(_(" Can't find patch or gpatch in PATH\n"))
         ui.write(_(" (specify a patch utility in your .hgrc file)\n"))
@@ -889,27 +893,28 @@ def debuginstall(ui):
         # actually attempt a patch here
         a = "1\n2\n3\n4\n"
         b = "1\n2\n3\ninsert\n4\n"
-        d = mdiff.unidiff(a, None, b, None, "a")
         fa = writetemp(a)
+        d = mdiff.unidiff(a, None, b, None, os.path.basename(fa))
         fd = writetemp(d)
-        fp = os.popen('%s %s %s' % (patcher, fa, fd))
-        files = []
-        output = ""
-        for line in fp:
-            output += line
-            if line.startswith('patching file '):
-                pf = util.parse_patch_output(line.rstrip())
-                files.append(pf)
-        if files != [fa]:
-            ui.write(_(" unexpected patch output!"))
-            ui.write(_(" (you may have an incompatible version of patch)\n"))
-            ui.write(output)
+        
+        files = {}
+        try:
+            patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
+        except util.Abort, e:
+            ui.write(_(" patch call failed:\n"))
+            ui.write(" " + str(e) + "\n")
             problems += 1
-        a = file(fa).read()
-        if a != b:
-            ui.write(_(" patch test failed!"))
-            ui.write(_(" (you may have an incompatible version of patch)\n"))
-            problems += 1
+        else:            
+            if list(files) != [os.path.basename(fa)]:
+                ui.write(_(" unexpected patch output!"))
+                ui.write(_(" (you may have an incompatible version of patch)\n"))
+                problems += 1
+            a = file(fa).read()
+            if a != b:
+                ui.write(_(" patch test failed!"))
+                ui.write(_(" (you may have an incompatible version of patch)\n"))
+                problems += 1
+                
         os.unlink(fa)
         os.unlink(fd)
 
@@ -1158,13 +1163,14 @@ def grep(ui, repo, pattern, *pats, **opt
 
     prev = {}
     def display(fn, rev, states, prevstates):
-        counts = {'-': 0, '+': 0}
+        found = False
         filerevmatches = {}
-        if incrementing or not opts['all']:
-            a, b, r = prevstates, states, rev
+        r = prev.get(fn, -1)
+        if opts['all']:
+            iter = difflinestates(states, prevstates)
         else:
-            a, b, r = states, prevstates, prev.get(fn, -1)
-        for change, l in difflinestates(a, b):
+            iter = [('', l) for l in prevstates]
+        for change, l in iter:
             cols = [fn, str(r)]
             if opts['line_number']:
                 cols.append(str(l.linenum))
@@ -1180,19 +1186,17 @@ def grep(ui, repo, pattern, *pats, **opt
             else:
                 cols.append(l.line)
             ui.write(sep.join(cols), eol)
-            counts[change] += 1
-        return counts['+'], counts['-']
+            found = True
+        return found
 
     fstate = {}
     skip = {}
     get = util.cachefunc(lambda r: repo.changectx(r).changeset())
     changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
-    count = 0
-    incrementing = False
+    found = False
     follow = opts.get('follow')
     for st, rev, fns in changeiter:
         if st == 'window':
-            incrementing = rev
             matches.clear()
         elif st == 'add':
             mf = repo.changectx(rev).manifest()
@@ -1218,10 +1222,10 @@ def grep(ui, repo, pattern, *pats, **opt
                     if copy:
                         skip[copy] = True
                     continue
-                if incrementing or not opts['all'] or fstate[fn]:
-                    pos, neg = display(fn, rev, m, fstate[fn])
-                    count += pos + neg
-                    if pos and not opts['all']:
+                if fn in prev or fstate[fn]:
+                    r = display(fn, rev, m, fstate[fn])
+                    found = found or r
+                    if r and not opts['all']:
                         skip[fn] = True
                         if copy:
                             skip[copy] = True
@@ -1230,15 +1234,14 @@ def grep(ui, repo, pattern, *pats, **opt
                     fstate[copy] = m
                 prev[fn] = rev
 
-    if not incrementing:
-        fstate = fstate.items()
-        fstate.sort()
-        for fn, state in fstate:
-            if fn in skip:
-                continue
-            if fn not in copies.get(prev[fn], {}):
-                display(fn, rev, {}, state)
-    return (count == 0 and 1) or 0
+    fstate = fstate.items()
+    fstate.sort()
+    for fn, state in fstate:
+        if fn in skip:
+            continue
+        if fn not in copies.get(prev[fn], {}):
+            found = display(fn, rev, {}, state) or found
+    return (not found and 1) or 0
 
 def heads(ui, repo, **opts):
     """show current repository heads
@@ -1485,15 +1488,21 @@ def import_(ui, repo, patch1, *patches, 
     text/plain body parts before first diff are added to commit
     message.
 
-    If imported patch was generated by hg export, user and description
+    If the imported patch was generated by hg export, user and description
     from patch override values from message headers and body.  Values
     given on command line with -m and -u override these.
 
+    If --exact is specified, import will set the working directory
+    to the parent of each patch before applying it, and will abort
+    if the resulting changeset has a different ID than the one
+    recorded in the patch. This may happen due to character set
+    problems or other deficiencies in the text patch format.
+
     To read a patch from standard input, use patch name "-".
     """
     patches = (patch1,) + patches
 
-    if not opts['force']:
+    if opts.get('exact') or not opts['force']:
         bail_if_changed(repo)
 
     d = opts["base"]
@@ -1507,10 +1516,10 @@ def import_(ui, repo, patch1, *patches, 
 
         if pf == '-':
             ui.status(_("applying patch from stdin\n"))
-            tmpname, message, user, date = patch.extract(ui, sys.stdin)
+            tmpname, message, user, date, branch, nodeid, p1, p2 = patch.extract(ui, sys.stdin)
         else:
             ui.status(_("applying %s\n") % p)
-            tmpname, message, user, date = patch.extract(ui, file(pf))
+            tmpname, message, user, date, branch, nodeid, p1, p2 = patch.extract(ui, file(pf))
 
         if tmpname is None:
             raise util.Abort(_('no diffs found'))
@@ -1528,13 +1537,37 @@ def import_(ui, repo, patch1, *patches, 
                 message = None
             ui.debug(_('message:\n%s\n') % message)
 
+            wp = repo.workingctx().parents()
+            if opts.get('exact'):
+                if not nodeid or not p1:
+                    raise util.Abort(_('not a mercurial patch'))
+                p1 = repo.lookup(p1)
+                p2 = repo.lookup(p2 or hex(nullid))
+
+                if p1 != wp[0].node():
+                    hg.clean(repo, p1, wlock=wlock)
+                repo.dirstate.setparents(p1, p2)
+                repo.dirstate.setbranch(branch or 'default')
+            elif p2:
+                try:
+                    p1 = repo.lookup(p1)
+                    p2 = repo.lookup(p2)
+                    if p1 == wp[0].node():
+                        repo.dirstate.setparents(p1, p2)
+                except hg.RepoError:
+                    pass
+
             files = {}
             try:
                 fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
                                    files=files)
             finally:
                 files = patch.updatedir(ui, repo, files, wlock=wlock)
-            repo.commit(files, message, user, date, wlock=wlock, lock=lock)
+            n = repo.commit(files, message, user, date, wlock=wlock, lock=lock)
+            if opts.get('exact'):
+                if hex(n) != nodeid:
+                    repo.rollback(wlock=wlock, lock=lock)
+                    raise util.Abort(_('patch is damaged or loses information'))
         finally:
             os.unlink(tmpname)
 
@@ -1554,10 +1587,15 @@ def incoming(ui, repo, source="default",
     setremoteconfig(ui, opts)
 
     other = hg.repository(ui, source)
+    ui.status(_('comparing with %s\n') % source)
     incoming = repo.findincoming(other, force=opts["force"])
     if not incoming:
+        try:
+            os.unlink(opts["bundle"])
+        except:
+            pass
         ui.status(_("no changes found\n"))
-        return
+        return 1
 
     cleanup = None
     try:
@@ -1613,9 +1651,8 @@ def locate(ui, repo, *pats, **opts):
     Print all files under Mercurial control whose names match the
     given patterns.
 
-    This command searches the current directory and its
-    subdirectories.  To search an entire repository, move to the root
-    of the repository.
+    This command searches the entire repository by default.  To search
+    just the current directory and its subdirectories, use "--include .".
 
     If no patterns are given to match, this command prints all file
     names.
@@ -1632,14 +1669,21 @@ def locate(ui, repo, *pats, **opts):
     else:
         node = None
 
+    ret = 1
     for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
-                                             head='(?:.*/|)'):
+                                             badmatch=util.always,
+                                             default='relglob'):
+        if src == 'b':
+            continue
         if not node and repo.dirstate.state(abs) == '?':
             continue
         if opts['fullpath']:
             ui.write(os.path.join(repo.root, abs), end)
         else:
             ui.write(((pats and rel) or abs), end)
+        ret = 0
+
+    return ret
 
 def log(ui, repo, *pats, **opts):
     """show revision history of entire repository or files
@@ -1722,7 +1766,6 @@ def log(ui, repo, *pats, **opts):
     if opts["date"]:
         df = util.matchdate(opts["date"])
 
-
     displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
     for st, rev, fns in changeiter:
         if st == 'add':
@@ -1789,7 +1832,7 @@ def manifest(ui, repo, rev=None):
             ui.write("%3s " % (m.execf(f) and "755" or "644"))
         ui.write("%s\n" % f)
 
-def merge(ui, repo, node=None, force=None, branch=None):
+def merge(ui, repo, node=None, force=None):
     """merge working directory with another revision
 
     Merge the contents of the current working directory and the
@@ -1803,9 +1846,7 @@ def merge(ui, repo, node=None, force=Non
     revision to merge with must be provided.
     """
 
-    if node or branch:
-        node = _lookup(repo, node, branch)
-    else:
+    if not node:
         heads = repo.heads()
         if len(heads) > 2:
             raise util.Abort(_('repo has %d heads - '
@@ -1837,10 +1878,11 @@ def outgoing(ui, repo, dest=None, **opts
         revs = [repo.lookup(rev) for rev in opts['rev']]
 
     other = hg.repository(ui, dest)
+    ui.status(_('comparing with %s\n') % dest)
     o = repo.findoutgoing(other, force=opts['force'])
     if not o:
         ui.status(_("no changes found\n"))
-        return
+        return 1
     o = repo.changelog.nodesbetween(o, revs)[0]
     if opts['newest_first']:
         o.reverse()
@@ -2043,7 +2085,9 @@ def remove(ui, repo, *pats, **opts):
     This only removes files from the current branch, not from the
     entire project history.  If the files still exist in the working
     directory, they will be deleted from it.  If invoked with --after,
-    files that have been manually deleted are marked as removed.
+    files are marked as removed, but not actually unlinked unless --force
+    is also given. Without exact file names, --after will only mark
+    files as removed if they are no longer in the working directory.
 
     This command schedules the files to be removed at the next commit.
     To undo a remove before that, see hg revert.
@@ -2061,9 +2105,7 @@ def remove(ui, repo, *pats, **opts):
     remove, forget = [], []
     for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
         reason = None
-        if abs not in deleted and opts['after']:
-            reason = _('is still present')
-        elif abs in modified and not opts['force']:
+        if abs in modified and not opts['force']:
             reason = _('is modified (use -f to force removal)')
         elif abs in added:
             if opts['force']:
@@ -2072,6 +2114,8 @@ def remove(ui, repo, *pats, **opts):
             reason = _('has been marked for add (use -f to force removal)')
         elif abs in unknown:
             reason = _('is not managed')
+        elif opts['after'] and not exact and abs not in deleted:
+            continue
         elif abs in removed:
             continue
         if reason:
@@ -2082,7 +2126,7 @@ def remove(ui, repo, *pats, **opts):
                 ui.status(_('removing %s\n') % rel)
             remove.append(abs)
     repo.forget(forget)
-    repo.remove(remove, unlink=not opts['after'])
+    repo.remove(remove, unlink=opts['force'] or not opts['after'])
 
 def rename(ui, repo, *pats, **opts):
     """rename files; equivalent of copy + remove
@@ -2106,7 +2150,7 @@ def rename(ui, repo, *pats, **opts):
             ui.status(_('removing %s\n') % rel)
         names.append(abs)
     if not opts.get('dry_run'):
-        repo.remove(names, True, wlock)
+        repo.remove(names, True, wlock=wlock)
     return errs
 
 def revert(ui, repo, *pats, **opts):
@@ -2153,8 +2197,9 @@ def revert(ui, repo, *pats, **opts):
     if not opts['rev'] and p2 != nullid:
         raise util.Abort(_('uncommitted merge - please provide a '
                            'specific revision'))
-    node = repo.changectx(opts['rev']).node()
-    mf = repo.manifest.read(repo.changelog.read(node)[0])
+    ctx = repo.changectx(opts['rev'])
+    node = ctx.node()
+    mf = ctx.manifest()
     if node == parent:
         pmf = mf
     else:
@@ -2228,7 +2273,8 @@ def revert(ui, repo, *pats, **opts):
         def handle(xlist, dobackup):
             xlist[0].append(abs)
             update[abs] = 1
-            if dobackup and not opts['no_backup'] and os.path.exists(rel):
+            if (dobackup and not opts['no_backup'] and
+                (os.path.islink(rel) or os.path.exists(rel))):
                 bakname = "%s.orig" % rel
                 ui.note(_('saving current version of %s as %s\n') %
                         (rel, bakname))
@@ -2254,7 +2300,7 @@ def revert(ui, repo, *pats, **opts):
             if pmf is None:
                 # only need parent manifest in this unlikely case,
                 # so do not read by default
-                pmf = repo.manifest.read(repo.changelog.read(parent)[0])
+                pmf = repo.changectx(parent).manifest()
             if abs in pmf:
                 if mfentry:
                     # if version of file is same in parent and target
@@ -2335,44 +2381,27 @@ def serve(ui, repo, **opts):
         raise hg.RepoError(_("There is no Mercurial repository here"
                              " (.hg not found)"))
 
-    if opts['daemon'] and not opts['daemon_pipefds']:
-        rfd, wfd = os.pipe()
-        args = sys.argv[:]
-        args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
-        pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
-                         args[0], args)
-        os.close(wfd)
-        os.read(rfd, 1)
-        os._exit(0)
-
-    httpd = hgweb.server.create_server(parentui, repo)
-
-    if ui.verbose:
-        if httpd.port != 80:
-            ui.status(_('listening at http://%s:%d/\n') %
-                      (httpd.addr, httpd.port))
-        else:
-            ui.status(_('listening at http://%s/\n') % httpd.addr)
-
-    if opts['pid_file']:
-        fp = open(opts['pid_file'], 'w')
-        fp.write(str(os.getpid()) + '\n')
-        fp.close()
-
-    if opts['daemon_pipefds']:
-        rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
-        os.close(rfd)
-        os.write(wfd, 'y')
-        os.close(wfd)
-        sys.stdout.flush()
-        sys.stderr.flush()
-        fd = os.open(util.nulldev, os.O_RDWR)
-        if fd != 0: os.dup2(fd, 0)
-        if fd != 1: os.dup2(fd, 1)
-        if fd != 2: os.dup2(fd, 2)
-        if fd not in (0, 1, 2): os.close(fd)
-
-    httpd.serve_forever()
+    class service:
+        def init(self):
+            try:
+                self.httpd = hgweb.server.create_server(parentui, repo)
+            except socket.error, inst:
+                raise util.Abort(_('cannot start server: ') + inst.args[1])
+
+            if not ui.verbose: return
+
+            if httpd.port != 80:
+                ui.status(_('listening at http://%s:%d/\n') %
+                          (httpd.addr, httpd.port))
+            else:
+                ui.status(_('listening at http://%s/\n') % httpd.addr)
+
+        def run(self):
+            self.httpd.serve_forever()
+
+    service = service()
+
+    cmdutil.service(opts, initfn=service.init, runfn=service.run)
 
 def status(ui, repo, *pats, **opts):
     """show changed files in the working directory
@@ -2498,9 +2527,10 @@ def tags(ui, repo):
     hexfunc = ui.debugflag and hex or short
     for t, n in l:
         try:
+	    hn = hexfunc(n)
             r = "%5d:%s" % (repo.changelog.rev(n), hexfunc(n))
-        except KeyError:
-            r = "    ?:?"
+        except revlog.LookupError:
+            r = "    ?:%s" % hn
         if ui.quiet:
             ui.write("%s\n" % t)
         else:
@@ -2528,10 +2558,11 @@ def unbundle(ui, repo, fname, **opts):
     modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
     return postincoming(ui, repo, modheads, opts['update'])
 
-def update(ui, repo, node=None, clean=False, branch=None, date=None):
+def update(ui, repo, node=None, clean=False, date=None):
     """update working directory
 
-    Update the working directory to the specified revision.
+    Update the working directory to the specified revision, or the
+    tip of the current branch if none is specified.
 
     If there are no outstanding changes in the working directory and
     there is a linear relationship between the current version and the
@@ -2548,43 +2579,11 @@ def update(ui, repo, node=None, clean=Fa
             raise util.Abort(_("you can't specify a revision and a date"))
         node = cmdutil.finddate(ui, repo, date)
 
-    node = _lookup(repo, node, branch)
     if clean:
         return hg.clean(repo, node)
     else:
         return hg.update(repo, node)
 
-def _lookup(repo, node, branch=None):
-    if branch:
-        repo.ui.warn(_("the --branch option is deprecated, "
-                       "please use 'hg branch' instead\n"))
-        br = repo.branchlookup(branch=branch)
-        found = []
-        for x in br:
-            if branch in br[x]:
-                found.append(x)
-        if len(found) > 1:
-            repo.ui.warn(_("Found multiple heads for %s\n") % branch)
-            for x in found:
-                cmdutil.show_changeset(ui, repo, {}).show(changenode=x)
-            raise util.Abort("")
-        if len(found) == 1:
-            node = found[0]
-            repo.ui.warn(_("Using head %s for branch %s\n")
-                         % (short(node), branch))
-        else:
-            raise util.Abort(_("branch %s not found") % branch)
-    else:
-        if node:
-            node = repo.lookup(node)
-        else:
-            wc = repo.workingctx()
-            try:
-                node = repo.branchtags()[wc.branch()]
-            except KeyError:
-                raise util.Abort(_("branch %s not found") % wc.branch())
-    return node
-
 def verify(ui, repo):
     """verify the integrity of the repository
 
@@ -2685,7 +2684,10 @@ table = {
           ('u', 'user', '', _('record user as committer')),
          ] + walkopts + commitopts,
          _('hg backout [OPTION]... REV')),
-    "branch": (branch, [], _('hg branch [NAME]')),
+    "branch": (branch,
+               [('f', 'force', None,
+                 _('set branch name even if it shadows an existing branch'))],
+                _('hg branch [NAME]')),
     "branches": (branches, [], _('hg branches')),
     "bundle":
         (bundle,
@@ -2790,8 +2792,7 @@ table = {
          _('hg grep [OPTION]... PATTERN [FILE]...')),
     "heads":
         (heads,
-         [('b', 'branches', None, _('show branches (DEPRECATED)')),
-          ('', 'style', '', _('display using template map file')),
+         [('', 'style', '', _('display using template map file')),
           ('r', 'rev', '', _('show only heads which are descendants of rev')),
           ('', 'template', '', _('display with template'))],
          _('hg heads [-r REV]')),
@@ -2802,9 +2803,11 @@ table = {
          [('p', 'strip', 1,
            _('directory strip option for patch. This has the same\n'
              'meaning as the corresponding patch option')),
-          ('b', 'base', '', _('base path (DEPRECATED)')),
+          ('b', 'base', '', _('base path')),
           ('f', 'force', None,
-           _('skip check for outstanding uncommitted changes'))] + commitopts,
+           _('skip check for outstanding uncommitted changes')),
+          ('', 'exact', None,
+           _('apply patch to the nodes from which it was generated'))] + commitopts,
          _('hg import [-p NUM] [-m MESSAGE] [-f] PATCH...')),
     "incoming|in": (incoming,
          [('M', 'no-merges', None, _('do not show merges')),
@@ -2834,8 +2837,7 @@ table = {
          _('hg locate [OPTION]... [PATTERN]...')),
     "^log|history":
         (log,
-         [('b', 'branches', None, _('show branches (DEPRECATED)')),
-          ('f', 'follow', None,
+         [('f', 'follow', None,
            _('follow changeset history, or file history across copies and renames')),
           ('', 'follow-first', None,
            _('only follow the first parent of merge changesets')),
@@ -2856,8 +2858,7 @@ table = {
     "manifest": (manifest, [], _('hg manifest [REV]')),
     "^merge":
         (merge,
-         [('b', 'branch', '', _('merge with head of a specific branch (DEPRECATED)')),
-          ('f', 'force', None, _('force a merge with outstanding changes'))],
+         [('f', 'force', None, _('force a merge with outstanding changes'))],
          _('hg merge [-f] [REV]')),
     "outgoing|out": (outgoing,
          [('M', 'no-merges', None, _('do not show merges')),
@@ -2872,8 +2873,7 @@ table = {
          _('hg outgoing [-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
     "^parents":
         (parents,
-         [('b', 'branches', None, _('show branches (DEPRECATED)')),
-          ('r', 'rev', '', _('show parents from the specified rev')),
+         [('r', 'rev', '', _('show parents from the specified rev')),
           ('', 'style', '', _('display using template map file')),
           ('', 'template', '', _('display with template'))],
          _('hg parents [-r REV] [FILE]')),
@@ -2978,8 +2978,7 @@ table = {
     "tags": (tags, [], _('hg tags')),
     "tip":
         (tip,
-         [('b', 'branches', None, _('show branches (DEPRECATED)')),
-          ('', 'style', '', _('display using template map file')),
+         [('', 'style', '', _('display using template map file')),
           ('p', 'patch', None, _('show patch')),
           ('', 'template', '', _('display with template'))],
          _('hg tip [-p]')),
@@ -2990,9 +2989,7 @@ table = {
          _('hg unbundle [-u] FILE')),
     "^update|up|checkout|co":
         (update,
-         [('b', 'branch', '',
-           _('checkout the head of a specific branch (DEPRECATED)')),
-          ('C', 'clean', None, _('overwrite locally modified files')),
+         [('C', 'clean', None, _('overwrite locally modified files')),
           ('d', 'date', '', _('tipmost revision matching date'))],
          _('hg update [-C] [-d DATE] [REV]')),
     "verify": (verify, [], _('hg verify')),
@@ -3149,9 +3146,10 @@ def load_extensions(ui):
         if reposetup:
             hg.repo_setup_hooks.append(reposetup)
         cmdtable = getattr(mod, 'cmdtable', {})
-        for t in cmdtable:
-            if t in table:
-                ui.warn(_("module %s overrides %s\n") % (name, t))
+        overrides = [cmd for cmd in cmdtable if cmd in table]
+        if overrides:
+            ui.warn(_("extension '%s' overrides commands: %s\n")
+                    % (name, " ".join(overrides)))
         table.update(cmdtable)
 
 def parseconfig(config):
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -6,9 +6,8 @@
 # of the GNU General Public License, incorporated herein by reference.
 
 from node import *
-from i18n import gettext as _
-from demandload import demandload
-demandload(globals(), "ancestor bdiff repo revlog util os")
+from i18n import _
+import ancestor, bdiff, repo, revlog, util, os, errno
 
 class changectx(object):
     """A changecontext object makes access to data related to a particular
@@ -84,21 +83,22 @@ class changectx(object):
             try:
                 return self._manifest[path]
             except KeyError:
-                raise repo.LookupError(_("'%s' not found in manifest") % path)
+                raise revlog.LookupError(_("'%s' not found in manifest") % path)
         if '_manifestdelta' in self.__dict__ or path in self.files():
             if path in self._manifestdelta:
                 return self._manifestdelta[path]
         node, flag = self._repo.manifest.find(self._changeset[0], path)
         if not node:
-            raise repo.LookupError(_("'%s' not found in manifest") % path)
+            raise revlog.LookupError(_("'%s' not found in manifest") % path)
 
         return node
 
-    def filectx(self, path, fileid=None):
+    def filectx(self, path, fileid=None, filelog=None):
         """get a file context from this changeset"""
         if fileid is None:
             fileid = self.filenode(path)
-        return filectx(self._repo, path, fileid=fileid, changectx=self)
+        return filectx(self._repo, path, fileid=fileid,
+                       changectx=self, filelog=filelog)
 
     def filectxs(self):
         """generate a file context for each file in this changeset's
@@ -126,16 +126,18 @@ class filectx(object):
         self._repo = repo
         self._path = path
 
-        assert changeid is not None or fileid is not None
+        assert (changeid is not None
+                or fileid is not None
+                or changectx is not None)
 
         if filelog:
             self._filelog = filelog
-        if changectx:
-            self._changectx = changectx
-            self._changeid = changectx.node()
 
         if fileid is None:
-            self._changeid = changeid
+            if changectx is None:
+                self._changeid = changeid
+            else:
+                self._changectx = changectx
         else:
             self._fileid = fileid
 
@@ -150,13 +152,10 @@ class filectx(object):
             self._changeid = self._filelog.linkrev(self._filenode)
             return self._changeid
         elif name == '_filenode':
-            try:
-                if '_fileid' in self.__dict__:
-                    self._filenode = self._filelog.lookup(self._fileid)
-                else:
-                    self._filenode = self._changectx.filenode(self._path)
-            except revlog.RevlogError, inst:
-                raise repo.LookupError(str(inst))
+            if '_fileid' in self.__dict__:
+                self._filenode = self._filelog.lookup(self._fileid)
+            else:
+                self._filenode = self._changectx.filenode(self._path)
             return self._filenode
         elif name == '_filerev':
             self._filerev = self._filelog.rev(self._filenode)
@@ -168,7 +167,7 @@ class filectx(object):
         try:
             n = self._filenode
             return True
-        except repo.LookupError:
+        except revlog.LookupError:
             # file is missing
             return False
 
@@ -379,13 +378,15 @@ class workingctx(changectx):
         """generate a manifest corresponding to the working directory"""
 
         man = self._parents[0].manifest().copy()
+        is_exec = util.execfunc(self._repo.root, man.execf)
+        is_link = util.linkfunc(self._repo.root, man.linkf)
         copied = self._repo.dirstate.copies()
         modified, added, removed, deleted, unknown = self._status[:5]
         for i, l in (("a", added), ("m", modified), ("u", unknown)):
             for f in l:
                 man[f] = man.get(copied.get(f, f), nullid) + i
                 try:
-                    man.set(f, util.is_exec(self._repo.wjoin(f), man.execf(f)))
+                    man.set(f, is_exec(f), is_link(f))
                 except OSError:
                     pass
 
@@ -420,9 +421,10 @@ class workingctx(changectx):
     def children(self):
         return []
 
-    def filectx(self, path):
+    def filectx(self, path, filelog=None):
         """get a file context from the working directory"""
-        return workingfilectx(self._repo, path, workingctx=self)
+        return workingfilectx(self._repo, path, workingctx=self,
+                              filelog=filelog)
 
     def ancestor(self, c2):
         """return the ancestor context of self and c2"""
@@ -480,7 +482,7 @@ class workingfilectx(filectx):
         rp = self._repopath
         if rp == self._path:
             return None
-        return rp, self._workingctx._parents._manifest.get(rp, nullid)
+        return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
 
     def parents(self):
         '''return parent filectxs, following copies if necessary'''
@@ -501,5 +503,12 @@ class workingfilectx(filectx):
         return []
 
     def size(self): return os.stat(self._repo.wjoin(self._path)).st_size
+    def date(self):
+        t, tz = self._changectx.date()
+        try:
+            return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
+        except OSError, err:
+            if err.errno != errno.ENOENT: raise
+            return (t, tz)
 
     def cmp(self, text): return self._repo.wread(self._path) == text
new file mode 100644
--- /dev/null
+++ b/mercurial/demandimport.py
@@ -0,0 +1,115 @@
+# demandimport.py - global demand-loading of modules for Mercurial
+#
+# Copyright 2006 Matt Mackall <mpm@selenic.com>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+'''
+demandimport - automatic demandloading of modules
+
+To enable this module, do:
+
+  import demandimport; demandimport.enable()
+
+Imports of the following forms will be demand-loaded:
+
+  import a, b.c
+  import a.b as c
+  from a import b,c # a will be loaded immediately
+
+These imports will not be delayed:
+
+  from a import *
+  b = __import__(a)
+'''
+
+_origimport = __import__
+
+class _demandmod(object):
+    """module demand-loader and proxy"""
+    def __init__(self, name, globals, locals):
+        if '.' in name:
+            head, rest = name.split('.', 1)
+            after = [rest]
+        else:
+            head = name
+            after = []
+        object.__setattr__(self, "_data", (head, globals, locals, after))
+        object.__setattr__(self, "_module", None)
+    def _extend(self, name):
+        """add to the list of submodules to load"""
+        self._data[3].append(name)
+    def _load(self):
+        if not self._module:
+            head, globals, locals, after = self._data
+            mod = _origimport(head, globals, locals)
+            # load submodules
+            def subload(mod, p):
+                h, t = p, None
+                if '.' in p:
+                    h, t = p.split('.', 1)
+                if not hasattr(mod, h):
+                    setattr(mod, h, _demandmod(p, mod.__dict__, mod.__dict__))
+                elif t:
+                    subload(getattr(mod, h), t)
+
+            for x in after:
+                subload(mod, x)
+
+            # are we in the locals dictionary still?
+            if locals and locals.get(head) == self:
+                locals[head] = mod
+            object.__setattr__(self, "_module", mod)
+    def __repr__(self):
+        return "<unloaded module '%s'>" % self._data[0]
+    def __call__(self, *args, **kwargs):
+        raise TypeError("'unloaded module' object is not callable")
+    def __getattribute__(self, attr):
+        if attr in ('_data', '_extend', '_load', '_module'):
+            return object.__getattribute__(self, attr)
+        self._load()
+        return getattr(self._module, attr)
+    def __setattr__(self, attr, val):
+        self._load()
+        setattr(self._module, attr, val)
+
+def _demandimport(name, globals=None, locals=None, fromlist=None):
+    if not locals or name in ignore or fromlist == ('*',):
+        # these cases we can't really delay
+        return _origimport(name, globals, locals, fromlist)
+    elif not fromlist:
+        # import a [as b]
+        if '.' in name: # a.b
+            base, rest = name.split('.', 1)
+            # email.__init__ loading email.mime
+            if globals and globals.get('__name__', None) == base:
+                return _origimport(name, globals, locals, fromlist)
+            # if a is already demand-loaded, add b to its submodule list
+            if base in locals:
+                if isinstance(locals[base], _demandmod):
+                    locals[base]._extend(rest)
+                return locals[base]
+        return _demandmod(name, globals, locals)
+    else:
+        # from a import b,c,d
+        mod = _origimport(name, globals, locals)
+        # recurse down the module chain
+        for comp in name.split('.')[1:]:
+            mod = getattr(mod, comp)
+        for x in fromlist:
+            # set requested submodules for demand load
+            if not(hasattr(mod, x)):
+                setattr(mod, x, _demandmod(x, mod.__dict__, mod.__dict__))
+        return mod
+
+ignore = ['_hashlib', '_xmlplus', 'fcntl', 'win32com.gen_py']
+
+def enable():
+    "enable global demand-loading of modules"
+    __builtins__["__import__"] = _demandimport
+
+def disable():
+    "disable global demand-loading of modules"
+    __builtins__["__import__"] = _origimport
+
deleted file mode 100644
--- a/mercurial/demandload.py
+++ /dev/null
@@ -1,135 +0,0 @@
-'''Demand load modules when used, not when imported.'''
-
-__author__ = '''Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>.
-This software may be used and distributed according to the terms
-of the GNU General Public License, incorporated herein by reference.'''
-
-# this is based on matt's original demandload module.  it is a
-# complete rewrite.  some time, we may need to support syntax of
-# "import foo as bar".
-
-class _importer(object):
-    '''import a module.  it is not imported until needed, and is
-    imported at most once per scope.'''
-
-    def __init__(self, scope, modname, fromlist):
-        '''scope is context (globals() or locals()) in which import
-        should be made.  modname is name of module to import.
-        fromlist is list of modules for "from foo import ..."
-        emulation.'''
-
-        self.scope = scope
-        self.modname = modname
-        self.fromlist = fromlist
-        self.mod = None
-
-    def module(self):
-        '''import the module if needed, and return.'''
-        if self.mod is None:
-            self.mod = __import__(self.modname, self.scope, self.scope,
-                                  self.fromlist)
-            del self.modname, self.fromlist
-        return self.mod
-
-class _replacer(object):
-    '''placeholder for a demand loaded module. demandload puts this in
-    a target scope.  when an attribute of this object is looked up,
-    this object is replaced in the target scope with the actual
-    module.
-
-    we use __getattribute__ to avoid namespace clashes between
-    placeholder object and real module.'''
-
-    def __init__(self, importer, target):
-        self.importer = importer
-        self.target = target
-        # consider case where we do this:
-        #   demandload(globals(), 'foo.bar foo.quux')
-        # foo will already exist in target scope when we get to
-        # foo.quux.  so we remember that we will need to demandload
-        # quux into foo's scope when we really load it.
-        self.later = []
-
-    def module(self):
-        return object.__getattribute__(self, 'importer').module()
-
-    def __getattribute__(self, key):
-        '''look up an attribute in a module and return it. replace the
-        name of the module in the caller\'s dict with the actual
-        module.'''
-
-        module = object.__getattribute__(self, 'module')()
-        target = object.__getattribute__(self, 'target')
-        importer = object.__getattribute__(self, 'importer')
-        later = object.__getattribute__(self, 'later')
-
-        if later:
-            demandload(module.__dict__, ' '.join(later))
-
-        importer.scope[target] = module
-
-        return getattr(module, key)
-
-class _replacer_from(_replacer):
-    '''placeholder for a demand loaded module.  used for "from foo
-    import ..." emulation. semantics of this are different than
-    regular import, so different implementation needed.'''
-
-    def module(self):
-        importer = object.__getattribute__(self, 'importer')
-        target = object.__getattribute__(self, 'target')
-
-        return getattr(importer.module(), target)
-
-    def __call__(self, *args, **kwargs):
-        target = object.__getattribute__(self, 'module')()
-        return target(*args, **kwargs)
-
-def demandload(scope, modules):
-    '''import modules into scope when each is first used.
-
-    scope should be the value of globals() in the module calling this
-    function, or locals() in the calling function.
-
-    modules is a string listing module names, separated by white
-    space.  names are handled like this:
-
-    foo            import foo
-    foo bar        import foo, bar
-    foo@bar        import foo as bar
-    foo.bar        import foo.bar
-    foo:bar        from foo import bar
-    foo:bar,quux   from foo import bar, quux
-    foo.bar:quux   from foo.bar import quux'''
-
-    for mod in modules.split():
-        col = mod.find(':')
-        if col >= 0:
-            fromlist = mod[col+1:].split(',')
-            mod = mod[:col]
-        else:
-            fromlist = []
-        as_ = None
-        if '@' in mod:
-            mod, as_ = mod.split("@")
-        importer = _importer(scope, mod, fromlist)
-        if fromlist:
-            for name in fromlist:
-                scope[name] = _replacer_from(importer, name)
-        else:
-            dot = mod.find('.')
-            if dot >= 0:
-                basemod = mod[:dot]
-                val = scope.get(basemod)
-                # if base module has already been demandload()ed,
-                # remember to load this submodule into its namespace
-                # when needed.
-                if isinstance(val, _replacer):
-                    later = object.__getattribute__(val, 'later')
-                    later.append(mod[dot+1:])
-                    continue
-            else:
-                basemod = mod
-            if not as_:
-                as_ = basemod
-            scope[as_] = _replacer(importer, as_)
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -8,9 +8,9 @@ of the GNU General Public License, incor
 """
 
 from node import *
-from i18n import gettext as _
-from demandload import *
-demandload(globals(), "struct os time bisect stat strutil util re errno")
+from i18n import _
+import struct, os, time, bisect, stat, strutil, util, re, errno
+import cStringIO
 
 class dirstate(object):
     format = ">cllll"
@@ -21,6 +21,7 @@ class dirstate(object):
         self.dirty = 0
         self.ui = ui
         self.map = None
+        self.fp = None
         self.pl = None
         self.dirs = None
         self.copymap = {}
@@ -136,12 +137,28 @@ class dirstate(object):
             self.lazyread()
             return self[key]
 
+    _unknown = ('?', 0, 0, 0)
+
+    def get(self, key):
+        try:
+            return self[key]
+        except KeyError:
+            return self._unknown
+
     def __contains__(self, key):
         self.lazyread()
         return key in self.map
 
     def parents(self):
-        self.lazyread()
+        if self.pl is None:
+            self.pl = [nullid, nullid]
+            try:
+                self.fp = self.opener('dirstate')
+                st = self.fp.read(40)
+                if len(st) == 40:
+                    self.pl = st[:20], st[20:40]
+            except IOError, err:
+                if err.errno != errno.ENOENT: raise
         return self.pl
 
     def branch(self):
@@ -205,12 +222,27 @@ class dirstate(object):
         self.map = {}
         self.pl = [nullid, nullid]
         try:
-            st = self.opener("dirstate").read()
+            if self.fp:
+                self.fp.seek(0)
+                st = self.fp.read()
+                self.fp = None
+            else:
+                st = self.opener("dirstate").read()
             if st:
                 self.parse(st)
         except IOError, err:
             if err.errno != errno.ENOENT: raise
 
+    def reload(self):
+        def mtime():
+            m = self.map and self.map.get('.hgignore')
+            return m and m[-1]
+
+        old_mtime = self.ignorefunc and mtime()
+        self.read()
+        if old_mtime != mtime():
+            self.ignorefunc = None
+
     def copy(self, source, dest):
         self.lazyread()
         self.markdirty()
@@ -317,15 +349,17 @@ class dirstate(object):
     def write(self):
         if not self.dirty:
             return
-        st = self.opener("dirstate", "w", atomictemp=True)
-        st.write("".join(self.pl))
-        for f, e in self.map.items():
+        cs = cStringIO.StringIO()
+        cs.write("".join(self.pl))
+        for f, e in self.map.iteritems():
             c = self.copied(f)
             if c:
                 f = f + "\0" + c
             e = struct.pack(self.format, e[0], e[1], e[2], e[3], len(f))
-            st.write(e + f)
-        st.rename()
+            cs.write(e)
+            cs.write(f)
+        st = self.opener("dirstate", "w", atomic=True)
+        st.write(cs.getvalue())
         self.dirty = 0
 
     def filterfiles(self, files):
@@ -359,14 +393,13 @@ class dirstate(object):
         return ret
 
     def supported_type(self, f, st, verbose=False):
-        if stat.S_ISREG(st.st_mode):
+        if stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode):
             return True
         if verbose:
             kind = 'unknown'
             if stat.S_ISCHR(st.st_mode): kind = _('character device')
             elif stat.S_ISBLK(st.st_mode): kind = _('block device')
             elif stat.S_ISFIFO(st.st_mode): kind = _('fifo')
-            elif stat.S_ISLNK(st.st_mode): kind = _('symbolic link')
             elif stat.S_ISSOCK(st.st_mode): kind = _('socket')
             elif stat.S_ISDIR(st.st_mode): kind = _('directory')
             self.ui.warn(_('%s: unsupported file type (type is %s)\n') % (
@@ -380,7 +413,7 @@ class dirstate(object):
             yield src, f
 
     def statwalk(self, files=None, match=util.always, ignored=False,
-                 badmatch=None):
+                 badmatch=None, directories=False):
         '''
         walk recursively through the directory tree, finding all files
         matched by the match function
@@ -388,6 +421,7 @@ class dirstate(object):
         results are yielded in a tuple (src, filename, st), where src
         is one of:
         'f' the file was found in the directory tree
+        'd' the file is a directory of the tree
         'm' the file was only in the dirstate and not in the tree
         'b' file was not found and matched badmatch
 
@@ -408,7 +442,10 @@ class dirstate(object):
                 return False
             return match(file_)
 
-        if ignored: imatch = match
+        ignore = self.ignore
+        if ignored:
+            imatch = match
+            ignore = util.never
 
         # self.root may end with a path separator when self.root == '/'
         common_prefix_len = len(self.root)
@@ -417,6 +454,8 @@ class dirstate(object):
         # recursion free walker, faster than os.walk.
         def findfiles(s):
             work = [s]
+            if directories:
+                yield 'd', util.normpath(s[common_prefix_len:]), os.lstat(s)
             while work:
                 top = work.pop()
                 names = os.listdir(top)
@@ -441,9 +480,10 @@ class dirstate(object):
                     # don't trip over symlinks
                     st = os.lstat(p)
                     if stat.S_ISDIR(st.st_mode):
-                        ds = util.pconvert(os.path.join(nd, f +'/'))
-                        if imatch(ds):
+                        if not ignore(np):
                             work.append(p)
+                            if directories:
+                                yield 'd', np, st
                         if imatch(np) and np in dc:
                             yield 'm', np, st
                     elif imatch(np):
--- a/mercurial/filelog.py
+++ b/mercurial/filelog.py
@@ -6,15 +6,12 @@
 # of the GNU General Public License, incorporated herein by reference.
 
 from revlog import *
-from demandload import *
-demandload(globals(), "os")
+import os
 
 class filelog(revlog):
-    def __init__(self, opener, path, defversion=REVLOG_DEFAULT_VERSION):
+    def __init__(self, opener, path):
         revlog.__init__(self, opener,
-                        "/".join(("data", self.encodedir(path + ".i"))),
-                        "/".join(("data", self.encodedir(path + ".d"))),
-                        defversion)
+                        "/".join(("data", self.encodedir(path + ".i"))))
 
     # This avoids a collision between a file named foo and a dir named
     # foo.i or foo.d
--- a/mercurial/hg.py
+++ b/mercurial/hg.py
@@ -8,10 +8,11 @@
 
 from node import *
 from repo import *
-from demandload import *
-from i18n import gettext as _
-demandload(globals(), "localrepo bundlerepo httprepo sshrepo statichttprepo")
-demandload(globals(), "errno lock os shutil util merge@_merge verify@_verify")
+from i18n import _
+import localrepo, bundlerepo, httprepo, sshrepo, statichttprepo
+import errno, lock, os, shutil, util
+import merge as _merge
+import verify as _verify
 
 def _local(path):
     return (os.path.isfile(util.drop_scheme('file', path)) and
--- a/mercurial/hgweb/__init__.py
+++ b/mercurial/hgweb/__init__.py
@@ -6,6 +6,11 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-from mercurial.demandload import demandload
-demandload(globals(), "mercurial.hgweb.hgweb_mod:hgweb")
-demandload(globals(), "mercurial.hgweb.hgwebdir_mod:hgwebdir")
+import hgweb_mod, hgwebdir_mod
+
+def hgweb(*args, **kwargs):
+    return hgweb_mod.hgweb(*args, **kwargs)
+
+def hgwebdir(*args, **kwargs):
+    return hgwebdir_mod.hgwebdir(*args, **kwargs)
+
--- a/mercurial/hgweb/common.py
+++ b/mercurial/hgweb/common.py
@@ -7,7 +7,6 @@
 # of the GNU General Public License, incorporated herein by reference.
 
 import os, mimetypes
-import os.path
 
 def get_mtime(repo_path):
     store_path = os.path.join(repo_path, ".hg")
@@ -39,7 +38,7 @@ def staticfile(directory, fname, req):
         os.stat(path)
         ct = mimetypes.guess_type(path)[0] or "text/plain"
         req.header([('Content-type', ct),
-                    ('Content-length', os.path.getsize(path))])
+                    ('Content-length', str(os.path.getsize(path)))])
         return file(path, 'rb').read()
     except (TypeError, OSError):
         # illegal fname or unreadable file
--- a/mercurial/hgweb/hgweb_mod.py
+++ b/mercurial/hgweb/hgweb_mod.py
@@ -6,17 +6,13 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-import os
-import os.path
-import mimetypes
-from mercurial.demandload import demandload
-demandload(globals(), "re zlib ConfigParser mimetools cStringIO sys tempfile")
-demandload(globals(), 'urllib bz2')
-demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,streamclone,patch")
-demandload(globals(), "mercurial:revlog,templater")
-demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile,style_map")
+import os, mimetypes, re, zlib, mimetools, cStringIO, sys
+import tempfile, urllib, bz2
 from mercurial.node import *
 from mercurial.i18n import gettext as _
+from mercurial import mdiff, ui, hg, util, archival, streamclone, patch
+from mercurial import revlog, templater
+from common import get_mtime, staticfile, style_map
 
 def _up(p):
     if p[0] != "/":
@@ -172,14 +168,10 @@ class hgweb(object):
                     yield self.t("diffline", line=l)
 
         r = self.repo
-        cl = r.changelog
-        mf = r.manifest
-        change1 = cl.read(node1)
-        change2 = cl.read(node2)
-        mmap1 = mf.read(change1[0])
-        mmap2 = mf.read(change2[0])
-        date1 = util.datestr(change1[2])
-        date2 = util.datestr(change2[2])
+        c1 = r.changectx(node1)
+        c2 = r.changectx(node2)
+        date1 = util.datestr(c1.date())
+        date2 = util.datestr(c2.date())
 
         modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
         if files:
@@ -188,17 +180,17 @@ class hgweb(object):
 
         diffopts = patch.diffopts(self.repo.ui, untrusted=True)
         for f in modified:
-            to = r.file(f).read(mmap1[f])
-            tn = r.file(f).read(mmap2[f])
+            to = c1.filectx(f).data()
+            tn = c2.filectx(f).data()
             yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
                                           opts=diffopts), f, tn)
         for f in added:
             to = None
-            tn = r.file(f).read(mmap2[f])
+            tn = c2.filectx(f).data()
             yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
                                           opts=diffopts), f, tn)
         for f in removed:
-            to = r.file(f).read(mmap1[f])
+            to = c1.filectx(f).data()
             tn = None
             yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
                                           opts=diffopts), f, tn)
@@ -497,8 +489,6 @@ class hgweb(object):
                      archives=self.archivelist(hex(node)))
 
     def tags(self):
-        cl = self.repo.changelog
-
         i = self.repo.tagslist()
         i.reverse()
 
@@ -509,7 +499,7 @@ class hgweb(object):
                     continue
                 yield {"parity": self.stripes(parity),
                        "tag": k,
-                       "date": cl.read(n)[2],
+                       "date": self.repo.changectx(n).date(),
                        "node": hex(n)}
                 parity += 1
 
@@ -519,8 +509,6 @@ class hgweb(object):
                      entriesnotip=lambda **x: entries(True, **x))
 
     def summary(self):
-        cl = self.repo.changelog
-
         i = self.repo.tagslist()
         i.reverse()
 
@@ -535,69 +523,64 @@ class hgweb(object):
                 if count > 10: # limit to 10 tags
                     break;
 
-                c = cl.read(n)
-                t = c[2]
-
                 yield self.t("tagentry",
-                             parity = self.stripes(parity),
-                             tag = k,
-                             node = hex(n),
-                             date = t)
+                             parity=self.stripes(parity),
+                             tag=k,
+                             node=hex(n),
+                             date=self.repo.changectx(n).date())
                 parity += 1
 
-        def heads(**map):
+
+        def branches(**map):
             parity = 0
-            count = 0
 
-            for node in self.repo.heads():
-                count += 1
-                if count > 10:
-                    break;
+            b = self.repo.branchtags()
+            l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
+            l.sort()
 
-                ctx = self.repo.changectx(node)
+            for r,n,t in l:
+                ctx = self.repo.changectx(n)
 
                 yield {'parity': self.stripes(parity),
-                       'branch': ctx.branch(),
-                       'node': hex(node),
+                       'branch': t,
+                       'node': hex(n),
                        'date': ctx.date()}
                 parity += 1
 
         def changelist(**map):
             parity = 0
-            cl = self.repo.changelog
             l = [] # build a list in forward order for efficiency
             for i in xrange(start, end):
-                n = cl.node(i)
-                changes = cl.read(n)
-                hn = hex(n)
-                t = changes[2]
+                ctx = self.repo.changectx(i)
+                hn = hex(ctx.node())
 
                 l.insert(0, self.t(
                     'shortlogentry',
-                    parity = parity,
-                    author = changes[1],
-                    desc = changes[4],
-                    date = t,
-                    rev = i,
-                    node = hn))
+                    parity=parity,
+                    author=ctx.user(),
+                    desc=ctx.description(),
+                    date=ctx.date(),
+                    rev=i,
+                    node=hn))
                 parity = 1 - parity
 
             yield l
 
+        cl = self.repo.changelog
         count = cl.count()
         start = max(0, count - self.maxchanges)
         end = min(count, start + self.maxchanges)
 
         yield self.t("summary",
-                 desc = self.config("web", "description", "unknown"),
-                 owner = (self.config("ui", "username") or # preferred
-                          self.config("web", "contact") or # deprecated
-                          self.config("web", "author", "unknown")), # also
-                 lastchange = cl.read(cl.tip())[2],
-                 tags = tagentries,
-                 heads = heads,
-                 shortlog = changelist,
-                 node = hex(cl.tip()),
+                 desc=self.config("web", "description", "unknown"),
+                 owner=(self.config("ui", "username") or # preferred
+                        self.config("web", "contact") or # deprecated
+                        self.config("web", "author", "unknown")), # also
+                 lastchange=cl.read(cl.tip())[2],
+                 tags=tagentries,
+                 branches=branches,
+                 shortlog=changelist,
+                 node=hex(cl.tip()),
                  archives=self.archivelist("tip"))
 
     def filediff(self, fctx):
@@ -623,9 +606,13 @@ class hgweb(object):
         'zip': ('application/zip', 'zip', '.zip', None),
         }
 
-    def archive(self, req, cnode, type_):
+    def archive(self, req, id, type_):
         reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
-        name = "%s-%s" % (reponame, short(cnode))
+        cnode = self.repo.lookup(id)
+        arch_version = id
+        if cnode == id or id == 'tip':
+            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' %
@@ -789,6 +776,9 @@ class hgweb(object):
         port = req.env["SERVER_PORT"]
         port = port != "80" and (":" + port) or ""
         urlbase = 'http://%s%s' % (req.env['SERVER_NAME'], port)
+        staticurl = self.config("web", "staticurl") or req.url + 'static/'
+        if not staticurl.endswith('/'):
+            staticurl += '/'
 
         if not self.reponame:
             self.reponame = (self.config("web", "name")
@@ -797,6 +787,7 @@ class hgweb(object):
 
         self.t = templater.templater(mapfile, templater.common_filters,
                                      defaults={"url": req.url,
+                                               "staticurl": staticurl,
                                                "urlbase": urlbase,
                                                "repo": self.reponame,
                                                "header": header,
@@ -876,7 +867,7 @@ class hgweb(object):
             try:
                 req.write(self.filerevision(self.filectx(req)))
                 return
-            except hg.RepoError:
+            except revlog.LookupError:
                 pass
 
         req.write(self.manifest(self.changectx(req), path))
@@ -1005,12 +996,11 @@ class hgweb(object):
         req.write(z.flush())
 
     def do_archive(self, req):
-        changeset = self.repo.lookup(req.form['node'][0])
         type_ = req.form['type'][0]
         allowed = self.configlist("web", "allow_archive")
         if (type_ in self.archives and (type_ in allowed or
             self.configbool("web", "allow" + type_, False))):
-            self.archive(req, changeset, type_)
+            self.archive(req, req.form['node'][0], type_)
             return
 
         req.write(self.t("error"))
@@ -1028,7 +1018,7 @@ class hgweb(object):
     def do_capabilities(self, req):
         caps = ['lookup', 'changegroupsubset']
         if self.configbool('server', 'uncompressed'):
-            caps.append('stream=%d' % self.repo.revlogversion)
+            caps.append('stream=%d' % self.repo.changelog.version)
         # XXX: make configurable and/or share code with do_unbundle:
         unbundleversions = ['HG10GZ', 'HG10BZ', 'HG10UN']
         if unbundleversions:
--- a/mercurial/hgweb/hgwebdir_mod.py
+++ b/mercurial/hgweb/hgwebdir_mod.py
@@ -6,13 +6,12 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-import os
-from mercurial.demandload import demandload
-demandload(globals(), "mimetools cStringIO")
-demandload(globals(), "mercurial:ui,hg,util,templater")
-demandload(globals(), "mercurial.hgweb.hgweb_mod:hgweb")
-demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile,style_map")
+from mercurial import demandimport; demandimport.enable()
+import os, mimetools, cStringIO
 from mercurial.i18n import gettext as _
+from mercurial import ui, hg, util, templater
+from common import get_mtime, staticfile, style_map
+from hgweb_mod import hgweb
 
 # This is a stopgap
 class hgwebdir(object):
@@ -31,8 +30,11 @@ class hgwebdir(object):
             self.repos = cleannames(config.items())
             self.repos.sort()
         else:
-            cp = util.configparser()
-            cp.read(config)
+            if isinstance(config, util.configparser):
+                cp = config
+            else:
+                cp = util.configparser()
+                cp.read(config)
             self.repos = []
             if cp.has_section('web'):
                 if cp.has_option('web', 'motd'):
@@ -86,6 +88,10 @@ class hgwebdir(object):
         if not url.endswith('/'):
             url += '/'
 
+        staticurl = config('web', 'staticurl') or url + 'static/'
+        if not staticurl.endswith('/'):
+            staticurl += '/'
+
         style = self.style
         if style is None:
             style = config('web', 'style', '')
@@ -96,7 +102,8 @@ class hgwebdir(object):
                                    defaults={"header": header,
                                              "footer": footer,
                                              "motd": motd,
-                                             "url": url})
+                                             "url": url,
+                                             "staticurl": staticurl})
 
         def archivelist(ui, nodeid, url):
             allowed = ui.configlist("web", "allow_archive", untrusted=True)
--- a/mercurial/hgweb/request.py
+++ b/mercurial/hgweb/request.py
@@ -6,8 +6,7 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-from mercurial.demandload import demandload
-demandload(globals(), "socket sys cgi os errno")
+import socket, cgi, errno
 from mercurial.i18n import gettext as _
 
 class wsgiapplication(object):
--- a/mercurial/hgweb/server.py
+++ b/mercurial/hgweb/server.py
@@ -6,11 +6,11 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-from mercurial.demandload import demandload
-import os, sys, errno
-demandload(globals(), "urllib BaseHTTPServer socket SocketServer traceback")
-demandload(globals(), "mercurial:ui,hg,util,templater")
-demandload(globals(), "hgweb_mod:hgweb hgwebdir_mod:hgwebdir request:wsgiapplication")
+import os, sys, errno, urllib, BaseHTTPServer, socket, SocketServer, traceback
+from mercurial import ui, hg, util, templater
+from hgweb_mod import hgweb
+from hgwebdir_mod import hgwebdir
+from request import wsgiapplication
 from mercurial.i18n import gettext as _
 
 def _splitURI(uri):
--- a/mercurial/httprepo.py
+++ b/mercurial/httprepo.py
@@ -8,10 +8,9 @@
 
 from node import *
 from remoterepo import *
-from i18n import gettext as _
-from demandload import *
-demandload(globals(), "hg os urllib urllib2 urlparse zlib util httplib")
-demandload(globals(), "errno keepalive tempfile socket changegroup")
+from i18n import _
+import hg, os, urllib, urllib2, urlparse, zlib, util, httplib
+import errno, keepalive, tempfile, socket, changegroup
 
 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
     def __init__(self, ui):
--- a/mercurial/i18n.py
+++ b/mercurial/i18n.py
@@ -7,9 +7,7 @@ This software may be used and distribute
 of the GNU General Public License, incorporated herein by reference.
 """
 
-# the import from gettext is _really_ slow
-# for now we use a dummy function
-gettext = lambda x: x
-#import gettext
-#t = gettext.translation('hg', '/usr/share/locale', fallback=1)
-#gettext = t.gettext
+import gettext
+t = gettext.translation('hg', fallback=1)
+gettext = t.gettext
+_ = gettext
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -6,13 +6,11 @@
 # of the GNU General Public License, incorporated herein by reference.
 
 from node import *
-from i18n import gettext as _
-from demandload import *
-import repo
-demandload(globals(), "appendfile changegroup")
-demandload(globals(), "changelog dirstate filelog manifest context")
-demandload(globals(), "re lock transaction tempfile stat mdiff errno ui")
-demandload(globals(), "os revlog time util")
+from i18n import _
+import repo, changegroup
+import changelog, dirstate, filelog, manifest, context
+import re, lock, transaction, tempfile, stat, mdiff, errno, ui
+import os, revlog, time, util
 
 class localrepository(repo.repository):
     capabilities = ('lookup', 'changegroupsubset')
@@ -43,17 +41,19 @@ class localrepository(repo.repository):
                 if not os.path.exists(path):
                     os.mkdir(path)
                 os.mkdir(self.path)
-                os.mkdir(os.path.join(self.path, "store"))
-                requirements = ("revlogv1", "store")
+                requirements = ["revlogv1"]
+                if parentui.configbool('format', 'usestore', True):
+                    os.mkdir(os.path.join(self.path, "store"))
+                    requirements.append("store")
+                    # create an invalid changelog
+                    self.opener("00changelog.i", "a").write(
+                        '\0\0\0\2' # represents revlogv2
+                        ' dummy changelog to prevent using the old repo layout'
+                    )
                 reqfile = self.opener("requires", "w")
                 for r in requirements:
                     reqfile.write("%s\n" % r)
                 reqfile.close()
-                # create an invalid changelog
-                self.opener("00changelog.i", "a").write(
-                    '\0\0\0\2' # represents revlogv2
-                    ' dummy changelog to prevent using the old repo layout'
-                )
             else:
                 raise repo.RepoError(_("repository %s not found") % path)
         elif create:
@@ -88,39 +88,18 @@ class localrepository(repo.repository):
         except IOError:
             pass
 
-        v = self.ui.configrevlog()
-        self.revlogversion = int(v.get('format', revlog.REVLOG_DEFAULT_FORMAT))
-        self.revlogv1 = self.revlogversion != revlog.REVLOGV0
-        fl = v.get('flags', None)
-        flags = 0
-        if fl != None:
-            for x in fl.split():
-                flags |= revlog.flagstr(x)
-        elif self.revlogv1:
-            flags = revlog.REVLOG_DEFAULT_FLAGS
-
-        v = self.revlogversion | flags
-        self.manifest = manifest.manifest(self.sopener, v)
-        self.changelog = changelog.changelog(self.sopener, v)
+        self.changelog = changelog.changelog(self.sopener)
+        self.sopener.defversion = self.changelog.version
+        self.manifest = manifest.manifest(self.sopener)
 
         fallback = self.ui.config('ui', 'fallbackencoding')
         if fallback:
             util._fallbackencoding = fallback
 
-        # the changelog might not have the inline index flag
-        # on.  If the format of the changelog is the same as found in
-        # .hgrc, apply any flags found in the .hgrc as well.
-        # Otherwise, just version from the changelog
-        v = self.changelog.version
-        if v == self.revlogversion:
-            v |= flags
-        self.revlogversion = v
-
         self.tagscache = None
         self.branchcache = None
         self.nodetagscache = None
-        self.encodepats = None
-        self.decodepats = None
+        self.filterpats = {}
         self.transhandle = None
 
         self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root)
@@ -140,32 +119,34 @@ class localrepository(repo.repository):
             be run as hooks without wrappers to convert return values.'''
 
             self.ui.note(_("calling hook %s: %s\n") % (hname, funcname))
-            d = funcname.rfind('.')
-            if d == -1:
-                raise util.Abort(_('%s hook is invalid ("%s" not in a module)')
-                                 % (hname, funcname))
-            modname = funcname[:d]
-            try:
-                obj = __import__(modname)
-            except ImportError:
+            obj = funcname
+            if not callable(obj):
+                d = funcname.rfind('.')
+                if d == -1:
+                    raise util.Abort(_('%s hook is invalid ("%s" not in '
+                                       'a module)') % (hname, funcname))
+                modname = funcname[:d]
                 try:
-                    # extensions are loaded with hgext_ prefix
-                    obj = __import__("hgext_%s" % modname)
+                    obj = __import__(modname)
                 except ImportError:
+                    try:
+                        # extensions are loaded with hgext_ prefix
+                        obj = __import__("hgext_%s" % modname)
+                    except ImportError:
+                        raise util.Abort(_('%s hook is invalid '
+                                           '(import of "%s" failed)') %
+                                         (hname, modname))
+                try:
+                    for p in funcname.split('.')[1:]:
+                        obj = getattr(obj, p)
+                except AttributeError, err:
                     raise util.Abort(_('%s hook is invalid '
-                                       '(import of "%s" failed)') %
-                                     (hname, modname))
-            try:
-                for p in funcname.split('.')[1:]:
-                    obj = getattr(obj, p)
-            except AttributeError, err:
-                raise util.Abort(_('%s hook is invalid '
-                                   '("%s" is not defined)') %
-                                 (hname, funcname))
-            if not callable(obj):
-                raise util.Abort(_('%s hook is invalid '
-                                   '("%s" is not callable)') %
-                                 (hname, funcname))
+                                       '("%s" is not defined)') %
+                                     (hname, funcname))
+                if not callable(obj):
+                    raise util.Abort(_('%s hook is invalid '
+                                       '("%s" is not callable)') %
+                                     (hname, funcname))
             try:
                 r = obj(ui=self.ui, repo=self, hooktype=name, **args)
             except (KeyboardInterrupt, util.SignalInterrupt):
@@ -203,7 +184,9 @@ class localrepository(repo.repository):
                  if hname.split(".", 1)[0] == name and cmd]
         hooks.sort()
         for hname, cmd in hooks:
-            if cmd.startswith('python:'):
+            if callable(cmd):
+                r = callhook(hname, cmd) or r
+            elif cmd.startswith('python:'):
                 r = callhook(hname, cmd[7:].strip()) or r
             else:
                 r = runhook(hname, cmd) or r
@@ -211,6 +194,37 @@ class localrepository(repo.repository):
 
     tag_disallowed = ':\r\n'
 
+    def _tag(self, name, node, message, local, user, date, parent=None):
+        use_dirstate = parent is None
+
+        for c in self.tag_disallowed:
+            if c in name:
+                raise util.Abort(_('%r cannot be used in a tag name') % c)
+
+        self.hook('pretag', throw=True, node=hex(node), tag=name, local=local)
+
+        if local:
+            # local tags are stored in the current charset
+            self.opener('localtags', 'a').write('%s %s\n' % (hex(node), name))
+            self.hook('tag', node=hex(node), tag=name, local=local)
+            return
+
+        # committed tags are stored in UTF-8
+        line = '%s %s\n' % (hex(node), util.fromlocal(name))
+        if use_dirstate:
+            self.wfile('.hgtags', 'ab').write(line)
+        else:
+            ntags = self.filectx('.hgtags', parent).data()
+            self.wfile('.hgtags', 'ab').write(ntags + line)
+        if use_dirstate and self.dirstate.state('.hgtags') == '?':
+            self.add(['.hgtags'])
+
+        tagnode = self.commit(['.hgtags'], message, user, date, p1=parent)
+
+        self.hook('tag', node=hex(node), tag=name, local=local)
+
+        return tagnode
+
     def tag(self, name, node, message, local, user, date):
         '''tag a revision with a symbolic name.
 
@@ -229,31 +243,13 @@ class localrepository(repo.repository):
 
         date: date tuple to use if committing'''
 
-        for c in self.tag_disallowed:
-            if c in name:
-                raise util.Abort(_('%r cannot be used in a tag name') % c)
-
-        self.hook('pretag', throw=True, node=hex(node), tag=name, local=local)
-
-        if local:
-            # local tags are stored in the current charset
-            self.opener('localtags', 'a').write('%s %s\n' % (hex(node), name))
-            self.hook('tag', node=hex(node), tag=name, local=local)
-            return
-
         for x in self.status()[:5]:
             if '.hgtags' in x:
                 raise util.Abort(_('working copy of .hgtags is changed '
                                    '(please commit .hgtags manually)'))
 
-        # committed tags are stored in UTF-8
-        line = '%s %s\n' % (hex(node), util.fromlocal(name))
-        self.wfile('.hgtags', 'ab').write(line)
-        if self.dirstate.state('.hgtags') == '?':
-            self.add(['.hgtags'])
 
-        self.commit(['.hgtags'], message, user, date)
-        self.hook('tag', node=hex(node), tag=name, local=local)
+        self._tag(name, node, message, local, user, date)
 
     def tags(self):
         '''return a mapping of tag to node'''
@@ -344,7 +340,7 @@ class localrepository(repo.repository):
             rev = c.rev()
             try:
                 fnode = c.filenode('.hgtags')
-            except repo.LookupError:
+            except revlog.LookupError:
                 continue
             ret.append((rev, node, fnode))
             if fnode in last:
@@ -477,7 +473,7 @@ class localrepository(repo.repository):
     def file(self, f):
         if f[0] == '/':
             f = f[1:]
-        return filelog.filelog(self.sopener, f, self.revlogversion)
+        return filelog.filelog(self.sopener, f)
 
     def changectx(self, changeid=None):
         return context.changectx(self, changeid)
@@ -509,17 +505,18 @@ class localrepository(repo.repository):
     def wfile(self, f, mode='r'):
         return self.wopener(f, mode)
 
-    def wread(self, filename):
-        if self.encodepats == None:
+    def _link(self, f):
+        return os.path.islink(self.wjoin(f))
+
+    def _filter(self, filter, filename, data):
+        if filter not in self.filterpats:
             l = []
-            for pat, cmd in self.ui.configitems("encode"):
+            for pat, cmd in self.ui.configitems(filter):
                 mf = util.matcher(self.root, "", [pat], [], [])[1]
                 l.append((mf, cmd))
-            self.encodepats = l
+            self.filterpats[filter] = l
 
-        data = self.wopener(filename, 'r').read()
-
-        for mf, cmd in self.encodepats:
+        for mf, cmd in self.filterpats[filter]:
             if mf(filename):
                 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
                 data = util.filter(data, cmd)
@@ -527,23 +524,36 @@ class localrepository(repo.repository):
 
         return data
 
-    def wwrite(self, filename, data, fd=None):
-        if self.decodepats == None:
-            l = []
-            for pat, cmd in self.ui.configitems("decode"):
-                mf = util.matcher(self.root, "", [pat], [], [])[1]
-                l.append((mf, cmd))
-            self.decodepats = l
+    def wread(self, filename):
+        if self._link(filename):
+            data = os.readlink(self.wjoin(filename))
+        else:
+            data = self.wopener(filename, 'r').read()
+        return self._filter("encode", filename, data)
 
-        for mf, cmd in self.decodepats:
-            if mf(filename):
-                self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
-                data = util.filter(data, cmd)
-                break
+    def wwrite(self, filename, data, flags):
+        data = self._filter("decode", filename, data)
+        if "l" in flags:
+            f = self.wjoin(filename)
+            try:
+                os.unlink(f)
+            except OSError:
+                pass
+            d = os.path.dirname(f)
+            if not os.path.exists(d):
+                os.makedirs(d)
+            os.symlink(data, f)
+        else:
+            try:
+                if self._link(filename):
+                    os.unlink(self.wjoin(filename))
+            except OSError:
+                pass
+            self.wopener(filename, 'w').write(data)
+            util.set_exec(self.wjoin(filename), "x" in flags)
 
-        if fd:
-            return fd.write(data)
-        return self.wopener(filename, 'w').write(data)
+    def wwritedata(self, filename, data):
+        return self._filter("decode", filename, data)
 
     def transaction(self):
         tr = self.transhandle
@@ -576,10 +586,11 @@ class localrepository(repo.repository):
             self.ui.warn(_("no interrupted transaction available\n"))
             return False
 
-    def rollback(self, wlock=None):
+    def rollback(self, wlock=None, lock=None):
         if not wlock:
             wlock = self.wlock()
-        l = self.lock()
+        if not lock:
+            lock = self.lock()
         if os.path.exists(self.sjoin("undo")):
             self.ui.status(_("rolling back last transaction\n"))
             transaction.rollback(self.sopener, self.sjoin("undo"))
@@ -590,7 +601,7 @@ class localrepository(repo.repository):
             self.ui.warn(_("no rollback information available\n"))
 
     def wreload(self):
-        self.dirstate.read()
+        self.dirstate.reload()
 
     def reload(self):
         self.changelog.load()
@@ -683,11 +694,11 @@ class localrepository(repo.repository):
         changelist.append(fn)
         return fl.add(t, meta, transaction, linkrev, fp1, fp2)
 
-    def rawcommit(self, files, text, user, date, p1=None, p2=None, wlock=None):
+    def rawcommit(self, files, text, user, date, p1=None, p2=None, wlock=None, extra={}):
         if p1 is None:
             p1, p2 = self.dirstate.parents()
         return self.commit(files=files, text=text, user=user, date=date,
-                           p1=p1, p2=p2, wlock=wlock)
+                           p1=p1, p2=p2, wlock=wlock, extra=extra)
 
     def commit(self, files=None, text="", user=None, date=None,
                match=util.always, force=False, lock=None, wlock=None,
@@ -761,12 +772,14 @@ class localrepository(repo.repository):
         new = {}
         linkrev = self.changelog.count()
         commit.sort()
+        is_exec = util.execfunc(self.root, m1.execf)
+        is_link = util.linkfunc(self.root, m1.linkf)
         for f in commit:
             self.ui.note(f + "\n")
             try:
                 new[f] = self.filecommit(f, m1, m2, linkrev, tr, changed)
-                m1.set(f, util.is_exec(self.wjoin(f), m1.execf(f)))
-            except IOError:
+                m1.set(f, is_exec(f), is_link(f))
+            except (OSError, IOError):
                 if use_dirstate:
                     self.ui.warn(_("trouble committing %s!\n") % f)
                     raise
@@ -776,11 +789,13 @@ class localrepository(repo.repository):
         # update manifest
         m1.update(new)
         remove.sort()
+        removed = []
 
         for f in remove:
             if f in m1:
                 del m1[f]
-        mn = self.manifest.add(m1, tr, linkrev, c1[0], c2[0], (new, remove))
+                removed.append(f)
+        mn = self.manifest.add(m1, tr, linkrev, c1[0], c2[0], (new, removed))
 
         # add changeset
         new = new.keys()
@@ -795,8 +810,10 @@ class localrepository(repo.repository):
             edittext.append("HG: user: %s" % user)
             if p2 != nullid:
                 edittext.append("HG: branch merge")
+            if branchname:
+                edittext.append("HG: branch %s" % util.tolocal(branchname))
             edittext.extend(["HG: changed %s" % f for f in changed])
-            edittext.extend(["HG: removed %s" % f for f in remove])
+            edittext.extend(["HG: removed %s" % f for f in removed])
             if not changed and not remove:
                 edittext.append("HG: no files changed")
             edittext.append("")
@@ -814,17 +831,20 @@ class localrepository(repo.repository):
         text = '\n'.join(lines)
         if branchname:
             extra["branch"] = branchname
-        n = self.changelog.add(mn, changed + remove, text, tr, p1, p2,
+        n = self.changelog.add(mn, changed + removed, text, tr, p1, p2,
                                user, date, extra)
         self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
                   parent2=xp2)
         tr.close()
 
+        if self.branchcache and "branch" in extra:
+            self.branchcache[util.tolocal(extra["branch"])] = n
+
         if use_dirstate or update_dirstate:
             self.dirstate.setparents(n)
             if use_dirstate:
                 self.dirstate.update(new, "n")
-                self.dirstate.forget(remove)
+                self.dirstate.forget(removed)
 
         self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2)
         return n
@@ -844,7 +864,13 @@ class localrepository(repo.repository):
 
         if node:
             fdict = dict.fromkeys(files)
-            for fn in self.manifest.read(self.changelog.read(node)[0]):
+            # for dirstate.walk, files=['.'] means "walk the whole tree".
+            # follow that here, too
+            fdict.pop('.', None)
+            mdict = self.manifest.read(self.changelog.read(node)[0])
+            mfiles = mdict.keys()
+            mfiles.sort()
+            for fn in mfiles:
                 for ffn in fdict:
                     # match if the file is the exact name or a directory
                     if ffn == fn or fn.startswith("%s/" % ffn):
@@ -852,7 +878,9 @@ class localrepository(repo.repository):
                         break
                 if match(fn):
                     yield 'm', fn
-            for fn in fdict:
+            ffiles = fdict.keys()
+            ffiles.sort()
+            for fn in ffiles:
                 if badmatch and badmatch(fn):
                     if match(fn):
                         yield 'b', fn
@@ -871,9 +899,9 @@ class localrepository(repo.repository):
         If node2 is None, compare node1 with working directory.
         """
 
-        def fcmp(fn, mf):
+        def fcmp(fn, getnode):
             t1 = self.wread(fn)
-            return self.file(fn).cmp(mf.get(fn, nullid), t1)
+            return self.file(fn).cmp(getnode(fn), t1)
 
         def mfmatches(node):
             change = self.changelog.read(node)
@@ -896,13 +924,10 @@ class localrepository(repo.repository):
             # all the revisions in parent->child order.
             mf1 = mfmatches(node1)
 
+        mywlock = False
+
         # are we comparing the working directory?
         if not node2:
-            if not wlock:
-                try:
-                    wlock = self.wlock(wait=0)
-                except lock.LockException:
-                    wlock = None
             (lookup, modified, added, removed, deleted, unknown,
              ignored, clean) = self.dirstate.status(files, match,
                                                     list_ignored, list_clean)
@@ -911,25 +936,38 @@ class localrepository(repo.repository):
             if compareworking:
                 if lookup:
                     # do a full compare of any files that might have changed
-                    mf2 = mfmatches(self.dirstate.parents()[0])
+                    mnode = self.changelog.read(self.dirstate.parents()[0])[0]
+                    getnode = lambda fn: (self.manifest.find(mnode, fn)[0] or
+                                          nullid)
                     for f in lookup:
-                        if fcmp(f, mf2):
+                        if fcmp(f, getnode):
                             modified.append(f)
                         else:
                             clean.append(f)
-                            if wlock is not None:
+                            if not wlock and not mywlock:
+                                mywlock = True
+                                try:
+                                    wlock = self.wlock(wait=0)
+                                except lock.LockException:
+                                    pass
+                            if wlock:
                                 self.dirstate.update([f], "n")
             else:
                 # we are comparing working dir against non-parent
                 # generate a pseudo-manifest for the working dir
                 # XXX: create it in dirstate.py ?
                 mf2 = mfmatches(self.dirstate.parents()[0])
+                is_exec = util.execfunc(self.root, mf2.execf)
+                is_link = util.linkfunc(self.root, mf2.linkf)
                 for f in lookup + modified + added:
                     mf2[f] = ""
-                    mf2.set(f, execf=util.is_exec(self.wjoin(f), mf2.execf(f)))
+                    mf2.set(f, is_exec(f), is_link(f))
                 for f in removed:
                     if f in mf2:
                         del mf2[f]
+
+            if mywlock and wlock:
+                wlock.release()
         else:
             # we are comparing two revisions
             mf2 = mfmatches(node2)
@@ -942,10 +980,12 @@ class localrepository(repo.repository):
             # reasonable order
             mf2keys = mf2.keys()
             mf2keys.sort()
+            getnode = lambda fn: mf1.get(fn, nullid)
             for fn in mf2keys:
                 if mf1.has_key(fn):
                     if mf1.flags(fn) != mf2.flags(fn) or \
-                       (mf1[fn] != mf2[fn] and (mf2[fn] != "" or fcmp(fn, mf1))):
+                       (mf1[fn] != mf2[fn] and (mf2[fn] != "" or
+                                                fcmp(fn, getnode))):
                         modified.append(fn)
                     elif list_clean:
                         clean.append(fn)
@@ -965,11 +1005,12 @@ class localrepository(repo.repository):
             wlock = self.wlock()
         for f in list:
             p = self.wjoin(f)
-            if not os.path.exists(p):
+            islink = os.path.islink(p)
+            if not islink and not os.path.exists(p):
                 self.ui.warn(_("%s does not exist!\n") % f)
-            elif not os.path.isfile(p):
-                self.ui.warn(_("%s not added: only files supported currently\n")
-                             % f)
+            elif not islink and not os.path.isfile(p):
+                self.ui.warn(_("%s not added: only files and symlinks "
+                               "supported currently\n") % f)
             elif self.dirstate.state(f) in 'an':
                 self.ui.warn(_("%s already tracked!\n") % f)
             else:
@@ -995,8 +1036,7 @@ class localrepository(repo.repository):
         if not wlock:
             wlock = self.wlock()
         for f in list:
-            p = self.wjoin(f)
-            if os.path.exists(p):
+            if unlink and os.path.exists(self.wjoin(f)):
                 self.ui.warn(_("%s still exists!\n") % f)
             elif self.dirstate.state(f) == 'a':
                 self.dirstate.forget([f])
@@ -1016,16 +1056,16 @@ class localrepository(repo.repository):
                 self.ui.warn("%s not removed!\n" % f)
             else:
                 t = self.file(f).read(m[f])
-                self.wwrite(f, t)
-                util.set_exec(self.wjoin(f), m.execf(f))
+                self.wwrite(f, t, m.flags(f))
                 self.dirstate.update([f], "n")
 
     def copy(self, source, dest, wlock=None):
         p = self.wjoin(dest)
-        if not os.path.exists(p):
+        if not (os.path.exists(p) or os.path.islink(p)):
             self.ui.warn(_("%s does not exist!\n") % dest)
-        elif not os.path.isfile(p):
-            self.ui.warn(_("copy failed: %s is not a file\n") % dest)
+        elif not (os.path.isfile(p) or os.path.islink(p)):
+            self.ui.warn(_("copy failed: %s is not a file or a "
+                           "symbolic link\n") % dest)
         else:
             if not wlock:
                 wlock = self.wlock()
@@ -1040,112 +1080,6 @@ class localrepository(repo.repository):
         heads.sort()
         return [n for (r, n) in heads]
 
-    # branchlookup returns a dict giving a list of branches for
-    # each head.  A branch is defined as the tag of a node or
-    # the branch of the node's parents.  If a node has multiple
-    # branch tags, tags are eliminated if they are visible from other
-    # branch tags.
-    #
-    # So, for this graph:  a->b->c->d->e
-    #                       \         /
-    #                        aa -----/
-    # a has tag 2.6.12
-    # d has tag 2.6.13
-    # e would have branch tags for 2.6.12 and 2.6.13.  Because the node
-    # for 2.6.12 can be reached from the node 2.6.13, that is eliminated
-    # from the list.
-    #
-    # It is possible that more than one head will have the same branch tag.
-    # callers need to check the result for multiple heads under the same
-    # branch tag if that is a problem for them (ie checkout of a specific
-    # branch).
-    #
-    # passing in a specific branch will limit the depth of the search
-    # through the parents.  It won't limit the branches returned in the
-    # result though.
-    def branchlookup(self, heads=None, branch=None):
-        if not heads:
-            heads = self.heads()
-        headt = [ h for h in heads ]
-        chlog = self.changelog
-        branches = {}
-        merges = []
-        seenmerge = {}
-
-        # traverse the tree once for each head, recording in the branches
-        # dict which tags are visible from this head.  The branches
-        # dict also records which tags are visible from each tag
-        # while we traverse.
-        while headt or merges:
-            if merges:
-                n, found = merges.pop()
-                visit = [n]
-            else:
-                h = headt.pop()
-                visit = [h]
-                found = [h]
-                seen = {}
-            while visit:
-                n = visit.pop()
-                if n in seen:
-                    continue
-                pp = chlog.parents(n)
-                tags = self.nodetags(n)
-                if tags:
-                    for x in tags:
-                        if x == 'tip':
-                            continue
-                        for f in found:
-                            branches.setdefault(f, {})[n] = 1
-                        branches.setdefault(n, {})[n] = 1
-                        break
-                    if n not in found:
-                        found.append(n)
-                    if branch in tags:
-                        continue
-                seen[n] = 1
-                if pp[1] != nullid and n not in seenmerge:
-                    merges.append((pp[1], [x for x in found]))
-                    seenmerge[n] = 1
-                if pp[0] != nullid:
-                    visit.append(pp[0])
-        # traverse the branches dict, eliminating branch tags from each
-        # head that are visible from another branch tag for that head.
-        out = {}
-        viscache = {}
-        for h in heads:
-            def visible(node):
-                if node in viscache:
-                    return viscache[node]
-                ret = {}
-                visit = [node]
-                while visit:
-                    x = visit.pop()
-                    if x in viscache:
-                        ret.update(viscache[x])
-                    elif x not in ret:
-                        ret[x] = 1
-                        if x in branches:
-                            visit[len(visit):] = branches[x].keys()
-                viscache[node] = ret
-                return ret
-            if h not in branches:
-                continue
-            # O(n^2), but somewhat limited.  This only searches the
-            # tags visible from a specific head, not all the tags in the
-            # whole repo.
-            for b in branches[h]:
-                vis = False
-                for bb in branches[h].keys():
-                    if b != bb:
-                        if b in visible(bb):
-                            vis = True
-                            break
-                if not vis:
-                    l = out.setdefault(h, [])
-                    l[len(l):] = self.nodetags(b)
-        return out
-
     def branches(self, nodes):
         if not nodes:
             nodes = [self.changelog.tip()]
@@ -1858,55 +1792,45 @@ class localrepository(repo.repository):
 
         # write changelog data to temp files so concurrent readers will not see
         # inconsistent view
-        cl = None
-        try:
-            cl = appendfile.appendchangelog(self.sopener,
-                                            self.changelog.version)
+        cl = self.changelog
+        cl.delayupdate()
+        oldheads = len(cl.heads())
 
-            oldheads = len(cl.heads())
+        # pull off the changeset group
+        self.ui.status(_("adding changesets\n"))
+        cor = cl.count() - 1
+        chunkiter = changegroup.chunkiter(source)
+        if cl.addgroup(chunkiter, csmap, tr, 1) is None:
+            raise util.Abort(_("received changelog group is empty"))
+        cnr = cl.count() - 1
+        changesets = cnr - cor
 
-            # pull off the changeset group
-            self.ui.status(_("adding changesets\n"))
-            cor = cl.count() - 1
-            chunkiter = changegroup.chunkiter(source)
-            if cl.addgroup(chunkiter, csmap, tr, 1) is None:
-                raise util.Abort(_("received changelog group is empty"))
-            cnr = cl.count() - 1
-            changesets = cnr - cor
+        # pull off the manifest group
+        self.ui.status(_("adding manifests\n"))
+        chunkiter = changegroup.chunkiter(source)
+        # no need to check for empty manifest group here:
+        # if the result of the merge of 1 and 2 is the same in 3 and 4,
+        # no new manifest will be created and the manifest group will
+        # be empty during the pull
+        self.manifest.addgroup(chunkiter, revmap, tr)
 
-            # pull off the manifest group
-            self.ui.status(_("adding manifests\n"))
+        # process the files
+        self.ui.status(_("adding file changes\n"))
+        while 1:
+            f = changegroup.getchunk(source)
+            if not f:
+                break
+            self.ui.debug(_("adding %s revisions\n") % f)
+            fl = self.file(f)
+            o = fl.count()
             chunkiter = changegroup.chunkiter(source)
-            # no need to check for empty manifest group here:
-            # if the result of the merge of 1 and 2 is the same in 3 and 4,
-            # no new manifest will be created and the manifest group will
-            # be empty during the pull
-            self.manifest.addgroup(chunkiter, revmap, tr)
-
-            # process the files
-            self.ui.status(_("adding file changes\n"))
-            while 1:
-                f = changegroup.getchunk(source)
-                if not f:
-                    break
-                self.ui.debug(_("adding %s revisions\n") % f)
-                fl = self.file(f)
-                o = fl.count()
-                chunkiter = changegroup.chunkiter(source)
-                if fl.addgroup(chunkiter, revmap, tr) is None:
-                    raise util.Abort(_("received file revlog group is empty"))
-                revisions += fl.count() - o
-                files += 1
-
-            cl.writedata()
-        finally:
-            if cl:
-                cl.cleanup()
+            if fl.addgroup(chunkiter, revmap, tr) is None:
+                raise util.Abort(_("received file revlog group is empty"))
+            revisions += fl.count() - o
+            files += 1
 
         # make changelog see real files again
-        self.changelog = changelog.changelog(self.sopener,
-                                             self.changelog.version)
-        self.changelog.checkinlinesize(tr)
+        cl.finalize(tr)
 
         newheads = len(self.changelog.heads())
         heads = ""
--- a/mercurial/lock.py
+++ b/mercurial/lock.py
@@ -5,8 +5,7 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-from demandload import *
-demandload(globals(), 'errno os socket time util')
+import errno, os, socket, time, util
 
 class LockException(IOError):
     def __init__(self, errno, strerror, filename, desc):
--- a/mercurial/mail.py
+++ b/mercurial/mail.py
@@ -5,9 +5,8 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-from i18n import gettext as _
-from demandload import *
-demandload(globals(), "os re smtplib templater util socket")
+from i18n import _
+import os, smtplib, templater, util, socket
 
 def _smtp(ui):
     '''send mail using smtp.'''
--- a/mercurial/manifest.py
+++ b/mercurial/manifest.py
@@ -6,10 +6,8 @@
 # of the GNU General Public License, incorporated herein by reference.
 
 from revlog import *
-from i18n import gettext as _
-from demandload import *
-demandload(globals(), "array bisect struct")
-demandload(globals(), "mdiff")
+from i18n import _
+import array, bisect, struct, mdiff
 
 class manifestdict(dict):
     def __init__(self, mapping=None, flags=None):
@@ -37,11 +35,10 @@ class manifestdict(dict):
         return manifestdict(dict.copy(self), dict.copy(self._flags))
 
 class manifest(revlog):
-    def __init__(self, opener, defversion=REVLOGV0):
+    def __init__(self, opener):
         self.mapcache = None
         self.listcache = None
-        revlog.__init__(self, opener, "00manifest.i", "00manifest.d",
-                        defversion)
+        revlog.__init__(self, opener, "00manifest.i")
 
     def parselines(self, lines):
         for l in lines.splitlines(1):
@@ -108,7 +105,7 @@ class manifest(revlog):
 
     def find(self, node, f):
         '''look up entry for a single file efficiently.
-        return (node, flag) pair if found, (None, None) if not.'''
+        return (node, flags) pair if found, (None, None) if not.'''
         if self.mapcache and node == self.mapcache[0]:
             return self.mapcache[1].get(f), self.mapcache[1].flags(f)
         text = self.revision(node)
@@ -117,7 +114,7 @@ class manifest(revlog):
             return None, None
         l = text[start:end]
         f, n = l.split('\0')
-        return bin(n[:40]), n[40:-1] == 'x'
+        return bin(n[:40]), n[40:-1]
 
     def add(self, map, transaction, link, p1=None, p2=None,
             changed=None):
--- a/mercurial/mdiff.py
+++ b/mercurial/mdiff.py
@@ -5,9 +5,7 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-from demandload import demandload
-import bdiff, mpatch
-demandload(globals(), "re struct util md5")
+import bdiff, mpatch, re, struct, util, md5
 
 def splitnewlines(text):
     '''like str.splitlines, but only split on newlines.'''
@@ -252,6 +250,10 @@ def patchtext(bin):
 def patch(a, bin):
     return mpatch.patches(a, [bin])
 
+# similar to difflib.SequenceMatcher.get_matching_blocks
+def get_matching_blocks(a, b):
+    return [(d[0], d[2], d[1] - d[0]) for d in bdiff.blocks(a, b)]
+
 patches = mpatch.patches
 patchedsize = mpatch.patchedsize
 textdiff = bdiff.bdiff
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -6,9 +6,8 @@
 # of the GNU General Public License, incorporated herein by reference.
 
 from node import *
-from i18n import gettext as _
-from demandload import *
-demandload(globals(), "errno util os tempfile context")
+from i18n import _
+import errno, util, os, tempfile, context
 
 def filemerge(repo, fw, fo, wctx, mctx):
     """perform a 3-way merge in the working directory
@@ -21,8 +20,9 @@ def filemerge(repo, fw, fo, wctx, mctx):
     def temp(prefix, ctx):
         pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
         (fd, name) = tempfile.mkstemp(prefix=pre)
+        data = repo.wwritedata(ctx.path(), ctx.data())
         f = os.fdopen(fd, "wb")
-        repo.wwrite(ctx.path(), ctx.data(), f)
+        f.write(data)
         f.close()
         return name
 
@@ -256,12 +256,17 @@ def manifestmerge(repo, p1, p2, pa, over
     copy = {}
 
     def fmerge(f, f2=None, fa=None):
-        """merge executable flags"""
+        """merge flags"""
         if not f2:
             f2 = f
             fa = f
         a, b, c = ma.execf(fa), m1.execf(f), m2.execf(f2)
-        return ((a^b) | (a^c)) ^ a
+        if ((a^b) | (a^c)) ^ a:
+            return 'x'
+        a, b, c = ma.linkf(fa), m1.linkf(f), m2.linkf(f2)
+        if ((a^b) | (a^c)) ^ a:
+            return 'l'
+        return ''
 
     def act(msg, m, f, *args):
         repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
@@ -286,21 +291,21 @@ def manifestmerge(repo, p1, p2, pa, over
                 # is remote's version newer?
                 # or are we going back in time and clean?
                 elif overwrite or m2[f] != a or (backwards and not n[20:]):
-                    act("remote is newer", "g", f, m2.execf(f))
+                    act("remote is newer", "g", f, m2.flags(f))
                 # local is newer, not overwrite, check mode bits
-                elif fmerge(f) != m1.execf(f):
-                    act("update permissions", "e", f, m2.execf(f))
+                elif fmerge(f) != m1.flags(f):
+                    act("update permissions", "e", f, m2.flags(f))
             # contents same, check mode bits
-            elif m1.execf(f) != m2.execf(f):
-                if overwrite or fmerge(f) != m1.execf(f):
-                    act("update permissions", "e", f, m2.execf(f))
+            elif m1.flags(f) != m2.flags(f):
+                if overwrite or fmerge(f) != m1.flags(f):
+                    act("update permissions", "e", f, m2.flags(f))
         elif f in copied:
             continue
         elif f in copy:
             f2 = copy[f]
             if f2 not in m2: # directory rename
                 act("remote renamed directory to " + f2, "d",
-                    f, None, f2, m1.execf(f))
+                    f, None, f2, m1.flags(f))
             elif f2 in m1: # case 2 A,B/B/B
                 act("local copied to " + f2, "m",
                     f, f2, f, fmerge(f, f2, f2), False)
@@ -331,7 +336,7 @@ def manifestmerge(repo, p1, p2, pa, over
             f2 = copy[f]
             if f2 not in m1: # directory rename
                 act("local renamed directory to " + f2, "d",
-                    None, f, f2, m2.execf(f))
+                    None, f, f2, m2.flags(f))
             elif f2 in m2: # rename case 1, A/A,B/A
                 act("remote copied to " + f, "m",
                     f2, f, f, fmerge(f2, f, f2), False)
@@ -340,14 +345,14 @@ def manifestmerge(repo, p1, p2, pa, over
                     f2, f, f, fmerge(f2, f, f2), True)
         elif f in ma:
             if overwrite or backwards:
-                act("recreating", "g", f, m2.execf(f))
+                act("recreating", "g", f, m2.flags(f))
             elif n != ma[f]:
                 if repo.ui.prompt(
                     (_("remote changed %s which local deleted\n") % f) +
                     _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("k"):
-                    act("prompt recreating", "g", f, m2.execf(f))
+                    act("prompt recreating", "g", f, m2.flags(f))
         else:
-            act("remote created", "g", f, m2.execf(f))
+            act("remote created", "g", f, m2.flags(f))
 
     return action
 
@@ -371,7 +376,7 @@ def applyupdates(repo, action, wctx, mct
                                  (f, inst.strerror))
             removed += 1
         elif m == "m": # merge
-            f2, fd, flag, move = a[2:]
+            f2, fd, flags, move = a[2:]
             r = filemerge(repo, f, f2, wctx, mctx)
             if r > 0:
                 unresolved += 1
@@ -382,35 +387,32 @@ def applyupdates(repo, action, wctx, mct
                     merged += 1
                 if f != fd:
                     repo.ui.debug(_("copying %s to %s\n") % (f, fd))
-                    repo.wwrite(fd, repo.wread(f))
+                    repo.wwrite(fd, repo.wread(f), flags)
                     if move:
                         repo.ui.debug(_("removing %s\n") % f)
                         os.unlink(repo.wjoin(f))
-            util.set_exec(repo.wjoin(fd), flag)
+            util.set_exec(repo.wjoin(fd), "x" in flags)
         elif m == "g": # get
-            flag = a[2]
+            flags = a[2]
             repo.ui.note(_("getting %s\n") % f)
             t = mctx.filectx(f).data()
-            repo.wwrite(f, t)
-            util.set_exec(repo.wjoin(f), flag)
+            repo.wwrite(f, t, flags)
             updated += 1
         elif m == "d": # directory rename
-            f2, fd, flag = a[2:]
+            f2, fd, flags = a[2:]
             if f:
                 repo.ui.note(_("moving %s to %s\n") % (f, fd))
                 t = wctx.filectx(f).data()
-                repo.wwrite(fd, t)
-                util.set_exec(repo.wjoin(fd), flag)
+                repo.wwrite(fd, t, flags)
                 util.unlink(repo.wjoin(f))
             if f2:
                 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
                 t = mctx.filectx(f2).data()
-                repo.wwrite(fd, t)
-                util.set_exec(repo.wjoin(fd), flag)
+                repo.wwrite(fd, t, flags)
             updated += 1
         elif m == "e": # exec
-            flag = a[2]
-            util.set_exec(repo.wjoin(f), flag)
+            flags = a[2]
+            util.set_exec(repo.wjoin(f), flags)
 
     return updated, merged, removed, unresolved
 
@@ -480,21 +482,32 @@ def update(repo, node, branchmerge, forc
     if not wlock:
         wlock = repo.wlock()
 
+    wc = repo.workingctx()
+    if node is None:
+        # tip of current branch
+        try:
+            node = repo.branchtags()[wc.branch()]
+        except KeyError:
+            raise util.Abort(_("branch %s not found") % wc.branch())
     overwrite = force and not branchmerge
     forcemerge = force and branchmerge
-    wc = repo.workingctx()
     pl = wc.parents()
     p1, p2 = pl[0], repo.changectx(node)
     pa = p1.ancestor(p2)
     fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
+    fastforward = False
 
     ### check phase
     if not overwrite and len(pl) > 1:
         raise util.Abort(_("outstanding uncommitted merges"))
     if pa == p1 or pa == p2: # is there a linear path from p1 to p2?
         if branchmerge:
-            raise util.Abort(_("there is nothing to merge, just use "
-                               "'hg update' or look at 'hg heads'"))
+            if p1.branch() != p2.branch():
+                fastforward = True
+                branchmerge = False
+            else:
+                raise util.Abort(_("there is nothing to merge, just use "
+                                   "'hg update' or look at 'hg heads'"))
     elif not (overwrite or branchmerge):
         raise util.Abort(_("update spans branches, use 'hg merge' "
                            "or 'hg update -C' to lose changes"))
@@ -523,7 +536,7 @@ def update(repo, node, branchmerge, forc
     if not partial:
         recordupdates(repo, action, branchmerge)
         repo.dirstate.setparents(fp1, fp2)
-        if not branchmerge:
+        if not branchmerge and not fastforward:
             repo.dirstate.setbranch(p2.branch())
         repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
 
--- a/mercurial/mpatch.c
+++ b/mercurial/mpatch.c
@@ -42,7 +42,11 @@ static uint32_t ntohl(uint32_t x)
 #else
 /* not windows */
 # include <sys/types.h>
-# include <arpa/inet.h>
+# ifdef __BEOS__
+#  include <ByteOrder.h>
+# else
+#  include <arpa/inet.h>
+# endif
 # include <inttypes.h>
 #endif
 
--- a/mercurial/node.py
+++ b/mercurial/node.py
@@ -7,8 +7,7 @@ This software may be used and distribute
 of the GNU General Public License, incorporated herein by reference.
 """
 
-from demandload import demandload
-demandload(globals(), "binascii")
+import binascii
 
 nullrev = -1
 nullid = "\0" * 20
deleted file mode 100644
--- a/mercurial/packagescan.py
+++ /dev/null
@@ -1,131 +0,0 @@
-# packagescan.py - Helper module for identifing used modules.
-# Used for the py2exe distutil.
-# This module must be the first mercurial module imported in setup.py
-#
-# Copyright 2005, 2006 Volker Kleinfeld <Volker.Kleinfeld@gmx.de>
-#
-# This software may be used and distributed according to the terms
-# of the GNU General Public License, incorporated herein by reference.
-import glob
-import os
-import sys
-import ihooks
-import types
-import string
-
-# Install this module as fake demandload module
-sys.modules['mercurial.demandload'] = sys.modules[__name__]
-
-# Requiredmodules contains the modules imported by demandload.
-# Please note that demandload can be invoked before the
-# mercurial.packagescan.scan method is invoked in case a mercurial
-# module is imported.
-requiredmodules = {}
-def demandload(scope, modules):
-    """ fake demandload function that collects the required modules
-        foo            import foo
-        foo bar        import foo, bar
-        foo.bar        import foo.bar
-        foo@bar        import foo as bar
-        foo:bar        from foo import bar
-        foo:bar,quux   from foo import bar, quux
-        foo.bar:quux   from foo.bar import quux"""
-
-    for m in modules.split():
-        mod = None
-        try:
-            module, fromlist = m.split(':')
-            fromlist = fromlist.split(',')
-        except:
-            module = m
-            fromlist = []
-        as_ = None
-        if '@' in module:
-            module, as_ = module.split('@')
-        mod = __import__(module, scope, scope, fromlist)
-        if fromlist == []:
-            # mod is only the top package, but we need all packages
-            comp = module.split('.')
-            i = 1
-            mn = comp[0]
-            while True:
-                # mn and mod.__name__ might not be the same
-                if not as_:
-                    as_ = mn
-                scope[as_] = mod
-                requiredmodules[mod.__name__] = 1
-                if len(comp) == i: break
-                mod = getattr(mod, comp[i])
-                mn = string.join(comp[:i+1],'.')
-                i += 1
-        else:
-            # mod is the last package in the component list
-            requiredmodules[mod.__name__] = 1
-            for f in fromlist:
-                scope[f] = getattr(mod, f)
-                if type(scope[f]) == types.ModuleType:
-                    requiredmodules[scope[f].__name__] = 1
-
-class SkipPackage(Exception):
-    def __init__(self, reason):
-        self.reason = reason
-
-scan_in_progress = False
-
-def scan(libpath, packagename):
-    """ helper for finding all required modules of package <packagename> """
-    global scan_in_progress
-    scan_in_progress = True
-    # Use the package in the build directory
-    libpath = os.path.abspath(libpath)
-    sys.path.insert(0, libpath)
-    packdir = os.path.join(libpath, packagename.replace('.', '/'))
-    # A normal import would not find the package in
-    # the build directory. ihook is used to force the import.
-    # After the package is imported the import scope for
-    # the following imports is settled.
-    p = importfrom(packdir)
-    globals()[packagename] = p
-    sys.modules[packagename] = p
-    # Fetch the python modules in the package
-    cwd = os.getcwd()
-    os.chdir(packdir)
-    pymodulefiles = glob.glob('*.py')
-    extmodulefiles = glob.glob('*.pyd')
-    os.chdir(cwd)
-    # Import all python modules and by that run the fake demandload
-    for m in pymodulefiles:
-        if m == '__init__.py': continue
-        tmp = {}
-        mname, ext = os.path.splitext(m)
-        fullname = packagename+'.'+mname
-        try:
-            __import__(fullname, tmp, tmp)
-        except SkipPackage, inst:
-            print >> sys.stderr, 'skipping %s: %s' % (fullname, inst.reason)
-            continue
-        requiredmodules[fullname] = 1
-    # Import all extension modules and by that run the fake demandload
-    for m in extmodulefiles:
-        tmp = {}
-        mname, ext = os.path.splitext(m)
-        fullname = packagename+'.'+mname
-        __import__(fullname, tmp, tmp)
-        requiredmodules[fullname] = 1
-
-def getmodules():
-    return requiredmodules.keys()
-
-def importfrom(filename):
-    """
-    import module/package from a named file and returns the module.
-    It does not check on sys.modules or includes the module in the scope.
-    """
-    loader = ihooks.BasicModuleLoader()
-    path, file = os.path.split(filename)
-    name, ext  = os.path.splitext(file)
-    m = loader.find_module_in_dir(name, path)
-    if not m:
-        raise ImportError, name
-    m = loader.load_module(name, m)
-    return m
--- a/mercurial/patch.py
+++ b/mercurial/patch.py
@@ -5,12 +5,11 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-from demandload import demandload
-from i18n import gettext as _
+from i18n import _
 from node import *
-demandload(globals(), "base85 cmdutil mdiff util")
-demandload(globals(), "cStringIO email.Parser errno os popen2 re shutil sha")
-demandload(globals(), "sys tempfile zlib")
+import base85, cmdutil, mdiff, util, context, revlog
+import cStringIO, email.Parser, os, popen2, re, sha
+import sys, tempfile, zlib
 
 # helper functions
 
@@ -34,11 +33,11 @@ def copyfile(src, dst, basedir=None):
 def extract(ui, fileobj):
     '''extract patch from data read from fileobj.
 
-    patch can be normal patch or contained in email message.
+    patch can be a normal patch or contained in an email message.
 
-    return tuple (filename, message, user, date). any item in returned
-    tuple can be None.  if filename is None, fileobj did not contain
-    patch. caller must unlink filename when done.'''
+    return tuple (filename, message, user, date, node, p1, p2).
+    Any item in the returned tuple can be None. If filename is None,
+    fileobj did not contain a patch. Caller must unlink filename when done.'''
 
     # attempt to detect the start of a patch
     # (this heuristic is borrowed from quilt)
@@ -49,16 +48,21 @@ def extract(ui, fileobj):
     fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
     tmpfp = os.fdopen(fd, 'w')
     try:
-        hgpatch = False
-
         msg = email.Parser.Parser().parse(fileobj)
 
         message = msg['Subject']
         user = msg['From']
         # should try to parse msg['Date']
         date = None
+        nodeid = None
+        branch = None
+        parents = []
 
         if message:
+            if message.startswith('[PATCH'):
+                pend = message.find(']')
+                if pend >= 0:
+                    message = message[pend+1:].lstrip()
             message = message.replace('\n\t', ' ')
             ui.debug('Subject: %s\n' % message)
         if user:
@@ -74,6 +78,9 @@ def extract(ui, fileobj):
             payload = part.get_payload(decode=True)
             m = diffre.search(payload)
             if m:
+                hgpatch = False
+                ignoretext = False
+
                 ui.debug(_('found patch at byte %d\n') % m.start(0))
                 diffs_seen += 1
                 cfp = cStringIO.StringIO()
@@ -93,7 +100,15 @@ def extract(ui, fileobj):
                             ui.debug('From: %s\n' % user)
                         elif line.startswith("# Date "):
                             date = line[7:]
-                    if not line.startswith('# '):
+                        elif line.startswith("# Branch "):
+                            branch = line[9:]
+                        elif line.startswith("# Node ID "):
+                            nodeid = line[10:]
+                        elif line.startswith("# Parent "):
+                            parents.append(line[10:])
+                    elif line == '---' and 'git-send-email' in msg['X-Mailer']:
+                        ignoretext = True
+                    if not line.startswith('# ') and not ignoretext:
                         cfp.write(line)
                         cfp.write('\n')
                 message = cfp.getvalue()
@@ -111,8 +126,10 @@ def extract(ui, fileobj):
     tmpfp.close()
     if not diffs_seen:
         os.unlink(tmpname)
-        return None, message, user, date
-    return tmpname, message, user, date
+        return None, message, user, date, branch, None, None, None
+    p1 = parents and parents.pop(0) or None
+    p2 = parents and parents.pop(0) or None
+    return tmpname, message, user, date, branch, nodeid, p1, p2
 
 GP_PATCH  = 1 << 0  # we have to run patch
 GP_FILTER = 1 << 1  # there's some copy/rename operation
@@ -279,9 +296,14 @@ def patch(patchname, ui, strip=1, cwd=No
         """patch and updates the files and fuzz variables"""
         fuzz = False
 
-        patcher = util.find_in_path('gpatch', os.environ.get('PATH', ''),
-                                    'patch')
         args = []
+        patcher = ui.config('ui', 'patch')
+        if not patcher:
+            patcher = util.find_in_path('gpatch', os.environ.get('PATH', ''),
+                                        'patch')
+            if util.needbinarypatch():
+                args.append('--binary')
+                                    
         if cwd:
             args.append('-d %s' % util.shellquote(cwd))
         fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
@@ -378,8 +400,9 @@ def updatedir(ui, repo, patches, wlock=N
             dst = os.path.join(repo.root, gp.path)
             # patch won't create empty files
             if ctype == 'ADD' and not os.path.exists(dst):
-                repo.wwrite(gp.path, '')
-            util.set_exec(dst, x)
+                repo.wwrite(gp.path, '', x and 'x' or '')
+            else:
+                util.set_exec(dst, x)
     cmdutil.addremove(repo, cfiles, wlock=wlock)
     files = patches.keys()
     files.extend([r for r in removes if r not in files])
@@ -441,107 +464,65 @@ def diff(repo, node1=None, node2=None, f
     if not node1:
         node1 = repo.dirstate.parents()[0]
 
-    clcache = {}
-    def getchangelog(n):
-        if n not in clcache:
-            clcache[n] = repo.changelog.read(n)
-        return clcache[n]
-    mcache = {}
-    def getmanifest(n):
-        if n not in mcache:
-            mcache[n] = repo.manifest.read(n)
-        return mcache[n]
-    fcache = {}
-    def getfile(f):
-        if f not in fcache:
-            fcache[f] = repo.file(f)
-        return fcache[f]
+    ccache = {}
+    def getctx(r):
+        if r not in ccache:
+            ccache[r] = context.changectx(repo, r)
+        return ccache[r]
+
+    flcache = {}
+    def getfilectx(f, ctx):
+        flctx = ctx.filectx(f, filelog=flcache.get(f))
+        if f not in flcache:
+            flcache[f] = flctx._filelog
+        return flctx
 
     # reading the data for node1 early allows it to play nicely
     # with repo.status and the revlog cache.
-    change = getchangelog(node1)
-    mmap = getmanifest(change[0])
-    date1 = util.datestr(change[2])
+    ctx1 = context.changectx(repo, node1)
+    # force manifest reading
+    man1 = ctx1.manifest()
+    date1 = util.datestr(ctx1.date())
 
     if not changes:
         changes = repo.status(node1, node2, files, match=match)[:5]
     modified, added, removed, deleted, unknown = changes
-    if files:
-        def filterfiles(filters):
-            l = [x for x in filters if x in files]
-
-            for t in files:
-                if not t.endswith("/"):
-                    t += "/"
-                l += [x for x in filters if x.startswith(t)]
-            return l
-
-        modified, added, removed = map(filterfiles, (modified, added, removed))
 
     if not modified and not added and not removed:
         return
 
-    # returns False if there was no rename between n1 and n2
-    # returns None if the file was created between n1 and n2
-    # returns the (file, node) present in n1 that was renamed to f in n2
-    def renamedbetween(f, n1, n2):
-        r1, r2 = map(repo.changelog.rev, (n1, n2))
+    if node2:
+        ctx2 = context.changectx(repo, node2)
+    else:
+        ctx2 = context.workingctx(repo)
+    man2 = ctx2.manifest()
+
+    # returns False if there was no rename between ctx1 and ctx2
+    # returns None if the file was created between ctx1 and ctx2
+    # returns the (file, node) present in ctx1 that was renamed to f in ctx2
+    def renamed(f):
+        startrev = ctx1.rev()
+        c = ctx2
+        crev = c.rev()
+        if crev is None:
+            crev = repo.changelog.count()
         orig = f
-        src = None
-        while r2 > r1:
-            cl = getchangelog(n2)
-            if f in cl[3]:
-                m = getmanifest(cl[0])
+        while crev > startrev:
+            if f in c.files():
                 try:
-                    src = getfile(f).renamed(m[f])
-                except KeyError:
+                    src = getfilectx(f, c).renamed()
+                except revlog.LookupError:
                     return None
                 if src:
                     f = src[0]
-            n2 = repo.changelog.parents(n2)[0]
-            r2 = repo.changelog.rev(n2)
-        cl = getchangelog(n1)
-        m = getmanifest(cl[0])
-        if f not in m:
+            crev = c.parents()[0].rev()
+            # try to reuse
+            c = getctx(crev)
+        if f not in man1:
             return None
         if f == orig:
             return False
-        return f, m[f]
-
-    if node2:
-        change = getchangelog(node2)
-        mmap2 = getmanifest(change[0])
-        _date2 = util.datestr(change[2])
-        def date2(f):
-            return _date2
-        def read(f):
-            return getfile(f).read(mmap2[f])
-        def renamed(f):
-            return renamedbetween(f, node1, node2)
-    else:
-        tz = util.makedate()[1]
-        _date2 = util.datestr()
-        def date2(f):
-            try:
-                return util.datestr((os.lstat(repo.wjoin(f)).st_mtime, tz))
-            except OSError, err:
-                if err.errno != errno.ENOENT: raise
-                return _date2
-        def read(f):
-            return repo.wread(f)
-        def renamed(f):
-            src = repo.dirstate.copied(f)
-            parent = repo.dirstate.parents()[0]
-            if src:
-                f = src
-            of = renamedbetween(f, node1, parent)
-            if of or of is None:
-                return of
-            elif src:
-                cl = getchangelog(parent)[0]
-                return (src, getmanifest(cl)[src])
-            else:
-                return None
+        return f
 
     if repo.ui.quiet:
         r = None
@@ -555,20 +536,21 @@ def diff(repo, node1=None, node2=None, f
             src = renamed(f)
             if src:
                 copied[f] = src
-        srcs = [x[1][0] for x in copied.items()]
+        srcs = [x[1] for x in copied.items()]
 
     all = modified + added + removed
     all.sort()
     gone = {}
+
     for f in all:
         to = None
         tn = None
         dodiff = True
         header = []
-        if f in mmap:
-            to = getfile(f).read(mmap[f])
+        if f in man1:
+            to = getfilectx(f, ctx1).data()
         if f not in removed:
-            tn = read(f)
+            tn = getfilectx(f, ctx2).data()
         if opts.git:
             def gitmode(x):
                 return x and '100755' or '100644'
@@ -579,13 +561,10 @@ def diff(repo, node1=None, node2=None, f
 
             a, b = f, f
             if f in added:
-                if node2:
-                    mode = gitmode(mmap2.execf(f))
-                else:
-                    mode = gitmode(util.is_exec(repo.wjoin(f), None))
+                mode = gitmode(man2.execf(f))
                 if f in copied:
-                    a, arev = copied[f]
-                    omode = gitmode(mmap.execf(a))
+                    a = copied[f]
+                    omode = gitmode(man1.execf(a))
                     addmodehdr(header, omode, mode)
                     if a in removed and a not in gone:
                         op = 'rename'
@@ -594,7 +573,7 @@ def diff(repo, node1=None, node2=None, f
                         op = 'copy'
                     header.append('%s from %s\n' % (op, a))
                     header.append('%s to %s\n' % (op, f))
-                    to = getfile(a).read(arev)
+                    to = getfilectx(a, ctx1).data()
                 else:
                     header.append('new file mode %s\n' % mode)
                 if util.binary(tn):
@@ -603,14 +582,11 @@ def diff(repo, node1=None, node2=None, f
                 if f in srcs:
                     dodiff = False
                 else:
-                    mode = gitmode(mmap.execf(f))
+                    mode = gitmode(man1.execf(f))
                     header.append('deleted file mode %s\n' % mode)
             else:
-                omode = gitmode(mmap.execf(f))
-                if node2:
-                    nmode = gitmode(mmap2.execf(f))
-                else:
-                    nmode = gitmode(util.is_exec(repo.wjoin(f), mmap.execf(f)))
+                omode = gitmode(man1.execf(f))
+                nmode = gitmode(man2.execf(f))
                 addmodehdr(header, omode, nmode)
                 if util.binary(to) or util.binary(tn):
                     dodiff = 'binary'
@@ -620,7 +596,10 @@ def diff(repo, node1=None, node2=None, f
             if dodiff == 'binary':
                 text = b85diff(fp, to, tn)
             else:
-                text = mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts)
+                text = mdiff.unidiff(to, date1,
+                                    # ctx2 date may be dynamic
+                                    tn, util.datestr(ctx2.date()),
+                                    f, r, opts=opts)
             if text or len(header) > 1:
                 fp.write(''.join(header))
             fp.write(text)
@@ -632,27 +611,31 @@ def export(repo, revs, template='hg-%h.p
     total = len(revs)
     revwidth = max([len(str(rev)) for rev in revs])
 
-    def single(node, seqno, fp):
-        parents = [p for p in repo.changelog.parents(node) if p != nullid]
+    def single(rev, seqno, fp):
+        ctx = repo.changectx(rev)
+        node = ctx.node()
+        parents = [p.node() for p in ctx.parents() if p]
+        branch = ctx.branch()
         if switch_parent:
             parents.reverse()
         prev = (parents and parents[0]) or nullid
-        change = repo.changelog.read(node)
 
         if not fp:
             fp = cmdutil.make_file(repo, template, node, total=total,
                                    seqno=seqno, revwidth=revwidth)
-        if fp not in (sys.stdout, repo.ui):
+        if fp != sys.stdout and hasattr(fp, 'name'):
             repo.ui.note("%s\n" % fp.name)
 
         fp.write("# HG changeset patch\n")
-        fp.write("# User %s\n" % change[1])
-        fp.write("# Date %d %d\n" % change[2])
+        fp.write("# User %s\n" % ctx.user())
+        fp.write("# Date %d %d\n" % ctx.date())
+        if branch and (branch != 'default'):
+            fp.write("# Branch %s\n" % branch)
         fp.write("# Node ID %s\n" % hex(node))
         fp.write("# Parent  %s\n" % hex(prev))
         if len(parents) > 1:
             fp.write("# Parent  %s\n" % hex(parents[1]))
-        fp.write(change[4].rstrip())
+        fp.write(ctx.description().rstrip())
         fp.write("\n\n")
 
         diff(repo, prev, node, fp=fp, opts=opts)
@@ -660,7 +643,7 @@ def export(repo, revs, template='hg-%h.p
             fp.close()
 
     for seqno, rev in enumerate(revs):
-        single(repo.lookup(rev), seqno+1, fp)
+        single(rev, seqno+1, fp)
 
 def diffstat(patchlines):
     if not util.find_in_path('diffstat', os.environ.get('PATH', '')):
--- a/mercurial/repo.py
+++ b/mercurial/repo.py
@@ -9,9 +9,6 @@
 class RepoError(Exception):
     pass
 
-class LookupError(RepoError):
-    pass
-
 class repository(object):
     def capable(self, name):
         '''tell whether repo supports named capability.
--- a/mercurial/revlog.py
+++ b/mercurial/revlog.py
@@ -11,10 +11,9 @@ of the GNU General Public License, incor
 """
 
 from node import *
-from i18n import gettext as _
-from demandload import demandload
-demandload(globals(), "binascii changegroup errno ancestor mdiff os")
-demandload(globals(), "sha struct util zlib")
+from i18n import _
+import binascii, changegroup, errno, ancestor, mdiff, os
+import sha, struct, util, zlib
 
 # revlog version strings
 REVLOGV0 = 0
@@ -27,11 +26,6 @@ REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDAT
 REVLOG_DEFAULT_FORMAT = REVLOGNG
 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
 
-def flagstr(flag):
-    if flag == "inline":
-        return REVLOGNGINLINEDATA
-    raise RevlogError(_("unknown revlog flag %s") % flag)
-
 def hash(text, p1, p2):
     """generate a hash from the given text and its parent hashes
 
@@ -147,6 +141,9 @@ class lazyparser(object):
         lend = len(data) / self.s
         i = blockstart / self.s
         off = 0
+        # lazyindex supports __delitem__
+        if lend > len(self.index) - i:
+            lend = len(self.index) - i
         for x in xrange(lend):
             if self.index[i + x] == None:
                 b = data[off : off + self.s]
@@ -282,6 +279,7 @@ class lazymap(object):
         del self.p.map[key]
 
 class RevlogError(Exception): pass
+class LookupError(RevlogError): pass
 
 class revlog(object):
     """
@@ -308,8 +306,7 @@ class revlog(object):
     remove data, and can use some simple techniques to avoid the need
     for locking while reading.
     """
-    def __init__(self, opener, indexfile, datafile,
-                 defversion=REVLOG_DEFAULT_VERSION):
+    def __init__(self, opener, indexfile):
         """
         create a revlog object
 
@@ -317,13 +314,17 @@ class revlog(object):
         and can be used to implement COW semantics or the like.
         """
         self.indexfile = indexfile
-        self.datafile = datafile
+        self.datafile = indexfile[:-2] + ".d"
         self.opener = opener
 
         self.indexstat = None
         self.cache = None
         self.chunkcache = None
-        self.defversion = defversion
+        self.defversion = REVLOG_DEFAULT_VERSION
+        if hasattr(opener, "defversion"):
+            self.defversion = opener.defversion
+            if self.defversion & REVLOGNG:
+                self.defversion |= REVLOGNGINLINEDATA
         self.load()
 
     def load(self):
@@ -475,7 +476,7 @@ class revlog(object):
         try:
             return self.nodemap[node]
         except KeyError:
-            raise RevlogError(_('%s: no node %s') % (self.indexfile, hex(node)))
+            raise LookupError(_('%s: no node %s') % (self.indexfile, hex(node)))
     def linkrev(self, node):
         return (node == nullid) and nullrev or self.index[self.rev(node)][-4]
     def parents(self, node):
@@ -770,7 +771,7 @@ class revlog(object):
                 node = id
                 r = self.rev(node) # quick search the index
                 return node
-            except RevlogError:
+            except LookupError:
                 pass # may be partial hex id
         try:
             # str(rev)
@@ -799,7 +800,7 @@ class revlog(object):
                 for n in self.nodemap:
                     if n.startswith(bin_id) and hex(n).startswith(id):
                         if node is not None:
-                            raise RevlogError(_("Ambiguous identifier"))
+                            raise LookupError(_("Ambiguous identifier"))
                         node = n
                 if node is not None:
                     return node
@@ -819,7 +820,7 @@ class revlog(object):
         if n:
             return n
 
-        raise RevlogError(_("No match found"))
+        raise LookupError(_("No match found"))
 
     def cmp(self, node, text):
         """compare text with a given file revision"""
@@ -1159,13 +1160,13 @@ class revlog(object):
 
             for p in (p1, p2):
                 if not p in self.nodemap:
-                    raise RevlogError(_("unknown parent %s") % short(p))
+                    raise LookupError(_("unknown parent %s") % short(p))
 
             if not chain:
                 # retrieve the parent revision of the delta chain
                 chain = p1
                 if not chain in self.nodemap:
-                    raise RevlogError(_("unknown base %s") % short(chain[:4]))
+                    raise LookupError(_("unknown base %s") % short(chain[:4]))
 
             # full versions are inserted when the needed deltas become
             # comparable to the uncompressed text or when the previous
--- a/mercurial/sshrepo.py
+++ b/mercurial/sshrepo.py
@@ -7,9 +7,8 @@
 
 from node import *
 from remoterepo import *
-from i18n import gettext as _
-from demandload import *
-demandload(globals(), "hg os re stat util")
+from i18n import _
+import hg, os, re, stat, util
 
 class sshrepository(remoterepository):
     def __init__(self, ui, path, create=0):
--- a/mercurial/sshserver.py
+++ b/mercurial/sshserver.py
@@ -6,10 +6,9 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-from demandload import demandload
-from i18n import gettext as _
+from i18n import _
 from node import *
-demandload(globals(), "os streamclone sys tempfile util")
+import os, streamclone, sys, tempfile, util
 
 class sshserver(object):
     def __init__(self, ui, repo):
@@ -74,7 +73,7 @@ class sshserver(object):
 
         caps = ['unbundle', 'lookup', 'changegroupsubset']
         if self.ui.configbool('server', 'uncompressed'):
-            caps.append('stream=%d' % self.repo.revlogversion)
+            caps.append('stream=%d' % self.repo.changelog.version)
         self.respond("capabilities: %s\n" % (' '.join(caps),))
 
     def do_lock(self):
--- a/mercurial/statichttprepo.py
+++ b/mercurial/statichttprepo.py
@@ -7,10 +7,9 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-from demandload import *
-from i18n import gettext as _
-demandload(globals(), "changelog filelog httprangereader")
-demandload(globals(), "repo localrepo manifest os urllib urllib2 util")
+from i18n import _
+import changelog, filelog, httprangereader
+import repo, localrepo, manifest, os, urllib, urllib2, util
 
 class rangereader(httprangereader.httprangereader):
     def read(self, size=None):
@@ -33,7 +32,6 @@ class statichttprepository(localrepo.loc
     def __init__(self, ui, path):
         self._url = path
         self.ui = ui
-        self.revlogversion = 0
 
         self.path = (path + "/.hg")
         self.opener = opener(self.path)
--- a/mercurial/streamclone.py
+++ b/mercurial/streamclone.py
@@ -5,9 +5,8 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-from demandload import demandload
-from i18n import gettext as _
-demandload(globals(), "os stat util lock")
+from i18n import _
+import os, stat, util, lock
 
 # if server supports streaming clone, it advertises "stream"
 # capability with value that is version+flags of repo it is serving.
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -5,10 +5,9 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-from demandload import demandload
-from i18n import gettext as _
+from i18n import _
 from node import *
-demandload(globals(), "cgi re sys os time urllib util textwrap")
+import cgi, re, sys, os, time, urllib, util, textwrap
 
 def parsestring(s, quoted=True):
     '''parse a string using simple c-like syntax.
--- a/mercurial/transaction.py
+++ b/mercurial/transaction.py
@@ -11,9 +11,8 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-from demandload import demandload
-from i18n import gettext as _
-demandload(globals(), 'os')
+from i18n import _
+import os
 
 class transaction(object):
     def __init__(self, report, opener, journal, after=None):
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -5,10 +5,9 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-from i18n import gettext as _
-from demandload import *
-demandload(globals(), "errno getpass os re socket sys tempfile")
-demandload(globals(), "ConfigParser traceback util")
+from i18n import _
+import errno, getpass, os, re, socket, sys, tempfile
+import ConfigParser, traceback, util
 
 def dupconfig(orig):
     new = util.configparser(orig.defaults())
@@ -310,7 +309,7 @@ class ui(object):
         sections.sort()
         for section in sections:
             for name, value in self.configitems(section, untrusted):
-                yield section, name, value.replace('\n', '\\n')
+                yield section, name, str(value).replace('\n', '\\n')
 
     def extensions(self):
         result = self.configitems("extensions")
@@ -326,12 +325,6 @@ class ui(object):
                 result.append(os.path.expanduser(value))
         return result
 
-    def configrevlog(self):
-        result = {}
-        for key, value in self.configitems("revlog"):
-            result[key.lower()] = value
-        return result
-
     def username(self):
         """Return default username to be used in commits.
 
@@ -388,6 +381,9 @@ class ui(object):
             if not sys.stdout.closed: sys.stdout.flush()
             for a in args:
                 sys.stderr.write(str(a))
+            # stderr may be buffered under win32 when redirected to files,
+            # including stdout.
+            if not sys.stderr.closed: sys.stderr.flush()
         except IOError, inst:
             if inst.errno != errno.EPIPE:
                 raise
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -12,10 +12,9 @@ This contains helper routines that are i
 platform-specific details from the core.
 """
 
-from i18n import gettext as _
-from demandload import *
-demandload(globals(), "cStringIO errno getpass popen2 re shutil sys tempfile")
-demandload(globals(), "os threading time calendar ConfigParser locale glob")
+from i18n import _
+import cStringIO, errno, getpass, popen2, re, shutil, sys, tempfile
+import os, threading, time, calendar, ConfigParser, locale, glob
 
 try:
     _encoding = os.environ.get("HGENCODING") or locale.getpreferredencoding() \
@@ -117,11 +116,23 @@ extendeddateformats = defaultdateformats
 class SignalInterrupt(Exception):
     """Exception raised on SIGTERM and SIGHUP."""
 
-# like SafeConfigParser but with case-sensitive keys
+# differences from SafeConfigParser:
+# - case-sensitive keys
+# - allows values that are not strings (this means that you may not
+#   be able to save the configuration to a file)
 class configparser(ConfigParser.SafeConfigParser):
     def optionxform(self, optionstr):
         return optionstr
 
+    def set(self, section, option, value):
+        return ConfigParser.ConfigParser.set(self, section, option, value)
+
+    def _interpolate(self, section, option, rawval, vars):
+        if not isinstance(rawval, basestring):
+            return rawval
+        return ConfigParser.SafeConfigParser._interpolate(self, section,
+                                                          option, rawval, vars)
+
 def cachefunc(func):
     '''cache the result of function calls'''
     # XXX doesn't handle keywords args
@@ -200,18 +211,6 @@ def filter(s, cmd):
             return fn(s, cmd[len(name):].lstrip())
     return pipefilter(s, cmd)
 
-def find_in_path(name, path, default=None):
-    '''find name in search path. path can be string (will be split
-    with os.pathsep), or iterable thing that returns strings.  if name
-    found, return path to name. else return default.'''
-    if isinstance(path, str):
-        path = path.split(os.pathsep)
-    for p in path:
-        p_name = os.path.join(p, name)
-        if os.path.exists(p_name):
-            return p_name
-    return default
-
 def binary(s):
     """return true if a string is binary data using diff's heuristic"""
     if s and '\0' in s[:4096]:
@@ -387,16 +386,17 @@ def canonpath(root, cwd, myname):
 
         raise Abort('%s not under root' % myname)
 
-def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
-    return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
+def matcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None):
+    return _matcher(canonroot, cwd, names, inc, exc, 'glob', src)
 
-def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='',
-               src=None, globbed=False):
-    if not globbed:
+def cmdmatcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None,
+               globbed=False, default=None):
+    default = default or 'relpath'
+    if default == 'relpath' and not globbed:
         names = expand_glob(names)
-    return _matcher(canonroot, cwd, names, inc, exc, head, 'relpath', src)
+    return _matcher(canonroot, cwd, names, inc, exc, default, src)
 
-def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
+def _matcher(canonroot, cwd, names, inc, exc, dflt_pat, src):
     """build a function to match a set of file patterns
 
     arguments:
@@ -405,26 +405,30 @@ def _matcher(canonroot, cwd, names, inc,
     names - patterns to find
     inc - patterns to include
     exc - patterns to exclude
-    head - a regex to prepend to patterns to control whether a match is rooted
+    dflt_pat - if a pattern in names has no explicit type, assume this one
+    src - where these patterns came from (e.g. .hgignore)
 
     a pattern is one of:
-    'glob:<rooted glob>'
-    're:<rooted regexp>'
-    'path:<rooted path>'
-    'relglob:<relative glob>'
-    'relpath:<relative path>'
-    'relre:<relative regexp>'
-    '<rooted path or regexp>'
+    'glob:<glob>' - a glob relative to cwd
+    're:<regexp>' - a regular expression
+    'path:<path>' - a path relative to canonroot
+    'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
+    'relpath:<path>' - a path relative to cwd
+    'relre:<regexp>' - a regexp that doesn't have to match the start of a name
+    '<something>' - one of the cases above, selected by the dflt_pat argument
 
     returns:
     a 3-tuple containing
-    - list of explicit non-pattern names passed in
+    - list of roots (places where one should start a recursive walk of the fs);
+      this often matches the explicit non-pattern names passed in, but also
+      includes the initial part of glob: patterns that has no glob characters
     - a bool match(filename) function
     - a bool indicating if any patterns were passed in
+    """
 
-    todo:
-    make head regex a rooted bool
-    """
+    # a common case: no patterns at all
+    if not names and not inc and not exc:
+        return [], always, False
 
     def contains_glob(name):
         for c in name:
@@ -433,84 +437,92 @@ def _matcher(canonroot, cwd, names, inc,
 
     def regex(kind, name, tail):
         '''convert a pattern into a regular expression'''
+        if not name:
+            return ''
         if kind == 're':
             return name
         elif kind == 'path':
             return '^' + re.escape(name) + '(?:/|$)'
         elif kind == 'relglob':
-            return head + globre(name, '(?:|.*/)', tail)
+            return globre(name, '(?:|.*/)', tail)
         elif kind == 'relpath':
-            return head + re.escape(name) + tail
+            return re.escape(name) + '(?:/|$)'
         elif kind == 'relre':
             if name.startswith('^'):
                 return name
             return '.*' + name
-        return head + globre(name, '', tail)
+        return globre(name, '', tail)
 
     def matchfn(pats, tail):
         """build a matching function from a set of patterns"""
         if not pats:
             return
-        matches = []
-        for k, p in pats:
-            try:
-                pat = '(?:%s)' % regex(k, p, tail)
-                matches.append(re.compile(pat).match)
-            except re.error:
-                if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
-                else: raise Abort("invalid pattern (%s): %s" % (k, p))
-
-        def buildfn(text):
-            for m in matches:
-                r = m(text)
-                if r:
-                    return r
-
-        return buildfn
+        try:
+            pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
+            return re.compile(pat).match
+        except re.error:
+            for k, p in pats:
+                try:
+                    re.compile('(?:%s)' % regex(k, p, tail))
+                except re.error:
+                    if src:
+                        raise Abort("%s: invalid pattern (%s): %s" %
+                                    (src, k, p))
+                    else:
+                        raise Abort("invalid pattern (%s): %s" % (k, p))
+            raise Abort("invalid pattern")
 
     def globprefix(pat):
         '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
         root = []
-        for p in pat.split(os.sep):
+        for p in pat.split('/'):
             if contains_glob(p): break
             root.append(p)
-        return '/'.join(root)
+        return '/'.join(root) or '.'
+
+    def normalizepats(names, default):
+        pats = []
+        roots = []
+        anypats = False
+        for kind, name in [patkind(p, default) for p in names]:
+            if kind in ('glob', 'relpath'):
+                name = canonpath(canonroot, cwd, name)
+            elif kind in ('relglob', 'path'):
+                name = normpath(name)
+
+            pats.append((kind, name))
 
-    pats = []
-    files = []
-    roots = []
-    for kind, name in [patkind(p, dflt_pat) for p in names]:
-        if kind in ('glob', 'relpath'):
-            name = canonpath(canonroot, cwd, name)
-            if name == '':
-                kind, name = 'glob', '**'
-        if kind in ('glob', 'path', 're'):
-            pats.append((kind, name))
-        if kind == 'glob':
-            root = globprefix(name)
-            if root: roots.append(root)
-        elif kind == 'relpath':
-            files.append((kind, name))
-            roots.append(name)
+            if kind in ('glob', 're', 'relglob', 'relre'):
+                anypats = True
+
+            if kind == 'glob':
+                root = globprefix(name)
+                roots.append(root)
+            elif kind in ('relpath', 'path'):
+                roots.append(name or '.')
+            elif kind == 'relglob':
+                roots.append('.')
+        return roots, pats, anypats
+
+    roots, pats, anypats = normalizepats(names, dflt_pat)
 
     patmatch = matchfn(pats, '$') or always
-    filematch = matchfn(files, '(?:/|$)') or always
     incmatch = always
     if inc:
-        inckinds = [patkind(canonpath(canonroot, cwd, i)) for i in inc]
+        dummy, inckinds, dummy = normalizepats(inc, 'glob')
         incmatch = matchfn(inckinds, '(?:/|$)')
     excmatch = lambda fn: False
     if exc:
-        exckinds = [patkind(canonpath(canonroot, cwd, x)) for x in exc]
+        dummy, exckinds, dummy = normalizepats(exc, 'glob')
         excmatch = matchfn(exckinds, '(?:/|$)')
 
-    return (roots,
-            lambda fn: (incmatch(fn) and not excmatch(fn) and
-                        (fn.endswith('/') or
-                         (not pats and not files) or
-                         (pats and patmatch(fn)) or
-                         (files and filematch(fn)))),
-            (inc or exc or (pats and pats != [('glob', '**')])) and True)
+    if not names and inc and not exc:
+        # common case: hgignore patterns
+        match = incmatch
+    else:
+        match = lambda fn: incmatch(fn) and not excmatch(fn) and patmatch(fn)
+
+    return (roots, match, (inc or exc or anypats) and True)
 
 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
     '''enhanced shell command execution.
@@ -559,6 +571,15 @@ def system(cmd, environ={}, cwd=None, on
         if cwd is not None and oldcwd != cwd:
             os.chdir(oldcwd)
 
+# os.path.lexists is not available on python2.3
+def lexists(filename):
+    "test whether a file with this name exists. does not follow symlinks"
+    try:
+        os.lstat(filename)
+    except:
+        return False
+    return True
+
 def rename(src, dst):
     """forcibly rename a file"""
     try:
@@ -588,11 +609,18 @@ def unlink(f):
 
 def copyfile(src, dest):
     "copy a file, preserving mode"
-    try:
-        shutil.copyfile(src, dest)
-        shutil.copymode(src, dest)
-    except shutil.Error, inst:
-        raise Abort(str(inst))
+    if os.path.islink(src):
+        try:
+            os.unlink(dest)
+        except:
+            pass
+        os.symlink(os.readlink(src), dest)
+    else:
+        try:
+            shutil.copyfile(src, dest)
+            shutil.copymode(src, dest)
+        except shutil.Error, inst:
+            raise Abort(str(inst))
 
 def copyfiles(src, dst, hardlink=None):
     """Copy a directory tree using hardlinks if possible"""
@@ -724,12 +752,54 @@ def checkfolding(path):
     except:
         return True
 
+def checkexec(path):
+    """
+    Check whether the given path is on a filesystem with UNIX-like exec flags
+
+    Requires a directory (like /foo/.hg)
+    """
+    fh, fn = tempfile.mkstemp("", "", path)
+    os.close(fh)
+    m = os.stat(fn).st_mode
+    os.chmod(fn, m ^ 0111)
+    r = (os.stat(fn).st_mode != m)
+    os.unlink(fn)
+    return r
+
+def execfunc(path, fallback):
+    '''return an is_exec() function with default to fallback'''
+    if checkexec(path):
+        return lambda x: is_exec(os.path.join(path, x))
+    return fallback
+
+def checklink(path):
+    """check whether the given path is on a symlink-capable filesystem"""
+    # mktemp is not racy because symlink creation will fail if the
+    # file already exists
+    name = tempfile.mktemp(dir=path)
+    try:
+        os.symlink(".", name)
+        os.unlink(name)
+        return True
+    except (OSError, AttributeError):
+        return False
+
+def linkfunc(path, fallback):
+    '''return an is_link() function with default to fallback'''
+    if checklink(path):
+        return lambda x: os.path.islink(os.path.join(path, x))
+    return fallback
+
 _umask = os.umask(0)
 os.umask(_umask)
 
+def needbinarypatch():
+    """return True if patches should be applied in binary mode by default."""
+    return os.name == 'nt'
+
 # Platform specific variants
 if os.name == 'nt':
-    demandload(globals(), "msvcrt")
+    import msvcrt
     nulldev = 'NUL:'
 
     class winstdout:
@@ -770,19 +840,18 @@ if os.name == 'nt':
         except:
             return [r'c:\mercurial\mercurial.ini']
 
-    def os_rcpath():
-        '''return default os-specific hgrc search path'''
-        path = system_rcpath()
-        path.append(user_rcpath())
+    def user_rcpath():
+        '''return os-specific hgrc search path to the user dir'''
+        try:
+            userrc = user_rcpath_win32()
+        except:
+            userrc = os.path.join(os.path.expanduser('~'), 'mercurial.ini')
+        path = [userrc]
         userprofile = os.environ.get('USERPROFILE')
         if userprofile:
             path.append(os.path.join(userprofile, 'mercurial.ini'))
         return path
 
-    def user_rcpath():
-        '''return os-specific hgrc search path to the user dir'''
-        return os.path.join(os.path.expanduser('~'), 'mercurial.ini')
-
     def parse_patch_output(output_line):
         """parses the output produced by patch and returns the file name"""
         pf = output_line[14:]
@@ -794,10 +863,10 @@ if os.name == 'nt':
         '''return False if pid dead, True if running or not known'''
         return True
 
-    def is_exec(f, last):
-        return last
+    def set_exec(f, mode):
+        pass
 
-    def set_exec(f, mode):
+    def set_link(f, mode):
         pass
 
     def set_binary(fd):
@@ -843,6 +912,30 @@ if os.name == 'nt':
     # username and groupname functions above, too.
     def isowner(fp, st=None):
         return True
+        
+    def find_in_path(name, path, default=None):
+        '''find name in search path. path can be string (will be split
+        with os.pathsep), or iterable thing that returns strings.  if name
+        found, return path to name. else return default. name is looked up
+        using cmd.exe rules, using PATHEXT.'''
+        if isinstance(path, str):
+            path = path.split(os.pathsep)
+            
+        pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
+        pathext = pathext.lower().split(os.pathsep)
+        isexec = os.path.splitext(name)[1].lower() in pathext
+        
+        for p in path:
+            p_name = os.path.join(p, name)
+            
+            if isexec and os.path.exists(p_name):
+                return p_name
+            
+            for ext in pathext:
+                p_name_ext = p_name + ext
+                if os.path.exists(p_name_ext):
+                    return p_name_ext
+        return default
 
     try:
         # override functions with win32 versions if possible
@@ -865,18 +958,18 @@ else:
             pass
         return rcs
 
-    def os_rcpath():
-        '''return default os-specific hgrc search path'''
+    def system_rcpath():
         path = []
         # old mod_python does not set sys.argv
         if len(getattr(sys, 'argv', [])) > 0:
             path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
                                   '/../etc/mercurial'))
         path.extend(rcfiles('/etc/mercurial'))
-        path.append(os.path.expanduser('~/.hgrc'))
-        path = [os.path.normpath(f) for f in path]
         return path
 
+    def user_rcpath():
+        return [os.path.expanduser('~/.hgrc')]
+
     def parse_patch_output(output_line):
         """parses the output produced by patch and returns the file name"""
         pf = output_line[14:]
@@ -884,7 +977,7 @@ else:
             pf = pf[1:-1] # Remove the quotes
         return pf
 
-    def is_exec(f, last):
+    def is_exec(f):
         """check whether a file is executable"""
         return (os.lstat(f).st_mode & 0100 != 0)
 
@@ -899,6 +992,26 @@ else:
         else:
             os.chmod(f, s & 0666)
 
+    def set_link(f, mode):
+        """make a file a symbolic link/regular file
+
+        if a file is changed to a link, its contents become the link data
+        if a link is changed to a file, its link data become its contents
+        """
+
+        m = os.path.islink(f)
+        if m == bool(mode):
+            return
+
+        if mode: # switch file to link
+            data = file(f).read()
+            os.unlink(f)
+            os.symlink(data, f)
+        else:
+            data = os.readlink(f)
+            os.unlink(f)
+            file(f, "w").write(data)
+
     def set_binary(fd):
         pass
 
@@ -961,6 +1074,18 @@ else:
         if st is None:
             st = fstat(fp)
         return st.st_uid == os.getuid()
+        
+    def find_in_path(name, path, default=None):
+        '''find name in search path. path can be string (will be split
+        with os.pathsep), or iterable thing that returns strings.  if name
+        found, return path to name. else return default.'''
+        if isinstance(path, str):
+            path = path.split(os.pathsep)
+        for p in path:
+            p_name = os.path.join(p, name)
+            if os.path.exists(p_name):
+                return p_name
+        return default
 
 def _buildencodefun():
     e = '_'
@@ -1061,11 +1186,19 @@ def opener(base, audit=True):
     class atomicfile(atomictempfile):
         """the file will only be copied on close"""
         def __init__(self, name, mode):
+            self._err = False
             atomictempfile.__init__(self, name, mode)
+        def write(self, s):
+            try:
+                atomictempfile.write(self, s)
+            except:
+                self._err = True
+                raise
         def close(self):
             self.rename()
         def __del__(self):
-            self.rename()
+            if not self._err:
+                self.rename()
 
     def o(path, mode="r", text=False, atomic=False, atomictemp=False):
         if audit_p:
@@ -1339,6 +1472,13 @@ def walkrepos(path):
 
 _rcpath = None
 
+def os_rcpath():
+    '''return default os-specific hgrc search path'''
+    path = system_rcpath()
+    path.extend(user_rcpath())
+    path = [os.path.normpath(f) for f in path]
+    return path
+
 def rcpath():
     '''return hgrc search path. if env var HGRCPATH is set, use it.
     for each item in path, if directory, use files ending in .rc,
--- a/mercurial/util_win32.py
+++ b/mercurial/util_win32.py
@@ -13,10 +13,10 @@
 
 import win32api
 
-from demandload import *
-from i18n import gettext as _
-demandload(globals(), 'errno os pywintypes win32con win32file win32process')
-demandload(globals(), 'cStringIO win32com.shell:shell,shellcon winerror')
+from i18n import _
+import errno, os, pywintypes, win32con, win32file, win32process
+import cStringIO, winerror
+from win32com.shell import shell,shellcon
 
 class WinError:
     winerror_map = {
@@ -187,7 +187,7 @@ def system_rcpath_win32():
         filename = win32api.GetModuleFileName(0)
     return [os.path.join(os.path.dirname(filename), 'mercurial.ini')]
 
-def user_rcpath():
+def user_rcpath_win32():
     '''return os-specific hgrc search path to the user dir'''
     userdir = os.path.expanduser('~')
     if userdir == '~':
--- a/mercurial/verify.py
+++ b/mercurial/verify.py
@@ -6,7 +6,7 @@
 # of the GNU General Public License, incorporated herein by reference.
 
 from node import *
-from i18n import gettext as _
+from i18n import _
 import revlog, mdiff
 
 def verify(repo):
@@ -17,6 +17,8 @@ def verify(repo):
     warnings = [0]
     neededmanifests = {}
 
+    lock = repo.lock()
+
     def err(msg):
         repo.ui.warn(msg + "\n")
         errors[0] += 1
@@ -39,8 +41,8 @@ def verify(repo):
         elif revlogv1:
             warn(_("warning: `%s' uses revlog format 0") % name)
 
-    revlogv1 = repo.revlogversion != revlog.REVLOGV0
-    if repo.ui.verbose or revlogv1 != repo.revlogv1:
+    revlogv1 = repo.changelog.version != revlog.REVLOGV0
+    if repo.ui.verbose or not revlogv1:
         repo.ui.status(_("repository uses revlog format %d\n") %
                        (revlogv1 and 1 or 0))
 
--- a/setup.py
+++ b/setup.py
@@ -13,9 +13,11 @@ import os
 from distutils.core import setup, Extension
 from distutils.command.install_data import install_data
 
-# mercurial.packagescan must be the first mercurial module imported
-import mercurial.packagescan
 import mercurial.version
+import mercurial.demandimport
+mercurial.demandimport.enable = lambda: None
+
+extra = {}
 
 # py2exe needs to be installed to work
 try:
@@ -35,34 +37,10 @@ try:
     except ImportError:
         pass
 
-    # Due to the use of demandload py2exe is not finding the modules.
-    # packagescan.getmodules creates a list of modules included in
-    # the mercurial package plus dependant modules.
-    from py2exe.build_exe import py2exe as build_exe
+    extra['console'] = ['hg']
 
-    class py2exe_for_demandload(build_exe):
-        """ overwrites the py2exe command class for getting the build
-        directory and for setting the 'includes' option."""
-        def initialize_options(self):
-            self.build_lib = None
-            build_exe.initialize_options(self)
-        def finalize_options(self):
-            # Get the build directory, ie. where to search for modules.
-            self.set_undefined_options('build',
-                                       ('build_lib', 'build_lib'))
-            # Sets the 'includes' option with the list of needed modules
-            if not self.includes:
-                self.includes = []
-            else:
-                self.includes = self.includes.split(',')
-            mercurial.packagescan.scan(self.build_lib, 'mercurial')
-            mercurial.packagescan.scan(self.build_lib, 'mercurial.hgweb')
-            mercurial.packagescan.scan(self.build_lib, 'hgext')
-            self.includes += mercurial.packagescan.getmodules()
-            build_exe.finalize_options(self)
 except ImportError:
-    py2exe_for_demandload = None
-
+    pass
 
 # specify version string, otherwise 'hg identify' will be used:
 version = ''
@@ -75,10 +53,6 @@ class install_package_data(install_data)
 
 mercurial.version.remember_version(version)
 cmdclass = {'install_data': install_package_data}
-py2exe_opts = {}
-if py2exe_for_demandload is not None:
-    cmdclass['py2exe'] = py2exe_for_demandload
-    py2exe_opts['console'] = ['hg']
 
 setup(name='mercurial',
       version=mercurial.version.get_version(),
@@ -100,4 +74,4 @@ setup(name='mercurial',
                                    license='COPYING',
                                    readme='contrib/macosx/Readme.html',
                                    welcome='contrib/macosx/Welcome.html')),
-      **py2exe_opts)
+      **extra)
--- a/templates/gitweb/header.tmpl
+++ b/templates/gitweb/header.tmpl
@@ -4,7 +4,7 @@ Content-type: text/html; charset={encodi
 <!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">
 <head>
-<link rel="icon" href="{url}static/hgicon.png" type="image/png">
+<link rel="icon" href="{staticurl}hgicon.png" type="image/png">
 <meta name="robots" content="index, nofollow"/>
-<link rel="stylesheet" href="{url}static/style-gitweb.css" type="text/css" />
+<link rel="stylesheet" href="{staticurl}style-gitweb.css" type="text/css" />
 
--- a/templates/gitweb/map
+++ b/templates/gitweb/map
@@ -39,7 +39,7 @@ filerevchild = '<tr><td class="metatag">
 fileannotatechild = '<tr><td class="metatag">child {rev}:</td><td><a href="{url}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
 tags = tags.tmpl
 tagentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><a class="list" href="{url}rev/{node|short}{sessionvars%urlparameter}"><b>#tag|escape#</b></a></td><td class="link"><a href="{url}rev/#node|short#{sessionvars%urlparameter}">changeset</a> | <a href="{url}log/#node|short#{sessionvars%urlparameter}">changelog</a> | <a href="{url}file/#node|short#{sessionvars%urlparameter}">manifest</a></td></tr>'
-headentry = '<tr class="parity{parity}"><td class="age"><i>{date|age} ago</i></td><td><a class="list" href="{url}rev/{node|short}{sessionvars%urlparameter}"><b>{node|short}</td><td>{branch|escape}</td><td class="link"><a href="{url}rev/{node|short}{sessionvars%urlparameter}">changeset</a> | <a href="{url}log/{node|short}{sessionvars%urlparameter}">changelog</a> | <a href="{url}file/{node|short}{sessionvars%urlparameter}">manifest</a></td></tr>'
+branchentry = '<tr class="parity{parity}"><td class="age"><i>{date|age} ago</i></td><td><a class="list" href="{url}rev/{node|short}{sessionvars%urlparameter}"><b>{node|short}</b></td><td>{branch|escape}</td><td class="link"><a href="{url}rev/{node|short}{sessionvars%urlparameter}">changeset</a> | <a href="{url}log/{node|short}{sessionvars%urlparameter}">changelog</a> | <a href="{url}file/{node|short}{sessionvars%urlparameter}">manifest</a></td></tr>'
 diffblock = '<pre>#lines#</pre>'
 changelogtag = '<tr><th class="tag">tag:</th><td class="tag">#tag|escape#</td></tr>'
 changesettag = '<tr><td>tag</td><td>#tag|escape#</td></tr>'
@@ -48,7 +48,7 @@ filelogparent = '<tr><td align="right">p
 filediffchild = '<tr><th class="child">child {rev}:</th><td class="child"><a href="{url}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
 filelogchild = '<tr><td align="right">child #rev#:&nbsp;</td><td><a href="{url}file{node|short}/#file|urlescape#{sessionvars%urlparameter}">#node|short#</a></td></tr>'
 shortlog = shortlog.tmpl
-shortlogentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><i>#author#</i></td><td><a class="list" href="{url}rev/#node|short#{sessionvars%urlparameter}"><b>#desc|strip|firstline|escape#</b></a></td><td class="link"><a href="{url}rev/#node|short#{sessionvars%urlparameter}">changeset</a> | <a href="{url}file/#node|short#{sessionvars%urlparameter}">manifest</a></td></tr>'
+shortlogentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><i>#author#</i></td><td><a class="list" href="{url}rev/#node|short#{sessionvars%urlparameter}"><b>#desc|strip|firstline|escape#</b></a></td><td class="link" nowrap><a href="{url}rev/#node|short#{sessionvars%urlparameter}">changeset</a> | <a href="{url}file/#node|short#{sessionvars%urlparameter}">manifest</a></td></tr>'
 filelogentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><a class="list" href="{url}rev/#node|short#{sessionvars%urlparameter}"><b>#desc|strip|firstline|escape#</b></a></td><td class="link"><a href="{url}file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">file</a>&nbsp;|&nbsp;<a href="{url}diff/#node|short#/#file|urlescape#{sessionvars%urlparameter}">diff</a>&nbsp;|&nbsp;<a href="{url}annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">annotate</a> #rename%filelogrename#</td></tr>'
 archiveentry = ' | <a href="{url}archive/{node|short}{extension}">#type|escape#</a> '
 indexentry = '<tr class="parity#parity#"><td><a class="list" href="#url#{sessionvars%urlparameter}"><b>#name|escape#</b></a></td><td>#description#</td><td>#contact|obfuscate#</td><td class="age">#lastchange|age# ago</td><td class="indexlinks"><a class="rss_logo" href="#url#rss-log">RSS</a> #archives%archiveentry#</td></tr>' 
--- a/templates/gitweb/summary.tmpl
+++ b/templates/gitweb/summary.tmpl
@@ -36,10 +36,9 @@ summary |
 <tr class="light"><td colspan="3"><a class="list" href="{url}tags{sessionvars%urlparameter}">...</a></td></tr>
 </table>
 
-<div><a class="title"
-href="#">heads</a></div>
+<div><a class="title" href="#">branches</a></div>
 <table cellspacing="0">
-{heads%headentry}
+{branches%branchentry}
 <tr class="light">
   <td colspan="3"><a class="list"  href="#">...</a></td>
 </tr>
--- a/templates/header.tmpl
+++ b/templates/header.tmpl
@@ -3,6 +3,6 @@ Content-type: text/html; charset={encodi
 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
 <html>
 <head>
-<link rel="icon" href="#url#static/hgicon.png" type="image/png">
+<link rel="icon" href="#staticurl#hgicon.png" type="image/png">
 <meta name="robots" content="index, nofollow" />
-<link rel="stylesheet" href="#url#static/style.css" type="text/css" />
+<link rel="stylesheet" href="#staticurl#style.css" type="text/css" />
new file mode 100644
--- /dev/null
+++ b/templates/raw/index.tmpl
@@ -0,0 +1,2 @@
+#header#
+#entries%indexentry#
new file mode 100644
--- /dev/null
+++ b/templates/raw/manifest.tmpl
@@ -0,0 +1,3 @@
+{header}
+{dentries%manifestdirentry}{fentries%manifestfileentry}
+{footer}
--- a/templates/raw/map
+++ b/templates/raw/map
@@ -14,3 +14,8 @@ diffblock = '#lines#'
 filediff = filediff.tmpl
 fileannotate = fileannotate.tmpl
 annotateline = '#author#@#rev#: #line#'
+manifest = manifest.tmpl
+manifestdirentry = 'drwxr-xr-x {basename}\n'
+manifestfileentry = '{permissions|permissions} {size} {basename}\n'
+index = index.tmpl
+indexentry = '#url#\n'
--- a/tests/README
+++ b/tests/README
@@ -1,93 +1,7 @@
-A simple testing framework
-
 To run the tests, do:
 
 cd tests/
 python run-tests.py
 
-This finds all scripts in the test directory named test-* and executes
-them. The scripts can be either shell scripts or Python. Each test is
-run in a temporary directory that is removed when the test is complete.
-
-A test-<x> succeeds if the script returns success and its output
-matches test-<x>.out. If the new output doesn't match, it is stored in
-test-<x>.err.
-
-There are some tricky points here that you should be aware of when
-writing tests:
-
-- hg commit and hg merge want user interaction
-
-  for commit use -m "text"
-  for hg merge, set HGMERGE to something noninteractive (like true or merge)
-
-- changeset hashes will change based on user and date which make
-  things like hg history output change
-
-  use commit -m "test" -u test -d "1000000 0"
-
-- diff and export may show the current time
-
-  use -D/--nodates to strip the dates
-
-- You can append your own hgrc settings to the file that the environment
-  variable HGRCPATH points to. This file is cleared before running a test.
-
-You also need to be careful that the tests are portable from one platform
-to another.  You're probably working on Linux, where the GNU toolchain has
-more (or different) functionality than on MacOS, *BSD, Solaris, AIX, etc.
-While testing on all platforms is the only sure-fire way to make sure that
-you've written portable code, here's a list of problems that have been
-found and fixed in the tests.  Another, more comprehensive list may be
-found in the GNU Autoconf manual, online here:
-
-    http://www.gnu.org/software/autoconf/manual/html_node/Portable-Shell.html
-
-sh:
-
-The Bourne shell is a very basic shell.  /bin/sh on Linux is typically
-bash, which even in Bourne-shell mode has many features that Bourne shells
-on other Unix systems don't have (and even on Linux /bin/sh isn't
-guaranteed to be bash).  You'll need to be careful about constructs that
-seem ubiquitous, but are actually not available in the least common
-denominator.  While using another shell (ksh, bash explicitly, posix shell,
-etc.) explicitly may seem like another option, these may not exist in a
-portable location, and so are generally probably not a good idea.  You may
-find that rewriting the test in python will be easier.
-
-- don't use pushd/popd; save the output of "pwd" and use "cd" in place of
-  the pushd, and cd back to the saved pwd instead of popd.
-
-- don't use math expressions like let, (( ... )), or $(( ... )); use "expr"
-  instead.
-
-grep:
-
-- don't use the -q option; redirect stdout to /dev/null instead.
-
-- don't use extended regular expressions with grep; use egrep instead, and
-  don't escape any regex operators.
-
-sed:
-
-- make sure that the beginning-of-line matcher ("^") is at the very
-  beginning of the expression -- it may not be supported inside parens.
-
-echo:
-
-- echo may interpret "\n" and print a newline; use printf instead if you
-  want a literal "\n" (backslash + n).
-
-false:
-
-- false is guaranteed only to return a non-zero value; you cannot depend on
-  it being 1.  On Solaris in particular, /bin/false returns 255.  Rewrite
-  your test to not depend on a particular return value, or create a
-  temporary "false" executable, and call that instead.
-
-diff:
-
-- don't use the -N option.  There's no particularly good workaround short
-  of writing a reasonably complicated replacement script, but substituting
-  gdiff for diff if you can't rewrite the test not to need -N will probably
-  do.
+See http://www.selenic.com/mercurial/wiki/index.cgi/WritingTests for
+more information on writing tests.
new file mode 100644
--- /dev/null
+++ b/tests/printenv.py
@@ -0,0 +1,44 @@
+# simple script to be used in hooks
+# copy it to the current directory when the test starts:
+#
+#     cp "$TESTDIR"/printenv.py .
+#
+# put something like this in the repo .hg/hgrc:
+#
+#     [hooks]
+#     changegroup = python ../printenv.py <hookname> [exit] [output]
+#
+#   - <hookname> is a mandatory argument (e.g. "changegroup")
+#   - [exit] is the exit code of the hook (default: 0)
+#   - [output] is the name of the output file (default: use sys.stdout)
+#              the file will be opened in append mode.
+#
+import os
+import sys
+
+exitcode = 0
+out = sys.stdout
+
+name = sys.argv[1]
+if len(sys.argv) > 2:
+    exitcode = int(sys.argv[2])
+    if len(sys.argv) > 3:
+        out = open(sys.argv[3], "ab")
+
+env = [v for v in os.environ if v.startswith("HG_")]
+env.sort()
+
+# edit the variable part of the variable
+url = os.environ.get("HG_URL", "")
+if url.startswith("file:"):
+    os.environ["HG_URL"] = "file:"
+elif url.startswith("remote:http"):
+    os.environ["HG_URL"] = "remote:http"
+
+out.write("%s hook: " % name)
+for v in env:
+    out.write("%s=%s " % (v, os.environ[v]))
+out.write("\n")
+out.close()
+
+sys.exit(exitcode)
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -19,7 +19,7 @@ import sys
 import tempfile
 import time
 
-required_tools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed", "merge"]
+required_tools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
 
 parser = optparse.OptionParser("%prog [options] [tests]")
 parser.add_option("-v", "--verbose", action="store_true",
@@ -340,17 +340,19 @@ check_required_tools()
 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
 os.environ['TZ'] = 'GMT'
 
-os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
-os.environ["HGMERGE"]  = sys.executable + ' -c "import sys; sys.exit(0)"'
-os.environ["HGUSER"]   = "test"
-os.environ["HGENCODING"] = "ascii"
-os.environ["HGENCODINGMODE"] = "strict"
-
 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
 HGTMP   = os.environ["HGTMP"]   = tempfile.mkdtemp("", "hgtests.")
 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
 
+os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
+os.environ["HGMERGE"]  = ('python "%s" -L my -L other'
+                          % os.path.join(TESTDIR, os.path.pardir, 'contrib',
+                                         'simplemerge'))
+os.environ["HGUSER"]   = "test"
+os.environ["HGENCODING"] = "ascii"
+os.environ["HGENCODINGMODE"] = "strict"
+
 vlog("# Using TESTDIR", TESTDIR)
 vlog("# Using HGTMP", HGTMP)
 
--- a/tests/test-abort-checkin
+++ b/tests/test-abort-checkin
@@ -1,12 +1,19 @@
 #!/bin/sh
 
+cat > abortcommit.py <<EOF
+from mercurial import util
+
+def hook(**args):
+    raise util.Abort("no commits allowed")
+
+def reposetup(ui, repo):
+    repo.ui.setconfig("hooks", "pretxncommit.nocommits", hook)
+EOF
+abspath=`pwd`/abortcommit.py
+
 echo "[extensions]" >> $HGRCPATH
 echo "mq=" >> $HGRCPATH
-cat > $HGTMP/false <<EOF
-#!/bin/sh
-exit 1
-EOF
-chmod +x $HGTMP/false
+echo "abortcommit = $abspath" >> $HGRCPATH
 
 hg init foo
 cd foo
@@ -15,7 +22,7 @@ hg add foo
 
 # mq may keep a reference to the repository so __del__ will not be called
 # and .hg/journal.dirstate will not be deleted:
-HGEDITOR=$HGTMP/false hg ci
-HGEDITOR=$HGTMP/false hg ci
+hg ci -m foo
+hg ci -m foo
 
 exit 0
--- a/tests/test-abort-checkin.out
+++ b/tests/test-abort-checkin.out
@@ -1,6 +1,8 @@
-abort: edit failed: false exited with status 1
+error: pretxncommit.nocommits hook failed: no commits allowed
+abort: no commits allowed
 transaction abort!
 rollback completed
-abort: edit failed: false exited with status 1
+error: pretxncommit.nocommits hook failed: no commits allowed
+abort: no commits allowed
 transaction abort!
 rollback completed
--- a/tests/test-acl
+++ b/tests/test-acl
@@ -26,7 +26,7 @@ mkdir foo foo/Bar quux
 echo 'in foo' > foo/file.txt
 echo 'in foo/Bar' > foo/Bar/file.txt
 echo 'in quux' > quux/file.py
-hg add
+hg add -q
 hg ci -m 'add files' -d '1000000 0'
 echo >> foo/file.txt
 hg ci -m 'change foo/file' -d '1000001 0'
--- a/tests/test-acl.out
+++ b/tests/test-acl.out
@@ -1,6 +1,3 @@
-adding foo/Bar/file.txt
-adding foo/file.txt
-adding quux/file.py
 3:911600dab2ae
 requesting all changes
 adding changesets
new file mode 100755
--- /dev/null
+++ b/tests/test-addremove-similar
@@ -0,0 +1,37 @@
+#!/bin/sh
+
+hg init rep; cd rep
+
+touch empty-file
+python -c 'for x in range(10000): print x' > large-file
+
+hg addremove
+
+hg commit -m A
+
+rm large-file empty-file
+python -c 'for x in range(10,10000): print x' > another-file
+
+hg addremove -s50
+
+hg commit -m B
+
+cd ..
+
+hg init rep2; cd rep2
+
+python -c 'for x in range(10000): print x' > large-file
+python -c 'for x in range(50): print x' > tiny-file
+
+hg addremove
+
+hg commit -m A
+
+python -c 'for x in range(70): print x' > small-file
+rm tiny-file
+rm large-file
+
+hg addremove -s50
+
+hg commit -m B
+
new file mode 100644
--- /dev/null
+++ b/tests/test-addremove-similar.out
@@ -0,0 +1,12 @@
+adding empty-file
+adding large-file
+adding another-file
+removing empty-file
+removing large-file
+recording removal of large-file as rename to another-file (99% similar)
+adding large-file
+adding tiny-file
+adding small-file
+removing large-file
+removing tiny-file
+recording removal of tiny-file as rename to small-file (82% similar)
--- a/tests/test-annotate
+++ b/tests/test-annotate
@@ -1,5 +1,7 @@
 #!/bin/sh
 
+HGMERGE=true; export HGMERGE
+
 echo % init
 hg init
 
--- a/tests/test-backout
+++ b/tests/test-backout
@@ -1,5 +1,7 @@
 #!/bin/sh
 
+HGMERGE=true; export HGMERGE
+
 echo '# basic operation'
 hg init basic
 cd basic
--- a/tests/test-backout.out
+++ b/tests/test-backout.out
@@ -28,7 +28,6 @@ reverting a
 changeset 3:4cbb1e70196a backs out changeset 1:22bca4c721e5
 the backout changeset is a new head - do not forget to merge
 (use "backout --merge" if you want to auto-merge)
-b: No such file or directory
 adding a
 adding b
 adding c
--- a/tests/test-bad-extension
+++ b/tests/test-bad-extension
@@ -1,9 +1,11 @@
 #!/bin/sh
 
-echo 'syntax error' > badext.py
+echo 'raise Exception("bit bucket overflow")' > badext.py
 abspath=`pwd`/badext.py
 
 echo '[extensions]' >> $HGRCPATH
+echo "gpg =" >> $HGRCPATH
+echo "hgext.gpg =" >> $HGRCPATH
 echo "badext = $abspath" >> $HGRCPATH
 
 hg -q help help
--- a/tests/test-bad-extension.out
+++ b/tests/test-bad-extension.out
@@ -1,4 +1,5 @@
-*** failed to import extension badext: invalid syntax (badext.py, line 1)
+*** failed to import extension badext: bit bucket overflow
+extension 'hgext.gpg' overrides commands: sigs sigcheck sign
 hg help [COMMAND]
 
 show help for a command, extension, or list of commands
deleted file mode 100755
--- a/tests/test-branch
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/bin/sh
-#
-# test for branch handling
-#
-# XXX: need more tests
-
-hg init
-echo a > a
-echo b > b
-hg ci -A -m 0 -d "1000000 0"
-echo aa > a
-echo bb > b
-hg ci -m 1 -d "1000000 0"
-hg tag -l foo
-hg update 0
-hg parents -b 
-
-# test update
-hg update -b foo
-hg parents
-
-# test merge
-hg update 0
-echo c > c
-hg ci -A -m 0.0 -d "1000000 0"
-hg merge -b foo
-hg parents -b 
-
-# re-test with more branches
-hg update -C 0
-echo d > d
-hg ci -A -m 0.0 -d "1000000 0"
-hg merge -b foo
-hg parents -b 
deleted file mode 100644
--- a/tests/test-branch.out
+++ /dev/null
@@ -1,61 +0,0 @@
-adding a
-adding b
-2 files updated, 0 files merged, 0 files removed, 0 files unresolved
-the --branches option is deprecated, please use 'hg branches' instead
-changeset:   0:b544c4ac4389
-user:        test
-date:        Mon Jan 12 13:46:40 1970 +0000
-summary:     0
-
-the --branch option is deprecated, please use 'hg branch' instead
-Using head f4ac749470f2 for branch foo
-2 files updated, 0 files merged, 0 files removed, 0 files unresolved
-changeset:   1:f4ac749470f2
-tag:         foo
-tag:         tip
-user:        test
-date:        Mon Jan 12 13:46:40 1970 +0000
-summary:     1
-
-2 files updated, 0 files merged, 0 files removed, 0 files unresolved
-adding c
-the --branch option is deprecated, please use 'hg branch' instead
-Using head f4ac749470f2 for branch foo
-2 files updated, 0 files merged, 0 files removed, 0 files unresolved
-(branch merge, don't forget to commit)
-the --branches option is deprecated, please use 'hg branches' instead
-changeset:   2:1505d56ee00e
-tag:         tip
-parent:      0:b544c4ac4389
-user:        test
-date:        Mon Jan 12 13:46:40 1970 +0000
-summary:     0.0
-
-changeset:   1:f4ac749470f2
-tag:         foo
-branch:      foo
-user:        test
-date:        Mon Jan 12 13:46:40 1970 +0000
-summary:     1
-
-2 files updated, 0 files merged, 1 files removed, 0 files unresolved
-adding d
-the --branch option is deprecated, please use 'hg branch' instead
-Using head f4ac749470f2 for branch foo
-2 files updated, 0 files merged, 0 files removed, 0 files unresolved
-(branch merge, don't forget to commit)
-the --branches option is deprecated, please use 'hg branches' instead
-changeset:   3:53b72df12ae5
-tag:         tip
-parent:      0:b544c4ac4389
-user:        test
-date:        Mon Jan 12 13:46:40 1970 +0000
-summary:     0.0
-
-changeset:   1:f4ac749470f2
-tag:         foo
-branch:      foo
-user:        test
-date:        Mon Jan 12 13:46:40 1970 +0000
-summary:     1
-
--- a/tests/test-bundle
+++ b/tests/test-bundle
@@ -1,5 +1,7 @@
 #!/bin/sh
 
+cp "$TESTDIR"/printenv.py .
+
 hg init test
 cd test
 echo 0 > afile
@@ -43,7 +45,7 @@ hg init empty
 cd empty
 hg -R bundle://../full.hg log
 echo '[hooks]' >> .hg/hgrc
-echo 'changegroup = echo changegroup: u=$HG_URL' >> .hg/hgrc
+echo 'changegroup = python ../printenv.py changegroup' >> .hg/hgrc
 #doesn't work (yet ?)
 #hg -R bundle://../full.hg verify
 hg pull bundle://../full.hg
--- a/tests/test-bundle.out
+++ b/tests/test-bundle.out
@@ -87,7 +87,7 @@ user:        test
 date:        Mon Jan 12 13:46:40 1970 +0000
 summary:     0.0
 
-changegroup: u=bundle:../full.hg
+changegroup hook: HG_NODE=5649c9d34dd87d0ecb5fd39672128376e83b22e1 HG_SOURCE=pull HG_URL=bundle:../full.hg 
 pulling from bundle://../full.hg
 requesting all changes
 adding changesets
@@ -150,6 +150,7 @@ user:        test
 date:        Mon Jan 12 13:46:40 1970 +0000
 summary:     0.0
 
+comparing with bundle://../full.hg
 searching for changes
 changeset:   4:5f4f3ceb285e
 parent:      0:5649c9d34dd8
@@ -179,6 +180,7 @@ user:        test
 date:        Mon Jan 12 13:46:40 1970 +0000
 summary:     0.3m
 
+comparing with ../partial2
 searching for changes
 changeset:   4:5f4f3ceb285e
 parent:      0:5649c9d34dd8
@@ -211,6 +213,7 @@ summary:     0.3m
 abort: No such file or directory: ../does-not-exist.hg
 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 searching for changes
+comparing with ../bundle.hg
 searching for changes
 changeset:   2:ed1b79f46b9a
 tag:         tip
--- a/tests/test-commit
+++ b/tests/test-commit
@@ -71,4 +71,21 @@ echo % full log
 hg log -v
 cd ..
 
+echo % dot and subdir commit test
+hg init test3
+cd test3
+mkdir foo
+echo foo content > foo/plain-file
+hg add foo/plain-file
+hg ci -d '1000000 0' -u test -m commit-foo-subdir foo
+echo modified foo content > foo/plain-file
+hg ci -d '2000000 0' -u test -m commit-foo-dot .
+echo % full log
+hg log -v
+echo % subdir log
+cd foo
+hg log .
+cd ..
+cd ..
+
 exit 0
--- a/tests/test-commit.out
+++ b/tests/test-commit.out
@@ -21,8 +21,7 @@ abort: no match under directory .../test
 dir/file
 does-not-exist: No such file or directory
 abort: file .../test/does-not-exist not found!
-baz: unsupported file type (type is symbolic link)
-abort: can't commit .../test/baz: unsupported file type!
+abort: file .../test/baz not tracked!
 abort: file .../test/quux not tracked!
 dir/file
 % partial subdir commit test
@@ -65,3 +64,34 @@ description:
 commit-subdir-1
 
 
+% dot and subdir commit test
+% full log
+changeset:   1:d9180e04fa8a
+tag:         tip
+user:        test
+date:        Sat Jan 24 03:33:20 1970 +0000
+files:       foo/plain-file
+description:
+commit-foo-dot
+
+
+changeset:   0:80b572aaf098
+user:        test
+date:        Mon Jan 12 13:46:40 1970 +0000
+files:       foo/plain-file
+description:
+commit-foo-subdir
+
+
+% subdir log
+changeset:   1:d9180e04fa8a
+tag:         tip
+user:        test
+date:        Sat Jan 24 03:33:20 1970 +0000
+summary:     commit-foo-dot
+
+changeset:   0:80b572aaf098
+user:        test
+date:        Mon Jan 12 13:46:40 1970 +0000
+summary:     commit-foo-subdir
+
--- a/tests/test-conflict
+++ b/tests/test-conflict
@@ -9,8 +9,7 @@ hg commit -m branch1 -d "1000000 0"
 hg co 0
 echo "something else" > a
 hg commit -m branch2 -d "1000000 0"
-HGMERGE=merge; export HGMERGE
 hg merge 1
 hg id
-egrep -v ">>>|<<<" a
+cat a
 hg status
--- a/tests/test-conflict.out
+++ b/tests/test-conflict.out
@@ -1,5 +1,5 @@
 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-merge: warning: conflicts during merge
+warning: conflicts during merge.
 merging a
 merging a failed!
 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
@@ -7,7 +7,9 @@ There are unresolved merges, you can red
   hg update -C 2
   hg merge 1
 e7fe8eb3e180+0d24b7662d3e+ tip
+<<<<<<< my
 something else
 =======
 something
+>>>>>>> other
 M a
new file mode 100644
--- /dev/null
+++ b/tests/test-context.py
@@ -0,0 +1,20 @@
+import os
+from mercurial import hg, ui, commands
+
+u = ui.ui()
+
+repo = hg.repository(u, 'test1', create=1)
+os.chdir('test1')
+repo = hg.repository(u, '.') # FIXME: can't lock repo without doing this
+
+# create 'foo' with fixed time stamp
+f = file('foo', 'w')
+f.write('foo\n')
+f.close()
+os.utime('foo', (1000, 1000))
+
+# add+commit 'foo'
+repo.add(['foo'])
+repo.commit(text='commit1', date="0 0")
+
+print "workingfilectx.date =", repo.workingctx().filectx('foo').date()
new file mode 100644
--- /dev/null
+++ b/tests/test-context.py.out
@@ -0,0 +1,1 @@
+workingfilectx.date = (1000, 0)
--- a/tests/test-copy2
+++ b/tests/test-copy2
@@ -38,4 +38,10 @@ hg debugrename bar
 echo "# should show no copies"
 hg debugstate|grep '^copy'
 
+echo "# copy --after on an added file"
+cp bar baz
+hg add baz
+hg cp -A bar baz
+hg st -C
+
 exit 0
--- a/tests/test-copy2.out
+++ b/tests/test-copy2.out
@@ -20,3 +20,6 @@ copy: foo -> bar
      1         5       7      1       2 dd12c926cf16 2ed2a3912a0b 000000000000
 bar renamed from foo:dd12c926cf165e3eb4cf87b084955cb617221c17
 # should show no copies
+# copy --after on an added file
+A baz
+  bar
--- a/tests/test-empty-group.out
+++ b/tests/test-empty-group.out
@@ -21,6 +21,7 @@ adding manifests
 adding file changes
 added 4 changesets with 3 changes to 3 files
 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+comparing with b
 searching for changes
 changeset:   4:fdb3c546e859
 tag:         tip
@@ -30,6 +31,7 @@ user:        test
 date:        Mon Jan 12 13:46:40 1970 +0000
 summary:     m2
 
+comparing with c
 searching for changes
 changeset:   3:f40f830c0024
 parent:      2:de997049e034
@@ -38,6 +40,7 @@ user:        test
 date:        Mon Jan 12 13:46:40 1970 +0000
 summary:     m1
 
+comparing with c
 searching for changes
 changeset:   3:f40f830c0024
 tag:         tip
@@ -47,6 +50,7 @@ user:        test
 date:        Mon Jan 12 13:46:40 1970 +0000
 summary:     m1
 
+comparing with b
 searching for changes
 changeset:   3:fdb3c546e859
 tag:         tip
--- a/tests/test-filebranch
+++ b/tests/test-filebranch
@@ -3,11 +3,11 @@
 # This test makes sure that we don't mark a file as merged with its ancestor
 # when we do a merge.
 
-cat <<'EOF' > merge
-#!/bin/sh
-echo merging for `basename $1`
+cat <<EOF > merge
+import sys, os
+print "merging for", os.path.basename(sys.argv[1])
 EOF
-chmod +x merge
+HGMERGE="python ../merge"; export HGMERGE
 
 echo creating base
 hg init a
@@ -41,7 +41,7 @@ hg debugstate | cut -b 1-16,35-
 
 echo merging
 hg pull ../a
-env HGMERGE=../merge hg merge -v
+hg merge -v
 
 echo 2m > foo
 echo 2b > baz
--- a/tests/test-git-import.out
+++ b/tests/test-git-import.out
@@ -31,7 +31,6 @@ a
 a
 % rename and modify
 applying patch from stdin
-copy2: No such file or directory
 a
 a
 b
@@ -40,7 +39,6 @@ a
 % one file renamed multiple times
 applying patch from stdin
 9 rename2 rename3 rename3-2 / rename3 (rename2)rename3-2 (rename2)
-rename2: No such file or directory
 rename3
 rename3-2
 a
--- a/tests/test-globalopts
+++ b/tests/test-globalopts
@@ -17,7 +17,7 @@ cd ..
 hg clone a c
 cd c
 hg pull -f ../b
-HGMERGE=merge hg merge
+hg merge
 
 cd ..
 
new file mode 100755
--- /dev/null
+++ b/tests/test-glog
@@ -0,0 +1,140 @@
+#!/bin/sh
+
+# @  (34) head
+# |
+# | o  (33) head
+# | |
+# o |    (32) expand
+# |\ \
+# | o \    (31) expand
+# | |\ \
+# | | o \    (30) expand
+# | | |\ \
+# | | | o |  (29) regular commit
+# | | | | |
+# | | o | |    (28) merge zero known
+# | | |\ \ \
+# o | | | | |  (27) collapse
+# |/ / / / /
+# | | o---+  (26) merge one known; far right
+# | | | | |
+# +---o | |  (25) merge one known; far left
+# | | | | |
+# | | o | |  (24) merge one known; immediate right
+# | | |\| |
+# | | o | |  (23) merge one known; immediate left
+# | |/| | |
+# +---o---+  (22) merge two known; one far left, one far right
+# | |  / /
+# o | | |    (21) expand
+# |\ \ \ \
+# | o---+-+  (20) merge two known; two far right
+# |  / / /
+# o | | |    (19) expand
+# |\ \ \ \
+# +---+---o  (18) merge two known; two far left
+# | | | |
+# | o | |    (17) expand
+# | |\ \ \
+# | | o---+  (16) merge two known; one immediate right, one near right
+# | | |/ /
+# o | | |    (15) expand
+# |\ \ \ \
+# | o-----+  (14) merge two known; one immediate right, one far right
+# | |/ / /
+# o | | |    (13) expand
+# |\ \ \ \
+# +---o | |  (12) merge two known; one immediate right, one far left
+# | | |/ /
+# | o | |    (11) expand
+# | |\ \ \
+# | | o---+  (10) merge two known; one immediate left, one near right
+# | |/ / /
+# o | | |    (9) expand
+# |\ \ \ \
+# | o-----+  (8) merge two known; one immediate left, one far right
+# |/ / / /
+# o | | |    (7) expand
+# |\ \ \ \
+# +---o | |  (6) merge two known; one immediate left, one far left
+# | |/ / /
+# | o | |    (5) expand
+# | |\ \ \
+# | | o | |  (4) merge two known; one immediate left, one immediate right
+# | |/|/ /
+# | o / /  (3) collapse
+# |/ / /
+# o / /  (2) collapse
+# |/ /
+# o /  (1) collapse
+# |/
+# o  (0) root
+
+set -e
+
+commit()
+{
+    rev=$1
+    msg=$2
+    shift 2
+    if [ "$#" -gt 0 ]; then
+        hg debugsetparents "$@"
+    fi
+    echo $rev > $rev
+    hg add $rev
+    hg ci -d "$rev 0" -m "($rev) $msg"
+}
+
+echo "[extensions]" >> $HGRCPATH
+echo "graphlog=" >> $HGRCPATH
+
+echo % init
+hg init repo
+
+cd repo
+
+echo % empty repo
+hg glog
+
+echo % building tree
+commit 0 "root"
+commit 1 "collapse" 0
+commit 2 "collapse" 1
+commit 3 "collapse" 2
+commit 4 "merge two known; one immediate left, one immediate right" 1 3
+commit 5 "expand" 3 4
+commit 6 "merge two known; one immediate left, one far left" 2 5
+commit 7 "expand" 2 5
+commit 8 "merge two known; one immediate left, one far right" 0 7
+commit 9 "expand" 7 8
+commit 10 "merge two known; one immediate left, one near right" 0 6
+commit 11 "expand" 6 10
+commit 12 "merge two known; one immediate right, one far left" 1 9
+commit 13 "expand" 9 11
+commit 14 "merge two known; one immediate right, one far right" 0 12
+commit 15 "expand" 13 14
+commit 16 "merge two known; one immediate right, one near right" 0 1
+commit 17 "expand" 12 16
+commit 18 "merge two known; two far left" 1 15
+commit 19 "expand" 15 17
+commit 20 "merge two known; two far right" 0 18
+commit 21 "expand" 19 20
+commit 22 "merge two known; one far left, one far right" 18 21
+commit 23 "merge one known; immediate left" 1 22
+commit 24 "merge one known; immediate right" 0 23
+commit 25 "merge one known; far left" 21 24
+commit 26 "merge one known; far right" 18 25
+commit 27 "collapse" 21
+commit 28 "merge zero known" 1 26
+commit 29 "regular commit" 0
+commit 30 "expand" 28 29
+commit 31 "expand" 21 30
+commit 32 "expand" 27 31
+commit 33 "head" 18
+commit 34 "head" 32
+
+echo % glog -q
+hg glog -q
+
+echo % glog
+hg glog
new file mode 100644
--- /dev/null
+++ b/tests/test-glog.out
@@ -0,0 +1,309 @@
+% init
+% empty repo
+% building tree
+% glog -q
+@  34:0eed7cd895e0
+|
+| o  33:2e9d1b521374
+| |
+o |    32:77f7d8438a3c
+|\ \
+| o \    31:82ee55204a79
+| |\ \
+| | o \    30:777dfc428649
+| | |\ \
+| | | o |  29:f8e7fee63353
+| | | | |
+| | o | |    28:4b6e9bd48cf9
+| | |\ \ \
+o | | | | |  27:e9e08174cd30
+|/ / / / /
+| | o---+  26:720dc079a855
+| | | | |
++---o | |  25:9d4ed048d013
+| | | | |
+| | o | |  24:4a68967db00d
+| | |\| |
+| | o | |  23:bc31393cabdf
+| |/| | |
++---o---+  22:a37f2ea6ebc6
+| |  / /
+o | | |    21:e758e8f4ace9
+|\ \ \ \
+| o---+-+  20:aeccadad74b4
+|  / / /
+o | | |    19:138069b5dad7
+|\ \ \ \
++---+---o  18:5a8c9a29ef81
+| | | |
+| o | |    17:43e52b935494
+| |\ \ \
+| | o---+  16:449a2f9562a4
+| | |/ /
+o | | |    15:c0b4283d4c1d
+|\ \ \ \
+| o-----+  14:9d533950abf0
+| |/ / /
+o | | |    13:c39d0a2b8165
+|\ \ \ \
++---o | |  12:74dc7aea4494
+| | |/ /
+| o | |    11:c3c395dd8b98
+| |\ \ \
+| | o---+  10:8094c50149ef
+| |/ / /
+o | | |    9:79ab1812f961
+|\ \ \ \
+| o-----+  8:d7aa38594334
+|/ / / /
+o | | |    7:699392d1259e
+|\ \ \ \
++---o | |  6:0ca7c061cf45
+| |/ / /
+| o | |    5:3589c3c477ab
+| |\ \ \
+| | o | |  4:e2cad8233c77
+| |/|/ /
+| o / /  3:02173ffbf857
+|/ / /
+o / /  2:e8ea2256f9ec
+|/ /
+o /  1:3cae7826a707
+|/
+o  0:7aa22e58e8c1
+
+% glog
+@  changeset:   34:0eed7cd895e0
+|  tag:         tip
+|  parent:      32:77f7d8438a3c
+|  user:        test
+|  date:        Thu Jan 01 00:00:34 1970 +0000
+|  summary:     (34) head
+|
+| o  changeset:   33:2e9d1b521374
+| |  parent:      18:5a8c9a29ef81
+| |  user:        test
+| |  date:        Thu Jan 01 00:00:33 1970 +0000
+| |  summary:     (33) head
+| |
+o |    changeset:   32:77f7d8438a3c
+|\ \   parent:      27:e9e08174cd30
+| | |  parent:      31:82ee55204a79
+| | |  user:        test
+| | |  date:        Thu Jan 01 00:00:32 1970 +0000
+| | |  summary:     (32) expand
+| | |
+| o |    changeset:   31:82ee55204a79
+| |\ \   parent:      21:e758e8f4ace9
+| | | |  parent:      30:777dfc428649
+| | | |  user:        test
+| | | |  date:        Thu Jan 01 00:00:31 1970 +0000
+| | | |  summary:     (31) expand
+| | | |
+| | o |    changeset:   30:777dfc428649
+| | |\ \   parent:      28:4b6e9bd48cf9
+| | | | |  parent:      29:f8e7fee63353
+| | | | |  user:        test
+| | | | |  date:        Thu Jan 01 00:00:30 1970 +0000
+| | | | |  summary:     (30) expand
+| | | | |
+| | | o |  changeset:   29:f8e7fee63353
+| | | | |  parent:      0:7aa22e58e8c1
+| | | | |  user:        test
+| | | | |  date:        Thu Jan 01 00:00:29 1970 +0000
+| | | | |  summary:     (29) regular commit
+| | | | |
+| | o | |    changeset:   28:4b6e9bd48cf9
+| | |\ \ \   parent:      1:3cae7826a707
+| | | | | |  parent:      26:720dc079a855
+| | | | | |  user:        test
+| | | | | |  date:        Thu Jan 01 00:00:28 1970 +0000
+| | | | | |  summary:     (28) merge zero known
+| | | | | |
+o | | | | |  changeset:   27:e9e08174cd30
+|/ / / / /   parent:      21:e758e8f4ace9
+| | | | |    user:        test
+| | | | |    date:        Thu Jan 01 00:00:27 1970 +0000
+| | | | |    summary:     (27) collapse
+| | | | |
+| | o---+  changeset:   26:720dc079a855
+| | | | |  parent:      18:5a8c9a29ef81
+| | | | |  parent:      25:9d4ed048d013
+| | | | |  user:        test
+| | | | |  date:        Thu Jan 01 00:00:26 1970 +0000
+| | | | |  summary:     (26) merge one known; far right
+| | | | |
++---o | |  changeset:   25:9d4ed048d013
+| | | | |  parent:      21:e758e8f4ace9
+| | | | |  parent:      24:4a68967db00d
+| | | | |  user:        test
+| | | | |  date:        Thu Jan 01 00:00:25 1970 +0000
+| | | | |  summary:     (25) merge one known; far left
+| | | | |
+| | o | |  changeset:   24:4a68967db00d
+| | |\| |  parent:      0:7aa22e58e8c1
+| | | | |  parent:      23:bc31393cabdf
+| | | | |  user:        test
+| | | | |  date:        Thu Jan 01 00:00:24 1970 +0000
+| | | | |  summary:     (24) merge one known; immediate right
+| | | | |
+| | o | |  changeset:   23:bc31393cabdf
+| |/| | |  parent:      1:3cae7826a707
+| | | | |  parent:      22:a37f2ea6ebc6
+| | | | |  user:        test
+| | | | |  date:        Thu Jan 01 00:00:23 1970 +0000
+| | | | |  summary:     (23) merge one known; immediate left
+| | | | |
++---o---+  changeset:   22:a37f2ea6ebc6
+| |   | |  parent:      18:5a8c9a29ef81
+| |  / /   parent:      21:e758e8f4ace9
+| | | |    user:        test
+| | | |    date:        Thu Jan 01 00:00:22 1970 +0000
+| | | |    summary:     (22) merge two known; one far left, one far right
+| | | |
+o | | |    changeset:   21:e758e8f4ace9
+|\ \ \ \   parent:      19:138069b5dad7
+| | | | |  parent:      20:aeccadad74b4
+| | | | |  user:        test
+| | | | |  date:        Thu Jan 01 00:00:21 1970 +0000
+| | | | |  summary:     (21) expand
+| | | | |
+| o---+-+  changeset:   20:aeccadad74b4
+|   | | |  parent:      0:7aa22e58e8c1
+|  / / /   parent:      18:5a8c9a29ef81
+| | | |    user:        test
+| | | |    date:        Thu Jan 01 00:00:20 1970 +0000
+| | | |    summary:     (20) merge two known; two far right
+| | | |
+o | | |    changeset:   19:138069b5dad7
+|\ \ \ \   parent:      15:c0b4283d4c1d
+| | | | |  parent:      17:43e52b935494
+| | | | |  user:        test
+| | | | |  date:        Thu Jan 01 00:00:19 1970 +0000
+| | | | |  summary:     (19) expand
+| | | | |
++---+---o  changeset:   18:5a8c9a29ef81
+| | | |    parent:      1:3cae7826a707
+| | | |    parent:      15:c0b4283d4c1d
+| | | |    user:        test
+| | | |    date:        Thu Jan 01 00:00:18 1970 +0000
+| | | |    summary:     (18) merge two known; two far left
+| | | |
+| o | |    changeset:   17:43e52b935494
+| |\ \ \   parent:      12:74dc7aea4494
+| | | | |  parent:      16:449a2f9562a4
+| | | | |  user:        test
+| | | | |  date:        Thu Jan 01 00:00:17 1970 +0000
+| | | | |  summary:     (17) expand
+| | | | |
+| | o---+  changeset:   16:449a2f9562a4
+| | | | |  parent:      0:7aa22e58e8c1
+| | |/ /   parent:      1:3cae7826a707
+| | | |    user:        test
+| | | |    date:        Thu Jan 01 00:00:16 1970 +0000
+| | | |    summary:     (16) merge two known; one immediate right, one near right
+| | | |
+o | | |    changeset:   15:c0b4283d4c1d
+|\ \ \ \   parent:      13:c39d0a2b8165
+| | | | |  parent:      14:9d533950abf0
+| | | | |  user:        test
+| | | | |  date:        Thu Jan 01 00:00:15 1970 +0000
+| | | | |  summary:     (15) expand
+| | | | |
+| o-----+  changeset:   14:9d533950abf0
+| | | | |  parent:      0:7aa22e58e8c1
+| |/ / /   parent:      12:74dc7aea4494
+| | | |    user:        test
+| | | |    date:        Thu Jan 01 00:00:14 1970 +0000
+| | | |    summary:     (14) merge two known; one immediate right, one far right
+| | | |
+o | | |    changeset:   13:c39d0a2b8165
+|\ \ \ \   parent:      9:79ab1812f961
+| | | | |  parent:      11:c3c395dd8b98
+| | | | |  user:        test
+| | | | |  date:        Thu Jan 01 00:00:13 1970 +0000
+| | | | |  summary:     (13) expand
+| | | | |
++---o | |  changeset:   12:74dc7aea4494
+| | |/ /   parent:      1:3cae7826a707
+| | | |    parent:      9:79ab1812f961
+| | | |    user:        test
+| | | |    date:        Thu Jan 01 00:00:12 1970 +0000
+| | | |    summary:     (12) merge two known; one immediate right, one far left
+| | | |
+| o | |    changeset:   11:c3c395dd8b98
+| |\ \ \   parent:      6:0ca7c061cf45
+| | | | |  parent:      10:8094c50149ef
+| | | | |  user:        test
+| | | | |  date:        Thu Jan 01 00:00:11 1970 +0000
+| | | | |  summary:     (11) expand
+| | | | |
+| | o---+  changeset:   10:8094c50149ef
+| | | | |  parent:      0:7aa22e58e8c1
+| |/ / /   parent:      6:0ca7c061cf45
+| | | |    user:        test
+| | | |    date:        Thu Jan 01 00:00:10 1970 +0000
+| | | |    summary:     (10) merge two known; one immediate left, one near right
+| | | |
+o | | |    changeset:   9:79ab1812f961
+|\ \ \ \   parent:      7:699392d1259e
+| | | | |  parent:      8:d7aa38594334
+| | | | |  user:        test
+| | | | |  date:        Thu Jan 01 00:00:09 1970 +0000
+| | | | |  summary:     (9) expand
+| | | | |
+| o-----+  changeset:   8:d7aa38594334
+| | | | |  parent:      0:7aa22e58e8c1
+|/ / / /   parent:      7:699392d1259e
+| | | |    user:        test
+| | | |    date:        Thu Jan 01 00:00:08 1970 +0000
+| | | |    summary:     (8) merge two known; one immediate left, one far right
+| | | |
+o | | |    changeset:   7:699392d1259e
+|\ \ \ \   parent:      2:e8ea2256f9ec
+| | | | |  parent:      5:3589c3c477ab
+| | | | |  user:        test
+| | | | |  date:        Thu Jan 01 00:00:07 1970 +0000
+| | | | |  summary:     (7) expand
+| | | | |
++---o | |  changeset:   6:0ca7c061cf45
+| |/ / /   parent:      2:e8ea2256f9ec
+| | | |    parent:      5:3589c3c477ab
+| | | |    user:        test
+| | | |    date:        Thu Jan 01 00:00:06 1970 +0000
+| | | |    summary:     (6) merge two known; one immediate left, one far left
+| | | |
+| o | |    changeset:   5:3589c3c477ab
+| |\ \ \   parent:      3:02173ffbf857
+| | | | |  parent:      4:e2cad8233c77
+| | | | |  user:        test
+| | | | |  date:        Thu Jan 01 00:00:05 1970 +0000
+| | | | |  summary:     (5) expand
+| | | | |
+| | o | |  changeset:   4:e2cad8233c77
+| |/|/ /   parent:      1:3cae7826a707
+| | | |    parent:      3:02173ffbf857
+| | | |    user:        test
+| | | |    date:        Thu Jan 01 00:00:04 1970 +0000
+| | | |    summary:     (4) merge two known; one immediate left, one immediate right
+| | | |
+| o | |  changeset:   3:02173ffbf857
+|/ / /   user:        test
+| | |    date:        Thu Jan 01 00:00:03 1970 +0000
+| | |    summary:     (3) collapse
+| | |
+o | |  changeset:   2:e8ea2256f9ec
+|/ /   user:        test
+| |    date:        Thu Jan 01 00:00:02 1970 +0000
+| |    summary:     (2) collapse
+| |
+o |  changeset:   1:3cae7826a707
+|/   user:        test
+|    date:        Thu Jan 01 00:00:01 1970 +0000
+|    summary:     (1) collapse
+|
+o  changeset:   0:7aa22e58e8c1
+   user:        test
+   date:        Thu Jan 01 00:00:00 1970 +0000
+   summary:     (0) root
+
--- a/tests/test-grep
+++ b/tests/test-grep
@@ -17,8 +17,11 @@ hg commit -m 3 -u eggs -d '3 0'
 head -n 3 port > port1
 mv port1 port
 hg commit -m 4 -u spam -d '4 0'
+echo % simple
 hg grep port port
+echo % all
 hg grep --all -nu port port
+echo % other
 hg grep import port
 
 hg cp port port2
@@ -28,3 +31,22 @@ hg grep -f 'import$' port2
 echo deport >> port2
 hg commit -m 5 -u eggs -d '6 0'
 hg grep -f --all -nu port port2
+
+cd ..
+hg init t2
+cd t2
+hg grep foobar foo
+hg grep foobar
+echo blue >> color
+echo black >> color
+hg add color
+hg ci -m 0 -d '0 0'
+echo orange >> color
+hg ci -m 1 -d '0 0'
+echo black > color
+hg ci -m 2 -d '0 0'
+echo orange >> color
+echo blue >> color
+hg ci -m 3 -d '0 0'
+hg grep orange
+hg grep --all orange
--- a/tests/test-grep.out
+++ b/tests/test-grep.out
@@ -1,6 +1,8 @@
+% simple
 port:4:export
 port:4:vaportight
 port:4:import/export
+% all
 port:4:4:-:spam:import/export
 port:3:4:+:eggs:import/export
 port:2:1:-:spam:import
@@ -10,6 +12,7 @@ port:2:2:+:spam:vaportight
 port:2:3:+:spam:import/export
 port:1:2:+:eggs:export
 port:0:1:+:spam:import
+% other
 port:4:import/export
 % follow
 port:0:import
@@ -23,3 +26,7 @@ port:2:2:+:spam:vaportight
 port:2:3:+:spam:import/export
 port:1:2:+:eggs:export
 port:0:1:+:spam:import
+color:3:orange
+color:3:+:orange
+color:2:-:orange
+color:1:+:orange
new file mode 100755
--- /dev/null
+++ b/tests/test-hgweb
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+hg init test
+cd test
+mkdir da
+echo foo > da/foo
+echo foo > foo
+hg ci -Ambase -d '0 0'
+hg serve -p 20060 -d --pid-file=hg.pid
+echo % manifest
+("$TESTDIR/get-with-headers.py" localhost:20060 '/file/tip/?style=raw')
+("$TESTDIR/get-with-headers.py" localhost:20060 '/file/tip/da?style=raw')
+kill `cat hg.pid`
new file mode 100644
--- /dev/null
+++ b/tests/test-hgweb.out
@@ -0,0 +1,16 @@
+adding da/foo
+adding foo
+% manifest
+200 Script output follows
+
+
+drwxr-xr-x da
+-rw-r--r-- 4 foo
+
+
+200 Script output follows
+
+
+-rw-r--r-- 4 foo
+
+
--- a/tests/test-hook
+++ b/tests/test-hook
@@ -1,13 +1,16 @@
 #!/bin/sh
 
+cp "$TESTDIR"/printenv.py .
+
 # commit hooks can see env vars
 hg init a
 cd a
 echo "[hooks]" > .hg/hgrc
-echo 'commit = echo commit hook: n=$HG_NODE p1=$HG_PARENT1 p2=$HG_PARENT2' >> .hg/hgrc
-echo 'commit.b = echo commit hook b' >> .hg/hgrc
-echo 'precommit = echo precommit hook: p1=$HG_PARENT1 p2=$HG_PARENT2' >> .hg/hgrc
-echo 'pretxncommit = echo pretxncommit hook: n=$HG_NODE p1=$HG_PARENT1 p2=$HG_PARENT2; hg -q tip' >> .hg/hgrc
+echo 'commit = unset HG_LOCAL HG_TAG; python ../printenv.py commit' >> .hg/hgrc
+echo 'commit.b = unset HG_LOCAL HG_TAG; python ../printenv.py commit.b' >> .hg/hgrc
+echo 'precommit = unset HG_LOCAL HG_NODE HG_TAG; python ../printenv.py precommit' >> .hg/hgrc
+echo 'pretxncommit = unset HG_LOCAL HG_TAG; python ../printenv.py pretxncommit' >> .hg/hgrc
+echo 'pretxncommit.tip = hg -q tip' >> .hg/hgrc
 echo a > a
 hg add a
 hg commit -m a -d "1000000 0"
@@ -17,9 +20,9 @@ cd ../b
 
 # changegroup hooks can see env vars
 echo '[hooks]' > .hg/hgrc
-echo 'prechangegroup = echo prechangegroup hook: u=`echo $HG_URL | sed s,file:.*,file:,`' >> .hg/hgrc
-echo 'changegroup = echo changegroup hook: n=$HG_NODE u=`echo $HG_URL | sed s,file:.*,file:,`' >> .hg/hgrc
-echo 'incoming = echo incoming hook: n=$HG_NODE u=`echo $HG_URL | sed s,file:.*,file:,`' >> .hg/hgrc
+echo 'prechangegroup = python ../printenv.py prechangegroup' >> .hg/hgrc
+echo 'changegroup = python ../printenv.py changegroup' >> .hg/hgrc
+echo 'incoming = python ../printenv.py incoming' >> .hg/hgrc
 
 # pretxncommit and commit hooks can see both parents of merge
 cd ../a
@@ -37,19 +40,20 @@ hg pull ../a
 
 # tag hooks can see env vars
 cd ../a
-echo 'pretag = echo pretag hook: t=$HG_TAG n=$HG_NODE l=$HG_LOCAL' >> .hg/hgrc
-echo 'tag = echo tag hook: t=$HG_TAG n=$HG_NODE l=$HG_LOCAL' >> .hg/hgrc
+echo 'pretag = python ../printenv.py pretag' >> .hg/hgrc
+echo 'tag = unset HG_PARENT1 HG_PARENT2; python ../printenv.py tag' >> .hg/hgrc
 hg tag -d '3 0' a
 hg tag -l la
 
 # pretag hook can forbid tagging
-echo 'pretag.forbid = echo pretag.forbid hook; exit 1' >> .hg/hgrc
+echo 'pretag.forbid = python ../printenv.py pretag.forbid 1' >> .hg/hgrc
 hg tag -d '4 0' fa
 hg tag -l fla
 
 # pretxncommit hook can see changeset, can roll back txn, changeset
 # no more there after
-echo 'pretxncommit.forbid = echo pretxncommit.forbid hook: tip=`hg -q tip`; exit 1' >> .hg/hgrc
+echo 'pretxncommit.forbid0 = hg tip -q' >> .hg/hgrc
+echo 'pretxncommit.forbid1 = python ../printenv.py pretxncommit.forbid 1' >> .hg/hgrc
 echo z > z
 hg add z
 hg -q tip
@@ -57,42 +61,43 @@ hg commit -m 'fail' -d '4 0'
 hg -q tip
 
 # precommit hook can prevent commit
-echo 'precommit.forbid = echo precommit.forbid hook; exit 1' >> .hg/hgrc
+echo 'precommit.forbid = python ../printenv.py precommit.forbid 1' >> .hg/hgrc
 hg commit -m 'fail' -d '4 0'
 hg -q tip
 
 # preupdate hook can prevent update
-echo 'preupdate = echo preupdate hook: p1=$HG_PARENT1 p2=$HG_PARENT2' >> .hg/hgrc
+echo 'preupdate = python ../printenv.py preupdate' >> .hg/hgrc
 hg update 1
 
 # update hook
-echo 'update = echo update hook: p1=$HG_PARENT1 p2=$HG_PARENT2 err=$HG_ERROR' >> .hg/hgrc
+echo 'update = python ../printenv.py update' >> .hg/hgrc
 hg update
 
 # prechangegroup hook can prevent incoming changes
 cd ../b
 hg -q tip
 echo '[hooks]' > .hg/hgrc
-echo 'prechangegroup.forbid = echo prechangegroup.forbid hook; exit 1' >> .hg/hgrc
+echo 'prechangegroup.forbid = python ../printenv.py prechangegroup.forbid 1' >> .hg/hgrc
 hg pull ../a
 
 # pretxnchangegroup hook can see incoming changes, can roll back txn,
 # incoming changes no longer there after
 echo '[hooks]' > .hg/hgrc
-echo 'pretxnchangegroup.forbid = echo pretxnchangegroup.forbid hook: tip=`hg -q tip`; exit 1' >> .hg/hgrc
+echo 'pretxnchangegroup.forbid0 = hg tip -q' >> .hg/hgrc
+echo 'pretxnchangegroup.forbid1 = python ../printenv.py pretxnchangegroup.forbid 1' >> .hg/hgrc
 hg pull ../a
 hg -q tip
 
 # outgoing hooks can see env vars
 rm .hg/hgrc
 echo '[hooks]' > ../a/.hg/hgrc
-echo 'preoutgoing = echo preoutgoing hook: s=$HG_SOURCE' >> ../a/.hg/hgrc
-echo 'outgoing = echo outgoing hook: n=$HG_NODE s=$HG_SOURCE' >> ../a/.hg/hgrc
+echo 'preoutgoing = python ../printenv.py preoutgoing' >> ../a/.hg/hgrc
+echo 'outgoing = python ../printenv.py outgoing' >> ../a/.hg/hgrc
 hg pull ../a
 hg rollback
 
 # preoutgoing hook can prevent outgoing changes
-echo 'preoutgoing.forbid = echo preoutgoing.forbid hook; exit 1' >> ../a/.hg/hgrc
+echo 'preoutgoing.forbid = python ../printenv.py preoutgoing.forbid 1' >> ../a/.hg/hgrc
 hg pull ../a
 
 cat > hooktests.py <<EOF
@@ -183,4 +188,26 @@ echo 'commit.abort = python:hooktests.ab
 echo a >> a
 hg --traceback commit -A -m a 2>&1 | grep '^Traceback'
 
+cd ..
+hg init c
+cd c
+
+cat > hookext.py <<EOF
+def autohook(**args):
+    print "Automatically installed hook"
+
+def reposetup(ui, repo):
+    repo.ui.setconfig("hooks", "commit.auto", autohook)
+EOF
+echo '[extensions]' >> .hg/hgrc
+echo 'hookext = hookext.py' >> .hg/hgrc
+
+touch foo
+hg add foo
+hg ci -m 'add foo'
+echo >> foo
+hg ci --debug -m 'change foo' | sed -e 's/ at .*>/>/'
+
+hg showconfig hooks | sed -e 's/ at .*>/>/'
+
 exit 0
--- a/tests/test-hook.out
+++ b/tests/test-hook.out
@@ -1,32 +1,32 @@
-precommit hook: p1=0000000000000000000000000000000000000000 p2=
-pretxncommit hook: n=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b p1=0000000000000000000000000000000000000000 p2=
+precommit hook: HG_PARENT1=0000000000000000000000000000000000000000 HG_PARENT2= 
+pretxncommit hook: HG_NODE=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b HG_PARENT1=0000000000000000000000000000000000000000 HG_PARENT2= 
 0:29b62aeb769f
-commit hook: n=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b p1=0000000000000000000000000000000000000000 p2=
-commit hook b
+commit hook: HG_NODE=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b HG_PARENT1=0000000000000000000000000000000000000000 HG_PARENT2= 
+commit.b hook: HG_NODE=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b HG_PARENT1=0000000000000000000000000000000000000000 HG_PARENT2= 
 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-precommit hook: p1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b p2=
-pretxncommit hook: n=b702efe9688826e3a91283852b328b84dbf37bc2 p1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b p2=
+precommit hook: HG_PARENT1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b HG_PARENT2= 
+pretxncommit hook: HG_NODE=b702efe9688826e3a91283852b328b84dbf37bc2 HG_PARENT1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b HG_PARENT2= 
 1:b702efe96888
-commit hook: n=b702efe9688826e3a91283852b328b84dbf37bc2 p1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b p2=
-commit hook b
+commit hook: HG_NODE=b702efe9688826e3a91283852b328b84dbf37bc2 HG_PARENT1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b HG_PARENT2= 
+commit.b hook: HG_NODE=b702efe9688826e3a91283852b328b84dbf37bc2 HG_PARENT1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b HG_PARENT2= 
 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-precommit hook: p1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b p2=
-pretxncommit hook: n=1324a5531bac09b329c3845d35ae6a7526874edb p1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b p2=
+precommit hook: HG_PARENT1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b HG_PARENT2= 
+pretxncommit hook: HG_NODE=1324a5531bac09b329c3845d35ae6a7526874edb HG_PARENT1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b HG_PARENT2= 
 2:1324a5531bac
-commit hook: n=1324a5531bac09b329c3845d35ae6a7526874edb p1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b p2=
-commit hook b
+commit hook: HG_NODE=1324a5531bac09b329c3845d35ae6a7526874edb HG_PARENT1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b HG_PARENT2= 
+commit.b hook: HG_NODE=1324a5531bac09b329c3845d35ae6a7526874edb HG_PARENT1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b HG_PARENT2= 
 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 (branch merge, don't forget to commit)
-precommit hook: p1=1324a5531bac09b329c3845d35ae6a7526874edb p2=b702efe9688826e3a91283852b328b84dbf37bc2
-pretxncommit hook: n=4c52fb2e402287dd5dc052090682536c8406c321 p1=1324a5531bac09b329c3845d35ae6a7526874edb p2=b702efe9688826e3a91283852b328b84dbf37bc2
+precommit hook: HG_PARENT1=1324a5531bac09b329c3845d35ae6a7526874edb HG_PARENT2=b702efe9688826e3a91283852b328b84dbf37bc2 
+pretxncommit hook: HG_NODE=4c52fb2e402287dd5dc052090682536c8406c321 HG_PARENT1=1324a5531bac09b329c3845d35ae6a7526874edb HG_PARENT2=b702efe9688826e3a91283852b328b84dbf37bc2 
 3:4c52fb2e4022
-commit hook: n=4c52fb2e402287dd5dc052090682536c8406c321 p1=1324a5531bac09b329c3845d35ae6a7526874edb p2=b702efe9688826e3a91283852b328b84dbf37bc2
-commit hook b
-prechangegroup hook: u=file:
-changegroup hook: n=b702efe9688826e3a91283852b328b84dbf37bc2 u=file:
-incoming hook: n=b702efe9688826e3a91283852b328b84dbf37bc2 u=file:
-incoming hook: n=1324a5531bac09b329c3845d35ae6a7526874edb u=file:
-incoming hook: n=4c52fb2e402287dd5dc052090682536c8406c321 u=file:
+commit hook: HG_NODE=4c52fb2e402287dd5dc052090682536c8406c321 HG_PARENT1=1324a5531bac09b329c3845d35ae6a7526874edb HG_PARENT2=b702efe9688826e3a91283852b328b84dbf37bc2 
+commit.b hook: HG_NODE=4c52fb2e402287dd5dc052090682536c8406c321 HG_PARENT1=1324a5531bac09b329c3845d35ae6a7526874edb HG_PARENT2=b702efe9688826e3a91283852b328b84dbf37bc2 
+prechangegroup hook: HG_SOURCE=pull HG_URL=file: 
+changegroup hook: HG_NODE=b702efe9688826e3a91283852b328b84dbf37bc2 HG_SOURCE=pull HG_URL=file: 
+incoming hook: HG_NODE=b702efe9688826e3a91283852b328b84dbf37bc2 HG_SOURCE=pull HG_URL=file: 
+incoming hook: HG_NODE=1324a5531bac09b329c3845d35ae6a7526874edb HG_SOURCE=pull HG_URL=file: 
+incoming hook: HG_NODE=4c52fb2e402287dd5dc052090682536c8406c321 HG_SOURCE=pull HG_URL=file: 
 pulling from ../a
 searching for changes
 adding changesets
@@ -34,57 +34,58 @@ adding manifests
 adding file changes
 added 3 changesets with 2 changes to 2 files
 (run 'hg update' to get a working copy)
-pretag hook: t=a n=4c52fb2e402287dd5dc052090682536c8406c321 l=0
-precommit hook: p1=4c52fb2e402287dd5dc052090682536c8406c321 p2=
-pretxncommit hook: n=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 p1=4c52fb2e402287dd5dc052090682536c8406c321 p2=
+pretag hook: HG_LOCAL=0 HG_NODE=4c52fb2e402287dd5dc052090682536c8406c321 HG_TAG=a 
+precommit hook: HG_PARENT1=4c52fb2e402287dd5dc052090682536c8406c321 HG_PARENT2= 
+pretxncommit hook: HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_PARENT1=4c52fb2e402287dd5dc052090682536c8406c321 HG_PARENT2= 
 4:8ea2ef7ad3e8
-commit hook: n=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 p1=4c52fb2e402287dd5dc052090682536c8406c321 p2=
-commit hook b
-tag hook: t=a n=4c52fb2e402287dd5dc052090682536c8406c321 l=0
-pretag hook: t=la n=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 l=1
-tag hook: t=la n=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 l=1
-pretag hook: t=fa n=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 l=0
-pretag.forbid hook
+commit hook: HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_PARENT1=4c52fb2e402287dd5dc052090682536c8406c321 HG_PARENT2= 
+commit.b hook: HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_PARENT1=4c52fb2e402287dd5dc052090682536c8406c321 HG_PARENT2= 
+tag hook: HG_LOCAL=0 HG_NODE=4c52fb2e402287dd5dc052090682536c8406c321 HG_TAG=a 
+pretag hook: HG_LOCAL=1 HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_TAG=la 
+tag hook: HG_LOCAL=1 HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_TAG=la 
+pretag hook: HG_LOCAL=0 HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_TAG=fa 
+pretag.forbid hook: HG_LOCAL=0 HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_TAG=fa 
 abort: pretag.forbid hook exited with status 1
-pretag hook: t=fla n=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 l=1
-pretag.forbid hook
+pretag hook: HG_LOCAL=1 HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_TAG=fla 
+pretag.forbid hook: HG_LOCAL=1 HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_TAG=fla 
 abort: pretag.forbid hook exited with status 1
 4:8ea2ef7ad3e8
-precommit hook: p1=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 p2=
-pretxncommit hook: n=fad284daf8c032148abaffcd745dafeceefceb61 p1=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 p2=
+precommit hook: HG_PARENT1=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_PARENT2= 
+pretxncommit hook: HG_NODE=fad284daf8c032148abaffcd745dafeceefceb61 HG_PARENT1=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_PARENT2= 
 5:fad284daf8c0
-pretxncommit.forbid hook: tip=5:fad284daf8c0
-abort: pretxncommit.forbid hook exited with status 1
+pretxncommit.forbid hook: HG_NODE=fad284daf8c032148abaffcd745dafeceefceb61 HG_PARENT1=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_PARENT2= 
+abort: pretxncommit.forbid1 hook exited with status 1
 transaction abort!
 rollback completed
 4:8ea2ef7ad3e8
-precommit hook: p1=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 p2=
-precommit.forbid hook
+precommit hook: HG_PARENT1=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_PARENT2= 
+precommit.forbid hook: HG_PARENT1=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_PARENT2= 
 abort: precommit.forbid hook exited with status 1
 4:8ea2ef7ad3e8
-preupdate hook: p1=b702efe96888 p2=
+preupdate hook: HG_PARENT1=b702efe96888 HG_PARENT2= 
 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
-preupdate hook: p1=8ea2ef7ad3e8 p2=
-update hook: p1=8ea2ef7ad3e8 p2= err=0
+preupdate hook: HG_PARENT1=8ea2ef7ad3e8 HG_PARENT2= 
+update hook: HG_ERROR=0 HG_PARENT1=8ea2ef7ad3e8 HG_PARENT2= 
 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 3:4c52fb2e4022
-prechangegroup.forbid hook
+prechangegroup.forbid hook: HG_SOURCE=pull HG_URL=file: 
 pulling from ../a
 searching for changes
 abort: prechangegroup.forbid hook exited with status 1
-pretxnchangegroup.forbid hook: tip=4:8ea2ef7ad3e8
+4:8ea2ef7ad3e8
+pretxnchangegroup.forbid hook: HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_SOURCE=pull HG_URL=file: 
 pulling from ../a
 searching for changes
 adding changesets
 adding manifests
 adding file changes
 added 1 changesets with 1 changes to 1 files
-abort: pretxnchangegroup.forbid hook exited with status 1
+abort: pretxnchangegroup.forbid1 hook exited with status 1
 transaction abort!
 rollback completed
 3:4c52fb2e4022
-preoutgoing hook: s=pull
-outgoing hook: n=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 s=pull
+preoutgoing hook: HG_SOURCE=pull 
+outgoing hook: HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_SOURCE=pull 
 pulling from ../a
 searching for changes
 adding changesets
@@ -93,8 +94,8 @@ adding file changes
 added 1 changesets with 1 changes to 1 files
 (run 'hg update' to get a working copy)
 rolling back last transaction
-preoutgoing hook: s=pull
-preoutgoing.forbid hook
+preoutgoing hook: HG_SOURCE=pull 
+preoutgoing.forbid hook: HG_SOURCE=pull 
 pulling from ../a
 searching for changes
 abort: preoutgoing.forbid hook exited with status 1
@@ -138,3 +139,8 @@ added 1 changesets with 1 changes to 1 f
 (run 'hg update' to get a working copy)
 # make sure --traceback works
 Traceback (most recent call last):
+Automatically installed hook
+foo
+calling hook commit.auto: <function autohook>
+Automatically installed hook
+hooks.commit.auto=<function autohook>
--- a/tests/test-http
+++ b/tests/test-http
@@ -1,5 +1,7 @@
 #!/bin/sh
 
+cp "$TESTDIR"/printenv.py .
+
 hg init test
 cd test
 echo foo>foo
@@ -31,6 +33,6 @@ cd ..
 echo % pull
 cd copy-pull
 echo '[hooks]' >> .hg/hgrc
-echo 'changegroup = echo changegroup: u=$HG_URL' >> .hg/hgrc
+echo 'changegroup = python ../printenv.py changegroup' >> .hg/hgrc
 hg pull
 cd ..
--- a/tests/test-http.out
+++ b/tests/test-http.out
@@ -31,7 +31,7 @@ checking files
 1 files, 1 changesets, 1 total revisions
 adding bar
 % pull
-changegroup: u=http://localhost:20059/
+changegroup hook: HG_NODE=cfbd11a1fa315300a080c3de8fe36b0fc5820acf HG_SOURCE=pull HG_URL=http://localhost:20059/ 
 pulling from http://localhost:20059/
 searching for changes
 adding changesets
--- a/tests/test-hup.out
+++ b/tests/test-hup.out
@@ -4,4 +4,4 @@ adding changesets
 killed!
 transaction abort!
 rollback completed
-.hg/00changelog.i .hg/journal.dirstate .hg/requires .hg/store .hg/store/00changelog.i
+.hg/00changelog.i .hg/journal.dirstate .hg/requires .hg/store .hg/store/00changelog.i .hg/store/00changelog.i.a
new file mode 100755
--- /dev/null
+++ b/tests/test-impexp-branch
@@ -0,0 +1,45 @@
+#!/bin/sh
+
+cat >findbranch.py <<EOF
+import re, sys
+
+head_re = re.compile('^#(?:(?:\\s+([A-Za-z][A-Za-z0-9_]*)(?:\\s.*)?)|(?:\\s*))$')
+
+for line in sys.stdin:
+    hmatch = head_re.match(line)
+    if not hmatch:
+        sys.exit(1)
+    if hmatch.group(1) == 'Branch':
+        sys.exit(0)
+sys.exit(1)
+EOF
+hg init a
+cd a
+echo "Rev 1" >rev
+hg add rev
+hg commit -m "No branch."
+hg branch abranch
+echo "Rev  2" >rev
+hg commit -m "With branch."
+if hg export 0 | python ../findbranch.py; then
+    echo "Export of default branch revision has Branch header" 1>&2
+    exit 1
+fi
+if hg export 1 | python ../findbranch.py; then
+    :  # Do nothing
+else
+    echo "Export of branch revision is missing Branch header" 1>&2
+    exit 1
+fi
+# Make sure import still works with branch information in patches.
+cd ..
+hg init b
+cd b
+hg -R ../a export 0 | hg import -
+hg -R ../a export 1 | hg import -
+cd ..
+rm -rf b
+hg init b
+cd b
+hg -R ../a export 0 | hg import --exact -
+hg -R ../a export 1 | hg import --exact -
new file mode 100644
--- /dev/null
+++ b/tests/test-impexp-branch.out
@@ -0,0 +1,4 @@
+applying patch from stdin
+applying patch from stdin
+applying patch from stdin
+applying patch from stdin
--- a/tests/test-incoming-outgoing.out
+++ b/tests/test-incoming-outgoing.out
@@ -4,6 +4,7 @@ checking manifests
 crosschecking files in changesets and manifests
 checking files
 1 files, 9 changesets, 9 total revisions
+comparing with http://localhost:20059/
 changeset:   0:9cb21d99fe27
 user:        test
 date:        Mon Jan 12 13:46:40 1970 +0000
@@ -50,6 +51,7 @@ user:        test
 date:        Mon Jan 12 13:46:40 1970 +0000
 summary:     8
 
+comparing with http://localhost:20059/
 changeset:   0:9cb21d99fe27
 user:        test
 date:        Mon Jan 12 13:46:40 1970 +0000
@@ -75,6 +77,7 @@ user:        test
 date:        Mon Jan 12 13:46:40 1970 +0000
 summary:     4
 
+comparing with test
 changeset:   0:9cb21d99fe27
 user:        test
 date:        Mon Jan 12 13:46:40 1970 +0000
@@ -121,6 +124,7 @@ user:        test
 date:        Mon Jan 12 13:46:40 1970 +0000
 summary:     8
 
+comparing with test
 changeset:   0:9cb21d99fe27
 user:        test
 date:        Mon Jan 12 13:46:40 1970 +0000
@@ -146,6 +150,7 @@ user:        test
 date:        Mon Jan 12 13:46:40 1970 +0000
 summary:     4
 
+comparing with http://localhost:20059/
 changeset:   0:9cb21d99fe27
 user:        test
 date:        Mon Jan 12 13:46:40 1970 +0000
@@ -192,6 +197,7 @@ user:        test
 date:        Mon Jan 12 13:46:40 1970 +0000
 summary:     8
 
+comparing with test
 changeset:   0:9cb21d99fe27
 user:        test
 date:        Mon Jan 12 13:46:40 1970 +0000
@@ -266,6 +272,7 @@ checking manifests
 crosschecking files in changesets and manifests
 checking files
 1 files, 14 changesets, 14 total revisions
+comparing with test
 searching for changes
 changeset:   9:3741c3ad1096
 user:        test
@@ -293,6 +300,7 @@ user:        test
 date:        Mon Jan 12 13:46:40 1970 +0000
 summary:     13
 
+comparing with http://localhost:20059/
 searching for changes
 changeset:   9:3741c3ad1096
 user:        test
@@ -320,6 +328,7 @@ user:        test
 date:        Mon Jan 12 13:46:40 1970 +0000
 summary:     13
 
+comparing with http://localhost:20059/
 searching for changes
 changeset:   9:3741c3ad1096
 user:        test
--- a/tests/test-init
+++ b/tests/test-init
@@ -2,47 +2,68 @@
 
 # This test tries to exercise the ssh functionality with a dummy script
 
-cat <<'EOF' > dummyssh
-#!/bin/sh
-# this attempts to deal with relative pathnames
-cd `dirname $0`
+cat <<EOF > dummyssh
+import sys
+import os
 
-# check for proper args
-if [ $1 != "user@dummy" ] ; then
-	exit -1
-fi
+os.chdir(os.path.dirname(sys.argv[0]))
+if sys.argv[1] != "user@dummy":
+    sys.exit(-1)
+
+if not os.path.exists("dummyssh"):
+    sys.exit(-1)
 
-# check that we're in the right directory
-if [ ! -x dummyssh ] ; then
-	exit -1
-fi
+log = open("dummylog", "ab")
+log.write("Got arguments")
+for i, arg in enumerate(sys.argv[1:]):
+    log.write(" %d:%s" % (i+1, arg))
+log.write("\n")
+log.close()
+r = os.system(sys.argv[2])
+sys.exit(bool(r))
+EOF
 
-echo Got arguments 1:$1 2:$2 3:$3 4:$4 5:$5 >> dummylog
-$2
-EOF
-chmod +x dummyssh
+checknewrepo()
+{
+    name=$1
+
+    if [ -d $name/.hg/store ]; then
+	echo store created
+    fi
+
+    if [ -f $name/.hg/00changelog.i ]; then
+	echo 00changelog.i created
+    fi
+
+    cat $name/.hg/requires
+}
 
 echo "# creating 'local'"
 hg init local
+checknewrepo local
 echo this > local/foo
 hg ci --cwd local -A -m "init" -d "1000000 0"
 
+echo "# creating repo with old format"
+hg --config format.usestore=false init old
+checknewrepo old
+
 echo "#test failure"
 hg init local
 
 echo "# init+push to remote2"
-hg init -e ./dummyssh ssh://user@dummy/remote2
+hg init -e "python ./dummyssh" ssh://user@dummy/remote2
 hg incoming -R remote2 local
-hg push -R local -e ./dummyssh ssh://user@dummy/remote2
+hg push -R local -e "python ./dummyssh" ssh://user@dummy/remote2
 
 echo "# clone to remote1"
-hg clone -e ./dummyssh local ssh://user@dummy/remote1
+hg clone -e "python ./dummyssh" local ssh://user@dummy/remote1
 
 echo "# init to existing repo"
-hg init -e ./dummyssh ssh://user@dummy/remote1
+hg init -e "python ./dummyssh" ssh://user@dummy/remote1
 
 echo "# clone to existing repo"
-hg clone -e ./dummyssh local ssh://user@dummy/remote1
+hg clone -e "python ./dummyssh" local ssh://user@dummy/remote1
 
 echo "# output of dummyssh"
 cat dummylog
--- a/tests/test-init.out
+++ b/tests/test-init.out
@@ -1,8 +1,15 @@
 # creating 'local'
+store created
+00changelog.i created
+revlogv1
+store
 adding foo
+# creating repo with old format
+revlogv1
 #test failure
 abort: repository local already exists!
 # init+push to remote2
+comparing with local
 changeset:   0:c4e059d443be
 tag:         tip
 user:        test
@@ -28,13 +35,13 @@ abort: could not create remote repo!
 abort: repository remote1 already exists!
 abort: could not create remote repo!
 # output of dummyssh
-Got arguments 1:user@dummy 2:hg init remote2 3: 4: 5:
-Got arguments 1:user@dummy 2:hg -R remote2 serve --stdio 3: 4: 5:
-Got arguments 1:user@dummy 2:hg -R remote2 serve --stdio 3: 4: 5:
-Got arguments 1:user@dummy 2:hg init remote1 3: 4: 5:
-Got arguments 1:user@dummy 2:hg -R remote1 serve --stdio 3: 4: 5:
-Got arguments 1:user@dummy 2:hg init remote1 3: 4: 5:
-Got arguments 1:user@dummy 2:hg init remote1 3: 4: 5:
+Got arguments 1:user@dummy 2:hg init remote2
+Got arguments 1:user@dummy 2:hg -R remote2 serve --stdio
+Got arguments 1:user@dummy 2:hg -R remote2 serve --stdio
+Got arguments 1:user@dummy 2:hg init remote1
+Got arguments 1:user@dummy 2:hg -R remote1 serve --stdio
+Got arguments 1:user@dummy 2:hg init remote1
+Got arguments 1:user@dummy 2:hg init remote1
 # comparing repositories
 0:c4e059d443be
 0:c4e059d443be
--- a/tests/test-install
+++ b/tests/test-install
@@ -1,3 +1,3 @@
 #!/bin/sh
 
-HGMERGE=merge hg debuginstall
+hg debuginstall
--- a/tests/test-issue352
+++ b/tests/test-issue352
@@ -1,21 +1,22 @@
-#!/bin/bash
+#!/bin/sh
 # http://www.selenic.com/mercurial/bts/issue352
 
 hg init foo
 cd foo
 
-A=`echo -e -n 'he\rllo'`
+A=`printf 'he\rllo'`
 
-echo foo > "hell
-o"
 echo foo > "$A"
 hg add
 hg ci -A -m m
 rm "$A"
-ls
+
+echo foo > "hell
+o"
 hg add
-# BUG ? we don't walk on filenames with '\n' (regexp related) ?
-hg debugwalk
 hg ci -A -m m
 
+echo foo > "$A"
+hg debugwalk
+
 exit 0
--- a/tests/test-issue352.out
+++ b/tests/test-issue352.out
@@ -2,6 +2,13 @@ adding he
llo
 abort: '\n' and '\r' disallowed in filenames
 adding he
llo
 abort: '\n' and '\r' disallowed in filenames
-hell
+adding hell
+o
+abort: '\n' and '\r' disallowed in filenames
+adding hell
 o
-nothing changed
+abort: '\n' and '\r' disallowed in filenames
+f  he
llo  he
llo
+f  hell
+o  hell
+o
--- a/tests/test-locate
+++ b/tests/test-locate
@@ -1,5 +1,14 @@
 #!/bin/sh
-#
+
+hglocate()
+{
+    echo "hg locate $@"
+    hg locate "$@"
+    ret=$?
+    echo
+    return $ret
+}
+
 mkdir t
 cd t
 hg init
@@ -8,24 +17,40 @@ echo 0 > b
 echo 0 > t.h
 mkdir t
 echo 0 > t/x
+echo 0 > t/b
+echo 0 > t/e.h
+mkdir dir.h
+echo 0 > dir.h/foo
 hg ci -A -m m -d "1000000 0"
 touch nottracked
-hg locate a
-hg locate NONEXISTENT
-hg locate
+hglocate a && echo locate succeeded || echo locate failed
+hglocate NONEXISTENT && echo locate succeeded || echo locate failed
+hglocate
 hg rm a
 hg ci -m m -d "1000000 0"
-hg locate a
-hg locate NONEXISTENT
-hg locate
-hg locate -r 0 a
-hg locate -r 0 NONEXISTENT
-hg locate -r 0
+hglocate a
+hglocate NONEXISTENT
+hglocate relpath:NONEXISTENT
+hglocate
+hglocate -r 0 a
+hglocate -r 0 NONEXISTENT
+hglocate -r 0 relpath:NONEXISTENT
+hglocate -r 0
 echo % -I/-X with relative path should work
 cd t
-hg locate
-hg locate -I ../t
+hglocate
+hglocate -I ../t
 # test issue294
 cd ..
 rm -r t
-hg locate t
+hglocate 't/**'
+mkdir otherdir
+cd otherdir
+hglocate b
+hglocate '*.h'
+hglocate path:t/x
+hglocate 're:.*\.h$'
+hglocate -r 0 b
+hglocate -r 0 '*.h'
+hglocate -r 0 path:t/x
+hglocate -r 0 're:.*\.h$'
--- a/tests/test-locate.out
+++ b/tests/test-locate.out
@@ -1,27 +1,102 @@
 adding a
 adding b
+adding dir.h/foo
 adding t.h
+adding t/b
+adding t/e.h
 adding t/x
+hg locate a
 a
-NONEXISTENT: No such file or directory
+
+locate succeeded
+hg locate NONEXISTENT
+
+locate failed
+hg locate 
+a
+b
+dir.h/foo
+t.h
+t/b
+t/e.h
+t/x
+
+hg locate a
+
+hg locate NONEXISTENT
+
+hg locate relpath:NONEXISTENT
+
+hg locate 
+b
+dir.h/foo
+t.h
+t/b
+t/e.h
+t/x
+
+hg locate -r 0 a
+a
+
+hg locate -r 0 NONEXISTENT
+
+hg locate -r 0 relpath:NONEXISTENT
+
+hg locate -r 0
 a
 b
+dir.h/foo
 t.h
+t/b
+t/e.h
 t/x
-a: No such file or directory
-NONEXISTENT: No such file or directory
+
+% -I/-X with relative path should work
+hg locate 
 b
+dir.h/foo
 t.h
+t/b
+t/e.h
+t/x
+
+hg locate -I ../t
+t/b
+t/e.h
+t/x
+
+hg locate t/**
+t/b
+t/e.h
 t/x
-a
-NONEXISTENT: No such file in rev ce18e5bc5cd3
-a
-t/x
-b
-t.h
-% -I/-X with relative path should work
-b
-t.h
-t/x
-t/x
-t/x
+
+hg locate b
+../b
+../t/b
+
+hg locate *.h
+../t.h
+../t/e.h
+
+hg locate path:t/x
+../t/x
+
+hg locate re:.*\.h$
+../t.h
+../t/e.h
+
+hg locate -r 0 b
+../b
+../t/b
+
+hg locate -r 0 *.h
+../t.h
+../t/e.h
+
+hg locate -r 0 path:t/x
+../t/x
+
+hg locate -r 0 re:.*\.h$
+../t.h
+../t/e.h
+
--- a/tests/test-merge-commit
+++ b/tests/test-merge-commit
@@ -1,9 +1,6 @@
 #!/bin/sh
 # check that renames are correctly saved by a commit after a merge
 
-HGMERGE=merge
-export HGMERGE
-
 # test with the merge on 3 having the rename on the local parent
 hg init a
 cd a
--- a/tests/test-merge-local
+++ b/tests/test-merge-local
@@ -38,13 +38,13 @@ hg diff --nodates | grep "^[+-][^<>]"
 hg st
 
 echo "# local merge with conflicts"
-HGMERGE=merge hg co
+hg co
 hg co 0
 hg diff --nodates | grep "^[+-][^<>]"
 hg st
 
 echo "# local merge without conflicts"
 hg revert zzz2_merge_bad
-HGMERGE=merge hg co
+hg co
 hg diff --nodates | grep "^[+-][^<>]"
 hg st
--- a/tests/test-merge-local.out
+++ b/tests/test-merge-local.out
@@ -36,7 +36,7 @@ 2 files updated, 0 files merged, 3 files
 M zzz1_merge_ok
 M zzz2_merge_bad
 # local merge with conflicts
-merge: warning: conflicts during merge
+warning: conflicts during merge.
 merging zzz1_merge_ok
 merging zzz2_merge_bad
 merging zzz2_merge_bad failed!
--- a/tests/test-merge-revert
+++ b/tests/test-merge-revert
@@ -25,7 +25,7 @@ hg status
 hg id
 hg update -C 0
 echo "changed file1" >> file1
-HGMERGE=merge hg update
+hg update
 hg diff
 hg status
 hg id
--- a/tests/test-merge-revert2
+++ b/tests/test-merge-revert2
@@ -26,8 +26,8 @@ hg status
 hg id
 hg update -C 0
 echo "changed file1 different" >> file1
-HGMERGE=merge hg update
-hg diff --nodates | sed -e "s/\(<<<<<<<\) .*/\1/" -e "s/\(>>>>>>>\) .*/\1/"
+hg update
+hg diff --nodates
 hg status
 hg id
 hg revert --no-backup --all
--- a/tests/test-merge-revert2.out
+++ b/tests/test-merge-revert2.out
@@ -9,7 +9,7 @@ 9eca13a34789
 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 f248da0d4c3e tip
 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-merge: warning: conflicts during merge
+warning: conflicts during merge.
 merging file1
 merging file1 failed!
 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
@@ -23,11 +23,11 @@ diff -r f248da0d4c3e file1
 @@ -1,3 +1,7 @@ added file1
  added file1
  another line of text
-+<<<<<<<
++<<<<<<< my
 +changed file1 different
 +=======
  changed file1
-+>>>>>>>
++>>>>>>> other
 M file1
 f248da0d4c3e+ tip
 reverting file1
--- a/tests/test-merge1
+++ b/tests/test-merge1
@@ -1,10 +1,10 @@
 #!/bin/sh
 
-cat <<'EOF' > merge
-#!/bin/sh
-echo merging for `basename $1`
+cat <<EOF > merge
+import sys, os
+print "merging for", os.path.basename(sys.argv[1])
 EOF
-chmod +x merge
+HGMERGE="python ../merge"; export HGMERGE
 
 mkdir t
 cd t
@@ -22,7 +22,7 @@ hg add c
 hg commit -m "commit #2" -d "1000000 0"
 echo This is file b1 > b
 echo %% no merges expected
-env HGMERGE=../merge hg merge 1
+hg merge 1
 hg diff --nodates
 hg status
 cd ..; rm -r t
@@ -43,9 +43,9 @@ hg add c
 hg commit -m "commit #2" -d "1000000 0"
 echo This is file b2 > b
 echo %% merge should fail
-env HGMERGE=../merge hg merge 1
+hg merge 1
 echo %% merge of b expected
-env HGMERGE=../merge hg merge -f 1
+hg merge -f 1
 hg diff --nodates
 hg status
 cd ..; rm -r t
@@ -72,9 +72,9 @@ cat b
 
 echo This is file b22 > b
 echo %% merge fails
-env HGMERGE=../merge hg merge 2
+hg merge 2
 echo %% merge expected!
-env HGMERGE=../merge hg merge -f 2
+hg merge -f 2
 hg diff --nodates
 hg status
 cd ..; rm -r t
@@ -96,8 +96,8 @@ hg add c
 hg commit -m "commit #3" -d "1000000 0"
 echo This is file b33 > b
 echo %% merge of b should fail
-env HGMERGE=../merge hg merge 2
+hg merge 2
 echo %% merge of b expected
-env HGMERGE=../merge hg merge -f 2
+hg merge -f 2
 hg diff --nodates
 hg status
--- a/tests/test-merge6
+++ b/tests/test-merge6
@@ -1,11 +1,10 @@
 #!/bin/sh
 
-cat <<'EOF' > merge
-#!/bin/sh
-echo merging for `basename $1`
+cat <<EOF > merge
+import sys, os
+print "merging for", os.path.basename(sys.argv[1])
 EOF
-chmod +x merge
-HGMERGE=./merge; export HGMERGE
+HGMERGE="python ../merge"; export HGMERGE
 
 mkdir A1
 cd A1
--- a/tests/test-merge7
+++ b/tests/test-merge7
@@ -35,7 +35,7 @@ hg commit -m "2 -> 2.5" -d "1000000 0"
 
 # now pull and merge from test-a
 hg pull ../test-a
-HGMERGE=merge hg merge
+hg merge
 # resolve conflict
 cat >test.txt <<"EOF"
 one
@@ -57,9 +57,9 @@ hg commit -m "two -> two-point-one" -d "
 # pull and merge from test-a again
 cd ../test-b
 hg pull ../test-a
-HGMERGE=merge hg merge --debug
+hg merge --debug
 
-cat test.txt | sed "s% .*%%"
+cat test.txt
 
 hg debugindex .hg/store/data/test.txt.i
 
--- a/tests/test-merge7.out
+++ b/tests/test-merge7.out
@@ -6,7 +6,7 @@ adding manifests
 adding file changes
 added 1 changesets with 1 changes to 1 files (+1 heads)
 (run 'hg heads' to see heads, 'hg merge' to merge)
-merge: warning: conflicts during merge
+warning: conflicts during merge.
 merging test.txt
 merging test.txt failed!
 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
@@ -20,7 +20,7 @@ adding manifests
 adding file changes
 added 1 changesets with 1 changes to 1 files (+1 heads)
 (run 'hg heads' to see heads, 'hg merge' to merge)
-merge: warning: conflicts during merge
+warning: conflicts during merge.
 resolving manifests
  overwrite None partial False
  ancestor faaea63e63a9 local 451c744aabcc+ remote a070d41e8360
@@ -33,11 +33,11 @@ There are unresolved merges, you can red
   hg update -C 3
   hg merge 4
 one
-<<<<<<<
+<<<<<<< my
 two-point-five
 =======
 two-point-one
->>>>>>>
+>>>>>>> other
 three
    rev    offset  length   base linkrev nodeid       p1           p2
      0         0       7      0       0 01365c4cca56 000000000000 000000000000
--- a/tests/test-mq
+++ b/tests/test-mq
@@ -40,6 +40,40 @@ echo % qnew implies add
 hg -R c qnew test.patch
 hg -R c/.hg/patches st
 
+echo '% qinit; qinit -c'
+hg init d
+cd d
+hg qinit
+hg qinit -c
+# qinit -c should create both files if they don't exist
+echo '  .hgignore:'
+cat .hg/patches/.hgignore
+echo '  series:'
+cat .hg/patches/series
+hg qinit -c 2>&1 | sed -e 's/repository.*already/repository already/'
+cd ..
+
+echo '% qinit; <stuff>; qinit -c'
+hg init e
+cd e
+hg qnew A
+echo foo > foo
+hg add foo
+hg qrefresh
+hg qnew B
+echo >> foo
+hg qrefresh
+echo status >> .hg/patches/.hgignore
+echo bleh >> .hg/patches/.hgignore
+hg qinit -c
+hg -R .hg/patches status
+# qinit -c shouldn't touch these files if they already exist
+echo '  .hgignore:'
+cat .hg/patches/.hgignore
+echo '  series:'
+cat .hg/patches/series
+cd ..
+
 cd a
 
 echo % qnew -m
@@ -147,6 +181,46 @@ echo % push should succeed
 hg qpop -a
 hg push ../../k
 
+echo % qpush/qpop error codes
+errorcode()
+{
+    hg "$@" && echo "  $@ succeeds" || echo "  $@ fails"
+}
+
+# we want to start with some patches applied
+hg qpush -a
+echo "  % pops all patches and succeeds"
+errorcode qpop -a
+echo "  % does nothing and succeeds"
+errorcode qpop -a
+echo "  % fails - nothing else to pop"
+errorcode qpop
+echo "  % pushes a patch and succeeds"
+errorcode qpush
+echo "  % pops a patch and succeeds"
+errorcode qpop
+echo "  % pushes up to test1b.patch and succeeds"
+errorcode qpush test1b.patch
+echo "  % does nothing and succeeds"
+errorcode qpush test1b.patch
+echo "  % does nothing and succeeds"
+errorcode qpop test1b.patch
+echo "  % fails - can't push to this patch"
+errorcode qpush test.patch
+echo "  % fails - can't pop to this patch"
+errorcode qpop test2.patch
+echo "  % pops up to test.patch and succeeds"
+errorcode qpop test.patch
+echo "  % pushes all patches and succeeds"
+errorcode qpush -a
+echo "  % does nothing and succeeds"
+errorcode qpush -a
+echo "  % fails - nothing else to push"
+errorcode qpush
+echo "  % does nothing and succeeds"
+errorcode qpush test2.patch
+
+
 echo % strip
 cd ../../b
 echo x>x
@@ -188,6 +262,9 @@ echo bar > foo
 hg qpush -a
 hg st
 
+echo % mq tags
+hg log --template '{rev} {tags}\n' -r qparent:qtip
+
 cat >>$HGRCPATH <<EOF
 [diff]
 git = True
--- a/tests/test-mq-merge
+++ b/tests/test-mq-merge
@@ -37,3 +37,6 @@ hg ci -m update
 hg manifest
 hg qpush -a -m 2>&1 | rewrite_path
 hg manifest
+
+# ensure status is correct after merge
+hg qpop -a
--- a/tests/test-mq-merge.out
+++ b/tests/test-mq-merge.out
@@ -9,3 +9,4 @@ merging with queue at: .hg/patches.1
 applying rm_a
 Now at: rm_a
 b
+Patch queue now empty
new file mode 100755
--- /dev/null
+++ b/tests/test-mq-qgoto
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+echo "[extensions]" >> $HGRCPATH
+echo "mq=" >> $HGRCPATH
+
+hg init a
+cd a
+echo a > a
+hg ci -Ama
+
+hg qnew a.patch
+echo a >> a
+hg qrefresh
+
+hg qnew b.patch
+echo b > b
+hg add b
+hg qrefresh
+
+hg qnew c.patch
+echo c > c
+hg add c
+hg qrefresh
+
+hg qgoto a.patch
+hg qgoto c.patch
+hg qgoto b.patch
new file mode 100644
--- /dev/null
+++ b/tests/test-mq-qgoto.out
@@ -0,0 +1,6 @@
+adding a
+Now at: a.patch
+applying b.patch
+applying c.patch
+Now at: c.patch
+Now at: b.patch
--- a/tests/test-mq.out
+++ b/tests/test-mq.out
@@ -30,6 +30,7 @@ list of commands:
  qdelete      remove patches from queue
  qdiff        diff of the current patch
  qfold        fold the named patches into the current patch
+ qgoto        push or pop patches until named patch is at top of stack
  qguard       set or print guards for a patch
  qheader      Print the header of the topmost or specified patch
  qimport      import a patch
@@ -62,6 +63,26 @@ A series
 A .hgignore
 A series
 A test.patch
+% qinit; qinit -c
+  .hgignore:
+syntax: glob
+status
+guards
+  series:
+abort: repository already exists!
+% qinit; <stuff>; qinit -c
+adding .hg/patches/A
+adding .hg/patches/B
+A .hgignore
+A A
+A B
+A series
+  .hgignore:
+status
+bleh
+  series:
+A
+B
 % qnew -m
 foo bar
 % qrefresh
@@ -149,6 +170,61 @@ adding changesets
 adding manifests
 adding file changes
 added 1 changesets with 1 changes to 1 files
+% qpush/qpop error codes
+applying test.patch
+applying test1b.patch
+applying test2.patch
+Now at: test2.patch
+  % pops all patches and succeeds
+Patch queue now empty
+  qpop -a succeeds
+  % does nothing and succeeds
+no patches applied
+  qpop -a succeeds
+  % fails - nothing else to pop
+no patches applied
+  qpop fails
+  % pushes a patch and succeeds
+applying test.patch
+Now at: test.patch
+  qpush succeeds
+  % pops a patch and succeeds
+Patch queue now empty
+  qpop succeeds
+  % pushes up to test1b.patch and succeeds
+applying test.patch
+applying test1b.patch
+Now at: test1b.patch
+  qpush test1b.patch succeeds
+  % does nothing and succeeds
+qpush: test1b.patch is already at the top
+  qpush test1b.patch succeeds
+  % does nothing and succeeds
+qpop: test1b.patch is already at the top
+  qpop test1b.patch succeeds
+  % fails - can't push to this patch
+abort: cannot push to a previous patch: test.patch
+  qpush test.patch fails
+  % fails - can't pop to this patch
+abort: patch test2.patch is not applied
+  qpop test2.patch fails
+  % pops up to test.patch and succeeds
+Now at: test.patch
+  qpop test.patch succeeds
+  % pushes all patches and succeeds
+applying test1b.patch
+applying test2.patch
+Now at: test2.patch
+  qpush -a succeeds
+  % does nothing and succeeds
+all patches are currently applied
+  qpush -a succeeds
+  % fails - nothing else to push
+patch series already fully applied
+  qpush fails
+  % does nothing and succeeds
+all patches are currently applied
+  qpush test2.patch succeeds
 % strip
 adding x
 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
@@ -192,6 +268,10 @@ patch failed, rejects left in working di
 Errors during apply, please fix and refresh bar
 ? foo
 ? foo.rej
+% mq tags
+0 qparent
+1 qbase foo
+2 qtip bar tip
 new file
 
 diff --git a/new b/new
--- a/tests/test-newbranch
+++ b/tests/test-newbranch
@@ -12,7 +12,9 @@ hg branch
 hg ci -m "add branch name" -d "1000000 0"
 hg branch bar
 hg ci -m "change branch name" -d "1000000 0"
+echo % branch shadowing
 hg branch default
+hg branch -f default
 hg ci -m "clear branch name" -d "1000000 0"
 
 hg co foo
@@ -49,4 +51,15 @@ hg id
 hg branch foobar
 hg up
 
+echo % fastforward merge
+hg branch ff
+echo ff > ff
+hg ci -Am'fast forward' -d '1000000 0'
+hg up foo
+hg merge ff
+hg branch
+hg commit -m'Merge ff into foo' -d '1000000 0'
+hg parents
+hg manifest
+
 exit 0
--- a/tests/test-newbranch.out
+++ b/tests/test-newbranch.out
@@ -1,4 +1,6 @@
 foo
+% branch shadowing
+abort: a branch of the same name already exists (use --force to override)
 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 foo
 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -82,3 +84,18 @@ 67ec16bde7f1575d523313b9bca000f6a6f12dca
 bf1bc2f45e83
 4909a3732169 (foo) tip
 abort: branch foobar not found
+% fastforward merge
+adding ff
+0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+(branch merge, don't forget to commit)
+foo
+changeset:   6:9cc105113eeb
+branch:      foo
+tag:         tip
+user:        test
+date:        Mon Jan 12 13:46:40 1970 +0000
+summary:     Merge ff into foo
+
+a
+ff
--- a/tests/test-patchbomb.out
+++ b/tests/test-patchbomb.out
@@ -1,11 +1,12 @@
 adding a
 hg email: option --date not recognized
-hg email [OPTION]... [REV]...
+hg email [OPTION]... [DEST]...
+
+send changesets by email
 
-send changesets as a series of patch emails
-
-    The series starts with a "[PATCH 0 of N]" introduction, which
-    describes the series as a whole.
+    By default, diffs are sent in the format generated by hg export,
+    one per message.  The series starts with a "[PATCH 0 of N]"
+    introduction, which describes the series as a whole.
 
     Each patch email has a Subject line of "[PATCH M of N] ...", using
     the first line of the changeset description as the subject text.
@@ -14,29 +15,65 @@ send changesets as a series of patch ema
     program is installed, the result of running diffstat on the patch.
     Finally, the patch itself, as generated by "hg export".
 
+    With --outgoing, emails will be generated for patches not
+    found in the destination repository (or only those which are
+    ancestors of the specified revisions if any are provided)
+
+    With --bundle, changesets are selected as for --outgoing,
+    but a single email containing a binary Mercurial bundle as an
+    attachment will be sent.
+
+    Examples:
+
+    hg email -r 3000          # send patch 3000 only
+    hg email -r 3000 -r 3001  # send patches 3000 and 3001
+    hg email -r 3000:3005     # send patches 3000 through 3005
+    hg email 3000             # send patch 3000 (deprecated)
+
+    hg email -o               # send all patches not in default
+    hg email -o DEST          # send all patches not in DEST
+    hg email -o -r 3000       # send all ancestors of 3000 not in default
+    hg email -o -r 3000 DEST  # send all ancestors of 3000 not in DEST
+
+    hg email -b               # send bundle of all patches not in default
+    hg email -b DEST          # send bundle of all patches not in DEST
+    hg email -b -r 3000       # bundle of all ancestors of 3000 not in default
+    hg email -b -r 3000 DEST  # bundle of all ancestors of 3000 not in DEST
+
+    Before using this command, you will need to enable email in your hgrc.
+    See the [email] section in hgrc(5) for details.
+
 options:
 
- -a --attach    send patches as inline attachments
-    --bcc       email addresses of blind copy recipients
- -c --cc        email addresses of copy recipients
- -d --diffstat  add diffstat output to messages
- -g --git       use git extended diff format
- -f --from      email address of sender
-    --plain     omit hg patch header
- -n --test      print messages that would be sent
- -m --mbox      write messages to mbox file instead of sending them
- -s --subject   subject of first message (intro or single patch)
- -t --to        email addresses of recipients
+ -a --attach     send patches as inline attachments
+    --bcc        email addresses of blind copy recipients
+ -c --cc         email addresses of copy recipients
+ -d --diffstat   add diffstat output to messages
+ -g --git        use git extended diff format
+ -f --from       email address of sender
+    --plain      omit hg patch header
+ -n --test       print messages that would be sent
+ -m --mbox       write messages to mbox file instead of sending them
+ -o --outgoing   send changes not found in the target repository
+ -b --bundle     send changes not in target as a binary bundle
+ -r --rev        a revision to send
+ -s --subject    subject of first message (intro or single patch)
+ -t --to         email addresses of recipients
+    --force      run even when remote repository is unrelated (with -b)
+    --base       a base changeset to specify instead of a destination (with -b)
+ -e --ssh        specify ssh command to use
+    --remotecmd  specify hg command to run on the remote side
 
 use "hg -v help email" to show global options
 adding b
 hg email: option --date not recognized
-hg email [OPTION]... [REV]...
+hg email [OPTION]... [DEST]...
+
+send changesets by email
 
-send changesets as a series of patch emails
-
-    The series starts with a "[PATCH 0 of N]" introduction, which
-    describes the series as a whole.
+    By default, diffs are sent in the format generated by hg export,
+    one per message.  The series starts with a "[PATCH 0 of N]"
+    introduction, which describes the series as a whole.
 
     Each patch email has a Subject line of "[PATCH M of N] ...", using
     the first line of the changeset description as the subject text.
@@ -45,18 +82,53 @@ send changesets as a series of patch ema
     program is installed, the result of running diffstat on the patch.
     Finally, the patch itself, as generated by "hg export".
 
+    With --outgoing, emails will be generated for patches not
+    found in the destination repository (or only those which are
+    ancestors of the specified revisions if any are provided)
+
+    With --bundle, changesets are selected as for --outgoing,
+    but a single email containing a binary Mercurial bundle as an
+    attachment will be sent.
+
+    Examples:
+
+    hg email -r 3000          # send patch 3000 only
+    hg email -r 3000 -r 3001  # send patches 3000 and 3001
+    hg email -r 3000:3005     # send patches 3000 through 3005
+    hg email 3000             # send patch 3000 (deprecated)
+
+    hg email -o               # send all patches not in default
+    hg email -o DEST          # send all patches not in DEST
+    hg email -o -r 3000       # send all ancestors of 3000 not in default
+    hg email -o -r 3000 DEST  # send all ancestors of 3000 not in DEST
+
+    hg email -b               # send bundle of all patches not in default
+    hg email -b DEST          # send bundle of all patches not in DEST
+    hg email -b -r 3000       # bundle of all ancestors of 3000 not in default
+    hg email -b -r 3000 DEST  # bundle of all ancestors of 3000 not in DEST
+
+    Before using this command, you will need to enable email in your hgrc.
+    See the [email] section in hgrc(5) for details.
+
 options:
 
- -a --attach    send patches as inline attachments
-    --bcc       email addresses of blind copy recipients
- -c --cc        email addresses of copy recipients
- -d --diffstat  add diffstat output to messages
- -g --git       use git extended diff format
- -f --from      email address of sender
-    --plain     omit hg patch header
- -n --test      print messages that would be sent
- -m --mbox      write messages to mbox file instead of sending them
- -s --subject   subject of first message (intro or single patch)
- -t --to        email addresses of recipients
+ -a --attach     send patches as inline attachments
+    --bcc        email addresses of blind copy recipients
+ -c --cc         email addresses of copy recipients
+ -d --diffstat   add diffstat output to messages
+ -g --git        use git extended diff format
+ -f --from       email address of sender
+    --plain      omit hg patch header
+ -n --test       print messages that would be sent
+ -m --mbox       write messages to mbox file instead of sending them
+ -o --outgoing   send changes not found in the target repository
+ -b --bundle     send changes not in target as a binary bundle
+ -r --rev        a revision to send
+ -s --subject    subject of first message (intro or single patch)
+ -t --to         email addresses of recipients
+    --force      run even when remote repository is unrelated (with -b)
+    --base       a base changeset to specify instead of a destination (with -b)
+ -e --ssh        specify ssh command to use
+    --remotecmd  specify hg command to run on the remote side
 
 use "hg -v help email" to show global options
new file mode 100755
--- /dev/null
+++ b/tests/test-purge
@@ -0,0 +1,99 @@
+#!/bin/sh
+
+cat <<EOF >> $HGRCPATH
+[extensions]
+hgext.purge=
+EOF
+
+echo % init
+hg init t
+cd t
+
+echo % setup
+echo r1 > r1
+hg ci -qAmr1 -d'0 0'
+mkdir directory
+echo r2 > directory/r2
+hg ci -qAmr2 -d'1 0'
+echo 'ignored' > .hgignore
+hg ci -qAmr3 -d'2 0'
+
+echo % delete an empty directory
+mkdir empty_dir
+hg purge -p
+hg purge -v
+ls
+
+echo % delete an untracked directory
+mkdir untracked_dir
+touch untracked_dir/untracked_file1
+touch untracked_dir/untracked_file2
+hg purge -p
+hg purge -v
+ls
+
+echo % delete an untracked file
+touch untracked_file
+hg purge -p
+hg purge -v
+ls
+
+echo % delete an untracked file in a tracked directory
+touch directory/untracked_file
+hg purge -p
+hg purge -v
+ls
+
+echo % delete nested directories
+mkdir -p untracked_directory/nested_directory
+hg purge -p
+hg purge -v
+ls
+
+echo % delete nested directories from a subdir
+mkdir -p untracked_directory/nested_directory
+cd directory
+hg purge -p
+hg purge -v
+cd ..
+ls
+
+echo % delete only part of the tree
+mkdir -p untracked_directory/nested_directory
+touch directory/untracked_file
+cd directory
+hg purge -p ../untracked_directory
+hg purge -v ../untracked_directory
+cd ..
+ls
+ls directory/untracked_file
+rm directory/untracked_file
+
+echo % delete ignored files
+touch ignored
+hg purge -p
+hg purge -v
+ls
+
+echo % abort with missing files until we support name mangling filesystems
+touch untracked_file
+rm r1
+# hide error messages to avoid changing the output when the text changes
+hg purge -p 2> /dev/null
+if [ $? -ne 0 ]; then
+    echo "refused to run"
+fi
+if [ -f untracked_file ]; then
+    echo "untracked_file still around"
+fi
+hg purge -p --force
+hg purge -v 2> /dev/null
+if [ $? -ne 0 ]; then
+    echo "refused to run"
+fi
+if [ -f untracked_file ]; then
+    echo "untracked_file still around"
+fi
+hg purge -v --force
+hg revert --all --quiet
+ls
new file mode 100644
--- /dev/null
+++ b/tests/test-purge.out
@@ -0,0 +1,58 @@
+% init
+% setup
+% delete an empty directory
+empty_dir
+Removing directory empty_dir
+directory
+r1
+% delete an untracked directory
+untracked_dir/untracked_file1
+untracked_dir/untracked_file2
+Removing file untracked_dir/untracked_file1
+Removing file untracked_dir/untracked_file2
+Removing directory untracked_dir
+directory
+r1
+% delete an untracked file
+untracked_file
+Removing file untracked_file
+directory
+r1
+% delete an untracked file in a tracked directory
+directory/untracked_file
+Removing file directory/untracked_file
+directory
+r1
+% delete nested directories
+untracked_directory/nested_directory
+Removing directory untracked_directory/nested_directory
+Removing directory untracked_directory
+directory
+r1
+% delete nested directories from a subdir
+untracked_directory/nested_directory
+Removing directory untracked_directory/nested_directory
+Removing directory untracked_directory
+directory
+r1
+% delete only part of the tree
+untracked_directory/nested_directory
+Removing directory untracked_directory/nested_directory
+Removing directory untracked_directory
+directory
+r1
+directory/untracked_file
+% delete ignored files
+ignored
+Removing file ignored
+directory
+r1
+% abort with missing files until we support name mangling filesystems
+refused to run
+untracked_file still around
+untracked_file
+refused to run
+untracked_file still around
+Removing file untracked_file
+directory
+r1
--- a/tests/test-push-http
+++ b/tests/test-push-http
@@ -1,15 +1,17 @@
 #!/bin/sh
 
+cp "$TESTDIR"/printenv.py .
+
 hg init test
 cd test
 echo a > a
-hg ci -Ama
+hg ci -Ama -d '0 0'
 
 cd ..
 hg clone test test2
 cd test2
 echo a >> a
-hg ci -mb
+hg ci -mb -d '0 0'
 
 cd ../test
 
@@ -37,14 +39,14 @@ kill `cat hg.pid`
 echo % expect success
 echo 'allow_push = *' >> .hg/hgrc
 echo '[hooks]' >> .hg/hgrc
-echo 'changegroup = echo changegroup: u=$HG_URL >> $HGTMP/urls' >> .hg/hgrc
+echo 'changegroup = python ../printenv.py changegroup 0 ../urls' >> .hg/hgrc
 hg serve -p 20059 -d --pid-file=hg.pid
 cat hg.pid >> $DAEMON_PIDS
 hg --cwd ../test2 push http://localhost:20059/
 kill `cat hg.pid`
 hg rollback
 
-sed 's/\(remote:http.*\):.*/\1/' $HGTMP/urls
+cat ../urls
 
 echo % expect authorization error: all users denied
 echo '[web]' > .hg/hgrc
--- a/tests/test-push-http.out
+++ b/tests/test-push-http.out
@@ -20,7 +20,7 @@ adding manifests
 adding file changes
 added 1 changesets with 1 changes to 1 files
 rolling back last transaction
-changegroup: u=remote:http
+changegroup hook: HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_URL=remote:http 
 % expect authorization error: all users denied
 pushing to http://localhost:20059/
 searching for changes
--- a/tests/test-remove
+++ b/tests/test-remove
@@ -23,10 +23,23 @@ hg add a
 hg rm a
 hg rm -f a
 echo b > b
+mkdir c
+echo d > c/d
 hg ci -A -m 3 -d "1000001 0"
 echo c >> b
 hg rm b
 hg rm -f b
+hg rm -A c/d
+hg st
+cat c/d
+hg revert c
+hg rm -A
+hg st
+hg rm -A c
+hg st
+rm c/d
+hg rm -A
+hg st
 
 cd ..
 hg clone a b
--- a/tests/test-remove.out
+++ b/tests/test-remove.out
@@ -52,5 +52,15 @@ diff -r 8ba83d44753d -r a1fce69c50d9 foo
 not removing a: file has been marked for add (use -f to force removal)
 adding a
 adding b
+adding c/d
 not removing b: file is modified (use -f to force removal)
-2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+R b
+R c/d
+d
+undeleting c/d
+R b
+R b
+removing c/d
+R b
+R c/d
+3 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- a/tests/test-rename-merge2
+++ b/tests/test-rename-merge2
@@ -3,11 +3,13 @@
 mkdir -p t
 cd t
 
-cat <<'EOF' > merge
-#!/bin/sh
-echo merge $1 $2 $3 > $1
+cat <<EOF > merge
+import sys, os
+f = open(sys.argv[1], "wb")
+f.write("merge %s %s %s" % (sys.argv[1], sys.argv[2], sys.argv[3]))
+f.close()
 EOF
-chmod +x merge
+HGMERGE="python ../merge"; export HGMERGE
 
 # perform a test merge with possible renaming
 # 
@@ -49,7 +51,7 @@ tm()
     echo "--------------"
     echo "test L:$1 R:$2 W:$3 - $4"
     echo "--------------"
-    env HGMERGE=../merge hg merge -y --debug --traceback
+    hg merge -y --debug --traceback
 
     echo "--------------"
     hg status -camC -X rev
--- a/tests/test-revert
+++ b/tests/test-revert
@@ -92,4 +92,6 @@ mkdir newdir
 echo foo > newdir/newfile
 hg add newdir/newfile
 hg revert b newdir
+echo foobar > b/b
+hg revert .
 true
--- a/tests/test-revert.out
+++ b/tests/test-revert.out
@@ -62,3 +62,4 @@ reverting a
 adding b/b
 reverting b/b
 forgetting newdir/newfile
+reverting b/b
new file mode 100755
--- /dev/null
+++ b/tests/test-simplemerge-cmd
@@ -0,0 +1,61 @@
+#!/bin/sh
+
+cp "$TESTDIR"/../contrib/simplemerge .
+
+echo base > base
+
+echo local > local
+cat base >> local
+cp local orig
+
+cat base > other
+echo other >> other
+
+echo '% changing local directly'
+python simplemerge local base other && echo "merge succeeded"
+cat local
+cp orig local
+
+echo '% printing to stdout'
+python simplemerge -p local base other
+echo ' local:'
+cat local
+
+echo '% conflicts'
+cp base conflict-local
+cp other conflict-other
+echo not other >> conflict-local
+echo end >> conflict-local
+echo end >> conflict-other
+python simplemerge -p conflict-local base conflict-other || echo "merge failed"
+
+echo '% --no-minimal'
+python simplemerge -p --no-minimal conflict-local base conflict-other
+
+echo '% 1 label'
+python simplemerge -p -L foo conflict-local base conflict-other
+
+echo '% 2 labels'
+python simplemerge -p -L foo -L bar conflict-local base conflict-other
+
+echo '% too many labels'
+python simplemerge -p -L foo -L bar -L baz conflict-local base conflict-other
+
+echo '% binary file'
+python -c "f = file('binary-local', 'w'); f.write('\x00'); f.close()"
+cat orig >> binary-local
+python simplemerge -p binary-local base other
+
+echo '% binary file --text'
+python simplemerge -a -p binary-local base other
+
+echo '% help'
+python simplemerge --help
+
+echo '% wrong number of arguments'
+python simplemerge
+
+echo '% bad option'
+python simplemerge --foo -p local base other
+
+exit 0
new file mode 100644
index 0000000000000000000000000000000000000000..8141eb588a69a7b55ec50be707781ceb376ad3dc
GIT binary patch
literal 2581
zc%1E(J#X7E5QaPJSKOklGSH4jTfiv-bTHte&Q^4w#FIprA{p{jNB#9Zl2YuXA906n
z=3q#t`*@D`k}jd?h3S;(K-)%Wkji62dMXC=9YPqJ22r9c(&wTU0Sik#`l1Bqm7y3)
z76M5-(yv}Q?fU#f35_+aRt<%0c$e3sxysJQ667TB^O{qB$m`^u&1htC6y7k<`cwhr
z=p$lkXEk@#3Z=QF5~|ABYEVWE3*3t=vCLDRX!!%vi##Q~0WE5LR=u_MzqDV%+gvXE
zd%ap*T!gHJL6~W&C2H%51B3x<^aR0SquL4RXlIugRVyR>1TD{IiO8lr16Gnf1kmb$
zAkLki#qv-IALzt+VUkzBQFxEq6+sP7V^+@B-RHagH`wkzZnki<+26wM?$hm;q5xj|
z<OtMz#Of$!oE$ik(uyWHD(boNtT|{=(CGP`A+1u5^s{k}cZp9`uEQ|-m|_5f4Tcb@
z`EzHX^>)Z0{GM3O^4v@yu@$4H*K;Zrz<x)6^oj^gritl22KM5QEx$%5hhC7fW>pl{
z#fPvipxW~0rh!3Td3dq(7?5ZCs0kjwKcEjBBj$n3AwZsc2BCG@o&bX5<Gi49?3wPA
z*)u;cXvzdaAtU0_LKI!<D|LH*m;t1PsA$SNmCWh~o&Y^|u`4dyb7UO!0w`5*nx~g+
z!brde74h!b?|xLc;DymShU3i|$?@G+!qHnMGUHIAkDS7H<A8?mxibx|FLS#}?JBju
iNUe@|X}6wgE894nuk=L4f9JL{%p<Srx~l6Ub^Qd3d`%eu
new file mode 100644
--- /dev/null
+++ b/tests/test-simplemerge.py
@@ -0,0 +1,409 @@
+# Copyright (C) 2004, 2005 Canonical Ltd
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+import os
+import unittest
+from unittest import TestCase
+import imp
+import shutil
+from mercurial import util
+
+# copy simplemerge to the cwd to avoid creating a .pyc file in the source tree
+shutil.copyfile(os.path.join(os.environ['TESTDIR'], os.path.pardir,
+                             'contrib', 'simplemerge'),
+                'simplemerge.py')
+simplemerge = imp.load_source('simplemerge', 'simplemerge.py')
+Merge3 = simplemerge.Merge3
+CantReprocessAndShowBase = simplemerge.CantReprocessAndShowBase
+
+def split_lines(t):
+    from cStringIO import StringIO
+    return StringIO(t).readlines()
+
+############################################################
+# test case data from the gnu diffutils manual
+# common base
+TZU = split_lines("""     The Nameless is the origin of Heaven and Earth;
+     The named is the mother of all things.
+     
+     Therefore let there always be non-being,
+       so we may see their subtlety,
+     And let there always be being,
+       so we may see their outcome.
+     The two are the same,
+     But after they are produced,
+       they have different names.
+     They both may be called deep and profound.
+     Deeper and more profound,
+     The door of all subtleties!
+""")
+
+LAO = split_lines("""     The Way that can be told of is not the eternal Way;
+     The name that can be named is not the eternal name.
+     The Nameless is the origin of Heaven and Earth;
+     The Named is the mother of all things.
+     Therefore let there always be non-being,
+       so we may see their subtlety,
+     And let there always be being,
+       so we may see their outcome.
+     The two are the same,
+     But after they are produced,
+       they have different names.
+""")
+
+
+TAO = split_lines("""     The Way that can be told of is not the eternal Way;
+     The name that can be named is not the eternal name.
+     The Nameless is the origin of Heaven and Earth;
+     The named is the mother of all things.
+     
+     Therefore let there always be non-being,
+       so we may see their subtlety,
+     And let there always be being,
+       so we may see their result.
+     The two are the same,
+     But after they are produced,
+       they have different names.
+     
+       -- The Way of Lao-Tzu, tr. Wing-tsit Chan
+
+""")
+
+MERGED_RESULT = split_lines("""     The Way that can be told of is not the eternal Way;
+     The name that can be named is not the eternal name.
+     The Nameless is the origin of Heaven and Earth;
+     The Named is the mother of all things.
+     Therefore let there always be non-being,
+       so we may see their subtlety,
+     And let there always be being,
+       so we may see their result.
+     The two are the same,
+     But after they are produced,
+       they have different names.
+<<<<<<< LAO
+=======
+     
+       -- The Way of Lao-Tzu, tr. Wing-tsit Chan
+
+>>>>>>> TAO
+""")
+
+class TestMerge3(TestCase):
+    def log(self, msg):
+        pass
+
+    def test_no_changes(self):
+        """No conflicts because nothing changed"""
+        m3 = Merge3(['aaa', 'bbb'],
+                    ['aaa', 'bbb'],
+                    ['aaa', 'bbb'])
+
+        self.assertEquals(m3.find_unconflicted(),
+                          [(0, 2)])
+
+        self.assertEquals(list(m3.find_sync_regions()),
+                          [(0, 2,
+                            0, 2,
+                            0, 2),
+                           (2,2, 2,2, 2,2)])
+
+        self.assertEquals(list(m3.merge_regions()),
+                          [('unchanged', 0, 2)])
+
+        self.assertEquals(list(m3.merge_groups()),
+                          [('unchanged', ['aaa', 'bbb'])])
+
+    def test_front_insert(self):
+        m3 = Merge3(['zz'],
+                    ['aaa', 'bbb', 'zz'],
+                    ['zz'])
+
+        # todo: should use a sentinal at end as from get_matching_blocks
+        # to match without zz
+        self.assertEquals(list(m3.find_sync_regions()),
+                          [(0,1, 2,3, 0,1),
+                           (1,1, 3,3, 1,1),])
+
+        self.assertEquals(list(m3.merge_regions()),
+                          [('a', 0, 2),
+                           ('unchanged', 0, 1)])
+
+        self.assertEquals(list(m3.merge_groups()),
+                          [('a', ['aaa', 'bbb']),
+                           ('unchanged', ['zz'])])
+        
+    def test_null_insert(self):
+        m3 = Merge3([],
+                    ['aaa', 'bbb'],
+                    [])
+        # todo: should use a sentinal at end as from get_matching_blocks
+        # to match without zz
+        self.assertEquals(list(m3.find_sync_regions()),
+                          [(0,0, 2,2, 0,0)])
+
+        self.assertEquals(list(m3.merge_regions()),
+                          [('a', 0, 2)])
+
+        self.assertEquals(list(m3.merge_lines()),
+                          ['aaa', 'bbb'])
+
+    def test_no_conflicts(self):
+        """No conflicts because only one side changed"""
+        m3 = Merge3(['aaa', 'bbb'],
+                    ['aaa', '111', 'bbb'],
+                    ['aaa', 'bbb'])
+
+        self.assertEquals(m3.find_unconflicted(),
+                          [(0, 1), (1, 2)])
+
+        self.assertEquals(list(m3.find_sync_regions()),
+                          [(0,1, 0,1, 0,1),
+                           (1,2, 2,3, 1,2),
+                           (2,2, 3,3, 2,2),])
+
+        self.assertEquals(list(m3.merge_regions()),
+                          [('unchanged', 0, 1),
+                           ('a', 1, 2),
+                           ('unchanged', 1, 2),])
+
+    def test_append_a(self):
+        m3 = Merge3(['aaa\n', 'bbb\n'],
+                    ['aaa\n', 'bbb\n', '222\n'],
+                    ['aaa\n', 'bbb\n'])
+
+        self.assertEquals(''.join(m3.merge_lines()),
+                          'aaa\nbbb\n222\n')
+
+    def test_append_b(self):
+        m3 = Merge3(['aaa\n', 'bbb\n'],
+                    ['aaa\n', 'bbb\n'],
+                    ['aaa\n', 'bbb\n', '222\n'])
+
+        self.assertEquals(''.join(m3.merge_lines()),
+                          'aaa\nbbb\n222\n')
+
+    def test_append_agreement(self):
+        m3 = Merge3(['aaa\n', 'bbb\n'],
+                    ['aaa\n', 'bbb\n', '222\n'],
+                    ['aaa\n', 'bbb\n', '222\n'])
+
+        self.assertEquals(''.join(m3.merge_lines()),
+                          'aaa\nbbb\n222\n')
+
+    def test_append_clash(self):
+        m3 = Merge3(['aaa\n', 'bbb\n'],
+                    ['aaa\n', 'bbb\n', '222\n'],
+                    ['aaa\n', 'bbb\n', '333\n'])
+
+        ml = m3.merge_lines(name_a='a',
+                            name_b='b',
+                            start_marker='<<',
+                            mid_marker='--',
+                            end_marker='>>')
+        self.assertEquals(''.join(ml),
+'''\
+aaa
+bbb
+<< a
+222
+--
+333
+>> b
+''')
+
+    def test_insert_agreement(self):
+        m3 = Merge3(['aaa\n', 'bbb\n'],
+                    ['aaa\n', '222\n', 'bbb\n'],
+                    ['aaa\n', '222\n', 'bbb\n'])
+
+        ml = m3.merge_lines(name_a='a',
+                            name_b='b',
+                            start_marker='<<',
+                            mid_marker='--',
+                            end_marker='>>')
+        self.assertEquals(''.join(ml), 'aaa\n222\nbbb\n')
+        
+
+    def test_insert_clash(self):
+        """Both try to insert lines in the same place."""
+        m3 = Merge3(['aaa\n', 'bbb\n'],
+                    ['aaa\n', '111\n', 'bbb\n'],
+                    ['aaa\n', '222\n', 'bbb\n'])
+
+        self.assertEquals(m3.find_unconflicted(),
+                          [(0, 1), (1, 2)])
+
+        self.assertEquals(list(m3.find_sync_regions()),
+                          [(0,1, 0,1, 0,1),
+                           (1,2, 2,3, 2,3),
+                           (2,2, 3,3, 3,3),])
+
+        self.assertEquals(list(m3.merge_regions()),
+                          [('unchanged', 0,1),
+                           ('conflict', 1,1, 1,2, 1,2),
+                           ('unchanged', 1,2)])
+
+        self.assertEquals(list(m3.merge_groups()),
+                          [('unchanged', ['aaa\n']),
+                           ('conflict', [], ['111\n'], ['222\n']),
+                           ('unchanged', ['bbb\n']),
+                           ])
+
+        ml = m3.merge_lines(name_a='a',
+                            name_b='b',
+                            start_marker='<<',
+                            mid_marker='--',
+                            end_marker='>>')
+        self.assertEquals(''.join(ml),
+'''aaa
+<< a
+111
+--
+222
+>> b
+bbb
+''')
+
+    def test_replace_clash(self):
+        """Both try to insert lines in the same place."""
+        m3 = Merge3(['aaa', '000', 'bbb'],
+                    ['aaa', '111', 'bbb'],
+                    ['aaa', '222', 'bbb'])
+
+        self.assertEquals(m3.find_unconflicted(),
+                          [(0, 1), (2, 3)])
+
+        self.assertEquals(list(m3.find_sync_regions()),
+                          [(0,1, 0,1, 0,1),
+                           (2,3, 2,3, 2,3),
+                           (3,3, 3,3, 3,3),])
+
+    def test_replace_multi(self):
+        """Replacement with regions of different size."""
+        m3 = Merge3(['aaa', '000', '000', 'bbb'],
+                    ['aaa', '111', '111', '111', 'bbb'],
+                    ['aaa', '222', '222', '222', '222', 'bbb'])
+
+        self.assertEquals(m3.find_unconflicted(),
+                          [(0, 1), (3, 4)])
+
+
+        self.assertEquals(list(m3.find_sync_regions()),
+                          [(0,1, 0,1, 0,1),
+                           (3,4, 4,5, 5,6),
+                           (4,4, 5,5, 6,6),])
+
+    def test_merge_poem(self):
+        """Test case from diff3 manual"""
+        m3 = Merge3(TZU, LAO, TAO)
+        ml = list(m3.merge_lines('LAO', 'TAO'))
+        self.log('merge result:')
+        self.log(''.join(ml))
+        self.assertEquals(ml, MERGED_RESULT)
+
+    def test_minimal_conflicts_common(self):
+        """Reprocessing"""
+        base_text = ("a\n" * 20).splitlines(True)
+        this_text = ("a\n"*10+"b\n" * 10).splitlines(True)
+        other_text = ("a\n"*10+"c\n"+"b\n" * 8 + "c\n").splitlines(True)
+        m3 = Merge3(base_text, other_text, this_text)
+        m_lines = m3.merge_lines('OTHER', 'THIS', reprocess=True)
+        merged_text = "".join(list(m_lines))
+        optimal_text = ("a\n" * 10 + "<<<<<<< OTHER\nc\n=======\n"
+            + ">>>>>>> THIS\n"
+            + 8* "b\n" + "<<<<<<< OTHER\nc\n=======\n"
+            + 2* "b\n" + ">>>>>>> THIS\n")
+        self.assertEquals(optimal_text, merged_text)
+
+    def test_minimal_conflicts_unique(self):
+        def add_newline(s):
+            """Add a newline to each entry in the string"""
+            return [(x+'\n') for x in s]
+
+        base_text = add_newline("abcdefghijklm")
+        this_text = add_newline("abcdefghijklmNOPQRSTUVWXYZ")
+        other_text = add_newline("abcdefghijklm1OPQRSTUVWXY2")
+        m3 = Merge3(base_text, other_text, this_text)
+        m_lines = m3.merge_lines('OTHER', 'THIS', reprocess=True)
+        merged_text = "".join(list(m_lines))
+        optimal_text = ''.join(add_newline("abcdefghijklm")
+            + ["<<<<<<< OTHER\n1\n=======\nN\n>>>>>>> THIS\n"]
+            + add_newline('OPQRSTUVWXY')
+            + ["<<<<<<< OTHER\n2\n=======\nZ\n>>>>>>> THIS\n"]
+            )
+        self.assertEquals(optimal_text, merged_text)
+
+    def test_minimal_conflicts_nonunique(self):
+        def add_newline(s):
+            """Add a newline to each entry in the string"""
+            return [(x+'\n') for x in s]
+
+        base_text = add_newline("abacddefgghij")
+        this_text = add_newline("abacddefgghijkalmontfprz")
+        other_text = add_newline("abacddefgghijknlmontfprd")
+        m3 = Merge3(base_text, other_text, this_text)
+        m_lines = m3.merge_lines('OTHER', 'THIS', reprocess=True)
+        merged_text = "".join(list(m_lines))
+        optimal_text = ''.join(add_newline("abacddefgghijk")
+            + ["<<<<<<< OTHER\nn\n=======\na\n>>>>>>> THIS\n"]
+            + add_newline('lmontfpr')
+            + ["<<<<<<< OTHER\nd\n=======\nz\n>>>>>>> THIS\n"]
+            )
+        self.assertEquals(optimal_text, merged_text)
+
+    def test_reprocess_and_base(self):
+        """Reprocessing and showing base breaks correctly"""
+        base_text = ("a\n" * 20).splitlines(True)
+        this_text = ("a\n"*10+"b\n" * 10).splitlines(True)
+        other_text = ("a\n"*10+"c\n"+"b\n" * 8 + "c\n").splitlines(True)
+        m3 = Merge3(base_text, other_text, this_text)
+        m_lines = m3.merge_lines('OTHER', 'THIS', reprocess=True, 
+                                 base_marker='|||||||')
+        self.assertRaises(CantReprocessAndShowBase, list, m_lines)
+
+    def test_binary(self):
+        self.assertRaises(util.Abort, Merge3, ['\x00'], ['a'], ['b'])
+
+    def test_dos_text(self):
+        base_text = 'a\r\n'
+        this_text = 'b\r\n'
+        other_text = 'c\r\n'
+        m3 = Merge3(base_text.splitlines(True), other_text.splitlines(True),
+                    this_text.splitlines(True))
+        m_lines = m3.merge_lines('OTHER', 'THIS')
+        self.assertEqual('<<<<<<< OTHER\r\nc\r\n=======\r\nb\r\n'
+            '>>>>>>> THIS\r\n'.splitlines(True), list(m_lines))
+
+    def test_mac_text(self):
+        base_text = 'a\r'
+        this_text = 'b\r'
+        other_text = 'c\r'
+        m3 = Merge3(base_text.splitlines(True), other_text.splitlines(True),
+                    this_text.splitlines(True))
+        m_lines = m3.merge_lines('OTHER', 'THIS')
+        self.assertEqual('<<<<<<< OTHER\rc\r=======\rb\r'
+            '>>>>>>> THIS\r'.splitlines(True), list(m_lines))
+
+if __name__ == '__main__':
+    # hide the timer
+    import time
+    orig = time.time
+    try:
+        time.time = lambda: 0
+        unittest.main()
+    finally:
+        time.time = orig
+
new file mode 100644
--- /dev/null
+++ b/tests/test-simplemerge.py.out
@@ -0,0 +1,5 @@
+....................
+----------------------------------------------------------------------
+Ran 20 tests in 0.000s
+
+OK
--- a/tests/test-ssh
+++ b/tests/test-ssh
@@ -1,28 +1,31 @@
 #!/bin/sh
 
+cp "$TESTDIR"/printenv.py .
+
 # This test tries to exercise the ssh functionality with a dummy script
 
-cat <<'EOF' > dummyssh
-#!/bin/sh
-# this attempts to deal with relative pathnames
-cd `dirname $0`
+cat <<EOF > dummyssh
+import sys
+import os
 
-# check for proper args
-if [ $1 != "user@dummy" ] ; then
-	exit -1
-fi
+os.chdir(os.path.dirname(sys.argv[0]))
+if sys.argv[1] != "user@dummy":
+    sys.exit(-1)
+
+if not os.path.exists("dummyssh"):
+    sys.exit(-1)
 
-# check that we're in the right directory
-if [ ! -x dummyssh ] ; then
-	exit -1
-fi
+os.environ["SSH_CLIENT"] = "127.0.0.1 1 2"
 
-SSH_CLIENT='127.0.0.1 1 2'
-export SSH_CLIENT
-echo Got arguments 1:$1 2:$2 3:$3 4:$4 5:$5 >> dummylog
-$2
+log = open("dummylog", "ab")
+log.write("Got arguments")
+for i, arg in enumerate(sys.argv[1:]):
+    log.write(" %d:%s" % (i+1, arg))
+log.write("\n")
+log.close()
+r = os.system(sys.argv[2])
+sys.exit(bool(r))
 EOF
-chmod +x dummyssh
 
 echo "# creating 'remote'"
 hg init remote
@@ -33,33 +36,33 @@ hg ci -A -m "init" -d "1000000 0" foo fo
 echo '[server]' > .hg/hgrc
 echo 'uncompressed = True' >> .hg/hgrc
 echo '[hooks]' >> .hg/hgrc
-echo 'changegroup = echo changegroup in remote: u=$HG_URL >> ../dummylog' >> .hg/hgrc
+echo 'changegroup = python ../printenv.py changegroup-in-remote 0 ../dummylog' >> .hg/hgrc
 
 cd ..
 
 echo "# repo not found error"
-hg clone -e ./dummyssh ssh://user@dummy/nonexistent local
+hg clone -e "python ./dummyssh" ssh://user@dummy/nonexistent local
 
 echo "# clone remote via stream"
-hg clone -e ./dummyssh --uncompressed ssh://user@dummy/remote local-stream 2>&1 | \
+hg clone -e "python ./dummyssh" --uncompressed ssh://user@dummy/remote local-stream 2>&1 | \
   sed -e 's/[0-9][0-9.]*/XXX/g' -e 's/[KM]\(B\/sec\)/X\1/'
 cd local-stream
 hg verify
 cd ..
 
 echo "# clone remote via pull"
-hg clone -e ./dummyssh ssh://user@dummy/remote local
+hg clone -e "python ./dummyssh" ssh://user@dummy/remote local
 
 echo "# verify"
 cd local
 hg verify
 
 echo '[hooks]' >> .hg/hgrc
-echo 'changegroup = echo changegroup in local: u=$HG_URL >> ../dummylog' >> .hg/hgrc
+echo 'changegroup = python ../printenv.py changegroup-in-local 0 ../dummylog' >> .hg/hgrc
 
 echo "# empty default pull"
 hg paths
-hg pull -e ../dummyssh
+hg pull -e "python ../dummyssh"
 
 echo "# local change"
 echo bleah > foo
@@ -68,13 +71,13 @@ hg ci -m "add" -d "1000000 0"
 echo "# updating rc"
 echo "default-push = ssh://user@dummy/remote" >> .hg/hgrc
 echo "[ui]" >> .hg/hgrc
-echo "ssh = ../dummyssh" >> .hg/hgrc
+echo "ssh = python ../dummyssh" >> .hg/hgrc
 
 echo "# find outgoing"
 hg out ssh://user@dummy/remote
 
 echo "# find incoming on the remote side"
-hg incoming -R ../remote -e ../dummyssh ssh://user@dummy/local
+hg incoming -R ../remote -e "python ../dummyssh" ssh://user@dummy/local
 
 echo "# push"
 hg push
--- a/tests/test-ssh-clone-r
+++ b/tests/test-ssh-clone-r
@@ -2,27 +2,28 @@
 
 # This test tries to exercise the ssh functionality with a dummy script
 
-cat <<'EOF' > dummyssh
-#!/bin/sh
-# this attempts to deal with relative pathnames
-cd `dirname $0`
+cat <<EOF > dummyssh
+import sys
+import os
 
-# check for proper args
-if [ $1 != "user@dummy" ] ; then
-	exit -1
-fi
+os.chdir(os.path.dirname(sys.argv[0]))
+if sys.argv[1] != "user@dummy":
+    sys.exit(-1)
+
+if not os.path.exists("dummyssh"):
+    sys.exit(-1)
 
-# check that we're in the right directory
-if [ ! -x dummyssh ] ; then
-	exit -1
-fi
+os.environ["SSH_CLIENT"] = "127.0.0.1 1 2"
 
-SSH_CLIENT='127.0.0.1 1 2'
-export SSH_CLIENT
-echo Got arguments 1:$1 2:$2 3:$3 4:$4 5:$5 >> dummylog
-$2
+log = open("dummylog", "ab")
+log.write("Got arguments")
+for i, arg in enumerate(sys.argv[1:]):
+    log.write(" %d:%s" % (i+1, arg))
+log.write("\n")
+log.close()
+r = os.system(sys.argv[2])
+sys.exit(bool(r))
 EOF
-chmod +x dummyssh
 
 hg init remote
 cd remote
@@ -76,7 +77,7 @@ cd ..
 
 echo "# clone remote via stream"
 for i in 0 1 2 3 4 5 6 7 8; do
-   hg clone -e ./dummyssh --uncompressed -r "$i" ssh://user@dummy/remote test-"$i" 2>&1
+   hg clone -e "python ./dummyssh" --uncompressed -r "$i" ssh://user@dummy/remote test-"$i" 2>&1
    if cd test-"$i"; then
       hg verify
       cd ..
@@ -87,13 +88,13 @@ hg pull ../test-7
 hg verify
 cd ..
 cd test-1
-hg pull -e ../dummyssh -r 4 ssh://user@dummy/remote 2>&1
+hg pull -e "python ../dummyssh" -r 4 ssh://user@dummy/remote 2>&1
 hg verify
-hg pull -e ../dummyssh ssh://user@dummy/remote 2>&1
+hg pull -e "python ../dummyssh" ssh://user@dummy/remote 2>&1
 cd ..
 cd test-2
-hg pull -e ../dummyssh -r 5 ssh://user@dummy/remote 2>&1
+hg pull -e "python ../dummyssh" -r 5 ssh://user@dummy/remote 2>&1
 hg verify
-hg pull -e ../dummyssh ssh://user@dummy/remote 2>&1
+hg pull -e "python ../dummyssh" ssh://user@dummy/remote 2>&1
 hg verify
 cd ..
--- a/tests/test-ssh.out
+++ b/tests/test-ssh.out
@@ -33,6 +33,7 @@ no changes found
 # local change
 # updating rc
 # find outgoing
+comparing with ssh://user@dummy/remote
 searching for changes
 changeset:   1:572896fe480d
 tag:         tip
@@ -41,6 +42,7 @@ date:        Mon Jan 12 13:46:40 1970 +0
 summary:     add
 
 # find incoming on the remote side
+comparing with ssh://user@dummy/local
 searching for changes
 changeset:   1:572896fe480d
 tag:         tip
@@ -76,13 +78,13 @@ remote: adding changesets
 remote: adding manifests
 remote: adding file changes
 remote: added 1 changesets with 1 changes to 1 files
-Got arguments 1:user@dummy 2:hg -R nonexistent serve --stdio 3: 4: 5:
-Got arguments 1:user@dummy 2:hg -R remote serve --stdio 3: 4: 5:
-Got arguments 1:user@dummy 2:hg -R remote serve --stdio 3: 4: 5:
-Got arguments 1:user@dummy 2:hg -R remote serve --stdio 3: 4: 5:
-Got arguments 1:user@dummy 2:hg -R remote serve --stdio 3: 4: 5:
-Got arguments 1:user@dummy 2:hg -R local serve --stdio 3: 4: 5:
-Got arguments 1:user@dummy 2:hg -R remote serve --stdio 3: 4: 5:
-changegroup in remote: u=remote:ssh:127.0.0.1
-Got arguments 1:user@dummy 2:hg -R remote serve --stdio 3: 4: 5:
-changegroup in remote: u=remote:ssh:127.0.0.1
+Got arguments 1:user@dummy 2:hg -R nonexistent serve --stdio
+Got arguments 1:user@dummy 2:hg -R remote serve --stdio
+Got arguments 1:user@dummy 2:hg -R remote serve --stdio
+Got arguments 1:user@dummy 2:hg -R remote serve --stdio
+Got arguments 1:user@dummy 2:hg -R remote serve --stdio
+Got arguments 1:user@dummy 2:hg -R local serve --stdio
+Got arguments 1:user@dummy 2:hg -R remote serve --stdio
+changegroup-in-remote hook: HG_NODE=572896fe480d7581849806ee402175c49cb20037 HG_SOURCE=serve HG_URL=remote:ssh:127.0.0.1 
+Got arguments 1:user@dummy 2:hg -R remote serve --stdio
+changegroup-in-remote hook: HG_NODE=ac7448082955a0b2ff5cb4512c1e061c779bbc79 HG_SOURCE=serve HG_URL=remote:ssh:127.0.0.1 
--- a/tests/test-static-http
+++ b/tests/test-static-http
@@ -1,5 +1,7 @@
 #!/bin/sh
 
+cp "$TESTDIR"/printenv.py .
+
 http_proxy= hg clone static-http://localhost:20059/ copy
 echo $?
 test -d copy || echo copy: No such file or directory
@@ -44,7 +46,7 @@ hg commit -A -mtest2 -d '100000000 0'
 
 cd ../local
 echo '[hooks]' >> .hg/hgrc
-echo 'changegroup = echo changegroup: u=$HG_URL' >> .hg/hgrc
+echo 'changegroup = python ../printenv.py changegroup' >> .hg/hgrc
 http_proxy= hg pull
 
 kill $!
--- a/tests/test-static-http.out
+++ b/tests/test-static-http.out
@@ -20,7 +20,7 @@ checking files
 1 files, 1 changesets, 1 total revisions
 foo
 adding quux
-changegroup: u=static-http://localhost:20059/remote
+changegroup hook: HG_NODE=34401e0e9971e9720b613d9089ffa9a6eefb3d2d HG_SOURCE=pull HG_URL=static-http://localhost:20059/remote 
 pulling from static-http://localhost:20059/remote
 searching for changes
 adding changesets
new file mode 100755
--- /dev/null
+++ b/tests/test-symlink-basic
@@ -0,0 +1,45 @@
+#!/bin/sh
+
+cat >> readlink.py <<EOF
+import os
+import sys
+
+for f in sys.argv[1:]:
+    print f, '->', os.readlink(f)
+EOF
+
+hg init a
+cd a
+ln -s nothing dangling
+hg add dangling
+hg commit -m 'add symlink' -d '0 0'
+
+hg tip -v
+hg manifest --debug
+echo '% rev 0:'
+python ../readlink.py dangling
+
+rm dangling
+ln -s void dangling
+hg commit -m 'change symlink'
+echo '% rev 1:'
+python ../readlink.py dangling
+
+echo '% modifying link'
+rm dangling
+ln -s empty dangling
+python ../readlink.py dangling
+
+echo '% reverting to rev 0:'
+hg revert -r 0 -a
+python ../readlink.py dangling
+
+echo '% backups:'
+python ../readlink.py *.orig
+
+rm *.orig
+hg up -C
+echo '% copies'
+hg cp -v dangling dangling2
+hg st -Cmard
+python ../readlink.py dangling dangling2
new file mode 100644
--- /dev/null
+++ b/tests/test-symlink-basic.out
@@ -0,0 +1,28 @@
+changeset:   0:cabd88b706fc
+tag:         tip
+user:        test
+date:        Thu Jan 01 00:00:00 1970 +0000
+files:       dangling
+description:
+add symlink
+
+
+2564acbe54bbbedfbf608479340b359f04597f80 644 dangling
+% rev 0:
+dangling -> nothing
+% rev 1:
+dangling -> void
+% modifying link
+dangling -> empty
+% reverting to rev 0:
+reverting dangling
+dangling -> nothing
+% backups:
+dangling.orig -> empty
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% copies
+copying dangling to dangling2
+A dangling2
+  dangling
+dangling -> void
+dangling2 -> void
--- a/tests/test-symlinks
+++ b/tests/test-symlinks
@@ -55,3 +55,18 @@ echo '# try symlink outside repo to file
 ln -s x/f ../z
 # this should fail
 hg status ../z && { echo hg mistakenly exited with status 0; exit 1; } || :
+
+cd .. ; rm -r test
+hg init test; cd test;
+
+echo '# try cloning symlink in a subdir'
+echo '1. commit a symlink'
+mkdir -p a/b/c
+cd a/b/c
+ln -s /path/to/symlink/source demo
+cd ../../..
+hg stat
+hg commit -A -m 'add symlink in a/b/c subdir'
+echo '2. clone it'
+cd ..
+hg clone test testclone
--- a/tests/test-symlinks.out
+++ b/tests/test-symlinks.out
@@ -1,11 +1,12 @@
+adding bar
 adding foo
 adding bomb
 adding a.c
 adding dir/a.o
 adding dir/b.o
+M dir/b.o
 ! a.c
 ! dir/a.o
-! dir/b.o
 ? .hgignore
 a.c: unsupported file type (type is fifo)
 ! a.c
@@ -13,3 +14,9 @@ a.c: unsupported file type (type is fifo
 A f
 # try symlink outside repo to file inside
 abort: ../z not under root
+# try cloning symlink in a subdir
+1. commit a symlink
+? a/b/c/demo
+adding a/b/c/demo
+2. clone it
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- a/tests/test-transplant
+++ b/tests/test-transplant
@@ -22,28 +22,25 @@ hg ci -Amb3 -d '2 0'
 
 hg log --template '{rev} {parents} {desc}\n'
 
-cd ..
-hg clone t rebase
-cd rebase
+hg clone . ../rebase
+cd ../rebase
 
 hg up -C 1
 echo '% rebase b onto r1'
 hg transplant -a -b tip
 hg log --template '{rev} {parents} {desc}\n'
 
-cd ..
-hg clone t prune
-cd prune
+hg clone ../t ../prune
+cd ../prune
 
 hg up -C 1
 echo '% rebase b onto r1, skipping b2'
 hg transplant -a -b tip -p 3
 hg log --template '{rev} {parents} {desc}\n'
 
-cd ..
 echo '% remote transplant'
-hg clone -r 1 t remote
-cd remote
+hg clone -r 1 ../t ../remote
+cd ../remote
 hg transplant --log -s ../t 2 4
 hg log --template '{rev} {parents} {desc}\n'
 
@@ -54,11 +51,19 @@ hg log --template '{rev} {parents} {desc
 echo '% skip local changes transplanted to the source'
 echo b4 > b4
 hg ci -Amb4 -d '3 0'
-cd ..
-hg clone t pullback
-cd pullback
+hg clone ../t ../pullback
+cd ../pullback
 hg transplant -s ../remote -a -b tip
 
+echo '% remote transplant with pull'
+hg -R ../t serve -p 20062 -d --pid-file=../t.pid
+cat ../t.pid >> $DAEMON_PIDS
+
+hg clone -r 0 ../t ../rp
+cd ../rp
+hg transplant -s http://localhost:20062/ 2 4
+hg log --template '{rev} {parents} {desc}\n'
+
 echo '% transplant --continue'
 hg init ../tc
 cd ../tc
--- a/tests/test-transplant.out
+++ b/tests/test-transplant.out
@@ -75,6 +75,24 @@ 4 files updated, 0 files merged, 0 files
 searching for changes
 applying 4333daefcb15
 4333daefcb15 transplanted to 5f42c04e07cc
+% remote transplant with pull
+requesting all changes
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 1 changes to 1 files
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+searching for changes
+searching for changes
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 1 changes to 1 files
+applying a53251cdf717
+a53251cdf717 transplanted to 8d9279348abb
+2  b3
+1  b1
+0  r1
 % transplant --continue
 adding foo
 adding toremove
--- a/tests/test-ui-config
+++ b/tests/test-ui-config
@@ -1,5 +1,6 @@
 #!/usr/bin/env python
 
+import ConfigParser
 from mercurial import ui, util, commands
 
 testui = ui.ui()
@@ -70,3 +71,21 @@ try:
 except util.Abort, inst:
     print inst
 print "---"
+
+cp = util.configparser()
+cp.add_section('foo')
+cp.set('foo', 'bar', 'baz')
+try:
+    # should fail - keys are case-sensitive
+    cp.get('foo', 'Bar')
+except ConfigParser.NoOptionError, inst:
+    print inst
+
+def function():
+    pass
+
+cp.add_section('hook')
+# values that aren't strings should work
+cp.set('hook', 'commit', function)
+f = cp.get('hook', 'commit')
+print "f %s= function" % (f == function and '=' or '!')
--- a/tests/test-ui-config.out
+++ b/tests/test-ui-config.out
@@ -43,3 +43,5 @@ bad interpolation variable reference '%(
 Error in configuration section [interpolation] parameter 'value5':
 '%' must be followed by '%' or '(', found: '%bad2'
 ---
+No option 'Bar' in section: 'foo'
+f == function
--- a/tests/test-up-local-change
+++ b/tests/test-up-local-change
@@ -1,5 +1,7 @@
 #!/bin/sh
 
+HGMERGE=true; export HGMERGE
+
 set -e
 mkdir r1
 cd r1
--- a/tests/test-walk
+++ b/tests/test-walk
@@ -1,5 +1,19 @@
 #!/bin/sh
 
+debugwalk()
+{
+    echo "hg debugwalk $@"
+    hg debugwalk "$@"
+    echo
+}
+
+chdir()
+{
+    echo "cd $@"
+    cd "$@"
+    echo
+}
+
 mkdir t
 cd t
 hg init
@@ -18,50 +32,69 @@ echo fiddlehead > fiddlehead
 echo glob:glob > glob:glob
 hg addremove
 hg commit -m "commit #0" -d "1000000 0"
-hg debugwalk
-cd mammals
-hg debugwalk .
-hg debugwalk Procyonidae
-cd Procyonidae
-hg debugwalk .
-hg debugwalk ..
-cd ..
-hg debugwalk ../beans
-hg debugwalk .
-hg debugwalk .hg
-hg debugwalk ../.hg
-cd ..
-hg debugwalk -Ibeans
-hg debugwalk 'glob:mammals/../beans/b*'
-hg debugwalk '-X*/Procyonidae' mammals
-hg debugwalk path:mammals
-hg debugwalk ..
-hg debugwalk beans/../..
-hg debugwalk .hg
-hg debugwalk beans/../.hg
-hg debugwalk beans/../.hg/data
-hg debugwalk beans/.hg
+debugwalk
+debugwalk -I.
+chdir mammals
+debugwalk
+debugwalk -X ../beans
+debugwalk -I '*k'
+debugwalk -I 'glob:*k'
+debugwalk -I 'relglob:*k'
+debugwalk -I 'relglob:*k' .
+debugwalk -I 're:.*k$'
+debugwalk -I 'relre:.*k$'
+debugwalk -I 'path:beans'
+debugwalk -I 'relpath:../beans'
+debugwalk .
+debugwalk -I.
+debugwalk Procyonidae
+chdir Procyonidae
+debugwalk .
+debugwalk ..
+chdir ..
+debugwalk ../beans
+debugwalk .
+debugwalk .hg
+debugwalk ../.hg
+chdir ..
+debugwalk -Ibeans
+debugwalk 'glob:mammals/../beans/b*'
+debugwalk '-X*/Procyonidae' mammals
+debugwalk path:mammals
+debugwalk ..
+debugwalk beans/../..
+debugwalk .hg
+debugwalk beans/../.hg
+debugwalk beans/../.hg/data
+debugwalk beans/.hg
 # Don't know how to test absolute paths without always getting a false
 # error.
-#hg debugwalk `pwd`/beans
-#hg debugwalk `pwd`/..
-hg debugwalk glob:\*
-hg debugwalk 're:.*[kb]$'
-hg debugwalk path:beans/black
-hg debugwalk beans 'glob:beans/*'
-hg debugwalk 'glob:j*'
-hg debugwalk NOEXIST
+#debugwalk `pwd`/beans
+#debugwalk `pwd`/..
+debugwalk glob:\*
+debugwalk 'glob:**e'
+debugwalk 're:.*[kb]$'
+debugwalk path:beans/black
+debugwalk path:beans//black
+debugwalk relglob:Procyonidae
+debugwalk 'relglob:Procyonidae/**'
+debugwalk 'relglob:Procyonidae/**' fennel
+debugwalk beans 'glob:beans/*'
+debugwalk 'glob:mamm**'
+debugwalk 'glob:mamm**' fennel
+debugwalk 'glob:j*'
+debugwalk NOEXIST
 mkfifo fifo
-hg debugwalk fifo
+debugwalk fifo
 rm fenugreek
-hg debugwalk fenugreek
+debugwalk fenugreek
 hg rm fenugreek
-hg debugwalk fenugreek
+debugwalk fenugreek
 touch new
-hg debugwalk new
-cd ..
-hg -R t debugwalk t/mammals/skunk
+debugwalk new
+chdir ..
+debugwalk -R t t/mammals/skunk
 mkdir t2
-cd t2
-hg -R ../t debugwalk ../t/mammals/skunk
-hg --cwd ../t debugwalk mammals/skunk
+chdir t2
+debugwalk -R ../t ../t/mammals/skunk
+debugwalk --cwd ../t mammals/skunk
--- a/tests/test-walk.out
+++ b/tests/test-walk.out
@@ -12,6 +12,23 @@ adding mammals/Procyonidae/cacomistle
 adding mammals/Procyonidae/coatimundi
 adding mammals/Procyonidae/raccoon
 adding mammals/skunk
+hg debugwalk 
+f  beans/black                     beans/black
+f  beans/borlotti                  beans/borlotti
+f  beans/kidney                    beans/kidney
+f  beans/navy                      beans/navy
+f  beans/pinto                     beans/pinto
+f  beans/turtle                    beans/turtle
+f  fennel                          fennel
+f  fenugreek                       fenugreek
+f  fiddlehead                      fiddlehead
+f  glob:glob                       glob:glob
+f  mammals/Procyonidae/cacomistle  mammals/Procyonidae/cacomistle
+f  mammals/Procyonidae/coatimundi  mammals/Procyonidae/coatimundi
+f  mammals/Procyonidae/raccoon     mammals/Procyonidae/raccoon
+f  mammals/skunk                   mammals/skunk
+
+hg debugwalk -I.
 f  beans/black                     beans/black
 f  beans/borlotti                  beans/borlotti
 f  beans/kidney                    beans/kidney
@@ -26,75 +43,255 @@ f  mammals/Procyonidae/cacomistle  mamma
 f  mammals/Procyonidae/coatimundi  mammals/Procyonidae/coatimundi
 f  mammals/Procyonidae/raccoon     mammals/Procyonidae/raccoon
 f  mammals/skunk                   mammals/skunk
+
+cd mammals
+
+hg debugwalk 
+f  beans/black                     ../beans/black
+f  beans/borlotti                  ../beans/borlotti
+f  beans/kidney                    ../beans/kidney
+f  beans/navy                      ../beans/navy
+f  beans/pinto                     ../beans/pinto
+f  beans/turtle                    ../beans/turtle
+f  fennel                          ../fennel
+f  fenugreek                       ../fenugreek
+f  fiddlehead                      ../fiddlehead
+f  glob:glob                       ../glob:glob
 f  mammals/Procyonidae/cacomistle  Procyonidae/cacomistle
 f  mammals/Procyonidae/coatimundi  Procyonidae/coatimundi
 f  mammals/Procyonidae/raccoon     Procyonidae/raccoon
 f  mammals/skunk                   skunk
+
+hg debugwalk -X ../beans
+f  fennel                          ../fennel
+f  fenugreek                       ../fenugreek
+f  fiddlehead                      ../fiddlehead
+f  glob:glob                       ../glob:glob
 f  mammals/Procyonidae/cacomistle  Procyonidae/cacomistle
 f  mammals/Procyonidae/coatimundi  Procyonidae/coatimundi
 f  mammals/Procyonidae/raccoon     Procyonidae/raccoon
-f  mammals/Procyonidae/cacomistle  cacomistle
-f  mammals/Procyonidae/coatimundi  coatimundi
-f  mammals/Procyonidae/raccoon     raccoon
-f  mammals/Procyonidae/cacomistle  cacomistle
-f  mammals/Procyonidae/coatimundi  coatimundi
-f  mammals/Procyonidae/raccoon     raccoon
-f  mammals/skunk                   ../skunk
+f  mammals/skunk                   skunk
+
+hg debugwalk -I *k
+f  mammals/skunk  skunk
+
+hg debugwalk -I glob:*k
+f  mammals/skunk  skunk
+
+hg debugwalk -I relglob:*k
+f  beans/black    ../beans/black
+f  fenugreek      ../fenugreek
+f  mammals/skunk  skunk
+
+hg debugwalk -I relglob:*k .
+f  mammals/skunk  skunk
+
+hg debugwalk -I re:.*k$
+f  beans/black    ../beans/black
+f  fenugreek      ../fenugreek
+f  mammals/skunk  skunk
+
+hg debugwalk -I relre:.*k$
+f  beans/black    ../beans/black
+f  fenugreek      ../fenugreek
+f  mammals/skunk  skunk
+
+hg debugwalk -I path:beans
 f  beans/black     ../beans/black
 f  beans/borlotti  ../beans/borlotti
 f  beans/kidney    ../beans/kidney
 f  beans/navy      ../beans/navy
 f  beans/pinto     ../beans/pinto
 f  beans/turtle    ../beans/turtle
+
+hg debugwalk -I relpath:../beans
+f  beans/black     ../beans/black
+f  beans/borlotti  ../beans/borlotti
+f  beans/kidney    ../beans/kidney
+f  beans/navy      ../beans/navy
+f  beans/pinto     ../beans/pinto
+f  beans/turtle    ../beans/turtle
+
+hg debugwalk .
+f  mammals/Procyonidae/cacomistle  Procyonidae/cacomistle
+f  mammals/Procyonidae/coatimundi  Procyonidae/coatimundi
+f  mammals/Procyonidae/raccoon     Procyonidae/raccoon
+f  mammals/skunk                   skunk
+
+hg debugwalk -I.
 f  mammals/Procyonidae/cacomistle  Procyonidae/cacomistle
 f  mammals/Procyonidae/coatimundi  Procyonidae/coatimundi
 f  mammals/Procyonidae/raccoon     Procyonidae/raccoon
 f  mammals/skunk                   skunk
+
+hg debugwalk Procyonidae
+f  mammals/Procyonidae/cacomistle  Procyonidae/cacomistle
+f  mammals/Procyonidae/coatimundi  Procyonidae/coatimundi
+f  mammals/Procyonidae/raccoon     Procyonidae/raccoon
+
+cd Procyonidae
+
+hg debugwalk .
+f  mammals/Procyonidae/cacomistle  cacomistle
+f  mammals/Procyonidae/coatimundi  coatimundi
+f  mammals/Procyonidae/raccoon     raccoon
+
+hg debugwalk ..
+f  mammals/Procyonidae/cacomistle  cacomistle
+f  mammals/Procyonidae/coatimundi  coatimundi
+f  mammals/Procyonidae/raccoon     raccoon
+f  mammals/skunk                   ../skunk
+
+cd ..
+
+hg debugwalk ../beans
+f  beans/black     ../beans/black
+f  beans/borlotti  ../beans/borlotti
+f  beans/kidney    ../beans/kidney
+f  beans/navy      ../beans/navy
+f  beans/pinto     ../beans/pinto
+f  beans/turtle    ../beans/turtle
+
+hg debugwalk .
+f  mammals/Procyonidae/cacomistle  Procyonidae/cacomistle
+f  mammals/Procyonidae/coatimundi  Procyonidae/coatimundi
+f  mammals/Procyonidae/raccoon     Procyonidae/raccoon
+f  mammals/skunk                   skunk
+
+hg debugwalk .hg
 .hg: No such file or directory
+
+hg debugwalk ../.hg
 abort: path contains illegal component: .hg
 
+
+cd ..
+
+hg debugwalk -Ibeans
 f  beans/black     beans/black
 f  beans/borlotti  beans/borlotti
 f  beans/kidney    beans/kidney
 f  beans/navy      beans/navy
 f  beans/pinto     beans/pinto
 f  beans/turtle    beans/turtle
+
+hg debugwalk glob:mammals/../beans/b*
 f  beans/black     beans/black
 f  beans/borlotti  beans/borlotti
+
+hg debugwalk -X*/Procyonidae mammals
 f  mammals/skunk  mammals/skunk
+
+hg debugwalk path:mammals
 f  mammals/Procyonidae/cacomistle  mammals/Procyonidae/cacomistle
 f  mammals/Procyonidae/coatimundi  mammals/Procyonidae/coatimundi
 f  mammals/Procyonidae/raccoon     mammals/Procyonidae/raccoon
 f  mammals/skunk                   mammals/skunk
+
+hg debugwalk ..
 abort: .. not under root
+
+hg debugwalk beans/../..
 abort: beans/../.. not under root
+
+hg debugwalk .hg
 abort: path contains illegal component: .hg
 
+
+hg debugwalk beans/../.hg
 abort: path contains illegal component: .hg
 
+
+hg debugwalk beans/../.hg/data
 abort: path contains illegal component: .hg/data
 
+
+hg debugwalk beans/.hg
 beans/.hg: No such file or directory
+
+hg debugwalk glob:*
 f  fennel      fennel
 f  fenugreek   fenugreek
 f  fiddlehead  fiddlehead
 f  glob:glob   glob:glob
+
+hg debugwalk glob:**e
+f  beans/turtle                    beans/turtle
+f  mammals/Procyonidae/cacomistle  mammals/Procyonidae/cacomistle
+
+hg debugwalk re:.*[kb]$
 f  beans/black    beans/black
 f  fenugreek      fenugreek
 f  glob:glob      glob:glob
 f  mammals/skunk  mammals/skunk
-f  beans/black  beans/black
+
+hg debugwalk path:beans/black
+f  beans/black  beans/black  exact
+
+hg debugwalk path:beans//black
+f  beans/black  beans/black  exact
+
+hg debugwalk relglob:Procyonidae
+
+hg debugwalk relglob:Procyonidae/**
+f  mammals/Procyonidae/cacomistle  mammals/Procyonidae/cacomistle
+f  mammals/Procyonidae/coatimundi  mammals/Procyonidae/coatimundi
+f  mammals/Procyonidae/raccoon     mammals/Procyonidae/raccoon
+
+hg debugwalk relglob:Procyonidae/** fennel
+f  fennel                          fennel                          exact
+f  mammals/Procyonidae/cacomistle  mammals/Procyonidae/cacomistle
+f  mammals/Procyonidae/coatimundi  mammals/Procyonidae/coatimundi
+f  mammals/Procyonidae/raccoon     mammals/Procyonidae/raccoon
+
+hg debugwalk beans glob:beans/*
 f  beans/black     beans/black
 f  beans/borlotti  beans/borlotti
 f  beans/kidney    beans/kidney
 f  beans/navy      beans/navy
 f  beans/pinto     beans/pinto
 f  beans/turtle    beans/turtle
+
+hg debugwalk glob:mamm**
+f  mammals/Procyonidae/cacomistle  mammals/Procyonidae/cacomistle
+f  mammals/Procyonidae/coatimundi  mammals/Procyonidae/coatimundi
+f  mammals/Procyonidae/raccoon     mammals/Procyonidae/raccoon
+f  mammals/skunk                   mammals/skunk
+
+hg debugwalk glob:mamm** fennel
+f  fennel                          fennel                          exact
+f  mammals/Procyonidae/cacomistle  mammals/Procyonidae/cacomistle
+f  mammals/Procyonidae/coatimundi  mammals/Procyonidae/coatimundi
+f  mammals/Procyonidae/raccoon     mammals/Procyonidae/raccoon
+f  mammals/skunk                   mammals/skunk
+
+hg debugwalk glob:j*
+
+hg debugwalk NOEXIST
 NOEXIST: No such file or directory
+
+hg debugwalk fifo
 fifo: unsupported file type (type is fifo)
-m  fenugreek  fenugreek  exact
+
+hg debugwalk fenugreek
 m  fenugreek  fenugreek  exact
+
+hg debugwalk fenugreek
+m  fenugreek  fenugreek  exact
+
+hg debugwalk new
 f  new  new  exact
+
+cd ..
+
+hg debugwalk -R t t/mammals/skunk
 f  mammals/skunk  t/mammals/skunk  exact
+
+cd t2
+
+hg debugwalk -R ../t ../t/mammals/skunk
 f  mammals/skunk  ../t/mammals/skunk  exact
+
+hg debugwalk --cwd ../t mammals/skunk
 f  mammals/skunk  mammals/skunk  exact
+