changeset 1779:a1e6e02e9d05

Merge with mpm
author Josef "Jeff" Sipek <jeffpc@optonline.net>
date Sat, 18 Feb 2006 22:24:42 -0500
parents b08b87cecc37 (current diff) 57de7e1a81d2 (diff)
children 8a1f2eae2832
files mercurial/hgweb.py
diffstat 17 files changed, 535 insertions(+), 121 deletions(-) [+]
line wrap: on
line diff
--- a/doc/hg.1.txt	Sat Feb 18 22:01:09 2006 -0500
+++ b/doc/hg.1.txt	Sat Feb 18 22:24:42 2006 -0500
@@ -223,9 +223,11 @@
     probably with undesirable results.
 
     options:
-    -a, --text           treat all files as text
-    -I, --include <pat>  include names matching the given patterns
-    -X, --exclude <pat>  exclude names matching the given patterns
+    -a, --text              treat all files as text
+    -I, --include <pat>     include names matching the given patterns
+    -p, --show-function     show which function each change is in
+    -X, --exclude <pat>     exclude names matching the given patterns
+    -w, --ignore-all-space  ignore white space when comparing lines
 
 export [-o filespec] [revision] ...::
     Print the changeset header and diffs for one or more revisions.
@@ -600,9 +602,12 @@
 
     This lists both regular and local tags.
 
-tip::
+tip [-p]::
     Show the tip revision.
 
+    options:
+    -p, --patch      show patch
+
 unbundle <file>::
     (EXPERIMENTAL)
 
--- a/doc/hgrc.5.txt	Sat Feb 18 22:01:09 2006 -0500
+++ b/doc/hgrc.5.txt	Sat Feb 18 22:24:42 2006 -0500
@@ -145,20 +145,71 @@
     incoming.email = /my/email/hook
     incoming.autobuild = /my/build/hook
 
+  Most hooks are run with environment variables set that give added
+  useful information.  For each hook below, the environment variables
+  it is passed are listed with names of the form "$HG_foo".
+
   changegroup;;
-    Run after a changegroup has been added via push or pull. Passed
-    the ID of the first new changeset in $NODE.
+    Run after a changegroup has been added via push, pull or
+    unbundle. ID of the first new changeset is in $HG_NODE.
   commit;;
     Run after a changeset has been created in the local repository.
-    Passed the ID of the newly created changeset in environment
-    variable $NODE.
+    ID of the newly created changeset is in $HG_NODE.  Parent
+    changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
   incoming;;
     Run after a changeset has been pulled, pushed, or unbundled into
-    the local repository.  Passed the ID of the newly arrived
-    changeset in environment variable $NODE.
+    the local repository.  The ID of the newly arrived changeset is in
+    $HG_NODE.
+  outgoing;;
+    Run after sending changes from local repository to another.  ID of
+    first changeset sent is in $HG_NODE.  Source of operation is in
+    $HG_SOURCE; see "preoutgoing" hook for description.
+  prechangegroup;;
+    Run before a changegroup is added via push, pull or unbundle.
+    Exit status 0 allows the changegroup to proceed.  Non-zero status
+    will cause the push, pull or unbundle to fail.
   precommit;;
-    Run before starting a commit.  Exit status 0 allows the commit to
-    proceed.  Non-zero status will cause the commit to fail.
+    Run before starting a local commit.  Exit status 0 allows the
+    commit to proceed.  Non-zero status will cause the commit to fail.
+    Parent changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
+  preoutgoing;;
+    Run before computing changes to send from the local repository to
+    another.  Non-zero status will cause failure.  This lets you
+    prevent pull over http or ssh.  Also prevents against local pull,
+    push (outbound) or bundle commands, but not effective, since you
+    can just copy files instead then.  Source of operation is in
+    $HG_SOURCE.  If "serve", operation is happening on behalf of
+    remote ssh or http repository.  If "push", "pull" or "bundle",
+    operation is happening on behalf of repository on same system.
+  pretag;;
+    Run before creating a tag.  Exit status 0 allows the tag to be
+    created.  Non-zero status will cause the tag to fail.  ID of
+    changeset to tag is in $HG_NODE.  Name of tag is in $HG_TAG.  Tag
+    is local if $HG_LOCAL=1, in repo if $HG_LOCAL=0.
+  pretxnchangegroup;;
+    Run after a changegroup has been added via push, pull or unbundle,
+    but before the transaction has been committed.  Changegroup is
+    visible to hook program.  This lets you validate incoming changes
+    before accepting them.  Passed the ID of the first new changeset
+    in $HG_NODE.  Exit status 0 allows the transaction to commit.
+    Non-zero status will cause the transaction to be rolled back and
+    the push, pull or unbundle will fail.
+  pretxncommit;;
+    Run after a changeset has been created but the transaction not yet
+    committed.  Changeset is visible to hook program.  This lets you
+    validate commit message and changes.  Exit status 0 allows the
+    commit to proceed.  Non-zero status will cause the transaction to
+    be rolled back.  ID of changeset is in $HG_NODE.  Parent changeset
+    IDs are in $HG_PARENT1 and $HG_PARENT2.
+  tag;;
+    Run after a tag is created.  ID of tagged changeset is in
+    $HG_NODE.  Name of tag is in $HG_TAG.  Tag is local if
+    $HG_LOCAL=1, in repo if $HG_LOCAL=0.
+
+  In earlier releases, the names of hook environment variables did not
+  have a "HG_" prefix.  These unprefixed names are still provided in
+  the environment for backwards compatibility, but their use is
+  deprecated, and they will be removed in a future release.
 
 http_proxy::
   Used to access web-based Mercurial repositories through a HTTP
