merge with -stable
authorBenoit Boissinot <benoit.boissinot@ens-lyon.org>
Mon, 15 Jan 2007 18:23:21 +0100
changeset 4029 9210fba03d16
parent 4028 540d1059c802 (current diff)
parent 4024 de081fbb27fe (diff)
child 4033 84e469ce6408
merge with -stable
hgext/patchbomb.py
mercurial/bundlerepo.py
mercurial/demandload.py
mercurial/httprepo.py
mercurial/packagescan.py
tests/test-branch
tests/test-branch.out
--- a/Makefile	Mon Jan 15 17:56:20 2007 +0100
+++ b/Makefile	Mon Jan 15 18:23:21 2007 +0100
@@ -67,10 +67,10 @@
 	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	Mon Jan 15 17:56:20 2007 +0100
+++ b/README	Mon Jan 15 18:23:21 2007 +0100
@@ -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	Mon Jan 15 17:56:20 2007 +0100
+++ b/contrib/bash_completion	Mon Jan 15 18:23:21 2007 +0100
@@ -145,6 +145,7 @@
     # global options
     case "$prev" in
 	-R|--repository)
+	    _hg_paths
 	    _hg_repos
 	    return
 	;;
@@ -477,3 +478,25 @@
 {
     _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	Mon Jan 15 17:56:20 2007 +0100
+++ b/contrib/churn.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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	Mon Jan 15 17:56:20 2007 +0100
+++ b/contrib/convert-repo	Mon Jan 15 18:23:21 2007 +0100
@@ -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
 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,210 @@
         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"):
+                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]
+                        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
+        local = False
+        user, host = None, None
+        cmd = ['cvs', 'server']
+
+        status("connecting to %s\n" % root)
+
+        # only non-pserver for now
+        if root.startswith(":pserver"):
+            abort("can't handle pserver mode yet: %s\n" % root)
+
+        if root.startswith(":local:"):
+            local = True
+            root = root[7:]
+        else:
+            # :ext:user@host/home/user/path/to/cvsroot
+            if root.startswith(":ext:"):
+                root = root[5:]
+            m = re.match(r'(?:([^@:/]+)@)?([^:/]+):?(.*)', root)
+            if not m:
+                local = True
+            else:
+                local = False
+                user, host, root = m.group(1), m.group(2), m.group(3)
+
+        if not local:
+            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)
+                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 + "/HEAD"):
+            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 +273,11 @@
     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 +286,8 @@
             m = m.split()
             h = m[3]
             p = (m[1] == "100755")
-            changes.append((f, h, p))
+            self.modecache[(f, h)] = p
+            changes.append((f, h))
         return changes
 
     def getcommit(self, version):
@@ -92,7 +318,9 @@
         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 = {}
@@ -110,19 +338,23 @@
     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 and 'x' or '')
         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 +362,7 @@
         except:
             pass
 
-    def putcommit(self, files, parents, author, dest, text):
+    def putcommit(self, files, parents, commit):
         seen = {}
         pl = []
         for p in parents:
@@ -143,11 +375,18 @@
         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 +409,7 @@
         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 +419,25 @@
                                 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 +457,7 @@
             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 +500,60 @@
             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 +570,41 @@
             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")
--- a/contrib/hgk	Mon Jan 15 17:56:20 2007 +0100
+++ b/contrib/hgk	Mon Jan 15 18:23:21 2007 +0100
@@ -43,7 +43,9 @@
     }
     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 @@
 	    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 @@
 	    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 @@
     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 @@
     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 @@
 	} 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"
--- a/hg	Mon Jan 15 17:56:20 2007 +0100
+++ b/hg	Mon Jan 15 18:23:21 2007 +0100
@@ -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	Mon Jan 15 17:56:20 2007 +0100
+++ b/hgext/acl.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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 @@
 
     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	Mon Jan 15 17:56:20 2007 +0100
