changeset 4965:4106dde15aed

Merge with crew
author Matt Mackall <mpm@selenic.com>
date Sat, 21 Jul 2007 16:44:38 -0500
parents ee983d0dbea8 (diff) a28661788f2f (current diff)
children cf67b5f3743d
files hgext/convert/hg.py mercurial/cmdutil.py mercurial/commands.py mercurial/dirstate.py mercurial/patch.py
diffstat 23 files changed, 1353 insertions(+), 1263 deletions(-) [+]
line wrap: on
line diff
--- a/hgext/convert/hg.py
+++ b/hgext/convert/hg.py
@@ -26,8 +26,8 @@ class convert_mercurial(converter_sink):
 
     def putfile(self, f, e, data):
         self.repo.wwrite(f, data, e)
-        if self.repo.dirstate.state(f) == '?':
-            self.repo.dirstate.update([f], "a")
+        if f not in self.repo.dirstate:
+            self.repo.dirstate.add(f)
 
     def copyfile(self, source, dest):
         self.repo.copy(source, dest)
--- a/hgext/fetch.py
+++ b/hgext/fetch.py
@@ -23,29 +23,29 @@ def fetch(ui, repo, source='default', **
         if modheads == 0:
             return 0
         if modheads == 1:
-            return hg.clean(repo, repo.changelog.tip(), wlock=wlock)
+            return hg.clean(repo, repo.changelog.tip())
         newheads = repo.heads(parent)
         newchildren = [n for n in repo.heads(parent) if n != parent]
         newparent = parent
         if newchildren:
             newparent = newchildren[0]
-            hg.clean(repo, newparent, wlock=wlock)
+            hg.clean(repo, newparent)
         newheads = [n for n in repo.heads() if n != newparent]
         err = False
         if newheads:
             ui.status(_('merging with new head %d:%s\n') %
                       (repo.changelog.rev(newheads[0]), short(newheads[0])))
-            err = hg.merge(repo, newheads[0], remind=False, wlock=wlock)
+            err = hg.merge(repo, newheads[0], remind=False)
         if not err and len(newheads) > 1:
             ui.status(_('not merging with %d other new heads '
                         '(use "hg heads" and "hg merge" to merge them)') %
                       (len(newheads) - 1))
         if not err:
-            mod, add, rem = repo.status(wlock=wlock)[:3]
+            mod, add, rem = repo.status()[:3]
             message = (cmdutil.logmessage(opts) or
                        (_('Automated merge with %s') % other.url()))
             n = repo.commit(mod + add + rem, message,
-                            opts['user'], opts['date'], lock=lock, wlock=wlock,
+                            opts['user'], opts['date'],
                             force_editor=opts.get('force_editor'))
             ui.status(_('new changeset %d:%s merges remote changes '
                         'with local\n') % (repo.changelog.rev(n),
@@ -60,7 +60,7 @@ def fetch(ui, repo, source='default', **
             raise util.Abort(_("fetch -r doesn't work for remote repositories yet"))
         elif opts['rev']:
             revs = [other.lookup(rev) for rev in opts['rev']]
-        modheads = repo.pull(other, heads=revs, lock=lock)
+        modheads = repo.pull(other, heads=revs)
         return postincoming(other, modheads)
 
     parent, p2 = repo.dirstate.parents()
@@ -69,10 +69,11 @@ def fetch(ui, repo, source='default', **
                            '(use "hg update" to check out tip)'))
     if p2 != nullid:
         raise util.Abort(_('outstanding uncommitted merge'))
-    wlock = repo.wlock()
-    lock = repo.lock()
+    wlock = lock = None
     try:
-        mod, add, rem = repo.status(wlock=wlock)[:3]
+        wlock = repo.wlock()
+        lock = repo.lock()
+        mod, add, rem = repo.status()[:3]
         if mod or add or rem:
             raise util.Abort(_('outstanding uncommitted changes'))
         if len(repo.heads()) > 1:
@@ -80,8 +81,7 @@ def fetch(ui, repo, source='default', **
                                '(use "hg heads" and "hg merge" to merge)'))
         return pull()
     finally:
-        lock.release()
-        wlock.release()
+        del lock, wlock
 
 cmdtable = {
     'fetch':
--- a/hgext/gpg.py
+++ b/hgext/gpg.py
@@ -240,7 +240,7 @@ def sign(ui, repo, *revs, **opts):
 
     repo.wfile(".hgsigs", "ab").write(sigmessage)
 
-    if repo.dirstate.state(".hgsigs") == '?':
+    if '.hgsigs' not in repo.dirstate:
         repo.add([".hgsigs"])
 
     if opts["no_commit"]:
--- a/hgext/mq.py
+++ b/hgext/mq.py
@@ -323,10 +323,10 @@ class queue:
         patch.diff(repo, node1, node2, fns, match=matchfn,
                    fp=fp, changes=changes, opts=self.diffopts())
 
-    def mergeone(self, repo, mergeq, head, patch, rev, wlock):
+    def mergeone(self, repo, mergeq, head, patch, rev):
         # first try just applying the patch
         (err, n) = self.apply(repo, [ patch ], update_status=False,
-                              strict=True, merge=rev, wlock=wlock)
+                              strict=True, merge=rev)
 
         if err == 0:
             return (err, n)
@@ -337,15 +337,14 @@ class queue:
         self.ui.warn("patch didn't work out, merging %s\n" % patch)
 
         # apply failed, strip away that rev and merge.
-        hg.clean(repo, head, wlock=wlock)
-        self.strip(repo, n, update=False, backup='strip', wlock=wlock)
+        hg.clean(repo, head)
+        self.strip(repo, n, update=False, backup='strip')
 
         ctx = repo.changectx(rev)
-        ret = hg.merge(repo, rev, wlock=wlock)
+        ret = hg.merge(repo, rev)
         if ret:
             raise util.Abort(_("update returned %d") % ret)
-        n = repo.commit(None, ctx.description(), ctx.user(),
-                        force=1, wlock=wlock)
+        n = repo.commit(None, ctx.description(), ctx.user(), force=1)
         if n == None:
             raise util.Abort(_("repo commit failed"))
         try:
@@ -381,7 +380,7 @@ class queue:
                 return pp[1]
         return pp[0]
 
-    def mergepatch(self, repo, mergeq, series, wlock):
+    def mergepatch(self, repo, mergeq, series):
         if len(self.applied) == 0:
             # each of the patches merged in will have two parents.  This
             # can confuse the qrefresh, qdiff, and strip code because it
@@ -390,8 +389,7 @@ class queue:
             # the first patch in the queue is never a merge patch
             #
             pname = ".hg.patches.merge.marker"
-            n = repo.commit(None, '[mq]: merge marker', user=None, force=1,
-                            wlock=wlock)
+            n = repo.commit(None, '[mq]: merge marker', user=None, force=1)
             self.removeundo(repo)
             self.applied.append(statusentry(revlog.hex(n), pname))
             self.applied_dirty = 1
@@ -412,7 +410,7 @@ class queue:
                 self.ui.warn("patch %s is not applied\n" % patch)
                 return (1, None)
             rev = revlog.bin(info[1])
-            (err, head) = self.mergeone(repo, mergeq, head, patch, rev, wlock)
+            (err, head) = self.mergeone(repo, mergeq, head, patch, rev)
             if head:
                 self.applied.append(statusentry(revlog.hex(head), patch))
                 self.applied_dirty = 1
@@ -437,30 +435,30 @@ class queue:
         return (True, files, fuzz)
 
     def apply(self, repo, series, list=False, update_status=True,
-              strict=False, patchdir=None, merge=None, wlock=None,
-              all_files={}):
-        if not wlock:
-            wlock = repo.wlock()
-        lock = repo.lock()
-        tr = repo.transaction()
+              strict=False, patchdir=None, merge=None, all_files={}):
+        wlock = lock = tr = None
         try:
-            ret = self._apply(tr, repo, series, list, update_status,
-                              strict, patchdir, merge, wlock,
-                              lock=lock, all_files=all_files)
-            tr.close()
-            self.save_dirty()
-            return ret
-        except:
+            wlock = repo.wlock()
+            lock = repo.lock()
+            tr = repo.transaction()
             try:
-                tr.abort()
-            finally:
-                repo.invalidate()
-                repo.dirstate.invalidate()
-            raise
+                ret = self._apply(tr, repo, series, list, update_status,
+                                  strict, patchdir, merge, all_files=all_files)
+                tr.close()
+                self.save_dirty()
+                return ret
+            except:
+                try:
+                    tr.abort()
+                finally:
+                    repo.invalidate()
+                    repo.dirstate.invalidate()
+                raise
+        finally:
+            del lock, wlock, tr
 
     def _apply(self, tr, repo, series, list=False, update_status=True,
-               strict=False, patchdir=None, merge=None, wlock=None,
-               lock=None, all_files={}):
+               strict=False, patchdir=None, merge=None, all_files={}):
         # TODO unify with commands.py
         if not patchdir:
             patchdir = self.path
@@ -497,17 +495,18 @@ class queue:
                 removed = []
                 merged = []
                 for f in files:
-                    if os.path.exists(repo.dirstate.wjoin(f)):
+                    if os.path.exists(repo.wjoin(f)):
                         merged.append(f)
                     else:
                         removed.append(f)
-                repo.dirstate.update(repo.dirstate.filterfiles(removed), 'r')
-                repo.dirstate.update(repo.dirstate.filterfiles(merged), 'm')
+                for f in removed:
+                    repo.dirstate.remove(f)
+                for f in merged:
+                    repo.dirstate.merge(f)
                 p1, p2 = repo.dirstate.parents()
                 repo.dirstate.setparents(p1, merge)
-            files = patch.updatedir(self.ui, repo, files, wlock=wlock)
-            n = repo.commit(files, message, user, date, force=1, lock=lock,
-                            wlock=wlock)
+            files = patch.updatedir(self.ui, repo, files)
+            n = repo.commit(files, message, user, date, force=1)
 
             if n == None:
                 raise util.Abort(_("repo commit failed"))
@@ -614,44 +613,49 @@ class queue:
         commitfiles = m + a + r
         self.check_toppatch(repo)
         wlock = repo.wlock()
-        insert = self.full_series_end()
-        if msg:
-            n = repo.commit(commitfiles, msg, force=True, wlock=wlock)
-        else:
-            n = repo.commit(commitfiles,
-                            "[mq]: %s" % patch, force=True, wlock=wlock)
-        if n == None:
-            raise util.Abort(_("repo commit failed"))
-        self.full_series[insert:insert] = [patch]
-        self.applied.append(statusentry(revlog.hex(n), patch))
-        self.parse_series()
-        self.series_dirty = 1
-        self.applied_dirty = 1
-        p = self.opener(patch, "w")
-        if msg:
-            msg = msg + "\n"
-            p.write(msg)
-        p.close()
-        wlock = None
-        r = self.qrepo()
-        if r: r.add([patch])
-        if commitfiles:
-            self.refresh(repo, short=True)
-        self.removeundo(repo)
+        try:
+            insert = self.full_series_end()
+            if msg:
+                n = repo.commit(commitfiles, msg, force=True)
+            else:
+                n = repo.commit(commitfiles, "[mq]: %s" % patch, force=True)
+            if n == None:
+                raise util.Abort(_("repo commit failed"))
+            self.full_series[insert:insert] = [patch]
+            self.applied.append(statusentry(revlog.hex(n), patch))
+            self.parse_series()
+            self.series_dirty = 1
+            self.applied_dirty = 1
+            p = self.opener(patch, "w")
+            if msg:
+                msg = msg + "\n"
+                p.write(msg)
+            p.close()
+            wlock = None
+            r = self.qrepo()
+            if r: r.add([patch])
+            if commitfiles:
+                self.refresh(repo, short=True)
+            self.removeundo(repo)
+        finally:
+            del wlock
 
-    def strip(self, repo, rev, update=True, backup="all", wlock=None):
-        if not wlock:
+    def strip(self, repo, rev, update=True, backup="all"):
+        wlock = lock = None
+        try:
             wlock = repo.wlock()
-        lock = repo.lock()
+            lock = repo.lock()
 
-        if update:
-            self.check_localchanges(repo, refresh=False)
-            urev = self.qparents(repo, rev)
-            hg.clean(repo, urev, wlock=wlock)
-            repo.dirstate.write()
+            if update:
+                self.check_localchanges(repo, refresh=False)
+                urev = self.qparents(repo, rev)
+                hg.clean(repo, urev)
+                repo.dirstate.write()
 
-        self.removeundo(repo)
-        repair.strip(self.ui, repo, rev, backup)
+            self.removeundo(repo)
+            repair.strip(self.ui, repo, rev, backup)
+        finally:
+            del lock, wlock
 
     def isapplied(self, patch):
         """returns (index, rev, patch)"""
@@ -735,157 +739,161 @@ class queue:
         raise util.Abort(_("patch %s not in series") % patch)
 
     def push(self, repo, patch=None, force=False, list=False,
-             mergeq=None, wlock=None):
-        if not wlock:
-            wlock = repo.wlock()
-        patch = self.lookup(patch)
-        # Suppose our series file is: A B C and the current 'top' patch is B.
-        # qpush C should be performed (moving forward)
-        # qpush B is a NOP (no change)
-        # qpush A is an error (can't go backwards with qpush)
-        if patch:
-            info = self.isapplied(patch)
-            if info:
-                if info[0] < len(self.applied) - 1:
-                    raise util.Abort(_("cannot push to a previous patch: %s") %
-                                     patch)
-                if info[0] < len(self.series) - 1:
-                    self.ui.warn(_('qpush: %s is already at the top\n') % patch)
-                else:
-                    self.ui.warn(_('all patches are currently applied\n'))
-                return
+             mergeq=None):
+        wlock = repo.wlock()
+        try:
+            patch = self.lookup(patch)
+            # Suppose our series file is: A B C and the current 'top'
+            # patch is B. qpush C should be performed (moving forward)
+            # qpush B is a NOP (no change) qpush A is an error (can't
+            # go backwards with qpush)
+            if patch:
+                info = self.isapplied(patch)
+                if info:
+                    if info[0] < len(self.applied) - 1:
+                        raise util.Abort(
+                            _("cannot push to a previous patch: %s") % patch)
+                    if info[0] < len(self.series) - 1:
+                        self.ui.warn(
+                            _('qpush: %s is already at the top\n') % patch)
+                    else:
+                        self.ui.warn(_('all patches are currently applied\n'))
+                    return
 
-        # Following the above example, starting at 'top' of B:
-        #  qpush should be performed (pushes C), but a subsequent qpush without
-        #  an argument is an error (nothing to apply). This allows a loop
-        #  of "...while hg qpush..." to work as it detects an error when done
-        if self.series_end() == len(self.series):
-            self.ui.warn(_('patch series already fully applied\n'))
-            return 1
-        if not force:
-            self.check_localchanges(repo)
+            # Following the above example, starting at 'top' of B:
+            # qpush should be performed (pushes C), but a subsequent
+            # qpush without an argument is an error (nothing to
+            # apply). This allows a loop of "...while hg qpush..." to
+            # work as it detects an error when done
+            if self.series_end() == len(self.series):
+                self.ui.warn(_('patch series already fully applied\n'))
+                return 1
+            if not force:
+                self.check_localchanges(repo)
 
-        self.applied_dirty = 1;
-        start = self.series_end()
-        if start > 0:
-            self.check_toppatch(repo)
-        if not patch:
-            patch = self.series[start]
-            end = start + 1
-        else:
-            end = self.series.index(patch, start) + 1
-        s = self.series[start:end]
-        all_files = {}
-        try:
-            if mergeq:
-                ret = self.mergepatch(repo, mergeq, s, wlock)
+            self.applied_dirty = 1;
+            start = self.series_end()
+            if start > 0:
+                self.check_toppatch(repo)
+            if not patch:
+                patch = self.series[start]
+                end = start + 1
             else:
-                ret = self.apply(repo, s, list, wlock=wlock,
-                                 all_files=all_files)
-        except:
-            self.ui.warn(_('cleaning up working directory...'))
-            node = repo.dirstate.parents()[0]
-            hg.revert(repo, node, None, wlock)
-            unknown = repo.status(wlock=wlock)[4]
-            # only remove unknown files that we know we touched or
-            # created while patching
-            for f in unknown:
-                if f in all_files:
-                    util.unlink(repo.wjoin(f))
-            self.ui.warn(_('done\n'))
-            raise
-        top = self.applied[-1].name
-        if ret[0]:
-            self.ui.write("Errors during apply, please fix and refresh %s\n" %
-                          top)
-        else:
-            self.ui.write("Now at: %s\n" % top)
-        return ret[0]
+                end = self.series.index(patch, start) + 1
+            s = self.series[start:end]
+            all_files = {}
+            try:
+                if mergeq:
+                    ret = self.mergepatch(repo, mergeq, s)
+                else:
+                    ret = self.apply(repo, s, list, all_files=all_files)
+            except:
+                self.ui.warn(_('cleaning up working directory...'))
+                node = repo.dirstate.parents()[0]
+                hg.revert(repo, node, None)
+                unknown = repo.status()[4]
+                # only remove unknown files that we know we touched or
+                # created while patching
+                for f in unknown:
+                    if f in all_files:
+                        util.unlink(repo.wjoin(f))
+                self.ui.warn(_('done\n'))
+                raise
+            top = self.applied[-1].name
+            if ret[0]:
+                self.ui.write(
+                    "Errors during apply, please fix and refresh %s\n" % top)
+            else:
+                self.ui.write("Now at: %s\n" % top)
+            return ret[0]
+        finally:
+            del wlock
 
-    def pop(self, repo, patch=None, force=False, update=True, all=False,
-            wlock=None):
+    def pop(self, repo, patch=None, force=False, update=True, all=False):
         def getfile(f, rev):
             t = repo.file(f).read(rev)
             repo.wfile(f, "w").write(t)
 
-        if not wlock:
-            wlock = repo.wlock()
-        if patch:
-            # index, rev, patch
-            info = self.isapplied(patch)
-            if not info:
-                patch = self.lookup(patch)
-            info = self.isapplied(patch)
-            if not info:
-                raise util.Abort(_("patch %s is not applied") % patch)
+        wlock = repo.wlock()
+        try:
+            if patch:
+                # index, rev, patch
+                info = self.isapplied(patch)
+                if not info:
+                    patch = self.lookup(patch)
+                info = self.isapplied(patch)
+                if not info:
+                    raise util.Abort(_("patch %s is not applied") % patch)
 
-        if len(self.applied) == 0:
-            # Allow qpop -a to work repeatedly,
-            # but not qpop without an argument
-            self.ui.warn(_("no patches applied\n"))
-            return not all
+            if len(self.applied) == 0:
+                # Allow qpop -a to work repeatedly,
+                # but not qpop without an argument
+                self.ui.warn(_("no patches applied\n"))
+                return not all
 
-        if not update:
-            parents = repo.dirstate.parents()
-            rr = [ revlog.bin(x.rev) for x in self.applied ]
-            for p in parents:
-                if p in rr:
-                    self.ui.warn("qpop: forcing dirstate update\n")
-                    update = True
+            if not update:
+                parents = repo.dirstate.parents()
+                rr = [ revlog.bin(x.rev) for x in self.applied ]
+                for p in parents:
+                    if p in rr:
+                        self.ui.warn("qpop: forcing dirstate update\n")
+                        update = True
 
-        if not force and update:
-            self.check_localchanges(repo)
+            if not force and update:
+                self.check_localchanges(repo)
 
-        self.applied_dirty = 1;
-        end = len(self.applied)
-        if not patch:
-            if all:
-                popi = 0
+            self.applied_dirty = 1;
+            end = len(self.applied)
+            if not patch:
+                if all:
+                    popi = 0
+                else:
+                    popi = len(self.applied) - 1
             else:
-                popi = len(self.applied) - 1
-        else:
-            popi = info[0] + 1
-            if popi >= end:
-                self.ui.warn("qpop: %s is already at the top\n" % patch)
-                return
-        info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
+                popi = info[0] + 1
+                if popi >= end:
+                    self.ui.warn("qpop: %s is already at the top\n" % patch)
+                    return
+            info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
 
-        start = info[0]
-        rev = revlog.bin(info[1])
+            start = info[0]
+            rev = revlog.bin(info[1])
 
-        # we know there are no local changes, so we can make a simplified
-        # form of hg.update.
-        if update:
-            top = self.check_toppatch(repo)
-            qp = self.qparents(repo, rev)
-            changes = repo.changelog.read(qp)
-            mmap = repo.manifest.read(changes[0])
-            m, a, r, d, u = repo.status(qp, top)[:5]
-            if d:
-                raise util.Abort("deletions found between repo revs")
-            for f in m:
-                getfile(f, mmap[f])
-            for f in r:
-                getfile(f, mmap[f])
-                util.set_exec(repo.wjoin(f), mmap.execf(f))
-            repo.dirstate.update(m + r, 'n')
-            for f in a:
-                try:
-                    os.unlink(repo.wjoin(f))
-                except OSError, e:
-                    if e.errno != errno.ENOENT:
-                        raise
-                try: os.removedirs(os.path.dirname(repo.wjoin(f)))
-                except: pass
-            if a:
-                repo.dirstate.forget(a)
-            repo.dirstate.setparents(qp, revlog.nullid)
-        self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
-        del self.applied[start:end]
-        if len(self.applied):
-            self.ui.write("Now at: %s\n" % self.applied[-1].name)
-        else:
-            self.ui.write("Patch queue now empty\n")
+            # we know there are no local changes, so we can make a simplified
+            # form of hg.update.
+            if update:
+                top = self.check_toppatch(repo)
+                qp = self.qparents(repo, rev)
+                changes = repo.changelog.read(qp)
+                mmap = repo.manifest.read(changes[0])
+                m, a, r, d, u = repo.status(qp, top)[:5]
+                if d:
+                    raise util.Abort("deletions found between repo revs")
+                for f in m:
+                    getfile(f, mmap[f])
+                for f in r:
+                    getfile(f, mmap[f])
+                    util.set_exec(repo.wjoin(f), mmap.execf(f))
+                for f in m + r:
+                    repo.dirstate.normal(f)
+                for f in a:
+                    try:
+                        os.unlink(repo.wjoin(f))
+                    except OSError, e:
+                        if e.errno != errno.ENOENT:
+                            raise
+                    try: os.removedirs(os.path.dirname(repo.wjoin(f)))
+                    except: pass
+                    repo.dirstate.forget(f)
+                repo.dirstate.setparents(qp, revlog.nullid)
+            self.strip(repo, rev, update=False, backup='strip')
+            del self.applied[start:end]
+            if len(self.applied):
+                self.ui.write("Now at: %s\n" % self.applied[-1].name)
+            else:
+                self.ui.write("Patch queue now empty\n")
+        finally:
+            del wlock
 
     def diff(self, repo, pats, opts):
         top = self.check_toppatch(repo)
@@ -902,175 +910,184 @@ class queue:
             self.ui.write("No patches applied\n")
             return 1
         wlock = repo.wlock()