--- a/mercurial/commands.py	Sat Feb 18 22:01:09 2006 -0500
+++ b/mercurial/commands.py	Sat Feb 18 22:24:42 2006 -0500
@@ -261,7 +261,7 @@
                 mode)
 
 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
-           changes=None, text=False):
+           changes=None, text=False, opts={}):
     if not changes:
         changes = repo.changes(node1, node2, files, match=match)
     modified, added, removed, deleted, unknown = changes
@@ -296,8 +296,8 @@
     date1 = util.datestr(change[2])
 
     diffopts = ui.diffopts()
-    showfunc = diffopts['showfunc']
-    ignorews = diffopts['ignorews']
+    showfunc = opts.get('show_function') or diffopts['showfunc']
+    ignorews = opts.get('ignore_all_space') or diffopts['ignorews']
     for f in modified:
         to = None
         if f in mmap:
@@ -622,7 +622,7 @@
     dest = ui.expandpath(dest, repo.root)
     other = hg.repository(ui, dest)
     o = repo.findoutgoing(other)
-    cg = repo.changegroup(o)
+    cg = repo.changegroup(o, 'bundle')
 
     try:
         f.write("HG10")
@@ -1140,7 +1140,7 @@
     fns, matchfn, anypats = matchpats(repo, pats, opts)
 
     dodiff(sys.stdout, ui, repo, node1, node2, fns, match=matchfn,
-           text=opts['text'])
+           text=opts['text'], opts=opts)
 
 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
     node = repo.lookup(changeset)
@@ -1681,7 +1681,7 @@
             dodiff(ui, ui, repo, prev, n)
             ui.write("\n")
 
-def parents(ui, repo, rev=None):
+def parents(ui, repo, rev=None, branch=None):
     """show the parents of the working dir or revision
 
     Print the working directory's parent revisions.
@@ -1691,9 +1691,12 @@
     else:
         p = repo.dirstate.parents()
 
+    br = None
+    if branch is not None:
+        br = repo.branchlookup(p)
     for n in p:
         if n != nullid:
-            show_changeset(ui, repo, changenode=n)
+            show_changeset(ui, repo, changenode=n, brinfo=br)
 
 def paths(ui, search=None):
     """show definition of symbolic path names
@@ -1996,7 +1999,7 @@
                 arg, roots = getarg()
                 nodes = map(bin, roots.split(" "))
 
-                cg = repo.changegroup(nodes)
+                cg = repo.changegroup(nodes, 'serve')
                 while 1:
                     d = cg.read(4096)
                     if not d:
@@ -2113,8 +2116,12 @@
         if name.find(c) >= 0:
             raise util.Abort(_("%s cannot be used in a tag name") % repr(c))
 
+    repo.hook('pretag', throw=True, node=r, tag=name,
+              local=int(not not opts['local']))
+
     if opts['local']:
         repo.opener("localtags", "a").write("%s %s\n" % (r, name))
+        repo.hook('tag', node=r, tag=name, local=1)
         return
 
     for x in repo.changes():
@@ -2130,6 +2137,7 @@
                _("Added tag %s for changeset %s") % (name, r))
     try:
         repo.commit([".hgtags"], message, opts['user'], opts['date'])
+        repo.hook('tag', node=r, tag=name, local=0)
     except ValueError, inst:
         raise util.Abort(str(inst))
 
@@ -2150,13 +2158,15 @@
             r = "    ?:?"
         ui.write("%-30s %s\n" % (t, r))
 
-def tip(ui, repo):
+def tip(ui, repo, **opts):
     """show the tip revision
 
     Show the tip revision.
     """
     n = repo.changelog.tip()
     show_changeset(ui, repo, changenode=n)