+++ b/hgext/bugzilla.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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 @@
     _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 @@
         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 @@
         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 @@
         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 @@
             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 @@
                          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.changctx(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	Mon Jan 15 17:56:20 2007 +0100
+++ b/hgext/extdiff.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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 = '%s.%s' % (os.path.basename(repo.root), short(node))
         base = os.path.join(tmproot, dirname)
         os.mkdir(base)
@@ -74,7 +73,8 @@
             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, 'w'))
+            data = repo.wwritedata(wfn, repo.file(wfn).read(mf[wfn]))
+            open(dest, 'w').write(data)
         return dirname
 
     def snapshot_wdir(files):
--- a/hgext/fetch.py	Mon Jan 15 17:56:20 2007 +0100
+++ b/hgext/fetch.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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	Mon Jan 15 17:56:20 2007 +0100
+++ b/hgext/gpg.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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):
--- a/hgext/hbisect.py	Mon Jan 15 17:56:20 2007 +0100
+++ b/hgext/hbisect.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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"
 
--- a/hgext/hgk.py	Mon Jan 15 17:56:20 2007 +0100
+++ b/hgext/hgk.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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 @@
         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()
+    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 @@
 # 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 @@
                     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 @@
 
     # 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 @@
                 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	Mon Jan 15 17:56:20 2007 +0100
+++ b/hgext/mq.py	Mon Jan 15 18:23:21 2007 +0100
@@ -29,11 +29,9 @@
 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"
 
@@ -328,11 +326,12 @@
         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:
@@ -614,15 +613,12 @@
             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
@@ -703,7 +699,7 @@
             backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip')
             chgrpfile = bundle(backupch)
 
-        stripall(rev, revnum)
+        stripall(revnum)
 
         change = chlog.read(rev)
         chlog.strip(revnum, revnum)
@@ -834,14 +830,7 @@
             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()
--- a/hgext/notify.py	Mon Jan 15 17:56:20 2007 +0100
+++ b/hgext/notify.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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 @@
         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	Mon Jan 15 17:56:20 2007 +0100
+++ b/hgext/patchbomb.py	Mon Jan 15 18:23:21 2007 +0100
@@ -63,11 +63,10 @@
 #
 # 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
+import email.MIMEMultipart, email.MIMEText, email.Utils
+from mercurial import cmdutil, commands, hg, mail, ui, patch, util
+from mercurial.i18n import _
 from mercurial.node import *
 
 try:
@@ -216,8 +215,6 @@
     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'))
-
         tlen = len(str(len(patches)))
 
         subj = '[PATCH %0*d of %d] %s' % (
@@ -227,21 +224,13 @@
             prompt('Subject:', rest = ' [PATCH %0*d of %d] ' % (tlen, 0,
                 len(patches))))
 
-        ui.write(_('Finish with ^D or a dot on a line by itself.\n\n'))
-
-        body = []
-
-        while True:
-            try: l = raw_input()
-            except EOFError: break
-            if l == '.': break
-            body.append(l)
-
+        body = ''
         if opts['diffstat']:
             d = cdiffstat(_('Final summary:\n'), jumbo)
-            if d: body.append('\n' + d)
+            if d: body = '\n' + d
 
