changeset 1769:982fb022a16a

Merged RSS feed for tags from Peter van Dijk
author Thomas Arendsen Hein <thomas@intevation.de>
date Tue, 21 Feb 2006 16:04:47 +0100
parents b9fac31f34c9 (diff) f79afc26ae3b (current diff)
children 0762feff3043 b9671b41e360
files mercurial/hgweb.py
diffstat 56 files changed, 1373 insertions(+), 504 deletions(-) [+]
line wrap: on
line diff
--- a/contrib/bash_completion
+++ b/contrib/bash_completion
@@ -2,36 +2,36 @@ shopt -s extglob
 
 _hg_command_list()
 {
-    hg --debug help 2>/dev/null | \
+    "$hg" --debug help 2>/dev/null | \
 	awk 'function command_line(line) {
-		 gsub(/,/, "", line)
-		 gsub(/:.*/, "", line)
-		 split(line, aliases)
-		 command = aliases[1]
-		 delete aliases[1]
-		 print command
-		 for (i in aliases)
-		     if (index(command, aliases[i]) != 1)
-			 print aliases[i]
-	     }
-	     /^list of commands:/ {commands=1}
-	     commands && /^ debug/ {a[i++] = $0; next;}
-	     commands && /^ [^ ]/ {command_line($0)}
-	     /^global options:/ {exit 0}
-	     END {for (i in a) command_line(a[i])}'
+		gsub(/,/, "", line)
+		gsub(/:.*/, "", line)
+		split(line, aliases)
+		command = aliases[1]
+		delete aliases[1]
+		print command
+		for (i in aliases)
+		    if (index(command, aliases[i]) != 1)
+			print aliases[i]
+	    }
+	    /^list of commands:/ {commands=1}
+	    commands && /^ debug/ {a[i++] = $0; next;}
+	    commands && /^ [^ ]/ {command_line($0)}
+	    /^global options:/ {exit 0}
+	    END {for (i in a) command_line(a[i])}'
 
 }
 
 _hg_option_list()
 {
-    hg -v help $1 2> /dev/null | \
-        awk '/^ *-/ {
-		 for (i = 1; i <= NF; i ++) {
+    "$hg" -v help $1 2>/dev/null | \
+	awk '/^ *-/ {
+		for (i = 1; i <= NF; i ++) {
 		    if (index($i, "-") != 1)
-			 break;
+			break;
 		    print $i;
-		 }
-	     }'
+		}
+	    }'
 }
 
 
@@ -56,29 +56,29 @@ shopt -s extglob
 
 _hg_paths()
 {
-    local paths="$(hg paths 2> /dev/null | sed -e 's/ = .*$//')"
-    COMPREPLY=(${COMPREPLY[@]:-} $( compgen -W '$paths' -- "$cur" ))
+    local paths="$("$hg" paths 2>/dev/null | sed -e 's/ = .*$//')"
+    COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$paths' -- "$cur"))
 }
 
 _hg_repos()
 {
     local i
-    for i in $( compgen -d -- "$cur" ); do
-        test ! -d "$i"/.hg || COMPREPLY=(${COMPREPLY[@]:-} "$i")
+    for i in $(compgen -d -- "$cur"); do
+	test ! -d "$i"/.hg || COMPREPLY=(${COMPREPLY[@]:-} "$i")
     done
 }
 
 _hg_status()
 {
-    local files="$( hg status -n$1 . 2> /dev/null)"
-    COMPREPLY=(${COMPREPLY[@]:-} $( compgen -W '$files' -- "$cur" ))
+    local files="$("$hg" status -n$1 . 2>/dev/null)"
+    COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$files' -- "$cur"))
 }
 
 _hg_tags()
 {
-    local tags="$(hg tags 2> /dev/null |
-                      sed -e 's/[0-9]*:[a-f0-9]\{40\}$//; s/ *$//')"
-    COMPREPLY=( ${COMPREPLY[@]:-} $(compgen -W '$tags' -- "$cur") )
+    local tags="$("$hg" tags 2>/dev/null |
+	sed -e 's/[0-9]*:[a-f0-9]\{40\}$//; s/ *$//')"
+    COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$tags' -- "$cur"))
 }
 
 # this is "kind of" ugly...
@@ -87,7 +87,7 @@ shopt -s extglob
     local i count=0
     local filters="$1"
 
-    for (( i=1; $i<=$COMP_CWORD; i++ )); do
+    for ((i=1; $i<=$COMP_CWORD; i++)); do
 	if [[ "${COMP_WORDS[i]}" != -* ]]; then
 	    if [[ ${COMP_WORDS[i-1]} == @($filters|$global_args) ]]; then
 		continue
@@ -104,6 +104,7 @@ shopt -s extglob
     local cur prev cmd opts i
     # global options that receive an argument
     local global_args='--cwd|-R|--repository'
+    local hg="$1"
 
     COMPREPLY=()
     cur="$2"
@@ -112,7 +113,7 @@ shopt -s extglob
     # searching for the command
     # (first non-option argument that doesn't follow a global option that
     #  receives an argument)
-    for (( i=1; $i<=$COMP_CWORD; i++ )); do
+    for ((i=1; $i<=$COMP_CWORD; i++)); do
 	if [[ ${COMP_WORDS[i]} != -* ]]; then
 	    if [[ ${COMP_WORDS[i-1]} != @($global_args) ]]; then
 		cmd="${COMP_WORDS[i]}"
@@ -124,7 +125,7 @@ shopt -s extglob
     if [[ "$cur" == -* ]]; then
 	opts=$(_hg_option_list $cmd)
 
-	COMPREPLY=( ${COMPREPLY[@]:-} $(compgen -W '$opts' -- "$cur") )
+	COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$opts' -- "$cur"))
 	return
     fi
 
@@ -146,7 +147,7 @@ shopt -s extglob
     fi
 
     # canonicalize command name
-    cmd=$(hg -q help "$cmd" 2> /dev/null | sed -e 's/^hg //; s/ .*//; 1q')
+    cmd=$("$hg" -q help "$cmd" 2>/dev/null | sed -e 's/^hg //; s/ .*//; 1q')
 
     if [ "$cmd" != status ] && [ "$prev" = -r ] || [ "$prev" = --rev ]; then
 	_hg_tags
@@ -190,17 +191,17 @@ shopt -s extglob
 	    if [ $count = 1 ]; then
 		_hg_paths
 	    fi
-            _hg_repos
+	    _hg_repos
 	;;
 	debugindex|debugindexdot)
-	    COMPREPLY=(${COMPREPLY[@]:-} $( compgen -f -X "!*.i" -- "$cur" ))
+	    COMPREPLY=(${COMPREPLY[@]:-} $(compgen -f -X "!*.i" -- "$cur"))
 	;;
 	debugdata)
-	    COMPREPLY=(${COMPREPLY[@]:-} $( compgen -f -X "!*.d" -- "$cur" ))
+	    COMPREPLY=(${COMPREPLY[@]:-} $(compgen -f -X "!*.d" -- "$cur"))
 	;;
     esac
 
 }
 
-complete -o bashdefault -o default -F _hg hg 2> /dev/null \
+complete -o bashdefault -o default -F _hg hg 2>/dev/null \
     || complete -o default -F _hg hg
--- a/contrib/convert-repo
+++ b/contrib/convert-repo
@@ -21,7 +21,7 @@
 # interrupted and can be run repeatedly to copy new commits.
 
 import sys, os, zlib, sha, time
-from mercurial import hg, ui, util, commands
+from mercurial import hg, ui, util
 
 class convert_git:
     def __init__(self, path):
@@ -113,7 +113,7 @@ class convert_mercurial:
         except:
             pass
 
-    def putcommit(self, files, parents, author, date, text):
+    def putcommit(self, files, parents, author, dest, text):
         seen = {}
         pl = []
         for p in parents:
@@ -129,13 +129,8 @@ class convert_mercurial:
         while parents:
             p1 = p2
             p2 = parents.pop(0)
-            self.repo.dirstate.setparents(hg.bin(p1), hg.bin(p2))
-            if len(files) > 0:
-                olddir = os.getcwd()
-                os.chdir(self.path)
-                commands.addremove(self.repo.ui, self.repo, *files)
-                os.chdir(olddir)
-            self.repo.commit(files, text, author, date)
+            self.repo.rawcommit(files, text, author, dest,
+                                hg.bin(p1), hg.bin(p2))
             text = "(octopus merge fixup)\n"
             p2 = hg.hex(self.repo.changelog.tip())
 
@@ -265,6 +260,7 @@ class convert:
         t = self.toposort(parents)
         t = [n for n in t if n not in self.map]
         num = len(t)
+        c = None
 
         for c in t:
             num -= 1
@@ -279,7 +275,7 @@ class convert:
             if v in self.map:
                 ctags[k] = self.map[v]
 
-        if ctags:
+        if c and ctags:
             nrev = self.dest.puttags(ctags)
             # write another hash correspondence to override the previous
             # one so we don't end up with extra tag heads
--- a/contrib/hbisect.py
+++ b/contrib/hbisect.py
@@ -187,7 +187,7 @@ class bisect(object):
         check_clean(self.ui, self.repo)
         rev = self.next()
         self.ui.write("Now testing %s\n" % hg.hex(rev))
-        return self.repo.update(rev, allow=True, force=True)
+        return self.repo.update(rev, force=True)
 
     def good(self, rev):
         self.goodrevs.append(rev)
@@ -232,7 +232,7 @@ def test(ui, repo, rev):
             b.good(new_rev)
             ui.write("it is good\n")
         anc = b.ancestors()
-        repo.update(new_rev, allow=True, force=True)
+        repo.update(new_rev, force=True)
     for v in anc:
         if v != rev:
             ui.warn("fail to found cset! :(\n")
new file mode 100644
--- /dev/null
+++ b/contrib/macosx/Readme.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+  <meta http-equiv="Content-Style-Type" content="text/css">
+  <title></title>
+  <style type="text/css">
+    p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Helvetica}
+    p.p2 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px}
+    p.p3 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica}
+    p.p4 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; color: #000fed}
+    span.s1 {text-decoration: underline}
+    span.s2 {font: 12.0px Courier}
+  </style>
+</head>
+<body>
+<p class="p1"><b>Before you install</b></p>
+<p class="p2"><br></p>
+<p class="p3">This is <i>not</i> a stand-alone version of Mercurial.</p>
+<p class="p2"><br></p>
+<p class="p3">To use it, you must have the “official unofficial” MacPython 2.4.1 installed.</p>
+<p class="p2"><br></p>
+<p class="p3">You can download MacPython 2.4.1 from here:</p>
+<p class="p4"><span class="s1"><a href="http://python.org/ftp/python/2.4.1/MacPython-OSX-2.4.1-1.dmg">http://python.org/ftp/python/2.4.1/MacPython-OSX-2.4.1-1.dmg</a></span></p>
+<p class="p2"><br></p>
+<p class="p3">For more information on MacPython, go here:</p>
+<p class="p4"><span class="s1"><a href="http://undefined.org/python/">http://undefined.org/python</a></span></p>
+<p class="p2"><br></p>
+<p class="p1"><b>After you install</b></p>
+<p class="p2"><br></p>
+<p class="p3">This package installs the <span class="s2">hg</span> executable in <span class="s2">/usr/local/bin</span>. This directory may not be in your shell's search path. Don't forget to check.</p>
+<p class="p2"><br></p>
+<p class="p1"><b>Reporting problems</b></p>
+<p class="p2"><br></p>
+<p class="p3">If you run into any problems, please file a bug online:</p>
+<p class="p3"><a href="http://www.selenic.com/mercurial/bts">http://www.selenic.com/mercurial/bts</a></p>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/contrib/macosx/Welcome.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+  <meta http-equiv="Content-Style-Type" content="text/css">
+  <title></title>
+  <style type="text/css">
+    p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica}
+    p.p2 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px}
+  </style>
+</head>
+<body>
+<p class="p1">This is a prepackaged release of <a href="http://www.selenic.com/mercurial">Mercurial</a> for Mac OS X.</p>
+<p class="p2"><br></p>
+<p class="p1">It is based on Mercurial 0.8.</p>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/contrib/macosx/macosx-build.txt
@@ -0,0 +1,11 @@
+to build a new macosx binary package:
+
+install macpython from http://undefined.org/python/
+
+install py2app from http://pythonmac.org/packages/
+
+make sure /usr/local/bin is in your path
+
+run bdist_mpkg in top-level hg directory
+
+find packaged stuff in dist directory
--- a/contrib/win32/ReadMe.html
+++ b/contrib/win32/ReadMe.html
@@ -5,7 +5,7 @@
   </head>
 
   <body>
