changeset 1932:82995896d5af

Merge with http://hg.home.dataloss.nl/hg-portabletests
author Thomas Arendsen Hein <thomas@intevation.de>
date Mon, 13 Mar 2006 12:22:55 +0100
parents 6d50d6189269 (diff) 819a2508f2c6 (current diff)
children 7544700fd931
files tests/run-tests
diffstat 28 files changed, 714 insertions(+), 572 deletions(-) [+]
line wrap: on
line diff
--- a/PKG-INFO	Mon Mar 06 18:01:34 2006 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,10 +0,0 @@
-Metadata-Version: 1.0
-Name: mercurial
-Version: 0.7
-Summary: scalable distributed SCM
-Home-page: http://selenic.com/mercurial
-Author: Matt Mackall
-Author-email: mpm@selenic.com
-License: GNU GPL
-Description: UNKNOWN
-Platform: UNKNOWN
--- a/contrib/bash_completion	Mon Mar 06 18:01:34 2006 +0100
+++ b/contrib/bash_completion	Mon Mar 13 12:22:55 2006 +0100
@@ -1,30 +1,5 @@
 shopt -s extglob
 
-_hg_command_list()
-{
-    "$hg" --debug help 2>/dev/null | \
-	awk -F', ' '/^list of commands:/ {commands=1}
-	    commands==1 && /^ [^ ]/ {
-		line = substr($0, 2)
-		colon = index(line, ":")
-		if (colon > 0)
-		    line = substr(line, 1, colon-1)
-		n = split(line, aliases)
-		command = aliases[1]
-		if (index(command, "debug") == 1) {
-		    for (i=1; i<=n; i++)
-			debug[j++] = aliases[i]
-		    next
-		}
-		print command
-		for (i=2; i<=n; i++)
-		    if (index(command, aliases[i]) != 1)
-			print aliases[i]
-	    }
-	    /^global options:/ {exit 0}
-	    END {for (i in debug) print debug[i]}'
-}
-
 _hg_option_list()
 {
     "$hg" -v help $1 2>/dev/null | \
@@ -40,21 +15,9 @@
 
 _hg_commands()
 {
-    local all commands result
-
-    all=$(_hg_command_list)
-    commands=${all%%$'\n'debug*}
-    result=$(compgen -W '$commands' -- "$cur")
-
-    # hide debug commands from users, but complete them if
-    # there is no other possible command
-    if [ "$result" = "" ]; then
-	local debug
-	debug=debug${all#*$'\n'debug}
-	result=$(compgen -W '$debug' -- "$cur")
-    fi
-
-    COMPREPLY=(${COMPREPLY[@]:-} $result)
+    local commands
+    commands="$("$hg" debugcomplete "$cur" 2>/dev/null)" || commands=""
+    COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$commands' -- "$cur"))
 }
 
 _hg_paths()
--- a/contrib/hbisect.py	Mon Mar 06 18:01:34 2006 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,287 +0,0 @@
-#!/usr/bin/env python
-#
-# This software may be used and distributed according to the terms
-# of the GNU General Public License, incorporated herein by reference.
-
-from mercurial.demandload import demandload
-demandload(globals(), "os sys sets")
-from mercurial import hg
-
-versionstr = "0.0.3"
-
-def lookup_rev(ui, repo, rev=None):
-    """returns rev or the checked-out revision if rev is None"""
-    if not rev is None:
-        return repo.lookup(rev)
-    parents = [p for p in repo.dirstate.parents() if p != hg.nullid]
-    if len(parents) != 1:
-        ui.warn("unexpected number of parents\n")
-        ui.warn("please commit or revert\n")
-        sys.exit(1)
-    return parents.pop()
-
-def check_clean(ui, repo):
-        modified, added, removed, deleted, unknown = repo.changes()
-        if modified or added or removed:
-            ui.warn("Repository is not clean, please commit or revert\n")
-            sys.exit(1)
-
-class bisect(object):
-    """dichotomic search in the DAG of changesets"""
-    def __init__(self, ui, repo):
-        self.repo = repo
-        self.path = os.path.join(repo.join(""), "bisect")
-        self.ui = ui
-        self.goodrevs = []
-        self.badrev = None
-        self.good_dirty = 0
-        self.bad_dirty = 0
-        self.good_path = os.path.join(self.path, "good")
-        self.bad_path = os.path.join(self.path, "bad")
-
-        s = self.good_path
-        if os.path.exists(s):
-            self.goodrevs = self.repo.opener(s).read().splitlines()
-            self.goodrevs = [hg.bin(x) for x in self.goodrevs]
-        s = self.bad_path
-        if os.path.exists(s):
-            r = self.repo.opener(s).read().splitlines()
-            if r:
-                self.badrev = hg.bin(r.pop(0))
-
-    def __del__(self):
-        if not os.path.isdir(self.path):
-            return
-        f = self.repo.opener(self.good_path, "w")
-        f.write("\n".join([hg.hex(r) for r in  self.goodrevs]))
-        if len(self.goodrevs) > 0:
-            f.write("\n")
-        f = self.repo.opener(self.bad_path, "w")
-        if self.badrev:
-            f.write(hg.hex(self.badrev) + "\n")
-
-    def init(self):
-        """start a new bisection"""
-        if os.path.isdir(self.path):
-            self.ui.warn("bisect directory already exists\n")
-            return 1
-        os.mkdir(self.path)
-        check_clean(self.ui, self.repo)
-        return 0
-
-    def reset(self):
-        """finish a bisection"""
-        if os.path.isdir(self.path):
-            sl = [self.bad_path, self.good_path]
-            for s in sl:
-                if os.path.exists(s):
-                    os.unlink(s)
-            os.rmdir(self.path)
-        # Not sure about this
-        #self.ui.write("Going back to tip\n")
-        #self.repo.update(self.repo.changelog.tip())
-        return 1
-
-    def num_ancestors(self, head=None, stop=None):
-        """
-        returns a dict with the mapping:
-        node -> number of ancestors (self included)
-        for all nodes who are ancestor of head and
-        not in stop.
-        """
-        if head is None:
-            head = self.badrev
-        return self.__ancestors_and_nb_ancestors(head, stop)[1]
-        
-    def ancestors(self, head=None, stop=None):
-        """
-        returns the set of the ancestors of head (self included)
-        who are not in stop.
-        """
-        if head is None:
-            head = self.badrev
-        return self.__ancestors_and_nb_ancestors(head, stop)[0]
-        
-    def __ancestors_and_nb_ancestors(self, head, stop=None):
-        """
-        if stop is None then ancestors of goodrevs are used as
-        lower limit.
-
-        returns (anc, n_child) where anc is the set of the ancestors of head
-        and n_child is a dictionary with the following mapping:
-        node -> number of ancestors (self included)
-        """
-        cl = self.repo.changelog
-        if not stop:
-            stop = sets.Set([])
-            for g in reversed(self.goodrevs):
-                if g in stop:
-                    continue
-                stop.update(cl.reachable(g))
-        def num_children(a):
-            """
-            returns a dictionnary with the following mapping
-            node -> [number of children, empty set]
-            """
-            d = {a: [0, sets.Set([])]}
-            for i in xrange(cl.rev(a)+1):
-                n = cl.node(i)
-                if not d.has_key(n):
-                    d[n] = [0, sets.Set([])]
-                parents = [p for p in cl.parents(n) if p != hg.nullid]
-                for p in parents:
-                    d[p][0] += 1
-            return d
-        
-        if head in stop:
-            self.ui.warn("Unconsistent state, %s is good and bad\n"
-                          % hg.hex(head))
-            sys.exit(1)
-        n_child = num_children(head)
-        for i in xrange(cl.rev(head)+1):
-            n = cl.node(i)
-            parents = [p for p in cl.parents(n) if p != hg.nullid]
-            for p in parents:
-                n_child[p][0] -= 1
-                if not n in stop:
-                    n_child[n][1].union_update(n_child[p][1])
-                if n_child[p][0] == 0:
-                    n_child[p] = len(n_child[p][1])
-            if not n in stop:
-                n_child[n][1].add(n)
-            if n_child[n][0] == 0:
-                if n == head:
-                    anc = n_child[n][1]
-                n_child[n] = len(n_child[n][1])
-        return anc, n_child
-
-    def next(self):
-        if not self.badrev:
-            self.ui.warn("You should give at least one bad\n")
-            sys.exit(1)
-        if not self.goodrevs:
-            self.ui.warn("No good revision given\n")
-            self.ui.warn("Assuming the first revision is good\n")
-        ancestors, num_ancestors = self.__ancestors_and_nb_ancestors(self.badrev)
-        tot = len(ancestors)
-        if tot == 1:
-            if ancestors.pop() != self.badrev:
-                self.ui.warn("Could not find the first bad revision\n")
-                sys.exit(1)
-            self.ui.write(
-                "The first bad revision is : %s\n" % hg.hex(self.badrev))
-            sys.exit(0)
-        self.ui.write("%d revisions left\n" % tot)
-        best_rev = None
-        best_len = -1
-        for n in ancestors:
-            l = num_ancestors[n]
-            l = min(l, tot - l)
-            if l > best_len:
-                best_len = l
-                best_rev = n
-        return best_rev
-
-    def autonext(self):
-        """find and update to the next revision to test"""
-        check_clean(self.ui, self.repo)
-        rev = self.next()
-        self.ui.write("Now testing %s\n" % hg.hex(rev))
-        return self.repo.update(rev, force=True)
-
-    def good(self, rev):
-        self.goodrevs.append(rev)
-
-    def autogood(self, rev=None):
-        """mark revision as good and update to the next revision to test"""
-        check_clean(self.ui, self.repo)
-        rev = lookup_rev(self.ui, self.repo, rev)
-        self.good(rev)
-        if self.badrev:
-            self.autonext()
-
-    def bad(self, rev):
-        self.badrev = rev
-
-    def autobad(self, rev=None):
-        """mark revision as bad and update to the next revision to test"""
-        check_clean(self.ui, self.repo)
-        rev = lookup_rev(self.ui, self.repo, rev)
-        self.bad(rev)
-        if self.goodrevs:
-            self.autonext()
-
-# should we put it in the class ?
-def test(ui, repo, rev):
-    """test the bisection code"""
-    b = bisect(ui, repo)
-    rev = repo.lookup(rev)
-    ui.write("testing with rev %s\n" % hg.hex(rev))
-    anc = b.ancestors()
-    while len(anc) > 1:
-        if not rev in anc:
-            ui.warn("failure while bisecting\n")
-            sys.exit(1)
-        ui.write("it worked :)\n")
-        new_rev = b.next()
-        ui.write("choosing if good or bad\n")
-        if rev in b.ancestors(head=new_rev):
-            b.bad(new_rev)
-            ui.write("it is bad\n")
-        else:
-            b.good(new_rev)
-            ui.write("it is good\n")
-        anc = b.ancestors()
-        repo.update(new_rev, force=True)
-    for v in anc:
-        if v != rev:
-            ui.warn("fail to found cset! :(\n")
-            return 1
-    ui.write("Found bad cset: %s\n" % hg.hex(b.badrev))
-    ui.write("Everything is ok :)\n")
-    return 0
-
-def bisect_run(ui, repo, cmd=None, *args):
-    """bisect extension: dichotomic search in the DAG of changesets
-for subcommands see "hg bisect help\"
-    """
-    def help_(cmd=None, *args):
-        """show help for a given bisect subcommand or all subcommands"""
-        cmdtable = bisectcmdtable
-        if cmd:
-            doc = cmdtable[cmd][0].__doc__
-            synopsis = cmdtable[cmd][2]
-            ui.write(synopsis + "\n")
-            ui.write("\n" + doc + "\n")
-            return
-        ui.write("list of subcommands for the bisect extension\n\n")
-        cmds = cmdtable.keys()
-        cmds.sort()
-        m = max([len(c) for c in cmds])
-        for cmd in cmds:
-            doc = cmdtable[cmd][0].__doc__.splitlines(0)[0].rstrip()
-            ui.write(" %-*s   %s\n" % (m, cmd, doc))
-    
-    b = bisect(ui, repo)
-    bisectcmdtable = {
-        "init": (b.init, 0, "hg bisect init"),
-        "bad": (b.autobad, 1, "hg bisect bad [<rev>]"),
-        "good": (b.autogood, 1, "hg bisect good [<rev>]"),
-        "next": (b.autonext, 0, "hg bisect next"),
-        "reset": (b.reset, 0, "hg bisect reset"),
-        "help": (help_, 1, "hg bisect help [<subcommand>]"),
-    }
-            
-    if not bisectcmdtable.has_key(cmd):
-        ui.warn("bisect: Unknown sub-command\n")
-        return help_()
-    if len(args) > bisectcmdtable[cmd][1]:
-        ui.warn("bisect: Too many arguments\n")
-        return help_()
-    return bisectcmdtable[cmd][0](*args)
-
-cmdtable = {
-    "bisect": (bisect_run, [], 
-               "hg bisect [help|init|reset|next|good|bad]"),
-    #"bisect-test": (test, [], "hg bisect-test rev"),
-}
--- a/contrib/mercurial.spec	Mon Mar 06 18:01:34 2006 +0100
+++ b/contrib/mercurial.spec	Mon Mar 13 12:22:55 2006 +0100
@@ -1,7 +1,7 @@
 Summary: Mercurial -- a distributed SCM
 Name: mercurial
-Version: 0.7
-Release: 1
+Version: 0.8
+Release: 0
 License: GPL
 Group: Development/Tools
 Source: http://www.selenic.com/mercurial/release/%{name}-%{version}.tar.gz
@@ -10,6 +10,7 @@
 
 %define pythonver %(python -c 'import sys;print ".".join(map(str, sys.version_info[:2]))')
 %define pythonlib %{_libdir}/python%{pythonver}/site-packages/%{name}
+%define hgext %{_libdir}/python%{pythonver}/site-packages/hgext
 
 %description
 Mercurial is a fast, lightweight source control management system designed
@@ -30,10 +31,12 @@
 
 %files
 %defattr(-,root,root,-)
-%doc doc/* contrib/patchbomb *.cgi
+%doc doc/* *.cgi
 %dir %{pythonlib}
+%dir %{hgext}
 %{_bindir}/hgmerge
 %{_bindir}/hg
 %{pythonlib}/templates
 %{pythonlib}/*.py*
 %{pythonlib}/*.so
+%{hgext}/*.py*
--- a/contrib/win32/mercurial.iss	Mon Mar 06 18:01:34 2006 +0100
+++ b/contrib/win32/mercurial.iss	Mon Mar 13 12:22:55 2006 +0100
@@ -28,23 +28,22 @@
 DefaultGroupName=Mercurial
 
 [Files]
-Source: templates\*.*; DestDir: {app}\Templates; Flags: recursesubdirs createallsubdirs
+Source: ..\..\msys\1.0\bin\patch.exe; DestDir: {app}
 Source: contrib\mercurial.el; DestDir: {app}/Contrib
-Source: contrib\patchbomb; DestDir: {app}/Contrib
-Source: dist\w9xpopen.exe; DestDir: {app}
+Source: contrib\win32\ReadMe.html; DestDir: {app}; Flags: isreadme
+Source: contrib\win32\mercurial.ini; DestDir: {app}; DestName: Mercurial.ini; Flags: confirmoverwrite
+Source: contrib\win32\postinstall.txt; DestDir: {app}; DestName: ReleaseNotes.txt
 Source: dist\hg.exe; DestDir: {app}
+Source: dist\library.zip; DestDir: {app}
+Source: dist\mfc71.dll; DestDir: {sys}; Flags: sharedfile uninsnosharedfileprompt
 Source: dist\msvcr71.dll; DestDir: {sys}; Flags: sharedfile uninsnosharedfileprompt
-Source: dist\library.zip; DestDir: {app}
+Source: dist\w9xpopen.exe; DestDir: {app}
 Source: doc\*.txt; DestDir: {app}\Docs
-Source: dist\mfc71.dll; DestDir: {sys}; Flags: sharedfile uninsnosharedfileprompt
+Source: templates\*.*; DestDir: {app}\Templates; Flags: recursesubdirs createallsubdirs
+Source: CONTRIBUTORS; DestDir: {app}; DestName: Contributors.txt
 Source: COPYING; DestDir: {app}; DestName: Copying.txt
 Source: comparison.txt; DestDir: {app}\Docs; DestName: Comparison.txt
 Source: notes.txt; DestDir: {app}\Docs; DestName: DesignNotes.txt
-Source: CONTRIBUTORS; DestDir: {app}; DestName: Contributors.txt
-Source: contrib\win32\ReadMe.html; DestDir: {app}; Flags: isreadme
-Source: ..\..\msys\1.0\bin\patch.exe; DestDir: {app}
-Source: contrib\win32\mercurial.ini; DestDir: {app}; DestName: Mercurial.ini; Flags: confirmoverwrite
-Source: contrib\win32\postinstall.txt; DestDir: {app}; DestName: ReleaseNotes.txt
 
 [INI]
 Filename: {app}\Mercurial.url; Section: InternetShortcut; Key: URL; String: http://www.selenic.com/mercurial/
--- a/doc/Makefile	Mon Mar 06 18:01:34 2006 +0100
+++ b/doc/Makefile	Mon Mar 13 12:22:55 2006 +0100
@@ -24,4 +24,4 @@
 	asciidoc -b html4 $*.txt || asciidoc -b html $*.txt
 
 clean:
-	$(RM) $(MAN) $(MAN:%=%.xml) $(MAN:%=%.html)
+	$(RM) $(MAN) $(MAN:%=%.xml) $(MAN:%=%.html) *.[0-9].gendoc.txt
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/hbisect.py	Mon Mar 13 12:22:55 2006 +0100
@@ -0,0 +1,290 @@
+# bisect extension for mercurial
+#
+# Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
+# Inspired by git bisect, extension skeleton taken from mq.py.
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+from mercurial.demandload import demandload
+demandload(globals(), "os sys sets mercurial:hg,util")
+
+versionstr = "0.0.3"
+
+def lookup_rev(ui, repo, rev=None):
+    """returns rev or the checked-out revision if rev is None"""
+    if not rev is None:
+        return repo.lookup(rev)
+    parents = [p for p in repo.dirstate.parents() if p != hg.nullid]
+    if len(parents) != 1:
+        ui.warn("unexpected number of parents\n")
+        ui.warn("please commit or revert\n")
+        sys.exit(1)
+    return parents.pop()
+
+def check_clean(ui, repo):
+        modified, added, removed, deleted, unknown = repo.changes()
+        if modified or added or removed:
+            ui.warn("Repository is not clean, please commit or revert\n")
+            sys.exit(1)
+
+class bisect(object):
+    """dichotomic search in the DAG of changesets"""
+    def __init__(self, ui, repo):
+        self.repo = repo
+        self.path = repo.join("bisect")
+        self.opener = util.opener(self.path)
+        self.ui = ui
+        self.goodrevs = []
+        self.badrev = None
+        self.good_dirty = 0
+        self.bad_dirty = 0
+        self.good_path = "good"
+        self.bad_path = "bad"
+
+        if os.path.exists(os.path.join(self.path, self.good_path)):
+            self.goodrevs = self.opener(self.good_path).read().splitlines()
+            self.goodrevs = [hg.bin(x) for x in self.goodrevs]
+        if os.path.exists(os.path.join(self.path, self.bad_path)):
+            r = self.opener(self.bad_path).read().splitlines()
+            if r:
+                self.badrev = hg.bin(r.pop(0))
+
+    def __del__(self):
+        if not os.path.isdir(self.path):
+            return
+        f = self.opener(self.good_path, "w")
+        f.write("\n".join([hg.hex(r) for r in  self.goodrevs]))
+        if len(self.goodrevs) > 0:
+            f.write("\n")
+        f = self.opener(self.bad_path, "w")
+        if self.badrev:
+            f.write(hg.hex(self.badrev) + "\n")
+
+    def init(self):
+        """start a new bisection"""
+        if os.path.isdir(self.path):
+            self.ui.warn("bisect directory already exists\n")
+            return 1
+        os.mkdir(self.path)
+        check_clean(self.ui, self.repo)
+        return 0
+
+    def reset(self):
+        """finish a bisection"""
+        if os.path.isdir(self.path):
+            sl = [os.path.join(self.path, p)
+                  for p in [self.bad_path, self.good_path]]
+            for s in sl:
+                if os.path.exists(s):
+                    os.unlink(s)
+            os.rmdir(self.path)
+        # Not sure about this
+        #self.ui.write("Going back to tip\n")
+        #self.repo.update(self.repo.changelog.tip())
+        return 1
+
+    def num_ancestors(self, head=None, stop=None):
+        """
+        returns a dict with the mapping:
+        node -> number of ancestors (self included)
+        for all nodes who are ancestor of head and
+        not in stop.
+        """
+        if head is None:
+            head = self.badrev
+        return self.__ancestors_and_nb_ancestors(head, stop)[1]
+
+    def ancestors(self, head=None, stop=None):
+        """
+        returns the set of the ancestors of head (self included)
+        who are not in stop.
+        """
+        if head is None:
+            head = self.badrev
+        return self.__ancestors_and_nb_ancestors(head, stop)[0]
+
+    def __ancestors_and_nb_ancestors(self, head, stop=None):
+        """
+        if stop is None then ancestors of goodrevs are used as
+        lower limit.
+
+        returns (anc, n_child) where anc is the set of the ancestors of head
+        and n_child is a dictionary with the following mapping:
+        node -> number of ancestors (self included)
+        """
+        cl = self.repo.changelog
+        if not stop:
+            stop = sets.Set([])
+            for i in xrange(len(self.goodrevs)-1, -1, -1):
+                g = self.goodrevs[i]
+                if g in stop:
+                    continue
+                stop.update(cl.reachable(g))
+        def num_children(a):
+            """
+            returns a dictionnary with the following mapping
+            node -> [number of children, empty set]
+            """
+            d = {a: [0, sets.Set([])]}
+            for i in xrange(cl.rev(a)+1):
+                n = cl.node(i)
+                if not d.has_key(n):
+                    d[n] = [0, sets.Set([])]
+                parents = [p for p in cl.parents(n) if p != hg.nullid]
+                for p in parents:
+                    d[p][0] += 1
+            return d
+
+        if head in stop:
+            self.ui.warn("Unconsistent state, %s is good and bad\n"
+                          % hg.hex(head))
+            sys.exit(1)
+        n_child = num_children(head)
+        for i in xrange(cl.rev(head)+1):
+            n = cl.node(i)
+            parents = [p for p in cl.parents(n) if p != hg.nullid]
+            for p in parents:
+                n_child[p][0] -= 1
+                if not n in stop:
+                    n_child[n][1].union_update(n_child[p][1])
+                if n_child[p][0] == 0:
+                    n_child[p] = len(n_child[p][1])
+            if not n in stop:
+                n_child[n][1].add(n)
+            if n_child[n][0] == 0:
+                if n == head:
+                    anc = n_child[n][1]
+                n_child[n] = len(n_child[n][1])
+        return anc, n_child
+
+    def next(self):
+        if not self.badrev:
+            self.ui.warn("You should give at least one bad\n")
+            sys.exit(1)
+        if not self.goodrevs:
+            self.ui.warn("No good revision given\n")
+            self.ui.warn("Assuming the first revision is good\n")
+        ancestors, num_ancestors = self.__ancestors_and_nb_ancestors(
+                                    self.badrev)
+        tot = len(ancestors)
+        if tot == 1:
+            if ancestors.pop() != self.badrev:
+                self.ui.warn("Could not find the first bad revision\n")
+                sys.exit(1)
+            self.ui.write(
+                "The first bad revision is : %s\n" % hg.hex(self.badrev))
+            sys.exit(0)
+        self.ui.write("%d revisions left\n" % tot)
+        best_rev = None
+        best_len = -1
+        for n in ancestors:
+            l = num_ancestors[n]
+            l = min(l, tot - l)
+            if l > best_len:
+                best_len = l
+                best_rev = n
+        return best_rev
+
+    def autonext(self):
+        """find and update to the next revision to test"""
+        check_clean(self.ui, self.repo)
+        rev = self.next()
+        self.ui.write("Now testing %s\n" % hg.hex(rev))
+        return self.repo.update(rev, force=True)
+
+    def good(self, rev):
+        self.goodrevs.append(rev)
+
+    def autogood(self, rev=None):
+        """mark revision as good and update to the next revision to test"""
+        check_clean(self.ui, self.repo)
+        rev = lookup_rev(self.ui, self.repo, rev)
+        self.good(rev)
+        if self.badrev:
+            self.autonext()
+
+    def bad(self, rev):
+        self.badrev = rev
+
+    def autobad(self, rev=None):
+        """mark revision as bad and update to the next revision to test"""
+        check_clean(self.ui, self.repo)
+        rev = lookup_rev(self.ui, self.repo, rev)
+        self.bad(rev)
+        if self.goodrevs:
+            self.autonext()
+
+# should we put it in the class ?
+def test(ui, repo, rev):
+    """test the bisection code"""
+    b = bisect(ui, repo)
+    rev = repo.lookup(rev)
+    ui.write("testing with rev %s\n" % hg.hex(rev))
+    anc = b.ancestors()
+    while len(anc) > 1:
+        if not rev in anc:
+            ui.warn("failure while bisecting\n")
+            sys.exit(1)
+        ui.write("it worked :)\n")
+        new_rev = b.next()
+        ui.write("choosing if good or bad\n")
+        if rev in b.ancestors(head=new_rev):
+            b.bad(new_rev)
+            ui.write("it is bad\n")
+        else:
+            b.good(new_rev)
+            ui.write("it is good\n")
+        anc = b.ancestors()
+        repo.update(new_rev, force=True)
+    for v in anc:
+        if v != rev:
+            ui.warn("fail to found cset! :(\n")
+            return 1
+    ui.write("Found bad cset: %s\n" % hg.hex(b.badrev))
+    ui.write("Everything is ok :)\n")
+    return 0
+
+def bisect_run(ui, repo, cmd=None, *args):
+    """bisect extension: dichotomic search in the DAG of changesets
+for subcommands see "hg bisect help\"
+    """
+    def help_(cmd=None, *args):
+        """show help for a given bisect subcommand or all subcommands"""
+        cmdtable = bisectcmdtable
+        if cmd:
+            doc = cmdtable[cmd][0].__doc__
+            synopsis = cmdtable[cmd][2]
+            ui.write(synopsis + "\n")
+            ui.write("\n" + doc + "\n")
+            return
+        ui.write("list of subcommands for the bisect extension\n\n")
+        cmds = cmdtable.keys()
+        cmds.sort()
+        m = max([len(c) for c in cmds])
+        for cmd in cmds:
+            doc = cmdtable[cmd][0].__doc__.splitlines(0)[0].rstrip()
+            ui.write(" %-*s   %s\n" % (m, cmd, doc))
+
+    b = bisect(ui, repo)
+    bisectcmdtable = {
+        "init": (b.init, 0, "hg bisect init"),
+        "bad": (b.autobad, 1, "hg bisect bad [<rev>]"),
+        "good": (b.autogood, 1, "hg bisect good [<rev>]"),
+        "next": (b.autonext, 0, "hg bisect next"),
+        "reset": (b.reset, 0, "hg bisect reset"),
+        "help": (help_, 1, "hg bisect help [<subcommand>]"),
+    }
+
+    if not bisectcmdtable.has_key(cmd):
+        ui.warn("bisect: Unknown sub-command\n")
+        return help_()
+    if len(args) > bisectcmdtable[cmd][1]:
+        ui.warn("bisect: Too many arguments\n")
+        return help_()
+    return bisectcmdtable[cmd][0](*args)
+
+cmdtable = {
+    "bisect": (bisect_run, [], "hg bisect [help|init|reset|next|good|bad]"),
+    #"bisect-test": (test, [], "hg bisect-test rev"),
+}
--- a/hgext/mq.py	Mon Mar 06 18:01:34 2006 +0100
+++ b/hgext/mq.py	Mon Mar 13 12:22:55 2006 +0100
@@ -1,4 +1,3 @@
-#!/usr/bin/env python
 # queue.py - patch queues for mercurial
 #
 # Copyright 2005 Chris Mason <mason@suse.com>
@@ -17,28 +16,26 @@
 
 class queue:
     def __init__(self, ui, path, patchdir=None):
-        self.opener = util.opener(path)
         self.basepath = path
         if patchdir:
             self.path = patchdir
         else:
             self.path = os.path.join(path, "patches")
+        self.opener = util.opener(self.path)
         self.ui = ui
         self.applied = []
         self.full_series = []
         self.applied_dirty = 0
         self.series_dirty = 0
-        self.series_path = os.path.join(self.path, "series")
-        self.status_path = os.path.join(self.path, "status")
+        self.series_path = "series"
+        self.status_path = "status"
 
-        s = self.series_path
-        if os.path.exists(s):
-            self.full_series = self.opener(s).read().splitlines()
+        if os.path.exists(os.path.join(self.path, self.series_path)):
+            self.full_series = self.opener(self.series_path).read().splitlines()
         self.read_series(self.full_series)
 
-        s = self.status_path
-        if os.path.exists(s):
-            self.applied = self.opener(s).read().splitlines()
+        if os.path.exists(os.path.join(self.path, self.status_path)):
+            self.applied = self.opener(self.status_path).read().splitlines()
 
     def find_series(self, patch):
         pre = re.compile("(\s*)([^#]+)")
@@ -186,7 +183,7 @@
             self.ui.warn("Unable to read %s\n" % patch)
             sys.exit(1)
 
-        patchf = self.opener(os.path.join(self.path, patch), "w")
+        patchf = self.opener(patch, "w")
         if comments:
             comments = "\n".join(comments) + '\n\n'
             patchf.write(comments)
@@ -402,7 +399,7 @@
         self.read_series(self.full_series)
         self.series_dirty = 1
         self.applied_dirty = 1
-        p = self.opener(os.path.join(self.path, patch), "w")
+        p = self.opener(patch, "w")
         if msg:
             msg = msg + "\n"
             p.write(msg)
@@ -716,7 +713,7 @@
         patchparent = self.qparents(repo, top)
         message, comments, user, patchfound = self.readheaders(patch)
 
-        patchf = self.opener(os.path.join(self.path, patch), "w")
+        patchf = self.opener(patch, "w")
         if comments:
             comments = "\n".join(comments) + '\n\n'
             patchf.write(comments)
@@ -835,8 +832,9 @@
                 d = root[len(self.path) + 1:]
                 for f in files:
                     fl = os.path.join(d, f)
-                    if (fl not in self.series and fl != "status" and
-                        fl != "series" and not fl.startswith('.')):
+                    if (fl not in self.series and
+                        fl not in (self.status_path, self.series_path)
+                        and not fl.startswith('.')):
                         list.append(fl)
             list.sort()
             if list:
@@ -1012,7 +1010,7 @@
                 if not force and os.path.isfile(os.path.join(self.path, patch)):
                     self.ui.warn("patch %s already exists\n" % patch)
                     sys.exit(1)
-                patchf = self.opener(os.path.join(self.path, patch), "w")
+                patchf = self.opener(patch, "w")
                 patchf.write(text)
             if patch in self.series:
                 self.ui.warn("patch %s is already in the series file\n" % patch)
@@ -1205,7 +1203,7 @@
         util.copyfiles(path, newpath)
     if opts['empty']:
         try:
-            os.unlink(q.status_path)
+            os.unlink(os.path.join(q.path, q.status_path))
         except:
             pass
     return 0
--- a/hgext/patchbomb.py	Mon Mar 06 18:01:34 2006 +0100
+++ b/hgext/patchbomb.py	Mon Mar 13 12:22:55 2006 +0100
@@ -52,7 +52,7 @@
 from mercurial.demandload import *
 demandload(globals(), '''email.MIMEMultipart email.MIMEText email.Utils
                          mercurial:commands,hg,ui
-                         os popen2 smtplib socket sys tempfile time''')
+                         os errno popen2 smtplib socket sys tempfile time''')
 from mercurial.i18n import gettext as _
 
 try:
@@ -141,7 +141,10 @@
             body += cdiffstat('\n'.join(desc), patch) + '\n\n'
         body += '\n'.join(patch)
         msg = email.MIMEText.MIMEText(body)
-        subj = '[PATCH %d of %d] %s' % (idx, total, desc[0].strip())
+        if total == 1:
+            subj = '[PATCH] ' + desc[0].strip()
+        else:
+            subj = '[PATCH %d of %d] %s' % (idx, total, desc[0].strip())
         if subj.endswith('.'): subj = subj[:-1]
         msg['Subject'] = subj
         msg['X-Mercurial-Node'] = node
@@ -180,17 +183,9 @@
         jumbo.extend(p)
         msgs.append(makepatch(p, i + 1, len(patches)))
 
-    ui.write(_('\nWrite the introductory message for the patch series.\n\n'))
-
     sender = (opts['from'] or ui.config('patchbomb', 'from') or
               prompt('From', ui.username()))
 
-    msg = email.MIMEMultipart.MIMEMultipart()
-    msg['Subject'] = '[PATCH 0 of %d] %s' % (
-        len(patches),
-        opts['subject'] or
-        prompt('Subject:', rest = ' [PATCH 0 of %d] ' % len(patches)))
-
     def getaddrs(opt, prpt, default = None):
         addrs = opts[opt] or (ui.config('patchbomb', opt) or
                               prompt(prpt, default = default)).split(',')
@@ -198,26 +193,35 @@
     to = getaddrs('to', 'To')
     cc = getaddrs('cc', 'Cc', '')
 
-    ui.write(_('Finish with ^D or a dot on a line by itself.\n\n'))
+    if len(patches) > 1:
+        ui.write(_('\nWrite the introductory message for the patch series.\n\n'))
 
-    body = []
+        msg = email.MIMEMultipart.MIMEMultipart()
+        msg['Subject'] = '[PATCH 0 of %d] %s' % (
+            len(patches),
+            opts['subject'] or
+            prompt('Subject:', rest = ' [PATCH 0 of %d] ' % len(patches)))
+
+        ui.write(_('Finish with ^D or a dot on a line by itself.\n\n'))
+
+        body = []
 
-    while True:
-        try: l = raw_input()
-        except EOFError: break
-        if l == '.': break
-        body.append(l)
+        while True:
+            try: l = raw_input()
+            except EOFError: break
+            if l == '.': break
+            body.append(l)
 
-    msg.attach(email.MIMEText.MIMEText('\n'.join(body) + '\n'))
+        msg.attach(email.MIMEText.MIMEText('\n'.join(body) + '\n'))
+
+        if opts['diffstat']:
+            d = cdiffstat(_('Final summary:\n'), jumbo)
+            if d: msg.attach(email.MIMEText.MIMEText(d))
+
+        msgs.insert(0, msg)
 
     ui.write('\n')
 
-    if opts['diffstat']:
-        d = cdiffstat(_('Final summary:\n'), jumbo)
-        if d: msg.attach(email.MIMEText.MIMEText(d))
-
-    msgs.insert(0, msg)
-
     if not opts['test'] and not opts['mbox']:
         s = smtplib.SMTP()
         s.connect(host = ui.config('smtp', 'host', 'mail'),
@@ -250,8 +254,12 @@
         if opts['test']:
             ui.status('Displaying ', m['Subject'], ' ...\n')
             fp = os.popen(os.getenv('PAGER', 'more'), 'w')
-            fp.write(m.as_string(0))
-            fp.write('\n')
+            try:
+                fp.write(m.as_string(0))
+                fp.write('\n')
+            except IOError, inst:
+                if inst.errno != errno.EPIPE:
+                    raise
             fp.close()
         elif opts['mbox']:
             ui.status('Writing ', m['Subject'], ' ...\n')
--- a/hgmerge	Mon Mar 06 18:01:34 2006 +0100
+++ b/hgmerge	Mon Mar 13 12:22:55 2006 +0100
@@ -3,7 +3,13 @@
 # hgmerge - default merge helper for Mercurial
 #
 # This tries to find a way to do three-way merge on the current system.
-# The result ought to end up in $1.
+# The result ought to end up in $1.  Script is run in root directory of
+# repository.
+#
+# Environment variables set by Mercurial:
+# HG_FILE            name of file within repo
+# HG_MY_NODE         revision being merged
+# HG_OTHER_NODE      revision being merged
 
 set -e # bail out quickly on failure
 
--- a/mercurial/commands.py	Mon Mar 06 18:01:34 2006 +0100
+++ b/mercurial/commands.py	Mon Mar 13 12:22:55 2006 +0100
@@ -593,12 +593,7 @@
     change = repo.changelog.read(node)
     mmap = repo.manifest.read(change[0])
 
-    for src, abs, rel, exact in walk(repo, pats, opts):
-        if abs not in mmap:
-            ui.warn(_("warning: %s is not in the repository!\n") %
-                    ((pats and rel) or abs))
-            continue
-
+    for src, abs, rel, exact in walk(repo, pats, opts, node=node):
         f = repo.file(abs)
         if not opts['text'] and util.binary(f.read(mmap[abs])):
             ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
@@ -634,7 +629,7 @@
     contents including permissions, rename data, and revision history.
     """
     f = open(fname, "wb")
-    dest = ui.expandpath(dest, repo.root)
+    dest = ui.expandpath(dest)
     other = hg.repository(ui, dest)
     o = repo.findoutgoing(other)
     cg = repo.changegroup(o, 'bundle')
@@ -723,8 +718,7 @@
     if opts['remotecmd']:
         ui.setconfig("ui", "remotecmd", opts['remotecmd'])
 
-    if not os.path.exists(source):
-        source = ui.expandpath(source)
+    source = ui.expandpath(source)
 
     d = Dircleanup(dest)
     abspath = source
@@ -1018,6 +1012,12 @@
     a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
     ui.write("%d:%s\n" % (r.rev(a), hex(a)))
 
+def debugcomplete(ui, cmd):
+    """returns the completion list associated with the given command"""
+    clist = findpossible(cmd).keys()
+    clist.sort()
+    ui.write("%s\n" % " ".join(clist))
+
 def debugrebuildstate(ui, repo, rev=None):
     """rebuild the dirstate as it would look like for the given revision"""
     if not rev:
@@ -1063,13 +1063,8 @@
         error = _(".hg/dirstate inconsistent with current parent's manifest")
         raise util.Abort(error)
 
-def debugconfig(ui):
+def debugconfig(ui, repo):
     """show combined config settings from all hgrc files"""
-    try:
-        repo = hg.repository(ui)
-        ui = repo.ui
-    except hg.RepoError:
-        pass
     for section, name, value in ui.walkconfig():
         ui.write('%s.%s=%s\n' % (section, name, value))
 
@@ -1548,7 +1543,7 @@
 
     Currently only local repositories are supported.
     """
-    source = ui.expandpath(source, repo.root)
+    source = ui.expandpath(source)
     other = hg.repository(ui, source)
     if not other.local():
         raise util.Abort(_("incoming doesn't work for remote repositories yet"))
@@ -1735,7 +1730,7 @@
 
     See pull for valid source format details.
     """
-    dest = ui.expandpath(dest, repo.root)
+    dest = ui.expandpath(dest)
     other = hg.repository(ui, dest)
     o = repo.findoutgoing(other)
     o = repo.changelog.nodesbetween(o)[0]
@@ -1768,7 +1763,7 @@
         if n != nullid:
             show_changeset(ui, repo, changenode=n, brinfo=br)
 
-def paths(ui, search=None):
+def paths(ui, repo, search=None):
     """show definition of symbolic path names
 
     Show definition of symbolic path name NAME. If no name is given, show
@@ -1777,12 +1772,6 @@
     Path names are defined in the [paths] section of /etc/mercurial/hgrc
     and $HOME/.hgrc.  If run inside a repository, .hg/hgrc is used, too.
     """
-    try:
-        repo = hg.repository(ui)
-        ui = repo.ui
-    except hg.RepoError:
-        pass
-
     if search:
         for name, path in ui.configitems("paths"):
             if name == search:
@@ -1815,7 +1804,7 @@
     to the remote user's home directory by default; use two slashes at
     the start of a path to specify it as relative to the filesystem root.
     """
-    source = ui.expandpath(source, repo.root)
+    source = ui.expandpath(source)
     ui.status(_('pulling from %s\n') % (source))
 
     if opts['ssh']:
@@ -1860,7 +1849,7 @@
     SSH requires an accessible shell account on the destination
     machine and a copy of hg in the remote path.
     """
-    dest = ui.expandpath(dest, repo.root)
+    dest = ui.expandpath(dest)
     ui.status('pushing to %s\n' % (dest))
 
     if opts['ssh']:
@@ -1936,7 +1925,7 @@
     def okaytoremove(abs, rel, exact):
         modified, added, removed, deleted, unknown = repo.changes(files=[abs])
         reason = None
-        if modified:
+        if modified and not opts['force']:
             reason = _('is modified')
         elif added:
             reason = _('has been marked for add')
@@ -2443,6 +2432,7 @@
           ('X', 'exclude', [], _('exclude names matching the given patterns'))],
          _('hg copy [OPTION]... [SOURCE]... DEST')),
     "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
+    "debugcomplete": (debugcomplete, [], _('debugcomplete CMD')),
     "debugrebuildstate":
         (debugrebuildstate,
          [('r', 'rev', '', _('revision to rebuild to'))],
@@ -2579,7 +2569,8 @@
     "recover": (recover, [], _('hg recover')),
     "^remove|rm":
         (remove,
-         [('I', 'include', [], _('include names matching the given patterns')),
+         [('f', 'force', None, _('remove file even if modified')),
+          ('I', 'include', [], _('include names matching the given patterns')),
           ('X', 'exclude', [], _('exclude names matching the given patterns'))],
          _('hg remove [OPTION]... FILE...')),
     "rename|mv":
@@ -2589,7 +2580,7 @@
            _('forcibly copy over an existing managed file')),
           ('I', 'include', [], _('include names matching the given patterns')),
           ('X', 'exclude', [], _('exclude names matching the given patterns'))],
-         _('hg rename [OPTION]... [SOURCE]... DEST')),
+         _('hg rename [OPTION]... SOURCE... DEST')),
     "^revert":
         (revert,
          [('r', 'rev', '', _('revision to revert to')),
@@ -2658,7 +2649,8 @@
 }
 
 globalopts = [
-    ('R', 'repository', '', _('repository root directory')),
+    ('R', 'repository', '',
+     _('repository root directory or symbolic path name')),
     ('', 'cwd', '', _('change working directory')),
     ('y', 'noninteractive', None,
      _('do not prompt, assume \'yes\' for any required answers')),
@@ -2673,28 +2665,49 @@
     ('h', 'help', None, _('display help and exit')),
 ]
 
-norepo = ("clone init version help debugancestor debugconfig debugdata"
-          " debugindex debugindexdot paths")
-
-def find(cmd):
-    """Return (aliases, command table entry) for command string."""
-    choice = None
-    count = 0
+norepo = ("clone init version help debugancestor debugcomplete debugdata"
+          " debugindex debugindexdot")
+optionalrepo = ("paths debugconfig")
+
+def findpossible(cmd):
+    """
+    Return cmd -> (aliases, command table entry)
+    for each matching command
+    """
+    choice = {}
+    debugchoice = {}
     for e in table.keys():
         aliases = e.lstrip("^").split("|")
         if cmd in aliases:
-            return aliases, table[e]
+            choice[cmd] = (aliases, table[e])
+            continue
         for a in aliases:
             if a.startswith(cmd):
-                count += 1
-                choice = aliases, table[e]
+                if aliases[0].startswith("debug"):
+                    debugchoice[a] = (aliases, table[e])
+                else:
+                    choice[a] = (aliases, table[e])
                 break
 
-    if count > 1:
-        raise AmbiguousCommand(cmd)
+    if not choice and debugchoice:
+        choice = debugchoice
+
+    return choice
+
+def find(cmd):
+    """Return (aliases, command table entry) for command string."""
+    choice = findpossible(cmd)
+
+    if choice.has_key(cmd):
+        return choice[cmd]
+
+    if len(choice) > 1:
+        clist = choice.keys()
+        clist.sort()
+        raise AmbiguousCommand(cmd, clist)
 
     if choice:
-        return choice
+        return choice.values()[0]
 
     raise UnknownCommand(cmd)
 
@@ -2782,7 +2795,10 @@
                     mod = getattr(mod, comp)
                 return mod
             try:
-                mod = importh(x[0])
+                try:
+                    mod = importh("hgext." + x[0])
+                except ImportError:
+                    mod = importh(x[0])
             except Exception, inst:
                 on_exception(Exception, inst)
                 continue
@@ -2797,44 +2813,37 @@
 
     try:
         cmd, func, args, options, cmdoptions = parse(u, args)
-    except ParseError, inst:
-        if inst.args[0]:
-            u.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
-            help_(u, inst.args[0])
-        else:
-            u.warn(_("hg: %s\n") % inst.args[1])
-            help_(u, 'shortlist')
-        sys.exit(-1)
-    except AmbiguousCommand, inst:
-        u.warn(_("hg: command '%s' is ambiguous.\n") % inst.args[0])
-        sys.exit(1)
-    except UnknownCommand, inst:
-        u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
-        help_(u, 'shortlist')
-        sys.exit(1)
-
-    if options["time"]:
-        def get_times():
-            t = os.times()
-            if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
-                t = (t[0], t[1], t[2], t[3], time.clock())
-            return t
-        s = get_times()
-        def print_time():
-            t = get_times()
-            u.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
-                (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
-        atexit.register(print_time)
-
-    u.updateopts(options["verbose"], options["debug"], options["quiet"],
-              not options["noninteractive"])
-
-    # enter the debugger before command execution
-    if options['debugger']:
-        pdb.set_trace()
-
-    try:
+        if options["time"]:
+            def get_times():
+                t = os.times()
+                if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
+                    t = (t[0], t[1], t[2], t[3], time.clock())
+                return t
+            s = get_times()
+            def print_time():
+                t = get_times()
+                u.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
+                    (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
+            atexit.register(print_time)
+
+        u.updateopts(options["verbose"], options["debug"], options["quiet"],
+                not options["noninteractive"])
+
+        # enter the debugger before command execution
+        if options['debugger']:
+            pdb.set_trace()
+
         try:
+            if options['cwd']:
+                try:
+                    os.chdir(options['cwd'])
+                except OSError, inst:
+                    raise util.Abort('%s: %s' %
+                                     (options['cwd'], inst.strerror))
+
+            path = u.expandpath(options["repository"]) or ""
+            repo = path and hg.repository(u, path=path) or None
+
             if options['help']:
                 help_(u, cmd, options['version'])
                 sys.exit(0)
@@ -2845,20 +2854,17 @@
                 help_(u, 'shortlist')
                 sys.exit(0)
 
-            if options['cwd']:
+            if cmd not in norepo.split():
                 try:
-                    os.chdir(options['cwd'])
-                except OSError, inst:
-                    raise util.Abort('%s: %s' %
-                                     (options['cwd'], inst.strerror))
-
-            if cmd not in norepo.split():
-                path = options["repository"] or ""
-                repo = hg.repository(u, path=path)
-                u = repo.ui
-                for x in external:
-                    if hasattr(x, 'reposetup'):
-                        x.reposetup(u, repo)
+                    if not repo:
+                        repo = hg.repository(u, path=path)
+                    u = repo.ui
+                    for x in external:
+                        if hasattr(x, 'reposetup'):
+                            x.reposetup(u, repo)
+                except hg.RepoError:
+                    if cmd not in optionalrepo.split():
+                        raise
                 d = lambda: func(u, repo, *args, **cmdoptions)
             else:
                 d = lambda: func(u, *args, **cmdoptions)
@@ -2894,6 +2900,22 @@
             if options['traceback']:
                 traceback.print_exc()
             raise
+    except ParseError, inst:
+        if inst.args[0]:
+            u.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
+            help_(u, inst.args[0])
+        else:
+            u.warn(_("hg: %s\n") % inst.args[1])
+            help_(u, 'shortlist')
+        sys.exit(-1)
+    except AmbiguousCommand, inst:
+        u.warn(_("hg: command '%s' is ambiguous:\n    %s\n") % 
+                (inst.args[0], " ".join(inst.args[1])))
+        sys.exit(1)
+    except UnknownCommand, inst:
+        u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
+        help_(u, 'shortlist')
+        sys.exit(1)
     except hg.RepoError, inst:
         u.warn(_("abort: "), inst, "!\n")
     except revlog.RevlogError, inst:
@@ -2940,12 +2962,6 @@
         u.debug(inst, "\n")
         u.warn(_("%s: invalid arguments\n") % cmd)
         help_(u, cmd)
-    except AmbiguousCommand, inst:
-        u.warn(_("hg: command '%s' is ambiguous.\n") % inst.args[0])
-        help_(u, 'shortlist')
-    except UnknownCommand, inst:
-        u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
-        help_(u, 'shortlist')
     except SystemExit:
         # don't catch this in the catch-all below
         raise
--- a/mercurial/hgweb.py	Mon Mar 06 18:01:34 2006 +0100
+++ b/mercurial/hgweb.py	Mon Mar 13 12:22:55 2006 +0100
@@ -851,7 +851,7 @@
 
     def run(self, req=hgrequest()):
         def clean(path):
-            p = os.path.normpath(path)
+            p = util.normpath(path)
             if p[:2] == "..":
                 raise "suspicious path"
             return p
--- a/mercurial/httprepo.py	Mon Mar 06 18:01:34 2006 +0100
+++ b/mercurial/httprepo.py	Mon Mar 13 12:22:55 2006 +0100
@@ -67,6 +67,9 @@
     def dev(self):
         return -1
 
+    def lock(self):
+        raise util.Abort(_('operation not supported over http'))
+
     def do_cmd(self, cmd, **args):
         self.ui.debug(_("sending %s command\n") % cmd)
         q = {"cmd": cmd}
--- a/mercurial/localrepo.py	Mon Mar 06 18:01:34 2006 +0100
+++ b/mercurial/localrepo.py	Mon Mar 13 12:22:55 2006 +0100
@@ -47,35 +47,15 @@
 
         self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root)
         try:
-            self.ui.readconfig(self.join("hgrc"))
+            self.ui.readconfig(self.join("hgrc"), self.root)
         except IOError:
             pass
 
     def hook(self, name, throw=False, **args):
         def runhook(name, cmd):
             self.ui.note(_("running hook %s: %s\n") % (name, cmd))
-            old = {}
-            for k, v in args.items():
-                k = k.upper()
-                old['HG_' + k] = os.environ.get(k, None)
-                old[k] = os.environ.get(k, None)
-                os.environ['HG_' + k] = str(v)
-                os.environ[k] = str(v)
-
-            try:
-                # Hooks run in the repository root
-                olddir = os.getcwd()
-                os.chdir(self.root)
-                r = os.system(cmd)
-            finally:
-                for k, v in old.items():
-                    if v is not None:
-                        os.environ[k] = v
-                    else:
-                        del os.environ[k]
-
-                os.chdir(olddir)
-
+            env = dict([('HG_' + k.upper(), v) for k, v in args.iteritems()])
+            r = util.system(cmd, environ=env, cwd=self.root)
             if r:
                 desc, r = util.explain_exit(r)
                 if throw:
@@ -231,7 +211,7 @@
         self.opener("journal.dirstate", "w").write(ds)
 
         tr = transaction.transaction(self.ui.warn, self.opener,
-                                       self.join("journal"), 
+                                       self.join("journal"),
                                        aftertrans(self.path))
         self.transhandle = tr
         return tr
@@ -1654,14 +1634,18 @@
                     self.dirstate.update([f], 'n')
 
         # merge the tricky bits
+        failedmerge = []
         files = merge.keys()
         files.sort()
+        xp1 = hex(p1)
+        xp2 = hex(p2)
         for f in files:
             self.ui.status(_("merging %s\n") % f)
             my, other, flag = merge[f]
-            ret = self.merge3(f, my, other)
+            ret = self.merge3(f, my, other, xp1, xp2)
             if ret:
                 err = True
+                failedmerge.append(f)
             util.set_exec(self.wjoin(f), flag)
             if moddirstate:
                 if branch_merge:
@@ -1695,9 +1679,19 @@
 
         if moddirstate:
             self.dirstate.setparents(p1, p2)
+
+        stat = ((len(get), _("updated")),
+                (len(merge) - len(failedmerge), _("merged")),
+                (len(remove), _("removed")),
+                (len(failedmerge), _("unresolved")))
+        note = ", ".join([_("%d files %s") % s for s in stat])
+        self.ui.note("%s\n" % note)
+        if moddirstate and branch_merge:
+            self.ui.note(_("(branch merge, don't forget to commit)\n"))
+
         return err
 
-    def merge3(self, fn, my, other):
+    def merge3(self, fn, my, other, p1, p2):
         """perform a 3-way merge in the working directory"""
 
         def temp(prefix, node):
@@ -1720,7 +1714,13 @@
 
         cmd = (os.environ.get("HGMERGE") or self.ui.config("ui", "merge")
                or "hgmerge")
-        r = os.system('%s "%s" "%s" "%s"' % (cmd, a, b, c))
+        r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=self.root,
+                        environ={'HG_FILE': fn,
+                                 'HG_MY_NODE': p1,
+                                 'HG_OTHER_NODE': p2,
+                                 'HG_FILE_MY_NODE': hex(my),
+                                 'HG_FILE_OTHER_NODE': hex(other),
+                                 'HG_FILE_BASE_NODE': hex(base)})
         if r:
             self.ui.warn(_("merging %s failed!\n") % fn)
 
@@ -1771,6 +1771,7 @@
                 raise
             except Exception, inst:
                 err(_("unpacking changeset %s: %s") % (short(n), inst))
+                continue
 
             neededmanifests[changes[0]] = n
 
@@ -1808,10 +1809,14 @@
                 raise
             except Exception, inst:
                 err(_("unpacking manifest %s: %s") % (short(n), inst))
+                continue
 
-            ff = [ l.split('\0') for l in delta.splitlines() ]
-            for f, fn in ff:
-                filenodes.setdefault(f, {})[bin(fn[:40])] = 1
+            try:
+                ff = [ l.split('\0') for l in delta.splitlines() ]
+                for f, fn in ff:
+                    filenodes.setdefault(f, {})[bin(fn[:40])] = 1
+            except (ValueError, TypeError), inst:
+                err(_("broken delta in manifest %s: %s") % (short(n), inst))
 
         self.ui.status(_("crosschecking files in changesets and manifests\n"))
 
@@ -1835,6 +1840,9 @@
             if f == "/dev/null":
                 continue
             files += 1
+            if not f:
+                err(_("file without name in manifest %s") % short(n))
+                continue
             fl = self.file(f)
             checksize(fl, f)
 
@@ -1852,7 +1860,7 @@
                     del filenodes[f][n]
 
                 flr = fl.linkrev(n)
-                if flr not in filelinkrevs[f]:
+                if flr not in filelinkrevs.get(f, []):
                     err(_("%s:%s points to unexpected changeset %d")
                             % (f, short(n), flr))
                 else:
--- a/mercurial/lock.py	Mon Mar 06 18:01:34 2006 +0100
+++ b/mercurial/lock.py	Mon Mar 13 12:22:55 2006 +0100
@@ -6,7 +6,7 @@
 # of the GNU General Public License, incorporated herein by reference.
 
 from demandload import *
-demandload(globals(), 'errno os time util')
+demandload(globals(), 'errno os socket time util')
 
 class LockException(Exception):
     pass
@@ -16,11 +16,22 @@
     pass
 
 class lock(object):
+    # lock is symlink on platforms that support it, file on others.
+
+    # symlink is used because create of directory entry and contents
+    # are atomic even over nfs.
+
+    # old-style lock: symlink to pid
+    # new-style lock: symlink to hostname:pid
+
     def __init__(self, file, timeout=-1, releasefn=None):
         self.f = file
         self.held = 0
         self.timeout = timeout
         self.releasefn = releasefn
+        self.id = None
+        self.host = None
+        self.pid = None
         self.lock()
 
     def __del__(self):
@@ -41,15 +52,50 @@
                 raise inst
 
     def trylock(self):
-        pid = os.getpid()
+        if self.id is None:
+            self.host = socket.gethostname()
+            self.pid = os.getpid()
+            self.id = '%s:%s' % (self.host, self.pid)
+        while not self.held:
+            try:
+                util.makelock(self.id, self.f)
+                self.held = 1
+            except (OSError, IOError), why:
+                if why.errno == errno.EEXIST:
+                    locker = self.testlock()
+                    if locker:
+                        raise LockHeld(locker)
+                else:
+                    raise LockUnavailable(why)
+
+    def testlock(self):
+        '''return id of locker if lock is valid, else None.'''
+        # if old-style lock, we cannot tell what machine locker is on.
+        # with new-style lock, if locker is on this machine, we can
+        # see if locker is alive.  if locker is on this machine but
+        # not alive, we can safely break lock.
+        locker = util.readlock(self.f)
+        c = locker.find(':')
+        if c == -1:
+            return locker
+        host = locker[:c]
+        if host != self.host:
+            return locker
         try:
-            util.makelock(str(pid), self.f)
-            self.held = 1
-        except (OSError, IOError), why:
-            if why.errno == errno.EEXIST:
-                raise LockHeld(util.readlock(self.f))
-            else:
-                raise LockUnavailable(why)
+            pid = int(locker[c+1:])
+        except:
+            return locker
+        if util.testpid(pid):
+            return locker
+        # if locker dead, break lock.  must do this with another lock
+        # held, or can race and break valid lock.
+        try:
+            l = lock(self.f + '.break')
+            l.trylock()
+            os.unlink(self.f)
+            l.release()
+        except (LockHeld, LockUnavailable):
+            return locker
 
     def release(self):
         if self.held:
--- a/mercurial/packagescan.py	Mon Mar 06 18:01:34 2006 +0100
+++ b/mercurial/packagescan.py	Mon Mar 13 12:22:55 2006 +0100
@@ -16,8 +16,14 @@
     """ fake demandload function that collects the required modules """
     for m in modules.split():
         mod = None
-        mod = __import__(m,scope,scope)
-        scope[m] = mod
+        try:
+            module, submodules = m.split(':')
+            submodules = submodules.split(',')
+        except:
+            module = m
+            submodules = []
+        mod = __import__(module, scope, scope, submodules)
+        scope[module] = mod
         requiredmodules[mod.__name__] = 1
 
 def getmodules(libpath,packagename):
--- a/mercurial/revlog.py	Mon Mar 06 18:01:34 2006 +0100
+++ b/mercurial/revlog.py	Mon Mar 13 12:22:55 2006 +0100
@@ -48,7 +48,7 @@
     if t == '\0': return bin
     if t == 'x': return zlib.decompress(bin)
     if t == 'u': return bin[1:]
-    raise RevlogError(_("unknown compression type %s") % t)
+    raise RevlogError(_("unknown compression type %r") % t)
 
 indexformat = ">4l20s20s20s"
 
--- a/mercurial/ui.py	Mon Mar 06 18:01:34 2006 +0100
+++ b/mercurial/ui.py	Mon Mar 13 12:22:55 2006 +0100
@@ -5,18 +5,19 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-import os, ConfigParser
+import ConfigParser
 from i18n import gettext as _
 from demandload import *
-demandload(globals(), "re socket sys util")
+demandload(globals(), "os re socket sys util")
 
 class ui(object):
     def __init__(self, verbose=False, debug=False, quiet=False,
                  interactive=True, parentui=None):
         self.overlay = {}
-        self.cdata = ConfigParser.SafeConfigParser()
-        self.parentui = parentui and parentui.parentui or parentui
         if parentui is None:
+            # this is the parent of all ui children
+            self.parentui = None
+            self.cdata = ConfigParser.SafeConfigParser()
             self.readconfig(util.rcpath)
 
             self.quiet = self.configbool("ui", "quiet")
@@ -26,6 +27,16 @@
 
             self.updateopts(verbose, debug, quiet, interactive)
             self.diffcache = None
+        else:
+            # parentui may point to an ui object which is already a child
+            self.parentui = parentui.parentui or parentui
+            parent_cdata = self.parentui.cdata
+            self.cdata = ConfigParser.SafeConfigParser(parent_cdata.defaults())
+            # make interpolation work
+            for section in parent_cdata.sections():
+                self.cdata.add_section(section)
+                for name, value in parent_cdata.items(section, raw=True):
+                    self.cdata.set(section, name, value)
 
     def __getattr__(self, key):
         return getattr(self.parentui, key)
@@ -37,7 +48,7 @@
         self.debugflag = (self.debugflag or debug)
         self.interactive = (self.interactive and interactive)
 
-    def readconfig(self, fn):
+    def readconfig(self, fn, root=None):
         if isinstance(fn, basestring):
             fn = [fn]
         for f in fn:
@@ -45,6 +56,12 @@
                 self.cdata.read(f)
             except ConfigParser.ParsingError, inst:
                 raise util.Abort(_("Failed to parse %s\n%s") % (f, inst))
+        # translate paths relative to root (or home) into absolute paths
+        if root is None:
+            root = os.path.expanduser('~')
+        for name, path in self.configitems("paths"):
+            if path and path.find("://") == -1 and not os.path.isabs(path):
+                self.cdata.set("paths", name, os.path.join(root, path))
 
     def setconfig(self, section, name, val):
         self.overlay[(section, name)] = val
@@ -53,7 +70,10 @@
         if self.overlay.has_key((section, name)):
             return self.overlay[(section, name)]
         if self.cdata.has_option(section, name):
-            return self.cdata.get(section, name)
+            try:
+                return self.cdata.get(section, name)
+            except ConfigParser.InterpolationError, inst:
+                raise util.Abort(_("Error in configuration:\n%s") % inst)
         if self.parentui is None:
             return default
         else:
@@ -63,7 +83,10 @@
         if self.overlay.has_key((section, name)):
             return self.overlay[(section, name)]
         if self.cdata.has_option(section, name):
-            return self.cdata.getboolean(section, name)
+            try:
+                return self.cdata.getboolean(section, name)
+            except ConfigParser.InterpolationError, inst:
+                raise util.Abort(_("Error in configuration:\n%s") % inst)
         if self.parentui is None:
             return default
         else:
@@ -74,7 +97,10 @@
         if self.parentui is not None:
             items = dict(self.parentui.configitems(section))
         if self.cdata.has_section(section):
-            items.update(dict(self.cdata.items(section)))
+            try:
+                items.update(dict(self.cdata.items(section)))
+            except ConfigParser.InterpolationError, inst:
+                raise util.Abort(_("Error in configuration:\n%s") % inst)
         x = items.items()
         x.sort()
         return x
@@ -133,15 +159,12 @@
                 user = user[f+1:]
         return user
 
-    def expandpath(self, loc, root=""):
-        paths = {}
-        for name, path in self.configitems("paths"):
-            m = path.find("://")
-            if m == -1:
-                    path = os.path.join(root, path)
-            paths[name] = path
+    def expandpath(self, loc):
+        """Return repository location relative to cwd or from [paths]"""
+        if loc.find("://") != -1 or os.path.exists(loc):
+            return loc
 
-        return paths.get(loc, loc)
+        return self.config("paths", loc, loc)
 
     def write(self, *args):
         for a in args:
@@ -189,7 +212,9 @@
                   os.environ.get("EDITOR", "vi"))
 
         os.environ["HGUSER"] = self.username()
-        util.system("%s \"%s\"" % (editor, name), errprefix=_("edit failed"))
+        util.system("%s \"%s\"" % (editor, name),
+                    environ={'HGUSER': self.username()},
+                    onerr=util.Abort, errprefix=_("edit failed"))
 
         t = open(name).read()
         t = re.sub("(?m)^HG:.*\n", "", t)
@@ -197,4 +222,3 @@
         os.unlink(name)
 
         return t
-
--- a/mercurial/util.py	Mon Mar 06 18:01:34 2006 +0100
+++ b/mercurial/util.py	Mon Mar 13 12:22:55 2006 +0100
@@ -315,15 +315,42 @@
                          (files and filematch(fn)))),
             (inc or exc or (pats and pats != [('glob', '**')])) and True)
 
-def system(cmd, errprefix=None):
-    """execute a shell command that must succeed"""
-    rc = os.system(cmd)
-    if rc:
-        errmsg = "%s %s" % (os.path.basename(cmd.split(None, 1)[0]),
-                            explain_exit(rc)[0])
-        if errprefix:
-            errmsg = "%s: %s" % (errprefix, errmsg)
-        raise Abort(errmsg)
+def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
+    '''enhanced shell command execution.
+    run with environment maybe modified, maybe in different dir.
+
+    if command fails and onerr is None, return status.  if ui object,
+    print error message and return status, else raise onerr object as
+    exception.'''
+    oldenv = {}
+    for k in environ:
+        oldenv[k] = os.environ.get(k)
+    if cwd is not None:
+        oldcwd = os.getcwd()
+    try:
+        for k, v in environ.iteritems():
+            os.environ[k] = str(v)
+        if cwd is not None and oldcwd != cwd:
+            os.chdir(cwd)
+        rc = os.system(cmd)
+        if rc and onerr:
+            errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
+                                explain_exit(rc)[0])
+            if errprefix:
+                errmsg = '%s: %s' % (errprefix, errmsg)
+            try:
+                onerr.warn(errmsg + '\n')
+            except AttributeError:
+                raise onerr(errmsg)
+        return rc
+    finally:
+        for k, v in oldenv.iteritems():
+            if v is None:
+                del os.environ[k]
+            else:
+                os.environ[k] = v
+        if cwd is not None and oldcwd != cwd:
+            os.chdir(oldcwd)
 
 def rename(src, dst):
     """forcibly rename a file"""
@@ -499,7 +526,7 @@
         return pf
 
     try: # ActivePython can create hard links using win32file module
-        import win32file
+        import win32api, win32con, win32file
 
         def os_link(src, dst): # NB will only succeed on NTFS
             win32file.CreateHardLink(dst, src)
@@ -516,8 +543,18 @@
             except:
                 return os.stat(pathname).st_nlink
 
+        def testpid(pid):
+            '''return False if pid is dead, True if running or not known'''
+            try:
+                win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION,
+                                     False, pid)
+            except:
+                return True
+
     except ImportError:
-        pass
+        def testpid(pid):
+            '''return False if pid dead, True if running or not known'''
+            return True
 
     def is_exec(f, last):
         return last
@@ -614,6 +651,14 @@
             else:
                 raise
 
+    def testpid(pid):
+        '''return False if pid dead, True if running or not sure'''
+        try:
+            os.kill(pid, 0)
+            return True
+        except OSError, inst:
+            return inst.errno != errno.ESRCH
+
     def explain_exit(code):
         """return a 2-tuple (desc, code) describing a process's status"""
         if os.WIFEXITED(code):
--- a/setup.py	Mon Mar 06 18:01:34 2006 +0100
+++ b/setup.py	Mon Mar 13 12:22:55 2006 +0100
@@ -5,8 +5,11 @@
 # './setup.py install', or
 # './setup.py --help' for more options
 
+import sys
+if not hasattr(sys, 'version_info') or sys.version_info < (2, 3):
+    raise SystemExit, "Mercurial requires python 2.3 or later."
+
 import glob
-import sys
 from distutils.core import setup, Extension
 from distutils.command.install_data import install_data
 
--- a/tests/run-tests	Mon Mar 06 18:01:34 2006 +0100
+++ b/tests/run-tests	Mon Mar 13 12:22:55 2006 +0100
@@ -57,7 +57,16 @@
 fi
 cd "$TESTDIR"
 
-PATH="$INST/bin:$PATH"; export PATH
+BINDIR="$INST/bin"
+PATH="$BINDIR:$PATH"; export PATH
+if [ -n "$PYTHON" ]; then
+    {
+        echo "#!/bin/sh"
+        echo "exec \"$PYTHON"'" "$@"'
+    } > "$BINDIR/python"
+    chmod 755 "$BINDIR/python"
+fi
+
 PYTHONPATH="$PYTHONDIR"; export PYTHONPATH
 
 run_one() {
--- a/tests/test-clone-r	Mon Mar 06 18:01:34 2006 +0100
+++ b/tests/test-clone-r	Mon Mar 13 12:22:55 2006 +0100
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/bin/sh
 
 hg init test
 cd test
--- a/tests/test-filebranch.out	Mon Mar 06 18:01:34 2006 +0100
+++ b/tests/test-filebranch.out	Mon Mar 13 12:22:55 2006 +0100
@@ -19,6 +19,8 @@
 getting bar
 merging foo
 resolving foo
+1 files updated, 1 files merged, 0 files removed, 0 files unresolved
+(branch merge, don't forget to commit)
 we shouldn't have anything but foo in merge state here
 m 644          3 foo
 main: we should have a merge here
--- a/tests/test-flags.out	Mon Mar 06 18:01:34 2006 +0100
+++ b/tests/test-flags.out	Mon Mar 13 12:22:55 2006 +0100
@@ -44,5 +44,7 @@
 resolving manifests
 merging a
 resolving a
+0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+(branch merge, don't forget to commit)
 -rwxr-x---
 -rwxr-x---
--- a/tests/test-merge7.out	Mon Mar 06 18:01:34 2006 +0100
+++ b/tests/test-merge7.out	Mon Mar 13 12:22:55 2006 +0100
@@ -24,6 +24,8 @@
 resolving test.txt
 file test.txt: my fc3148072371 other d40249267ae3 ancestor 8fe46a3eb557
 merging test.txt failed!
+0 files updated, 0 files merged, 0 files removed, 1 files unresolved
+(branch merge, don't forget to commit)
 one
 <<<<<<<
 two-point-five
--- a/tests/test-push-r	Mon Mar 06 18:01:34 2006 +0100
+++ b/tests/test-push-r	Mon Mar 13 12:22:55 2006 +0100
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/bin/sh
 
 hg init test
 cd test
--- a/tests/test-up-local-change.out	Mon Mar 06 18:01:34 2006 +0100
+++ b/tests/test-up-local-change.out	Mon Mar 13 12:22:55 2006 +0100
@@ -21,6 +21,7 @@
 merging a
 resolving a
 file a: my b789fdd96dc2 other d730145abbf9 ancestor b789fdd96dc2
+1 files updated, 1 files merged, 0 files removed, 0 files unresolved
 changeset:   1:1e71731e6fbb
 tag:         tip
 user:        test
@@ -32,6 +33,7 @@
  ancestor a0c8bcbbb45c local 1165e8bd193e remote a0c8bcbbb45c
 remote deleted b
 removing b
+0 files updated, 0 files merged, 1 files removed, 0 files unresolved
 changeset:   0:c19d34741b0a
 user:        test
 date:        Thu Jan  1 00:00:00 1970 +0000
@@ -53,6 +55,7 @@
 merging a
 resolving a
 file a: my b789fdd96dc2 other d730145abbf9 ancestor b789fdd96dc2
+1 files updated, 1 files merged, 0 files removed, 0 files unresolved
 changeset:   1:1e71731e6fbb
 tag:         tip
 user:        test
@@ -113,6 +116,8 @@
 merging b
 resolving b
 file b: my 1e88685f5dde other 61de8c7723ca ancestor 000000000000
+0 files updated, 2 files merged, 0 files removed, 0 files unresolved
+(branch merge, don't forget to commit)
 changeset:   1:1e71731e6fbb
 user:        test
 date:        Thu Jan  1 00:00:00 1970 +0000
--- a/tests/test-update-reverse.out	Mon Mar 06 18:01:34 2006 +0100
+++ b/tests/test-update-reverse.out	Mon Mar 13 12:22:55 2006 +0100
@@ -47,6 +47,7 @@
 getting main
 removing side1
 removing side2
+1 files updated, 0 files merged, 2 files removed, 0 files unresolved
 Should only show a main
 a
 main