changeset 1697:7b4128f689bd

Merge with crew
author Matt Mackall <mpm@selenic.com>
date Sun, 05 Feb 2006 22:18:38 -0600
parents 586b50294ea8 (current diff) 4a3d4843a1bc (diff)
children ad4a2eefe4d7
files contrib/patchbomb templates/fileannotate-gitweb.tmpl
diffstat 19 files changed, 435 insertions(+), 167 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
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/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.
@@ -50,9 +48,9 @@
 from email.MIMEMultipart import MIMEMultipart
 from email.MIMEText import MIMEText
 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 +87,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 +106,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 +118,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 +171,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 +202,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,7 +217,7 @@ 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)
@@ -252,25 +261,15 @@ def patchbomb(ui, repo, *revs, **opts):
     if not opts['test']:
         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'),
+      ('s', 'subject', '', 'subject of introductory message'),
+      ('t', 'to', [], 'email addresses of recipients')],
+     "hg email [OPTION]... [REV]...")
+    }
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -1399,6 +1399,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 +1418,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"))
--- 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/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>
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/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-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-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