+    if opts['patch']:
+        dodiff(ui, ui, repo, repo.changelog.parents(n)[0], n)
 
 def unbundle(ui, repo, fname, **opts):
     """apply a changegroup file
@@ -2332,7 +2342,12 @@
          [('r', 'rev', [], _('revision')),
           ('a', 'text', None, _('treat all files as text')),
           ('I', 'include', [], _('include names matching the given patterns')),
-          ('X', 'exclude', [], _('exclude names matching the given patterns'))],
+          ('p', 'show-function', None,
+           _('show which function each change is in')),
+          ('w', 'ignore-all-space', None,
+           _('ignore white space when comparing lines')),
+          ('X', 'exclude', [],
+           _('exclude names matching the given patterns'))],
          _('hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...')),
     "^export":
         (export,
@@ -2407,7 +2422,10 @@
           ('p', 'patch', None, _('show patch')),
           ('n', 'newest-first', None, _('show newest record first'))],
          _('hg outgoing [-p] [-n] [-M] [DEST]')),
-    "^parents": (parents, [], _('hg parents [REV]')),
+    "^parents":
+        (parents,
+         [('b', 'branch', None, _('show branches'))],
+         _('hg parents [-b] [REV]')),
     "paths": (paths, [], _('hg paths [NAME]')),
     "^pull":
         (pull,
@@ -2490,7 +2508,7 @@
           ('r', 'rev', '', _('revision to tag'))],
          _('hg tag [-r REV] [OPTION]... NAME')),
     "tags": (tags, [], _('hg tags')),
-    "tip": (tip, [], _('hg tip')),
+    "tip": (tip, [('p', 'patch', None, _('show patch'))], _('hg tip')),
     "unbundle":
         (unbundle,
          [('u', 'update', None,
@@ -2530,17 +2548,20 @@
 def find(cmd):
     """Return (aliases, command table entry) for command string."""
     choice = None
+    count = 0
     for e in table.keys():
         aliases = e.lstrip("^").split("|")
         if cmd in aliases:
             return aliases, table[e]
         for a in aliases:
             if a.startswith(cmd):
-                if choice:
-                    raise AmbiguousCommand(cmd)
-                else:
-                    choice = aliases, table[e]
-                    break
+                count += 1
+                choice = aliases, table[e]
+                break
+
+    if count > 1:
+        raise AmbiguousCommand(cmd)
+
     if choice:
         return choice
 
--- a/mercurial/hgweb.py	Sat Feb 18 22:01:09 2006 -0500
+++ b/mercurial/hgweb.py	Sat Feb 18 22:24:42 2006 -0500
@@ -965,7 +965,7 @@
                 nodes = map(bin, req.form['roots'][0].split(" "))
 
             z = zlib.compressobj()
-            f = self.repo.changegroup(nodes)
+            f = self.repo.changegroup(nodes, 'serve')
             while 1:
                 chunk = f.read(4096)
                 if not chunk:
--- a/mercurial/httprepo.py	Sat Feb 18 22:01:09 2006 -0500
+++ b/mercurial/httprepo.py	Sat Feb 18 22:24:42 2006 -0500
@@ -119,7 +119,7 @@
             self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
             raise
 
-    def changegroup(self, nodes):
+    def changegroup(self, nodes, kind):
         n = " ".join(map(hex, nodes))
         f = self.do_cmd("changegroup", roots=n)
         bytes = 0
--- a/mercurial/localrepo.py	Sat Feb 18 22:01:09 2006 -0500
+++ b/mercurial/localrepo.py	Sat Feb 18 22:24:42 2006 -0500
@@ -48,30 +48,36 @@
         except IOError:
             pass
 
-    def hook(self, name, **args):
+    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[k] = v
+                os.environ['HG_' + k] = str(v)
+                os.environ[k] = str(v)
 
-            # Hooks run in the repository root
-            olddir = os.getcwd()
-            os.chdir(self.root)
-            r = os.system(cmd)
-            os.chdir(olddir)
+            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]
 
-            for k, v in old.items():
-                if v != None:
-                    os.environ[k] = v
-                else:
-                    del os.environ[k]
+                os.chdir(olddir)
 
             if r:
-                self.ui.warn(_("abort: %s hook failed with status %d!\n") %
-                             (name, r))
+                desc, r = util.explain_exit(r)
+                if throw:
+                    raise util.Abort(_('%s hook %s') % (name, desc))
+                self.ui.warn(_('error: %s hook %s\n') % (name, desc))
                 return False
             return True
 
@@ -268,6 +274,25 @@
         self.dirstate.read()
         return wlock
 
+    def checkfilemerge(self, filename, text, filelog, manifest1, manifest2):
+        "determine whether a new filenode is needed"
+        fp1 = manifest1.get(filename, nullid)
+        fp2 = manifest2.get(filename, nullid)
+
+        if fp2 != nullid:
+            # is one parent an ancestor of the other?
+            fpa = filelog.ancestor(fp1, fp2)
+            if fpa == fp1:
+                fp1, fp2 = fp2, nullid
+            elif fpa == fp2:
+                fp2 = nullid
+
+            # is the file unmodified from the parent? report existing entry
+            if fp2 == nullid and text == filelog.read(fp1):
+                return (fp1, None, None)
+
+        return (None, fp1, fp2)
+
     def rawcommit(self, files, text, user, date, p1=None, p2=None, wlock=None):
         orig_parent = self.dirstate.parents()[0] or nullid
         p1 = p1 or self.dirstate.parents()[0] or nullid
@@ -298,27 +323,10 @@
                 r = self.file(f)
                 mfm[f] = tm
 
-                fp1 = m1.get(f, nullid)
-                fp2 = m2.get(f, nullid)
-
-                # is the same revision on two branches of a merge?
-                if fp2 == fp1:
-                    fp2 = nullid
-
-                if fp2 != nullid:
-                    # is one parent an ancestor of the other?
-                    fpa = r.ancestor(fp1, fp2)
-                    if fpa == fp1:
-                        fp1, fp2 = fp2, nullid
-                    elif fpa == fp2:
-                        fp2 = nullid
-
-                    # is the file unmodified from the parent?
-                    if t == r.read(fp1):
-                        # record the proper existing parent in manifest
-                        # no need to add a revision
-                        mm[f] = fp1
-                        continue
+                (entry, fp1, fp2) = self.checkfilemerge(f, t, r, m1, m2)
+                if entry:
+                    mm[f] = entry
+                    continue
 
                 mm[f] = r.add(t, {}, tr, linkrev, fp1, fp2)
                 changed.append(f)
@@ -372,8 +380,11 @@
             self.ui.status(_("nothing changed\n"))
             return None
 
-        if not self.hook("precommit"):
-            return None
+        xp1 = hex(p1)
+        if p2 == nullid: xp2 = ''
+        else: xp2 = hex(p2)
+
+        self.hook("precommit", throw=True, parent1=xp1, parent2=xp2)
 
         if not wlock:
             wlock = self.wlock()
@@ -403,22 +414,9 @@
                 self.ui.debug(_(" %s: copy %s:%s\n") % (f, cp, meta["copyrev"]))
                 fp1, fp2 = nullid, nullid
             else:
-                fp1 = m1.get(f, nullid)
-                fp2 = m2.get(f, nullid)
-
-            if fp2 != nullid:
-                # is one parent an ancestor of the other?
-                fpa = r.ancestor(fp1, fp2)
-                if fpa == fp1:
-                    fp1, fp2 = fp2, nullid
-                elif fpa == fp2:
-                    fp2 = nullid
-
-                # is the file unmodified from the parent?
-                if not meta and t == r.read(fp1) and fp2 == nullid:
-                    # record the proper existing parent in manifest
-                    # no need to add a revision
-                    new[f] = fp1
+                entry, fp1, fp2 = self.checkfilemerge(f, t, r, m1, m2)
+                if entry:
+                    new[f] = entry
                     continue
 
             new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
@@ -459,14 +457,15 @@
 
         user = user or self.ui.username()
         n = self.changelog.add(mn, changed + remove, text, tr, p1, p2, user, date)
+        self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
+                  parent2=xp2)
         tr.close()
 
         self.dirstate.setparents(n)
         self.dirstate.update(new, "n")
         self.dirstate.forget(remove)
 
-        if not self.hook("commit", node=hex(n)):
-            return None
+        self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2)
         return n
 
     def walk(self, node=None, files=[], match=util.always):
@@ -947,9 +946,9 @@
             return 1
 
         if heads is None:
-            cg = remote.changegroup(fetch)
+            cg = remote.changegroup(fetch, 'pull')
         else:
-            cg = remote.changegroupsubset(fetch, heads)
+            cg = remote.changegroupsubset(fetch, heads, 'pull')
         return self.addchangegroup(cg)
 
     def push(self, remote, force=False):
@@ -974,10 +973,10 @@
                                  " use push -f to force)\n"))
                 return 1
 
-        cg = self.changegroup(update)
+        cg = self.changegroup(update, 'push')
         return remote.addchangegroup(cg)
 
-    def changegroupsubset(self, bases, heads):
+    def changegroupsubset(self, bases, heads, source):
         """This function generates a changegroup consisting of all the nodes
         that are descendents of any of the bases, and ancestors of any of
         the heads.