-    <h1>Mercurial version 0.7 for Windows</h1>
+    <h1>Mercurial version 0.8 for Windows</h1>
 
     <p>Welcome to Mercurial for Windows!</p>
 
--- a/contrib/win32/mercurial.iss
+++ b/contrib/win32/mercurial.iss
@@ -4,7 +4,7 @@
 [Setup]
 AppCopyright=Copyright 2005 Matt Mackall and others
 AppName=Mercurial
-AppVerName=Mercurial version 0.7
+AppVerName=Mercurial version 0.8
 InfoAfterFile=contrib/win32/postinstall.txt
 LicenseFile=COPYING
 ShowLanguageDialog=yes
@@ -14,10 +14,10 @@ AppSupportURL=http://www.selenic.com/mer
 AppUpdatesURL=http://www.selenic.com/mercurial
 AppID={{4B95A5F1-EF59-4B08-BED8-C891C46121B3}
 AppContact=mercurial@selenic.com
-OutputBaseFilename=Mercurial-0.7
+OutputBaseFilename=Mercurial-0.8
 DefaultDirName={sd}\Mercurial
 SourceDir=C:\hg\hg-release
-VersionInfoVersion=0.7
+VersionInfoVersion=0.8
 VersionInfoDescription=Mercurial distributed SCM
 VersionInfoCopyright=Copyright 2005 Matt Mackall and others
 VersionInfoCompany=Matt Mackall and others
--- a/contrib/win32/postinstall.txt
+++ b/contrib/win32/postinstall.txt
@@ -8,6 +8,27 @@ file that comes with this package.
 Release Notes
 -------------
 
+2006-01-29  v0.8 
+
+* Upgrade notes:
+
+  - diff and status command are now repo-wide by default
+   (use 'hg diff .' for the old behavior)
+  - GPG signing is now done with the gpg extension
+  - the --text option for commit, rawcommit, and tag has been removed
+  - the copy/rename --parents option has been removed
+
+* Major changes from 0.7 to 0.8:
+
+  - faster status, diff, and commit
+  - reduced memory usage for push and pull
+  - improved extension API
+  - new bisect, gpg, hgk, and win32text extensions
+  - short URLs, binary file handling, and optional gitweb skin for hgweb
+  - numerous new command options including log --keyword and pull --rev
+  - improved hooks and file filtering
+
+ 
 2005-09-21  v0.7 with modifications
 
 * New INI files have been added to control Mercurial's behaviour:
--- a/doc/Makefile
+++ b/doc/Makefile
@@ -15,7 +15,7 @@ html: $(HTML)
 	asciidoc -d manpage -b docbook $*.txt
 
 %.html: %.txt
-	asciidoc -b html4 $*.txt
+	asciidoc -b html4 $*.txt || asciidoc -b html $*.txt
 
 clean:
 	$(RM) $(MAN) $(MAN:%=%.xml) $(MAN:%=%.html)
--- a/doc/hg.1.txt
+++ b/doc/hg.1.txt
@@ -223,9 +223,11 @@ diff [-a] [-r revision] [-r revision] [f
     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.
@@ -374,6 +376,11 @@ log [-r revision ...] [-p] [files]::
     options:
     -I, --include <pat>   include names matching the given patterns
     -X, --exclude <pat>   exclude names matching the given patterns
+    -b, --branch          show branches
+    -k, --keyword <str>   search for keywords
+    -l, --limit <num>     print no more than this many changes
+    -M, --no-merges       do not show merges
+    -m, --only-merges     only show merges
     -r, --rev <A>         show the specified revision or range
     -p, --patch           show patch
 
@@ -541,10 +548,12 @@ serve [options]::
 
     options:
     -A, --accesslog <file>   name of access log file to write to
+    -d, --daemon             run server in background, as a daemon
     -E, --errorlog <file>    name of error log file to write to
     -a, --address <addr>     address to use
     -p, --port <n>           port to use (default: 8000)
     -n, --name <name>        name to show in web pages (default: working dir)
+    --pid-file <file>        write server process ID to given file
     -t, --templatedir <path> web templates to use
     -6, --ipv6               use IPv6 in addition to IPv4
 
@@ -600,9 +609,12 @@ tags::
 
     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
+++ b/doc/hgrc.5.txt
@@ -141,20 +141,75 @@ hooks::
 
     [hooks]
     # do not use the site-wide hook
-    commit =
-    commit.email = /my/email/hook
-    commit.autobuild = /my/build/hook
+    incoming =
+    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 or for each changeset
-    pulled. Passed the ID of the newly created changeset in
-    environment variable $NODE.
+    Run after a changeset has been created in the local repository.
+    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.  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/hg
+++ b/hg
@@ -1,9 +1,8 @@
 #!/usr/bin/env python
 #
-# mercurial - a minimal scalable distributed SCM
-# v0.6 "paola"
+# mercurial - scalable distributed SCM
 #
-# Copyright 2005 Matt Mackall <mpm@selenic.com>
+# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
 #
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
--- a/hgeditor
+++ b/hgeditor
@@ -34,7 +34,6 @@ HGTMP="${TMPDIR-/tmp}/hgeditor.$RANDOM.$
 }
 
 (
-    cd "`hg root`"
     grep '^HG: changed' "$1" | cut -b 13- | while read changed; do
         hg diff "$changed" >> "$HGTMP/diff"
     done
--- a/hgext/gpg.py
+++ b/hgext/gpg.py
@@ -1,6 +1,14 @@
-import os, tempfile, binascii, errno
+# GnuPG signing extension for Mercurial
+#
+# Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+import os, tempfile, binascii
 from mercurial import util
 from mercurial import node as hgnode
+from mercurial.i18n import gettext as _
 
 class gpg:
     def __init__(self, path, key=None):
@@ -14,6 +22,7 @@ class gpg:
     def verify(self, data, sig):
         """ returns of the good and bad signatures"""
         try:
+            # create temporary files
             fd, sigfile = tempfile.mkstemp(prefix="hggpgsig")
             fp = os.fdopen(fd, 'wb')
             fp.write(sig)
@@ -22,8 +31,8 @@ class gpg:
             fp = os.fdopen(fd, 'wb')
             fp.write(data)
             fp.close()
-            gpgcmd = "%s --logger-fd 1 --status-fd 1 --verify \"%s\" \"%s\"" % (self.path, sigfile, datafile)
-            #gpgcmd = "%s --status-fd 1 --verify \"%s\" \"%s\"" % (self.path, sigfile, datafile)
+            gpgcmd = ("%s --logger-fd 1 --status-fd 1 --verify "
+                      "\"%s\" \"%s\"" % (self.path, sigfile, datafile))
             ret = util.filter("", gpgcmd)
         except:
             for f in (sigfile, datafile):
@@ -41,7 +50,7 @@ class gpg:
                 continue
             l = l[9:]
             if l.startswith("ERRSIG"):
-                err = "error while verifying signature"
+                err = _("error while verifying signature")
                 break
             elif l.startswith("VALIDSIG"):
                 # fingerprint of the primary key
@@ -61,12 +70,97 @@ class gpg:
         return err, keys
 
 def newgpg(ui, **opts):
+    """create a new gpg instance"""
     gpgpath = ui.config("gpg", "cmd", "gpg")
     gpgkey = opts.get('key')
     if not gpgkey:
         gpgkey = ui.config("gpg", "key", None)
     return gpg(gpgpath, gpgkey)
 
+def sigwalk(repo):
+    """
+    walk over every sigs, yields a couple
+    ((node, version, sig), (filename, linenumber))
+    """
+    def parsefile(fileiter, context):
+        ln = 1
+        for l in fileiter:
+            if not l:
+                continue
+            yield (l.split(" ", 2), (context, ln))
+            ln +=1
+
+    fl = repo.file(".hgsigs")
+    h = fl.heads()
+    h.reverse()
+    # read the heads
+    for r in h:
+        fn = ".hgsigs|%s" % hgnode.short(r)
+        for item in parsefile(fl.read(r).splitlines(), fn):
+            yield item
+    try:
+        # read local signatures
+        fn = "localsigs"
+        for item in parsefile(repo.opener(fn), fn):
+            yield item
+    except IOError:
+        pass
+
+def getkeys(ui, repo, mygpg, sigdata, context):
+    """get the keys who signed a data"""
+    fn, ln = context
+    node, version, sig = sigdata
+    prefix = "%s:%d" % (fn, ln)
+    node = hgnode.bin(node)
+
+    data = node2txt(repo, node, version)
+    sig = binascii.a2b_base64(sig)
+    err, keys = mygpg.verify(data, sig)
+    if err:
+        ui.warn("%s:%d %s\n" % (fn, ln , err))
+        return None
+
+    validkeys = []
+    # warn for expired key and/or sigs
+    for key in keys:
+        if key[0] == "BADSIG":
+            ui.write(_("%s Bad signature from \"%s\"\n") % (prefix, key[2]))
+            continue
+        if key[0] == "EXPSIG":
+            ui.write(_("%s Note: Signature has expired"
+                       " (signed by: \"%s\")\n") % (prefix, key[2]))
+        elif key[0] == "EXPKEYSIG":
+            ui.write(_("%s Note: This key has expired"
+                       " (signed by: \"%s\")\n") % (prefix, key[2]))
+        validkeys.append((key[1], key[2], key[3]))
+    return validkeys
+
+def sigs(ui, repo):
+    """list signed changesets"""
+    mygpg = newgpg(ui)
+    revs = {}
+
+    for data, context in sigwalk(repo):
+        node, version, sig = data
+        fn, ln = context
+        try:
+            n = repo.lookup(node)
+        except KeyError:
+            ui.warn(_("%s:%d node does not exist\n") % (fn, ln))
+            continue
+        r = repo.changelog.rev(n)
+        keys = getkeys(ui, repo, mygpg, data, context)
+        if not keys:
+            continue
+        revs.setdefault(r, [])
+        revs[r].extend(keys)
+    nodes = list(revs)
+    nodes.reverse()
+    for rev in nodes:
+        for k in revs[rev]:
+            r = "%5d:%s" % (rev, hgnode.hex(repo.changelog.node(rev)))
+            ui.write("%-30s %s\n" % (keystr(ui, k), r))
+
 def check(ui, repo, rev):
     """verify all the signatures there may be for a particular revision"""
     mygpg = newgpg(ui)
@@ -74,63 +168,30 @@ def check(ui, repo, rev):
     hexrev = hgnode.hex(rev)
     keys = []
 
-    def addsig(fn, ln, l):
-        if not l: return
-        n, v, sig = l.split(" ", 2)
-        if n == hexrev:
-            data = node2txt(repo, rev, v)
-            sig = binascii.a2b_base64(sig)
-            err, k = mygpg.verify(data, sig)
-            if not err:
-                keys.append((k, fn, ln))
-            else:
-                ui.warn("%s:%d %s\n" % (fn, ln , err))
-
-    fl = repo.file(".hgsigs")
-    h = fl.heads()
-    h.reverse()
-    # read the heads
-    for r in h:
-        ln = 1
-        for l in fl.read(r).splitlines():
-            addsig(".hgsigs|%s" % hgnode.short(r), ln, l)
-            ln +=1
-    try:
-        # read local signatures
-        ln = 1
-        f = repo.opener("localsigs")
-        for l in f:
-            addsig("localsigs", ln, l)
-            ln +=1
-    except IOError:
-        pass
+    for data, context in sigwalk(repo):
+        node, version, sig = data
+        if node == hexrev:
+            k = getkeys(ui, repo, mygpg, data, context)
+            if k:
+                keys.extend(k)
 
     if not keys:
-        ui.write("%s not signed\n" % hgnode.short(rev))
+        ui.write(_("No valid signature for %s\n") % hgnode.short(rev))
         return
-    valid = []
-    # warn for expired key and/or sigs
-    for k, fn, ln in keys:
-        prefix = "%s:%d" % (fn, ln)
-        for key in k:
-            if key[0] == "BADSIG":
-                ui.write("%s Bad signature from \"%s\"\n" % (prefix, key[2]))
-                continue
-            if key[0] == "EXPSIG":
-                ui.write("%s Note: Signature has expired"
-                         " (signed by: \"%s\")\n" % (prefix, key[2]))
-            elif key[0] == "EXPKEYSIG":
-                ui.write("%s Note: This key has expired"
-                         " (signed by: \"%s\")\n" % (prefix, key[2]))
-            valid.append((key[1], key[2], key[3]))
+
     # print summary
     ui.write("%s is signed by:\n" % hgnode.short(rev))
-    for keyid, user, fingerprint in valid:
-        role = getrole(ui, fingerprint)
-        ui.write("  %s (%s)\n" % (user, role))
+    for key in keys:
+        ui.write(" %s\n" % keystr(ui, key))
 
-def getrole(ui, fingerprint):
-    return ui.config("gpg", fingerprint, "no role defined")
+def keystr(ui, key):
+    """associate a string to a key (username, comment)"""
+    keyid, user, fingerprint = key
+    comment = ui.config("gpg", fingerprint, None)
+    if comment:
+        return "%s (%s)" % (user, comment)
+    else:
+        return user
 
 def sign(ui, repo, *revs, **opts):
     """add a signature for the current tip or a given revision"""
@@ -150,7 +211,7 @@ def sign(ui, repo, *revs, **opts):
         data = node2txt(repo, n, sigver)
         sig = mygpg.sign(data)
         if not sig:
-            raise util.Abort("Error while signing")
+            raise util.Abort(_("Error while signing"))
         sig = binascii.b2a_base64(sig)
         sig = sig.replace("\n", "")
         sigmessage += "%s %s %s\n" % (hexnode, sigver, sig)
@@ -162,9 +223,9 @@ def sign(ui, repo, *revs, **opts):
 
     for x in repo.changes():
         if ".hgsigs" in x and not opts["force"]:
-            raise util.Abort("working copy of .hgsigs is changed "
-                             "(please commit .hgsigs manually"
-                             "or use --force)")
+            raise util.Abort(_("working copy of .hgsigs is changed "
+                               "(please commit .hgsigs manually "
+                               "or use --force)"))
 
     repo.wfile(".hgsigs", "ab").write(sigmessage)
 
@@ -176,7 +237,8 @@ def sign(ui, repo, *revs, **opts):
 
     message = opts['message']
     if not message:
-        message = "\n".join(["Added signature for changeset %s" % hgnode.hex(n)
+        message = "\n".join([_("Added signature for changeset %s")
+                             % hgnode.hex(n)
                              for n in nodes])
     try:
         repo.commit([".hgsigs"], message, opts['user'], opts['date'])
@@ -188,19 +250,20 @@ def node2txt(repo, node, ver):
     if ver == "0":
         return "%s\n" % hgnode.hex(node)
     else:
-        util.Abort("unknown signature version")
+        raise util.Abort(_("unknown signature version"))
 
 cmdtable = {
     "sign":
         (sign,
-         [('l', 'local', None, "make the signature local"),
-          ('f', 'force', None, "sign even if the sigfile is modified"),
-          ('', 'no-commit', None, "do not commit the sigfile after signing"),
-          ('m', 'message', "", "commit message"),
-          ('d', 'date', "", "date code"),
-          ('u', 'user', "", "user"),
-          ('k', 'key', "", "the key id to sign with")],
-         "hg sign [OPTION]... REVISIONS"),
-    "sigcheck": (check, [], 'hg sigcheck REVISION')
+         [('l', 'local', None, _("make the signature local")),
+          ('f', 'force', None, _("sign even if the sigfile is modified")),
+          ('', 'no-commit', None, _("do not commit the sigfile after signing")),
+          ('m', 'message', "", _("commit message")),
+          ('d', 'date', "", _("date code")),
+          ('u', 'user', "", _("user")),
+          ('k', 'key', "", _("the key id to sign with"))],
+         _("hg sign [OPTION]... [REVISION]...")),
+    "sigcheck": (check, [], _('hg sigcheck REVISION')),
+    "sigs": (sigs, [], _('hg sigs')),
 }
 
old mode 100755
new mode 100644
rename from contrib/patchbomb
rename to hgext/patchbomb.py
--- a/contrib/patchbomb
+++ b/hgext/patchbomb.py
@@ -1,7 +1,5 @@
-#!/usr/bin/python
-#
-# Interactive script for sending a collection of Mercurial changesets
-# as a series of patch emails.
+# Command for sending a collection of Mercurial changesets as a series
+# of patch emails.
 #
 # The series is started off with a "[PATCH 0 of N]" introduction,
 # which describes the series as a whole.
@@ -29,6 +27,10 @@
 # firing it up "for real", in which case it will use your pager to
 # display each of the messages that it would send.
 #
+# The "-m" (mbox) option will create an mbox file instead of sending
+# the messages directly. This can be reviewed e.g. with "mutt -R -f mbox",
+# and finally sent with "formail -s sendmail -bm -t < mbox".
+#
 # To configure a default mail host, add a section like this to your
 # hgrc file:
 #
@@ -49,10 +51,11 @@
 
 from email.MIMEMultipart import MIMEMultipart
 from email.MIMEText import MIMEText
+from email.Utils import parseaddr
 from mercurial import commands
-from mercurial import fancyopts
 from mercurial import hg
 from mercurial import ui
+from mercurial.i18n import gettext as _
 import os
 import popen2
 import smtplib
@@ -89,6 +92,17 @@ def diffstat(patch):
         except: pass
 
 def patchbomb(ui, repo, *revs, **opts):
+    '''send changesets as a series of patch emails
+
+    The series starts with a "[PATCH 0 of N]" introduction, which
+    describes the series as a whole.
+
+    Each patch email has a Subject line of "[PATCH M of N] ...", using
+    the first line of the changeset description as the subject text.
+    The message contains two or three body parts.  First, the rest of
+    the changeset description.  Next, (optionally) if the diffstat
+    program is installed, the result of running diffstat on the patch.
+    Finally, the patch itself, as generated by "hg export".'''
     def prompt(prompt, default = None, rest = ': ', empty_ok = False):
         if default: prompt += ' [%s]' % default
         prompt += rest
@@ -97,7 +111,7 @@ def patchbomb(ui, repo, *revs, **opts):
             if r: return r
             if default is not None: return default
             if empty_ok: return r
-            ui.warn('Please enter a valid value.\n')
+            ui.warn(_('Please enter a valid value.\n'))
 
     def confirm(s):
         if not prompt(s, default = 'y', rest = '? ').lower().startswith('y'):
@@ -109,7 +123,7 @@ def patchbomb(ui, repo, *revs, **opts):
             if summary:
                 ui.write(summary, '\n')
                 ui.write(s, '\n')
-            confirm('Does the diffstat above look okay')
+            confirm(_('Does the diffstat above look okay'))
         return s
 
     def makepatch(patch, idx, total):
@@ -162,20 +176,20 @@ def patchbomb(ui, repo, *revs, **opts):
             self.container.append(''.join(self.lines).split('\n'))
             self.lines = []
 
-    commands.export(ui, repo, *args, **{'output': exportee(patches),
+    commands.export(ui, repo, *revs, **{'output': exportee(patches),
                                         'switch_parent': False,
                                         'text': None})
 
     jumbo = []
     msgs = []
 
-    ui.write('This patch series consists of %d patches.\n\n' % len(patches))
+    ui.write(_('This patch series consists of %d patches.\n\n') % len(patches))
 
     for p, i in zip(patches, range(len(patches))):
         jumbo.extend(p)
         msgs.append(makepatch(p, i + 1, len(patches)))
 
-    ui.write('\nWrite the introductory message for the patch series.\n\n')
+    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()))
@@ -193,7 +207,7 @@ def patchbomb(ui, repo, *revs, **opts):
     to = getaddrs('to', 'To')
     cc = getaddrs('cc', 'Cc', '')
 
-    ui.write('Finish with ^D or a dot on a line by itself.\n\n')
+    ui.write(_('Finish with ^D or a dot on a line by itself.\n\n'))
 
     body = []
 
@@ -208,12 +222,12 @@ def patchbomb(ui, repo, *revs, **opts):
     ui.write('\n')
 
     if opts['diffstat']:
-        d = cdiffstat('Final summary:\n', jumbo)
+        d = cdiffstat(_('Final summary:\n'), jumbo)
         if d: msg.attach(MIMEText(d))
 
     msgs.insert(0, msg)
 
-    if not opts['test']:
+    if not opts['test'] and not opts['mbox']:
         s = smtplib.SMTP()
         s.connect(host = ui.config('smtp', 'host', 'mail'),
                   port = int(ui.config('smtp', 'port', 25)))
@@ -227,6 +241,7 @@ def patchbomb(ui, repo, *revs, **opts):
             s.login(username, password)
     parent = None
     tz = time.strftime('%z')
+    sender_addr = parseaddr(sender)[1]
     for m in msgs:
         try:
             m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
@@ -241,36 +256,36 @@ def patchbomb(ui, repo, *revs, **opts):
         m['From'] = sender
         m['To'] = ', '.join(to)
         if cc: m['Cc'] = ', '.join(cc)
-        ui.status('Sending ', m['Subject'], ' ...\n')
         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')
             fp.close()
+        elif opts['mbox']:
+            ui.status('Writing ', m['Subject'], ' ...\n')
+            fp = open(opts['mbox'], m.has_key('In-Reply-To') and 'ab+' or 'wb+')
+            date = time.asctime(time.localtime(start_time))
+            fp.write('From %s %s\n' % (sender_addr, date))
+            fp.write(m.as_string(0))
+            fp.write('\n\n')
+            fp.close()
         else:
+            ui.status('Sending ', m['Subject'], ' ...\n')
             s.sendmail(sender, to + cc, m.as_string(0))
-    if not opts['test']:
+    if not opts['test'] and not opts['mbox']:
         s.close()
 
-if __name__ == '__main__':
-    optspec = [('c', 'cc', [], 'email addresses of copy recipients'),
-               ('d', 'diffstat', None, 'add diffstat output to messages'),
-               ('f', 'from', '', 'email address of sender'),
-               ('', 'plain', None, 'omit hg patch header'),
-               ('n', 'test', None, 'print messages that would be sent'),
-               ('s', 'subject', '', 'subject of introductory message'),
-               ('t', 'to', [], 'email addresses of recipients')]
-    options = {}
-    try:
-        args = fancyopts.fancyopts(sys.argv[1:], commands.globalopts + optspec,
-                                   options)
-    except fancyopts.getopt.GetoptError, inst:
-        u = ui.ui()
-        u.warn('error: %s' % inst)
-        sys.exit(1)
-
-    u = ui.ui(options["verbose"], options["debug"], options["quiet"],
-              not options["noninteractive"])
-    repo = hg.repository(ui = u)
-
-    patchbomb(u, repo, *args, **options)
+cmdtable = {
+    'email':
+    (patchbomb,
+     [('c', 'cc', [], 'email addresses of copy recipients'),
+      ('d', 'diffstat', None, 'add diffstat output to messages'),
+      ('f', 'from', '', 'email address of sender'),
+      ('', 'plain', None, 'omit hg patch header'),
+      ('n', 'test', None, 'print messages that would be sent'),
+      ('m', 'mbox', '', 'write messages to mbox file instead of sending them'),
+      ('s', 'subject', '', 'subject of introductory message'),
+      ('t', 'to', [], 'email addresses of recipients')],
+     "hg email [OPTION]... [REV]...")
+    }
--- a/hgmerge
+++ b/hgmerge
@@ -17,127 +17,151 @@ fi
 
 # find decent versions of our utilities, insisting on the GNU versions where we
 # need to
+MERGE=merge
 DIFF3=gdiff3
 DIFF=gdiff
 PATCH=gpatch
 
+type $MERGE >/dev/null 2>&1 || MERGE=
 type $DIFF3 >/dev/null 2>&1 || DIFF3=diff3
 type $DIFF  >/dev/null 2>&1 || DIFF=diff
-type $PATCH  >/dev/null 2>&1 || PATCH=patch
+type $PATCH >/dev/null 2>&1 || PATCH=patch
 $DIFF3 --version >/dev/null 2>&1 || DIFF3=
 
-# Back up our file
-cp "$LOCAL" "$LOCAL.orig"
+# find optional visual utilities
+FILEMERGE='/Developer/Applications/Utilities/FileMerge.app/Contents/MacOS/FileMerge'
+KDIFF3=kdiff3
+TKDIFF=tkdiff
+
+type $FILEMERGE >/dev/null 2>&1 || FILEMERGE=
+type $KDIFF3    >/dev/null 2>&1 || KDIFF3=
+type $TKDIFF    >/dev/null 2>&1 || TKDIFF=
+
+# random part of names
+RAND="$RANDOM.$RANDOM.$RANDOM.$$"
+
+# temporary directory for diff+patch merge
+HGTMP="${TMPDIR-/tmp}/hgmerge.$RAND"
+
+# backup file
+BACKUP="$LOCAL.orig.$RAND"
+
+# file used to test for file change
+CHGTEST="$LOCAL.chg.$RAND"
+
+# put all your required cleanup here
+cleanup() {
+    rm -f "$BACKUP" "$CHGTEST"
+    rm -rf "$HGTMP"
+}
+
+# functions concerning program exit
+success() {
+    cleanup
+    exit 0
+}
+
+failure() {
+    echo "merge failed" 1>&2
+    mv "$BACKUP" "$LOCAL"
+    cleanup
+    exit 1
+}
+
+# Clean up when interrupted
+trap "failure" 1 2 3 6 15 # HUP INT QUIT ABRT TERM
+
+# Back up our file (and try hard to keep the mtime unchanged)
+mv "$LOCAL" "$BACKUP"
+cp "$BACKUP" "$LOCAL"
 
 # Attempt to do a non-interactive merge
-if type merge > /dev/null 2>&1; then
-    merge "$LOCAL" "$BASE" "$OTHER" 2> /dev/null && exit 0
-    cp "$LOCAL.orig" "$LOCAL"
+if [ -n "$MERGE" ]; then
+    $MERGE "$LOCAL" "$BASE" "$OTHER" 2> /dev/null && success
+    cp "$BACKUP" "$LOCAL"
 elif [ -n "$DIFF3" ]; then
-    echo $DIFF3 -m "$LOCAL.orig" "$BASE" "$OTHER"
-    $DIFF3 -m "$LOCAL.orig" "$BASE" "$OTHER" > "$LOCAL" && exit 0
+    echo $DIFF3 -m "$BACKUP" "$BASE" "$OTHER"
+    $DIFF3 -m "$BACKUP" "$BASE" "$OTHER" > "$LOCAL" && success
     if [ $? -eq 2 ]; then
         echo "$DIFF3 failed! Exiting." 1>&2
-        cp "$LOCAL.orig" "$LOCAL"
-        exit 1
+        cp "$BACKUP" "$LOCAL"
+        failure
     fi
-    cp "$LOCAL.orig" "$LOCAL"
+    cp "$BACKUP" "$LOCAL"
 fi
 
 # on MacOS X try FileMerge.app, shipped with Apple's developer tools
-# TODO: make proper temp files. foo.orig and foo.link are dangerous
-FILEMERGE='/Developer/Applications/Utilities/FileMerge.app/Contents/MacOS/FileMerge'
-if type "$FILEMERGE" > /dev/null 2>&1; then
-    cp "$LOCAL.orig" "$LOCAL"
-    ln "$LOCAL" "$LOCAL.link"
+if [ -n "$FILEMERGE" ]; then
+    cp "$BACKUP" "$LOCAL"
+    cp "$BACKUP" "$CHGTEST"
     # filemerge prefers the right by default
-    if ! "$FILEMERGE" -left "$OTHER" -right "$LOCAL" -ancestor "$BASE" -merge "$LOCAL"
+    $FILEMERGE -left "$OTHER" -right "$LOCAL" -ancestor "$BASE" -merge "$LOCAL"
+    [ $? -ne 0 ] && echo "FileMerge failed to launch" && failure
+    if test "$LOCAL" -nt "$CHGTEST"
     then
-        echo "FileMerge failed to launch"
-        exit 1
-    fi
-    if ! test "$LOCAL" -ef "$LOCAL.link"
-    then
-        rm "$LOCAL.orig" "$LOCAL.link"
-        exit 0
+        success
     else
-        rm "$LOCAL.link"
-        echo "$LOCAL is unchanged. Was the merge successful?"
+        echo "$LOCAL seems unchanged. Was the merge successful?"
         select answer in yes no
         do
-            if test "$answer" == "yes"
-            then
-                rm "$LOCAL.orig"
-                exit 0
-            else
-                exit 1
-            fi
+            test "$answer" == "yes" && success || failure
         done
-        exit 1
     fi
+    failure
 fi
 
 if [ -n "$DISPLAY" ]; then
     # try using kdiff3, which is fairly nice
-    if type kdiff3 > /dev/null 2>&1; then
-	kdiff3 --auto "$BASE" "$LOCAL" "$OTHER" -o "$LOCAL" || exit 1
-	exit 0
+    if [ -n "$KDIFF3" ]; then
+	$KDIFF3 --auto "$BASE" "$LOCAL" "$OTHER" -o "$LOCAL" || failure
+	success
     fi
 
     # try using tkdiff, which is a bit less sophisticated
-    if type tkdiff > /dev/null 2>&1; then
-	tkdiff "$LOCAL" "$OTHER" -a "$BASE" -o "$LOCAL" || exit 1
-	exit 0
+    if [ -n "$TKDIFF" ]; then
+	$TKDIFF "$LOCAL" "$OTHER" -a "$BASE" -o "$LOCAL" || failure
+	success
     fi
 fi
 
 # Attempt to do a merge with $EDITOR
-if type merge > /dev/null 2>&1; then
+if [ -n "$MERGE" ]; then
     echo "conflicts detected in $LOCAL"
-    merge "$LOCAL" "$BASE" "$OTHER" 2>/dev/null || $EDITOR "$LOCAL"
-    exit 0
+    $MERGE "$LOCAL" "$BASE" "$OTHER" 2>/dev/null || $EDITOR "$LOCAL"
+    success
 fi
 
 if [ -n "$DIFF3" ]; then
     echo "conflicts detected in $LOCAL"
-    $DIFF3 -m "$LOCAL.orig" "$BASE" "$OTHER" > "$LOCAL" || {
+    $DIFF3 -m "$BACKUP" "$BASE" "$OTHER" > "$LOCAL" || {
         case $? in
             1)
                 $EDITOR "$LOCAL" ;;
             2)  echo "$DIFF3 failed! Exiting." 1>&2
-                cp "$LOCAL.orig" "$LOCAL"
-                exit 1 ;;
+                cp "$BACKUP" "$LOCAL"
+                failure ;;
         esac
-        exit 0
+        success
     }
 fi
 
-HGTMP=""
-cleanup_exit() {
-    rm -rf "$HGTMP"
-}
-
 # attempt to manually merge with diff and patch
 if [ -n "$DIFF" -a -n "$PATCH" ]; then
-    # Remove temporary files even if we get interrupted
-    trap "cleanup_exit" 0 # normal exit
-    trap "exit 1" 1 2 3 6 15 # HUP INT QUIT ABRT TERM
 
-    HGTMP="${TMPDIR-/tmp}/hgmerge.$RANDOM.$RANDOM.$RANDOM.$$"
     (umask 077 && mkdir "$HGTMP") || {
-	echo "Could not create temporary directory! Exiting." 1>&2
-	exit 1
+	echo "Could not create temporary directory $HGTMP" 1>&2
+	failure
     }
 
     $DIFF -u "$BASE" "$OTHER" > "$HGTMP/diff" || :
     if $PATCH "$LOCAL" < "$HGTMP/diff"; then
-	exit 0
+	success
     else
 	# If rejects are empty after using the editor, merge was ok
-	$EDITOR "$LOCAL" "$LOCAL.rej" && test -s "$LOCAL.rej" || exit 0
+	$EDITOR "$LOCAL" "$LOCAL.rej" && test -s "$LOCAL.rej" || success
     fi
-    exit 1
+    failure
 fi
 
 echo "hgmerge: unable to find merge, tkdiff, kdiff3, or diff+patch!"
-exit 1
+failure
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -115,8 +115,8 @@ def walkchangerevs(ui, repo, pats, opts)
                     yield rev
 
         minrev, maxrev = min(revs), max(revs)
-        for file in files:
-            filelog = repo.file(file)
+        for file_ in files:
+            filelog = repo.file(file_)
             # A zero count may be a directory or deleted file, so
             # try to find matching entries on the slow path.
             if filelog.count() == 0:
@@ -127,7 +127,7 @@ def walkchangerevs(ui, repo, pats, opts)
                     if rev < minrev:
                         break
                     fncache.setdefault(rev, [])
-                    fncache[rev].append(file)
+                    fncache[rev].append(file_)
                     wanted[rev] = 1
     if slowpath:
         # The slow path checks files modified in every changeset.
@@ -261,7 +261,7 @@ def make_file(repo, r, pat, node=None,
                 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 @@ def dodiff(fp, ui, repo, node1, node2, f
     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:
@@ -405,6 +405,8 @@ def help_(ui, cmd=None, with_version=Fal
 
         # description
         doc = i[0].__doc__
+        if not doc:
+            doc = _("(No help text available)")
         if ui.quiet:
             doc = doc.splitlines(0)[0]
         ui.write("%s\n" % doc.rstrip())
@@ -445,10 +447,10 @@ def help_(ui, cmd=None, with_version=Fal
             f = f.lstrip("^")
             if not ui.debugflag and f.startswith("debug"):
                 continue
-            d = ""
-            if e[0].__doc__:
-                d = e[0].__doc__.splitlines(0)[0].rstrip()
-            h[f] = d
+            doc = e[0].__doc__
+            if not doc:
+                doc = _("(No help text available)")
+            h[f] = doc.splitlines(0)[0].rstrip()
             cmds[f] = c.lstrip("^")
 
         fns = h.keys()
@@ -516,6 +518,9 @@ def addremove(ui, repo, *pats, **opts):
     New files are ignored if they match any of the patterns in .hgignore. As
     with add, these changes take effect at the next commit.
     """
+    return addremove_lock(ui, repo, pats, opts)
+
+def addremove_lock(ui, repo, pats, opts, wlock=None):
     add, remove = [], []
     for src, abs, rel, exact in walk(repo, pats, opts):
         if src == 'f' and repo.dirstate.state(abs) == '?':
@@ -526,8 +531,8 @@ def addremove(ui, repo, *pats, **opts):
             remove.append(abs)
             if ui.verbose or not exact:
                 ui.status(_('removing %s\n') % ((pats and rel) or abs))
-    repo.add(add)
-    repo.remove(remove)
+    repo.add(add, wlock=wlock)
+    repo.remove(remove, wlock=wlock)
 
 def annotate(ui, repo, *pats, **opts):
     """show changeset information per file line
@@ -616,7 +621,7 @@ def bundle(ui, repo, fname, dest="defaul
     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")
@@ -719,8 +724,8 @@ def clone(ui, source, dest=None, **opts)
             # can end up with extra data in the cloned revlogs that's
             # not pointed to by changesets, thus causing verify to
             # fail
-            l1 = lock.lock(os.path.join(source, ".hg", "lock"))
-        except OSError:
+            l1 = other.lock()
+        except lock.LockException:
             copy = False
 
     if copy:
@@ -812,14 +817,19 @@ def docopy(ui, repo, pats, opts):
         reasons = {'?': _('is not managed'),
                    'a': _('has been marked for add'),
                    'r': _('has been marked for remove')}
-        reason = reasons.get(repo.dirstate.state(abs))
+        state = repo.dirstate.state(abs)
+        reason = reasons.get(state)
         if reason:
+            if state == 'a':
+                origsrc = repo.dirstate.copied(abs)
+                if origsrc is not None:
+                    return origsrc
             if exact:
                 ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
         else:
-            return True
-
-    def copy(abssrc, relsrc, target, exact):
+            return abs
+
+    def copy(origsrc, abssrc, relsrc, target, exact):
         abstarget = util.canonpath(repo.root, cwd, target)
         reltarget = util.pathto(cwd, abstarget)
         prevsrc = targets.get(abstarget)
@@ -858,7 +868,7 @@ def docopy(ui, repo, pats, opts):
         if ui.verbose or not exact:
             ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
         targets[abstarget] = abssrc
-        repo.copy(abssrc, abstarget)
+        repo.copy(origsrc, abstarget)
         copied.append((abssrc, relsrc, exact))
 
     def targetpathfn(pat, dest, srcs):
@@ -932,8 +942,9 @@ def docopy(ui, repo, pats, opts):
     for pat in pats:
         srcs = []
         for tag, abssrc, relsrc, exact in walk(repo, [pat], opts):
-            if okaytocopy(abssrc, relsrc, exact):
-                srcs.append((abssrc, relsrc, exact))
+            origsrc = okaytocopy(abssrc, relsrc, exact)
+            if origsrc:
+                srcs.append((origsrc, abssrc, relsrc, exact))
         if not srcs:
             continue
         copylist.append((tfn(pat, dest, srcs), srcs))
@@ -941,8 +952,8 @@ def docopy(ui, repo, pats, opts):
         raise util.Abort(_('no files to copy'))
 
     for targetpath, srcs in copylist:
-        for abssrc, relsrc, exact in srcs:
-            copy(abssrc, relsrc, targetpath(abssrc), exact)
+        for origsrc, abssrc, relsrc, exact in srcs:
+            copy(origsrc, abssrc, relsrc, targetpath(abssrc), exact)
 
     if errors:
         ui.warn(_('(consider using --after)\n'))
@@ -974,6 +985,18 @@ def debugancestor(ui, index, rev1, rev2)
     a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
     ui.write("%d:%s\n" % (r.rev(a), hex(a)))
 
+def debugrebuildstate(ui, repo, rev=None):
+    """rebuild the dirstate as it would look like for the given revision"""
+    if not rev:
+        rev = repo.changelog.tip()
+    else:
+        rev = repo.lookup(rev)
+    change = repo.changelog.read(rev)
+    n = change[0]
+    files = repo.manifest.readflags(n)
+    wlock = repo.wlock()
+    repo.dirstate.rebuild(rev, files.iteritems())
+
 def debugcheckstate(ui, repo):
     """validate the correctness of the current dirstate"""
     parent1, parent2 = repo.dirstate.parents()
@@ -1134,7 +1157,7 @@ def diff(ui, repo, *pats, **opts):
     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)
@@ -1278,6 +1301,7 @@ def grep(ui, repo, pattern, *pats, **opt
             s = linestate(line, lnum, cstart, cend)
             m[s] = s
 
+    # FIXME: prev isn't used, why ?
     prev = {}
     ucache = {}
     def display(fn, rev, states, prevstates):
@@ -1587,7 +1611,19 @@ def log(ui, repo, *pats, **opts):
                 self.write(*args)
         def __getattr__(self, key):
             return getattr(self.ui, key)
+
     changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
+
+    if opts['limit']:
+        try:
+            limit = int(opts['limit'])
+        except ValueError:
+            raise util.Abort(_('limit must be a positive integer'))
+        if limit <= 0: raise util.Abort(_('limit must be positive'))
+    else:
+        limit = sys.maxint
+    count = 0
+
     for st, rev, fns in changeiter:
         if st == 'window':
             du = dui(ui)
@@ -1601,7 +1637,6 @@ def log(ui, repo, *pats, **opts):
             if opts['only_merges'] and len(parents) != 2:
                 continue
 
-            br = None
             if opts['keyword']:
                 changes = getchange(rev)
                 miss = 0
@@ -1614,7 +1649,8 @@ def log(ui, repo, *pats, **opts):
                 if miss:
                     continue
 
-            if opts['branch']:
+            br = None
+            if opts['branches']:
                 br = repo.branchlookup([repo.changelog.node(rev)])
 
             show_changeset(du, repo, rev, brinfo=br)
@@ -1623,8 +1659,11 @@ def log(ui, repo, *pats, **opts):
                 dodiff(du, du, repo, prev, changenode, match=matchfn)
                 du.write("\n\n")
         elif st == 'iter':
-            for args in du.hunk[rev]:
-                ui.write(*args)
+            if count == limit: break
+            if du.hunk[rev]:
+                count += 1
+                for args in du.hunk[rev]:
+                    ui.write(*args)
 
 def manifest(ui, repo, rev=None):
     """output the latest or given revision of the project manifest
@@ -1675,7 +1714,7 @@ def outgoing(ui, repo, dest="default-pus
             dodiff(ui, ui, repo, prev, n)
             ui.write("\n")
 
-def parents(ui, repo, rev=None):
+def parents(ui, repo, rev=None, branches=None):
     """show the parents of the working dir or revision
 
     Print the working directory's parent revisions.
@@ -1685,9 +1724,12 @@ def parents(ui, repo, rev=None):
     else:
         p = repo.dirstate.parents()
 
+    br = None
+    if branches 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
@@ -1990,7 +2032,7 @@ def serve(ui, repo, **opts):
                 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:
@@ -2013,6 +2055,16 @@ def serve(ui, repo, **opts):
         if opts[o]:
             ui.setconfig("web", o, opts[o])
 
+    if opts['daemon'] and not opts['daemon_pipefds']:
+        rfd, wfd = os.pipe()
+        args = sys.argv[:]
+        args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
+        pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
+                         args[0], args)
+        os.close(wfd)
+        os.read(rfd, 1)
+        os._exit(0)
+
     try:
         httpd = hgweb.create_server(repo)
     except socket.error, inst:
@@ -2031,6 +2083,25 @@ def serve(ui, repo, **opts):
             ui.status(_('listening at http://%s:%d/\n') % (addr, port))
         else:
             ui.status(_('listening at http://%s/\n') % addr)
+
+    if opts['pid_file']:
+        fp = open(opts['pid_file'], 'w')
+        fp.write(str(os.getpid()))
+        fp.close()
+
+    if opts['daemon_pipefds']:
+        rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
+        os.close(rfd)
+        os.write(wfd, 'y')
+        os.close(wfd)
+        sys.stdout.flush()
+        sys.stderr.flush()
+        fd = os.open(util.nulldev, os.O_RDWR)
+        if fd != 0: os.dup2(fd, 0)
+        if fd != 1: os.dup2(fd, 1)
+        if fd != 2: os.dup2(fd, 2)
+        if fd not in (0, 1, 2): os.close(fd)
+
     httpd.serve_forever()
 
 def status(ui, repo, *pats, **opts):
@@ -2107,8 +2178,12 @@ def tag(ui, repo, name, rev_=None, **opt
         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():
@@ -2124,6 +2199,7 @@ def tag(ui, repo, name, rev_=None, **opt
                _("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))
 
@@ -2144,13 +2220,18 @@ def tags(ui, repo):
             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)
+    br = None
+    if opts['branches']:
+        br = repo.branchlookup([n])
+    show_changeset(ui, repo, changenode=n, brinfo=br)
+    if opts['patch']:
+        dodiff(ui, ui, repo, repo.changelog.parents(n)[0], n)
 
 def unbundle(ui, repo, fname, **opts):
     """apply a changegroup file
@@ -2308,6 +2389,10 @@ table = {
            _('forcibly copy over an existing managed file'))],
          _('hg copy [OPTION]... [SOURCE]... DEST')),
     "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
+    "debugrebuildstate":
+        (debugrebuildstate,
+         [('r', 'rev', "", _("revision to rebuild to"))],
+         _('debugrebuildstate [-r REV] [REV]')),
     "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
     "debugconfig": (debugconfig, [], _('debugconfig')),
     "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
@@ -2326,7 +2411,12 @@ table = {
          [('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,
@@ -2354,7 +2444,7 @@ table = {
          _('hg grep [OPTION]... PATTERN [FILE]...')),
     "heads":
         (heads,
-         [('b', 'branches', None, _('find branch info')),
+         [('b', 'branches', None, _('show branches')),
           ('r', 'rev', '', _('show only heads which are descendants of rev'))],
          _('hg heads [-b] [-r <rev>]')),
     "help": (help_, [], _('hg help [COMMAND]')),
@@ -2388,8 +2478,9 @@ table = {
         (log,
          [('I', 'include', [], _('include names matching the given patterns')),
           ('X', 'exclude', [], _('exclude names matching the given patterns')),
-          ('b', 'branch', None, _('show branches')),
+          ('b', 'branches', None, _('show branches')),
           ('k', 'keyword', [], _('search for a keyword')),
+          ('l', 'limit', '', _('limit number of changes displayed')),
           ('r', 'rev', [], _('show the specified revision or range')),
           ('M', 'no-merges', None, _('do not show merges')),
           ('m', 'only-merges', None, _('show only merges')),
@@ -2401,7 +2492,10 @@ table = {
           ('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', 'branches', None, _('show branches'))],
+         _('hg parents [-b] [REV]')),
     "paths": (paths, [], _('hg paths [NAME]')),
     "^pull":
         (pull,
@@ -2452,11 +2546,14 @@ table = {
     "^serve":
         (serve,
          [('A', 'accesslog', '', _('name of access log file to write to')),
+          ('d', 'daemon', None, _('run server in background')),
+          ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
           ('E', 'errorlog', '', _('name of error log file to write to')),
           ('p', 'port', 0, _('port to use (default: 8000)')),
           ('a', 'address', '', _('address to use')),
           ('n', 'name', '',
            _('name to show in web pages (default: working dir)')),
+          ('', 'pid-file', '', _('name of file to write process ID to')),
           ('', 'stdio', None, _('for remote clients')),
           ('t', 'templates', '', _('web templates to use')),
           ('', 'style', '', _('template style to use')),
@@ -2484,7 +2581,11 @@ table = {
           ('r', 'rev', '', _('revision to tag'))],
          _('hg tag [-r REV] [OPTION]... NAME')),
     "tags": (tags, [], _('hg tags')),
-    "tip": (tip, [], _('hg tip')),
+    "tip":
+        (tip,
+         [('b', 'branches', None, _('show branches')),
+          ('p', 'patch', None, _('show patch'))],
+         _('hg [-b] [-p] tip')),
     "unbundle":
         (unbundle,
          [('u', 'update', None,
@@ -2524,17 +2625,20 @@ norepo = ("clone init version help debug
 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/dirstate.py
+++ b/mercurial/dirstate.py
@@ -197,6 +197,19 @@ class dirstate(object):
 
     def clear(self):
         self.map = {}
+        self.copies = {}
+        self.markdirty()
+
+    def rebuild(self, parent, files):
+        self.clear()
+        umask = os.umask(0)
+        os.umask(umask)
+        for f, mode in files:
+            if mode:
+                self.map[f] = ('n', ~umask, -1, 0)
+            else:
+                self.map[f] = ('n', ~umask & 0666, -1, 0)
+        self.pl = (parent, nullid)
         self.markdirty()
 
     def write(self):
@@ -270,11 +283,11 @@ class dirstate(object):
         elif not dc:
             dc = self.filterfiles(files)
 
-        def statmatch(file, stat):
-            file = util.pconvert(file)
-            if file not in dc and self.ignore(file):
+        def statmatch(file_, stat):
+            file_ = util.pconvert(file_)
+            if file_ not in dc and self.ignore(file_):
                 return False
-            return match(file)
+            return match(file_)
 
         return self.walkhelper(files=files, statmatch=statmatch, dc=dc)
 
@@ -350,9 +363,9 @@ class dirstate(object):
                 continue
             if stat.S_ISDIR(st.st_mode):
                 cmp1 = (lambda x, y: cmp(x[1], y[1]))
-                sorted = [ x for x in findfiles(f) ]
-                sorted.sort(cmp1)
-                for e in sorted:
+                sorted_ = [ x for x in findfiles(f) ]
+                sorted_.sort(cmp1)
+                for e in sorted_:
                     yield e
             else:
                 ff = util.normpath(ff)
@@ -380,7 +393,7 @@ class dirstate(object):
 
         for src, fn, st in self.statwalk(files, match):
             try:
-                type, mode, size, time = self[fn]
+                type_, mode, size, time = self[fn]
             except KeyError:
                 unknown.append(fn)
                 continue
@@ -399,22 +412,23 @@ class dirstate(object):
                         nonexistent = False
                 # XXX: what to do with file no longer present in the fs
                 # who are not removed in the dirstate ?
-                if nonexistent and type in "nm":
+                if nonexistent and type_ in "nm":
                     deleted.append(fn)
                     continue
             # check the common case first
-            if type == 'n':
+            if type_ == 'n':
                 if not st:
                     st = os.stat(fn)
-                if size != st.st_size or (mode ^ st.st_mode) & 0100:
+                if size >= 0 and (size != st.st_size
+                                  or (mode ^ st.st_mode) & 0100):
                     modified.append(fn)
                 elif time != st.st_mtime:
                     lookup.append(fn)
-            elif type == 'm':
+            elif type_ == 'm':
                 modified.append(fn)
-            elif type == 'a':
+            elif type_ == 'a':
                 added.append(fn)
-            elif type == 'r':
+            elif type_ == 'r':
                 removed.append(fn)
 
         return (lookup, modified, added, removed, deleted, unknown)
--- a/mercurial/hgweb.py
+++ b/mercurial/hgweb.py
@@ -298,19 +298,25 @@ class hgweb(object):
 
     def changelog(self, pos):
         def changenav(**map):
-            def seq(factor=1):
-                yield 1 * factor
-                yield 3 * factor
-                #yield 5 * factor
+            def seq(factor, maxchanges=None):
+                if maxchanges:
+                    yield maxchanges
+                    if maxchanges >= 20 and maxchanges <= 40:
+                        yield 50
+                else:
+                    yield 1 * factor
+                    yield 3 * factor
                 for f in seq(factor * 10):
                     yield f
 
             l = []
-            for f in seq():
-                if f < self.maxchanges / 2:
+            last = 0
+            for f in seq(1, self.maxchanges):
+                if f < self.maxchanges or f <= last:
                     continue
                 if f > count:
                     break
+                last = f
                 r = "%d" % f
                 if pos + f < count:
                     l.append(("+" + r, pos + f))
@@ -958,7 +964,7 @@ class hgweb(object):
                 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
+++ b/mercurial/httprepo.py
@@ -119,7 +119,7 @@ class httprepository(remoterepository):
             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
+++ b/mercurial/localrepo.py
@@ -48,30 +48,36 @@ class localrepository(object):
         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
 
@@ -225,7 +231,7 @@ class localrepository(object):
                                        self.join("journal"), after)
 
     def recover(self):
-        lock = self.lock()
+        l = self.lock()
         if os.path.exists(self.join("journal")):
             self.ui.status(_("rolling back interrupted transaction\n"))
             transaction.rollback(self.opener, self.join("journal"))
@@ -236,9 +242,10 @@ class localrepository(object):
             self.ui.warn(_("no interrupted transaction available\n"))
             return False
 
-    def undo(self):
-        wlock = self.wlock()
-        lock = self.lock()
+    def undo(self, wlock=None):
+        if not wlock:
+            wlock = self.wlock()
+        l = self.lock()
         if os.path.exists(self.join("undo")):
             self.ui.status(_("rolling back last transaction\n"))
             transaction.rollback(self.opener, self.join("undo"))
@@ -247,27 +254,46 @@ class localrepository(object):
         else:
             self.ui.warn(_("no undo information available\n"))
 
-    def lock(self, wait=1):
+    def do_lock(self, lockname, wait, releasefn=None, acquirefn=None):
         try:
-            return lock.lock(self.join("lock"), 0)
-        except lock.LockHeld, inst:
-            if wait:
-                self.ui.warn(_("waiting for lock held by %s\n") % inst.args[0])
-                return lock.lock(self.join("lock"), wait)
-            raise inst
-
-    def wlock(self, wait=1):
-        try:
-            wlock = lock.lock(self.join("wlock"), 0, self.dirstate.write)
+            l = lock.lock(self.join(lockname), 0, releasefn)
         except lock.LockHeld, inst:
             if not wait:
                 raise inst
             self.ui.warn(_("waiting for lock held by %s\n") % inst.args[0])
-            wlock = lock.lock(self.join("wlock"), wait, self.dirstate.write)
-        self.dirstate.read()
-        return wlock
+            l = lock.lock(self.join(lockname), wait, releasefn)
+        if acquirefn:
+            acquirefn()
+        return l
+
+    def lock(self, wait=1):
+        return self.do_lock("lock", wait)
+
+    def wlock(self, wait=1):
+        return self.do_lock("wlock", wait,
+                            self.dirstate.write,
+                            self.dirstate.read)
 
-    def rawcommit(self, files, text, user, date, p1=None, p2=None):
+    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
         p2 = p2 or self.dirstate.parents()[1] or nullid
@@ -283,8 +309,9 @@ class localrepository(object):
         else:
             update_dirstate = 0
 
-        wlock = self.wlock()
-        lock = self.lock()
+        if not wlock:
+            wlock = self.wlock()
+        l = self.lock()
         tr = self.transaction()
         mm = m1.copy()
         mfm = mf1.copy()
@@ -296,27 +323,10 @@ class localrepository(object):
                 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)
@@ -340,7 +350,7 @@ class localrepository(object):
             self.dirstate.setparents(n, nullid)
 
     def commit(self, files=None, text="", user=None, date=None,
-               match=util.always, force=False):
+               match=util.always, force=False, wlock=None):
         commit = []
         remove = []
         changed = []
@@ -370,11 +380,15 @@ class localrepository(object):
             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)
 
-        wlock = self.wlock()
-        lock = self.lock()
+        self.hook("precommit", throw=True, parent1=xp1, parent2=xp2)
+
+        if not wlock:
+            wlock = self.wlock()
+        l = self.lock()
         tr = self.transaction()
 
         # check in files
@@ -400,22 +414,9 @@ class localrepository(object):
                 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)
@@ -437,29 +438,34 @@ class localrepository(object):
         new.sort()
 
         if not text:
-            edittext = ""
+            edittext = [""]
             if p2 != nullid:
-                edittext += "HG: branch merge\n"
-            edittext += "\n" + "HG: manifest hash %s\n" % hex(mn)
-            edittext += "".join(["HG: changed %s\n" % f for f in changed])
-            edittext += "".join(["HG: removed %s\n" % f for f in remove])
+                edittext.append("HG: branch merge")
+            edittext.extend(["HG: changed %s" % f for f in changed])
+            edittext.extend(["HG: removed %s" % f for f in remove])
             if not changed and not remove:
-                edittext += "HG: no files changed\n"
-            edittext = self.ui.edit(edittext)
+                edittext.append("HG: no files changed")
+            edittext.append("")
+            # run editor in the repository root
+            olddir = os.getcwd()
+            os.chdir(self.root)
+            edittext = self.ui.edit("\n".join(edittext))
+            os.chdir(olddir)
             if not edittext.rstrip():
                 return None
             text = edittext
 
         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):
@@ -476,7 +482,8 @@ class localrepository(object):
             for src, fn in self.dirstate.walk(files, match):
                 yield src, fn
 
-    def changes(self, node1=None, node2=None, files=[], match=util.always):
+    def changes(self, node1=None, node2=None, files=[], match=util.always,
+                wlock=None):
         """return changes between two nodes or node and working directory
 
         If node1 is None, use the first dirstate parent instead.
@@ -498,10 +505,11 @@ class localrepository(object):
 
         # are we comparing the working directory?
         if not node2:
-            try:
-                wlock = self.wlock(wait=0)
-            except lock.LockHeld:
-                wlock = None
+            if not wlock:
+                try:
+                    wlock = self.wlock(wait=0)
+                except lock.LockException:
+                    wlock = None
             lookup, modified, added, removed, deleted, unknown = (
                 self.dirstate.changes(files, match))
 
@@ -550,8 +558,9 @@ class localrepository(object):
             l.sort()
         return (modified, added, removed, deleted, unknown)
 
-    def add(self, list):
-        wlock = self.wlock()
+    def add(self, list, wlock=None):
+        if not wlock:
+            wlock = self.wlock()
         for f in list:
             p = self.wjoin(f)
             if not os.path.exists(p):
@@ -564,15 +573,16 @@ class localrepository(object):
             else:
                 self.dirstate.update([f], "a")
 
-    def forget(self, list):
-        wlock = self.wlock()
+    def forget(self, list, wlock=None):
+        if not wlock:
+            wlock = self.wlock()
         for f in list:
             if self.dirstate.state(f) not in 'ai':
                 self.ui.warn(_("%s not added!\n") % f)
             else:
                 self.dirstate.forget([f])
 
-    def remove(self, list, unlink=False):
+    def remove(self, list, unlink=False, wlock=None):
         if unlink:
             for f in list:
                 try:
@@ -580,25 +590,26 @@ class localrepository(object):
                 except OSError, inst:
                     if inst.errno != errno.ENOENT:
                         raise
-        wlock = self.wlock()
+        if not wlock:
+            wlock = self.wlock()
         for f in list:
             p = self.wjoin(f)
             if os.path.exists(p):
                 self.ui.warn(_("%s still exists!\n") % f)
             elif self.dirstate.state(f) == 'a':
-                self.ui.warn(_("%s never committed!\n") % f)
                 self.dirstate.forget([f])
             elif f not in self.dirstate:
                 self.ui.warn(_("%s not tracked!\n") % f)
             else:
                 self.dirstate.update([f], "r")
 
-    def undelete(self, list):
+    def undelete(self, list, wlock=None):
         p = self.dirstate.parents()[0]
         mn = self.changelog.read(p)[0]
         mf = self.manifest.readflags(mn)
         m = self.manifest.read(mn)
-        wlock = self.wlock()
+        if not wlock:
+            wlock = self.wlock()
         for f in list:
             if self.dirstate.state(f) not in  "r":
                 self.ui.warn("%s not removed!\n" % f)
@@ -608,14 +619,15 @@ class localrepository(object):
                 util.set_exec(self.wjoin(f), mf[f])
                 self.dirstate.update([f], "n")
 
-    def copy(self, source, dest):
+    def copy(self, source, dest, wlock=None):
         p = self.wjoin(dest)
         if not os.path.exists(p):
             self.ui.warn(_("%s does not exist!\n") % dest)
         elif not os.path.isfile(p):
             self.ui.warn(_("copy failed: %s is not a file\n") % dest)
         else:
-            wlock = self.wlock()
+            if not wlock:
+                wlock = self.wlock()
             if self.dirstate.state(dest) == '?':
                 self.dirstate.update([dest], "a")
             self.dirstate.copy(source, dest)
@@ -919,7 +931,7 @@ class localrepository(object):
         return subset
 
     def pull(self, remote, heads=None):
-        lock = self.lock()
+        l = self.lock()
 
         # if we have an empty repo, fetch everything
         if self.changelog.tip() == nullid:
@@ -933,13 +945,13 @@ class localrepository(object):
             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):
-        lock = remote.lock()
+        l = remote.lock()
 
         base = {}
         heads = remote.heads()
@@ -960,10 +972,10 @@ class localrepository(object):
                                  " 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.
@@ -975,6 +987,8 @@ class localrepository(object):
         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
@@ -1227,14 +1241,19 @@ class localrepository(object):
             # 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])
@@ -1286,6 +1305,7 @@ class localrepository(object):
                         yield chnk
 
             yield struct.pack(">l", 0)
+            self.hook('outgoing', node=hex(nodes[0]), source=source)
 
         return util.chunkbuffer(gengroup())
 
@@ -1321,6 +1341,9 @@ class localrepository(object):
 
         if not source:
             return
+
+        self.hook('prechangegroup', throw=True)
+
         changesets = files = revisions = 0
 
         tr = self.transaction()
@@ -1363,21 +1386,19 @@ class localrepository(object):
                          " 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("commit", node=hex(self.changelog.node(i)))
-
-        return
+                self.hook("incoming", node=hex(self.changelog.node(i)))
 
     def update(self, node, allow=False, force=False, choose=None,
-               moddirstate=True, forcemerge=False):
+               moddirstate=True, forcemerge=False, wlock=None):
         pl = self.dirstate.parents()
         if not force and pl[1] != nullid:
             self.ui.warn(_("aborting: outstanding uncommitted merges\n"))
@@ -1399,6 +1420,13 @@ class localrepository(object):
 
         modified, added, removed, deleted, unknown = self.changes()
 
+        # is this a jump, or a merge?  i.e. is there a linear path
+        # from p1 to p2?
+        linear_path = (pa == p1 or pa == p2)
+
+        if allow and linear_path:
+            raise util.Abort(_("there is nothing to merge, "
+                               "just use 'hg update'"))
         if allow and not forcemerge:
             if modified or added or removed:
                 raise util.Abort(_("outstanding uncommited changes"))
@@ -1411,10 +1439,6 @@ class localrepository(object):
                         raise util.Abort(_("'%s' already exists in the working"
                                            " dir and differs from remote") % f)
 
-        # is this a jump, or a merge?  i.e. is there a linear path
-        # from p1 to p2?
-        linear_path = (pa == p1 or pa == p2)
-
         # resolve the manifest to determine which files
         # we care about merging
         self.ui.note(_("resolving manifests\n"))
@@ -1436,7 +1460,7 @@ class localrepository(object):
             mw[f] = ""
             mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
 
-        if moddirstate:
+        if moddirstate and not wlock:
             wlock = self.wlock()
 
         for f in deleted + removed:
--- a/mercurial/lock.py
+++ b/mercurial/lock.py
@@ -5,10 +5,14 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-import os, time
+import errno, os, time
 import util
 
-class LockHeld(Exception):
+class LockException(Exception):
+    pass
+class LockHeld(LockException):
+    pass
+class LockUnavailable(LockException):
     pass
 
 class lock(object):
@@ -38,8 +42,11 @@ class lock(object):
         try:
             util.makelock(str(pid), self.f)
             self.held = 1
-        except (OSError, IOError):
-            raise LockHeld(util.readlock(self.f))
+        except (OSError, IOError), why:
+            if why.errno == errno.EEXIST:
+                raise LockHeld(util.readlock(self.f))
+            else:
+                raise LockUnavailable(why)
 
     def release(self):
         if self.held:
--- a/mercurial/mdiff.py
+++ b/mercurial/mdiff.py
@@ -18,16 +18,22 @@ def unidiff(a, ad, b, bd, fn, r=None, te
 
     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
+++ b/mercurial/mpatch.c
@@ -43,6 +43,7 @@ static uint32_t ntohl(uint32_t x)
 #endif
 
 static char mpatch_doc[] = "Efficient binary patching.";
+static PyObject *mpatch_Error;
 
 struct frag {
 	int start, end, len;
@@ -65,8 +66,11 @@ static struct flist *lalloc(int size)
 			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 @@ static struct flist *decode(char *bin, i
 
 	/* 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 @@ static struct flist *decode(char *bin, i
 		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 @@ static int calcsize(int len, struct flis
 	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 @@ static int calcsize(int len, struct flis
 	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 @@ static void apply(char *buf, char *orig,
 		f++;
 	}
 	memcpy(p, orig + last, len - last);
+	return 1;
 }
 
 /* recursively generate a patch of all bins between start and end */
@@ -304,16 +331,25 @@ patches(PyObject *self, PyObject *args)
 
 	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 @@ PyMODINIT_FUNC
 initmpatch(void)
 {
 	Py_InitModule3("mpatch", methods, mpatch_doc);
+	mpatch_Error = PyErr_NewException("mpatch.mpatchError", NULL, NULL);
 }
 
--- a/mercurial/revlog.py
+++ b/mercurial/revlog.py
@@ -624,12 +624,10 @@ class revlog(object):
             # we store negative distances because heap returns smallest member
             h = [(-dist[node], node)]
             seen = {}
-            earliest = self.count()
             while h:
                 d, n = heapq.heappop(h)
                 if n not in seen:
                     seen[n] = 1
-                    r = self.rev(n)
                     yield (-d, n)
                     for p in self.parents(n):
                         heapq.heappush(h, (-dist[p], p))
@@ -690,11 +688,6 @@ class revlog(object):
         p = self.parents(self.node(revs[0]))[0]
         revs.insert(0, self.rev(p))
 
-        # helper to reconstruct intermediate versions
-        def construct(text, base, rev):
-            bins = [self.chunk(r) for r in xrange(base + 1, rev + 1)]
-            return mdiff.patches(text, bins)
-
         # build deltas
         for d in xrange(0, len(revs) - 1):
             a, b = revs[d], revs[d + 1]
@@ -738,10 +731,10 @@ class revlog(object):
         base = prev = -1
         start = end = measure = 0
         if r:
-            start = self.start(self.base(t))
+            base = self.base(t)
+            start = self.start(base)
             end = self.end(t)
-            measure = self.length(self.base(t))
-            base = self.base(t)
+            measure = self.length(base)
             prev = self.tip()
 
         transaction.add(self.datafile, end)
@@ -793,14 +786,15 @@ class revlog(object):
                     raise RevlogError(_("consistency error adding group"))
                 measure = len(text)
             else:
-                e = (end, len(cdelta), self.base(t), link, p1, p2, node)
+                e = (end, len(cdelta), base, link, p1, p2, node)
                 self.index.append(e)
                 self.nodemap[node] = r
                 dfh.write(cdelta)
                 ifh.write(struct.pack(indexformat, *e))
 
             t, r, chain, prev = r, r + 1, node, node
-            start = self.start(self.base(t))
+            base = self.base(t)
+            start = self.start(base)
             end = self.end(t)
 
         dfh.close()
@@ -828,6 +822,7 @@ class revlog(object):
 
         # then reset internal state in memory to forget those revisions
         self.cache = None
+        self.chunkcache = None
         for p in self.index[rev:]:
             del self.nodemap[p[6]]
         del self.index[rev:]
--- a/mercurial/sshrepo.py
+++ b/mercurial/sshrepo.py
@@ -110,7 +110,7 @@ class sshrepository(remoterepository):
         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/setup.py
+++ b/setup.py
@@ -81,7 +81,7 @@ try:
           author='Matt Mackall',
           author_email='mpm@selenic.com',
           url='http://selenic.com/mercurial',
-          description='scalable distributed SCM',
+          description='Scalable distributed SCM',
           license='GNU GPL',
           packages=['mercurial', 'hgext'],
           ext_modules=[Extension('mercurial.mpatch', ['mercurial/mpatch.c']),
@@ -92,6 +92,10 @@ try:
                        glob.glob('templates/*.tmpl'))],
           cmdclass=cmdclass,
           scripts=['hg', 'hgmerge'],
+          options=dict(bdist_mpkg=dict(zipdist=True,
+                                       license='COPYING',
+                                       readme='contrib/macosx/Readme.html',
+                                       welcome='contrib/macosx/Welcome.html')),
           **py2exe_opts)
 finally:
     mercurial.version.forget_version()
--- a/templates/changelog-gitweb.tmpl
+++ b/templates/changelog-gitweb.tmpl
@@ -27,4 +27,8 @@
 
 #entries%changelogentry#
 
+<div class="page_nav">
+#changenav%naventry#<br/>
+</div>
+
 #footer#
--- a/templates/changelogentry-gitweb.tmpl
+++ b/templates/changelogentry-gitweb.tmpl
@@ -5,7 +5,7 @@
 <div class="log_link">
 <a href="?cmd=changeset;node=#node#;style=gitweb">changeset</a><br/>
 </div>
-<i>#author|obfuscate# [#date|rfc822date#]</i><br/>
+<i>#author|obfuscate# [#date|rfc822date#] rev #rev#</i><br/>
 </div>
 <div class="log_body">
 #desc|escape|addbreaks#
--- a/templates/fileannotate-gitweb.tmpl
+++ b/templates/fileannotate-gitweb.tmpl
@@ -10,7 +10,7 @@
 </div>
 
 <div class="page_nav">
-<a href="?cmd=summary;style=gitweb">summary</a> | <a href="?cmd=changelog;style=gitweb">changelog</a> | <a href="?cmd=tags;style=gitweb">tags</a> | <a href="?cmd=manifest;manifest=#manifest#;path=#path|urlescape#;style=gitweb">manifest</a> | <a href="?cmd=changeset;node=#node#;style=gitweb">changeset</a> | <a href="?cmd=file;file=#file|urlescape#;filenode=#filenode#;style=gitweb">file</a> | <a href="?cmd=filelog;file=#file|urlescape#;filenode=#filenode#;style=gitweb">revisions</a> | annotate<br/>
+<a href="?cmd=summary;style=gitweb">summary</a> | <a href="?cmd=changelog;style=gitweb">changelog</a> | <a href="?cmd=tags;style=gitweb">tags</a> | <a href="?cmd=manifest;manifest=#manifest#;path=#path|urlescape#;style=gitweb">manifest</a> | <a href="?cmd=changeset;node=#node#;style=gitweb">changeset</a> | <a href="?cmd=file;file=#file|urlescape#;filenode=#filenode#;style=gitweb">file</a> | <a href="?cmd=filelog;file=#file|urlescape#;filenode=#filenode#;style=gitweb">revisions</a> | annotate | <a href="?cmd=annotate;file=#file|urlescape#;filenode=#filenode#;style=raw">raw</a><br/>
 </div>
 
 <div class="title">#file|escape#</div>
@@ -19,6 +19,7 @@
 <tr>
  <td class="metatag">changeset #rev#:</td>
  <td><a href="?cs=#node|short#;style=gitweb">#node|short#</a></td></tr>
+#rename%filerename#
 #parent%fileannotateparent#
 #child%fileannotatechild#
 <tr>
new file mode 100644
--- /dev/null
+++ b/templates/fileannotate-raw.tmpl
@@ -0,0 +1,5 @@
+#header#
+#annotate%annotateline#
+#footer#
+
+
--- a/templates/fileannotate.tmpl
+++ b/templates/fileannotate.tmpl
@@ -10,6 +10,7 @@
 <a href="?mf=#manifest|short#;path=#path|urlescape#">manifest</a>
 <a href="?f=#filenode|short#;file=#file|urlescape#">file</a>
 <a href="?fl=#filenode|short#;file=#file|urlescape#">revisions</a>
+<a href="?fa=#filenode|short#;file=#file|urlescape#;style=raw">raw</a>
 </div>
 
 <h2>Annotate #file|escape#</h2>
--- a/templates/filelog-gitweb.tmpl
+++ b/templates/filelog-gitweb.tmpl
@@ -1,12 +1,12 @@
 #header#
-<title>#repo|escape#: Manifest</title>
+<title>#repo|escape#: File revisions</title>
 <link rel="alternate" type="application/rss+xml"
    href="?cmd=changelog;style=rss" title="RSS feed for #repo|escape#">
 </head>
 <body>
 
 <div class="page_header">
-<a href="http://www.selenic.com/mercurial/" title="Mercurial"><div style="float:right;">Mercurial</div></a><a href="?cmd=summary;style=gitweb">#repo|escape#</a> / manifest
+<a href="http://www.selenic.com/mercurial/" title="Mercurial"><div style="float:right;">Mercurial</div></a><a href="?cmd=summary;style=gitweb">#repo|escape#</a> / file revisions
 </div>
 
 <div class="page_nav">
--- a/templates/filerevision-gitweb.tmpl
+++ b/templates/filerevision-gitweb.tmpl
@@ -19,6 +19,7 @@
 <tr>
  <td class="metatag">changeset #rev#:</td>
  <td><a href="?cs=#node|short#;style=gitweb">#node|short#</a></td></tr>
+#rename%filerename#
 #parent%fileannotateparent#
 #child%fileannotatechild#
 <tr>
--- a/templates/map
+++ b/templates/map
@@ -43,7 +43,7 @@ filediffparent = "<tr><th class="parent"
 filelogparent = "<tr><td align="right">parent #rev#:&nbsp;</td><td><a href="?f=#node|short#;file=#file|urlescape#">#node|short#</a></td></tr>"
 filediffchild = "<tr><th class="child">child #rev#:</th><td class="child"><a href="?cs=#node|short#">#node|short#</a></td></tr>"
 filelogchild = "<tr><td align="right">child #rev#:&nbsp;</td><td><a href="?f=#node|short#;file=#file|urlescape#">#node|short#</a></td></tr>"
-indexentry = "<tr class="parity#parity#"><td><a href="#url#">#name|escape#</a></td><td>#shortdesc|escape#</td><td>#contact|obfuscate#</td><td>#lastupdate|age# ago</td><td><a href="#url#?cl=tip;style=rss">RSS</a></td></tr>"
+indexentry = "<tr class="parity#parity#"><td><a href="#url#">#name|escape#</a></td><td>#shortdesc#</td><td>#contact|obfuscate#</td><td>#lastupdate|age# ago</td><td><a href="#url#?cl=tip;style=rss">RSS</a></td></tr>"
 index = index.tmpl
 archiveentry = "<a href="?ca=#node|short#;type=#type|urlescape#">#type|escape#</a> "
 notfound = notfound.tmpl
--- a/templates/map-gitweb
+++ b/templates/map-gitweb
@@ -20,7 +20,6 @@ filerevision = filerevision-gitweb.tmpl
 fileannotate = fileannotate-gitweb.tmpl
 filelog = filelog-gitweb.tmpl
 fileline = "<div style="font-family:monospace; white-space: pre;" class="parity#parity#"><span class="linenr">   #linenumber#</span> #line|escape#</div>"
-filelogentry = filelogentry-gitweb.tmpl
 annotateline = "<tr style="font-family:monospace; white-space: pre;" class="parity#parity#"><td class="linenr" style="text-align: right;"><a href="?cs=#node|short#;style=gitweb">#author|obfuscate#@#rev#</a></td><td>#line|escape#</td></tr>"
 difflineplus = "<div class="pre" style="color:#008800;">#line|escape#</div>"
 difflineminus = "<div class="pre" style="color:#cc0000;">#line|escape#</div>"
@@ -29,6 +28,8 @@ diffline = "<div class="pre">#line|escap
 changelogparent = "<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="?cmd=changeset;node=#node#;style=gitweb">#node|short#</a></td></tr>"
 changesetparent = "<tr><td>parent</td><td style="font-family:monospace"><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb">#node|short#</a></td></tr>"
 filerevparent = "<tr><td class="metatag">parent:</td><td><a href="?cmd=file;file=#file|urlescape#;filenode=#node#;style=gitweb">#node|short#</a></td></tr>"
+filerename = "<tr><td class="metatag">parent:</td><td><a href="?f=#node|short#;file=#file|urlescape#;style=gitweb">#file|escape#@#node|short#</a></td></tr>"
+filelogrename = "| <a href="?f=#node|short#;file=#file|urlescape#;style=gitweb">base</a>"
 fileannotateparent = "<tr><td class="metatag">parent:</td><td><a href="?cmd=annotate;file=#file|urlescape#;filenode=#node#;style=gitweb">#node|short#</a></td></tr>"
 changelogchild = "<tr><th class="child">child #rev#:</th><td class="child"><a href="?cmd=changeset;node=#node#;style=gitweb">#node|short#</a></td></tr>"
 changesetchild = "<tr><td>child</td><td style="font-family:monospace"><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb">#node|short#</a></td></tr>"
@@ -45,5 +46,5 @@ filediffchild = "<tr><th class="child">c
 filelogchild = "<tr><td align="right">child #rev#:&nbsp;</td><td><a href="?cmd=file;file=#file|urlescape#;filenode=#node#;style=gitweb">#node|short#</a></td></tr>"
 shortlog = shortlog-gitweb.tmpl
 shortlogentry = "<tr class="parity#parity#"><td><i>#date|age# ago</i></td><td><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb"><b>#desc|firstline|escape#</b></a></td><td class="link"><a href="?cmd=changeset;node=#node|short#;style=gitweb">changeset</a> |  <a href="?cmd=manifest;manifest=#manifest|short#;path=/;style=gitweb">manifest</a></td></tr>"
-filelogentry = "<tr class="parity#parity#"><td><i>#date|age# ago</i></td><td><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb"><b>#desc|firstline|escape#</b></a></td><td class="link"><!-- FIXME: <a href="?fd=#node|short#;file=#file|urlescape#;style=gitweb">diff</a> | --> <a href="?fa=#filenode|short#;file=#file|urlescape#;style=gitweb">annotate</a></td></tr>"
+filelogentry = "<tr class="parity#parity#"><td><i>#date|age# ago</i></td><td><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb"><b>#desc|firstline|escape#</b></a></td><td class="link"><!-- FIXME: <a href="?fd=#node|short#;file=#file|urlescape#;style=gitweb">diff</a> | --> <a href="?fa=#filenode|short#;file=#file|urlescape#;style=gitweb">annotate</a> #rename%filelogrename#</td></tr>"
 archiveentry = " | <a href="?ca=#node|short#;type=#type|urlescape#">#type|escape#</a> "
--- a/templates/map-raw
+++ b/templates/map-raw
@@ -1,15 +1,16 @@
 header = header-raw.tmpl
 footer = ""
 changeset = changeset-raw.tmpl
-annotateline = "<tr class="parity#parity#"><td class="annotate"><a href="?cmd=changeset;node=#node#">#author#@#rev#</a></td><td><pre>#line|escape#</pre></td></tr>"
-difflineplus = "#line|escape#"
-difflineminus = "#line|escape#"
-difflineat = "#line|escape#"
-diffline = "#line|escape#"
+difflineplus = "#line#"
+difflineminus = "#line#"
+difflineat = "#line#"
+diffline = "#line#"
 changesetparent = "# parent: #node#"
 changesetchild = "# child: #node#"
-filenodelink = "#file|urlescape#"
+filenodelink = ""
 filerevision = filerevision-raw.tmpl
-fileline = "#line|escape#"
+fileline = "#line#"
 diffblock = "#lines#"
 filediff = filediff-raw.tmpl
+fileannotate = fileannotate-raw.tmpl
+annotateline = "#author#@#rev#: #line#"
--- a/tests/test-archive
+++ b/tests/test-archive
@@ -18,8 +18,8 @@ echo "name = test-archive" >> .hg/hgrc
 echo "allowzip = true" >> .hg/hgrc
 echo "allowgz = true" >> .hg/hgrc
 echo "allowbz2 = true" >> .hg/hgrc
-hg serve -p 20059 > /dev/null &
-sleep 1 # wait for server to be started
+serverpid=`mktemp`
+hg serve -p 20059 -d --pid-file=$serverpid
 
 TIP=`hg id -v | cut -f1 -d' '`
 QTIP=`hg id -q`
@@ -35,4 +35,5 @@ http_proxy= python getarchive.py "$TIP" 
 http_proxy= python getarchive.py "$TIP" zip > archive.zip
 unzip -t archive.zip | sed "s/$QTIP/TIP/"
 
-kill $!
+kill `cat $serverpid`
+rm $serverpid
--- a/tests/test-archive.out
+++ b/tests/test-archive.out
@@ -12,4 +12,3 @@ Archive:  archive.zip
     testing: test-archive-TIP/baz/bletch   OK
     testing: test-archive-TIP/foo   OK
 No errors detected in compressed data of archive.zip.
-killed!
--- a/tests/test-diffdir
+++ b/tests/test-diffdir
@@ -12,3 +12,7 @@ hg diff | sed -e "s/\(+++ [a-zA-Z0-9_/.-
 
 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
+++ b/tests/test-diffdir.out
@@ -8,3 +8,13 @@ diff -r 3903775176ed b
 +++ 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
new file mode 100755
--- /dev/null
+++ b/tests/test-excessive-merge
@@ -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
new file mode 100644
--- /dev/null
+++ b/tests/test-excessive-merge.out
@@ -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
+++ b/tests/test-help.out
@@ -171,10 +171,12 @@ diff repository (or selected files)
 
 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
+++ b/tests/test-hook
@@ -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
+++ b/tests/test-hook.out
@@ -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-merge3.out
+++ b/tests/test-merge3.out
@@ -1,3 +1,2 @@
 removing b
-b never committed!
 nothing changed
--- a/tests/test-pull
+++ b/tests/test-pull
@@ -7,8 +7,8 @@ hg init
 hg addremove
 hg commit -m 1
 hg verify
-hg serve -p 20059 > /dev/null &
-sleep 1 # wait for server to be started
+serverpid=`mktemp`
+hg serve -p 20059 -d --pid-file=$serverpid
 cd ..
 
 hg clone http://localhost:20059/ copy
@@ -19,4 +19,5 @@ cat foo
 hg manifest
 hg pull
 
-kill $!
+kill `cat $serverpid`
+rm $serverpid
--- a/tests/test-pull-permission
+++ b/tests/test-pull-permission
@@ -12,9 +12,8 @@ chmod -w .hg
 cd ..
 
 hg clone a b
+
+chmod +w a/.hg # let test clean up
+
 cd b
 hg verify
-
-cd ..
-
-chmod +w a/.hg # let test clean up
--- a/tests/test-pull.out
+++ b/tests/test-pull.out
@@ -19,4 +19,3 @@ 2ed2a3912a0b24502043eae84ee4b279c18b90dd
 pulling from http://localhost:20059/
 searching for changes
 no changes found
-killed!
--- a/tests/test-rename
+++ b/tests/test-rename
@@ -158,3 +158,16 @@ hg remove d1/b
 hg rename d1 d3
 hg status
 hg update -C
+
+echo "# transitive rename"
+hg rename d1/b d1/bb
+hg rename d1/bb d1/bc
+hg status
+hg update -C
+
+echo "# transitive rename --after"
+hg rename d1/b d1/bb
+mv d1/bb d1/bc
+hg rename --after d1/bb d1/bc
+hg status
+hg update -C
--- a/tests/test-rename.out
+++ b/tests/test-rename.out
@@ -246,3 +246,9 @@ R d1/a
 R d1/b
 R d1/ba
 R d1/d11/a1
+# transitive rename
+A d1/bc
+R d1/b
+# transitive rename --after
+A d1/bc
+R d1/b
--- a/tests/test-ro-message
+++ b/tests/test-ro-message
@@ -14,4 +14,4 @@ echo 'stationary' >>b/vehicle
 "$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'
--- a/tests/test-up-local-change
+++ b/tests/test-up-local-change
@@ -24,11 +24,34 @@ hg commit -m "2" -d "0 0"
 cd ../r2
 hg -q pull ../r1
 hg status
+hg parents
 hg --debug up
+hg parents
+hg --debug up 0
+hg parents
 hg --debug up -m || echo failed
-hg --debug up -f -m
+hg parents
+hg --debug up
 hg parents
 hg -v history
 hg diff | sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \
               -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/"
 
+# create a second head
+cd ../r1
+hg up 0
+echo b2 > b
+echo a3 > a
+hg addremove
+hg commit -m "3" -d "0 0"
+
+cd ../r2
+hg -q pull ../r1
+hg status
+hg parents
+hg --debug up || echo failed
+hg --debug up -m || echo failed
+hg --debug up -f -m
+hg parents
+hg diff | sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \
+              -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/"
--- a/tests/test-up-local-change.out
+++ b/tests/test-up-local-change.out
@@ -7,6 +7,11 @@ diff -r c19d34741b0a a
 +abc
 adding b
 M a
+changeset:   0:c19d34741b0a
+user:        test
+date:        Thu Jan  1 00:00:00 1970 +0000
+summary:     1
+
 resolving manifests
  force None allow None moddirstate True linear True
  ancestor a0c8bcbbb45c local a0c8bcbbb45c remote 1165e8bd193e
@@ -16,11 +21,38 @@ getting b
 merging a
 resolving a
 file a: my b789fdd96dc2 other d730145abbf9 ancestor b789fdd96dc2
-abort: outstanding uncommited changes
-failed
+changeset:   1:1e71731e6fbb
+tag:         tip
+user:        test
+date:        Thu Jan  1 00:00:00 1970 +0000
+summary:     2
+
 resolving manifests
- force None allow 1 moddirstate True linear True
- ancestor 1165e8bd193e local 1165e8bd193e remote 1165e8bd193e
+ force None allow None moddirstate True linear True
+ ancestor a0c8bcbbb45c local 1165e8bd193e remote a0c8bcbbb45c
+remote deleted b
+removing b
+changeset:   0:c19d34741b0a
+user:        test
+date:        Thu Jan  1 00:00:00 1970 +0000
+summary:     1
+
+abort: there is nothing to merge, just use 'hg update'
+failed
+changeset:   0:c19d34741b0a
+user:        test
+date:        Thu Jan  1 00:00:00 1970 +0000
+summary:     1
+
+resolving manifests
+ force None allow None moddirstate True linear True
+ ancestor a0c8bcbbb45c local a0c8bcbbb45c remote 1165e8bd193e
+ a versions differ, resolve
+remote created b
+getting b
+merging a
+resolving a
+file a: my b789fdd96dc2 other d730145abbf9 ancestor b789fdd96dc2
 changeset:   1:1e71731e6fbb
 tag:         tip
 user:        test
@@ -50,3 +82,52 @@ diff -r 1e71731e6fbb a
 @@ -1,1 +1,1 @@ a2
 -a2
 +abc
+adding b
+M a
+changeset:   1:1e71731e6fbb
+user:        test
+date:        Thu Jan  1 00:00:00 1970 +0000
+summary:     2
+
+resolving manifests
+ force None allow None moddirstate True linear False
+ ancestor a0c8bcbbb45c local 1165e8bd193e remote 4096f2872392
+ a versions differ, resolve
+ b versions differ, resolve
+this update spans a branch affecting the following files:
+ a (resolve)
+ b (resolve)
+aborting update spanning branches!
+(use update -m to merge across branches or -C to lose changes)
+failed
+abort: outstanding uncommited changes
+failed
+resolving manifests
+ force None allow 1 moddirstate True linear False
+ ancestor a0c8bcbbb45c local 1165e8bd193e remote 4096f2872392
+ a versions differ, resolve
+ b versions differ, resolve
+merging a
+resolving a
+file a: my d730145abbf9 other 13e0d5f949fa ancestor b789fdd96dc2
+merging b
+resolving b
+file b: my 1e88685f5dde other 61de8c7723ca ancestor 000000000000
+changeset:   1:1e71731e6fbb
+user:        test
+date:        Thu Jan  1 00:00:00 1970 +0000
+summary:     2
+
+changeset:   2:83c51d0caff4
+tag:         tip
+parent:      0:c19d34741b0a
+user:        test
+date:        Thu Jan  1 00:00:00 1970 +0000
+summary:     3
+
+diff -r 1e71731e6fbb a
+--- a/a
++++ b/a
+@@ -1,1 +1,1 @@ a2
+-a2
++abc