-        self.check_toppatch(repo)
-        (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
-        top = revlog.bin(top)
-        cparents = repo.changelog.parents(top)
-        patchparent = self.qparents(repo, top)
-        message, comments, user, date, patchfound = self.readheaders(patchfn)
-
-        patchf = self.opener(patchfn, 'r+')
-
-        # if the patch was a git patch, refresh it as a git patch
-        for line in patchf:
-            if line.startswith('diff --git'):
-                self.diffopts().git = True
-                break
-        patchf.seek(0)
-        patchf.truncate()
+        try:
+            self.check_toppatch(repo)
+            (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
+            top = revlog.bin(top)
+            cparents = repo.changelog.parents(top)
+            patchparent = self.qparents(repo, top)
+            message, comments, user, date, patchfound = self.readheaders(patchfn)
 
-        msg = opts.get('msg', '').rstrip()
-        if msg:
-            if comments:
-                # Remove existing message.
-                ci = 0
-                subj = None
-                for mi in xrange(len(message)):
-                    if comments[ci].lower().startswith('subject: '):
-                        subj = comments[ci][9:]
-                    while message[mi] != comments[ci] and message[mi] != subj:
-                        ci += 1
-                    del comments[ci]
-            comments.append(msg)
-        if comments:
-            comments = "\n".join(comments) + '\n\n'
-            patchf.write(comments)
+            patchf = self.opener(patchfn, 'r+')
+
+            # if the patch was a git patch, refresh it as a git patch
+            for line in patchf:
+                if line.startswith('diff --git'):
+                    self.diffopts().git = True
+                    break
+            patchf.seek(0)
+            patchf.truncate()
 
-        if opts.get('git'):
-            self.diffopts().git = True
-        fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
-        tip = repo.changelog.tip()
-        if top == tip:
-            # if the top of our patch queue is also the tip, there is an
-            # optimization here.  We update the dirstate in place and strip
-            # off the tip commit.  Then just commit the current directory
-            # tree.  We can also send repo.commit the list of files
-            # changed to speed up the diff
-            #
-            # in short mode, we only diff the files included in the
-            # patch already
-            #
-            # this should really read:
-            #   mm, dd, aa, aa2, uu = repo.status(tip, patchparent)[:5]
-            # but we do it backwards to take advantage of manifest/chlog
-            # caching against the next repo.status call
-            #
-            mm, aa, dd, aa2, uu = repo.status(patchparent, tip)[:5]
-            changes = repo.changelog.read(tip)
-            man = repo.manifest.read(changes[0])
-            aaa = aa[:]
-            if opts.get('short'):
-                filelist = mm + aa + dd
-                match = dict.fromkeys(filelist).__contains__
-            else:
-                filelist = None
-                match = util.always
-            m, a, r, d, u = repo.status(files=filelist, match=match)[:5]
+            msg = opts.get('msg', '').rstrip()
+            if msg:
+                if comments:
+                    # Remove existing message.
+                    ci = 0
+                    subj = None
+                    for mi in xrange(len(message)):
+                        if comments[ci].lower().startswith('subject: '):
+                            subj = comments[ci][9:]
+                        while message[mi] != comments[ci] and message[mi] != subj:
+                            ci += 1
+                        del comments[ci]
+                comments.append(msg)
+            if comments:
+                comments = "\n".join(comments) + '\n\n'
+                patchf.write(comments)
 
-            # we might end up with files that were added between tip and
-            # the dirstate parent, but then changed in the local dirstate.
-            # in this case, we want them to only show up in the added section
-            for x in m:
-                if x not in aa:
-                    mm.append(x)
-            # we might end up with files added by the local dirstate that
-            # were deleted by the patch.  In this case, they should only
-            # show up in the changed section.
-            for x in a:
-                if x in dd:
-                    del dd[dd.index(x)]
-                    mm.append(x)
+            if opts.get('git'):
+                self.diffopts().git = True
+            fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
+            tip = repo.changelog.tip()
+            if top == tip:
+                # if the top of our patch queue is also the tip, there is an
+                # optimization here.  We update the dirstate in place and strip
+                # off the tip commit.  Then just commit the current directory
+                # tree.  We can also send repo.commit the list of files
+                # changed to speed up the diff
+                #
+                # in short mode, we only diff the files included in the
+                # patch already
+                #
+                # this should really read:
+                #   mm, dd, aa, aa2, uu = repo.status(tip, patchparent)[:5]
+                # but we do it backwards to take advantage of manifest/chlog
+                # caching against the next repo.status call
+                #
+                mm, aa, dd, aa2, uu = repo.status(patchparent, tip)[:5]
+                changes = repo.changelog.read(tip)
+                man = repo.manifest.read(changes[0])
+                aaa = aa[:]
+                if opts.get('short'):
+                    filelist = mm + aa + dd
+                    match = dict.fromkeys(filelist).__contains__
                 else:
-                    aa.append(x)
-            # make sure any files deleted in the local dirstate
-            # are not in the add or change column of the patch
-            forget = []
-            for x in d + r:
-                if x in aa:
-                    del aa[aa.index(x)]
-                    forget.append(x)
-                    continue
-                elif x in mm:
-                    del mm[mm.index(x)]
-                dd.append(x)
+                    filelist = None
+                    match = util.always
+                m, a, r, d, u = repo.status(files=filelist, match=match)[:5]
 
-            m = util.unique(mm)
-            r = util.unique(dd)
-            a = util.unique(aa)
-            c = [filter(matchfn, l) for l in (m, a, r, [], u)]
-            filelist = util.unique(c[0] + c[1] + c[2])
-            patch.diff(repo, patchparent, files=filelist, match=matchfn,
-                       fp=patchf, changes=c, opts=self.diffopts())
-            patchf.close()
+                # we might end up with files that were added between
+                # tip and the dirstate parent, but then changed in the
+                # local dirstate. in this case, we want them to only
+                # show up in the added section
+                for x in m:
+                    if x not in aa:
+                        mm.append(x)
+                # we might end up with files added by the local dirstate that
+                # were deleted by the patch.  In this case, they should only
+                # show up in the changed section.
+                for x in a:
+                    if x in dd:
+                        del dd[dd.index(x)]
+                        mm.append(x)
+                    else:
+                        aa.append(x)
+                # make sure any files deleted in the local dirstate
+                # are not in the add or change column of the patch
+                forget = []
+                for x in d + r:
+                    if x in aa:
+                        del aa[aa.index(x)]
+                        forget.append(x)
+                        continue
+                    elif x in mm:
+                        del mm[mm.index(x)]
+                    dd.append(x)
+
+                m = util.unique(mm)
+                r = util.unique(dd)
+                a = util.unique(aa)
+                c = [filter(matchfn, l) for l in (m, a, r, [], u)]
+                filelist = util.unique(c[0] + c[1] + c[2])
+                patch.diff(repo, patchparent, files=filelist, match=matchfn,
+                           fp=patchf, changes=c, opts=self.diffopts())
+                patchf.close()
 
-            repo.dirstate.setparents(*cparents)
-            copies = {}
-            for dst in a:
-                src = repo.dirstate.copied(dst)
-                if src is None:
-                    continue
-                copies.setdefault(src, []).append(dst)
-            repo.dirstate.update(a, 'a')
-            # remember the copies between patchparent and tip
-            # this may be slow, so don't do it if we're not tracking copies
-            if self.diffopts().git:
-                for dst in aaa:
-                    f = repo.file(dst)
-                    src = f.renamed(man[dst])
-                    if src:
-                        copies[src[0]] = copies.get(dst, [])
-                        if dst in a:
-                            copies[src[0]].append(dst)
-                    # we can't copy a file created by the patch itself
-                    if dst in copies:
-                        del copies[dst]
-            for src, dsts in copies.iteritems():
-                for dst in dsts:
-                    repo.dirstate.copy(src, dst)
-            repo.dirstate.update(r, 'r')
-            # if the patch excludes a modified file, mark that file with mtime=0
-            # so status can see it.
-            mm = []
-            for i in xrange(len(m)-1, -1, -1):
-                if not matchfn(m[i]):
-                    mm.append(m[i])
-                    del m[i]
-            repo.dirstate.update(m, 'n')
-            repo.dirstate.update(mm, 'n', st_mtime=-1, st_size=-1)
-            repo.dirstate.forget(forget)
+                repo.dirstate.setparents(*cparents)
+                copies = {}
+                for dst in a:
+                    src = repo.dirstate.copied(dst)
+                    if src is None:
+                        continue
+                    copies.setdefault(src, []).append(dst)
+                    repo.dirstate.add(dst)
+                # remember the copies between patchparent and tip
+                # this may be slow, so don't do it if we're not tracking copies
+                if self.diffopts().git:
+                    for dst in aaa:
+                        f = repo.file(dst)
+                        src = f.renamed(man[dst])
+                        if src:
+                            copies[src[0]] = copies.get(dst, [])
+                            if dst in a:
+                                copies[src[0]].append(dst)
+                        # we can't copy a file created by the patch itself
+                        if dst in copies:
+                            del copies[dst]
+                for src, dsts in copies.iteritems():
+                    for dst in dsts:
+                        repo.dirstate.copy(src, dst)
+                for f in r:
+                    repo.dirstate.remove(f)
+                # if the patch excludes a modified file, mark that
+                # file with mtime=0 so status can see it.
+                mm = []
+                for i in xrange(len(m)-1, -1, -1):
+                    if not matchfn(m[i]):
+                        mm.append(m[i])
+                        del m[i]
+                for f in m:
+                    repo.dirstate.normal(f)
+                for f in mm:
+                    repo.dirstate.normaldirty(f)
+                for f in forget:
+                    repo.dirstate.forget(f)
 
-            if not msg:
-                if not message:
-                    message = "[mq]: %s\n" % patchfn
+                if not msg:
+                    if not message:
+                        message = "[mq]: %s\n" % patchfn
+                    else:
+                        message = "\n".join(message)
                 else:
-                    message = "\n".join(message)
-            else:
-                message = msg
+                    message = msg
 
-            self.strip(repo, top, update=False, backup='strip', wlock=wlock)
-            n = repo.commit(filelist, message, changes[1], match=matchfn,
-                            force=1, wlock=wlock)
-            self.applied[-1] = statusentry(revlog.hex(n), patchfn)
-            self.applied_dirty = 1
-            self.removeundo(repo)
-        else:
-            self.printdiff(repo, patchparent, fp=patchf)
-            patchf.close()
-            added = repo.status()[1]
-            for a in added:
-                f = repo.wjoin(a)
-                try:
-                    os.unlink(f)
-                except OSError, e:
-                    if e.errno != errno.ENOENT:
-                        raise
-                try: os.removedirs(os.path.dirname(f))
-                except: pass
-            # forget the file copies in the dirstate
-            # push should readd the files later on
-            repo.dirstate.forget(added)
-            self.pop(repo, force=True, wlock=wlock)
-            self.push(repo, force=True, wlock=wlock)
+                self.strip(repo, top, update=False,
+                           backup='strip')
+                n = repo.commit(filelist, message, changes[1], match=matchfn,
+                                force=1)
+                self.applied[-1] = statusentry(revlog.hex(n), patchfn)
+                self.applied_dirty = 1
+                self.removeundo(repo)
+            else:
+                self.printdiff(repo, patchparent, fp=patchf)
+                patchf.close()
+                added = repo.status()[1]
+                for a in added:
+                    f = repo.wjoin(a)
+                    try:
+                        os.unlink(f)
+                    except OSError, e:
+                        if e.errno != errno.ENOENT:
+                            raise
+                    try: os.removedirs(os.path.dirname(f))
+                    except: pass
+                    # forget the file copies in the dirstate
+                    # push should readd the files later on
+                    repo.dirstate.forget(a)
+                self.pop(repo, force=True)
+                self.push(repo, force=True)
+        finally:
+            del wlock
 
     def init(self, repo, create=False):
         if not create and os.path.isdir(self.path):
@@ -1866,10 +1883,13 @@ def rename(ui, repo, patch, name=None, *
     r = q.qrepo()
     if r:
         wlock = r.wlock()
-        if r.dirstate.state(name) == 'r':
-            r.undelete([name], wlock)
-        r.copy(patch, name, wlock)
-        r.remove([patch], False, wlock)
+        try:
+            if r.dirstate[name] == 'r':
+                r.undelete([name])
+            r.copy(patch, name)
+            r.remove([patch], False)
+        finally:
+            del wlock
 
     q.save_dirty()
 
--- a/hgext/transplant.py
+++ b/hgext/transplant.py
@@ -96,9 +96,10 @@ class transplanter:
         diffopts = patch.diffopts(self.ui, opts)
         diffopts.git = True
 
-        wlock = repo.wlock()
-        lock = repo.lock()
+        lock = wlock = None
         try:
+            wlock = repo.wlock()
+            lock = repo.lock()
             for rev in revs:
                 node = revmap[rev]
                 revstr = '%s:%s' % (rev, revlog.short(node))
@@ -118,9 +119,8 @@ class transplanter:
                         continue
                     if pulls:
                         if source != repo:
-                            repo.pull(source, heads=pulls, lock=lock)
-                        merge.update(repo, pulls[-1], False, False, None,
-                                     wlock=wlock)
+                            repo.pull(source, heads=pulls)
+                        merge.update(repo, pulls[-1], False, False, None)
                         p1, p2 = repo.dirstate.parents()
                         pulls = []
 
@@ -131,7 +131,7 @@ class transplanter:
                     # fail.
                     domerge = True
                     if not hasnode(repo, node):
-                        repo.pull(source, heads=[node], lock=lock)
+                        repo.pull(source, heads=[node])
 
                 if parents[1] != revlog.nullid:
                     self.ui.note(_('skipping merge changeset %s:%s\n')
@@ -146,11 +146,11 @@ class transplanter:
                 del revmap[rev]
                 if patchfile or domerge:
                     try:
-                        n = self.applyone(repo, node, source.changelog.read(node),
+                        n = self.applyone(repo, node,
+                                          source.changelog.read(node),
                                           patchfile, merge=domerge,
                                           log=opts.get('log'),
-                                          filter=opts.get('filter'),
-                                          lock=lock, wlock=wlock)
+                                          filter=opts.get('filter'))
                         if n and domerge:
                             self.ui.status(_('%s merged at %s\n') % (revstr,
                                       revlog.short(n)))
@@ -161,11 +161,12 @@ class transplanter:
                         if patchfile:
                             os.unlink(patchfile)
             if pulls:
-                repo.pull(source, heads=pulls, lock=lock)
-                merge.update(repo, pulls[-1], False, False, None, wlock=wlock)
+                repo.pull(source, heads=pulls)
+                merge.update(repo, pulls[-1], False, False, None)
         finally:
             self.saveseries(revmap, merges)
             self.transplants.write()
+            del lock, wlock
 
     def filter(self, filter, changelog, patchfile):
         '''arbitrarily rewrite changeset before applying it'''
@@ -193,7 +194,7 @@ class transplanter:
         return (user, date, msg)
 
     def applyone(self, repo, node, cl, patchfile, merge=False, log=False,
-                 filter=None, lock=None, wlock=None):
+                 filter=None):
         '''apply the patch in patchfile to the repository as a transplant'''
         (manifest, user, (time, timezone), files, message) = cl[:5]
         date = "%d %d" % (time, timezone)
@@ -219,7 +220,7 @@ class transplanter:
                         self.ui.warn(_('%s: empty changeset') % revlog.hex(node))
                         return None
                 finally:
-                    files = patch.updatedir(self.ui, repo, files, wlock=wlock)
+                    files = patch.updatedir(self.ui, repo, files)
             except Exception, inst:
                 if filter:
                     os.unlink(patchfile)
@@ -237,8 +238,7 @@ class transplanter:
             p1, p2 = repo.dirstate.parents()
             repo.dirstate.setparents(p1, node)
 
-        n = repo.commit(files, message, user, date, lock=lock, wlock=wlock,
-                        extra=extra)
+        n = repo.commit(files, message, user, date, extra=extra)
         if not merge:
             self.transplants.set(n, node)
 
@@ -272,20 +272,24 @@ class transplanter:
 
         extra = {'transplant_source': node}
         wlock = repo.wlock()
-        p1, p2 = repo.dirstate.parents()
-        if p1 != parents[0]:
-            raise util.Abort(_('working dir not at transplant parent %s') %
-                             revlog.hex(parents[0]))
-        if merge:
-            repo.dirstate.setparents(p1, parents[1])
-        n = repo.commit(None, message, user, date, wlock=wlock, extra=extra)
-        if not n:
-            raise util.Abort(_('commit failed'))
-        if not merge:
-            self.transplants.set(n, node)
-        self.unlog()
+        try:
+            p1, p2 = repo.dirstate.parents()
+            if p1 != parents[0]:
+                raise util.Abort(
+                    _('working dir not at transplant parent %s') %
+                                 revlog.hex(parents[0]))
+            if merge:
+                repo.dirstate.setparents(p1, parents[1])
+            n = repo.commit(None, message, user, date, extra=extra)
+            if not n:
+                raise util.Abort(_('commit failed'))
+            if not merge:
+                self.transplants.set(n, node)
+            self.unlog()
 
-        return n, node
+            return n, node
+        finally:
+            del wlock
 
     def readseries(self):
         nodes = []
--- a/mercurial/bundlerepo.py
+++ b/mercurial/bundlerepo.py
@@ -61,7 +61,7 @@ class bundlerevlog(revlog.revlog):
             if self.version == revlog.REVLOGV0:
                 e = (start, size, None, link, p1, p2, node)
             else:
-                e = (self.offset_type(start, 0), size, -1, None, link,
+                e = (revlog.offset_type(start, 0), size, -1, None, link,
                      self.rev(p1), self.rev(p2), node)
             self.basemap[n] = prev
             self.index.append(e)
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -628,8 +628,7 @@ def findrenames(repo, added=None, remove
         if bestname:
             yield bestname, a, bestscore
 
-def addremove(repo, pats=[], opts={}, wlock=None, dry_run=None,
-              similarity=None):
+def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
     if dry_run is None:
         dry_run = opts.get('dry_run')
     if similarity is None:
@@ -638,19 +637,19 @@ def addremove(repo, pats=[], opts={}, wl
     mapping = {}
     for src, abs, rel, exact in walk(repo, pats, opts):
         target = repo.wjoin(abs)
-        if src == 'f' and repo.dirstate.state(abs) == '?':
+        if src == 'f' and abs not in repo.dirstate:
             add.append(abs)
             mapping[abs] = rel, exact
             if repo.ui.verbose or not exact:
                 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
-        if repo.dirstate.state(abs) != 'r' and not util.lexists(target):
+        if repo.dirstate[abs] != 'r' and not util.lexists(target):
             remove.append(abs)
             mapping[abs] = rel, exact
             if repo.ui.verbose or not exact:
                 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
     if not dry_run:
-        repo.add(add, wlock=wlock)
-        repo.remove(remove, wlock=wlock)
+        repo.add(add)
+        repo.remove(remove)
     if similarity > 0:
         for old, new, score in findrenames(repo, add, remove, similarity):
             oldrel, oldexact = mapping[old]
@@ -660,7 +659,7 @@ def addremove(repo, pats=[], opts={}, wl
                                  '(%d%% similar)\n') %
                                (oldrel, newrel, score * 100))
             if not dry_run:
-                repo.copy(old, new, wlock=wlock)
+                repo.copy(old, new)
 
 def service(opts, parentfn=None, initfn=None, runfn=None):
     '''Run a command as a service.'''
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -33,7 +33,7 @@ def add(ui, repo, *pats, **opts):
             if ui.verbose:
                 ui.status(_('adding %s\n') % rel)
             names.append(abs)
-        elif repo.dirstate.state(abs) == '?':
+        elif abs not in repo.dirstate:
             ui.status(_('adding %s\n') % rel)
             names.append(abs)
     if not opts.get('dry_run'):
@@ -456,7 +456,7 @@ def commit(ui, repo, *pats, **opts):
                 elif not (stat.S_ISREG(mode) or stat.S_ISLNK(mode)):
                     raise util.Abort(_("can't commit %s: "
                                        "unsupported file type!") % rf)
-                elif repo.dirstate.state(f) == '?':
+                elif f not in repo.dirstate:
                     raise util.Abort(_("file %s not tracked!") % rf)
     else:
         files = []
@@ -466,7 +466,7 @@ def commit(ui, repo, *pats, **opts):
     except ValueError, inst:
         raise util.Abort(str(inst))
 
-def docopy(ui, repo, pats, opts, wlock):
+def docopy(ui, repo, pats, opts):
     # called with the repo lock held
     #
     # hgsep => pathname that uses "/" to separate directories
@@ -482,7 +482,7 @@ def docopy(ui, repo, pats, opts, wlock):
     def okaytocopy(abs, rel, exact):
         reasons = {'?': _('is not managed'),
                    'r': _('has been marked for remove')}
-        state = repo.dirstate.state(abs)
+        state = repo.dirstate[abs]
         reason = reasons.get(state)
         if reason:
             if exact:
@@ -510,7 +510,7 @@ def docopy(ui, repo, pats, opts, wlock):
                      repo.pathto(prevsrc, cwd)))
             return
         if (not opts['after'] and os.path.exists(target) or
-            opts['after'] and repo.dirstate.state(abstarget) not in '?ar'):
+            opts['after'] and repo.dirstate[abstarget] in 'mn'):
             if not opts['force']:
                 ui.warn(_('%s: not overwriting - file exists\n') %
                         reltarget)
@@ -525,16 +525,16 @@ def docopy(ui, repo, pats, opts, wlock):
             if not os.path.isdir(targetdir) and not opts.get('dry_run'):
                 os.makedirs(targetdir)
             try:
-                restore = repo.dirstate.state(abstarget) == 'r'
+                restore = repo.dirstate[abstarget] == 'r'
                 if restore and not opts.get('dry_run'):
-                    repo.undelete([abstarget], wlock)
+                    repo.undelete([abstarget])
                 try:
                     if not opts.get('dry_run'):
                         util.copyfile(src, target)
                     restore = False
                 finally:
                     if restore:
-                        repo.remove([abstarget], wlock=wlock)
+                        repo.remove([abstarget])
             except IOError, inst:
                 if inst.errno == errno.ENOENT:
                     ui.warn(_('%s: deleted in working copy\n') % relsrc)
@@ -547,15 +547,15 @@ def docopy(ui, repo, pats, opts, wlock):
             ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
         targets[abstarget] = abssrc
         if abstarget != origsrc:
-            if repo.dirstate.state(origsrc) == 'a':
+            if repo.dirstate[origsrc] == 'a':
                 if not ui.quiet:
                     ui.warn(_("%s has not been committed yet, so no copy "
                               "data will be stored for %s.\n")
                             % (repo.pathto(origsrc, cwd), reltarget))
                 if abstarget not in repo.dirstate and not opts.get('dry_run'):
-                    repo.add([abstarget], wlock)
+                    repo.add([abstarget])
             elif not opts.get('dry_run'):
-                repo.copy(origsrc, abstarget, wlock)
+                repo.copy(origsrc, abstarget)
         copied.append((abssrc, relsrc, exact))
 
     # pat: ossep
@@ -675,8 +675,11 @@ def copy(ui, repo, *pats, **opts):
     This command takes effect in the next commit. To undo a copy
     before that, see hg revert.
     """
-    wlock = repo.wlock(0)
-    errs, copied = docopy(ui, repo, pats, opts, wlock)
+    wlock = repo.wlock(False)
+    try:
+        errs, copied = docopy(ui, repo, pats, opts)
+    finally:
+        del wlock
     return errs
 
 def debugancestor(ui, index, rev1, rev2):
@@ -713,17 +716,19 @@ def debugrebuildstate(ui, repo, rev=""):
     ctx = repo.changectx(rev)
     files = ctx.manifest()
     wlock = repo.wlock()
-    repo.dirstate.rebuild(rev, files)
+    try:
+        repo.dirstate.rebuild(rev, files)
+    finally:
+        del wlock
 
 def debugcheckstate(ui, repo):
     """validate the correctness of the current dirstate"""
     parent1, parent2 = repo.dirstate.parents()
-    dc = repo.dirstate
     m1 = repo.changectx(parent1).manifest()
     m2 = repo.changectx(parent2).manifest()
     errors = 0
-    for f in dc:
-        state = repo.dirstate.state(f)
+    for f in repo.dirstate:
+        state = repo.dirstate[f]
         if state in "nr" and f not in m1:
             ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
             errors += 1
@@ -735,7 +740,7 @@ def debugcheckstate(ui, repo):
                     (f, state))
             errors += 1
     for f in m1:
-        state = repo.dirstate.state(f)
+        state = repo.dirstate[f]
         if state not in "nrm":
             ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
             errors += 1
@@ -783,12 +788,14 @@ def debugsetparents(ui, repo, rev1, rev2
     try:
         repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
     finally:
-        wlock.release()
+        del wlock
 
 def debugstate(ui, repo):
     """show the contents of the current dirstate"""
-    dc = repo.dirstate
-    for file_ in dc:
+    dc = repo.dirstate._map
+    k = dc.keys()
+    k.sort()
+    for file_ in k:
         if dc[file_][3] == -1:
             # Pad or slice to locale representation
             locale_len = len(time.strftime("%x %X", time.localtime(0)))
@@ -1581,70 +1588,75 @@ def import_(ui, repo, patch1, *patches, 
 
     d = opts["base"]
     strip = opts["strip"]
-
-    wlock = repo.wlock()
-    lock = repo.lock()
-
-    for p in patches:
-        pf = os.path.join(d, p)
-
-        if pf == '-':
-            ui.status(_("applying patch from stdin\n"))
-            tmpname, message, user, date, branch, nodeid, p1, p2 = patch.extract(ui, sys.stdin)
-        else:
-            ui.status(_("applying %s\n") % p)
-            tmpname, message, user, date, branch, nodeid, p1, p2 = patch.extract(ui, file(pf, 'rb'))
-
-        if tmpname is None:
-            raise util.Abort(_('no diffs found'))
-
-        try:
-            cmdline_message = cmdutil.logmessage(opts)
-            if cmdline_message:
-                # pickup the cmdline msg
-                message = cmdline_message
-            elif message:
-                # pickup the patch msg
-                message = message.strip()
+    wlock = lock = None
+    try:
+        wlock = repo.wlock()
+        lock = repo.lock()
+        for p in patches:
+            pf = os.path.join(d, p)
+
+            if pf == '-':
+                ui.status(_("applying patch from stdin\n"))
+                data = patch.extract(ui, sys.stdin)
             else:
-                # launch the editor
-                message = None
-            ui.debug(_('message:\n%s\n') % message)
-
-            wp = repo.workingctx().parents()
-            if opts.get('exact'):
-                if not nodeid or not p1:
-                    raise util.Abort(_('not a mercurial patch'))
-                p1 = repo.lookup(p1)
-                p2 = repo.lookup(p2 or hex(nullid))
-
-                if p1 != wp[0].node():
-                    hg.clean(repo, p1, wlock=wlock)
-                repo.dirstate.setparents(p1, p2)
-            elif p2:
-                try:
+                ui.status(_("applying %s\n") % p)
+                data = patch.extract(ui, file(pf, 'rb'))
+
+            tmpname, message, user, date, branch, nodeid, p1, p2 = data
+
+            if tmpname is None:
+                raise util.Abort(_('no diffs found'))
+
+            try:
+                cmdline_message = cmdutil.logmessage(opts)
+                if cmdline_message:
+                    # pickup the cmdline msg
+                    message = cmdline_message
+                elif message:
+                    # pickup the patch msg
+                    message = message.strip()
+                else:
+                    # launch the editor
+                    message = None
+                ui.debug(_('message:\n%s\n') % message)
+
+                wp = repo.workingctx().parents()
+                if opts.get('exact'):
+                    if not nodeid or not p1:
+                        raise util.Abort(_('not a mercurial patch'))
                     p1 = repo.lookup(p1)
-                    p2 = repo.lookup(p2)
-                    if p1 == wp[0].node():
-                        repo.dirstate.setparents(p1, p2)
-                except hg.RepoError:
-                    pass
-            if opts.get('exact') or opts.get('import_branch'):
-                repo.dirstate.setbranch(branch or 'default')
-
-            files = {}
-            try:
-                fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
-                                   files=files)
+                    p2 = repo.lookup(p2 or hex(nullid))
+
+                    if p1 != wp[0].node():
+                        hg.clean(repo, p1)
+                    repo.dirstate.setparents(p1, p2)
+                elif p2:
+                    try:
+                        p1 = repo.lookup(p1)
+                        p2 = repo.lookup(p2)
+                        if p1 == wp[0].node():
+                            repo.dirstate.setparents(p1, p2)
+                    except hg.RepoError:
+                        pass
+                if opts.get('exact') or opts.get('import_branch'):
+                    repo.dirstate.setbranch(branch or 'default')
+
+                files = {}
+                try:
+                    fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
+                                       files=files)
+                finally:
+                    files = patch.updatedir(ui, repo, files)
+                n = repo.commit(files, message, user, date)
+                if opts.get('exact'):
+                    if hex(n) != nodeid:
+                        repo.rollback()
+                        raise util.Abort(_('patch is damaged' +
+                                           ' or loses information'))
             finally:
-                files = patch.updatedir(ui, repo, files, wlock=wlock)
-            n = repo.commit(files, message, user, date, wlock=wlock, lock=lock)
-            if opts.get('exact'):
-                if hex(n) != nodeid:
-                    repo.rollback(wlock=wlock, lock=lock)
-                    raise util.Abort(_('patch is damaged or loses information'))
-        finally:
-            os.unlink(tmpname)
+                os.unlink(tmpname)
+    finally:
+        del wlock, lock
 
 def incoming(ui, repo, source="default", **opts):
     """show new changesets found in source
@@ -1759,7 +1771,7 @@ def locate(ui, repo, *pats, **opts):
                                              default='relglob'):
         if src == 'b':
             continue
-        if not node and repo.dirstate.state(abs) == '?':
+        if not node and abs not in repo.dirstate:
             continue
         if opts['fullpath']:
             ui.write(os.path.join(repo.root, abs), end)
@@ -2213,7 +2225,7 @@ def remove(ui, repo, *pats, **opts):
                 forget.append(abs)
                 continue
             reason = _('has been marked for add (use -f to force removal)')
-        elif repo.dirstate.state(abs) == '?':
+        elif abs not in repo.dirstate:
             reason = _('is not managed')
         elif opts['after'] and not exact and abs not in deleted:
             continue
@@ -2243,16 +2255,19 @@ def rename(ui, repo, *pats, **opts):
     This command takes effect in the next commit. To undo a rename
     before that, see hg revert.
     """
-    wlock = repo.wlock(0)
-    errs, copied = docopy(ui, repo, pats, opts, wlock)
-    names = []
-    for abs, rel, exact in copied:
-        if ui.verbose or not exact:
-            ui.status(_('removing %s\n') % rel)
-        names.append(abs)
-    if not opts.get('dry_run'):
-        repo.remove(names, True, wlock=wlock)
-    return errs
+    wlock = repo.wlock(False)
+    try:
+        errs, copied = docopy(ui, repo, pats, opts)
+        names = []
+        for abs, rel, exact in copied:
+            if ui.verbose or not exact:
+                ui.status(_('removing %s\n') % rel)
+            names.append(abs)
+        if not opts.get('dry_run'):
+            repo.remove(names, True)
+        return errs
+    finally:
+        del wlock
 
 def revert(ui, repo, *pats, **opts):
     """revert files or dirs to their states as of some revision
@@ -2306,8 +2321,6 @@ def revert(ui, repo, *pats, **opts):
     else:
         pmf = None
 
-    wlock = repo.wlock()
-
     # need all matching names in dirstate and manifest of target rev,
     # so have to walk both. do not print errors if files exist in one
     # but not other.
@@ -2315,109 +2328,116 @@ def revert(ui, repo, *pats, **opts):
     names = {}
     target_only = {}
 
-    # walk dirstate.
-
-    for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
-                                             badmatch=mf.has_key):
-        names[abs] = (rel, exact)
-        if src == 'b':
-            target_only[abs] = True
-
-    # walk target manifest.
-
-    def badmatch(path):
-        if path in names:
-            return True
-        path_ = path + '/'
-        for f in names:
-            if f.startswith(path_):
+    wlock = repo.wlock()
+    try:
+        # walk dirstate.
+        for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
+                                                 badmatch=mf.has_key):
+            names[abs] = (rel, exact)
+            if src == 'b':
+                target_only[abs] = True
+
+        # walk target manifest.
+
+        def badmatch(path):
+            if path in names:
                 return True