@@ -989,6 +988,8 @@
         Another wrinkle is doing the reverse, figuring out which changeset in
         the changegroup a particular filenode or manifestnode belongs to."""
 
+        self.hook('preoutgoing', throw=True, source=source)
+
         # Set up some initial variables
         # Make it easy to refer to self.changelog
         cl = self.changelog
@@ -1241,14 +1242,19 @@
             # Signal that no more groups are left.
             yield struct.pack(">l", 0)
 
+            self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source)
+
         return util.chunkbuffer(gengroup())
 
-    def changegroup(self, basenodes):
+    def changegroup(self, basenodes, source):
         """Generate a changegroup of all nodes that we have that a recipient
         doesn't.
 
         This is much easier than the previous function as we can assume that
         the recipient has any changenode we aren't sending them."""
+
+        self.hook('preoutgoing', throw=True, source=source)
+
         cl = self.changelog
         nodes = cl.nodesbetween(basenodes, None)[0]
         revset = dict.fromkeys([cl.rev(n) for n in nodes])
@@ -1300,6 +1306,7 @@
                         yield chnk
 
             yield struct.pack(">l", 0)
+            self.hook('outgoing', node=hex(nodes[0]), source=source)
 
         return util.chunkbuffer(gengroup())
 
@@ -1335,6 +1342,9 @@
 
         if not source:
             return
+
+        self.hook('prechangegroup', throw=True)
+
         changesets = files = revisions = 0
 
         tr = self.transaction()
@@ -1377,19 +1387,17 @@
                          " with %d changes to %d files%s\n")
                          % (changesets, revisions, files, heads))
 
+        self.hook('pretxnchangegroup', throw=True,
+                  node=hex(self.changelog.node(cor+1)))
+
         tr.close()
 
         if changesets > 0:
-            if not self.hook("changegroup",
-                             node=hex(self.changelog.node(cor+1))):
-                self.ui.warn(_("abort: changegroup hook returned failure!\n"))
-                return 1
+            self.hook("changegroup", node=hex(self.changelog.node(cor+1)))
 
             for i in range(cor + 1, cnr + 1):
                 self.hook("incoming", node=hex(self.changelog.node(i)))
 
-        return
-
     def update(self, node, allow=False, force=False, choose=None,
                moddirstate=True, forcemerge=False, wlock=None):
         pl = self.dirstate.parents()