-        body = '\n'.join(body) + '\n'
+        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
--- a/hgext/transplant.py	Mon Jan 15 17:56:20 2007 +0100
+++ b/hgext/transplant.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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, cmdutil, commands, hg, merge, patch, revlog
+from mercurial import util
 
 '''patch transplanting tool
 
@@ -588,5 +587,5 @@
           ('', '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]...'))
 }
--- a/mercurial/appendfile.py	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/appendfile.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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(), "cStringIO changelog errno manifest os tempfile util")
+import cStringIO, changelog, errno, manifest, os, tempfile, util
 
 # writes to metadata files are ordered.  reads: changelog, manifest,
 # normal files.  writes: normal files, manifest, changelog.
--- a/mercurial/archival.py	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/archival.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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
@@ -155,15 +154,12 @@
     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/bundlerepo.py	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/bundlerepo.py	Mon Jan 15 18:23:21 2007 +0100
@@ -11,9 +11,8 @@
 """
 
 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
 
@@ -50,7 +49,7 @@
                 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:
--- a/mercurial/changegroup.py	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/changegroup.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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 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 @@
     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	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/changelog.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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):
     """
--- a/mercurial/cmdutil.py	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/cmdutil.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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, util, templater, patch
 
 revrangesep = ':'
 
@@ -148,13 +146,12 @@
 def findrenames(repo, added=None, removed=None, threshold=0.5):
     if added is None or removed is None:
         added, removed = repo.status()[1:3]
-    changes = repo.changelog.read(repo.dirstate.parents()[0])
-    mf = repo.manifest.read(changes[0])
+    ctx = repo.changectx()
     for a in added:
         aa = repo.wread(a)
         bestscore, bestname = None, None
         for r in removed:
-            rr = repo.file(r).read(mf[r])
+            rr = ctx.filectx(r).data()
             delta = mdiff.textdiff(aa, rr)
             if len(delta) < len(aa):
                 myscore = 1.0 - (float(len(delta)) / len(aa))
@@ -199,12 +196,11 @@
 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
@@ -268,11 +264,6 @@
         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])))
@@ -320,8 +311,8 @@
 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)
         self.t = templater.templater(mapfile, templater.common_filters,
                                      cache={'parent': '{rev}:{node|short} ',
                                             'manifest': '{rev}:{node|short}',
@@ -407,12 +398,6 @@
             if branch:
                 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))]
@@ -526,11 +511,6 @@
     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:
@@ -552,12 +532,12 @@
                            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	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/commands.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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,8 +240,7 @@
     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'))
@@ -685,15 +684,12 @@
     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)
 
@@ -704,10 +700,8 @@
     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)
@@ -1150,13 +1144,14 @@
 
     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))
@@ -1172,19 +1167,17 @@
             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()
@@ -1210,10 +1203,10 @@
                     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
@@ -1222,15 +1215,14 @@
                     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
@@ -1539,8 +1531,12 @@
     other = hg.repository(ui, 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:
@@ -1705,7 +1701,6 @@
     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':
@@ -1772,7 +1767,7 @@
             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
@@ -1786,9 +1781,7 @@
     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 - '
@@ -1823,7 +1816,7 @@
     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()
@@ -2136,8 +2129,9 @@
     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:
@@ -2227,7 +2221,7 @@
             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
@@ -2461,9 +2455,10 @@
     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:
@@ -2488,7 +2483,7 @@
     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.
@@ -2508,36 +2503,11 @@
             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:
-        node = node and repo.lookup(node) or repo.changelog.tip()
-    return node
-
 def verify(ui, repo):
     """verify the integrity of the repository
 
@@ -2743,8 +2713,7 @@
          _('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]')),
@@ -2755,7 +2724,7 @@
          [('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,
          _('hg import [-p NUM] [-m MESSAGE] [-f] PATCH...')),
@@ -2787,8 +2756,7 @@
          _('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')),
@@ -2809,8 +2777,7 @@
     "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')),
@@ -2825,8 +2792,7 @@
          _('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]')),
@@ -2929,8 +2895,7 @@
     "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]')),
@@ -2941,9 +2906,7 @@
          _('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')),
@@ -3097,9 +3060,10 @@
         if uisetup:
             uisetup(ui)
         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	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/context.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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 @@
             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 @@
         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 @@
             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 @@
         try:
             n = self._filenode
             return True
-        except repo.LookupError:
+        except revlog.LookupError:
             # file is missing
             return False
 
@@ -379,13 +378,15 @@
         """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
 
@@ -424,9 +425,10 @@
     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"""
@@ -484,7 +486,7 @@
         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'''
@@ -505,5 +507,12 @@
         return []
 
     def size(self): return os.stat(self._repo.wjoin(self._path)).st_size