-        return False
-
-    for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
-                                             badmatch=badmatch):
-        if abs in names or src == 'b':
-            continue
-        names[abs] = (rel, exact)
-        target_only[abs] = True
-
-    changes = repo.status(match=names.has_key, wlock=wlock)[:5]
-    modified, added, removed, deleted, unknown = map(dict.fromkeys, changes)
-
-    revert = ([], _('reverting %s\n'))
-    add = ([], _('adding %s\n'))
-    remove = ([], _('removing %s\n'))
-    forget = ([], _('forgetting %s\n'))
-    undelete = ([], _('undeleting %s\n'))
-    update = {}
-
-    disptable = (
-        # dispatch table:
-        #   file state
-        #   action if in target manifest
-        #   action if not in target manifest
-        #   make backup if in target manifest
-        #   make backup if not in target manifest
-        (modified, revert, remove, True, True),
-        (added, revert, forget, True, False),
-        (removed, undelete, None, False, False),
-        (deleted, revert, remove, False, False),
-        (unknown, add, None, True, False),
-        (target_only, add, None, False, False),
-        )
-
-    entries = names.items()
-    entries.sort()
-
-    for abs, (rel, exact) in entries:
-        mfentry = mf.get(abs)
-        target = repo.wjoin(abs)
-        def handle(xlist, dobackup):
-            xlist[0].append(abs)
-            update[abs] = 1
-            if dobackup and not opts['no_backup'] and util.lexists(target):
-                bakname = "%s.orig" % rel
-                ui.note(_('saving current version of %s as %s\n') %
-                        (rel, bakname))
-                if not opts.get('dry_run'):
-                    util.copyfile(target, bakname)
-            if ui.verbose or not exact:
-                ui.status(xlist[1] % rel)
-        for table, hitlist, misslist, backuphit, backupmiss in disptable:
-            if abs not in table: continue
-            # file has changed in dirstate
-            if mfentry:
-                handle(hitlist, backuphit)
-            elif misslist is not None:
-                handle(misslist, backupmiss)
+            path_ = path + '/'
+            for f in names:
+                if f.startswith(path_):
+                    return True
+            return False
+
+        for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
+                                                 badmatch=badmatch):
+            if abs in names or src == 'b':
+                continue
+            names[abs] = (rel, exact)
+            target_only[abs] = True
+
+        changes = repo.status(match=names.has_key)[:5]
+        modified, added, removed, deleted, unknown = map(dict.fromkeys, changes)
+
+        revert = ([], _('reverting %s\n'))
+        add = ([], _('adding %s\n'))
+        remove = ([], _('removing %s\n'))
+        forget = ([], _('forgetting %s\n'))
+        undelete = ([], _('undeleting %s\n'))
+        update = {}
+
+        disptable = (
+            # dispatch table:
+            #   file state
+            #   action if in target manifest
+            #   action if not in target manifest
+            #   make backup if in target manifest
+            #   make backup if not in target manifest
+            (modified, revert, remove, True, True),
+            (added, revert, forget, True, False),
+            (removed, undelete, None, False, False),
+            (deleted, revert, remove, False, False),
+            (unknown, add, None, True, False),
+            (target_only, add, None, False, False),
+            )
+
+        entries = names.items()
+        entries.sort()
+
+        for abs, (rel, exact) in entries:
+            mfentry = mf.get(abs)
+            target = repo.wjoin(abs)
+            def handle(xlist, dobackup):
+                xlist[0].append(abs)
+                update[abs] = 1
+                if dobackup and not opts['no_backup'] and util.lexists(target):
+                    bakname = "%s.orig" % rel
+                    ui.note(_('saving current version of %s as %s\n') %
+                            (rel, bakname))
+                    if not opts.get('dry_run'):
+                        util.copyfile(target, bakname)
+                if ui.verbose or not exact:
+                    ui.status(xlist[1] % rel)
+            for table, hitlist, misslist, backuphit, backupmiss in disptable:
+                if abs not in table: continue
+                # file has changed in dirstate
+                if mfentry:
+                    handle(hitlist, backuphit)
+                elif misslist is not None:
+                    handle(misslist, backupmiss)
+                else:
+                    if exact: ui.warn(_('file not managed: %s\n') % rel)
+                break
             else:
-                if exact: ui.warn(_('file not managed: %s\n') % rel)
-            break
-        else:
-            # file has not changed in dirstate
-            if node == parent:
-                if exact: ui.warn(_('no changes needed to %s\n') % rel)
-                continue
-            if pmf is None:
-                # only need parent manifest in this unlikely case,
-                # so do not read by default
-                pmf = repo.changectx(parent).manifest()
-            if abs in pmf:
-                if mfentry:
-                    # if version of file is same in parent and target
-                    # manifests, do nothing
-                    if pmf[abs] != mfentry:
-                        handle(revert, False)
-                else:
-                    handle(remove, False)
-
-    if not opts.get('dry_run'):
-        repo.dirstate.forget(forget[0])
-        r = hg.revert(repo, node, update.has_key, wlock)
-        repo.dirstate.update(add[0], 'a')
-        repo.dirstate.update(undelete[0], 'n')
-        repo.dirstate.update(remove[0], 'r')
-        return r
+                # file has not changed in dirstate
+                if node == parent:
+                    if exact: ui.warn(_('no changes needed to %s\n') % rel)
+                    continue
+                if pmf is None:
+                    # only need parent manifest in this unlikely case,
+                    # so do not read by default
+                    pmf = repo.changectx(parent).manifest()
+                if abs in pmf:
+                    if mfentry:
+                        # if version of file is same in parent and target
+                        # manifests, do nothing
+                        if pmf[abs] != mfentry:
+                            handle(revert, False)
+                    else:
+                        handle(remove, False)
+
+        if not opts.get('dry_run'):
+            for f in forget[0]:
+                repo.dirstate.forget(f)
+            r = hg.revert(repo, node, update.has_key)
+            for f in add[0]:
+                repo.dirstate.add(f)
+            for f in undelete[0]:
+                repo.dirstate.normal(f)
+            for f in remove[0]:
+                repo.dirstate.remove(f)
+            return r
+    finally:
+        del wlock
 
 def rollback(ui, repo):
     """roll back the last transaction in this repository
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -60,6 +60,18 @@ class changectx(object):
         else:
             raise AttributeError, name
 
+    def __contains__(self, key):
+        return key in self._manifest
+
+    def __getitem__(self, key):
+        return self.filectx(key)
+
+    def __iter__(self):
+        a = self._manifest.keys()
+        a.sort()
+        for f in a:
+            return f
+
     def changeset(self): return self._changeset
     def manifest(self): return self._manifest
 
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -20,8 +20,8 @@ class dirstate(object):
     def __init__(self, opener, ui, root):
         self._opener = opener
         self._root = root
-        self._dirty = 0
-        self._dirtypl = 0
+        self._dirty = False
+        self._dirtypl = False
         self._ui = ui
 
     def __getattr__(self, name):
@@ -53,7 +53,7 @@ class dirstate(object):
                 self._incpath(f)
             return self._dirs
         elif name == '_ignore':
-            files = [self.wjoin('.hgignore')]
+            files = [self._join('.hgignore')]
             for name, path in self._ui.configitems("ui"):
                 if name == 'ignore' or name.startswith('ignore.'):
                     files.append(os.path.expanduser(path))
@@ -65,7 +65,7 @@ class dirstate(object):
         else:
             raise AttributeError, name
 
-    def wjoin(self, f):
+    def _join(self, f):
         return os.path.join(self._root, f)
 
     def getcwd(self):
@@ -89,11 +89,14 @@ class dirstate(object):
             return path.replace(os.sep, '/')
         return path
 
-    def __del__(self):
-        self.write()
-
     def __getitem__(self, key):
-        return self._map[key]
+        ''' current states:
+        n  normal
+        m  needs merging
+        r  marked for removal
+        a  marked for addition
+        ?  not tracked'''
+        return self._map.get(key, ("?",))[0]
 
     def __contains__(self, key):
         return key in self._map
@@ -110,21 +113,14 @@ class dirstate(object):
     def branch(self):
         return self._branch
 
-    def markdirty(self):
-        self._dirty = 1
-
     def setparents(self, p1, p2=nullid):
-        self.markdirty()
-        self._dirtypl = 1
+        self._dirty = self._dirtypl = True
         self._pl = p1, p2
 
     def setbranch(self, branch):
         self._branch = branch
         self._opener("branch", "w").write(branch + '\n')
 
-    def state(self, key):
-        return self._map.get(key, ("?",))[0]
-
     def _read(self):
         self._map = {}
         self._copymap = {}
@@ -166,10 +162,10 @@ class dirstate(object):
         for a in "_map _copymap _branch _pl _dirs _ignore".split():
             if a in self.__dict__:
                 delattr(self, a)
-        self._dirty = 0
+        self._dirty = False
 
     def copy(self, source, dest):
-        self.markdirty()
+        self._dirty = True
         self._copymap[dest] = source
 
     def copied(self, file):
@@ -204,41 +200,54 @@ class dirstate(object):
                 raise util.Abort(_('file named %r already in dirstate') % d)
         self._incpath(f)
 
-    def update(self, files, state, **kw):
-        ''' current states:
-        n  normal
-        m  needs merging
-        r  marked for removal
-        a  marked for addition'''
+    def normal(self, f):
+        'mark a file normal'
+        self._dirty = True
+        s = os.lstat(self._join(f))
+        self._map[f] = ('n', s.st_mode, s.st_size, s.st_mtime)
+        if self._copymap.has_key(f):
+            del self._copymap[f]
 
-        if not files: return
-        self.markdirty()
-        for f in files:
-            if self._copymap.has_key(f):
-                del self._copymap[f]
+    def normaldirty(self, f):
+        'mark a file normal, but possibly dirty'
+        self._dirty = True
+        s = os.lstat(self._join(f))
+        self._map[f] = ('n', s.st_mode, -1, -1)
+        if f in self._copymap:
+            del self._copymap[f]
+
+    def add(self, f):
+        'mark a file added'
+        self._dirty = True
+        self._incpathcheck(f)
+        self._map[f] = ('a', 0, -1, -1)
+        if f in self._copymap:
+            del self._copymap[f]
 
-            if state == "r":
-                self._map[f] = ('r', 0, 0, 0)
-                self._decpath(f)
-                continue
-            else:
-                if state == "a":
-                    self._incpathcheck(f)
-                s = os.lstat(self.wjoin(f))
-                st_size = kw.get('st_size', s.st_size)
-                st_mtime = kw.get('st_mtime', s.st_mtime)
-                self._map[f] = (state, s.st_mode, st_size, st_mtime)
+    def remove(self, f):
+        'mark a file removed'
+        self._dirty = True
+        self._map[f] = ('r', 0, 0, 0)
+        self._decpath(f)
+        if f in self._copymap:
+            del self._copymap[f]
 
-    def forget(self, files):
-        if not files: return
-        self.markdirty()
-        for f in files:
-            try:
-                del self._map[f]
-                self._decpath(f)
-            except KeyError:
-                self._ui.warn(_("not in dirstate: %s!\n") % f)
-                pass
+    def merge(self, f):
+        'mark a file merged'
+        self._dirty = True
+        s = os.lstat(self._join(f))
+        self._map[f] = ('m', s.st_mode, s.st_size, s.st_mtime)
+        if f in self._copymap:
+            del self._copymap[f]
+
+    def forget(self, f):
+        'forget a file'
+        self._dirty = True
+        try:
+            del self._map[f]
+            self._decpath(f)
+        except KeyError:
+            self._ui.warn(_("not in dirstate: %s!\n") % f)
 
     def rebuild(self, parent, files):
         self.invalidate()
@@ -248,7 +257,7 @@ class dirstate(object):
             else:
                 self._map[f] = ('n', 0666, -1, 0)
         self._pl = (parent, nullid)
-        self.markdirty()
+        self._dirty = True
 
     def write(self):
         if not self._dirty:
@@ -265,10 +274,9 @@ class dirstate(object):
         st = self._opener("dirstate", "w", atomictemp=True)
         st.write(cs.getvalue())
         st.rename()
-        self._dirty = 0
-        self._dirtypl = 0
+        self._dirty = self._dirtypl = False
 
-    def filterfiles(self, files):
+    def _filter(self, files):
         ret = {}
         unknown = []
 
@@ -339,7 +347,7 @@ class dirstate(object):
             dc = self._map.copy()
         else:
             files = util.unique(files)
-            dc = self.filterfiles(files)
+            dc = self._filter(files)
 
         def imatch(file_):
             if file_ not in dc and self._ignore(file_):
@@ -405,7 +413,7 @@ class dirstate(object):
         files.sort()
         for ff in files:
             nf = util.normpath(ff)
-            f = self.wjoin(ff)
+            f = self._join(ff)
             try:
                 st = os.lstat(f)
             except OSError, inst:
@@ -442,14 +450,13 @@ class dirstate(object):
             if not seen(k) and imatch(k):
                 yield 'm', k, None
 
-    def status(self, files=None, match=util.always, list_ignored=False,
-               list_clean=False):
+    def status(self, files, match, list_ignored, list_clean):
         lookup, modified, added, unknown, ignored = [], [], [], [], []
         removed, deleted, clean = [], [], []
 
         for src, fn, st in self.statwalk(files, match, ignored=list_ignored):
             try:
-                type_, mode, size, time = self[fn]
+                type_, mode, size, time = self._map[fn]
             except KeyError:
                 if list_ignored and self._ignore(fn):
                     ignored.append(fn)
@@ -460,7 +467,7 @@ class dirstate(object):
                 nonexistent = True
                 if not st:
                     try:
-                        st = os.lstat(self.wjoin(fn))
+                        st = os.lstat(self._join(fn))
                     except OSError, inst:
                         if inst.errno != errno.ENOENT:
                             raise
@@ -476,7 +483,7 @@ class dirstate(object):
             # check the common case first
             if type_ == 'n':
                 if not st:
-                    st = os.lstat(self.wjoin(fn))
+                    st = os.lstat(self._join(fn))
                 if (size >= 0 and (size != st.st_size
                                    or (mode ^ st.st_mode) & 0100)
                     or fn in self._copymap):
--- a/mercurial/hg.py
+++ b/mercurial/hg.py
@@ -130,103 +130,99 @@ def clone(ui, source, dest=None, pull=Fa
             if self.dir_:
                 self.rmtree(self.dir_, True)
 
-    dir_cleanup = None
-    if islocal(dest):
-        dir_cleanup = DirCleanup(dest)
+    src_lock = dest_lock = dir_cleanup = None
+    try:
+        if islocal(dest):
+            dir_cleanup = DirCleanup(dest)
 
-    abspath = origsource
-    copy = False
-    if src_repo.local() and islocal(dest):
-        abspath = os.path.abspath(origsource)
-        copy = not pull and not rev
+        abspath = origsource
+        copy = False
+        if src_repo.local() and islocal(dest):
+            abspath = os.path.abspath(origsource)
+            copy = not pull and not rev
 
-    src_lock, dest_lock = None, None
-    if copy:
-        try:
-            # we use a lock here because if we race with commit, we
-            # can end up with extra data in the cloned revlogs that's
-            # not pointed to by changesets, thus causing verify to
-            # fail
-            src_lock = src_repo.lock()
-        except lock.LockException:
-            copy = False
+        if copy:
+            try:
+                # we use a lock here because if we race with commit, we
+                # can end up with extra data in the cloned revlogs that's
+                # not pointed to by changesets, thus causing verify to
+                # fail
+                src_lock = src_repo.lock()
+            except lock.LockException:
+                copy = False
 
-    if copy:
-        def force_copy(src, dst):
-            try:
-                util.copyfiles(src, dst)
-            except OSError, inst:
-                if inst.errno != errno.ENOENT:
-                    raise
+        if copy:
+            def force_copy(src, dst):
+                try:
+                    util.copyfiles(src, dst)
+                except OSError, inst:
+                    if inst.errno != errno.ENOENT:
+                        raise
 
-        src_store = os.path.realpath(src_repo.spath)
-        if not os.path.exists(dest):
-            os.mkdir(dest)
-        dest_path = os.path.realpath(os.path.join(dest, ".hg"))
-        os.mkdir(dest_path)
-        if src_repo.spath != src_repo.path:
-            dest_store = os.path.join(dest_path, "store")
-            os.mkdir(dest_store)
-        else:
-            dest_store = dest_path
-        # copy the requires file
-        force_copy(src_repo.join("requires"),
-                   os.path.join(dest_path, "requires"))
-        # we lock here to avoid premature writing to the target
-        dest_lock = lock.lock(os.path.join(dest_store, "lock"))
+            src_store = os.path.realpath(src_repo.spath)
+            if not os.path.exists(dest):
+                os.mkdir(dest)
+            dest_path = os.path.realpath(os.path.join(dest, ".hg"))
+            os.mkdir(dest_path)
+            if src_repo.spath != src_repo.path:
+                dest_store = os.path.join(dest_path, "store")
+                os.mkdir(dest_store)
+            else:
+                dest_store = dest_path
+            # copy the requires file
+            force_copy(src_repo.join("requires"),
+                       os.path.join(dest_path, "requires"))
+            # we lock here to avoid premature writing to the target
+            dest_lock = lock.lock(os.path.join(dest_store, "lock"))
 
-        files = ("data",
-                 "00manifest.d", "00manifest.i",
-                 "00changelog.d", "00changelog.i")
-        for f in files:
-            src = os.path.join(src_store, f)
-            dst = os.path.join(dest_store, f)
-            force_copy(src, dst)
+            files = ("data",
+                     "00manifest.d", "00manifest.i",
+                     "00changelog.d", "00changelog.i")
+            for f in files:
+                src = os.path.join(src_store, f)
+                dst = os.path.join(dest_store, f)
+                force_copy(src, dst)
+
+            # we need to re-init the repo after manually copying the data
+            # into it
+            dest_repo = repository(ui, dest)
+
+        else:
+            dest_repo = repository(ui, dest, create=True)
 
-        # we need to re-init the repo after manually copying the data
-        # into it
-        dest_repo = repository(ui, dest)
-
-    else:
-        dest_repo = repository(ui, dest, create=True)
+            revs = None
+            if rev:
+                if 'lookup' not in src_repo.capabilities:
+                    raise util.Abort(_("src repository does not support revision "
+                                       "lookup and so doesn't support clone by "
+                                       "revision"))
+                revs = [src_repo.lookup(r) for r in rev]
 
-        revs = None
-        if rev:
-            if 'lookup' not in src_repo.capabilities:
-                raise util.Abort(_("src repository does not support revision "
-                                   "lookup and so doesn't support clone by "
-                                   "revision"))
-            revs = [src_repo.lookup(r) for r in rev]
+            if dest_repo.local():
+                dest_repo.clone(src_repo, heads=revs, stream=stream)
+            elif src_repo.local():
+                src_repo.push(dest_repo, revs=revs)
+            else:
+                raise util.Abort(_("clone from remote to remote not supported"))
 
         if dest_repo.local():
-            dest_repo.clone(src_repo, heads=revs, stream=stream)
-        elif src_repo.local():
-            src_repo.push(dest_repo, revs=revs)
-        else:
-            raise util.Abort(_("clone from remote to remote not supported"))
-
-    if src_lock:
-        src_lock.release()
+            fp = dest_repo.opener("hgrc", "w", text=True)
+            fp.write("[paths]\n")
+            fp.write("default = %s\n" % abspath)
+            fp.close()
 
-    if dest_repo.local():
-        fp = dest_repo.opener("hgrc", "w", text=True)
-        fp.write("[paths]\n")
-        fp.write("default = %s\n" % abspath)
-        fp.close()
-
-        if dest_lock:
-            dest_lock.release()
+            if update:
+                try:
+                    checkout = dest_repo.lookup("default")
+                except:
+                    checkout = dest_repo.changelog.tip()
+                _update(dest_repo, checkout)
+        if dir_cleanup:
+            dir_cleanup.close()
 
-        if update:
-            try:
-                checkout = dest_repo.lookup("default")
-            except:
-                checkout = dest_repo.changelog.tip()
-            _update(dest_repo, checkout)
-    if dir_cleanup:
-        dir_cleanup.close()
-
-    return src_repo, dest_repo
+        return src_repo, dest_repo
+    finally:
+        del src_lock, dest_lock, dir_cleanup
 
 def _showstats(repo, stats):
     stats = ((stats[0], _("updated")),
@@ -241,7 +237,7 @@ def _update(repo, node): return update(r
 def update(repo, node):
     """update the working directory to node, merging linear changes"""
     pl = repo.parents()
-    stats = _merge.update(repo, node, False, False, None, None)
+    stats = _merge.update(repo, node, False, False, None)
     _showstats(repo, stats)
     if stats[3]:
         repo.ui.status(_("There are unresolved merges with"
@@ -255,15 +251,15 @@ def update(repo, node):
                        % (pl[0].rev(), repo.changectx(node).rev()))
     return stats[3]
 
-def clean(repo, node, wlock=None, show_stats=True):
+def clean(repo, node, show_stats=True):
     """forcibly switch the working directory to node, clobbering changes"""
-    stats = _merge.update(repo, node, False, True, None, wlock)
+    stats = _merge.update(repo, node, False, True, None)
     if show_stats: _showstats(repo, stats)
     return stats[3]
 
-def merge(repo, node, force=None, remind=True, wlock=None):
+def merge(repo, node, force=None, remind=True):
     """branch merge with node, resolving changes"""
-    stats = _merge.update(repo, node, True, force, False, wlock)
+    stats = _merge.update(repo, node, True, force, False)
     _showstats(repo, stats)
     if stats[3]:
         pl = repo.parents()
@@ -276,9 +272,9 @@ def merge(repo, node, force=None, remind
         repo.ui.status(_("(branch merge, don't forget to commit)\n"))
     return stats[3]
 
-def revert(repo, node, choose, wlock):
+def revert(repo, node, choose):
     """revert changes to revision in node without updating dirstate"""
-    return _merge.update(repo, node, False, True, choose, wlock)[3]
+    return _merge.update(repo, node, False, True, choose)[3]
 
 def verify(repo):
     """verify the consistency of a repository"""
--- a/mercurial/hgweb/hgweb_mod.py
+++ b/mercurial/hgweb/hgweb_mod.py
@@ -1168,7 +1168,7 @@ class hgweb(object):
                     req.write('%d\n' % ret)
                     req.write(val)
                 finally:
-                    lock.release()
+                    del lock
             except (OSError, IOError), inst:
                 req.write('0\n')
                 filename = getattr(inst, 'filename', '')
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -8,7 +8,7 @@
 from node import *
 from i18n import _
 import repo, changegroup
-import changelog, dirstate, filelog, manifest, context
+import changelog, dirstate, filelog, manifest, context, weakref
 import re, lock, transaction, tempfile, stat, mdiff, errno, ui
 import os, revlog, time, util, extensions, hook
 
@@ -16,8 +16,6 @@ class localrepository(repo.repository):
     capabilities = ('lookup', 'changegroupsubset')
     supported = ('revlogv1', 'store')
 
-    def __del__(self):
-        self.transhandle = None
     def __init__(self, parentui, path=None, create=0):
         repo.repository.__init__(self)
         self.path = path
@@ -84,7 +82,7 @@ class localrepository(repo.repository):
         self.branchcache = None
         self.nodetagscache = None
         self.filterpats = {}
-        self.transhandle = None
+        self._transref = self._lockref = self._wlockref = None
 
     def __getattr__(self, name):
         if name == 'changelog':
@@ -125,7 +123,7 @@ class localrepository(repo.repository):
             fp.write('%s %s\n' % (hex(node), munge and munge(name) or name))
             fp.close()
             self.hook('tag', node=hex(node), tag=name, local=local)
-            
+
         prevtags = ''
         if local:
             try:
@@ -156,7 +154,7 @@ class localrepository(repo.repository):
         # committed tags are stored in UTF-8
         writetag(fp, name, util.fromlocal, prevtags)
 
-        if use_dirstate and self.dirstate.state('.hgtags') == '?':
+        if use_dirstate and '.hgtags' not in self.dirstate:
             self.add(['.hgtags'])
 
         tagnode = self.commit(['.hgtags'], message, user, date, p1=parent,
@@ -396,6 +394,11 @@ class localrepository(repo.repository):
         n = self.changelog._partialmatch(key)
         if n:
             return n
+        try:
+            if len(key) == 20:
+                key = hex(key)
+        except:
+            pass
         raise repo.RepoError(_("unknown revision '%s'") % key)
 
     def dev(self):
@@ -495,9 +498,8 @@ class localrepository(repo.repository):
         return self._filter("decode", filename, data)
 
     def transaction(self):
-        tr = self.transhandle
-        if tr != None and tr.running():
-            return tr.nest()
+        if self._transref and self._transref():
+            return self._transref().nest()
 
         # save dirstate for rollback
         try:
@@ -511,33 +513,38 @@ class localrepository(repo.repository):
         tr = transaction.transaction(self.ui.warn, self.sopener,
                                        self.sjoin("journal"),
                                        aftertrans(renames))
-        self.transhandle = tr
+        self._transref = weakref.ref(tr)
         return tr
 
     def recover(self):
         l = self.lock()
-        if os.path.exists(self.sjoin("journal")):
-            self.ui.status(_("rolling back interrupted transaction\n"))
-            transaction.rollback(self.sopener, self.sjoin("journal"))
-            self.invalidate()
-            return True
-        else:
-            self.ui.warn(_("no interrupted transaction available\n"))
-            return False
+        try:
+            if os.path.exists(self.sjoin("journal")):
+                self.ui.status(_("rolling back interrupted transaction\n"))
+                transaction.rollback(self.sopener, self.sjoin("journal"))
+                self.invalidate()
+                return True
+            else:
+                self.ui.warn(_("no interrupted transaction available\n"))
+                return False
+        finally:
+            del l
 
-    def rollback(self, wlock=None, lock=None):
-        if not wlock:
+    def rollback(self):
+        wlock = lock = None
+        try:
             wlock = self.wlock()
-        if not lock:
             lock = self.lock()
-        if os.path.exists(self.sjoin("undo")):
-            self.ui.status(_("rolling back last transaction\n"))
-            transaction.rollback(self.sopener, self.sjoin("undo"))
-            util.rename(self.join("undo.dirstate"), self.join("dirstate"))
-            self.invalidate()
-            self.dirstate.invalidate()
-        else:
-            self.ui.warn(_("no rollback information available\n"))
+            if os.path.exists(self.sjoin("undo")):
+                self.ui.status(_("rolling back last transaction\n"))
+                transaction.rollback(self.sopener, self.sjoin("undo"))
+                util.rename(self.join("undo.dirstate"), self.join("dirstate"))
+                self.invalidate()
+                self.dirstate.invalidate()
+            else:
+                self.ui.warn(_("no rollback information available\n"))
+        finally:
+            del wlock, lock
 
     def invalidate(self):
         for a in "changelog manifest".split():
@@ -546,8 +553,7 @@ class localrepository(repo.repository):
         self.tagscache = None
         self.nodetagscache = None
 
-    def do_lock(self, lockname, wait, releasefn=None, acquirefn=None,
-                desc=None):
+    def _lock(self, lockname, wait, releasefn, acquirefn, desc):
         try:
             l = lock.lock(lockname, 0, releasefn, desc=desc)
         except lock.LockHeld, inst:
@@ -562,15 +568,24 @@ class localrepository(repo.repository):
             acquirefn()
         return l
 
-    def lock(self, wait=1):
-        return self.do_lock(self.sjoin("lock"), wait,
-                            acquirefn=self.invalidate,
-                            desc=_('repository %s') % self.origroot)
+    def lock(self, wait=True):
+        if self._lockref and self._lockref():
+            return self._lockref()
+
+        l = self._lock(self.sjoin("lock"), wait, None, self.invalidate,
+                       _('repository %s') % self.origroot)
+        self._lockref = weakref.ref(l)
+        return l
 
-    def wlock(self, wait=1):
-        return self.do_lock(self.join("wlock"), wait, self.dirstate.write,
-                            self.dirstate.invalidate,
-                            desc=_('working directory of %s') % self.origroot)
+    def wlock(self, wait=True):
+        if self._wlockref and self._wlockref():
+            return self._wlockref()
+
+        l = self._lock(self.join("wlock"), wait, self.dirstate.write,
+                       self.dirstate.invalidate, _('working directory of %s') %
+                       self.origroot)
+        self._wlockref = weakref.ref(l)
+        return l
 
     def filecommit(self, fn, manifest1, manifest2, linkrev, transaction, changelist):
         """