--- a/mercurial/mdiff.py	Sat Feb 18 22:01:09 2006 -0500
+++ b/mercurial/mdiff.py	Sat Feb 18 22:24:42 2006 -0500
@@ -18,16 +18,22 @@
 
     if not text and (util.binary(a) or util.binary(b)):
         l = ['Binary file %s has changed\n' % fn]
-    elif a == None:
+    elif not a:
         b = b.splitlines(1)
-        l1 = "--- %s\t%s\n" % ("/dev/null", epoch)
+        if a is None:
+            l1 = "--- %s\t%s\n" % ("/dev/null", epoch)
+        else:
+            l1 = "--- %s\t%s\n" % ("a/" + fn, ad)
         l2 = "+++ %s\t%s\n" % ("b/" + fn, bd)
         l3 = "@@ -0,0 +1,%d @@\n" % len(b)
         l = [l1, l2, l3] + ["+" + e for e in b]
-    elif b == None:
+    elif not b:
         a = a.splitlines(1)
         l1 = "--- %s\t%s\n" % ("a/" + fn, ad)
-        l2 = "+++ %s\t%s\n" % ("/dev/null", epoch)
+        if b is None:
+            l2 = "+++ %s\t%s\n" % ("/dev/null", epoch)
+        else:
+            l2 = "+++ %s\t%s\n" % ("b/" + fn, bd)
         l3 = "@@ -1,%d +0,0 @@\n" % len(a)
         l = [l1, l2, l3] + ["-" + e for e in a]
     else:
--- a/mercurial/mpatch.c	Sat Feb 18 22:01:09 2006 -0500
+++ b/mercurial/mpatch.c	Sat Feb 18 22:24:42 2006 -0500
@@ -43,6 +43,7 @@
 #endif
 
 static char mpatch_doc[] = "Efficient binary patching.";
+static PyObject *mpatch_Error;
 
 struct frag {
 	int start, end, len;
@@ -65,8 +66,11 @@
 			a = NULL;
 		} else
 			a->head = a->tail = a->base;
+			return a;
 	}
-	return a;
+	if (!PyErr_Occurred())
+		PyErr_NoMemory();
+	return NULL;
 }
 
 static void lfree(struct flist *a)
@@ -215,6 +219,9 @@
 
 	/* assume worst case size, we won't have many of these lists */
 	l = lalloc(len / 12);
+	if (!l)
+		return NULL;
+
 	lt = l->tail;
 
 	while (bin < end) {
@@ -227,6 +234,13 @@
 		lt++;
 	}
 
+	if (bin != end) {
+		if (!PyErr_Occurred())
+			PyErr_SetString(mpatch_Error, "patch cannot be decoded");
+		lfree(l);
+		return NULL;
+	}
+
 	l->tail = lt;
 	return l;
 }
@@ -238,6 +252,12 @@
 	struct frag *f = l->head;
 
 	while (f != l->tail) {
+		if (f->start < last || f->end > len) {
+			if (!PyErr_Occurred())
+				PyErr_SetString(mpatch_Error,
+				                "invalid patch");
+			return -1;
+		}
 		outlen += f->start - last;
 		last = f->end;
 		outlen += f->len;
@@ -248,13 +268,19 @@
 	return outlen;
 }
 
-static void apply(char *buf, char *orig, int len, struct flist *l)
+static int apply(char *buf, char *orig, int len, struct flist *l)
 {
 	struct frag *f = l->head;
 	int last = 0;
 	char *p = buf;
 
 	while (f != l->tail) {
+		if (f->start < last || f->end > len) {
+			if (!PyErr_Occurred())
+				PyErr_SetString(mpatch_Error,
+				                "invalid patch");
+			return 0;
+		}
 		memcpy(p, orig + last, f->start - last);
 		p += f->start - last;
 		memcpy(p, f->data, f->len);
@@ -263,6 +289,7 @@
 		f++;
 	}
 	memcpy(p, orig + last, len - last);
+	return 1;
 }
 
 /* recursively generate a patch of all bins between start and end */
@@ -304,16 +331,25 @@
 
 	patch = fold(bins, 0, len);
 	if (!patch)
-		return PyErr_NoMemory();
+		return NULL;
 
 	outlen = calcsize(PyString_Size(text), patch);
+	if (outlen < 0) {
+		result = NULL;
+		goto cleanup;
+	}
 	result = PyString_FromStringAndSize(NULL, outlen);
-	if (result) {
-		in = PyString_AsString(text);
-		out = PyString_AsString(result);
-		apply(out, in, PyString_Size(text), patch);
+	if (!result) {
+		result = NULL;
+		goto cleanup;
 	}
-
+	in = PyString_AsString(text);
+	out = PyString_AsString(result);
+	if (!apply(out, in, PyString_Size(text), patch)) {
+		Py_DECREF(result);
+		result = NULL;
+	}
+cleanup:
 	lfree(patch);
 	return result;
 }
@@ -327,5 +363,6 @@
 initmpatch(void)
 {
 	Py_InitModule3("mpatch", methods, mpatch_doc);
+	mpatch_Error = PyErr_NewException("mpatch.mpatchError", NULL, NULL);
 }
 