+    def date(self):
+        t, tz = self._changectx.date()
+        try:
+            return (os.lstat(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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/demandimport.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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', '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
+
--- a/mercurial/demandload.py	Mon Jan 15 17:56:20 2007 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -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	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/dirstate.py	Mon Jan 15 18:23:21 2007 +0100
@@ -8,9 +8,8 @@
 """
 
 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
 
 class dirstate(object):
     format = ">cllll"
@@ -338,14 +337,13 @@
         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') % (
--- a/mercurial/filelog.py	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/filelog.py	Mon Jan 15 18:23:21 2007 +0100
@@ -6,8 +6,7 @@
 # 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):
--- a/mercurial/hg.py	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/hg.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/hgweb/__init__.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/hgweb/common.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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")
--- a/mercurial/hgweb/hgweb_mod.py	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/hgweb/hgweb_mod.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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 @@
                     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 @@
 
         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 @@
                      archives=self.archivelist(hex(node)))
 
     def tags(self):
-        cl = self.repo.changelog
-
         i = self.repo.tagslist()
         i.reverse()
 
@@ -509,7 +499,7 @@
                     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 @@
                      entriesnotip=lambda **x: entries(True, **x))
 
     def summary(self):
-        cl = self.repo.changelog
-
         i = self.repo.tagslist()
         i.reverse()
 
@@ -535,14 +523,11 @@
                 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):
@@ -564,40 +549,38 @@
 
         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,
+                 heads=heads,
+                 shortlog=changelist,
+                 node=hex(cl.tip()),
                  archives=self.archivelist("tip"))
 
     def filediff(self, fctx):
@@ -873,7 +856,7 @@
             try:
                 req.write(self.filerevision(self.filectx(req)))
                 return
-            except hg.RepoError:
+            except revlog.LookupError:
                 pass
 
         req.write(self.manifest(self.changectx(req), path))
--- a/mercurial/hgweb/hgwebdir_mod.py	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/hgweb/hgwebdir_mod.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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):
--- a/mercurial/hgweb/request.py	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/hgweb/request.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/hgweb/server.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/httprepo.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/i18n.py	Mon Jan 15 18:23:21 2007 +0100
@@ -7,9 +7,7 @@
 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	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/localrepo.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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, appendfile, 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')
@@ -119,10 +117,14 @@
         self.tagscache = None
         self.branchcache = None
         self.nodetagscache = None
-        self.encodepats = None
-        self.decodepats = None
+        self.filterpats = {}
         self.transhandle = None
 
+        self._link = lambda x: False
+        if util.checklink(self.root):
+            r = self.root # avoid circular reference in lambda
+            self._link = lambda x: util.is_link(os.path.join(r, x))
+
         self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root)
 
     def url(self):
@@ -319,7 +321,7 @@
             rev = c.rev()
             try:
                 fnode = c.filenode('.hgtags')
-            except repo.LookupError:
+            except revlog.LookupError:
                 continue
             ret.append((rev, node, fnode))
             if fnode in last:
@@ -480,17 +482,15 @@
     def wfile(self, f, mode='r'):
         return self.wopener(f, mode)
 
-    def wread(self, filename):
-        if self.encodepats == None:
+    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)
@@ -498,23 +498,32 @@
 
         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:
+            try:
+                os.unlink(self.wjoin(filename))
+            except OSError:
+                pass
+            os.symlink(data, self.wjoin(filename))
+        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
@@ -636,11 +645,11 @@
         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,
@@ -714,12 +723,14 @@
         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:
                 if use_dirstate:
                     self.ui.warn(_("trouble committing %s!\n") % f)
                     raise
@@ -729,11 +740,13 @@
         # 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()
@@ -748,8 +761,10 @@
             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("")
@@ -767,17 +782,20 @@
         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
@@ -877,9 +895,11 @@
                 # 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]
@@ -918,11 +938,12 @@
             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:
@@ -969,8 +990,7 @@
                 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):
@@ -993,112 +1013,6 @@
         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()]
--- a/mercurial/lock.py	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/lock.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/mail.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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")
+from i18n import _
+import os, smtplib, templater, util
 
 def _smtp(ui):
     '''send mail using smtp.'''
--- a/mercurial/manifest.py	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/manifest.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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):
--- a/mercurial/mdiff.py	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/mdiff.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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")
+import bdiff, mpatch, re, struct, util
 
 def splitnewlines(text):
     '''like str.splitlines, but only split on newlines.'''
--- a/mercurial/merge.py	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/merge.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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")
+from i18n import _
+import errno, util, os, tempfile
 
 def filemerge(repo, fw, fo, wctx, mctx):
     """perform a 3-way merge in the working directory
@@ -21,8 +20,9 @@
     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
 
@@ -220,12 +220,17 @@
     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))