@@ -632,171 +647,176 @@ class localrepository(repo.repository):
         changelist.append(fn)
         return fl.add(t, meta, transaction, linkrev, fp1, fp2)
 
-    def rawcommit(self, files, text, user, date, p1=None, p2=None, wlock=None, extra={}):
+    def rawcommit(self, files, text, user, date, p1=None, p2=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, extra=extra)
+                           p1=p1, p2=p2, extra=extra)
 
     def commit(self, files=None, text="", user=None, date=None,
-               match=util.always, force=False, lock=None, wlock=None,
-               force_editor=False, p1=None, p2=None, extra={}):
-
-        commit = []
-        remove = []
-        changed = []
-        use_dirstate = (p1 is None) # not rawcommit
-        extra = extra.copy()
+               match=util.always, force=False, force_editor=False,
+               p1=None, p2=None, extra={}):
+        wlock = lock = tr = None
+        try:
+            commit = []
+            remove = []
+            changed = []
+            use_dirstate = (p1 is None) # not rawcommit
+            extra = extra.copy()
 
-        if use_dirstate:
-            if files:
-                for f in files:
-                    s = self.dirstate.state(f)
-                    if s in 'nmai':
-                        commit.append(f)
-                    elif s == 'r':
-                        remove.append(f)
-                    else:
-                        self.ui.warn(_("%s not tracked!\n") % f)
+            if use_dirstate:
+                if files:
+                    for f in files:
+                        s = self.dirstate[f]
+                        if s in 'nma':
+                            commit.append(f)
+                        elif s == 'r':
+                            remove.append(f)
+                        else:
+                            self.ui.warn(_("%s not tracked!\n") % f)
+                else:
+                    changes = self.status(match=match)[:5]
+                    modified, added, removed, deleted, unknown = changes
+                    commit = modified + added
+                    remove = removed
             else:
-                changes = self.status(match=match)[:5]
-                modified, added, removed, deleted, unknown = changes
-                commit = modified + added
-                remove = removed
-        else:
-            commit = files
+                commit = files
 
-        if use_dirstate:
-            p1, p2 = self.dirstate.parents()
-            update_dirstate = True
-        else:
-            p1, p2 = p1, p2 or nullid
-            update_dirstate = (self.dirstate.parents()[0] == p1)
+            if use_dirstate:
+                p1, p2 = self.dirstate.parents()
+                update_dirstate = True
+            else:
+                p1, p2 = p1, p2 or nullid
+                update_dirstate = (self.dirstate.parents()[0] == p1)
 
-        c1 = self.changelog.read(p1)
-        c2 = self.changelog.read(p2)
-        m1 = self.manifest.read(c1[0]).copy()
-        m2 = self.manifest.read(c2[0])
+            c1 = self.changelog.read(p1)
+            c2 = self.changelog.read(p2)
+            m1 = self.manifest.read(c1[0]).copy()
+            m2 = self.manifest.read(c2[0])
 
-        if use_dirstate:
-            branchname = self.workingctx().branch()
-            try:
-                branchname = branchname.decode('UTF-8').encode('UTF-8')
-            except UnicodeDecodeError:
-                raise util.Abort(_('branch name not in UTF-8!'))
-        else:
-            branchname = ""
+            if use_dirstate:
+                branchname = self.workingctx().branch()
+                try:
+                    branchname = branchname.decode('UTF-8').encode('UTF-8')
+                except UnicodeDecodeError:
+                    raise util.Abort(_('branch name not in UTF-8!'))
+            else:
+                branchname = ""
 
-        if use_dirstate:
-            oldname = c1[5].get("branch") # stored in UTF-8
-            if (not commit and not remove and not force and p2 == nullid
-                and branchname == oldname):
-                self.ui.status(_("nothing changed\n"))
-                return None
+            if use_dirstate:
+                oldname = c1[5].get("branch") # stored in UTF-8
+                if (not commit and not remove and not force and p2 == nullid
+                    and branchname == oldname):
+                    self.ui.status(_("nothing changed\n"))
+                    return None
 
-        xp1 = hex(p1)
-        if p2 == nullid: xp2 = ''
-        else: xp2 = hex(p2)
-
-        self.hook("precommit", throw=True, parent1=xp1, parent2=xp2)
+            xp1 = hex(p1)
+            if p2 == nullid: xp2 = ''
+            else: xp2 = hex(p2)
 
-        if not wlock:
+            self.hook("precommit", throw=True, parent1=xp1, parent2=xp2)
+
             wlock = self.wlock()
-        if not lock:
             lock = self.lock()
-        tr = self.transaction()
+            tr = self.transaction()
 
-        # check in files
-        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)
-                new_exec = is_exec(f)
-                new_link = is_link(f)
-                if not changed or changed[-1] != f:
-                    # mention the file in the changelog if some flag changed,
-                    # even if there was no content change.
-                    old_exec = m1.execf(f)
-                    old_link = m1.linkf(f)
-                    if old_exec != new_exec or old_link != new_link:
-                        changed.append(f)
-                m1.set(f, new_exec, new_link)
-            except (OSError, IOError):
-                if use_dirstate:
-                    self.ui.warn(_("trouble committing %s!\n") % f)
-                    raise
-                else:
-                    remove.append(f)
+            # check in files
+            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)
+                    new_exec = is_exec(f)
+                    new_link = is_link(f)
+                    if not changed or changed[-1] != f:
+                        # mention the file in the changelog if some
+                        # flag changed, even if there was no content
+                        # change.
+                        old_exec = m1.execf(f)
+                        old_link = m1.linkf(f)
+                        if old_exec != new_exec or old_link != new_link:
+                            changed.append(f)
+                    m1.set(f, new_exec, new_link)
+                except (OSError, IOError):
+                    if use_dirstate:
+                        self.ui.warn(_("trouble committing %s!\n") % f)
+                        raise
+                    else:
+                        remove.append(f)
 
-        # update manifest
-        m1.update(new)
-        remove.sort()
-        removed = []
+            # update manifest
+            m1.update(new)
+            remove.sort()
+            removed = []
 
-        for f in remove:
-            if f in m1:
-                del m1[f]
-                removed.append(f)
-            elif f in m2:
-                removed.append(f)
-        mn = self.manifest.add(m1, tr, linkrev, c1[0], c2[0], (new, removed))
+            for f in remove:
+                if f in m1:
+                    del m1[f]
+                    removed.append(f)
+                elif f in m2:
+                    removed.append(f)
+            mn = self.manifest.add(m1, tr, linkrev, c1[0], c2[0],
+                                   (new, removed))
 
-        # add changeset
-        new = new.keys()
-        new.sort()
+            # add changeset
+            new = new.keys()
+            new.sort()
 
-        user = user or self.ui.username()
-        if not text or force_editor:
-            edittext = []
-            if text:
-                edittext.append(text)
-            edittext.append("")
-            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 removed])
-            if not changed and not remove:
-                edittext.append("HG: no files changed")
-            edittext.append("")
-            # run editor in the repository root
-            olddir = os.getcwd()
-            os.chdir(self.root)
-            text = self.ui.edit("\n".join(edittext), user)
-            os.chdir(olddir)
+            user = user or self.ui.username()
+            if not text or force_editor:
+                edittext = []
+                if text:
+                    edittext.append(text)
+                edittext.append("")
+                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 removed])
+                if not changed and not remove:
+                    edittext.append("HG: no files changed")
+                edittext.append("")
+                # run editor in the repository root
+                olddir = os.getcwd()
+                os.chdir(self.root)
+                text = self.ui.edit("\n".join(edittext), user)
+                os.chdir(olddir)
 
-        lines = [line.rstrip() for line in text.rstrip().splitlines()]
-        while lines and not lines[0]:
-            del lines[0]
-        if not lines:
-            return None
-        text = '\n'.join(lines)
-        if branchname:
-            extra["branch"] = branchname
-        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()
+            lines = [line.rstrip() for line in text.rstrip().splitlines()]
+            while lines and not lines[0]:
+                del lines[0]
+            if not lines:
+                return None
+            text = '\n'.join(lines)
+            if branchname:
+                extra["branch"] = branchname
+            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 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(removed)
+            if use_dirstate or update_dirstate:
+                self.dirstate.setparents(n)
+                if use_dirstate:
+                    for f in new:
+                        self.dirstate.normal(f)
+                    for f in removed:
+                        self.dirstate.forget(f)
 