--- a/mercurial/sshrepo.py	Sat Feb 18 22:01:09 2006 -0500
+++ b/mercurial/sshrepo.py	Sat Feb 18 22:24:42 2006 -0500
@@ -110,7 +110,7 @@
         except:
             raise hg.RepoError(_("unexpected response '%s'") % (d[:400] + "..."))
 
-    def changegroup(self, nodes):
+    def changegroup(self, nodes, kind):
         n = " ".join(map(hex, nodes))
         f = self.do_cmd("changegroup", roots=n)
         return self.pipei
--- a/tests/test-diffdir	Sat Feb 18 22:01:09 2006 -0500
+++ b/tests/test-diffdir	Sat Feb 18 22:24:42 2006 -0500
@@ -12,3 +12,7 @@
 
 hg diff -r tip | sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \
                      -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/"
+
+echo foo > a
+hg diff | sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \
+              -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/"
--- a/tests/test-diffdir.out	Sat Feb 18 22:01:09 2006 -0500
+++ b/tests/test-diffdir.out	Sat Feb 18 22:24:42 2006 -0500
@@ -8,3 +8,13 @@
 +++ b/b
 @@ -0,0 +1,1 @@
 +123
+diff -r 3903775176ed a
+--- a/a
++++ b/a
+@@ -0,0 +1,1 @@
++foo
+diff -r 3903775176ed b
+--- /dev/null
++++ b/b
+@@ -0,0 +1,1 @@
++123
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-excessive-merge	Sat Feb 18 22:24:42 2006 -0500
@@ -0,0 +1,46 @@
+#!/bin/sh
+
+hg init
+
+echo foo > a
+echo foo > b
+hg add a b
+
+hg ci -m "test" -d "0 0"
+
+echo blah > a
+
+hg ci -m "branch a" -d "0 0"
+
+hg co 0
+
+echo blah > b
+
+hg ci -m "branch b" -d "0 0"
+HGMERGE=true hg up -m 1
+
+hg ci -m "merge b/a -> blah" -d "0 0"
+
+hg co 1
+HGMERGE=true hg up -m 2
+hg ci -m "merge a/b -> blah" -d "0 0"
+
+hg log
+hg debugindex .hg/00changelog.i
+
+echo
+
+echo 1
+hg manifest 1
+echo 2
+hg manifest 2
+echo 3
+hg manifest 3
+echo 4
+hg manifest 4
+
+echo
+
+hg debugindex .hg/data/a.i
+
+hg verify
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-excessive-merge.out	Sat Feb 18 22:24:42 2006 -0500
@@ -0,0 +1,59 @@
+changeset:   4:2ee31f665a86
+tag:         tip
+parent:      1:96155394af80
+parent:      2:92cc4c306b19
+user:        test
+date:        Thu Jan  1 00:00:00 1970 +0000
+summary:     merge a/b -> blah
+
+changeset:   3:e16a66a37edd
+parent:      2:92cc4c306b19
+parent:      1:96155394af80
+user:        test
+date:        Thu Jan  1 00:00:00 1970 +0000
+summary:     merge b/a -> blah
+
+changeset:   2:92cc4c306b19
+parent:      0:5e0375449e74
+user:        test
+date:        Thu Jan  1 00:00:00 1970 +0000
+summary:     branch b
+
+changeset:   1:96155394af80
+user:        test
+date:        Thu Jan  1 00:00:00 1970 +0000
+summary:     branch a
+
+changeset:   0:5e0375449e74
+user:        test
+date:        Thu Jan  1 00:00:00 1970 +0000
+summary:     test
+
+   rev    offset  length   base linkrev nodeid       p1           p2
+     0         0      60      0       0 5e0375449e74 000000000000 000000000000
+     1        60      62      1       1 96155394af80 5e0375449e74 000000000000
+     2       122      62      2       2 92cc4c306b19 5e0375449e74 000000000000
+     3       184      69      3       3 e16a66a37edd 92cc4c306b19 96155394af80
+     4       253      29      3       4 2ee31f665a86 96155394af80 92cc4c306b19
+
+1
+79d7492df40aa0fa093ec4209be78043c181f094 644 a
+2ed2a3912a0b24502043eae84ee4b279c18b90dd 644 b
+2
+2ed2a3912a0b24502043eae84ee4b279c18b90dd 644 a
+79d7492df40aa0fa093ec4209be78043c181f094 644 b
+3
+79d7492df40aa0fa093ec4209be78043c181f094 644 a
+79d7492df40aa0fa093ec4209be78043c181f094 644 b
+4
+79d7492df40aa0fa093ec4209be78043c181f094 644 a
+79d7492df40aa0fa093ec4209be78043c181f094 644 b
+
+   rev    offset  length   base linkrev nodeid       p1           p2
+     0         0       5      0       0 2ed2a3912a0b 000000000000 000000000000
+     1         5       6      1       1 79d7492df40a 2ed2a3912a0b 000000000000
+checking changesets
+checking manifests
+crosschecking files in changesets and manifests
+checking files
+2 files, 5 changesets, 4 total revisions
--- a/tests/test-help.out	Sat Feb 18 22:01:09 2006 -0500
+++ b/tests/test-help.out	Sat Feb 18 22:24:42 2006 -0500
@@ -171,10 +171,12 @@
 
 options:
 