@@ -250,21 +255,21 @@
                 # 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)
@@ -295,7 +300,7 @@
             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)
@@ -304,14 +309,14 @@
                     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
 
@@ -335,7 +340,7 @@
                                  (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
@@ -346,35 +351,32 @@
                     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
 
@@ -441,6 +443,9 @@
     wlock = working dir lock, if already held
     """
 
+    if node is None:
+        node = "tip"
+
     if not wlock:
         wlock = repo.wlock()
 
--- a/mercurial/node.py	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/node.py	Mon Jan 15 18:23:21 2007 +0100
@@ -7,8 +7,7 @@
 of the GNU General Public License, incorporated herein by reference.
 """
 
-from demandload import demandload
-demandload(globals(), "binascii")
+import binascii
 
 nullrev = -1
 nullid = "\0" * 20
--- a/mercurial/packagescan.py	Mon Jan 15 17:56:20 2007 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -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	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/patch.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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
 
@@ -378,8 +377,9 @@
             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])
@@ -437,27 +437,25 @@
     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]
@@ -477,67 +475,38 @@
     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
@@ -551,20 +520,21 @@
             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'
@@ -575,13 +545,10 @@
 
             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'
@@ -590,7 +557,7 @@
                         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):
@@ -599,14 +566,11 @@
                 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'
@@ -616,7 +580,10 @@
             fp.write(''.join(header))
             b85diff(fp, to, tn)
         elif dodiff:
-            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)
@@ -628,12 +595,13 @@
     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]
         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,
@@ -642,13 +610,13 @@
             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())
         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)
@@ -656,7 +624,7 @@
             fp.close()
 
     for seqno, rev in enumerate(revs):
-        single(repo.lookup(rev), seqno+1, fp)
+        single(rev, seqno+1, fp)
 
 def diffstat(patchlines):
     fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
--- a/mercurial/repo.py	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/repo.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/revlog.py	Mon Jan 15 18:23:21 2007 +0100
@@ -11,10 +11,9 @@
 """
 
 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
@@ -282,6 +281,7 @@
         del self.p.map[key]
 
 class RevlogError(Exception): pass
+class LookupError(RevlogError): pass
 
 class revlog(object):
     """
@@ -472,7 +472,7 @@
         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):
@@ -767,7 +767,7 @@
                 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)
@@ -796,7 +796,7 @@
                 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