-        self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2)
-        return n
+            self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2)
+            return n
+        finally:
+            del lock, wlock, tr
 
     def walk(self, node=None, files=[], match=util.always, badmatch=None):
         '''
@@ -841,7 +861,7 @@ class localrepository(repo.repository):
                 yield src, fn
 
     def status(self, node1=None, node2=None, files=[], match=util.always,
-                wlock=None, list_ignored=False, list_clean=False):
+               list_ignored=False, list_clean=False):
         """return status of files between two nodes or node and working directory
 
         If node1 is None, use the first dirstate parent instead.
@@ -873,8 +893,6 @@ class localrepository(repo.repository):
             # all the revisions in parent->child order.
             mf1 = mfmatches(node1)
 
-        mywlock = False
-
         # are we comparing the working directory?
         if not node2:
             (lookup, modified, added, removed, deleted, unknown,
@@ -884,24 +902,30 @@ class localrepository(repo.repository):
             # are we comparing working dir against its parent?
             if compareworking:
                 if lookup:
+                    fixup = []
                     # do a full compare of any files that might have changed
-                    mnode = self.changelog.read(self.dirstate.parents()[0])[0]
-                    getnode = lambda fn: (self.manifest.find(mnode, fn)[0] or
-                                          nullid)
+                    ctx = self.changectx()
                     for f in lookup:
-                        if fcmp(f, getnode):
+                        if f not in ctx or ctx[f].cmp(self.wread(f)):
                             modified.append(f)
                         else:
+                            fixup.append(f)
                             if list_clean:
                                 clean.append(f)
-                            if not wlock and not mywlock:
-                                mywlock = True
-                                try:
-                                    wlock = self.wlock(wait=0)
-                                except lock.LockException:
-                                    pass
+
+                    # update dirstate for files that are actually clean
+                    if fixup:
+                        wlock = None
+                        try:
+                            try:
+                                wlock = self.wlock(False)
+                            except lock.LockException:
+                                pass
                             if wlock:
-                                self.dirstate.update([f], "n")
+                                for f in fixup:
+                                    self.dirstate.normal(f)
+                        finally:
+                            del wlock
             else:
                 # we are comparing working dir against non-parent
                 # generate a pseudo-manifest for the working dir
@@ -916,8 +940,6 @@ class localrepository(repo.repository):
                     if f in mf2:
                         del mf2[f]
 
-            if mywlock and wlock:
-                wlock.release()
         else:
             # we are comparing two revisions
             mf2 = mfmatches(node2)
@@ -950,85 +972,98 @@ class localrepository(repo.repository):
             l.sort()
         return (modified, added, removed, deleted, unknown, ignored, clean)
 
-    def add(self, list, wlock=None):
-        if not wlock:
-            wlock = self.wlock()
-        for f in list:
-            p = self.wjoin(f)
-            try:
-                st = os.lstat(p)
-            except:
-                self.ui.warn(_("%s does not exist!\n") % f)
-                continue
-            if st.st_size > 10000000:
-                self.ui.warn(_("%s: files over 10MB may cause memory and"
-                               " performance problems\n"
-                               "(use 'hg revert %s' to unadd the file)\n")
-                               % (f, f))
-            if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
-                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:
-                self.dirstate.update([f], "a")
-
-    def forget(self, list, wlock=None):
-        if not wlock:
-            wlock = self.wlock()
-        for f in list:
-            if self.dirstate.state(f) not in 'ai':
-                self.ui.warn(_("%s not added!\n") % f)
-            else:
-                self.dirstate.forget([f])
-
-    def remove(self, list, unlink=False, wlock=None):
-        if unlink:
+    def add(self, list):
+        wlock = self.wlock()
+        try:
             for f in list:
+                p = self.wjoin(f)
                 try:
-                    util.unlink(self.wjoin(f))
-                except OSError, inst:
-                    if inst.errno != errno.ENOENT:
-                        raise
-        if not wlock:
-            wlock = self.wlock()
-        for f in list:
-            if unlink and os.path.exists(self.wjoin(f)):
-                self.ui.warn(_("%s still exists!\n") % f)
-            elif self.dirstate.state(f) == 'a':
-                self.dirstate.forget([f])
-            elif f not in self.dirstate:
-                self.ui.warn(_("%s not tracked!\n") % f)
-            else:
-                self.dirstate.update([f], "r")
+                    st = os.lstat(p)
+                except:
+                    self.ui.warn(_("%s does not exist!\n") % f)
+                    continue
+                if st.st_size > 10000000:
+                    self.ui.warn(_("%s: files over 10MB may cause memory and"
+                                   " performance problems\n"
+                                   "(use 'hg revert %s' to unadd the file)\n")
+                                   % (f, f))
+                if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
+                    self.ui.warn(_("%s not added: only files and symlinks "
+                                   "supported currently\n") % f)
+                elif self.dirstate[f] in 'an':
+                    self.ui.warn(_("%s already tracked!\n") % f)
+                else:
+                    self.dirstate.add(f)
+        finally:
+            del wlock
 
-    def undelete(self, list, wlock=None):
-        p = self.dirstate.parents()[0]
-        mn = self.changelog.read(p)[0]
-        m = self.manifest.read(mn)
-        if not wlock:
+    def forget(self, list):
+        wlock = self.wlock()
+        try:
+            for f in list:
+                if self.dirstate[f] != 'a':
+                    self.ui.warn(_("%s not added!\n") % f)
+                else:
+                    self.dirstate.forget(f)
+        finally:
+            del wlock
+
+    def remove(self, list, unlink=False):
+        wlock = None
+        try:
+            if unlink:
+                for f in list:
+                    try:
+                        util.unlink(self.wjoin(f))
+                    except OSError, inst:
+                        if inst.errno != errno.ENOENT:
+                            raise
             wlock = self.wlock()
-        for f in list:
-            if self.dirstate.state(f) not in  "r":
-                self.ui.warn("%s not removed!\n" % f)
-            else:
-                t = self.file(f).read(m[f])
-                self.wwrite(f, t, m.flags(f))
-                self.dirstate.update([f], "n")
+            for f in list:
+                if unlink and os.path.exists(self.wjoin(f)):
+                    self.ui.warn(_("%s still exists!\n") % f)
+                elif self.dirstate[f] == 'a':
+                    self.dirstate.forget(f)
+                elif f not in self.dirstate:
+                    self.ui.warn(_("%s not tracked!\n") % f)
+                else:
+                    self.dirstate.remove(f)
+        finally:
+            del wlock
 
-    def copy(self, source, dest, wlock=None):
-        p = self.wjoin(dest)
-        if not (os.path.exists(p) or os.path.islink(p)):
-            self.ui.warn(_("%s does not exist!\n") % dest)
-        elif not (os.path.isfile(p) or os.path.islink(p)):
-            self.ui.warn(_("copy failed: %s is not a file or a "
-                           "symbolic link\n") % dest)
-        else:
-            if not wlock:
+    def undelete(self, list):
+        wlock = None
+        try:
+            p = self.dirstate.parents()[0]
+            mn = self.changelog.read(p)[0]
+            m = self.manifest.read(mn)
+            wlock = self.wlock()
+            for f in list:
+                if self.dirstate[f] != 'r':
+                    self.ui.warn("%s not removed!\n" % f)
+                else:
+                    t = self.file(f).read(m[f])
+                    self.wwrite(f, t, m.flags(f))
+                    self.dirstate.normal(f)
+        finally:
+            del wlock
+
+    def copy(self, source, dest):
+        wlock = None
+        try:
+            p = self.wjoin(dest)
+            if not (os.path.exists(p) or os.path.islink(p)):
+                self.ui.warn(_("%s does not exist!\n") % dest)
+            elif not (os.path.isfile(p) or os.path.islink(p)):
+                self.ui.warn(_("copy failed: %s is not a file or a "
+                               "symbolic link\n") % dest)
+            else:
                 wlock = self.wlock()
-            if self.dirstate.state(dest) == '?':
-                self.dirstate.update([dest], "a")
-            self.dirstate.copy(source, dest)
+                if dest not in self.dirstate:
+                    self.dirstate.add(dest)
+                self.dirstate.copy(source, dest)
+        finally:
+            del wlock
 
     def heads(self, start=None):
         heads = self.changelog.heads(start)
@@ -1305,12 +1340,8 @@ class localrepository(repo.repository):
         else:
             return subset
 
-    def pull(self, remote, heads=None, force=False, lock=None):
-        mylock = False
-        if not lock:
-            lock = self.lock()
-            mylock = True
-
+    def pull(self, remote, heads=None, force=False):
+        lock = self.lock()
         try:
             fetch = self.findincoming(remote, force=force)
             if fetch == [nullid]:
@@ -1328,8 +1359,7 @@ class localrepository(repo.repository):
                 cg = remote.changegroupsubset(fetch, heads, 'pull')
             return self.addchangegroup(cg, 'pull', remote.url())
         finally:
-            if mylock:
-                lock.release()
+            del lock
 
     def push(self, remote, force=False, revs=None):
         # there are two ways to push to remote repo:
@@ -1402,12 +1432,14 @@ class localrepository(repo.repository):
 
     def push_addchangegroup(self, remote, force, revs):
         lock = remote.lock()
-
-        ret = self.prepush(remote, force, revs)
-        if ret[0] is not None:
-            cg, remote_heads = ret
-            return remote.addchangegroup(cg, 'push', self.url())
-        return ret[1]
+        try:
+            ret = self.prepush(remote, force, revs)
+            if ret[0] is not None:
+                cg, remote_heads = ret
+                return remote.addchangegroup(cg, 'push', self.url())
+            return ret[1]
+        finally:
+            del lock
 
     def push_unbundle(self, remote, force, revs):
         # local repo finds heads on server, finds out what revs it
@@ -1791,65 +1823,67 @@ class localrepository(repo.repository):
 
         changesets = files = revisions = 0
 
-        tr = self.transaction()
-
         # write changelog data to temp files so concurrent readers will not see
         # inconsistent view
         cl = self.changelog
         cl.delayupdate()
         oldheads = len(cl.heads())
 
-        # pull off the changeset group
-        self.ui.status(_("adding changesets\n"))
-        cor = cl.count() - 1
-        chunkiter = changegroup.chunkiter(source)
-        if cl.addgroup(chunkiter, csmap, tr, 1) is None:
-            raise util.Abort(_("received changelog group is empty"))
-        cnr = cl.count() - 1
-        changesets = cnr - cor
+        tr = self.transaction()
+        try:
+            # pull off the changeset group
+            self.ui.status(_("adding changesets\n"))
+            cor = cl.count() - 1
+            chunkiter = changegroup.chunkiter(source)
+            if cl.addgroup(chunkiter, csmap, tr, 1) is None:
+                raise util.Abort(_("received changelog group is empty"))
+            cnr = cl.count() - 1
+            changesets = cnr - cor
 
-        # pull off the manifest group
-        self.ui.status(_("adding manifests\n"))
-        chunkiter = changegroup.chunkiter(source)
-        # no need to check for empty manifest group here:
-        # if the result of the merge of 1 and 2 is the same in 3 and 4,
-        # no new manifest will be created and the manifest group will
-        # be empty during the pull
-        self.manifest.addgroup(chunkiter, revmap, tr)
+            # pull off the manifest group
+            self.ui.status(_("adding manifests\n"))
+            chunkiter = changegroup.chunkiter(source)
+            # no need to check for empty manifest group here:
+            # if the result of the merge of 1 and 2 is the same in 3 and 4,
+            # no new manifest will be created and the manifest group will
+            # be empty during the pull
+            self.manifest.addgroup(chunkiter, revmap, tr)
 
-        # process the files
-        self.ui.status(_("adding file changes\n"))
-        while 1:
-            f = changegroup.getchunk(source)
-            if not f:
-                break
-            self.ui.debug(_("adding %s revisions\n") % f)
-            fl = self.file(f)
-            o = fl.count()
-            chunkiter = changegroup.chunkiter(source)
-            if fl.addgroup(chunkiter, revmap, tr) is None:
-                raise util.Abort(_("received file revlog group is empty"))
-            revisions += fl.count() - o
-            files += 1
+            # process the files
+            self.ui.status(_("adding file changes\n"))
+            while 1:
+                f = changegroup.getchunk(source)
+                if not f:
+                    break
+                self.ui.debug(_("adding %s revisions\n") % f)
+                fl = self.file(f)
+                o = fl.count()
+                chunkiter = changegroup.chunkiter(source)
+                if fl.addgroup(chunkiter, revmap, tr) is None:
+                    raise util.Abort(_("received file revlog group is empty"))
+                revisions += fl.count() - o
+                files += 1
+
+            # make changelog see real files again
+            cl.finalize(tr)
 
-        # make changelog see real files again
-        cl.finalize(tr)
+            newheads = len(self.changelog.heads())
+            heads = ""
+            if oldheads and newheads != oldheads:
+                heads = _(" (%+d heads)") % (newheads - oldheads)
 
-        newheads = len(self.changelog.heads())
-        heads = ""
-        if oldheads and newheads != oldheads:
-            heads = _(" (%+d heads)") % (newheads - oldheads)
+            self.ui.status(_("added %d changesets"
+                             " with %d changes to %d files%s\n")
+                             % (changesets, revisions, files, heads))
 
-        self.ui.status(_("added %d changesets"
-                         " with %d changes to %d files%s\n")
-                         % (changesets, revisions, files, heads))
+            if changesets > 0:
+                self.hook('pretxnchangegroup', throw=True,
+                          node=hex(self.changelog.node(cor+1)), source=srctype,
+                          url=url)
 
-        if changesets > 0:
-            self.hook('pretxnchangegroup', throw=True,
-                      node=hex(self.changelog.node(cor+1)), source=srctype,
-                      url=url)
-
-        tr.close()
+            tr.close()
+        finally:
+            del tr
 
         if changesets > 0:
             self.hook("changegroup", node=hex(self.changelog.node(cor+1)),
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -447,25 +447,25 @@ def recordupdates(repo, action, branchme
         f, m = a[:2]
         if m == "r": # remove
             if branchmerge:
-                repo.dirstate.update([f], 'r')
+                repo.dirstate.remove(f)
             else:
-                repo.dirstate.forget([f])
+                repo.dirstate.forget(f)
         elif m == "f": # forget
-            repo.dirstate.forget([f])
+            repo.dirstate.forget(f)
         elif m == "g": # get
             if branchmerge:
-                repo.dirstate.update([f], 'n', st_mtime=-1)
+                repo.dirstate.normaldirty(f)
             else:
-                repo.dirstate.update([f], 'n')
+                repo.dirstate.normal(f)
         elif m == "m": # merge
             f2, fd, flag, move = a[2:]
             if branchmerge:
                 # We've done a branch merge, mark this file as merged
                 # so that we properly record the merger later
-                repo.dirstate.update([fd], 'm')
+                repo.dirstate.merge(fd)
                 if f != f2: # copy/rename
                     if move:
-                        repo.dirstate.update([f], 'r')
+                        repo.dirstate.remove(f)
                     if f != fd:
                         repo.dirstate.copy(f, fd)
                     else:
@@ -476,95 +476,94 @@ def recordupdates(repo, action, branchme
                 # of that file some time in the past. Thus our
                 # merge will appear as a normal local file
                 # modification.
-                repo.dirstate.update([fd], 'n', st_size=-1, st_mtime=-1)
+                repo.dirstate.normaldirty(fd)
                 if move:
-                    repo.dirstate.forget([f])
+                    repo.dirstate.forget(f)
         elif m == "d": # directory rename
             f2, fd, flag = a[2:]
             if not f2 and f not in repo.dirstate:
                 # untracked file moved
                 continue
             if branchmerge:
-                repo.dirstate.update([fd], 'a')
+                repo.dirstate.add(fd)
                 if f:
-                    repo.dirstate.update([f], 'r')
+                    repo.dirstate.remove(f)
                     repo.dirstate.copy(f, fd)
                 if f2:
                     repo.dirstate.copy(f2, fd)
             else:
-                repo.dirstate.update([fd], 'n')
+                repo.dirstate.normal(fd)
                 if f:
-                    repo.dirstate.forget([f])
+                    repo.dirstate.forget(f)
 
-def update(repo, node, branchmerge, force, partial, wlock):
+def update(repo, node, branchmerge, force, partial):
     """
     Perform a merge between the working directory and the given node
 
     branchmerge = whether to merge between branches
     force = whether to force branch merging or file overwriting
     partial = a function to filter file lists (dirstate not updated)
-    wlock = working dir lock, if already held
     """
 
-    if not wlock:
-        wlock = repo.wlock()
-
-    wc = repo.workingctx()
-    if node is None:
-        # tip of current branch
-        try:
-            node = repo.branchtags()[wc.branch()]
-        except KeyError:
-            raise util.Abort(_("branch %s not found") % wc.branch())
-    overwrite = force and not branchmerge
-    forcemerge = force and branchmerge
-    pl = wc.parents()
-    p1, p2 = pl[0], repo.changectx(node)
-    pa = p1.ancestor(p2)
-    fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
-    fastforward = False
+    wlock = repo.wlock()
+    try:
+        wc = repo.workingctx()
+        if node is None:
+            # tip of current branch
+            try:
+                node = repo.branchtags()[wc.branch()]
+            except KeyError:
+                raise util.Abort(_("branch %s not found") % wc.branch())
+        overwrite = force and not branchmerge
+        forcemerge = force and branchmerge
+        pl = wc.parents()
+        p1, p2 = pl[0], repo.changectx(node)
+        pa = p1.ancestor(p2)
+        fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
+        fastforward = False
 
-    ### check phase
-    if not overwrite and len(pl) > 1:
-        raise util.Abort(_("outstanding uncommitted merges"))
-    if pa == p1 or pa == p2: # is there a linear path from p1 to p2?
-        if branchmerge:
-            if p1.branch() != p2.branch() and pa != p2:
-                fastforward = True
-            else:
-                raise util.Abort(_("there is nothing to merge, just use "
-                                   "'hg update' or look at 'hg heads'"))
-    elif not (overwrite or branchmerge):
-        raise util.Abort(_("update spans branches, use 'hg merge' "
-                           "or 'hg update -C' to lose changes"))
-    if branchmerge and not forcemerge:
-        if wc.files():
-            raise util.Abort(_("outstanding uncommitted changes"))
+        ### check phase
+        if not overwrite and len(pl) > 1:
+            raise util.Abort(_("outstanding uncommitted merges"))
+        if pa == p1 or pa == p2: # is there a linear path from p1 to p2?
+            if branchmerge:
+                if p1.branch() != p2.branch() and pa != p2:
+                    fastforward = True
+                else:
+                    raise util.Abort(_("there is nothing to merge, just use "
+                                       "'hg update' or look at 'hg heads'"))
+        elif not (overwrite or branchmerge):
+            raise util.Abort(_("update spans branches, use 'hg merge' "
+                               "or 'hg update -C' to lose changes"))
+        if branchmerge and not forcemerge:
+            if wc.files():
+                raise util.Abort(_("outstanding uncommitted changes"))
 
-    ### calculate phase
-    action = []
-    if not force:
-        checkunknown(wc, p2)
-    if not util.checkfolding(repo.path):
-        checkcollision(p2)
-    if not branchmerge:
-        action += forgetremoved(wc, p2)
-    action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
+        ### calculate phase
+        action = []
+        if not force:
+            checkunknown(wc, p2)
+        if not util.checkfolding(repo.path):
+            checkcollision(p2)
+        if not branchmerge:
+            action += forgetremoved(wc, p2)
+        action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
 
-    ### apply phase
-    if not branchmerge: # just jump to the new rev
-        fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
-    if not partial:
-        repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
+        ### apply phase
+        if not branchmerge: # just jump to the new rev
+            fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
+        if not partial:
+            repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
 
-    stats = applyupdates(repo, action, wc, p2)
+        stats = applyupdates(repo, action, wc, p2)
 
-    if not partial:
-        recordupdates(repo, action, branchmerge)
-        repo.dirstate.setparents(fp1, fp2)
-        if not branchmerge and not fastforward:
-            repo.dirstate.setbranch(p2.branch())
-        repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
+        if not partial:
+            recordupdates(repo, action, branchmerge)
+            repo.dirstate.setparents(fp1, fp2)
+            if not branchmerge and not fastforward:
+                repo.dirstate.setbranch(p2.branch())
+            repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
 
-    return stats
-
+        return stats
+    finally:
+        del wlock
--- a/mercurial/patch.py
+++ b/mercurial/patch.py
@@ -1013,7 +1013,7 @@ def diffopts(ui, opts={}, untrusted=Fals
         ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
         ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'))
 
-def updatedir(ui, repo, patches, wlock=None):
+def updatedir(ui, repo, patches):
     '''Update dirstate after patch application according to metadata'''
     if not patches:
         return
@@ -1035,11 +1035,11 @@ def updatedir(ui, repo, patches, wlock=N
     for src, dst, after in copies:
         if not after:
             copyfile(src, dst, repo.root)
-        repo.copy(src, dst, wlock=wlock)
+        repo.copy(src, dst)
     removes = removes.keys()
     if removes:
         removes.sort()
-        repo.remove(removes, True, wlock=wlock)
+        repo.remove(removes, True)
     for f in patches:
         ctype, gp = patches[f]
         if gp and gp.mode:
@@ -1050,7 +1050,7 @@ def updatedir(ui, repo, patches, wlock=N
                 repo.wwrite(gp.path, '', x and 'x' or '')
             else:
                 util.set_exec(dst, x)
-    cmdutil.addremove(repo, cfiles, wlock=wlock)
+    cmdutil.addremove(repo, cfiles)
     files = patches.keys()
     files.extend([r for r in removes if r not in files])
     files.sort()
--- a/mercurial/revlog.py
+++ b/mercurial/revlog.py
@@ -281,6 +281,17 @@ class lazymap(object):
 class RevlogError(Exception): pass
 class LookupError(RevlogError): pass
 
+def getoffset(q):
+    if q & 0xFFFF:
+        raise RevlogError(_('incompatible revision flag %x') % q)
+    return int(q >> 16)
+
+def gettype(q):
+    return int(q & 0xFFFF)
+
+def offset_type(offset, type):
+    return long(long(offset) << 16 | type)
+
 class revlog(object):
     """
     the underlying revision storage object
@@ -325,14 +336,16 @@ class revlog(object):
             self.defversion = opener.defversion
             if self.defversion & REVLOGNG:
                 self.defversion |= REVLOGNGINLINEDATA
-        self.load()
+        self._load()
 
-    def load(self):
+    def _load(self):
         v = self.defversion
         try:
             f = self.opener(self.indexfile)
             i = f.read(4)
             f.seek(0)
+            if len(i) > 0:
+                v = struct.unpack(versionformat, i)[0]
         except IOError, inst:
             if inst.errno != errno.ENOENT:
                 raise
@@ -351,8 +364,6 @@ class revlog(object):
                     and st.st_size == oldst.st_size):
                     return
                 self.indexstat = st
-            if len(i) > 0:
-                v = struct.unpack(versionformat, i)[0]
         flags = v & ~0xFFFF
         fmt = v & 0xFFFF
         if fmt == REVLOGV0:
@@ -375,29 +386,28 @@ class revlog(object):
             shaoffset = ngshaoffset
 
         if i:
-            if (lazyparser.safe_to_use and not self.inlinedata() and
+            if (lazyparser.safe_to_use and not self._inline() and
                 st and st.st_size > 10000):
                 # big index, let's parse it on demand
                 parser = lazyparser(f, st.st_size, self.indexformat, shaoffset)
                 self.index = lazyindex(parser)
                 self.nodemap = lazymap(parser)
             else:
-                self.parseindex(f, st)
+                self._parseindex(f, st)
             if self.version != REVLOGV0:
                 e = list(self.index[0])
-                type = self.ngtype(e[0])
-                e[0] = self.offset_type(0, type)
+                type = gettype(e[0])
+                e[0] = offset_type(0, type)
                 self.index[0] = e
         else:
             self.nodemap = {nullid: nullrev}
             self.index = []
 
-
-    def parseindex(self, fp, st):
+    def _parseindex(self, fp, st):
         s = struct.calcsize(self.indexformat)
         self.index = []
         self.nodemap =  {nullid: nullrev}
-        inline = self.inlinedata()
+        inline = self._inline()
         n = 0
         leftover = None
         while True:
@@ -408,7 +418,7 @@ class revlog(object):
                 data = fp.read()
             if not data:
                 break
-            if n == 0 and self.inlinedata():
+            if n == 0 and self._inline():
                 # cache the first chunk
                 self.chunkcache = (0, data)
             if leftover:
@@ -438,37 +448,26 @@ class revlog(object):
                 break
 
 
-    def ngoffset(self, q):
-        if q & 0xFFFF:
-            raise RevlogError(_('%s: incompatible revision flag %x') %
-                              (self.indexfile, q))
-        return long(q >> 16)
-
-    def ngtype(self, q):
-        return int(q & 0xFFFF)
-
-    def offset_type(self, offset, type):
-        return long(long(offset) << 16 | type)
-
-    def loadindex(self, start, end):
+    def _loadindex(self, start, end):
         """load a block of indexes all at once from the lazy parser"""
         if isinstance(self.index, lazyindex):
             self.index.p.loadindex(start, end)
 
-    def loadindexmap(self):
+    def _loadindexmap(self):
         """loads both the map and the index from the lazy parser"""
         if isinstance(self.index, lazyindex):
             p = self.index.p
             p.loadindex()
             self.nodemap = p.map
 
-    def loadmap(self):
+    def _loadmap(self):
         """loads the map from the lazy parser"""
         if isinstance(self.nodemap, lazymap):
             self.nodemap.p.loadmap()
             self.nodemap = self.nodemap.p.map
 
-    def inlinedata(self): return self.version & REVLOGNGINLINEDATA
+    def _inline(self): return self.version & REVLOGNGINLINEDATA
+
     def tip(self): return self.node(len(self.index) - 1)
     def count(self): return len(self.index)
     def node(self, rev):
@@ -498,7 +497,7 @@ class revlog(object):
         if rev == nullrev:
             return 0
         if self.version != REVLOGV0:
-            return self.ngoffset(self.index[rev][0])
+            return getoffset(self.index[rev][0])
         return self.index[rev][0]
 
     def end(self, rev): return self.start(rev) + self.length(rev)
@@ -832,14 +831,6 @@ class revlog(object):
         p1, p2 = self.parents(node)
         return hash(text, p1, p2) != node
 
-    def makenode(self, node, text):
-        """calculate a file nodeid for text, descended or possibly
-        unchanged from node"""
-
-        if self.cmp(node, text):
-            return hash(text, node, nullid)
-        return node
-
     def diff(self, a, b):
         """return a delta between two revisions"""
         return mdiff.textdiff(a, b)
@@ -850,7 +841,7 @@ class revlog(object):
 
     def chunk(self, rev, df=None, cachelen=4096):
         start, length = self.start(rev), self.length(rev)
-        inline = self.inlinedata()
+        inline = self._inline()
         if inline:
             start += (rev + 1) * struct.calcsize(self.indexformat)
         end = start + length
@@ -908,7 +899,7 @@ class revlog(object):
         rev = self.rev(node)
         base = self.base(rev)
 
-        if self.inlinedata():
+        if self._inline():
             # we probably have the whole chunk cached
             df = None
         else:
@@ -918,9 +909,9 @@ class revlog(object):
         if self.cache and self.cache[1] >= base and self.cache[1] < rev:
             base = self.cache[1]
             text = self.cache[2]
-            self.loadindex(base, rev + 1)
+            self._loadindex(base, rev + 1)
         else:
-            self.loadindex(base, rev + 1)
+            self._loadindex(base, rev + 1)
             text = self.chunk(base, df=df)
 
         bins = []
@@ -938,7 +929,7 @@ class revlog(object):
         return text
 
     def checkinlinesize(self, tr, fp=None):
-        if not self.inlinedata():
+        if not self._inline():
             return
         if not fp:
             fp = self.opener(self.indexfile, 'r')
@@ -995,7 +986,7 @@ class revlog(object):
         p1, p2 - the parent nodeids of the revision
         d - an optional precomputed delta
         """
-        if not self.inlinedata():
+        if not self._inline():
             dfh = self.opener(self.datafile, "a")
         else:
             dfh = None
@@ -1042,14 +1033,14 @@ class revlog(object):
         if self.version == REVLOGV0:
             e = (offset, l, base, link, p1, p2, node)
         else:
-            e = (self.offset_type(offset, 0), l, len(text),
+            e = (offset_type(offset, 0), l, len(text),
                  base, link, self.rev(p1), self.rev(p2), node)
 
         self.index.append(e)
         self.nodemap[node] = n
         entry = struct.pack(self.indexformat, *e)
 
-        if not self.inlinedata():
+        if not self._inline():
             transaction.add(self.datafile, offset)
             transaction.add(self.indexfile, n * len(entry))
             if data[0]:
@@ -1067,7 +1058,7 @@ class revlog(object):
 
         ifh.write(entry)
 
-        if self.inlinedata():
+        if self._inline():
             ifh.write(data[0])
             ifh.write(data[1])
             self.checkinlinesize(transaction, ifh)
@@ -1144,7 +1135,7 @@ class revlog(object):
         ifh = self.opener(self.indexfile, "a+")
         ifh.seek(0, 2)
         transaction.add(self.indexfile, ifh.tell(), self.count())
-        if self.inlinedata():
+        if self._inline():
             dfh = None
         else:
             transaction.add(self.datafile, end)
@@ -1193,7 +1184,7 @@ class revlog(object):
                 text = self.patches(text, [delta])
                 chk = self._addrevision(text, transaction, link, p1, p2, None,
                                         ifh, dfh)
-                if not dfh and not self.inlinedata():
+                if not dfh and not self._inline():
                     # addrevision switched from inline to conventional
                     # reopen the index
                     dfh = self.opener(self.datafile, "a")
@@ -1205,15 +1196,15 @@ class revlog(object):
                 if self.version == REVLOGV0:
                     e = (end, len(cdelta), base, link, p1, p2, node)
                 else:
-                    e = (self.offset_type(end, 0), len(cdelta), textlen, base,
+                    e = (offset_type(end, 0), len(cdelta), textlen, base,
                          link, self.rev(p1), self.rev(p2), node)
                 self.index.append(e)
                 self.nodemap[node] = r
-                if self.inlinedata():
+                if self._inline():
                     ifh.write(struct.pack(self.indexformat, *e))
                     ifh.write(cdelta)
                     self.checkinlinesize(transaction, ifh)
-                    if not self.inlinedata():
+                    if not self._inline():
                         dfh = self.opener(self.datafile, "a")
                         ifh = self.opener(self.indexfile, "a")
                 else:
@@ -1232,7 +1223,7 @@ class revlog(object):
             return
 
         if isinstance(self.index, lazyindex):
-            self.loadindexmap()
+            self._loadindexmap()
 
         # When stripping away a revision, we need to make sure it
         # does not actually belong to an older changeset.
@@ -1245,7 +1236,7 @@ class revlog(object):
 
         # first truncate the files on disk
         end = self.start(rev)
-        if not self.inlinedata():
+        if not self._inline():
             df = self.opener(self.datafile, "a")
             df.truncate(end)
             end = rev * struct.calcsize(self.indexformat)
@@ -1285,7 +1276,7 @@ class revlog(object):
             s = struct.calcsize(self.indexformat)
             i = actual / s
             di = actual - (i * s)
-            if self.inlinedata():
+            if self._inline():
                 databytes = 0
                 for r in xrange(self.count()):
                     databytes += self.length(r)
--- a/mercurial/streamclone.py
+++ b/mercurial/streamclone.py
@@ -66,22 +66,25 @@ def stream_out(repo, fileobj, untrusted=
 
     # get consistent snapshot of repo. lock during scan so lock not
     # needed while we stream, and commits can happen.
+    lock = None
     try:
-        repolock = repo.lock()
-    except (lock.LockHeld, lock.LockUnavailable), inst:
-        repo.ui.warn('locking the repository failed: %s\n' % (inst,))
-        fileobj.write('2\n')
-        return
+        try:
+            repolock = repo.lock()
+        except (lock.LockHeld, lock.LockUnavailable), inst:
+            repo.ui.warn('locking the repository failed: %s\n' % (inst,))
+            fileobj.write('2\n')
+            return
 
-    fileobj.write('0\n')
-    repo.ui.debug('scanning\n')
-    entries = []
-    total_bytes = 0
-    for name, size in walkrepo(repo.spath):
-        name = repo.decodefn(util.pconvert(name))
-        entries.append((name, size))
-        total_bytes += size
-    repolock.release()
+        fileobj.write('0\n')
+        repo.ui.debug('scanning\n')
+        entries = []
+        total_bytes = 0
+        for name, size in walkrepo(repo.spath):
+            name = repo.decodefn(util.pconvert(name))
+            entries.append((name, size))
+            total_bytes += size
+    finally:
+        del repolock
 
     repo.ui.debug('%d files, %d bytes to transfer\n' %
                   (len(entries), total_bytes))
--- a/mercurial/verify.py
+++ b/mercurial/verify.py
@@ -10,6 +10,13 @@ from i18n import _
 import revlog, mdiff
 
 def verify(repo):
+    lock = repo.lock()
+    try:
+        return _verify(repo)
+    finally:
+        del lock
+
+def _verify(repo):
     filelinkrevs = {}
     filenodes = {}
     changesets = revisions = files = 0
@@ -17,8 +24,6 @@ def verify(repo):
     warnings = [0]
     neededmanifests = {}
 
-    lock = repo.lock()
-
     def err(msg):
         repo.ui.warn(msg + "\n")
         errors[0] += 1
--- a/tests/test-abort-checkin.out
+++ b/tests/test-abort-checkin.out
@@ -1,8 +1,8 @@
 error: pretxncommit.nocommits hook failed: no commits allowed
-abort: no commits allowed
 transaction abort!
 rollback completed
+abort: no commits allowed
 error: pretxncommit.nocommits hook failed: no commits allowed
-abort: no commits allowed
 transaction abort!
 rollback completed
+abort: no commits allowed
--- a/tests/test-acl.out
+++ b/tests/test-acl.out
@@ -129,9 +129,9 @@ acl: acl.allow enabled, 0 entries for us
 acl: acl.deny not enabled
 acl: user fred not allowed on foo/file.txt
 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset ef1ea85a6374
-abort: acl: access denied for changeset ef1ea85a6374
 transaction abort!
 rollback completed
+abort: acl: access denied for changeset ef1ea85a6374
 no rollback information available
 0:6675d58eff77
 
@@ -170,9 +170,9 @@ acl: allowing changeset ef1ea85a6374
 acl: allowing changeset f9cafe1212c8
 acl: user fred not allowed on quux/file.py
 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset 911600dab2ae
-abort: acl: access denied for changeset 911600dab2ae
 transaction abort!
 rollback completed
+abort: acl: access denied for changeset 911600dab2ae
 no rollback information available
 0:6675d58eff77
 
@@ -210,9 +210,9 @@ acl: acl.allow enabled, 0 entries for us
 acl: acl.deny enabled, 0 entries for user barney
 acl: user barney not allowed on foo/file.txt
 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset ef1ea85a6374
-abort: acl: access denied for changeset ef1ea85a6374
 transaction abort!
 rollback completed
+abort: acl: access denied for changeset ef1ea85a6374
 no rollback information available
 0:6675d58eff77
 
@@ -253,9 +253,9 @@ acl: allowing changeset ef1ea85a6374
 acl: allowing changeset f9cafe1212c8
 acl: user fred not allowed on quux/file.py
 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset 911600dab2ae
-abort: acl: access denied for changeset 911600dab2ae
 transaction abort!
 rollback completed
+abort: acl: access denied for changeset 911600dab2ae
 no rollback information available
 0:6675d58eff77
 
@@ -296,9 +296,9 @@ acl: acl.deny enabled, 2 entries for use
 acl: allowing changeset ef1ea85a6374
 acl: user fred denied on foo/Bar/file.txt
 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset f9cafe1212c8
-abort: acl: access denied for changeset f9cafe1212c8
 transaction abort!
 rollback completed
+abort: acl: access denied for changeset f9cafe1212c8
 no rollback information available
 0:6675d58eff77
 
@@ -338,9 +338,9 @@ acl: acl.allow enabled, 0 entries for us
 acl: acl.deny enabled, 0 entries for user barney
 acl: user barney not allowed on foo/file.txt
 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset ef1ea85a6374
-abort: acl: access denied for changeset ef1ea85a6374
 transaction abort!
 rollback completed
+abort: acl: access denied for changeset ef1ea85a6374
 no rollback information available
 0:6675d58eff77
 
@@ -427,9 +427,9 @@ acl: allowing changeset ef1ea85a6374
 acl: allowing changeset f9cafe1212c8
 acl: user wilma not allowed on quux/file.py
 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset 911600dab2ae
-abort: acl: access denied for changeset 911600dab2ae
 transaction abort!
 rollback completed
+abort: acl: access denied for changeset 911600dab2ae
 no rollback information available
 0:6675d58eff77
 
@@ -471,9 +471,9 @@ adding quux/file.py revisions
 added 3 changesets with 3 changes to 3 files
 calling hook pretxnchangegroup.acl: hgext.acl.hook
 error: pretxnchangegroup.acl hook failed: unable to open ../acl.config: No such file or directory
-abort: unable to open ../acl.config: No such file or directory
 transaction abort!
 rollback completed
+abort: unable to open ../acl.config: No such file or directory
 no rollback information available
 0:6675d58eff77
 
@@ -524,9 +524,9 @@ acl: allowing changeset ef1ea85a6374
 acl: allowing changeset f9cafe1212c8
 acl: user betty not allowed on quux/file.py
 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset 911600dab2ae
-abort: acl: access denied for changeset 911600dab2ae
 transaction abort!
 rollback completed
+abort: acl: access denied for changeset 911600dab2ae
 no rollback information available
 0:6675d58eff77
 
--- a/tests/test-clone-pull-corruption.out
+++ b/tests/test-clone-pull-corruption.out
@@ -1,8 +1,8 @@
 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 pulling from ../source
-abort: pretxncommit hook exited with status 1
 transaction abort!
 rollback completed
+abort: pretxncommit hook exited with status 1
 searching for changes
 adding changesets
 adding manifests
--- a/tests/test-committer.out
+++ b/tests/test-committer.out
@@ -22,7 +22,7 @@ user:        foo@bar.com
 date:        Mon Jan 12 13:46:40 1970 +0000
 summary:     commit-1
 
-abort: Please specify a username.
 transaction abort!
 rollback completed
+abort: Please specify a username.
 No username found, using user@host instead
--- a/tests/test-hook.out
+++ b/tests/test-hook.out
@@ -60,9 +60,9 @@ precommit hook: HG_PARENT1=8ea2ef7ad3e8c
 pretxncommit hook: HG_NODE=fad284daf8c032148abaffcd745dafeceefceb61 HG_PARENT1=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 
 5:fad284daf8c0
 pretxncommit.forbid hook: HG_NODE=fad284daf8c032148abaffcd745dafeceefceb61 HG_PARENT1=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 
-abort: pretxncommit.forbid1 hook exited with status 1
 transaction abort!
 rollback completed
+abort: pretxncommit.forbid1 hook exited with status 1
 4:8ea2ef7ad3e8
 precommit hook: HG_PARENT1=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 
 precommit.forbid hook: HG_PARENT1=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 
@@ -86,9 +86,9 @@ adding changesets
 adding manifests
 adding file changes
 added 1 changesets with 1 changes to 1 files
-abort: pretxnchangegroup.forbid1 hook exited with status 1
 transaction abort!
 rollback completed
+abort: pretxnchangegroup.forbid1 hook exited with status 1
 3:4c52fb2e4022
 preoutgoing hook: HG_SOURCE=pull 
 outgoing hook: HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_SOURCE=pull