- -r --rev      revision
- -a --text     treat all files as text
- -I --include  include names matching the given patterns
- -X --exclude  exclude names matching the given patterns
+ -r --rev               revision
+ -a --text              treat all files as text
+ -I --include           include names matching the given patterns
+ -p --show-function     show which function each change is in
+ -w --ignore-all-space  ignore white space when comparing lines
+ -X --exclude           exclude names matching the given patterns
 hg status [OPTION]... [FILE]...
 
 show changed files in the working directory
--- a/tests/test-hook	Sat Feb 18 22:01:09 2006 -0500
+++ b/tests/test-hook	Sat Feb 18 22:24:42 2006 -0500
@@ -1,10 +1,90 @@
 #!/bin/sh
 
-hg init
+# commit hooks can see env vars
+hg init a
+cd a
 echo "[hooks]" > .hg/hgrc
-echo 'precommit = echo precommit hook' >> .hg/hgrc
-echo 'commit = echo commit hook: $NODE' >> .hg/hgrc
+echo 'commit = echo commit hook: n=$HG_NODE p1=$HG_PARENT1 p2=$HG_PARENT2' >> .hg/hgrc
 echo 'commit.b = echo commit hook b' >> .hg/hgrc
+echo 'precommit = echo precommit hook: p1=$HG_PARENT1 p2=$HG_PARENT2' >> .hg/hgrc
+echo 'pretxncommit = echo pretxncommit hook: n=$HG_NODE p1=$HG_PARENT1 p2=$HG_PARENT2; hg -q tip' >> .hg/hgrc
 echo a > a
 hg add a
-hg commit -m "test" -d "0 0"
+hg commit -m a -d "0 0"
+
+hg clone . ../b
+cd ../b
+
+# changegroup hooks can see env vars
+echo '[hooks]' > .hg/hgrc
+echo 'prechangegroup = echo prechangegroup hook' >> .hg/hgrc
+echo 'changegroup = echo changegroup hook: n=$HG_NODE' >> .hg/hgrc
+echo 'incoming = echo incoming hook: n=$HG_NODE' >> .hg/hgrc
+
+# pretxncommit and commit hooks can see both parents of merge
+cd ../a
+echo b >> a
+hg commit -m a1 -d "1 0"
+hg update -C 0
+echo b > b
+hg add b
+hg commit -m b -d '1 0'
+hg update -m 1
+hg commit -m merge -d '2 0'
+
+cd ../b
+hg pull ../a
+
+# tag hooks can see env vars
+cd ../a
+echo 'pretag = echo pretag hook: t=$HG_TAG n=$HG_NODE l=$HG_LOCAL' >> .hg/hgrc
+echo 'tag = echo tag hook: t=$HG_TAG n=$HG_NODE l=$HG_LOCAL' >> .hg/hgrc
+hg tag -d '3 0' a
+hg tag -l la
+
+# pretag hook can forbid tagging
+echo 'pretag.forbid = echo pretag.forbid hook; exit 1' >> .hg/hgrc
+hg tag -d '4 0' fa
+hg tag -l fla
+
+# pretxncommit hook can see changeset, can roll back txn, changeset
+# no more there after
+echo 'pretxncommit.forbid = echo pretxncommit.forbid hook: tip=`hg -q tip`; exit 1' >> .hg/hgrc
+echo z > z
+hg add z
+hg -q tip
+hg commit -m 'fail' -d '4 0'
+hg -q tip
+
+# precommit hook can prevent commit
+echo 'precommit.forbid = echo precommit.forbid hook; exit 1' >> .hg/hgrc
+hg commit -m 'fail' -d '4 0'
+hg -q tip
+
+# prechangegroup hook can prevent incoming changes
+cd ../b
+hg -q tip
+echo '[hooks]' > .hg/hgrc
+echo 'prechangegroup.forbid = echo prechangegroup.forbid hook; exit 1' >> .hg/hgrc
+hg pull ../a
+
+# pretxnchangegroup hook can see incoming changes, can roll back txn,
+# incoming changes no longer there after
+echo '[hooks]' > .hg/hgrc
+echo 'pretxnchangegroup.forbid = echo pretxnchangegroup.forbid hook: tip=`hg -q tip`; exit 1' >> .hg/hgrc
+hg pull ../a
+hg -q tip
+
+# outgoing hooks can see env vars
+rm .hg/hgrc
+echo '[hooks]' > ../a/.hg/hgrc
+echo 'preoutgoing = echo preoutgoing hook: s=$HG_SOURCE' >> ../a/.hg/hgrc
+echo 'outgoing = echo outgoing hook: n=$HG_NODE s=$HG_SOURCE' >> ../a/.hg/hgrc
+hg pull ../a
+hg undo
+
+# preoutgoing hook can prevent outgoing changes
+echo 'preoutgoing.forbid = echo preoutgoing.forbid hook; exit 1' >> ../a/.hg/hgrc
+hg pull ../a
+
+exit 0
--- a/tests/test-hook.out	Sat Feb 18 22:01:09 2006 -0500
+++ b/tests/test-hook.out	Sat Feb 18 22:24:42 2006 -0500
@@ -1,3 +1,88 @@
-precommit hook
+precommit hook: p1=0000000000000000000000000000000000000000 p2=
+pretxncommit hook: n=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b p1=0000000000000000000000000000000000000000 p2=
+0:cb9a9f314b8b
+commit hook b
+commit hook: n=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b p1=0000000000000000000000000000000000000000 p2=
+precommit hook: p1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b p2=
+pretxncommit hook: n=ab228980c14deea8b9555d91c9581127383e40fd p1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b p2=
+1:ab228980c14d
+commit hook b
+commit hook: n=ab228980c14deea8b9555d91c9581127383e40fd p1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b p2=
+precommit hook: p1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b p2=
+pretxncommit hook: n=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 p1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b p2=
+2:ee9deb46ab31
+commit hook b
+commit hook: n=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 p1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b p2=
+precommit hook: p1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 p2=ab228980c14deea8b9555d91c9581127383e40fd
+pretxncommit hook: n=07f3376c1e655977439df2a814e3cc14b27abac2 p1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 p2=ab228980c14deea8b9555d91c9581127383e40fd
+3:07f3376c1e65
+commit hook b
+commit hook: n=07f3376c1e655977439df2a814e3cc14b27abac2 p1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 p2=ab228980c14deea8b9555d91c9581127383e40fd
+prechangegroup hook
+changegroup hook: n=ab228980c14deea8b9555d91c9581127383e40fd
+incoming hook: n=ab228980c14deea8b9555d91c9581127383e40fd
+incoming hook: n=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2
+incoming hook: n=07f3376c1e655977439df2a814e3cc14b27abac2
+pulling from ../a
+searching for changes
+adding changesets
+adding manifests
+adding file changes
+added 3 changesets with 2 changes to 2 files
+(run 'hg update' to get a working copy)
+pretag hook: t=a n=07f3376c1e655977439df2a814e3cc14b27abac2 l=0
+precommit hook: p1=07f3376c1e655977439df2a814e3cc14b27abac2 p2=
+pretxncommit hook: n=3cd2c6a5a36c5908aad3bc0d717c29873a05dfc2 p1=07f3376c1e655977439df2a814e3cc14b27abac2 p2=
+4:3cd2c6a5a36c
 commit hook b