@@ -816,7 +816,7 @@
         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"""
@@ -1156,13 +1156,13 @@
 
             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	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/sshrepo.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/sshserver.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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):
--- a/mercurial/statichttprepo.py	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/statichttprepo.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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):
--- a/mercurial/streamclone.py	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/streamclone.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/templater.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/transaction.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/ui.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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())
@@ -386,6 +385,9 @@
             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	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/util.py	Mon Jan 15 18:23:21 2007 +0100
@@ -12,10 +12,9 @@
 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")
+from i18n import _
+import cStringIO, errno, getpass, popen2, re, shutil, sys, tempfile
+import os, threading, time, calendar, ConfigParser, locale
 
 _encoding = os.environ.get("HGENCODING") or locale.getpreferredencoding() \
             or "ascii"
@@ -694,9 +693,47 @@
     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: is_link(os.path.join(path, x))
+    return fallback
+
 # Platform specific variants
 if os.name == 'nt':
-    demandload(globals(), "msvcrt")
+    import msvcrt
     nulldev = 'NUL:'
 
     class winstdout:
@@ -753,10 +790,10 @@
         '''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):
@@ -798,6 +835,8 @@
 
 else:
     nulldev = '/dev/null'
+    _umask = os.umask(0)
+    os.umask(_umask)
 
     def rcfiles(path):
         rcs = [os.path.join(path, 'hgrc')]
@@ -828,7 +867,7 @@
             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)
 
@@ -839,12 +878,34 @@
         if mode:
             # Turn on +x for every +r bit when making a file executable
             # and obey umask.
-            umask = os.umask(0)
-            os.umask(umask)
-            os.chmod(f, s | (s & 0444) >> 2 & ~umask)
+            os.chmod(f, s | (s & 0444) >> 2 & ~_umask)
         else:
             os.chmod(f, s & 0666)
 
+    def is_link(f):
+        """check whether a file is a symlink"""
+        return (os.lstat(f).st_mode & 0120000 == 0120000)
+
+    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 = is_link(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
 
--- a/mercurial/util_win32.py	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/util_win32.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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 = {
--- a/mercurial/verify.py	Mon Jan 15 17:56:20 2007 +0100
+++ b/mercurial/verify.py	Mon Jan 15 18:23:21 2007 +0100
@@ -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):
--- a/setup.py	Mon Jan 15 17:56:20 2007 +0100
+++ b/setup.py	Mon Jan 15 18:23:21 2007 +0100
@@ -13,9 +13,11 @@
 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 @@
     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 @@
 
 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 @@
                                    license='COPYING',
                                    readme='contrib/macosx/Readme.html',
                                    welcome='contrib/macosx/Welcome.html')),
-      **py2exe_opts)
+      **extra)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/raw/index.tmpl	Mon Jan 15 18:23:21 2007 +0100
@@ -0,0 +1,2 @@
+#header#
+#entries%indexentry#
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/raw/manifest.tmpl	Mon Jan 15 18:23:21 2007 +0100
@@ -0,0 +1,3 @@
+{header}
+{dentries%manifestdirentry}{fentries%manifestfileentry}
+{footer}
--- a/templates/raw/map	Mon Jan 15 17:56:20 2007 +0100
+++ b/templates/raw/map	Mon Jan 15 18:23:21 2007 +0100
@@ -14,3 +14,8 @@
 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	Mon Jan 15 17:56:20 2007 +0100
+++ b/tests/README	Mon Jan 15 18:23:21 2007 +0100
@@ -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.
--- a/tests/test-bad-extension	Mon Jan 15 17:56:20 2007 +0100
+++ b/tests/test-bad-extension	Mon Jan 15 18:23:21 2007 +0100
@@ -4,6 +4,8 @@
 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	Mon Jan 15 17:56:20 2007 +0100
+++ b/tests/test-bad-extension.out	Mon Jan 15 18:23:21 2007 +0100
@@ -1,4 +1,5 @@
 *** failed to import extension badext: invalid syntax (badext.py, line 1)
+extension 'hgext.gpg' overrides commands: sigs sigcheck sign
 hg help [COMMAND]
 
 show help for a command, extension, or list of commands
--- a/tests/test-branch	Mon Jan 15 17:56:20 2007 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -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 
--- a/tests/test-branch.out	Mon Jan 15 17:56:20 2007 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -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-commit.out	Mon Jan 15 17:56:20 2007 +0100
+++ b/tests/test-commit.out	Mon Jan 15 18:23:21 2007 +0100
@@ -21,8 +21,7 @@
 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
--- a/tests/test-grep	Mon Jan 15 17:56:20 2007 +0100
+++ b/tests/test-grep	Mon Jan 15 18:23:21 2007 +0100
@@ -17,8 +17,11 @@
 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 @@
 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	Mon Jan 15 17:56:20 2007 +0100
+++ b/tests/test-grep.out	Mon Jan 15 18:23:21 2007 +0100
@@ -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: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: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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-hgweb	Mon Jan 15 18:23:21 2007 +0100
@@ -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`
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-hgweb.out	Mon Jan 15 18:23:21 2007 +0100
@@ -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-symlinks.out	Mon Jan 15 17:56:20 2007 +0100
+++ b/tests/test-symlinks.out	Mon Jan 15 18:23:21 2007 +0100
@@ -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