-commit hook: acb14030fe0a21b60322c440ad2d20cf7685a376
+commit hook: n=3cd2c6a5a36c5908aad3bc0d717c29873a05dfc2 p1=07f3376c1e655977439df2a814e3cc14b27abac2 p2=
+tag hook: t=a n=07f3376c1e655977439df2a814e3cc14b27abac2 l=0
+pretag hook: t=la n=3cd2c6a5a36c5908aad3bc0d717c29873a05dfc2 l=1
+tag hook: t=la n=3cd2c6a5a36c5908aad3bc0d717c29873a05dfc2 l=1
+pretag hook: t=fa n=3cd2c6a5a36c5908aad3bc0d717c29873a05dfc2 l=0
+pretag.forbid hook
+abort: pretag.forbid hook exited with status 1
+pretag hook: t=fla n=3cd2c6a5a36c5908aad3bc0d717c29873a05dfc2 l=1
+pretag.forbid hook
+abort: pretag.forbid hook exited with status 1
+4:3cd2c6a5a36c
+precommit hook: p1=3cd2c6a5a36c5908aad3bc0d717c29873a05dfc2 p2=
+pretxncommit.forbid hook: tip=5:469a61fe67d6
+abort: pretxncommit.forbid hook exited with status 1
+transaction abort!
+rollback completed
+4:3cd2c6a5a36c
+precommit.forbid hook
+abort: precommit.forbid hook exited with status 1
+4:3cd2c6a5a36c
+3:07f3376c1e65
+prechangegroup.forbid hook
+pulling from ../a
+searching for changes
+abort: prechangegroup.forbid hook exited with status 1
+pretxnchangegroup.forbid hook: tip=4:3cd2c6a5a36c
+pulling from ../a
+searching for changes
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 1 changes to 1 files
+abort: pretxnchangegroup.forbid hook exited with status 1
+transaction abort!
+rollback completed
+3:07f3376c1e65
+preoutgoing hook: s=pull
+outgoing hook: n=3cd2c6a5a36c5908aad3bc0d717c29873a05dfc2 s=pull
+pulling from ../a
+searching for changes
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 1 changes to 1 files
+(run 'hg update' to get a working copy)
+rolling back last transaction
+preoutgoing hook: s=pull
+preoutgoing.forbid hook
+pulling from ../a
+searching for changes
+abort: preoutgoing.forbid hook exited with status 1
--- a/tests/test-ro-message	Sat Feb 18 22:01:09 2006 -0500
+++ b/tests/test-ro-message	Sat Feb 18 22:24:42 2006 -0500
@@ -14,4 +14,4 @@
 "$HG" commit -m 'Clarifying the vehicle.'
 "$HG" update -C 1
 chmod a-w b/vehicle
-"$HG" update -m 2 2>&1 | sed 's|^\(.*[ 	]\)/tmp/[^/]*/\(.*\)$|\1\2|g'
+"$HG" update -m 2 2>&1 | sed 's|^\(.*[ 	]\).*/\([^/]*/[^/]*/[^/]*\)$|\1\2|g'