changeset 2386:280fde4b8701

merge with crew.
author Vadim Gelfer <vadim.gelfer@gmail.com>
date Thu, 01 Jun 2006 13:47:34 -0700
parents 482d3fb47d80 (diff) 5d895018ef42 (current diff)
children 609c56df709a
files .hgignore contrib/win32/ReadMe.html
diffstat 48 files changed, 3518 insertions(+), 1601 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Mon May 22 16:47:40 2006 +0200
+++ b/.hgignore	Thu Jun 01 13:47:34 2006 -0700
@@ -4,6 +4,7 @@
 *.orig
 *.rej
 *~
+*.o
 *.so
 *.pyc
 *.swp
@@ -12,6 +13,7 @@
 tests/annotated
 tests/*.err
 build
+contrib/hgsh/hgsh
 dist
 doc/*.[0-9]
 doc/*.[0-9].gendoc.txt
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/darcs2hg.py	Thu Jun 01 13:47:34 2006 -0700
@@ -0,0 +1,130 @@
+#!/usr/bin/env python
+# Encoding: iso-8859-1
+# vim: tw=80 ts=4 sw=4 noet
+# -----------------------------------------------------------------------------
+# Project   : Basic Darcs to Mercurial conversion script
+# -----------------------------------------------------------------------------
+# Author    : Sebastien Pierre <sebastien@xprima.com>
+# Creation  : 24-May-2006
+# Last mod  : 26-May-2006
+# History   :
+#             26-May-2006 - Updated
+#             24-May-2006 - First implementation
+# -----------------------------------------------------------------------------
+
+import os, sys
+import tempfile
+import xml.dom.minidom as xml_dom
+from time import strptime, mktime
+
+DARCS_REPO = None
+HG_REPO    = None
+
+USAGE = """\
+%s DARCSREPO HGREPO
+
+    Converts the given Darcs repository to a new Mercurial repository. The given
+    HGREPO must not exist, as it will be created and filled up (this will avoid
+    overwriting valuable data.
+
+""" % (os.path.basename(sys.argv[0]))
+
+# ------------------------------------------------------------------------------
+#
+# Utilities
+#
+# ------------------------------------------------------------------------------
+
+def cmd(text, path=None):
+	"""Executes a command, in the given directory (if any), and returns the
+	command result as a string."""
+	cwd = None
+	if path:
+		path = os.path.abspath(path)
+		cwd  = os.getcwd()
+		os.chdir(path)
+	print text
+	res = os.popen(text).read()
+	if path:
+		os.chdir(cwd)
+	return res
+
+def writefile(path, data):
+	"""Writes the given data into the given file."""
+	f = file(path, "w") ; f.write(data)  ; f.close()
+
+# ------------------------------------------------------------------------------
+#
+# Darcs interface
+#
+# ------------------------------------------------------------------------------
+
+def darcs_changes(darcsRepo):
+	"""Gets the changes list from the given darcs repository. This returns the
+	chronological list of changes as (change name, change summary)."""
+	changes    = cmd("darcs changes --reverse --xml-output", darcsRepo)
+	doc        = xml_dom.parseString(changes)
+	res        = []
+	for patch_node in doc.childNodes[0].childNodes:
+		name = filter(lambda n:n.nodeName == "name", patch_node.childNodes)
+		comm = filter(lambda n:n.nodeName == "comment", patch_node.childNodes)
+		if not name:continue
+		else: name = name[0].childNodes[0].data
+		if not comm: comm = ""
+		else: comm = comm[0].childNodes[0].data
+		author = patch_node.getAttribute("author")
+		date = patch_node.getAttribute("date")
+		yield author, date, name, comm
+
+def darcs_pull(hg_repo, darcs_repo, change):
+	cmd("darcs pull '%s' --all --patches='%s'" % (darcs_repo, change), hg_repo)
+
+# ------------------------------------------------------------------------------
+#
+# Mercurial interface
+#
+# ------------------------------------------------------------------------------
+
+def hg_commit( hg_repo, text, author, date ):
+	fd, tmpfile = tempfile.mkstemp(prefix="darcs2hg_")
+	writefile(tmpfile, text)
+	cmd("hg add -X _darcs", hg_repo)
+	cmd("hg remove -X _darcs --after", hg_repo)
+	cmd("hg commit -l %s -u '%s' -d '%s 0'"  % (tmpfile, author, date), hg_repo)
+	os.unlink(tmpfile)
+
+# ------------------------------------------------------------------------------
+#
+# Main
+#
+# ------------------------------------------------------------------------------
+
+if __name__ == "__main__":
+	args = sys.argv[1:]
+	# We parse the arguments
+	if len(args)   == 2:
+		darcs_repo = os.path.abspath(args[0])
+		hg_repo    = os.path.abspath(args[1])
+	else:
+		print USAGE
+		sys.exit(-1)
+	# Initializes the target repo
+	if not os.path.isdir(darcs_repo + "/_darcs"):
+		print "No darcs directory found at: " + darc_repo
+		sys.exit(-1)
+	if not os.path.isdir(hg_repo):
+		os.mkdir(hg_repo)
+	else:
+		print "Given HG repository must not exist. It will be created"
+		sys.exit(-1)
+	cmd("hg init '%s'" % (hg_repo))
+	cmd("darcs initialize", hg_repo)
+	# Get the changes from the Darcs repository
+	for author, date, summary, description in darcs_changes(darcs_repo):
+		text = summary + "\n" + description
+		darcs_pull(hg_repo, darcs_repo, summary)
+		epoch = int(mktime(strptime(date, '%Y%m%d%H%M%S')))
+		hg_commit(hg_repo, text, author, epoch)
+
+# EOF
+
--- a/contrib/hg-menu.vim	Mon May 22 16:47:40 2006 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,93 +0,0 @@
-" vim600: set foldmethod=marker:
-" =============================================================================
-"  Name Of File: hg-menu.vim
-"   Description: Interface to Mercurial Version Control.
-"        Author: Steve Borho (modified Jeff Lanzarotta's RCS script)
-"          Date: Wednesday, October 5, 2005
-"       Version: 0.1.0
-"     Copyright: None.
-"         Usage: These command and gui menu displays useful hg functions
-" Configuration: Your hg executable must be in your path.
-" =============================================================================
- 
-" Section: Init {{{1
-if exists("loaded_hg_menu")
-  finish
-endif
-let loaded_hg_menu = 1
-
-" Section: Menu Options {{{1
-if has("gui")
-"  amenu H&G.Commit\ File<Tab>,ci :!hg commit %<CR>:e!<CR>
-"  amenu H&G.Commit\ All<Tab>,call :!hg commit<CR>:e!<CR>
-"  amenu H&G.-SEP1-        <nul>
-  amenu H&G.Add<Tab>\\add :!hg add %<CR><CR>
-  amenu H&G.Forget\ Add<Tab>\\fgt :!hg forget %<CR><CR>
-  amenu H&G.Show\ Differences<Tab>\\diff :call ShowResults("FileDiff", "hg\ diff")<CR><CR>
-  amenu H&G.Revert\ to\ Last\ Version<Tab>\\revert :!hg revert %<CR>:e!<CR>
-  amenu H&G.Show\ History<Tab>\\log :call ShowResults("FileLog", "hg\ log")<CR><CR>
-  amenu H&G.Annotate<Tab>\\an :call ShowResults("annotate", "hg\ annotate")<CR><CR>
-  amenu H&G.-SEP1-        <nul>
-  amenu H&G.Repo\ Status<Tab>\\stat :call ShowResults("RepoStatus", "hg\ status")<CR><CR>
-  amenu H&G.Pull<Tab>\\pull :!hg pull<CR>:e!<CR>
-  amenu H&G.Update<Tab>\\upd :!hg update<CR>:e!<CR>
-endif
-
-" Section: Mappings {{{1
-if(v:version >= 600)
-  " The default Leader is \ 'backslash'
-  map <Leader>add       :!hg add %<CR><CR>
-  map <Leader>fgt       :!hg forget %<CR><CR>
-  map <Leader>diff      :call ShowResults("FileDiff", "hg\ diff")<CR><CR>
-  map <Leader>revert    :!hg revert %<CR>:e!<CR>
-  map <Leader>log       :call ShowResults("FileLog", "hg\ log")<CR><CR>
-  map <Leader>an        :call ShowResults("annotate", "hg\ annotate")<CR><CR>
-  map <Leader>stat      :call ShowResults("RepoStatus", "hg\ status")<CR><CR>
-  map <Leader>upd       :!hg update<CR>:e!<CR>
-  map <Leader>pull      :!hg pull<CR>:e!<CR>
-else
-  " pre 6.0, the default Leader was a comma
-  map ,add          :!hg add %<CR><CR>
-  map ,fgt          :!hg forget %<CR><CR>
-  map ,diff         :call ShowResults("FileDiff", "hg\ diff")<CR><CR>
-  map ,revert       :!hg revert<CR>:e!<CR>
-  map ,log          :call ShowResults("FileLog", "hg\ log")<CR><CR>
-  map ,an           :call ShowResults("annotate", "hg\ annotate")<CR><CR>
-  map ,stat         :call ShowResults("RepoStatus", "hg\ status")<CR><CR>
-  map ,upd          :!hg update<CR>:e!<CR>
-  map ,pull         :!hg pull<CR>:e!<CR>
-endif
-
-" Section: Functions {{{1
-" Show the log results of the current file with a revision control system.
-function! ShowResults(bufferName, cmdName)
-  " Modify the shortmess option:
-  " A  don't give the "ATTENTION" message when an existing swap file is
-  "    found.
-  set shortmess+=A
-
-  " Get the name of the current buffer.
-  let currentBuffer = bufname("%")
-
-  " If a buffer with the name rlog exists, delete it.
-  if bufexists(a:bufferName)
-    execute 'bd! ' a:bufferName
-  endif
-
-  " Create a new buffer.
-  execute 'new ' a:bufferName
-
-  " Execute the command.
-  execute 'r!' a:cmdName ' ' currentBuffer
-
-  " Make is so that the file can't be edited.
-  setlocal nomodified
-  setlocal nomodifiable
-  setlocal readonly
-
-  " Go to the beginning of the buffer.
-  execute "normal 1G"
-
-  " Restore the shortmess option.
-  set shortmess-=A
-endfunction
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/hgsh/Makefile	Thu Jun 01 13:47:34 2006 -0700
@@ -0,0 +1,13 @@
+CC := gcc
+CFLAGS := -g -O2 -Wall -Werror
+
+prefix ?= /usr/bin
+
+hgsh: hgsh.o
+	$(CC) -o $@ $<
+
+install: hgsh
+	install -m755 hgsh $(prefix)
+
+clean:
+	rm -f *.o hgsh
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/hgsh/hgsh.c	Thu Jun 01 13:47:34 2006 -0700
@@ -0,0 +1,372 @@
+/*
+ * hgsh.c - restricted login shell for mercurial
+ *
+ * Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
+ *
+ * This software may be used and distributed according to the terms of the
+ * GNU General Public License, incorporated herein by reference.
+ *
+ * this program is login shell for dedicated mercurial user account. it
+ * only allows few actions:
+ *
+ * 1. run hg in server mode on specific repository. no other hg commands
+ * are allowed. we try to verify that repo to be accessed exists under
+ * given top-level directory.
+ *
+ * 2. (optional) forward ssh connection from firewall/gateway machine to
+ * "real" mercurial host, to let users outside intranet pull and push
+ * changes through firewall.
+ *
+ * 3. (optional) run normal shell, to allow to "su" to mercurial user, use
+ * "sudo" to run programs as that user, or run cron jobs as that user.
+ *
+ * only tested on linux yet. patches for non-linux systems welcome.
+ */
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE /* for asprintf */
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+/*
+ * user config.
+ *
+ * if you see a hostname below, just use first part of hostname. example,
+ * if you have host named foo.bar.com, use "foo".
+ */
+
+/*
+ * HG_GATEWAY: hostname of gateway/firewall machine that people outside your
+ * intranet ssh into if they need to ssh to other machines. if you do not
+ * have such machine, set to NULL.
+ */
+#ifndef HG_GATEWAY
+#define HG_GATEWAY     "gateway"
+#endif
+
+/*
+ * HG_HOST: hostname of mercurial server. if any machine is allowed, set to
+ * NULL.
+ */
+#ifndef HG_HOST
+#define HG_HOST         "mercurial"
+#endif
+
+/*
+ * HG_USER: username to log in from HG_GATEWAY to HG_HOST. if gateway and
+ * host username are same, set to NULL.
+ */
+#ifndef HG_USER
+#define HG_USER         "hg"
+#endif
+
+/*
+ * HG_ROOT: root of tree full of mercurial repos. if you do not want to
+ * validate location of repo when someone is try to access, set to NULL.
+ */
+#ifndef HG_ROOT
+#define HG_ROOT         "/home/hg/repos"
+#endif
+
+/*
+ * HG: path to the mercurial executable to run.
+ */
+#ifndef HG
+#define HG              "/home/hg/bin/hg"
+#endif
+
+/*
+ * HG_SHELL: shell to use for actions like "sudo" and "su" access to
+ * mercurial user, and cron jobs. if you want to make these things
+ * impossible, set to NULL.
+ */
+#ifndef HG_SHELL
+#define HG_SHELL        NULL
+// #define HG_SHELL        "/bin/bash"
+#endif
+
+/*
+ * HG_HELP: some way for users to get support if they have problem. if they
+ * should not get helpful message, set to NULL.
+ */
+#ifndef HG_HELP
+#define HG_HELP         "please contact support@example.com for help."
+#endif
+
+/*
+ * SSH: path to ssh executable to run, if forwarding from HG_GATEWAY to
+ * HG_HOST. if you want to use rsh instead (why?), you need to modify
+ * arguments it is called with. see forward_through_gateway.
+ */
+#ifndef SSH
+#define SSH             "/usr/bin/ssh"
+#endif
+
+/*
+ * tell whether to print command that is to be executed. useful for
+ * debugging. should not interfere with mercurial operation, since
+ * mercurial only cares about stdin and stdout, and this prints to stderr.
+ */
+static const int debug = 0;
+
+static void print_cmdline(int argc, char **argv)
+{
+    FILE *fp = stderr;
+    int i;
+
+    fputs("command: ", fp);
+
+    for (i = 0; i < argc; i++) {
+        char *spc = strpbrk(argv[i], " \t\r\n");
+        if (spc) {
+            fputc('\'', fp);
+        }
+        fputs(argv[i], fp);
+        if (spc) {
+            fputc('\'', fp);
+        }
+        if (i < argc - 1) {
+            fputc(' ', fp);
+        }
+    }
+    fputc('\n', fp);
+    fflush(fp);
+}
+
+static void usage(const char *reason, int exitcode)
+{
+    char *hg_help = HG_HELP;
+
+    if (reason) {
+        fprintf(stderr, "*** Error: %s.\n", reason);
+    }
+    fprintf(stderr, "*** This program has been invoked incorrectly.\n");
+    if (hg_help) {
+        fprintf(stderr, "*** %s\n", hg_help);
+    }
+    exit(exitcode ? exitcode : EX_USAGE);
+}
+
+/*
+ * run on gateway host to make another ssh connection, to "real" mercurial
+ * server. it sends its command line unmodified to far end.
+ *
+ * never called if HG_GATEWAY is NULL.
+ */
+static void forward_through_gateway(int argc, char **argv)
+{
+    char *ssh = SSH;
+    char *hg_host = HG_HOST;
+    char *hg_user = HG_USER;
+    char **nargv = alloca((10 + argc) * sizeof(char *));
+    int i = 0, j;
+
+    nargv[i++] = ssh;
+    nargv[i++] = "-q";
+    nargv[i++] = "-T";
+    nargv[i++] = "-x";
+    if (hg_user) {
+        nargv[i++] = "-l";
+        nargv[i++] = hg_user;
+    }
+    nargv[i++] = hg_host;
+
+    /*
+     * sshd called us with added "-c", because it thinks we are a shell.
+     * drop it if we find it.
+     */
+    j = 1;
+    if (j < argc && strcmp(argv[j], "-c") == 0) {
+        j++;
+    }
+
+    for (; j < argc; i++, j++) {
+        nargv[i] = argv[j];
+    }
+    nargv[i] = NULL;
+
+    if (debug) {
+        print_cmdline(i, nargv);
+    }
+
+    execv(ssh, nargv);
+    perror(ssh);
+    exit(EX_UNAVAILABLE);
+}
+
+/*
+ * run shell. let administrator "su" to mercurial user's account to do
+ * administrative works.
+ *
+ * never called if HG_SHELL is NULL.
+ */
+static void run_shell(int argc, char **argv)
+{
+    char *hg_shell = HG_SHELL;
+    char **nargv;
+    char *c;
+    int i;
+
+    nargv = alloca((argc + 3) * sizeof(char *));
+    c = strrchr(hg_shell, '/');
+
+    /* tell "real" shell it is login shell, if needed. */
+
+    if (argv[0][0] == '-' && c) {
+        nargv[0] = strdup(c);
+        if (nargv[0] == NULL) {
+            perror("malloc");
+            exit(EX_OSERR);
+        }
+        nargv[0][0] = '-';
+    } else {
+        nargv[0] = hg_shell;
+    }
+
+    for (i = 1; i < argc; i++) {
+        nargv[i] = argv[i];
+    }
+    nargv[i] = NULL;
+
+    if (debug) {
+        print_cmdline(i, nargv);
+    }
+
+    execv(hg_shell, nargv);
+    perror(hg_shell);
+    exit(EX_OSFILE);
+}
+
+/*
+ * paranoid wrapper, runs hg executable in server mode.
+ */
+static void serve_data(int argc, char **argv)
+{
+    char *hg_root = HG_ROOT;
+    char *repo, *abspath;
+    char *nargv[6];
+    struct stat st;
+    size_t repolen;
+    int i;
+
+    /*
+     * check argv for looking okay. we should be invoked with argv
+     * resembling like this:
+     *
+     *   hgsh
+     *   -c
+     *   hg -R some/path serve --stdio
+     *
+     * the "-c" is added by sshd, because it thinks we are login shell.
+     */
+
+    if (argc != 3) {
+        goto badargs;
+    }
+
+    if (strcmp(argv[1], "-c") != 0) {
+        goto badargs;
+    }
+
+    if (sscanf(argv[2], "hg -R %as serve --stdio", &repo) != 1) {
+        goto badargs;
+    }
+
+    repolen = repo ? strlen(repo) : 0;
+
+    if (repolen == 0) {
+        goto badargs;
+    }
+
+    if (hg_root) {
+        if (asprintf(&abspath, "%s/%s/.hg/data", hg_root, repo) == -1) {
+            goto badargs;
+        }
+
+        /*
+         * attempt to stop break out from inside the repository tree. could
+         * do something more clever here, because e.g. we could traverse a
+         * symlink that looks safe, but really breaks us out of tree.
+         */
+
+        if (strstr(abspath, "/../") != NULL) {
+            goto badargs;
+        }
+
+        /* verify that we really are looking at valid repo. */
+
+        if (stat(abspath, &st) == -1) {
+            perror(repo);
+            exit(EX_DATAERR);
+        }
+
+        if (chdir(hg_root) == -1) {
+            perror(hg_root);
+            exit(EX_SOFTWARE);
+        }
+    }
+
+    i = 0;
+    nargv[i++] = HG;
+    nargv[i++] = "-R";
+    nargv[i++] = repo;
+    nargv[i++] = "serve";
+    nargv[i++] = "--stdio";
+    nargv[i] = NULL;
+
+    if (debug) {
+        print_cmdline(i, nargv);
+    }
+
+    execv(HG, nargv);
+    perror(HG);
+    exit(EX_UNAVAILABLE);
+
+badargs:
+    /* print useless error message. */
+
+    usage("invalid arguments", EX_DATAERR);
+}
+
+int main(int argc, char **argv)
+{
+    char host[1024];
+    char *c;
+
+    if (gethostname(host, sizeof(host)) == -1) {
+        perror("gethostname");
+        exit(EX_OSERR);
+    }
+
+    if ((c = strchr(host, '.')) != NULL) {
+        *c = '\0';
+    }
+
+    if (getenv("SSH_CLIENT")) {
+        char *hg_gateway = HG_GATEWAY;
+        char *hg_host = HG_HOST;
+
+        if (hg_gateway && strcmp(host, hg_gateway) == 0) {
+            forward_through_gateway(argc, argv);
+        }
+
+        if (hg_host && strcmp(host, hg_host) != 0) {
+            usage("invoked on unexpected host", EX_USAGE);
+        }
+
+        serve_data(argc, argv);
+    } else if (HG_SHELL) {
+        run_shell(argc, argv);
+    } else {
+        usage("invalid arguments", EX_DATAERR);
+    }
+
+    return 0;
+}
--- a/contrib/mercurial.el	Mon May 22 16:47:40 2006 +0200
+++ b/contrib/mercurial.el	Thu Jun 01 13:47:34 2006 -0700
@@ -382,14 +382,27 @@
       (set-buffer hg-prev-buffer))
     (let ((path (or default (buffer-file-name))))
       (if (or (not path) current-prefix-arg)
-	  (expand-file-name
-	   (read-file-name (format "File, directory or pattern%s: "
-				   (or prompt ""))
-			   (and path (file-name-directory path))
-			   nil nil
-			   (and path (file-name-nondirectory path))
-			   'hg-file-history))
-	path))))
+          (expand-file-name
+           (eval (list* 'read-file-name
+                        (format "File, directory or pattern%s: "
+                                (or prompt ""))
+                        (and path (file-name-directory path))
+                        nil nil
+                        (and path (file-name-nondirectory path))
+                        (if hg-running-xemacs
+                            (cons (quote 'hg-file-history) nil)
+                          nil))))
+        path))))
+
+(defun hg-read-number (&optional prompt default)
+  "Read a integer value."
+  (save-excursion
+    (if (or (not default) current-prefix-arg)
+        (string-to-number
+         (eval (list* 'read-string
+                      (or prompt "") 
+                      (if default (cons (format "%d" default) nil) nil))))
+      default)))
 
 (defun hg-read-config ()
   "Return an alist of (key . value) pairs of Mercurial config data.
@@ -950,36 +963,55 @@
     (kill-entire-line))
   (run-hooks 'hg-log-mode-hook))
 
-(defun hg-log (path &optional rev1 rev2)
-  "Display the revision history of PATH, between REV1 and REV2.
-REV1 defaults to hg-log-limit changes from the tip revision, while
-REV2 defaults to the tip.
+(defun hg-log (path &optional rev1 rev2 log-limit)
+  "Display the revision history of PATH.
+History is displayed between REV1 and REV2.
+Number of displayed changesets is limited to LOG-LIMIT.
+REV1 defaults to the tip, while
+REV2 defaults to `hg-rev-completion-limit' changes from the tip revision.
+LOG-LIMIT defaults to `hg-log-limit'.
 With a prefix argument, prompt for each parameter."
   (interactive (list (hg-read-file-name " to log")
-		     (hg-read-rev " to start with" "-1")
-		     (hg-read-rev " to end with" (format "-%d" hg-log-limit))))
+                     (hg-read-rev " to start with"
+                                  "tip")
+                     (hg-read-rev " to end with"
+                                  (format "%d" (- hg-rev-completion-limit)))
+                     (hg-read-number "Output limited to: "
+                                     hg-log-limit)))
   (let ((a-path (hg-abbrev-file-name path))
-	(r1 (or rev1 (format "-%d" hg-log-limit)))
-	(r2 (or rev2 rev1 "-1")))
+        (r1 (or rev1 (format "-%d" hg-rev-completion-limit)))
+        (r2 (or rev2 rev1 "tip"))
+        (limit (format "%d" (or log-limit hg-log-limit))))
     (hg-view-output ((if (equal r1 r2)
-			 (format "Mercurial: Log of rev %s of %s" rev1 a-path)
-		       (format "Mercurial: Log from rev %s to %s of %s"
-			       r1 r2 a-path)))
-      (let ((revs (format "%s:%s" r1 r2)))
-	(if (> (length path) (length (hg-root path)))
-	    (call-process (hg-binary) nil t nil "log" "-r" revs path)
-	  (call-process (hg-binary) nil t nil "log" "-r" revs)))
+                         (format "Mercurial: Log of rev %s of %s" rev1 a-path)
+                       (format 
+                        "Mercurial: at most %s log(s) from rev %s to %s of %s"
+                        limit r1 r2 a-path)))
+      (eval (list* 'call-process (hg-binary) nil t nil
+                   "log"
+                   "-r" (format "%s:%s" r1 r2)
+                   "-l" limit
+                   (if (> (length path) (length (hg-root path)))
+                       (cons path nil)
+                     nil)))
       (hg-log-mode))))
 
-(defun hg-log-repo (path &optional rev1 rev2)
+(defun hg-log-repo (path &optional rev1 rev2 log-limit)
   "Display the revision history of the repository containing PATH.
-History is displayed between REV1, which defaults to the tip, and
-REV2, which defaults to the initial revision.
-Variable hg-log-limit controls the number of log entries displayed."
+History is displayed between REV1 and REV2.
+Number of displayed changesets is limited to LOG-LIMIT,
+REV1 defaults to the tip, while
+REV2 defaults to `hg-rev-completion-limit' changes from the tip revision.
+LOG-LIMIT defaults to `hg-log-limit'.
+With a prefix argument, prompt for each parameter."
   (interactive (list (hg-read-file-name " to log")
-		     (hg-read-rev " to start with" "tip")
-		     (hg-read-rev " to end with" (format "-%d" hg-log-limit))))
-  (hg-log (hg-root path) rev1 rev2))
+                     (hg-read-rev " to start with"
+                                  "tip")
+                     (hg-read-rev " to end with" 
+                                  (format "%d" (- hg-rev-completion-limit)))
+                     (hg-read-number "Output limited to: "
+                                     hg-log-limit)))
+  (hg-log (hg-root path) rev1 rev2 log-limit))
 
 (defun hg-outgoing (&optional repo)
   "Display changesets present locally that are not present in REPO."
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/vim/hg-menu.vim	Thu Jun 01 13:47:34 2006 -0700
@@ -0,0 +1,93 @@
+" vim600: set foldmethod=marker:
+" =============================================================================
+"  Name Of File: hg-menu.vim
+"   Description: Interface to Mercurial Version Control.
+"        Author: Steve Borho (modified Jeff Lanzarotta's RCS script)
+"          Date: Wednesday, October 5, 2005
+"       Version: 0.1.0
+"     Copyright: None.
+"         Usage: These command and gui menu displays useful hg functions
+" Configuration: Your hg executable must be in your path.
+" =============================================================================
+ 
+" Section: Init {{{1
+if exists("loaded_hg_menu")
+  finish
+endif
+let loaded_hg_menu = 1
+
+" Section: Menu Options {{{1
+if has("gui")
+"  amenu H&G.Commit\ File<Tab>,ci :!hg commit %<CR>:e!<CR>
+"  amenu H&G.Commit\ All<Tab>,call :!hg commit<CR>:e!<CR>
+"  amenu H&G.-SEP1-        <nul>
+  amenu H&G.Add<Tab>\\add :!hg add %<CR><CR>
+  amenu H&G.Forget\ Add<Tab>\\fgt :!hg forget %<CR><CR>
+  amenu H&G.Show\ Differences<Tab>\\diff :call ShowResults("FileDiff", "hg\ diff")<CR><CR>
+  amenu H&G.Revert\ to\ Last\ Version<Tab>\\revert :!hg revert %<CR>:e!<CR>
+  amenu H&G.Show\ History<Tab>\\log :call ShowResults("FileLog", "hg\ log")<CR><CR>
+  amenu H&G.Annotate<Tab>\\an :call ShowResults("annotate", "hg\ annotate")<CR><CR>
+  amenu H&G.-SEP1-        <nul>
+  amenu H&G.Repo\ Status<Tab>\\stat :call ShowResults("RepoStatus", "hg\ status")<CR><CR>
+  amenu H&G.Pull<Tab>\\pull :!hg pull<CR>:e!<CR>
+  amenu H&G.Update<Tab>\\upd :!hg update<CR>:e!<CR>
+endif
+
+" Section: Mappings {{{1
+if(v:version >= 600)
+  " The default Leader is \ 'backslash'
+  map <Leader>add       :!hg add %<CR><CR>
+  map <Leader>fgt       :!hg forget %<CR><CR>
+  map <Leader>diff      :call ShowResults("FileDiff", "hg\ diff")<CR><CR>
+  map <Leader>revert    :!hg revert %<CR>:e!<CR>
+  map <Leader>log       :call ShowResults("FileLog", "hg\ log")<CR><CR>
+  map <Leader>an        :call ShowResults("annotate", "hg\ annotate")<CR><CR>
+  map <Leader>stat      :call ShowResults("RepoStatus", "hg\ status")<CR><CR>
+  map <Leader>upd       :!hg update<CR>:e!<CR>
+  map <Leader>pull      :!hg pull<CR>:e!<CR>
+else
+  " pre 6.0, the default Leader was a comma
+  map ,add          :!hg add %<CR><CR>
+  map ,fgt          :!hg forget %<CR><CR>
+  map ,diff         :call ShowResults("FileDiff", "hg\ diff")<CR><CR>
+  map ,revert       :!hg revert<CR>:e!<CR>
+  map ,log          :call ShowResults("FileLog", "hg\ log")<CR><CR>
+  map ,an           :call ShowResults("annotate", "hg\ annotate")<CR><CR>
+  map ,stat         :call ShowResults("RepoStatus", "hg\ status")<CR><CR>
+  map ,upd          :!hg update<CR>:e!<CR>
+  map ,pull         :!hg pull<CR>:e!<CR>
+endif
+
+" Section: Functions {{{1
+" Show the log results of the current file with a revision control system.
+function! ShowResults(bufferName, cmdName)
+  " Modify the shortmess option:
+  " A  don't give the "ATTENTION" message when an existing swap file is
+  "    found.
+  set shortmess+=A
+
+  " Get the name of the current buffer.
+  let currentBuffer = bufname("%")
+
+  " If a buffer with the name rlog exists, delete it.
+  if bufexists(a:bufferName)
+    execute 'bd! ' a:bufferName
+  endif
+
+  " Create a new buffer.
+  execute 'new ' a:bufferName
+
+  " Execute the command.
+  execute 'r!' a:cmdName ' ' currentBuffer
+
+  " Make is so that the file can't be edited.
+  setlocal nomodified
+  setlocal nomodifiable
+  setlocal readonly
+
+  " Go to the beginning of the buffer.
+  execute "normal 1G"
+
+  " Restore the shortmess option.
+  set shortmess-=A
+endfunction
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/vim/patchreview.txt	Thu Jun 01 13:47:34 2006 -0700
@@ -0,0 +1,97 @@
+*patchreview.txt* Vim global plugin for doing single or multipatch code reviews
+
+            Author: Manpreet Singh (junkblocker-CAT-yahoo-DOG-com)
+                    (Replace -CAT- and -DOG- with @ and . first)
+            Copyright (C) 2006 by Manpreet Singh
+            License : This file is placed in the public domain.
+
+=============================================================================
+
+CONTENTS	                               *patchreview* *patchreview-contents*
+
+  1. Contents.........................................: |patchreview-contents|
+  2. Introduction.....................................: |patchreview-intro|
+  3. PatchReview options..............................: |patchreview-options|
+  4. PatchReview Usage................................: |patchreview-usage|
+     4.1 PatchReview Usage............................: |:PatchReview|
+     4.2 PatchReview Usage............................: |:PatchReviewCleanup|
+
+=============================================================================
+
+PatchReview Introduction                                  *patchreview-intro*
+
+The Patch Review plugin allows single or multipatch code review to be done in
+VIM. VIM provides the |:diffpatch| command to do single file reviews but can
+not handle patch files containing multiple patches as is common with software
+development projects. This plugin provides that missing functionality. It also
+tries to improve on |:diffpatch|'s behaviour of creating the patched files in
+the same directory as original file which can lead to project workspace
+pollution.
+
+=============================================================================
+
+PatchReview Options                                     *patchreview-options*
+
+  g:patchreview_filterdiff : Optional path to filterdiff binary. PatchReview
+                             tries to locate filterdiff on system path
+                             automatically. If the binary is not on system
+                             path, this option tell PatchReview the full path
+                             to the binary.  This option, if specified,
+                             overrides the default filterdiff binary on the
+                             path.
+
+     examples:
+        (On Windows with Cygwin)
+
+           let g:patchreview_filterdiff = 'c:\\cygwin\\bin\\filterdiff.exe'
+
+        (On *nix systems)
+
+           let g:patchreview_filterdiff = '/usr/bin/filterdiff'
+
+  g:patchreview_patch      : Optional path to patch binary. PatchReview tries
+                             to locate patch on system path automatically. If
+                             the binary is not on system path, this option
+                             tell PatchReview the full path to the binary.
+                             This option, if specified, overrides the default
+                             patch binary on the path.
+
+     examples:
+        (On Windows with Cygwin)
+
+           let g:patchreview_patch = 'c:\\cygwin\\bin\\patch.exe'
+
+        (On *nix systems)
+
+           let g:patchreview_patch = '/usr/bin/gpatch'
+
+
+  g:patchreview_tmpdir : Optional path where the plugin can save temporary
+                         files.  If this is not specified, the plugin tries to
+                         use TMP, TEMP and TMPDIR environment variables in
+                         succession.
+
+    examples:
+        (On Windows)      let g:patchreview_tmpdir = 'c:\\tmp'
+        (On *nix systems) let g:patchreview_tmpdir = '~/tmp'
+
+=============================================================================
+
+PatchReview Usage                                          *patchreview-usage*
+                                                                *:PatchReview*
+
+  :PatchReview patchfile_path [optional_source_directory]
+
+    Perform a patch review in the current directory based on the supplied
+    patchfile_path. If optional_source_directory is specified, patchreview is
+    done on that directory. Othewise, the current directory is assumed to be
+    the source directory.
+                                                          *:PatchReviewCleanup*
+
+  :PatchReviewCleanup
+
+    After you are done using the :PatchReview command, you can cleanup the
+    temporary files in the temporary directory using this command.
+
+=============================================================================
+vim: ft=help:ts=2:sts=2:sw=2:tw=78:tw=78
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/vim/patchreview.vim	Thu Jun 01 13:47:34 2006 -0700
@@ -0,0 +1,332 @@
+" Vim global plugin for doing single or multipatch code reviews"{{{
+
+" Version       : 0.1                                          "{{{
+" Last Modified : Thu 25 May 2006 10:15:11 PM PDT
+" Author        : Manpreet Singh (junkblocker AT yahoo DOT com)
+" Copyright     : 2006 by Manpreet Singh
+" License       : This file is placed in the public domain.
+"
+" History       : 0.1 - First released
+"}}}
+" Documentation:                                                         "{{{
+" ===========================================================================
+" This plugin allows single or multipatch code reviews to be done in VIM. Vim
+" has :diffpatch command to do single file reviews but can not handle patch
+" files containing multiple patches. This plugin provides that missing
+" functionality and doesn't require the original file to be open.
+"
+" Installing:                                                            "{{{
+"
+"  For a quick start...
+"
+"   Requirements:                                                        "{{{
+"
+"   1) (g)vim 7.0 or higher built with +diff option.
+"   2) patch and patchutils ( http://cyberelk.net/tim/patchutils/ ) installed
+"      for your OS. For windows it is availble from Cygwin (
+"      http://www.cygwin.com ) or GnuWin32 ( http://gnuwin32.sourceforge.net/
+"      ).
+""}}}
+"   Install:                                                            "{{{
+"
+"   1) Extract this in your $VIM/vimfiles or $HOME/.vim directory and restart
+"      vim.
+"
+"   2) Make sure that you have filterdiff from patchutils and patch commands
+"      installed.
+"
+"   3) Optinally, specify the locations to filterdiff and patch commands and
+"      location of a temporary directory to use in your .vimrc.
+"
+"      let g:patchreview_filterdiff  = '/path/to/filterdiff'
+"      let g:patchreview_patch       = '/path/to/patch'
+"      let g:patchreview_tmpdir      = '/tmp/or/something'
+"
+"   4) Optionally, generate help tags to use help
+"
+"      :helptags ~/.vim/doc
+"      or
+"      :helptags c:\vim\vimfiles\doc
+""}}}
+""}}}
+" Usage:                                                                 "{{{
+"
+"  :PatchReview path_to_submitted_patchfile [optional_source_directory]
+"
+"  after review is done
+"
+"  :PatchReviewCleanup
+"
+" See :help patchreview for details after you've created help tags.
+""}}}
+"}}}
+" Code                                                                   "{{{
+
+" Enabled only during development                                        "{{{
+" unlet! g:loaded_patchreview " DEBUG
+" unlet! g:patchreview_tmpdir " DEBUG
+" unlet! g:patchreview_filterdiff " DEBUG
+" unlet! g:patchreview_patch " DEBUG
+"}}}
+
+" load only once                                                         "{{{
+if exists('g:loaded_patchreview')
+  finish
+endif
+let g:loaded_patchreview=1
+let s:msgbufname = 'Patch Review Messages'
+"}}}
+
+function! <SID>PR_wipeMsgBuf()                                           "{{{
+  let s:winnum = bufwinnr(s:msgbufname)
+  if s:winnum != -1 " If the window is already open, jump to it
+    let s:cur_winnr = winnr()
+    if winnr() != s:winnum
+      exe s:winnum . 'wincmd w'
+      exe 'bw'
+      exe s:cur_winnr . 'wincmd w'
+    endif
+  endif
+endfunction
+"}}}
+
+function! <SID>PR_echo(...)                                              "{{{
+  " Usage: PR_echo(msg, [return_to_original_window_flag])
+  "            default return_to_original_window_flag = 0
+  "
+  let s:cur_winnr = winnr()
+  let s:winnum = bufwinnr(s:msgbufname)
+  if s:winnum != -1 " If the window is already open, jump to it
+    if winnr() != s:winnum
+      exe s:winnum . 'wincmd w'
+    endif
+  else
+    let s:bufnum = bufnr(s:msgbufname)
+    if s:bufnum == -1
+      let s:wcmd = s:msgbufname
+    else
+      let s:wcmd = '+buffer' . s:bufnum
+    endif
+    exe 'silent! botright 5split ' . s:wcmd
+  endif
+  setlocal modifiable
+  setlocal buftype=nofile
+  setlocal bufhidden=delete
+  setlocal noswapfile
+  setlocal nowrap
+  setlocal nobuflisted
+  if a:0 != 0
+    silent! $put =a:1
+  endif
+  exe ':$'
+  setlocal nomodifiable
+  if a:0 > 1 && a:2
+    exe s:cur_winnr . 'wincmd w'
+  endif
+endfunction
+"}}}
+
+function! <SID>PR_checkBinary(BinaryName)                                "{{{
+  " Verify that BinaryName is specified or available
+  if ! exists('g:patchreview_' . a:BinaryName)
+    if executable(a:BinaryName)
+      let g:patchreview_{a:BinaryName} = a:BinaryName
+      return 1
+    else
+      call s:PR_echo('g:patchreview_' . a:BinaryName . ' is not defined and could not be found on path. Please define it in your .vimrc.')
+      return 0
+    endif
+  elseif ! executable(g:patchreview_{a:BinaryName})
+    call s:PR_echo('Specified g:patchreview_' . a:BinaryName . ' [' . g:patchreview_{a.BinaryName} . '] is not executable.')
+    return 0
+  else
+    return 1
+  endif
+endfunction
+"}}}
+
+function! <SID>PR_GetTempDirLocation(Quiet)                              "{{{
+  if exists('g:patchreview_tmpdir')
+    if ! isdirectory(g:patchreview_tmpdir) || ! filewritable(g:patchreview_tmpdir)
+      if ! a:Quiet
+        call s:PR_echo('Temporary directory specified by g:patchreview_tmpdir [' . g:patchreview_tmpdir . '] is not accessible.')
+        return 0
+      endif
+    endif
+  elseif exists("$TMP") && isdirectory($TMP) && filewritable($TMP)
+    let g:patchreview_tmpdir = $TMP
+  elseif exists("$TEMP") && isdirectory($TEMP) && filewritable($TEMP)
+    let g:patchreview_tmpdir = $TEMP
+  elseif exists("$TMPDIR") && isdirectory($TMPDIR) && filewritable($TMPDIR)
+    let g:patchreview_tmpdir = $TMPDIR
+  else
+    if ! a:Quiet
+      call s:PR_echo('Could not figure out a temporary directory to use. Please specify g:patchreview_tmpdir in your .vimrc.')
+      return 0
+    endif
+  endif
+  let g:patchreview_tmpdir = g:patchreview_tmpdir . '/'
+  let g:patchreview_tmpdir = substitute(g:patchreview_tmpdir, '\\', '/', 'g')
+  let g:patchreview_tmpdir = substitute(g:patchreview_tmpdir, '/+$', '/', '')
+  if has('win32')
+    let g:patchreview_tmpdir = substitute(g:patchreview_tmpdir, '/', '\\', 'g')
+  endif
+  return 1
+endfunction
+"}}}
+
+function! <SID>PatchReview(...)                                          "{{{
+  " VIM 7+ required"{{{
+  if version < 700
+    call s:PR_echo('This plugin needs VIM 7 or higher')
+    return
+  endif
+"}}}
+
+  let s:save_shortmess = &shortmess
+  set shortmess+=aW
+  call s:PR_wipeMsgBuf()
+
+  " Check passed arguments                                               "{{{
+  if a:0 == 0
+    call s:PR_echo('PatchReview command needs at least one argument specifying a patchfile path.')
+    let &shortmess = s:save_shortmess
+    return
+  endif
+  if a:0 >= 1 && a:0 <= 2
+    let s:PatchFilePath = expand(a:1, ':p')
+    if ! filereadable(s:PatchFilePath)
+      call s:PR_echo('File [' . s:PatchFilePath . '] is not accessible.')
+      let &shortmess = s:save_shortmess
+      return
+    endif
+    if a:0 == 2
+      let s:SrcDirectory = expand(a:2, ':p')
+      if ! isdirectory(s:SrcDirectory)
+        call s:PR_echo('[' . s:SrcDirectory . '] is not a directory')
+        let &shortmess = s:save_shortmess
+        return
+      endif
+      try
+        exe 'cd ' . s:SrcDirectory
+      catch /^.*E344.*/
+        call s:PR_echo('Could not change to directory [' . s:SrcDirectory . ']')
+        let &shortmess = s:save_shortmess
+        return
+      endtry
+    endif
+  else
+    call s:PR_echo('PatchReview command needs at most two arguments: patchfile path and optional source directory path.')
+    let &shortmess = s:save_shortmess
+    return
+  endif
+"}}}
+
+  " Verify that filterdiff and patch are specified or available          "{{{
+  if ! s:PR_checkBinary('filterdiff') || ! s:PR_checkBinary('patch')
+    let &shortmess = s:save_shortmess
+    return
+  endif
+
+  let s:retval = s:PR_GetTempDirLocation(0)
+  if ! s:retval
+    let &shortmess = s:save_shortmess
+    return
+  endif
+"}}}
+
+  " Requirements met, now execute                                        "{{{
+  let s:PatchFilePath = fnamemodify(s:PatchFilePath, ':p')
+  call s:PR_echo('Patch file      : ' . s:PatchFilePath)
+  call s:PR_echo('Source directory: ' . getcwd())
+  call s:PR_echo('------------------')
+  let s:theFilterDiffCommand = '' . g:patchreview_filterdiff . ' --list -s ' . s:PatchFilePath
+  let s:theFilesString = system(s:theFilterDiffCommand)
+  let s:theFilesList = split(s:theFilesString, '[\r\n]')
+  for s:filewithchangetype in s:theFilesList
+    if s:filewithchangetype !~ '^[!+-] '
+      call s:PR_echo('*** Skipping review generation due to understood change for [' . s:filewithchangetype . ']', 1)
+      continue
+    endif
+    unlet! s:RelativeFilePath
+    let s:RelativeFilePath = substitute(s:filewithchangetype, '^. ', '', '')
+    let s:RelativeFilePath = substitute(s:RelativeFilePath, '^[a-z][^\\\/]*[\\\/]' , '' , '')
+    if s:filewithchangetype =~ '^! '
+      let s:msgtype = 'Modification : '
+    elseif s:filewithchangetype =~ '^+ '
+      let s:msgtype = 'Addition     : '
+    elseif s:filewithchangetype =~ '^- '
+      let s:msgtype = 'Deletion     : '
+    endif
+    let s:bufnum = bufnr(s:RelativeFilePath)
+    if buflisted(s:bufnum) && getbufvar(s:bufnum, '&mod')
+      call s:PR_echo('Old buffer for file [' . s:RelativeFilePath . '] exists in modified state. Skipping review.', 1)
+      continue
+    endif
+    let s:tmpname = substitute(s:RelativeFilePath, '/', '_', 'g')
+    let s:tmpname = substitute(s:tmpname, '\\', '_', 'g')
+    let s:tmpname = g:patchreview_tmpdir . 'PatchReview.' . s:tmpname . '.' . strftime('%Y%m%d%H%M%S')
+    if has('win32')
+      let s:tmpname = substitute(s:tmpname, '/', '\\', 'g')
+    endif
+    if ! exists('s:patchreview_tmpfiles')
+      let s:patchreview_tmpfiles = []
+    endif
+    let s:patchreview_tmpfiles = s:patchreview_tmpfiles + [s:tmpname]
+
+    let s:filterdiffcmd = '!' . g:patchreview_filterdiff . ' -i ' . s:RelativeFilePath . ' ' . s:PatchFilePath . ' > ' . s:tmpname
+    silent! exe s:filterdiffcmd
+    if s:filewithchangetype =~ '^+ '
+      if has('win32')
+        let s:inputfile = 'nul'
+      else
+        let s:inputfile = '/dev/null'
+      endif
+    else
+      let s:inputfile = expand(s:RelativeFilePath, ':p')
+    endif
+    silent exe '!' . g:patchreview_patch . ' -o ' . s:tmpname . '.file ' . s:inputfile . ' < ' . s:tmpname
+    let s:origtabpagenr = tabpagenr()
+    silent! exe 'tabedit ' . s:RelativeFilePath
+    silent! exe 'vert diffsplit ' . s:tmpname . '.file'
+    if filereadable(s:tmpname . '.file.rej')
+      silent! exe 'topleft 5split ' . s:tmpname . '.file.rej'
+      call s:PR_echo(s:msgtype . '*** REJECTED *** ' . s:RelativeFilePath, 1)
+    else
+      call s:PR_echo(s:msgtype . ' ' . s:RelativeFilePath, 1)
+    endif
+    silent! exe 'tabn ' . s:origtabpagenr
+  endfor
+  call s:PR_echo('-----')
+  call s:PR_echo('Done.')
+  let &shortmess = s:save_shortmess
+"}}}
+endfunction
+"}}}
+
+function! <SID>PatchReviewCleanup()                                      "{{{
+  let s:retval = s:PR_GetTempDirLocation(1)
+  if s:retval && exists('g:patchreview_tmpdir') && isdirectory(g:patchreview_tmpdir) && filewritable(g:patchreview_tmpdir)
+    let s:zefilestr = globpath(g:patchreview_tmpdir, 'PatchReview.*')
+    let s:theFilesList = split(s:zefilestr, '\m[\r\n]\+')
+    for s:thefile in s:theFilesList
+      call delete(s:thefile)
+    endfor
+  endif
+endfunction
+"}}}
+
+" Commands                                                               "{{{
+"============================================================================
+" :PatchReview
+command! -nargs=* -complete=file PatchReview call s:PatchReview (<f-args>)
+
+
+" :PatchReviewCleanup
+command! -nargs=0 PatchReviewCleanup call s:PatchReviewCleanup ()
+"}}}
+"}}}
+
+" vim: textwidth=78 nowrap tabstop=2 shiftwidth=2 softtabstop=2 expandtab
+" vim: filetype=vim encoding=latin1 fileformat=unix foldlevel=0 foldmethod=marker
+"}}}
--- a/contrib/win32/ReadMe.html	Mon May 22 16:47:40 2006 +0200
+++ b/contrib/win32/ReadMe.html	Thu Jun 01 13:47:34 2006 -0700
@@ -45,6 +45,16 @@
     <p>This command should print a useful help message.  If it does,
       other Mercurial commands should work fine for you.</p>
 
+    <h1>Configuration notes</h1>
+    <p>The default editor for commit messages is 'vi'. You can set the EDITOR
+    (or HGEDITOR) environment variable to specify your preference or set it in
+    mercurial.ini:</p>
+    <pre>
+[ui]
+editor = whatever
+</pre>
+
+
     <h1>Reporting problems</h1>
 
     <p>Before you report any problems, please consult the <a
--- a/doc/hgrc.5.txt	Mon May 22 16:47:40 2006 +0200
+++ b/doc/hgrc.5.txt	Thu Jun 01 13:47:34 2006 -0700
@@ -36,11 +36,14 @@
     files override per-installation options.
 
 (Unix)    $HOME/.hgrc::
-(Windows) C:\Documents and Settings\USERNAME\Mercurial.ini
+(Windows) C:\Documents and Settings\USERNAME\Mercurial.ini::
+(Windows) $HOME\Mercurial.ini::
     Per-user configuration file, for the user running Mercurial.
     Options in this file apply to all Mercurial commands executed by
     any user in any directory.  Options in this file override
     per-installation and per-system options.
+    On Windows system, one of these is chosen exclusively according
+    to definition of HOME environment variable.
 
 (Unix, Windows) <repo>/.hg/hgrc::
     Per-repository configuration options that only apply in a
@@ -359,15 +362,20 @@
     Where to output the access log. Default is stdout.
   address;;
     Interface address to bind to. Default is all.
+  allow_archive;;
+    List of archive format (bz2, gz, zip) allowed for downloading.
+    Default is empty.
   allowbz2;;
-    Whether to allow .tar.bz2 downloading of repo revisions. Default is false.
+    (DEPRECATED) Whether to allow .tar.bz2 downloading of repo revisions.
+    Default is false.
   allowgz;;
-    Whether to allow .tar.gz downloading of repo revisions. Default is false.
+    (DEPRECATED) Whether to allow .tar.gz downloading of repo revisions.
+    Default is false.
   allowpull;;
     Whether to allow pulling from the repository. Default is true.
   allowzip;;
-    Whether to allow .zip downloading of repo revisions. Default is false.
-    This feature creates temporary files.
+    (DEPRECATED) Whether to allow .zip downloading of repo revisions.
+    Default is false. This feature creates temporary files.
   baseurl;;
     Base URL to use when publishing URLs in other locations, so
     third-party tools like email notification hooks can construct URLs.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/acl.py	Thu Jun 01 13:47:34 2006 -0700
@@ -0,0 +1,124 @@
+# acl.py - changeset access control for mercurial
+#
+# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+#
+# this hook allows to allow or deny access to parts of a repo when
+# taking incoming changesets.
+#
+# authorization is against local user name on system where hook is
+# run, not committer of original changeset (since that is easy to
+# spoof).
+#
+# acl hook is best to use if you use hgsh to set up restricted shells
+# for authenticated users to only push to / pull from.  not safe if
+# user has interactive shell access, because they can disable hook.
+# also not safe if remote users share one local account, because then
+# no way to tell remote users apart.
+#
+# to use, configure acl extension in hgrc like this:
+#
+#   [extensions]
+#   hgext.acl =
+#
+#   [hooks]
+#   pretxnchangegroup.acl = python:hgext.acl.hook
+#
+#   [acl]
+#   sources = serve        # check if source of incoming changes in this list
+#                          # ("serve" == ssh or http, "push", "pull", "bundle")
+#
+# allow and deny lists have subtree pattern (default syntax is glob)
+# on left, user names on right. deny list checked before allow list.
+#
+#   [acl.allow]
+#   # if acl.allow not present, all users allowed by default
+#   # empty acl.allow = no users allowed
+#   docs/** = doc_writer
+#   .hgtags = release_engineer
+#
+#   [acl.deny]
+#   # if acl.deny not present, no users denied by default
+#   # empty acl.deny = all users allowed
+#   glob pattern = user4, user5
+#   ** = user6
+
+from mercurial.demandload import *
+from mercurial.i18n import gettext as _
+from mercurial.node import *
+demandload(globals(), 'getpass mercurial:util')
+
+class checker(object):
+    '''acl checker.'''
+
+    def buildmatch(self, key):
+        '''return tuple of (match function, list enabled).'''
+        if not self.ui.has_config(key):
+            self.ui.debug(_('acl: %s not enabled\n') % key)
+            return None, False
+
+        thisuser = self.getuser()
+        pats = [pat for pat, user in self.ui.configitems(key)
+                if user == thisuser]
+        self.ui.debug(_('acl: %s enabled, %d entries for user %s\n') %
+                      (key, len(pats), thisuser))
+        if pats:
+            match = util.matcher(self.repo.root, names=pats)[1]
+        else:
+            match = util.never
+        return match, True
+
+    def getuser(self):
+        '''return name of authenticated user.'''
+        return self.user
+
+    def __init__(self, ui, repo):
+        self.ui = ui
+        self.repo = repo
+        self.user = getpass.getuser()
+        cfg = self.ui.config('acl', 'config')
+        if cfg:
+            self.ui.readconfig(cfg)
+        self.allow, self.allowable = self.buildmatch('acl.allow')
+        self.deny, self.deniable = self.buildmatch('acl.deny')
+
+    def skipsource(self, source):
+        '''true if incoming changes from this source should be skipped.'''
+        ok_sources = self.ui.config('acl', 'sources', 'serve').split()
+        return source not in ok_sources
+
+    def check(self, node):
+        '''return if access allowed, raise exception if not.'''
+        files = self.repo.changelog.read(node)[3]
+        if self.deniable:
+            for f in files:
+                if self.deny(f):
+                    self.ui.debug(_('acl: user %s denied on %s\n') %
+                                  (self.getuser(), f))
+                    raise util.Abort(_('acl: access denied for changeset %s') %
+                                     short(node))
+        if self.allowable:
+            for f in files:
+                if not self.allow(f):
+                    self.ui.debug(_('acl: user %s not allowed on %s\n') %
+                                  (self.getuser(), f))
+                    raise util.Abort(_('acl: access denied for changeset %s') %
+                                     short(node))
+        self.ui.debug(_('acl: allowing changeset %s\n') % short(node))
+
+def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
+    if hooktype != 'pretxnchangegroup':
+        raise util.Abort(_('config error - hook type "%s" cannot stop '
+                           'incoming changesets') % hooktype)
+
+    c = checker(ui, repo)
+    if c.skipsource(source):
+        ui.debug(_('acl: changes have source "%s" - skipping\n') % source)
+        return
+
+    start = repo.changelog.rev(bin(node))
+    end = repo.changelog.count()
+    for rev in xrange(start, end):
+        c.check(repo.changelog.node(rev))
--- a/hgext/bugzilla.py	Mon May 22 16:47:40 2006 +0200
+++ b/hgext/bugzilla.py	Thu Jun 01 13:47:34 2006 -0700
@@ -22,13 +22,16 @@
 #
 # config items:
 #
+# section name is 'bugzilla'.
+#  [bugzilla]
+#
 # REQUIRED:
 #   host = bugzilla # mysql server where bugzilla database lives
 #   password = **   # user's password
 #   version = 2.16  # version of bugzilla installed
 #
 # OPTIONAL:
-#   bzuser = ...    # bugzilla user id to record comments with
+#   bzuser = ...    # fallback bugzilla user name to record comments with
 #   db = bugs       # database to connect to
 #   notify = ...    # command to run to get bugzilla to send mail
 #   regexp = ...    # regexp to match bug ids (must contain one "()" group)
@@ -39,6 +42,15 @@
 #   user = bugs     # user to connect to database as
 #   [web]
 #   baseurl = http://hgserver/... # root of hg web site for browsing commits
+#
+# if hg committer names are not same as bugzilla user names, use
+# "usermap" feature to map from committer email to bugzilla user name.
+# usermap can be in hgrc or separate config file.
+#
+#   [bugzilla]
+#   usermap = filename # cfg file with "committer"="bugzilla user" info
+#   [usermap]
+#   committer_email = bugzilla_user_name
 
 from mercurial.demandload import *
 from mercurial.i18n import gettext as _
@@ -60,6 +72,9 @@
         passwd = self.ui.config('bugzilla', 'password')
         db = self.ui.config('bugzilla', 'db', 'bugs')
         timeout = int(self.ui.config('bugzilla', 'timeout', 5))
+        usermap = self.ui.config('bugzilla', 'usermap')
+        if usermap:
+            self.ui.readconfig(usermap)
         self.ui.note(_('connecting to %s:%s as %s, password %s\n') %
                      (host, db, user, '*' * len(passwd)))
         self.conn = MySQLdb.connect(host=host, user=user, passwd=passwd,
@@ -139,18 +154,29 @@
             self.user_ids[user] = userid
             return userid
 
-    def add_comment(self, bugid, text, prefuser):
+    def map_committer(self, user):
+        '''map name of committer to bugzilla user name.'''
+        for committer, bzuser in self.ui.configitems('usermap'):
+            if committer.lower() == user.lower():
+                return bzuser
+        return user
+
+    def add_comment(self, bugid, text, committer):
         '''add comment to bug. try adding comment as committer of
         changeset, otherwise as default bugzilla user.'''
+        user = self.map_committer(committer)
         try:
-            userid = self.get_user_id(prefuser)
+            userid = self.get_user_id(user)
         except KeyError:
             try:
                 defaultuser = self.ui.config('bugzilla', 'bzuser')
+                if not defaultuser:
+                    raise util.Abort(_('cannot find bugzilla user id for %s') %
+                                     user)
                 userid = self.get_user_id(defaultuser)
             except KeyError:
-                raise util.Abort(_('cannot find user id for %s or %s') %
-                                 (prefuser, defaultuser))
+                raise util.Abort(_('cannot find bugzilla user id for %s or %s') %
+                                 (user, defaultuser))
         now = time.strftime('%Y-%m-%d %H:%M:%S')
         self.run('''insert into longdescs
                     (bug_id, who, bug_when, thetext)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/extdiff.py	Thu Jun 01 13:47:34 2006 -0700
@@ -0,0 +1,150 @@
+# extdiff.py - external diff program support for mercurial
+#
+# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+#
+# allow to use external programs to compare revisions, or revision
+# with working dir. program is called with two arguments: paths to
+# directories containing snapshots of files to compare.
+#
+# to enable:
+#
+#   [extensions]
+#   hgext.extdiff =
+#
+# also allows to configure new diff commands, so you do not need to
+# type "hg extdiff -p kdiff3" always.
+#
+#   [extdiff]
+#   # add new command called vdiff, runs kdiff3
+#   cmd.vdiff = kdiff3
+#   # add new command called meld, runs meld (no need to name twice)
+#   cmd.meld =
+#
+# you can use -I/-X and list of file or directory names like normal
+# "hg diff" command. extdiff makes snapshots of only needed files, so
+# compare program will be fast.
+
+from mercurial.demandload import demandload
+from mercurial.i18n import gettext as _
+from mercurial.node import *
+demandload(globals(), 'mercurial:commands,util os shutil tempfile')
+
+def dodiff(ui, repo, diffcmd, pats, opts):
+    def snapshot_node(files, node):
+        '''snapshot files as of some revision'''
+        changes = repo.changelog.read(node)
+        mf = repo.manifest.read(changes[0])
+        dirname = '%s.%s' % (os.path.basename(repo.root), short(node))
+        base = os.path.join(tmproot, dirname)
+        os.mkdir(base)
+        if not ui.quiet:
+            ui.write_err(_('making snapshot of %d files from rev %s\n') %
+                         (len(files), short(node)))
+        for fn in files:
+            wfn = util.pconvert(fn)
+            ui.note('  %s\n' % wfn)
+            dest = os.path.join(base, wfn)
+            destdir = os.path.dirname(dest)
+            if not os.path.isdir(destdir):
+                os.makedirs(destdir)
+            repo.wwrite(wfn, repo.file(fn).read(mf[fn]), open(dest, 'w'))
+        return dirname
+
+    def snapshot_wdir(files):
+        '''snapshot files from working directory.
+        if not using snapshot, -I/-X does not work and recursive diff
+        in tools like kdiff3 and meld displays too many files.'''
+        dirname = os.path.basename(repo.root)
+        base = os.path.join(tmproot, dirname)
+        os.mkdir(base)
+        if not ui.quiet:
+            ui.write_err(_('making snapshot of %d files from working dir\n') %
+                         (len(files)))
+        for fn in files:
+            wfn = util.pconvert(fn)
+            ui.note('  %s\n' % wfn)
+            dest = os.path.join(base, wfn)
+            destdir = os.path.dirname(dest)
+            if not os.path.isdir(destdir):
+                os.makedirs(destdir)
+            fp = open(dest, 'w')
+            for chunk in util.filechunkiter(repo.wopener(wfn)):
+                fp.write(chunk)
+        return dirname
+
+    node1, node2 = commands.revpair(ui, repo, opts['rev'])
+    files, matchfn, anypats = commands.matchpats(repo, pats, opts)
+    modified, added, removed, deleted, unknown = repo.changes(
+        node1, node2, files, match=matchfn)
+    if not (modified or added or removed):
+        return 0
+
+    tmproot = tempfile.mkdtemp(prefix='extdiff.')
+    try:
+        dir1 = snapshot_node(modified + removed, node1)
+        if node2:
+            dir2 = snapshot_node(modified + added, node2)
+        else:
+            dir2 = snapshot_wdir(modified + added)
+        util.system('%s %s "%s" "%s"' %
+                    (diffcmd, ' '.join(opts['option']), dir1, dir2),
+                    cwd=tmproot)
+        return 1
+    finally:
+        ui.note(_('cleaning up temp directory\n'))
+        shutil.rmtree(tmproot)
+
+def extdiff(ui, repo, *pats, **opts):
+    '''use external program to diff repository (or selected files)
+
+    Show differences between revisions for the specified files, using
+    an external program.  The default program used is "diff -Npru".
+    To select a different program, use the -p option.  The program
+    will be passed the names of two directories to compare.  To pass
+    additional options to the program, use the -o option.  These will
+    be passed before the names of the directories to compare.
+
+    When two revision arguments are given, then changes are
+    shown between those revisions. If only one revision is
+    specified then that revision is compared to the working
+    directory, and, when no revisions are specified, the
+    working directory files are compared to its parent.'''
+    return dodiff(ui, repo, opts['program'] or 'diff -Npru', pats, opts)
+
+cmdtable = {
+    "extdiff":
+    (extdiff,
+     [('p', 'program', '', _('comparison program to run')),
+      ('o', 'option', [], _('pass option to comparison program')),
+      ('r', 'rev', [], _('revision')),
+      ('I', 'include', [], _('include names matching the given patterns')),
+      ('X', 'exclude', [], _('exclude names matching the given patterns'))],
+     _('hg extdiff [OPT]... [FILE]...')),
+    }
+
+def uisetup(ui):
+    for cmd, path in ui.configitems('extdiff'):
+        if not cmd.startswith('cmd.'): continue
+        cmd = cmd[4:]
+        if not path: path = cmd
+        def save(cmd, path):
+            '''use closure to save diff command to use'''
+            def mydiff(ui, repo, *pats, **opts):
+                return dodiff(ui, repo, path, pats, opts)
+            mydiff.__doc__ = '''use %s to diff repository (or selected files)
+
+            Show differences between revisions for the specified
+            files, using the %s program.
+
+            When two revision arguments are given, then changes are
+            shown between those revisions. If only one revision is
+            specified then that revision is compared to the working
+            directory, and, when no revisions are specified, the
+            working directory files are compared to its parent.''' % (cmd, cmd)
+            return mydiff
+        cmdtable[cmd] = (save(cmd, path),
+                         cmdtable['extdiff'][1][1:],
+                         _('hg %s [OPT]... [FILE]...') % cmd)
--- a/hgext/hbisect.py	Mon May 22 16:47:40 2006 +0200
+++ b/hgext/hbisect.py	Thu Jun 01 13:47:34 2006 -0700
@@ -6,8 +6,9 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
+from mercurial.i18n import gettext as _
 from mercurial.demandload import demandload
-demandload(globals(), "os sys sets mercurial:hg,util")
+demandload(globals(), "os sys sets mercurial:hg,util,commands")
 
 versionstr = "0.0.3"
 
@@ -17,9 +18,8 @@
         return repo.lookup(rev)
     parents = [p for p in repo.dirstate.parents() if p != hg.nullid]
     if len(parents) != 1:
-        ui.warn("unexpected number of parents\n")
-        ui.warn("please commit or revert\n")
-        sys.exit(1)
+        raise util.Abort(_("unexpected number of parents, "
+                           "please commit or revert"))
     return parents.pop()
 
 def check_clean(ui, repo):
@@ -64,8 +64,7 @@
     def init(self):
         """start a new bisection"""
         if os.path.isdir(self.path):
-            self.ui.warn("bisect directory already exists\n")
-            return 1
+            raise util.Abort(_("bisect directory already exists\n"))
         os.mkdir(self.path)
         check_clean(self.ui, self.repo)
         return 0
@@ -137,9 +136,8 @@
             return d
 
         if head in stop:
-            self.ui.warn("Unconsistent state, %s is good and bad\n"
-                          % hg.hex(head))
-            sys.exit(1)
+            raise util.Abort(_("Unconsistent state, %s:%s is good and bad")
+                             % (cl.rev(head), hg.short(head)))
         n_child = num_children(head)
         for i in xrange(cl.rev(head)+1):
             n = cl.node(i)
@@ -160,22 +158,20 @@
 
     def next(self):
         if not self.badrev:
-            self.ui.warn("You should give at least one bad\n")
-            sys.exit(1)
+            raise util.Abort(_("You should give at least one bad revision"))
         if not self.goodrevs:
-            self.ui.warn("No good revision given\n")
-            self.ui.warn("Assuming the first revision is good\n")
+            self.ui.warn(_("No good revision given\n"))
+            self.ui.warn(_("Marking the first revision as good\n"))
         ancestors, num_ancestors = self.__ancestors_and_nb_ancestors(
                                     self.badrev)
         tot = len(ancestors)
         if tot == 1:
             if ancestors.pop() != self.badrev:
-                self.ui.warn("Could not find the first bad revision\n")
-                sys.exit(1)
-            self.ui.write(
-                "The first bad revision is : %s\n" % hg.hex(self.badrev))
-            sys.exit(0)
-        self.ui.write("%d revisions left\n" % tot)
+                raise util.Abort(_("Could not find the first bad revision"))
+            self.ui.write(_("The first bad revision is:\n"))
+            displayer = commands.show_changeset(self.ui, self.repo, {})
+            displayer.show(changenode=self.badrev)
+            return None
         best_rev = None
         best_len = -1
         for n in ancestors:
@@ -184,14 +180,24 @@
             if l > best_len:
                 best_len = l
                 best_rev = n
+        assert best_rev is not None
+        nb_tests = 0
+        q, r = divmod(tot, 2)
+        while q:
+            nb_tests += 1
+            q, r = divmod(q, 2)
+        msg = _("Testing changeset %s:%s (%s changesets remaining, "
+                "~%s tests)\n") % (self.repo.changelog.rev(best_rev),
+                                   hg.short(best_rev), tot, nb_tests)
+        self.ui.write(msg)
         return best_rev
 
     def autonext(self):
         """find and update to the next revision to test"""
         check_clean(self.ui, self.repo)
         rev = self.next()
-        self.ui.write("Now testing %s\n" % hg.hex(rev))
-        return self.repo.update(rev, force=True)
+        if rev is not None:
+            return self.repo.update(rev, force=True)
 
     def good(self, rev):
         self.goodrevs.append(rev)
@@ -202,7 +208,7 @@
         rev = lookup_rev(self.ui, self.repo, rev)
         self.good(rev)
         if self.badrev:
-            self.autonext()
+            return self.autonext()
 
     def bad(self, rev):
         self.badrev = rev
@@ -236,7 +242,7 @@
             b.good(new_rev)
             ui.write("it is good\n")
         anc = b.ancestors()
-        repo.update(new_rev, force=True)
+        #repo.update(new_rev, force=True)
     for v in anc:
         if v != rev:
             ui.warn("fail to found cset! :(\n")
@@ -258,7 +264,7 @@
             ui.write(synopsis + "\n")
             ui.write("\n" + doc + "\n")
             return
-        ui.write("list of subcommands for the bisect extension\n\n")
+        ui.write(_("list of subcommands for the bisect extension\n\n"))
         cmds = cmdtable.keys()
         cmds.sort()
         m = max([len(c) for c in cmds])
@@ -268,23 +274,23 @@
 
     b = bisect(ui, repo)
     bisectcmdtable = {
-        "init": (b.init, 0, "hg bisect init"),
-        "bad": (b.autobad, 1, "hg bisect bad [<rev>]"),
-        "good": (b.autogood, 1, "hg bisect good [<rev>]"),
-        "next": (b.autonext, 0, "hg bisect next"),
-        "reset": (b.reset, 0, "hg bisect reset"),
-        "help": (help_, 1, "hg bisect help [<subcommand>]"),
+        "init": (b.init, 0, _("hg bisect init")),
+        "bad": (b.autobad, 1, _("hg bisect bad [<rev>]")),
+        "good": (b.autogood, 1, _("hg bisect good [<rev>]")),
+        "next": (b.autonext, 0, _("hg bisect next")),
+        "reset": (b.reset, 0, _("hg bisect reset")),
+        "help": (help_, 1, _("hg bisect help [<subcommand>]")),
     }
 
     if not bisectcmdtable.has_key(cmd):
-        ui.warn("bisect: Unknown sub-command\n")
+        ui.warn(_("bisect: Unknown sub-command\n"))
         return help_()
     if len(args) > bisectcmdtable[cmd][1]:
-        ui.warn("bisect: Too many arguments\n")
+        ui.warn(_("bisect: Too many arguments\n"))
         return help_()
     return bisectcmdtable[cmd][0](*args)
 
 cmdtable = {
-    "bisect": (bisect_run, [], "hg bisect [help|init|reset|next|good|bad]"),
+    "bisect": (bisect_run, [], _("hg bisect [help|init|reset|next|good|bad]")),
     #"bisect-test": (test, [], "hg bisect-test rev"),
 }
--- a/hgext/mq.py	Mon May 22 16:47:40 2006 +0200
+++ b/hgext/mq.py	Thu Jun 01 13:47:34 2006 -0700
@@ -102,6 +102,7 @@
         message = []
         comments = []
         user = None
+        date = None
         format = None
         subject = None
         diffstart = 0
@@ -119,6 +120,8 @@
                 # parse values when importing the result of an hg export
                 if line.startswith("# User "):
                     user = line[7:]
+                elif line.startswith("# Date "):
+                    date = line[7:]
                 elif not line.startswith("# ") and line:
                     message.append(line)
                     format = None
@@ -136,7 +139,7 @@
                 # when looking for tags (subject: from: etc) they
                 # end once you find a blank line in the source
                 format = "tagdone"
-            else:
+            elif message or line:
                 message.append(line)
             comments.append(line)
 
@@ -149,7 +152,7 @@
         if format and format.startswith("tag") and subject:
             message.insert(0, "")
             message.insert(0, subject)
-        return (message, comments, user, diffstart > 1)
+        return (message, comments, user, date, diffstart > 1)
 
     def mergeone(self, repo, mergeq, head, patch, rev, wlock):
         # first try just applying the patch
@@ -179,7 +182,7 @@
             self.ui.warn("repo commit failed\n")
             sys.exit(1)
         try:
-            message, comments, user, patchfound = mergeq.readheaders(patch)
+            message, comments, user, date, patchfound = mergeq.readheaders(patch)
         except:
             self.ui.warn("Unable to read %s\n" % patch)
             sys.exit(1)
@@ -267,7 +270,7 @@
             pf = os.path.join(patchdir, patch)
 
             try:
-                message, comments, user, patchfound = self.readheaders(patch)
+                message, comments, user, date, patchfound = self.readheaders(patch)
             except:
                 self.ui.warn("Unable to read %s\n" % pf)
                 err = 1
@@ -326,7 +329,7 @@
             if len(files) > 0:
                 commands.addremove_lock(self.ui, repo, files,
                                         opts={}, wlock=wlock)
-            n = repo.commit(files, message, user, force=1, lock=lock,
+            n = repo.commit(files, message, user, date, force=1, lock=lock,
                             wlock=wlock)
 
             if n == None:
@@ -716,7 +719,7 @@
         top = revlog.bin(top)
         cparents = repo.changelog.parents(top)
         patchparent = self.qparents(repo, top)
-        message, comments, user, patchfound = self.readheaders(patch)
+        message, comments, user, date, patchfound = self.readheaders(patch)
 
         patchf = self.opener(patch, "w")
         if comments:
--- a/hgext/notify.py	Mon May 22 16:47:40 2006 +0200
+++ b/hgext/notify.py	Thu Jun 01 13:47:34 2006 -0700
@@ -99,7 +99,9 @@
 
     def __init__(self, ui, repo, hooktype):
         self.ui = ui
-        self.ui.readconfig(self.ui.config('notify', 'config'))
+        cfg = self.ui.config('notify', 'config')
+        if cfg:
+            self.ui.readconfig(cfg)
         self.repo = repo
         self.stripcount = int(self.ui.config('notify', 'strip', 0))
         self.root = self.strip(self.repo.root)
@@ -123,7 +125,7 @@
 
         path = util.pconvert(path)
         count = self.stripcount
-        while path and count >= 0:
+        while count > 0:
             c = path.find('/')
             if c == -1:
                 break
@@ -225,6 +227,8 @@
             if not msgtext.endswith('\n'):
                 self.ui.write('\n')
         else:
+            self.ui.status(_('notify: sending %d subscribers %d changes\n') %
+                             (len(self.subs), count))
             mail = self.ui.sendmail()
             mail.sendmail(templater.email(msg['From']), self.subs, msgtext)
 
@@ -250,7 +254,12 @@
     if used as changegroup hook, send one email for all changesets in
     changegroup. else send one email per changeset.'''
     n = notifier(ui, repo, hooktype)
-    if not n.subs or n.skipsource(source):
+    if not n.subs:
+        ui.debug(_('notify: no subscribers to this repo\n'))
+        return
+    if n.skipsource(source):
+        ui.debug(_('notify: changes have source "%s" - skipping\n') %
+                  source)
         return
     node = bin(node)
     if hooktype == 'changegroup':
--- a/mercurial/archival.py	Mon May 22 16:47:40 2006 +0200
+++ b/mercurial/archival.py	Thu Jun 01 13:47:34 2006 -0700
@@ -156,7 +156,7 @@
         if matchfn and not matchfn(name): return
         if decode:
             fp = cStringIO.StringIO()
-            repo.wwrite(None, data, fp)
+            repo.wwrite(name, data, fp)
             data = fp.getvalue()
         archiver.addfile(name, mode, data)
 
--- a/mercurial/commands.py	Mon May 22 16:47:40 2006 +0200
+++ b/mercurial/commands.py	Thu Jun 01 13:47:34 2006 -0700
@@ -10,9 +10,11 @@
 from i18n import gettext as _
 demandload(globals(), "os re sys signal shutil imp urllib pdb")
 demandload(globals(), "fancyopts ui hg util lock revlog templater bundlerepo")
-demandload(globals(), "fnmatch hgweb mdiff random signal tempfile time")
+demandload(globals(), "fnmatch mdiff random signal tempfile time")
 demandload(globals(), "traceback errno socket version struct atexit sets bz2")
 demandload(globals(), "archival changegroup")
+demandload(globals(), "mercurial.hgweb.server:create_server")
+demandload(globals(), "mercurial.hgweb:hgweb,hgwebdir")
 
 class UnknownCommand(Exception):
     """Exception raised if command is not in the command table."""
@@ -179,39 +181,61 @@
 
 revrangesep = ':'
 
-def revrange(ui, repo, revs, revlog=None):
-    """Yield revision as strings from a list of revision specifications."""
-    if revlog is None:
-        revlog = repo.changelog
-    revcount = revlog.count()
-    def fix(val, defval):
-        if not val:
-            return defval
+def revfix(repo, val, defval):
+    '''turn user-level id of changeset into rev number.
+    user-level id can be tag, changeset, rev number, or negative rev
+    number relative to number of revs (-1 is tip, etc).'''
+    if not val:
+        return defval
+    try:
+        num = int(val)
+        if str(num) != val:
+            raise ValueError
+        if num < 0:
+            num += repo.changelog.count()
+        if num < 0:
+            num = 0
+        elif num >= repo.changelog.count():
+            raise ValueError
+    except ValueError:
         try:
-            num = int(val)
-            if str(num) != val:
-                raise ValueError
-            if num < 0:
-                num += revcount
-            if num < 0:
-                num = 0
-            elif num >= revcount:
-                raise ValueError
-        except ValueError:
-            try:
-                num = repo.changelog.rev(repo.lookup(val))
-            except KeyError:
-                try:
-                    num = revlog.rev(revlog.lookup(val))
-                except KeyError:
-                    raise util.Abort(_('invalid revision identifier %s'), val)
-        return num
+            num = repo.changelog.rev(repo.lookup(val))
+        except KeyError:
+            raise util.Abort(_('invalid revision identifier %s'), val)
+    return num
+
+def revpair(ui, repo, revs):
+    '''return pair of nodes, given list of revisions. second item can
+    be None, meaning use working dir.'''
+    if not revs:
+        return repo.dirstate.parents()[0], None
+    end = None
+    if len(revs) == 1:
+        start = revs[0]
+        if revrangesep in start:
+            start, end = start.split(revrangesep, 1)
+            start = revfix(repo, start, 0)
+            end = revfix(repo, end, repo.changelog.count() - 1)
+        else:
+            start = revfix(repo, start, None)
+    elif len(revs) == 2:
+        if revrangesep in revs[0] or revrangesep in revs[1]:
+            raise util.Abort(_('too many revisions specified'))
+        start = revfix(repo, revs[0], None)
+        end = revfix(repo, revs[1], None)
+    else:
+        raise util.Abort(_('too many revisions specified'))
+    if end is not None: end = repo.lookup(str(end))
+    return repo.lookup(str(start)), end
+
+def revrange(ui, repo, revs):
+    """Yield revision as strings from a list of revision specifications."""
     seen = {}
     for spec in revs:
         if spec.find(revrangesep) >= 0:
             start, end = spec.split(revrangesep, 1)
-            start = fix(start, 0)
-            end = fix(end, revcount - 1)
+            start = revfix(repo, start, 0)
+            end = revfix(repo, end, repo.changelog.count() - 1)
             step = start > end and -1 or 1
             for rev in xrange(start, end+step, step):
                 if rev in seen:
@@ -219,7 +243,7 @@
                 seen[rev] = 1
                 yield str(rev)
         else:
-            rev = fix(spec, None)
+            rev = revfix(repo, spec, None)
             if rev in seen:
                 continue
             seen[rev] = 1
@@ -1361,15 +1385,7 @@
     it detects as binary. With -a, diff will generate a diff anyway,
     probably with undesirable results.
     """
-    node1, node2 = None, None
-    revs = [repo.lookup(x) for x in opts['rev']]
-
-    if len(revs) > 0:
-        node1 = revs[0]
-    if len(revs) > 1:
-        node2 = revs[1]
-    if len(revs) > 2:
-        raise util.Abort(_("too many revisions to diff"))
+    node1, node2 = revpair(ui, repo, opts['rev'])
 
     fns, matchfn, anypats = matchpats(repo, pats, opts)
 
@@ -1392,6 +1408,7 @@
 
     fp.write("# HG changeset patch\n")
     fp.write("# User %s\n" % change[1])
+    fp.write("# Date %d %d\n" % change[2])
     fp.write("# Node ID %s\n" % hex(node))
     fp.write("# Parent  %s\n" % hex(prev))
     if len(parents) > 1:
@@ -1687,6 +1704,7 @@
 
         message = []
         user = None
+        date = None
         hgpatch = False
         for line in file(pf):
             line = line.rstrip()
@@ -1703,27 +1721,29 @@
                 if line.startswith("# User "):
                     user = line[7:]
                     ui.debug(_('User: %s\n') % user)
+                elif line.startswith("# Date "):
+                    date = line[7:]
                 elif not line.startswith("# ") and line:
                     message.append(line)
                     hgpatch = False
             elif line == '# HG changeset patch':
                 hgpatch = True
                 message = []       # We may have collected garbage
-            else:
+            elif message or line:
                 message.append(line)
 
         # make sure message isn't empty
         if not message:
             message = _("imported patch %s\n") % patch
         else:
-            message = "%s\n" % '\n'.join(message)
+            message = '\n'.join(message).rstrip()
         ui.debug(_('message:\n%s\n') % message)
 
         files = util.patch(strip, pf, ui)
 
         if len(files) > 0:
             addremove_lock(ui, repo, files, {})
-        repo.commit(files, message, user)
+        repo.commit(files, message, user, date)
 
 def incoming(ui, repo, source="default", **opts):
     """show new changesets found in source
@@ -2185,34 +2205,42 @@
     entire project history.  If the files still exist in the working
     directory, they will be deleted from it.  If invoked with --after,
     files that have been manually deleted are marked as removed.
+
+    Modified files and added files are not removed by default.  To
+    remove them, use the -f/--force option.
     """
     names = []
     if not opts['after'] and not pats:
         raise util.Abort(_('no files specified'))
-    def okaytoremove(abs, rel, exact):
-        modified, added, removed, deleted, unknown = repo.changes(files=[abs])
+    files, matchfn, anypats = matchpats(repo, pats, opts)
+    exact = dict.fromkeys(files)
+    mardu = map(dict.fromkeys, repo.changes(files=files, match=matchfn))
+    modified, added, removed, deleted, unknown = mardu
+    remove, forget = [], []
+    for src, abs, rel, exact in walk(repo, pats, opts):
         reason = None
-        if not deleted and opts['after']:
+        if abs not in deleted and opts['after']:
             reason = _('is still present')
-        elif modified and not opts['force']:
-            reason = _('is modified')
-        elif added:
-            reason = _('has been marked for add')
-        elif unknown:
+        elif abs in modified and not opts['force']:
+            reason = _('is modified (use -f to force removal)')
+        elif abs in added:
+            if opts['force']:
+                forget.append(abs)
+                continue
+            reason = _('has been marked for add (use -f to force removal)')
+        elif abs in unknown:
             reason = _('is not managed')
-        elif removed:
-            return False
+        elif abs in removed:
+            continue
         if reason:
             if exact:
                 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
         else:
-            return True
-    for src, abs, rel, exact in walk(repo, pats, opts):
-        if okaytoremove(abs, rel, exact):
             if ui.verbose or not exact:
                 ui.status(_('removing %s\n') % rel)
-            names.append(abs)
-    repo.remove(names, unlink=not opts['after'])
+            remove.append(abs)
+    repo.forget(forget)
+    repo.remove(remove, unlink=not opts['after'])
 
 def rename(ui, repo, *pats, **opts):
     """rename files; equivalent of copy + remove
@@ -2404,7 +2432,7 @@
     repository; for example an in-progress pull from the repository
     may fail if a rollback is performed.
     """
-    repo.undo()
+    repo.rollback()
 
 def root(ui, repo):
     """print the root (top) of the current working dir
@@ -2516,7 +2544,7 @@
         os._exit(0)
 
     try:
-        httpd = hgweb.create_server(ui, repo)
+        httpd = create_server(ui, repo, hgwebdir, hgweb)
     except socket.error, inst:
         raise util.Abort(_('cannot start server: ') + inst.args[1])
 
@@ -2729,7 +2757,7 @@
     instructions, see the rollback command.
     """
     ui.warn(_('(the undo command is deprecated; use rollback instead)\n'))
-    repo.undo()
+    repo.rollback()
 
 def update(ui, repo, node=None, merge=False, clean=False, force=None,
            branch=None, **opts):
@@ -3273,12 +3301,13 @@
             external.append(mod)
         except Exception, inst:
             u.warn(_("*** failed to import extension %s: %s\n") % (x[0], inst))
-            if u.traceback:
-                traceback.print_exc()
+            if u.print_exc():
                 return 1
-            continue
 
     for x in external:
+        uisetup = getattr(x, 'uisetup', None)
+        if uisetup:
+            uisetup(u)
         cmdtable = getattr(x, 'cmdtable', {})
         for t in cmdtable:
             if t in table:
@@ -3369,8 +3398,7 @@
             # enter the debugger when we hit an exception
             if options['debugger']:
                 pdb.post_mortem(sys.exc_info()[2])
-            if u.traceback:
-                traceback.print_exc()
+            u.print_exc()
             raise
     except ParseError, inst:
         if inst.args[0]:
--- a/mercurial/demandload.py	Mon May 22 16:47:40 2006 +0200
+++ b/mercurial/demandload.py	Thu Jun 01 13:47:34 2006 -0700
@@ -81,6 +81,10 @@
 
         return getattr(importer.module(), target)
 
+    def __call__(self, *args, **kwargs):
+        target = object.__getattribute__(self, 'module')()
+        return target(*args, **kwargs)
+
 def demandload(scope, modules):
     '''import modules into scope when each is first used.
 
--- a/mercurial/hgweb.py	Mon May 22 16:47:40 2006 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1142 +0,0 @@
-# hgweb.py - web interface to a mercurial repository
-#
-# Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
-# Copyright 2005 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.
-
-import os, cgi, sys
-import mimetypes
-from demandload import demandload
-demandload(globals(), "mdiff time re socket zlib errno ui hg ConfigParser")
-demandload(globals(), "tempfile StringIO BaseHTTPServer util SocketServer")
-demandload(globals(), "archival mimetypes templater urllib")
-from node import *
-from i18n import gettext as _
-
-def splitURI(uri):
-    """ Return path and query splited from uri
-
-    Just like CGI environment, the path is unquoted, the query is
-    not.
-    """
-    if '?' in uri:
-        path, query = uri.split('?', 1)
-    else:
-        path, query = uri, ''
-    return urllib.unquote(path), query
-
-def up(p):
-    if p[0] != "/":
-        p = "/" + p
-    if p[-1] == "/":
-        p = p[:-1]
-    up = os.path.dirname(p)
-    if up == "/":
-        return "/"
-    return up + "/"
-
-def get_mtime(repo_path):
-    hg_path = os.path.join(repo_path, ".hg")
-    cl_path = os.path.join(hg_path, "00changelog.i")
-    if os.path.exists(os.path.join(cl_path)):
-        return os.stat(cl_path).st_mtime
-    else:
-        return os.stat(hg_path).st_mtime
-
-def staticfile(directory, fname):
-    """return a file inside directory with guessed content-type header
-
-    fname always uses '/' as directory separator and isn't allowed to
-    contain unusual path components.
-    Content-type is guessed using the mimetypes module.
-    Return an empty string if fname is illegal or file not found.
-
-    """
-    parts = fname.split('/')
-    path = directory
-    for part in parts:
-        if (part in ('', os.curdir, os.pardir) or
-            os.sep in part or os.altsep is not None and os.altsep in part):
-            return ""
-        path = os.path.join(path, part)
-    try:
-        os.stat(path)
-        ct = mimetypes.guess_type(path)[0] or "text/plain"
-        return "Content-type: %s\n\n%s" % (ct, file(path).read())
-    except (TypeError, OSError):
-        # illegal fname or unreadable file
-        return ""
-
-class hgrequest(object):
-    def __init__(self, inp=None, out=None, env=None):
-        self.inp = inp or sys.stdin
-        self.out = out or sys.stdout
-        self.env = env or os.environ
-        self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
-
-    def write(self, *things):
-        for thing in things:
-            if hasattr(thing, "__iter__"):
-                for part in thing:
-                    self.write(part)
-            else:
-                try:
-                    self.out.write(str(thing))
-                except socket.error, inst:
-                    if inst[0] != errno.ECONNRESET:
-                        raise
-
-    def header(self, headers=[('Content-type','text/html')]):
-        for header in headers:
-            self.out.write("%s: %s\r\n" % header)
-        self.out.write("\r\n")
-
-    def httphdr(self, type, file="", size=0):
-
-        headers = [('Content-type', type)]
-        if file:
-            headers.append(('Content-disposition', 'attachment; filename=%s' % file))
-        if size > 0:
-            headers.append(('Content-length', str(size)))
-        self.header(headers)
-
-class hgweb(object):
-    def __init__(self, repo, name=None):
-        if type(repo) == type(""):
-            self.repo = hg.repository(ui.ui(), repo)
-        else:
-            self.repo = repo
-
-        self.mtime = -1
-        self.reponame = name
-        self.archives = 'zip', 'gz', 'bz2'
-
-    def refresh(self):
-        mtime = get_mtime(self.repo.root)
-        if mtime != self.mtime:
-            self.mtime = mtime
-            self.repo = hg.repository(self.repo.ui, self.repo.root)
-            self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
-            self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
-            self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
-
-    def archivelist(self, nodeid):
-        for i in self.archives:
-            if self.repo.ui.configbool("web", "allow" + i, False):
-                yield {"type" : i, "node" : nodeid, "url": ""}
-
-    def listfiles(self, files, mf):
-        for f in files[:self.maxfiles]:
-            yield self.t("filenodelink", node=hex(mf[f]), file=f)
-        if len(files) > self.maxfiles:
-            yield self.t("fileellipses")
-
-    def listfilediffs(self, files, changeset):
-        for f in files[:self.maxfiles]:
-            yield self.t("filedifflink", node=hex(changeset), file=f)
-        if len(files) > self.maxfiles:
-            yield self.t("fileellipses")
-
-    def siblings(self, siblings=[], rev=None, hiderev=None, **args):
-        if not rev:
-            rev = lambda x: ""
-        siblings = [s for s in siblings if s != nullid]
-        if len(siblings) == 1 and rev(siblings[0]) == hiderev:
-            return
-        for s in siblings:
-            yield dict(node=hex(s), rev=rev(s), **args)
-
-    def renamelink(self, fl, node):
-        r = fl.renamed(node)
-        if r:
-            return [dict(file=r[0], node=hex(r[1]))]
-        return []
-
-    def showtag(self, t1, node=nullid, **args):
-        for t in self.repo.nodetags(node):
-             yield self.t(t1, tag=t, **args)
-
-    def diff(self, node1, node2, files):
-        def filterfiles(filters, files):
-            l = [x for x in files if x in filters]
-
-            for t in filters:
-                if t and t[-1] != os.sep:
-                    t += os.sep
-                l += [x for x in files if x.startswith(t)]
-            return l
-
-        parity = [0]
-        def diffblock(diff, f, fn):
-            yield self.t("diffblock",
-                         lines=prettyprintlines(diff),
-                         parity=parity[0],
-                         file=f,
-                         filenode=hex(fn or nullid))
-            parity[0] = 1 - parity[0]
-
-        def prettyprintlines(diff):
-            for l in diff.splitlines(1):
-                if l.startswith('+'):
-                    yield self.t("difflineplus", line=l)
-                elif l.startswith('-'):
-                    yield self.t("difflineminus", line=l)
-                elif l.startswith('@'):
-                    yield self.t("difflineat", line=l)
-                else:
-                    yield self.t("diffline", line=l)
-
-        r = self.repo
-        cl = r.changelog
-        mf = r.manifest
-        change1 = cl.read(node1)
-        change2 = cl.read(node2)
-        mmap1 = mf.read(change1[0])
-        mmap2 = mf.read(change2[0])
-        date1 = util.datestr(change1[2])
-        date2 = util.datestr(change2[2])
-
-        modified, added, removed, deleted, unknown = r.changes(node1, node2)
-        if files:
-            modified, added, removed = map(lambda x: filterfiles(files, x),
-                                           (modified, added, removed))
-
-        diffopts = self.repo.ui.diffopts()
-        showfunc = diffopts['showfunc']
-        ignorews = diffopts['ignorews']
-        for f in modified:
-            to = r.file(f).read(mmap1[f])
-            tn = r.file(f).read(mmap2[f])
-            yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
-                            showfunc=showfunc, ignorews=ignorews), f, tn)
-        for f in added:
-            to = None
-            tn = r.file(f).read(mmap2[f])
-            yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
-                            showfunc=showfunc, ignorews=ignorews), f, tn)
-        for f in removed:
-            to = r.file(f).read(mmap1[f])
-            tn = None
-            yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
-                            showfunc=showfunc, ignorews=ignorews), f, tn)
-
-    def changelog(self, pos):
-        def changenav(**map):
-            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 = []
-            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))
-                if pos - f >= 0:
-                    l.insert(0, ("-" + r, pos - f))
-
-            yield {"rev": 0, "label": "(0)"}
-
-            for label, rev in l:
-                yield {"label": label, "rev": rev}
-
-            yield {"label": "tip", "rev": "tip"}
-
-        def changelist(**map):
-            parity = (start - end) & 1
-            cl = self.repo.changelog
-            l = [] # build a list in forward order for efficiency
-            for i in range(start, end):
-                n = cl.node(i)
-                changes = cl.read(n)
-                hn = hex(n)
-
-                l.insert(0, {"parity": parity,
-                             "author": changes[1],
-                             "parent": self.siblings(cl.parents(n), cl.rev,
-                                                     cl.rev(n) - 1),
-                             "child": self.siblings(cl.children(n), cl.rev,
-                                                    cl.rev(n) + 1),
-                             "changelogtag": self.showtag("changelogtag",n),
-                             "manifest": hex(changes[0]),
-                             "desc": changes[4],
-                             "date": changes[2],
-                             "files": self.listfilediffs(changes[3], n),
-                             "rev": i,
-                             "node": hn})
-                parity = 1 - parity
-
-            for e in l:
-                yield e
-
-        cl = self.repo.changelog
-        mf = cl.read(cl.tip())[0]
-        count = cl.count()
-        start = max(0, pos - self.maxchanges + 1)
-        end = min(count, start + self.maxchanges)
-        pos = end - 1
-
-        yield self.t('changelog',
-                     changenav=changenav,
-                     manifest=hex(mf),
-                     rev=pos, changesets=count, entries=changelist,
-                     archives=self.archivelist("tip"))
-
-    def search(self, query):
-
-        def changelist(**map):
-            cl = self.repo.changelog
-            count = 0
-            qw = query.lower().split()
-
-            def revgen():
-                for i in range(cl.count() - 1, 0, -100):
-                    l = []
-                    for j in range(max(0, i - 100), i):
-                        n = cl.node(j)
-                        changes = cl.read(n)
-                        l.append((n, j, changes))
-                    l.reverse()
-                    for e in l:
-                        yield e
-
-            for n, i, changes in revgen():
-                miss = 0
-                for q in qw:
-                    if not (q in changes[1].lower() or
-                            q in changes[4].lower() or
-                            q in " ".join(changes[3][:20]).lower()):
-                        miss = 1
-                        break
-                if miss:
-                    continue
-
-                count += 1
-                hn = hex(n)
-
-                yield self.t('searchentry',
-                             parity=count & 1,
-                             author=changes[1],
-                             parent=self.siblings(cl.parents(n), cl.rev),
-                             child=self.siblings(cl.children(n), cl.rev),
-                             changelogtag=self.showtag("changelogtag",n),
-                             manifest=hex(changes[0]),
-                             desc=changes[4],
-                             date=changes[2],
-                             files=self.listfilediffs(changes[3], n),
-                             rev=i,
-                             node=hn)
-
-                if count >= self.maxchanges:
-                    break
-
-        cl = self.repo.changelog
-        mf = cl.read(cl.tip())[0]
-
-        yield self.t('search',
-                     query=query,
-                     manifest=hex(mf),
-                     entries=changelist)
-
-    def changeset(self, nodeid):
-        cl = self.repo.changelog
-        n = self.repo.lookup(nodeid)
-        nodeid = hex(n)
-        changes = cl.read(n)
-        p1 = cl.parents(n)[0]
-
-        files = []
-        mf = self.repo.manifest.read(changes[0])
-        for f in changes[3]:
-            files.append(self.t("filenodelink",
-                                filenode=hex(mf.get(f, nullid)), file=f))
-
-        def diff(**map):
-            yield self.diff(p1, n, None)
-
-        yield self.t('changeset',
-                     diff=diff,
-                     rev=cl.rev(n),
-                     node=nodeid,
-                     parent=self.siblings(cl.parents(n), cl.rev),
-                     child=self.siblings(cl.children(n), cl.rev),
-                     changesettag=self.showtag("changesettag",n),
-                     manifest=hex(changes[0]),
-                     author=changes[1],
-                     desc=changes[4],
-                     date=changes[2],
-                     files=files,
-                     archives=self.archivelist(nodeid))
-
-    def filelog(self, f, filenode):
-        cl = self.repo.changelog
-        fl = self.repo.file(f)
-        filenode = hex(fl.lookup(filenode))
-        count = fl.count()
-
-        def entries(**map):
-            l = []
-            parity = (count - 1) & 1
-
-            for i in range(count):
-                n = fl.node(i)
-                lr = fl.linkrev(n)
-                cn = cl.node(lr)
-                cs = cl.read(cl.node(lr))
-
-                l.insert(0, {"parity": parity,
-                             "filenode": hex(n),
-                             "filerev": i,
-                             "file": f,
-                             "node": hex(cn),
-                             "author": cs[1],
-                             "date": cs[2],
-                             "rename": self.renamelink(fl, n),
-                             "parent": self.siblings(fl.parents(n),
-                                                     fl.rev, file=f),
-                             "child": self.siblings(fl.children(n),
-                                                    fl.rev, file=f),
-                             "desc": cs[4]})
-                parity = 1 - parity
-
-            for e in l:
-                yield e
-
-        yield self.t("filelog", file=f, filenode=filenode, entries=entries)
-
-    def filerevision(self, f, node):
-        fl = self.repo.file(f)
-        n = fl.lookup(node)
-        node = hex(n)
-        text = fl.read(n)
-        changerev = fl.linkrev(n)
-        cl = self.repo.changelog
-        cn = cl.node(changerev)
-        cs = cl.read(cn)
-        mfn = cs[0]
-
-        mt = mimetypes.guess_type(f)[0]
-        rawtext = text
-        if util.binary(text):
-            mt = mt or 'application/octet-stream'
-            text = "(binary:%s)" % mt
-        mt = mt or 'text/plain'
-
-        def lines():
-            for l, t in enumerate(text.splitlines(1)):
-                yield {"line": t,
-                       "linenumber": "% 6d" % (l + 1),
-                       "parity": l & 1}
-
-        yield self.t("filerevision",
-                     file=f,
-                     filenode=node,
-                     path=up(f),
-                     text=lines(),
-                     raw=rawtext,
-                     mimetype=mt,
-                     rev=changerev,
-                     node=hex(cn),
-                     manifest=hex(mfn),
-                     author=cs[1],
-                     date=cs[2],
-                     parent=self.siblings(fl.parents(n), fl.rev, file=f),
-                     child=self.siblings(fl.children(n), fl.rev, file=f),
-                     rename=self.renamelink(fl, n),
-                     permissions=self.repo.manifest.readflags(mfn)[f])
-
-    def fileannotate(self, f, node):
-        bcache = {}
-        ncache = {}
-        fl = self.repo.file(f)
-        n = fl.lookup(node)
-        node = hex(n)
-        changerev = fl.linkrev(n)
-
-        cl = self.repo.changelog
-        cn = cl.node(changerev)
-        cs = cl.read(cn)
-        mfn = cs[0]
-
-        def annotate(**map):
-            parity = 1
-            last = None
-            for r, l in fl.annotate(n):
-                try:
-                    cnode = ncache[r]
-                except KeyError:
-                    cnode = ncache[r] = self.repo.changelog.node(r)
-
-                try:
-                    name = bcache[r]
-                except KeyError:
-                    cl = self.repo.changelog.read(cnode)
-                    bcache[r] = name = self.repo.ui.shortuser(cl[1])
-
-                if last != cnode:
-                    parity = 1 - parity
-                    last = cnode
-
-                yield {"parity": parity,
-                       "node": hex(cnode),
-                       "rev": r,
-                       "author": name,
-                       "file": f,
-                       "line": l}
-
-        yield self.t("fileannotate",
-                     file=f,
-                     filenode=node,
-                     annotate=annotate,
-                     path=up(f),
-                     rev=changerev,
-                     node=hex(cn),
-                     manifest=hex(mfn),
-                     author=cs[1],
-                     date=cs[2],
-                     rename=self.renamelink(fl, n),
-                     parent=self.siblings(fl.parents(n), fl.rev, file=f),
-                     child=self.siblings(fl.children(n), fl.rev, file=f),
-                     permissions=self.repo.manifest.readflags(mfn)[f])
-
-    def manifest(self, mnode, path):
-        man = self.repo.manifest
-        mn = man.lookup(mnode)
-        mnode = hex(mn)
-        mf = man.read(mn)
-        rev = man.rev(mn)
-        node = self.repo.changelog.node(rev)
-        mff = man.readflags(mn)
-
-        files = {}
-
-        p = path[1:]
-        if p and p[-1] != "/":
-            p += "/"
-        l = len(p)
-
-        for f,n in mf.items():
-            if f[:l] != p:
-                continue
-            remain = f[l:]
-            if "/" in remain:
-                short = remain[:remain.find("/") + 1] # bleah
-                files[short] = (f, None)
-            else:
-                short = os.path.basename(remain)
-                files[short] = (f, n)
-
-        def filelist(**map):
-            parity = 0
-            fl = files.keys()
-            fl.sort()
-            for f in fl:
-                full, fnode = files[f]
-                if not fnode:
-                    continue
-
-                yield {"file": full,
-                       "manifest": mnode,
-                       "filenode": hex(fnode),
-                       "parity": parity,
-                       "basename": f,
-                       "permissions": mff[full]}
-                parity = 1 - parity
-
-        def dirlist(**map):
-            parity = 0
-            fl = files.keys()
-            fl.sort()
-            for f in fl:
-                full, fnode = files[f]
-                if fnode:
-                    continue
-
-                yield {"parity": parity,
-                       "path": os.path.join(path, f),
-                       "manifest": mnode,
-                       "basename": f[:-1]}
-                parity = 1 - parity
-
-        yield self.t("manifest",
-                     manifest=mnode,
-                     rev=rev,
-                     node=hex(node),
-                     path=path,
-                     up=up(path),
-                     fentries=filelist,
-                     dentries=dirlist,
-                     archives=self.archivelist(hex(node)))
-
-    def tags(self):
-        cl = self.repo.changelog
-        mf = cl.read(cl.tip())[0]
-
-        i = self.repo.tagslist()
-        i.reverse()
-
-        def entries(notip=False, **map):
-            parity = 0
-            for k,n in i:
-                if notip and k == "tip": continue
-                yield {"parity": parity,
-                       "tag": k,
-                       "tagmanifest": hex(cl.read(n)[0]),
-                       "date": cl.read(n)[2],
-                       "node": hex(n)}
-                parity = 1 - parity
-
-        yield self.t("tags",
-                     manifest=hex(mf),
-                     entries=lambda **x: entries(False, **x),
-                     entriesnotip=lambda **x: entries(True, **x))
-
-    def summary(self):
-        cl = self.repo.changelog
-        mf = cl.read(cl.tip())[0]
-
-        i = self.repo.tagslist()
-        i.reverse()
-
-        def tagentries(**map):
-            parity = 0
-            count = 0
-            for k,n in i:
-                if k == "tip": # skip tip
-                    continue;
-
-                count += 1
-                if count > 10: # limit to 10 tags
-                    break;
-
-                c = cl.read(n)
-                m = c[0]
-                t = c[2]
-
-                yield self.t("tagentry",
-                             parity = parity,
-                             tag = k,
-                             node = hex(n),
-                             date = t,
-                             tagmanifest = hex(m))
-                parity = 1 - parity
-
-        def changelist(**map):
-            parity = 0
-            cl = self.repo.changelog
-            l = [] # build a list in forward order for efficiency
-            for i in range(start, end):
-                n = cl.node(i)
-                changes = cl.read(n)
-                hn = hex(n)
-                t = changes[2]
-
-                l.insert(0, self.t(
-                    'shortlogentry',
-                    parity = parity,
-                    author = changes[1],
-                    manifest = hex(changes[0]),
-                    desc = changes[4],
-                    date = t,
-                    rev = i,
-                    node = hn))
-                parity = 1 - parity
-
-            yield l
-
-        cl = self.repo.changelog
-        mf = cl.read(cl.tip())[0]
-        count = cl.count()
-        start = max(0, count - self.maxchanges)
-        end = min(count, start + self.maxchanges)
-        pos = end - 1
-
-        yield self.t("summary",
-                 desc = self.repo.ui.config("web", "description", "unknown"),
-                 owner = (self.repo.ui.config("ui", "username") or # preferred
-                          self.repo.ui.config("web", "contact") or # deprecated
-                          self.repo.ui.config("web", "author", "unknown")), # also
-                 lastchange = (0, 0), # FIXME
-                 manifest = hex(mf),
-                 tags = tagentries,
-                 shortlog = changelist)
-
-    def filediff(self, file, changeset):
-        cl = self.repo.changelog
-        n = self.repo.lookup(changeset)
-        changeset = hex(n)
-        p1 = cl.parents(n)[0]
-        cs = cl.read(n)
-        mf = self.repo.manifest.read(cs[0])
-
-        def diff(**map):
-            yield self.diff(p1, n, [file])
-
-        yield self.t("filediff",
-                     file=file,
-                     filenode=hex(mf.get(file, nullid)),
-                     node=changeset,
-                     rev=self.repo.changelog.rev(n),
-                     parent=self.siblings(cl.parents(n), cl.rev),
-                     child=self.siblings(cl.children(n), cl.rev),
-                     diff=diff)
-
-    archive_specs = {
-        'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', 'x-bzip2'),
-        'gz': ('application/x-tar', 'tgz', '.tar.gz', 'x-gzip'),
-        'zip': ('application/zip', 'zip', '.zip', None),
-        }
-
-    def archive(self, req, cnode, type):
-        reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
-        name = "%s-%s" % (reponame, short(cnode))
-        mimetype, artype, extension, encoding = self.archive_specs[type]
-        headers = [('Content-type', mimetype),
-                   ('Content-disposition', 'attachment; filename=%s%s' %
-                    (name, extension))]
-        if encoding:
-            headers.append(('Content-encoding', encoding))
-        req.header(headers)
-        archival.archive(self.repo, req.out, cnode, artype, prefix=name)
-
-    # add tags to things
-    # tags -> list of changesets corresponding to tags
-    # find tag, changeset, file
-
-    def run(self, req=hgrequest()):
-        def clean(path):
-            p = util.normpath(path)
-            if p[:2] == "..":
-                raise "suspicious path"
-            return p
-
-        def header(**map):
-            yield self.t("header", **map)
-
-        def footer(**map):
-            yield self.t("footer",
-                         motd=self.repo.ui.config("web", "motd", ""),
-                         **map)
-
-        def expand_form(form):
-            shortcuts = {
-                'cl': [('cmd', ['changelog']), ('rev', None)],
-                'cs': [('cmd', ['changeset']), ('node', None)],
-                'f': [('cmd', ['file']), ('filenode', None)],
-                'fl': [('cmd', ['filelog']), ('filenode', None)],
-                'fd': [('cmd', ['filediff']), ('node', None)],
-                'fa': [('cmd', ['annotate']), ('filenode', None)],
-                'mf': [('cmd', ['manifest']), ('manifest', None)],
-                'ca': [('cmd', ['archive']), ('node', None)],
-                'tags': [('cmd', ['tags'])],
-                'tip': [('cmd', ['changeset']), ('node', ['tip'])],
-                'static': [('cmd', ['static']), ('file', None)]
-            }
-
-            for k in shortcuts.iterkeys():
-                if form.has_key(k):
-                    for name, value in shortcuts[k]:
-                        if value is None:
-                            value = form[k]
-                        form[name] = value
-                    del form[k]
-
-        self.refresh()
-
-        expand_form(req.form)
-
-        t = self.repo.ui.config("web", "templates", templater.templatepath())
-        static = self.repo.ui.config("web", "static", os.path.join(t,"static"))
-        m = os.path.join(t, "map")
-        style = self.repo.ui.config("web", "style", "")
-        if req.form.has_key('style'):
-            style = req.form['style'][0]
-        if style:
-            b = os.path.basename("map-" + style)
-            p = os.path.join(t, b)
-            if os.path.isfile(p):
-                m = p
-
-        port = req.env["SERVER_PORT"]
-        port = port != "80" and (":" + port) or ""
-        uri = req.env["REQUEST_URI"]
-        if "?" in uri:
-            uri = uri.split("?")[0]
-        url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
-        if not self.reponame:
-            self.reponame = (self.repo.ui.config("web", "name")
-                             or uri.strip('/') or self.repo.root)
-
-        self.t = templater.templater(m, templater.common_filters,
-                                     defaults={"url": url,
-                                               "repo": self.reponame,
-                                               "header": header,
-                                               "footer": footer,
-                                               })
-
-        if not req.form.has_key('cmd'):
-            req.form['cmd'] = [self.t.cache['default'],]
-
-        cmd = req.form['cmd'][0]
-        if cmd == 'changelog':
-            hi = self.repo.changelog.count() - 1
-            if req.form.has_key('rev'):
-                hi = req.form['rev'][0]
-                try:
-                    hi = self.repo.changelog.rev(self.repo.lookup(hi))
-                except hg.RepoError:
-                    req.write(self.search(hi)) # XXX redirect to 404 page?
-                    return
-
-            req.write(self.changelog(hi))
-
-        elif cmd == 'changeset':
-            req.write(self.changeset(req.form['node'][0]))
-
-        elif cmd == 'manifest':
-            req.write(self.manifest(req.form['manifest'][0],
-                                    clean(req.form['path'][0])))
-
-        elif cmd == 'tags':
-            req.write(self.tags())
-
-        elif cmd == 'summary':
-            req.write(self.summary())
-
-        elif cmd == 'filediff':
-            req.write(self.filediff(clean(req.form['file'][0]),
-                                    req.form['node'][0]))
-
-        elif cmd == 'file':
-            req.write(self.filerevision(clean(req.form['file'][0]),
-                                        req.form['filenode'][0]))
-
-        elif cmd == 'annotate':
-            req.write(self.fileannotate(clean(req.form['file'][0]),
-                                        req.form['filenode'][0]))
-
-        elif cmd == 'filelog':
-            req.write(self.filelog(clean(req.form['file'][0]),
-                                   req.form['filenode'][0]))
-
-        elif cmd == 'heads':
-            req.httphdr("application/mercurial-0.1")
-            h = self.repo.heads()
-            req.write(" ".join(map(hex, h)) + "\n")
-
-        elif cmd == 'branches':
-            req.httphdr("application/mercurial-0.1")
-            nodes = []
-            if req.form.has_key('nodes'):
-                nodes = map(bin, req.form['nodes'][0].split(" "))
-            for b in self.repo.branches(nodes):
-                req.write(" ".join(map(hex, b)) + "\n")
-
-        elif cmd == 'between':
-            req.httphdr("application/mercurial-0.1")
-            nodes = []
-            if req.form.has_key('pairs'):
-                pairs = [map(bin, p.split("-"))
-                         for p in req.form['pairs'][0].split(" ")]
-            for b in self.repo.between(pairs):
-                req.write(" ".join(map(hex, b)) + "\n")
-
-        elif cmd == 'changegroup':
-            req.httphdr("application/mercurial-0.1")
-            nodes = []
-            if not self.allowpull:
-                return
-
-            if req.form.has_key('roots'):
-                nodes = map(bin, req.form['roots'][0].split(" "))
-
-            z = zlib.compressobj()
-            f = self.repo.changegroup(nodes, 'serve')
-            while 1:
-                chunk = f.read(4096)
-                if not chunk:
-                    break
-                req.write(z.compress(chunk))
-
-            req.write(z.flush())
-
-        elif cmd == 'archive':
-            changeset = self.repo.lookup(req.form['node'][0])
-            type = req.form['type'][0]
-            if (type in self.archives and
-                self.repo.ui.configbool("web", "allow" + type, False)):
-                self.archive(req, changeset, type)
-                return
-
-            req.write(self.t("error"))
-
-        elif cmd == 'static':
-            fname = req.form['file'][0]
-            req.write(staticfile(static, fname)
-                      or self.t("error", error="%r not found" % fname))
-
-        else:
-            req.write(self.t("error"))
-
-def create_server(ui, repo):
-    use_threads = True
-
-    def openlog(opt, default):
-        if opt and opt != '-':
-            return open(opt, 'w')
-        return default
-
-    address = ui.config("web", "address", "")
-    port = int(ui.config("web", "port", 8000))
-    use_ipv6 = ui.configbool("web", "ipv6")
-    webdir_conf = ui.config("web", "webdir_conf")
-    accesslog = openlog(ui.config("web", "accesslog", "-"), sys.stdout)
-    errorlog = openlog(ui.config("web", "errorlog", "-"), sys.stderr)
-
-    if use_threads:
-        try:
-            from threading import activeCount
-        except ImportError:
-            use_threads = False
-
-    if use_threads:
-        _mixin = SocketServer.ThreadingMixIn
-    else:
-        if hasattr(os, "fork"):
-            _mixin = SocketServer.ForkingMixIn
-        else:
-            class _mixin: pass
-
-    class MercurialHTTPServer(_mixin, BaseHTTPServer.HTTPServer):
-        pass
-
-    class IPv6HTTPServer(MercurialHTTPServer):
-        address_family = getattr(socket, 'AF_INET6', None)
-
-        def __init__(self, *args, **kwargs):
-            if self.address_family is None:
-                raise hg.RepoError(_('IPv6 not available on this system'))
-            BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
-
-    class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
-
-        def log_error(self, format, *args):
-            errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
-                                                 self.log_date_time_string(),
-                                                 format % args))
-
-        def log_message(self, format, *args):
-            accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
-                                                  self.log_date_time_string(),
-                                                  format % args))
-
-        def do_POST(self):
-            try:
-                self.do_hgweb()
-            except socket.error, inst:
-                if inst[0] != errno.EPIPE:
-                    raise
-
-        def do_GET(self):
-            self.do_POST()
-
-        def do_hgweb(self):
-            path_info, query = splitURI(self.path)
-
-            env = {}
-            env['GATEWAY_INTERFACE'] = 'CGI/1.1'
-            env['REQUEST_METHOD'] = self.command
-            env['SERVER_NAME'] = self.server.server_name
-            env['SERVER_PORT'] = str(self.server.server_port)
-            env['REQUEST_URI'] = "/"
-            env['PATH_INFO'] = path_info
-            if query:
-                env['QUERY_STRING'] = query
-            host = self.address_string()
-            if host != self.client_address[0]:
-                env['REMOTE_HOST'] = host
-                env['REMOTE_ADDR'] = self.client_address[0]
-
-            if self.headers.typeheader is None:
-                env['CONTENT_TYPE'] = self.headers.type
-            else:
-                env['CONTENT_TYPE'] = self.headers.typeheader
-            length = self.headers.getheader('content-length')
-            if length:
-                env['CONTENT_LENGTH'] = length
-            accept = []
-            for line in self.headers.getallmatchingheaders('accept'):
-                if line[:1] in "\t\n\r ":
-                    accept.append(line.strip())
-                else:
-                    accept = accept + line[7:].split(',')
-            env['HTTP_ACCEPT'] = ','.join(accept)
-
-            req = hgrequest(self.rfile, self.wfile, env)
-            self.send_response(200, "Script output follows")
-
-            if webdir_conf:
-                hgwebobj = hgwebdir(webdir_conf)
-            elif repo is not None:
-                hgwebobj = hgweb(repo.__class__(repo.ui, repo.origroot))
-            else:
-                raise hg.RepoError(_('no repo found'))
-            hgwebobj.run(req)
-
-
-    if use_ipv6:
-        return IPv6HTTPServer((address, port), hgwebhandler)
-    else:
-        return MercurialHTTPServer((address, port), hgwebhandler)
-
-# This is a stopgap
-class hgwebdir(object):
-    def __init__(self, config):
-        def cleannames(items):
-            return [(name.strip(os.sep), path) for name, path in items]
-
-        self.motd = ""
-        self.repos_sorted = ('name', False)
-        if isinstance(config, (list, tuple)):
-            self.repos = cleannames(config)
-            self.repos_sorted = ('', False)
-        elif isinstance(config, dict):
-            self.repos = cleannames(config.items())
-            self.repos.sort()
-        else:
-            cp = ConfigParser.SafeConfigParser()
-            cp.read(config)
-            self.repos = []
-            if cp.has_section('web') and cp.has_option('web', 'motd'):
-                self.motd = cp.get('web', 'motd')
-            if cp.has_section('paths'):
-                self.repos.extend(cleannames(cp.items('paths')))
-            if cp.has_section('collections'):
-                for prefix, root in cp.items('collections'):
-                    for path in util.walkrepos(root):
-                        repo = os.path.normpath(path)
-                        name = repo
-                        if name.startswith(prefix):
-                            name = name[len(prefix):]
-                        self.repos.append((name.lstrip(os.sep), repo))
-            self.repos.sort()
-
-    def run(self, req=hgrequest()):
-        def header(**map):
-            yield tmpl("header", **map)
-
-        def footer(**map):
-            yield tmpl("footer", motd=self.motd, **map)
-
-        m = os.path.join(templater.templatepath(), "map")
-        tmpl = templater.templater(m, templater.common_filters,
-                                   defaults={"header": header,
-                                             "footer": footer})
-
-        def archivelist(ui, nodeid, url):
-            for i in ['zip', 'gz', 'bz2']:
-                if ui.configbool("web", "allow" + i, False):
-                    yield {"type" : i, "node": nodeid, "url": url}
-
-        def entries(sortcolumn="", descending=False, **map):
-            rows = []
-            parity = 0
-            for name, path in self.repos:
-                u = ui.ui()
-                try:
-                    u.readconfig(os.path.join(path, '.hg', 'hgrc'))
-                except IOError:
-                    pass
-                get = u.config
-
-                url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
-                       .replace("//", "/"))
-
-                # update time with local timezone
-                try:
-                    d = (get_mtime(path), util.makedate()[1])
-                except OSError:
-                    continue
-
-                contact = (get("ui", "username") or # preferred
-                           get("web", "contact") or # deprecated
-                           get("web", "author", "")) # also
-                description = get("web", "description", "")
-                name = get("web", "name", name)
-                row = dict(contact=contact or "unknown",
-                           contact_sort=contact.upper() or "unknown",
-                           name=name,
-                           name_sort=name,
-                           url=url,
-                           description=description or "unknown",
-                           description_sort=description.upper() or "unknown",
-                           lastchange=d,
-                           lastchange_sort=d[1]-d[0],
-                           archives=archivelist(u, "tip", url))
-                if (not sortcolumn
-                    or (sortcolumn, descending) == self.repos_sorted):
-                    # fast path for unsorted output
-                    row['parity'] = parity
-                    parity = 1 - parity
-                    yield row
-                else:
-                    rows.append((row["%s_sort" % sortcolumn], row))
-            if rows:
-                rows.sort()
-                if descending:
-                    rows.reverse()
-                for key, row in rows:
-                    row['parity'] = parity
-                    parity = 1 - parity
-                    yield row
-
-        virtual = req.env.get("PATH_INFO", "").strip('/')
-        if virtual:
-            real = dict(self.repos).get(virtual)
-            if real:
-                try:
-                    hgweb(real).run(req)
-                except IOError, inst:
-                    req.write(tmpl("error", error=inst.strerror))
-                except hg.RepoError, inst:
-                    req.write(tmpl("error", error=str(inst)))
-            else:
-                req.write(tmpl("notfound", repo=virtual))
-        else:
-            if req.form.has_key('static'):
-                static = os.path.join(templater.templatepath(), "static")
-                fname = req.form['static'][0]
-                req.write(staticfile(static, fname)
-                          or tmpl("error", error="%r not found" % fname))
-            else:
-                sortable = ["name", "description", "contact", "lastchange"]
-                sortcolumn, descending = self.repos_sorted
-                if req.form.has_key('sort'):
-                    sortcolumn = req.form['sort'][0]
-                    descending = sortcolumn.startswith('-')
-                    if descending:
-                        sortcolumn = sortcolumn[1:]
-                    if sortcolumn not in sortable:
-                        sortcolumn = ""
-
-                sort = [("sort_%s" % column,
-                         "%s%s" % ((not descending and column == sortcolumn)
-                                   and "-" or "", column))
-                        for column in sortable]
-                req.write(tmpl("index", entries=entries,
-                               sortcolumn=sortcolumn, descending=descending,
-                               **dict(sort)))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/hgweb/__init__.py	Thu Jun 01 13:47:34 2006 -0700
@@ -0,0 +1,11 @@
+# hgweb.py - web interface to a mercurial repository
+#
+# Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
+# Copyright 2005 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.
+
+from mercurial.demandload import demandload
+demandload(globals(), "mercurial.hgweb.hgweb_mod:hgweb")
+demandload(globals(), "mercurial.hgweb.hgwebdir_mod:hgwebdir")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/hgweb/common.py	Thu Jun 01 13:47:34 2006 -0700
@@ -0,0 +1,42 @@
+# hgweb.py - web interface to a mercurial repository
+#
+# Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
+# Copyright 2005 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.
+
+import os, mimetypes
+import os.path
+
+def get_mtime(repo_path):
+    hg_path = os.path.join(repo_path, ".hg")
+    cl_path = os.path.join(hg_path, "00changelog.i")
+    if os.path.exists(os.path.join(cl_path)):
+        return os.stat(cl_path).st_mtime
+    else:
+        return os.stat(hg_path).st_mtime
+
+def staticfile(directory, fname):
+    """return a file inside directory with guessed content-type header
+
+    fname always uses '/' as directory separator and isn't allowed to
+    contain unusual path components.
+    Content-type is guessed using the mimetypes module.
+    Return an empty string if fname is illegal or file not found.
+
+    """
+    parts = fname.split('/')
+    path = directory
+    for part in parts:
+        if (part in ('', os.curdir, os.pardir) or
+            os.sep in part or os.altsep is not None and os.altsep in part):
+            return ""
+        path = os.path.join(path, part)
+    try:
+        os.stat(path)
+        ct = mimetypes.guess_type(path)[0] or "text/plain"
+        return "Content-type: %s\n\n%s" % (ct, file(path).read())
+    except (TypeError, OSError):
+        # illegal fname or unreadable file
+        return ""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/hgweb/hgweb_mod.py	Thu Jun 01 13:47:34 2006 -0700
@@ -0,0 +1,822 @@
+# hgweb.py - web interface to a mercurial repository
+#
+# Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
+# Copyright 2005 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.
+
+import os
+import os.path
+import mimetypes
+from mercurial.demandload import demandload
+demandload(globals(), "re zlib ConfigParser")
+demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,templater")
+demandload(globals(), "mercurial.hgweb.request:hgrequest")
+demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile")
+from mercurial.node import *
+from mercurial.i18n import gettext as _
+
+def _up(p):
+    if p[0] != "/":
+        p = "/" + p
+    if p[-1] == "/":
+        p = p[:-1]
+    up = os.path.dirname(p)
+    if up == "/":
+        return "/"
+    return up + "/"
+
+class hgweb(object):
+    def __init__(self, repo, name=None):
+        if type(repo) == type(""):
+            self.repo = hg.repository(ui.ui(), repo)
+        else:
+            self.repo = repo
+
+        self.mtime = -1
+        self.reponame = name
+        self.archives = 'zip', 'gz', 'bz2'
+
+    def refresh(self):
+        mtime = get_mtime(self.repo.root)
+        if mtime != self.mtime:
+            self.mtime = mtime
+            self.repo = hg.repository(self.repo.ui, self.repo.root)
+            self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
+            self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
+            self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
+
+    def archivelist(self, nodeid):
+        allowed = (self.repo.ui.config("web", "allow_archive", "")
+                   .replace(",", " ").split())
+        for i in self.archives:
+            if i in allowed or self.repo.ui.configbool("web", "allow" + i):
+                yield {"type" : i, "node" : nodeid, "url": ""}
+
+    def listfiles(self, files, mf):
+        for f in files[:self.maxfiles]:
+            yield self.t("filenodelink", node=hex(mf[f]), file=f)
+        if len(files) > self.maxfiles:
+            yield self.t("fileellipses")
+
+    def listfilediffs(self, files, changeset):
+        for f in files[:self.maxfiles]:
+            yield self.t("filedifflink", node=hex(changeset), file=f)
+        if len(files) > self.maxfiles:
+            yield self.t("fileellipses")
+
+    def siblings(self, siblings=[], rev=None, hiderev=None, **args):
+        if not rev:
+            rev = lambda x: ""
+        siblings = [s for s in siblings if s != nullid]
+        if len(siblings) == 1 and rev(siblings[0]) == hiderev:
+            return
+        for s in siblings:
+            yield dict(node=hex(s), rev=rev(s), **args)
+
+    def renamelink(self, fl, node):
+        r = fl.renamed(node)
+        if r:
+            return [dict(file=r[0], node=hex(r[1]))]
+        return []
+
+    def showtag(self, t1, node=nullid, **args):
+        for t in self.repo.nodetags(node):
+             yield self.t(t1, tag=t, **args)
+
+    def diff(self, node1, node2, files):
+        def filterfiles(filters, files):
+            l = [x for x in files if x in filters]
+
+            for t in filters:
+                if t and t[-1] != os.sep:
+                    t += os.sep
+                l += [x for x in files if x.startswith(t)]
+            return l
+
+        parity = [0]
+        def diffblock(diff, f, fn):
+            yield self.t("diffblock",
+                         lines=prettyprintlines(diff),
+                         parity=parity[0],
+                         file=f,
+                         filenode=hex(fn or nullid))
+            parity[0] = 1 - parity[0]
+
+        def prettyprintlines(diff):
+            for l in diff.splitlines(1):
+                if l.startswith('+'):
+                    yield self.t("difflineplus", line=l)
+                elif l.startswith('-'):
+                    yield self.t("difflineminus", line=l)
+                elif l.startswith('@'):
+                    yield self.t("difflineat", line=l)
+                else:
+                    yield self.t("diffline", line=l)
+
+        r = self.repo
+        cl = r.changelog
+        mf = r.manifest
+        change1 = cl.read(node1)
+        change2 = cl.read(node2)
+        mmap1 = mf.read(change1[0])
+        mmap2 = mf.read(change2[0])
+        date1 = util.datestr(change1[2])
+        date2 = util.datestr(change2[2])
+
+        modified, added, removed, deleted, unknown = r.changes(node1, node2)
+        if files:
+            modified, added, removed = map(lambda x: filterfiles(files, x),
+                                           (modified, added, removed))
+
+        diffopts = self.repo.ui.diffopts()
+        showfunc = diffopts['showfunc']
+        ignorews = diffopts['ignorews']
+        for f in modified:
+            to = r.file(f).read(mmap1[f])
+            tn = r.file(f).read(mmap2[f])
+            yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
+                            showfunc=showfunc, ignorews=ignorews), f, tn)
+        for f in added:
+            to = None
+            tn = r.file(f).read(mmap2[f])
+            yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
+                            showfunc=showfunc, ignorews=ignorews), f, tn)
+        for f in removed:
+            to = r.file(f).read(mmap1[f])
+            tn = None
+            yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
+                            showfunc=showfunc, ignorews=ignorews), f, tn)
+
+    def changelog(self, pos):
+        def changenav(**map):
+            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 = []
+            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))
+                if pos - f >= 0:
+                    l.insert(0, ("-" + r, pos - f))
+
+            yield {"rev": 0, "label": "(0)"}
+
+            for label, rev in l:
+                yield {"label": label, "rev": rev}
+
+            yield {"label": "tip", "rev": "tip"}
+
+        def changelist(**map):
+            parity = (start - end) & 1
+            cl = self.repo.changelog
+            l = [] # build a list in forward order for efficiency
+            for i in range(start, end):
+                n = cl.node(i)
+                changes = cl.read(n)
+                hn = hex(n)
+
+                l.insert(0, {"parity": parity,
+                             "author": changes[1],
+                             "parent": self.siblings(cl.parents(n), cl.rev,
+                                                     cl.rev(n) - 1),
+                             "child": self.siblings(cl.children(n), cl.rev,
+                                                    cl.rev(n) + 1),
+                             "changelogtag": self.showtag("changelogtag",n),
+                             "manifest": hex(changes[0]),
+                             "desc": changes[4],
+                             "date": changes[2],
+                             "files": self.listfilediffs(changes[3], n),
+                             "rev": i,
+                             "node": hn})
+                parity = 1 - parity
+
+            for e in l:
+                yield e
+
+        cl = self.repo.changelog
+        mf = cl.read(cl.tip())[0]
+        count = cl.count()
+        start = max(0, pos - self.maxchanges + 1)
+        end = min(count, start + self.maxchanges)
+        pos = end - 1
+
+        yield self.t('changelog',
+                     changenav=changenav,
+                     manifest=hex(mf),
+                     rev=pos, changesets=count, entries=changelist,
+                     archives=self.archivelist("tip"))
+
+    def search(self, query):
+
+        def changelist(**map):
+            cl = self.repo.changelog
+            count = 0
+            qw = query.lower().split()
+
+            def revgen():
+                for i in range(cl.count() - 1, 0, -100):
+                    l = []
+                    for j in range(max(0, i - 100), i):
+                        n = cl.node(j)
+                        changes = cl.read(n)
+                        l.append((n, j, changes))
+                    l.reverse()
+                    for e in l:
+                        yield e
+
+            for n, i, changes in revgen():
+                miss = 0
+                for q in qw:
+                    if not (q in changes[1].lower() or
+                            q in changes[4].lower() or
+                            q in " ".join(changes[3][:20]).lower()):
+                        miss = 1
+                        break
+                if miss:
+                    continue
+
+                count += 1
+                hn = hex(n)
+
+                yield self.t('searchentry',
+                             parity=count & 1,
+                             author=changes[1],
+                             parent=self.siblings(cl.parents(n), cl.rev),
+                             child=self.siblings(cl.children(n), cl.rev),
+                             changelogtag=self.showtag("changelogtag",n),
+                             manifest=hex(changes[0]),
+                             desc=changes[4],
+                             date=changes[2],
+                             files=self.listfilediffs(changes[3], n),
+                             rev=i,
+                             node=hn)
+
+                if count >= self.maxchanges:
+                    break
+
+        cl = self.repo.changelog
+        mf = cl.read(cl.tip())[0]
+
+        yield self.t('search',
+                     query=query,
+                     manifest=hex(mf),
+                     entries=changelist)
+
+    def changeset(self, nodeid):
+        cl = self.repo.changelog
+        n = self.repo.lookup(nodeid)
+        nodeid = hex(n)
+        changes = cl.read(n)
+        p1 = cl.parents(n)[0]
+
+        files = []
+        mf = self.repo.manifest.read(changes[0])
+        for f in changes[3]:
+            files.append(self.t("filenodelink",
+                                filenode=hex(mf.get(f, nullid)), file=f))
+
+        def diff(**map):
+            yield self.diff(p1, n, None)
+
+        yield self.t('changeset',
+                     diff=diff,
+                     rev=cl.rev(n),
+                     node=nodeid,
+                     parent=self.siblings(cl.parents(n), cl.rev),
+                     child=self.siblings(cl.children(n), cl.rev),
+                     changesettag=self.showtag("changesettag",n),
+                     manifest=hex(changes[0]),
+                     author=changes[1],
+                     desc=changes[4],
+                     date=changes[2],
+                     files=files,
+                     archives=self.archivelist(nodeid))
+
+    def filelog(self, f, filenode):
+        cl = self.repo.changelog
+        fl = self.repo.file(f)
+        filenode = hex(fl.lookup(filenode))
+        count = fl.count()
+
+        def entries(**map):
+            l = []
+            parity = (count - 1) & 1
+
+            for i in range(count):
+                n = fl.node(i)
+                lr = fl.linkrev(n)
+                cn = cl.node(lr)
+                cs = cl.read(cl.node(lr))
+
+                l.insert(0, {"parity": parity,
+                             "filenode": hex(n),
+                             "filerev": i,
+                             "file": f,
+                             "node": hex(cn),
+                             "author": cs[1],
+                             "date": cs[2],
+                             "rename": self.renamelink(fl, n),
+                             "parent": self.siblings(fl.parents(n),
+                                                     fl.rev, file=f),
+                             "child": self.siblings(fl.children(n),
+                                                    fl.rev, file=f),
+                             "desc": cs[4]})
+                parity = 1 - parity
+
+            for e in l:
+                yield e
+
+        yield self.t("filelog", file=f, filenode=filenode, entries=entries)
+
+    def filerevision(self, f, node):
+        fl = self.repo.file(f)
+        n = fl.lookup(node)
+        node = hex(n)
+        text = fl.read(n)
+        changerev = fl.linkrev(n)
+        cl = self.repo.changelog
+        cn = cl.node(changerev)
+        cs = cl.read(cn)
+        mfn = cs[0]
+
+        mt = mimetypes.guess_type(f)[0]
+        rawtext = text
+        if util.binary(text):
+            mt = mt or 'application/octet-stream'
+            text = "(binary:%s)" % mt
+        mt = mt or 'text/plain'
+
+        def lines():
+            for l, t in enumerate(text.splitlines(1)):
+                yield {"line": t,
+                       "linenumber": "% 6d" % (l + 1),
+                       "parity": l & 1}
+
+        yield self.t("filerevision",
+                     file=f,
+                     filenode=node,
+                     path=_up(f),
+                     text=lines(),
+                     raw=rawtext,
+                     mimetype=mt,
+                     rev=changerev,
+                     node=hex(cn),
+                     manifest=hex(mfn),
+                     author=cs[1],
+                     date=cs[2],
+                     parent=self.siblings(fl.parents(n), fl.rev, file=f),
+                     child=self.siblings(fl.children(n), fl.rev, file=f),
+                     rename=self.renamelink(fl, n),
+                     permissions=self.repo.manifest.readflags(mfn)[f])
+
+    def fileannotate(self, f, node):
+        bcache = {}
+        ncache = {}
+        fl = self.repo.file(f)
+        n = fl.lookup(node)
+        node = hex(n)
+        changerev = fl.linkrev(n)
+
+        cl = self.repo.changelog
+        cn = cl.node(changerev)
+        cs = cl.read(cn)
+        mfn = cs[0]
+
+        def annotate(**map):
+            parity = 1
+            last = None
+            for r, l in fl.annotate(n):
+                try:
+                    cnode = ncache[r]
+                except KeyError:
+                    cnode = ncache[r] = self.repo.changelog.node(r)
+
+                try:
+                    name = bcache[r]
+                except KeyError:
+                    cl = self.repo.changelog.read(cnode)
+                    bcache[r] = name = self.repo.ui.shortuser(cl[1])
+
+                if last != cnode:
+                    parity = 1 - parity
+                    last = cnode
+
+                yield {"parity": parity,
+                       "node": hex(cnode),
+                       "rev": r,
+                       "author": name,
+                       "file": f,
+                       "line": l}
+
+        yield self.t("fileannotate",
+                     file=f,
+                     filenode=node,
+                     annotate=annotate,
+                     path=_up(f),
+                     rev=changerev,
+                     node=hex(cn),
+                     manifest=hex(mfn),
+                     author=cs[1],
+                     date=cs[2],
+                     rename=self.renamelink(fl, n),
+                     parent=self.siblings(fl.parents(n), fl.rev, file=f),
+                     child=self.siblings(fl.children(n), fl.rev, file=f),
+                     permissions=self.repo.manifest.readflags(mfn)[f])
+
+    def manifest(self, mnode, path):
+        man = self.repo.manifest
+        mn = man.lookup(mnode)
+        mnode = hex(mn)
+        mf = man.read(mn)
+        rev = man.rev(mn)
+        changerev = man.linkrev(mn)
+        node = self.repo.changelog.node(changerev)
+        mff = man.readflags(mn)
+
+        files = {}
+
+        p = path[1:]
+        if p and p[-1] != "/":
+            p += "/"
+        l = len(p)
+
+        for f,n in mf.items():
+            if f[:l] != p:
+                continue
+            remain = f[l:]
+            if "/" in remain:
+                short = remain[:remain.find("/") + 1] # bleah
+                files[short] = (f, None)
+            else:
+                short = os.path.basename(remain)
+                files[short] = (f, n)
+
+        def filelist(**map):
+            parity = 0
+            fl = files.keys()
+            fl.sort()
+            for f in fl:
+                full, fnode = files[f]
+                if not fnode:
+                    continue
+
+                yield {"file": full,
+                       "manifest": mnode,
+                       "filenode": hex(fnode),
+                       "parity": parity,
+                       "basename": f,
+                       "permissions": mff[full]}
+                parity = 1 - parity
+
+        def dirlist(**map):
+            parity = 0
+            fl = files.keys()
+            fl.sort()
+            for f in fl:
+                full, fnode = files[f]
+                if fnode:
+                    continue
+
+                yield {"parity": parity,
+                       "path": os.path.join(path, f),
+                       "manifest": mnode,
+                       "basename": f[:-1]}
+                parity = 1 - parity
+
+        yield self.t("manifest",
+                     manifest=mnode,
+                     rev=rev,
+                     node=hex(node),
+                     path=path,
+                     up=_up(path),
+                     fentries=filelist,
+                     dentries=dirlist,
+                     archives=self.archivelist(hex(node)))
+
+    def tags(self):
+        cl = self.repo.changelog
+        mf = cl.read(cl.tip())[0]
+
+        i = self.repo.tagslist()
+        i.reverse()
+
+        def entries(notip=False, **map):
+            parity = 0
+            for k,n in i:
+                if notip and k == "tip": continue
+                yield {"parity": parity,
+                       "tag": k,
+                       "tagmanifest": hex(cl.read(n)[0]),
+                       "date": cl.read(n)[2],
+                       "node": hex(n)}
+                parity = 1 - parity
+
+        yield self.t("tags",
+                     manifest=hex(mf),
+                     entries=lambda **x: entries(False, **x),
+                     entriesnotip=lambda **x: entries(True, **x))
+
+    def summary(self):
+        cl = self.repo.changelog
+        mf = cl.read(cl.tip())[0]
+
+        i = self.repo.tagslist()
+        i.reverse()
+
+        def tagentries(**map):
+            parity = 0
+            count = 0
+            for k,n in i:
+                if k == "tip": # skip tip
+                    continue;
+
+                count += 1
+                if count > 10: # limit to 10 tags
+                    break;
+
+                c = cl.read(n)
+                m = c[0]
+                t = c[2]
+
+                yield self.t("tagentry",
+                             parity = parity,
+                             tag = k,
+                             node = hex(n),
+                             date = t,
+                             tagmanifest = hex(m))
+                parity = 1 - parity
+
+        def changelist(**map):
+            parity = 0
+            cl = self.repo.changelog
+            l = [] # build a list in forward order for efficiency
+            for i in range(start, end):
+                n = cl.node(i)
+                changes = cl.read(n)
+                hn = hex(n)
+                t = changes[2]
+
+                l.insert(0, self.t(
+                    'shortlogentry',
+                    parity = parity,
+                    author = changes[1],
+                    manifest = hex(changes[0]),
+                    desc = changes[4],
+                    date = t,
+                    rev = i,
+                    node = hn))
+                parity = 1 - parity
+
+            yield l
+
+        cl = self.repo.changelog
+        mf = cl.read(cl.tip())[0]
+        count = cl.count()
+        start = max(0, count - self.maxchanges)
+        end = min(count, start + self.maxchanges)
+        pos = end - 1
+
+        yield self.t("summary",
+                 desc = self.repo.ui.config("web", "description", "unknown"),
+                 owner = (self.repo.ui.config("ui", "username") or # preferred
+                          self.repo.ui.config("web", "contact") or # deprecated
+                          self.repo.ui.config("web", "author", "unknown")), # also
+                 lastchange = (0, 0), # FIXME
+                 manifest = hex(mf),
+                 tags = tagentries,
+                 shortlog = changelist)
+
+    def filediff(self, file, changeset):
+        cl = self.repo.changelog
+        n = self.repo.lookup(changeset)
+        changeset = hex(n)
+        p1 = cl.parents(n)[0]
+        cs = cl.read(n)
+        mf = self.repo.manifest.read(cs[0])
+
+        def diff(**map):
+            yield self.diff(p1, n, [file])
+
+        yield self.t("filediff",
+                     file=file,
+                     filenode=hex(mf.get(file, nullid)),
+                     node=changeset,
+                     rev=self.repo.changelog.rev(n),
+                     parent=self.siblings(cl.parents(n), cl.rev),
+                     child=self.siblings(cl.children(n), cl.rev),
+                     diff=diff)
+
+    archive_specs = {
+        'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
+        'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
+        'zip': ('application/zip', 'zip', '.zip', None),
+        }
+
+    def archive(self, req, cnode, type):
+        reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
+        name = "%s-%s" % (reponame, short(cnode))
+        mimetype, artype, extension, encoding = self.archive_specs[type]
+        headers = [('Content-type', mimetype),
+                   ('Content-disposition', 'attachment; filename=%s%s' %
+                    (name, extension))]
+        if encoding:
+            headers.append(('Content-encoding', encoding))
+        req.header(headers)
+        archival.archive(self.repo, req.out, cnode, artype, prefix=name)
+
+    # add tags to things
+    # tags -> list of changesets corresponding to tags
+    # find tag, changeset, file
+
+    def run(self, req=hgrequest()):
+        def clean(path):
+            p = util.normpath(path)
+            if p[:2] == "..":
+                raise "suspicious path"
+            return p
+
+        def header(**map):
+            yield self.t("header", **map)
+
+        def footer(**map):
+            yield self.t("footer",
+                         motd=self.repo.ui.config("web", "motd", ""),
+                         **map)
+
+        def expand_form(form):
+            shortcuts = {
+                'cl': [('cmd', ['changelog']), ('rev', None)],
+                'cs': [('cmd', ['changeset']), ('node', None)],
+                'f': [('cmd', ['file']), ('filenode', None)],
+                'fl': [('cmd', ['filelog']), ('filenode', None)],
+                'fd': [('cmd', ['filediff']), ('node', None)],
+                'fa': [('cmd', ['annotate']), ('filenode', None)],
+                'mf': [('cmd', ['manifest']), ('manifest', None)],
+                'ca': [('cmd', ['archive']), ('node', None)],
+                'tags': [('cmd', ['tags'])],
+                'tip': [('cmd', ['changeset']), ('node', ['tip'])],
+                'static': [('cmd', ['static']), ('file', None)]
+            }
+
+            for k in shortcuts.iterkeys():
+                if form.has_key(k):
+                    for name, value in shortcuts[k]:
+                        if value is None:
+                            value = form[k]
+                        form[name] = value
+                    del form[k]
+
+        self.refresh()
+
+        expand_form(req.form)
+
+        t = self.repo.ui.config("web", "templates", templater.templatepath())
+        static = self.repo.ui.config("web", "static", os.path.join(t,"static"))
+        m = os.path.join(t, "map")
+        style = self.repo.ui.config("web", "style", "")
+        if req.form.has_key('style'):
+            style = req.form['style'][0]
+        if style:
+            b = os.path.basename("map-" + style)
+            p = os.path.join(t, b)
+            if os.path.isfile(p):
+                m = p
+
+        port = req.env["SERVER_PORT"]
+        port = port != "80" and (":" + port) or ""
+        uri = req.env["REQUEST_URI"]
+        if "?" in uri:
+            uri = uri.split("?")[0]
+        url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
+        if not self.reponame:
+            self.reponame = (self.repo.ui.config("web", "name")
+                             or uri.strip('/') or self.repo.root)
+
+        self.t = templater.templater(m, templater.common_filters,
+                                     defaults={"url": url,
+                                               "repo": self.reponame,
+                                               "header": header,
+                                               "footer": footer,
+                                               })
+
+        if not req.form.has_key('cmd'):
+            req.form['cmd'] = [self.t.cache['default'],]
+
+        cmd = req.form['cmd'][0]
+        if cmd == 'changelog':
+            hi = self.repo.changelog.count() - 1
+            if req.form.has_key('rev'):
+                hi = req.form['rev'][0]
+                try:
+                    hi = self.repo.changelog.rev(self.repo.lookup(hi))
+                except hg.RepoError:
+                    req.write(self.search(hi)) # XXX redirect to 404 page?
+                    return
+
+            req.write(self.changelog(hi))
+
+        elif cmd == 'changeset':
+            req.write(self.changeset(req.form['node'][0]))
+
+        elif cmd == 'manifest':
+            req.write(self.manifest(req.form['manifest'][0],
+                                    clean(req.form['path'][0])))
+
+        elif cmd == 'tags':
+            req.write(self.tags())
+
+        elif cmd == 'summary':
+            req.write(self.summary())
+
+        elif cmd == 'filediff':
+            req.write(self.filediff(clean(req.form['file'][0]),
+                                    req.form['node'][0]))
+
+        elif cmd == 'file':
+            req.write(self.filerevision(clean(req.form['file'][0]),
+                                        req.form['filenode'][0]))
+
+        elif cmd == 'annotate':
+            req.write(self.fileannotate(clean(req.form['file'][0]),
+                                        req.form['filenode'][0]))
+
+        elif cmd == 'filelog':
+            req.write(self.filelog(clean(req.form['file'][0]),
+                                   req.form['filenode'][0]))
+
+        elif cmd == 'heads':
+            req.httphdr("application/mercurial-0.1")
+            h = self.repo.heads()
+            req.write(" ".join(map(hex, h)) + "\n")
+
+        elif cmd == 'branches':
+            req.httphdr("application/mercurial-0.1")
+            nodes = []
+            if req.form.has_key('nodes'):
+                nodes = map(bin, req.form['nodes'][0].split(" "))
+            for b in self.repo.branches(nodes):
+                req.write(" ".join(map(hex, b)) + "\n")
+
+        elif cmd == 'between':
+            req.httphdr("application/mercurial-0.1")
+            nodes = []
+            if req.form.has_key('pairs'):
+                pairs = [map(bin, p.split("-"))
+                         for p in req.form['pairs'][0].split(" ")]
+            for b in self.repo.between(pairs):
+                req.write(" ".join(map(hex, b)) + "\n")
+
+        elif cmd == 'changegroup':
+            req.httphdr("application/mercurial-0.1")
+            nodes = []
+            if not self.allowpull:
+                return
+
+            if req.form.has_key('roots'):
+                nodes = map(bin, req.form['roots'][0].split(" "))
+
+            z = zlib.compressobj()
+            f = self.repo.changegroup(nodes, 'serve')
+            while 1:
+                chunk = f.read(4096)
+                if not chunk:
+                    break
+                req.write(z.compress(chunk))
+
+            req.write(z.flush())
+
+        elif cmd == 'archive':
+            changeset = self.repo.lookup(req.form['node'][0])
+            type = req.form['type'][0]
+            allowed = self.repo.ui.config("web", "allow_archive", "").split()
+            if (type in self.archives and (type in allowed or
+                self.repo.ui.configbool("web", "allow" + type, False))):
+                self.archive(req, changeset, type)
+                return
+
+            req.write(self.t("error"))
+
+        elif cmd == 'static':
+            fname = req.form['file'][0]
+            req.write(staticfile(static, fname)
+                      or self.t("error", error="%r not found" % fname))
+
+        else:
+            req.write(self.t("error"))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/hgweb/hgwebdir_mod.py	Thu Jun 01 13:47:34 2006 -0700
@@ -0,0 +1,156 @@
+# hgweb.py - web interface to a mercurial repository
+#
+# Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
+# Copyright 2005 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.
+
+import os
+from mercurial.demandload import demandload
+demandload(globals(), "ConfigParser")
+demandload(globals(), "mercurial:ui,hg,util,templater")
+demandload(globals(), "mercurial.hgweb.hgweb_mod:hgweb")
+demandload(globals(), "mercurial.hgweb.request:hgrequest")
+demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile")
+from mercurial.i18n import gettext as _
+
+# This is a stopgap
+class hgwebdir(object):
+    def __init__(self, config):
+        def cleannames(items):
+            return [(name.strip(os.sep), path) for name, path in items]
+
+        self.motd = ""
+        self.repos_sorted = ('name', False)
+        if isinstance(config, (list, tuple)):
+            self.repos = cleannames(config)
+            self.repos_sorted = ('', False)
+        elif isinstance(config, dict):
+            self.repos = cleannames(config.items())
+            self.repos.sort()
+        else:
+            cp = ConfigParser.SafeConfigParser()
+            cp.read(config)
+            self.repos = []
+            if cp.has_section('web') and cp.has_option('web', 'motd'):
+                self.motd = cp.get('web', 'motd')
+            if cp.has_section('paths'):
+                self.repos.extend(cleannames(cp.items('paths')))
+            if cp.has_section('collections'):
+                for prefix, root in cp.items('collections'):
+                    for path in util.walkrepos(root):
+                        repo = os.path.normpath(path)
+                        name = repo
+                        if name.startswith(prefix):
+                            name = name[len(prefix):]
+                        self.repos.append((name.lstrip(os.sep), repo))
+            self.repos.sort()
+
+    def run(self, req=hgrequest()):
+        def header(**map):
+            yield tmpl("header", **map)
+
+        def footer(**map):
+            yield tmpl("footer", motd=self.motd, **map)
+
+        m = os.path.join(templater.templatepath(), "map")
+        tmpl = templater.templater(m, templater.common_filters,
+                                   defaults={"header": header,
+                                             "footer": footer})
+
+        def archivelist(ui, nodeid, url):
+            allowed = (ui.config("web", "allow_archive", "")
+                       .replace(",", " ").split())
+            for i in ['zip', 'gz', 'bz2']:
+                if i in allowed or ui.configbool("web", "allow" + i):
+                    yield {"type" : i, "node": nodeid, "url": url}
+
+        def entries(sortcolumn="", descending=False, **map):
+            rows = []
+            parity = 0
+            for name, path in self.repos:
+                u = ui.ui()
+                try:
+                    u.readconfig(os.path.join(path, '.hg', 'hgrc'))
+                except IOError:
+                    pass
+                get = u.config
+
+                url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
+                       .replace("//", "/"))
+
+                # update time with local timezone
+                try:
+                    d = (get_mtime(path), util.makedate()[1])
+                except OSError:
+                    continue
+
+                contact = (get("ui", "username") or # preferred
+                           get("web", "contact") or # deprecated
+                           get("web", "author", "")) # also
+                description = get("web", "description", "")
+                name = get("web", "name", name)
+                row = dict(contact=contact or "unknown",
+                           contact_sort=contact.upper() or "unknown",
+                           name=name,
+                           name_sort=name,
+                           url=url,
+                           description=description or "unknown",
+                           description_sort=description.upper() or "unknown",
+                           lastchange=d,
+                           lastchange_sort=d[1]-d[0],
+                           archives=archivelist(u, "tip", url))
+                if (not sortcolumn
+                    or (sortcolumn, descending) == self.repos_sorted):
+                    # fast path for unsorted output
+                    row['parity'] = parity
+                    parity = 1 - parity
+                    yield row
+                else:
+                    rows.append((row["%s_sort" % sortcolumn], row))
+            if rows:
+                rows.sort()
+                if descending:
+                    rows.reverse()
+                for key, row in rows:
+                    row['parity'] = parity
+                    parity = 1 - parity
+                    yield row
+
+        virtual = req.env.get("PATH_INFO", "").strip('/')
+        if virtual:
+            real = dict(self.repos).get(virtual)
+            if real:
+                try:
+                    hgweb(real).run(req)
+                except IOError, inst:
+                    req.write(tmpl("error", error=inst.strerror))
+                except hg.RepoError, inst:
+                    req.write(tmpl("error", error=str(inst)))
+            else:
+                req.write(tmpl("notfound", repo=virtual))
+        else:
+            if req.form.has_key('static'):
+                static = os.path.join(templater.templatepath(), "static")
+                fname = req.form['static'][0]
+                req.write(staticfile(static, fname)
+                          or tmpl("error", error="%r not found" % fname))
+            else:
+                sortable = ["name", "description", "contact", "lastchange"]
+                sortcolumn, descending = self.repos_sorted
+                if req.form.has_key('sort'):
+                    sortcolumn = req.form['sort'][0]
+                    descending = sortcolumn.startswith('-')
+                    if descending:
+                        sortcolumn = sortcolumn[1:]
+                    if sortcolumn not in sortable:
+                        sortcolumn = ""
+
+                sort = [("sort_%s" % column,
+                         "%s%s" % ((not descending and column == sortcolumn)
+                                   and "-" or "", column))
+                        for column in sortable]
+                req.write(tmpl("index", entries=entries,
+                               sortcolumn=sortcolumn, descending=descending,
+                               **dict(sort)))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/hgweb/request.py	Thu Jun 01 13:47:34 2006 -0700
@@ -0,0 +1,44 @@
+# hgweb.py - web interface to a mercurial repository
+#
+# Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
+# Copyright 2005 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.
+
+from mercurial.demandload import demandload
+demandload(globals(), "socket sys cgi os")
+from mercurial.i18n import gettext as _
+
+class hgrequest(object):
+    def __init__(self, inp=None, out=None, env=None):
+        self.inp = inp or sys.stdin
+        self.out = out or sys.stdout
+        self.env = env or os.environ
+        self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
+
+    def write(self, *things):
+        for thing in things:
+            if hasattr(thing, "__iter__"):
+                for part in thing:
+                    self.write(part)
+            else:
+                try:
+                    self.out.write(str(thing))
+                except socket.error, inst:
+                    if inst[0] != errno.ECONNRESET:
+                        raise
+
+    def header(self, headers=[('Content-type','text/html')]):
+        for header in headers:
+            self.out.write("%s: %s\r\n" % header)
+        self.out.write("\r\n")
+
+    def httphdr(self, type, file="", size=0):
+
+        headers = [('Content-type', type)]
+        if file:
+            headers.append(('Content-disposition', 'attachment; filename=%s' % file))
+        if size > 0:
+            headers.append(('Content-length', str(size)))
+        self.header(headers)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/hgweb/server.py	Thu Jun 01 13:47:34 2006 -0700
@@ -0,0 +1,150 @@
+# hgweb.py - web interface to a mercurial repository
+#
+# Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
+# Copyright 2005 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.
+
+from mercurial.demandload import demandload
+import os, sys, errno
+demandload(globals(), "urllib BaseHTTPServer socket SocketServer")
+demandload(globals(), "mercurial:ui,hg,util,templater")
+demandload(globals(), "mercurial.hgweb.request:hgrequest")
+from mercurial.i18n import gettext as _
+
+def _splitURI(uri):
+    """ Return path and query splited from uri
+
+    Just like CGI environment, the path is unquoted, the query is
+    not.
+    """
+    if '?' in uri:
+        path, query = uri.split('?', 1)
+    else:
+        path, query = uri, ''
+    return urllib.unquote(path), query
+
+class _hgwebhandler(object, BaseHTTPServer.BaseHTTPRequestHandler):
+    def __init__(self, *args, **kargs):
+        BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kargs)
+
+    def log_error(self, format, *args):
+        errorlog = self.server.errorlog
+        errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
+                                             self.log_date_time_string(),
+                                             format % args))
+
+    def log_message(self, format, *args):
+        accesslog = self.server.accesslog
+        accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
+                                              self.log_date_time_string(),
+                                              format % args))
+
+    def do_POST(self):
+        try:
+            self.do_hgweb()
+        except socket.error, inst:
+            if inst[0] != errno.EPIPE:
+                raise
+
+    def do_GET(self):
+        self.do_POST()
+
+    def do_hgweb(self):
+        path_info, query = _splitURI(self.path)
+
+        env = {}
+        env['GATEWAY_INTERFACE'] = 'CGI/1.1'
+        env['REQUEST_METHOD'] = self.command
+        env['SERVER_NAME'] = self.server.server_name
+        env['SERVER_PORT'] = str(self.server.server_port)
+        env['REQUEST_URI'] = "/"
+        env['PATH_INFO'] = path_info
+        if query:
+            env['QUERY_STRING'] = query
+        host = self.address_string()
+        if host != self.client_address[0]:
+            env['REMOTE_HOST'] = host
+            env['REMOTE_ADDR'] = self.client_address[0]
+
+        if self.headers.typeheader is None:
+            env['CONTENT_TYPE'] = self.headers.type
+        else:
+            env['CONTENT_TYPE'] = self.headers.typeheader
+        length = self.headers.getheader('content-length')
+        if length:
+            env['CONTENT_LENGTH'] = length
+        accept = []
+        for line in self.headers.getallmatchingheaders('accept'):
+            if line[:1] in "\t\n\r ":
+                accept.append(line.strip())
+            else:
+                accept = accept + line[7:].split(',')
+        env['HTTP_ACCEPT'] = ','.join(accept)
+
+        req = hgrequest(self.rfile, self.wfile, env)
+        self.send_response(200, "Script output follows")
+        self.server.make_and_run_handler(req)
+
+def create_server(ui, repo, webdirmaker, repoviewmaker):
+    use_threads = True
+
+    def openlog(opt, default):
+        if opt and opt != '-':
+            return open(opt, 'w')
+        return default
+
+    address = ui.config("web", "address", "")
+    port = int(ui.config("web", "port", 8000))
+    use_ipv6 = ui.configbool("web", "ipv6")
+    webdir_conf = ui.config("web", "webdir_conf")
+    accesslog = openlog(ui.config("web", "accesslog", "-"), sys.stdout)
+    errorlog = openlog(ui.config("web", "errorlog", "-"), sys.stderr)
+
+    if use_threads:
+        try:
+            from threading import activeCount
+        except ImportError:
+            use_threads = False
+
+    if use_threads:
+        _mixin = SocketServer.ThreadingMixIn
+    else:
+        if hasattr(os, "fork"):
+            _mixin = SocketServer.ForkingMixIn
+        else:
+            class _mixin: pass
+
+    class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
+        def __init__(self, *args, **kargs):
+            BaseHTTPServer.HTTPServer.__init__(self, *args, **kargs)
+            self.accesslog = accesslog
+            self.errorlog = errorlog
+            self.repo = repo
+            self.webdir_conf = webdir_conf
+            self.webdirmaker = webdirmaker
+            self.repoviewmaker = repoviewmaker
+
+        def make_and_run_handler(self, req):
+            if self.webdir_conf:
+                hgwebobj = self.webdirmaker(self.server.webdir_conf)
+            elif self.repo is not None:
+                hgwebobj = self.repoviewmaker(repo.__class__(repo.ui,
+                                                             repo.origroot))
+            else:
+                raise hg.RepoError(_('no repo found'))
+            hgwebobj.run(req)
+
+    class IPv6HTTPServer(MercurialHTTPServer):
+        address_family = getattr(socket, 'AF_INET6', None)
+
+        def __init__(self, *args, **kwargs):
+            if self.address_family is None:
+                raise hg.RepoError(_('IPv6 not available on this system'))
+            super(IPv6HTTPServer, self).__init__(*args, **kargs)
+
+    if use_ipv6:
+        return IPv6HTTPServer((address, port), _hgwebhandler)
+    else:
+        return MercurialHTTPServer((address, port), _hgwebhandler)
--- a/mercurial/httprepo.py	Mon May 22 16:47:40 2006 +0200
+++ b/mercurial/httprepo.py	Thu Jun 01 13:47:34 2006 -0700
@@ -22,6 +22,9 @@
         if authinfo != (None, None):
             return authinfo
 
+        if not ui.interactive:
+            raise util.Abort(_('http authorization required'))
+
         self.ui.write(_("http authorization required\n"))
         self.ui.status(_("realm: %s\n") % realm)
         user = self.ui.prompt(_("user:"), default=None)
@@ -30,37 +33,95 @@
         self.add_password(realm, authuri, user, passwd)
         return (user, passwd)
 
+def netlocsplit(netloc):
+    '''split [user[:passwd]@]host[:port] into 4-tuple.'''
+
+    a = netloc.find('@')
+    if a == -1:
+        user, passwd = None, None
+    else:
+        userpass, netloc = netloc[:a], netloc[a+1:]
+        c = userpass.find(':')
+        if c == -1:
+            user, passwd = urllib.unquote(userpass), None
+        else:
+            user = urllib.unquote(userpass[:c])
+            passwd = urllib.unquote(userpass[c+1:])
+    c = netloc.find(':')
+    if c == -1:
+        host, port = netloc, None
+    else:
+        host, port = netloc[:c], netloc[c+1:]
+    return host, port, user, passwd
+
+def netlocunsplit(host, port, user=None, passwd=None):
+    '''turn host, port, user, passwd into [user[:passwd]@]host[:port].'''
+    if port:
+        hostport = host + ':' + port
+    else:
+        hostport = host
+    if user:
+        if passwd:
+            userpass = urllib.quote(user) + ':' + urllib.quote(passwd)
+        else:
+            userpass = urllib.quote(user)
+        return userpass + '@' + hostport
+    return hostport
+
 class httprepository(remoterepository):
     def __init__(self, ui, path):
-        # fix missing / after hostname
-        s = urlparse.urlsplit(path)
-        partial = s[2]
-        if not partial: partial = "/"
-        self.url = urlparse.urlunsplit((s[0], s[1], partial, '', ''))
+        scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
+        if query or frag:
+            raise util.Abort(_('unsupported URL component: "%s"') %
+                             (query or frag))
+        if not urlpath: urlpath = '/'
+        host, port, user, passwd = netlocsplit(netloc)
+
+        # urllib cannot handle URLs with embedded user or passwd
+        self.url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
+                                        urlpath, '', ''))
         self.ui = ui
-        no_list = [ "localhost", "127.0.0.1" ]
-        host = ui.config("http_proxy", "host")
-        if host is None:
-            host = os.environ.get("http_proxy")
-        if host and host.startswith('http://'):
-            host = host[7:]
-        user = ui.config("http_proxy", "user")
-        passwd = ui.config("http_proxy", "passwd")
-        no = ui.config("http_proxy", "no")
-        if no is None:
-            no = os.environ.get("no_proxy")
-        if no:
-            no_list = no_list + no.split(",")
+
+        proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
+        proxyauthinfo = None
+        handler = urllib2.BaseHandler()
+
+        if proxyurl:
+            # proxy can be proper url or host[:port]
+            if not (proxyurl.startswith('http:') or
+                    proxyurl.startswith('https:')):
+                proxyurl = 'http://' + proxyurl + '/'
+            snpqf = urlparse.urlsplit(proxyurl)
+            proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
+            hpup = netlocsplit(proxynetloc)
+
+            proxyhost, proxyport, proxyuser, proxypasswd = hpup
+            if not proxyuser:
+                proxyuser = ui.config("http_proxy", "user")
+                proxypasswd = ui.config("http_proxy", "passwd")
 
-        no_proxy = 0
-        for h in no_list:
-            if (path.startswith("http://" + h + "/") or
-                path.startswith("http://" + h + ":") or
-                path == "http://" + h):
-                no_proxy = 1
+            # see if we should use a proxy for this url
+            no_list = [ "localhost", "127.0.0.1" ]
+            no_list.extend([p.strip().lower() for
+                            p in ui.config("http_proxy", "no", '').split(',')
+                            if p.strip()])
+            no_list.extend([p.strip().lower() for
+                            p in os.getenv("no_proxy", '').split(',')
+                            if p.strip()])
+            # "http_proxy.always" config is for running tests on localhost
+            if (not ui.configbool("http_proxy", "always") and
+                host.lower() in no_list):
+                ui.debug(_('disabling proxy for %s\n') % host)
+            else:
+                proxyurl = urlparse.urlunsplit((
+                    proxyscheme, netlocunsplit(proxyhost, proxyport,
+                                               proxyuser, proxypasswd or ''),
+                    proxypath, proxyquery, proxyfrag))
+                handler = urllib2.ProxyHandler({scheme: proxyurl})
+                ui.debug(_('proxying through %s\n') % proxyurl)
 
-        # Note: urllib2 takes proxy values from the environment and those will
-        # take precedence
+        # urllib2 takes proxy values from the environment and those
+        # will take precedence if found, so drop them
         for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
             try:
                 if os.environ.has_key(env):
@@ -68,24 +129,15 @@
             except OSError:
                 pass
 
-        proxy_handler = urllib2.BaseHandler()
-        if host and not no_proxy:
-            proxy_handler = urllib2.ProxyHandler({"http" : "http://" + host})
+        passmgr = passwordmgr(ui)
+        if user:
+            ui.debug(_('will use user %s for http auth\n') % user)
+            passmgr.add_password(None, host, user, passwd or '')
 
-        proxyauthinfo = None
-        if user and passwd:
-            passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
-            passmgr.add_password(None, host, user, passwd)
-            proxyauthinfo = urllib2.ProxyBasicAuthHandler(passmgr)
-
-        if ui.interactive:
-            passmgr = passwordmgr(ui)
-            opener = urllib2.build_opener(
-                proxy_handler, proxyauthinfo,
-                urllib2.HTTPBasicAuthHandler(passmgr),
-                urllib2.HTTPDigestAuthHandler(passmgr))
-        else:
-            opener = urllib2.build_opener(proxy_handler, proxyauthinfo)
+        opener = urllib2.build_opener(
+            handler,
+            urllib2.HTTPBasicAuthHandler(passmgr),
+            urllib2.HTTPDigestAuthHandler(passmgr))
 
         # 1.0 here is the _protocol_ version
         opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
@@ -106,7 +158,9 @@
         try:
             resp = urllib2.urlopen(cu)
         except httplib.HTTPException, inst:
-            raise IOError(None, _('http error while sending %s command') % cmd)
+            self.ui.debug(_('http error while sending %s command\n') % cmd)
+            self.ui.print_exc()
+            raise IOError(None, inst)
         proto = resp.headers['content-type']
 
         # accept old "text/plain" and "application/hg-changegroup" for now
--- a/mercurial/localrepo.py	Mon May 22 16:47:40 2006 +0200
+++ b/mercurial/localrepo.py	Thu Jun 01 13:47:34 2006 -0700
@@ -12,7 +12,7 @@
 from demandload import *
 demandload(globals(), "appendfile changegroup")
 demandload(globals(), "re lock transaction tempfile stat mdiff errno ui")
-demandload(globals(), "revlog traceback")
+demandload(globals(), "revlog")
 
 class localrepository(object):
     def __del__(self):
@@ -125,8 +125,7 @@
                                    '%s\n') % (hname, exc))
                 if throw:
                     raise
-                if self.ui.traceback:
-                    traceback.print_exc()
+                self.ui.print_exc()
                 return True
             if r:
                 if throw:
@@ -166,37 +165,44 @@
                     return
                 s = l.split(" ", 1)
                 if len(s) != 2:
-                    self.ui.warn(_("%s: ignoring invalid tag\n") % context)
+                    self.ui.warn(_("%s: cannot parse entry\n") % context)
                     return
                 node, key = s
+                key = key.strip()
                 try:
                     bin_n = bin(node)
                 except TypeError:
-                    self.ui.warn(_("%s: ignoring invalid tag\n") % context)
+                    self.ui.warn(_("%s: node '%s' is not well formed\n") %
+                                 (context, node))
                     return
                 if bin_n not in self.changelog.nodemap:
-                    self.ui.warn(_("%s: ignoring invalid tag\n") % context)
+                    self.ui.warn(_("%s: tag '%s' refers to unknown node\n") %
+                                 (context, key))
                     return
-                self.tagscache[key.strip()] = bin_n
+                self.tagscache[key] = bin_n
 
-            # read each head of the tags file, ending with the tip
+            # read the tags file from each head, ending with the tip,
             # and add each tag found to the map, with "newer" ones
             # taking precedence
+            heads = self.heads()
+            heads.reverse()
             fl = self.file(".hgtags")
-            h = fl.heads()
-            h.reverse()
-            for r in h:
+            for node in heads:
+                change = self.changelog.read(node)
+                rev = self.changelog.rev(node)
+                fn, ff = self.manifest.find(change[0], '.hgtags')
+                if fn is None: continue
                 count = 0
-                for l in fl.read(r).splitlines():
+                for l in fl.read(fn).splitlines():
                     count += 1
-                    parsetag(l, ".hgtags:%d" % count)
-
+                    parsetag(l, _(".hgtags (rev %d:%s), line %d") %
+                             (rev, short(node), count))
             try:
                 f = self.opener("localtags")
                 count = 0
                 for l in f:
                     count += 1
-                    parsetag(l, "localtags:%d" % count)
+                    parsetag(l, _("localtags, line %d") % count)
             except IOError:
                 pass
 
@@ -297,7 +303,7 @@
         if tr != None and tr.running():
             return tr.nest()
 
-        # save dirstate for undo
+        # save dirstate for rollback
         try:
             ds = self.opener("dirstate").read()
         except IOError:
@@ -321,7 +327,7 @@
             self.ui.warn(_("no interrupted transaction available\n"))
             return False
 
-    def undo(self, wlock=None):
+    def rollback(self, wlock=None):
         if not wlock:
             wlock = self.wlock()
         l = self.lock()
@@ -332,7 +338,7 @@
             self.reload()
             self.wreload()
         else:
-            self.ui.warn(_("no undo information available\n"))
+            self.ui.warn(_("no rollback information available\n"))
 
     def wreload(self):
         self.dirstate.read()
@@ -550,12 +556,15 @@
             # run editor in the repository root
             olddir = os.getcwd()
             os.chdir(self.root)
-            edittext = self.ui.edit("\n".join(edittext), user)
+            text = self.ui.edit("\n".join(edittext), user)
             os.chdir(olddir)
-            if not edittext.rstrip():
-                return None
-            text = edittext
 
+        lines = [line.rstrip() for line in text.rstrip().splitlines()]
+        while lines and not lines[0]:
+            del lines[0]
+        if not lines:
+            return None
+        text = '\n'.join(lines)
         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)
@@ -862,7 +871,7 @@
         b = []
         for n in nodes:
             t = n
-            while n:
+            while 1:
                 p = self.changelog.parents(n)
                 if p[1] != nullid or p[0] == nullid:
                     b.append((t, n, p[0], p[1]))
@@ -890,6 +899,21 @@
         return r
 
     def findincoming(self, remote, base=None, heads=None, force=False):
+        """Return list of roots of the subsets of missing nodes from remote
+
+        If base dict is specified, assume that these nodes and their parents
+        exist on the remote side and that no child of a node of base exists
+        in both remote and self.
+        Furthermore base will be updated to include the nodes that exists
+        in self and remote but no children exists in self and remote.
+        If a list of heads is specified, return only nodes which are heads
+        or ancestors of these heads.
+
+        All the ancestors of base are in self and in remote.
+        All the descendants of the list returned are missing in self.
+        (and so we know that the rest of the nodes are missing in remote, see
+        outgoing)
+        """
         m = self.changelog.nodemap
         search = []
         fetch = {}
@@ -902,6 +926,7 @@
             heads = remote.heads()
 
         if self.changelog.tip() == nullid:
+            base[nullid] = 1
             if heads != [nullid]:
                 return [nullid]
             return []
@@ -920,7 +945,7 @@
         if not unknown:
             return []
 
-        rep = {}
+        req = dict.fromkeys(unknown)
         reqcnt = 0
 
         # search through remote branches
@@ -937,12 +962,12 @@
 
                 self.ui.debug(_("examining %s:%s\n")
                               % (short(n[0]), short(n[1])))
-                if n[0] == nullid:
-                    break
-                if n in seenbranch:
+                if n[0] == nullid: # found the end of the branch
+                    pass
+                elif n in seenbranch:
                     self.ui.debug(_("branch already found\n"))
                     continue
-                if n[1] and n[1] in m: # do we know the base?
+                elif n[1] and n[1] in m: # do we know the base?
                     self.ui.debug(_("found incomplete branch %s:%s\n")
                                   % (short(n[0]), short(n[1])))
                     search.append(n) # schedule branch range for scanning
@@ -953,14 +978,14 @@
                             self.ui.debug(_("found new changeset %s\n") %
                                           short(n[1]))
                             fetch[n[1]] = 1 # earliest unknown
-                            base[n[2]] = 1 # latest known
-                            continue
+                        for p in n[2:4]:
+                            if p in m:
+                                base[p] = 1 # latest known
 
-                    for a in n[2:4]:
-                        if a not in rep:
-                            r.append(a)
-                            rep[a] = 1
-
+                    for p in n[2:4]:
+                        if p not in req and p not in m:
+                            r.append(p)
+                            req[p] = 1
                 seen[n[0]] = 1
 
             if r:
@@ -971,12 +996,7 @@
                     for b in remote.branches(r[p:p+10]):
                         self.ui.debug(_("received %s:%s\n") %
                                       (short(b[0]), short(b[1])))
-                        if b[0] in m:
-                            self.ui.debug(_("found base node %s\n")
-                                          % short(b[0]))
-                            base[b[0]] = 1
-                        elif b[0] not in seen:
-                            unknown.append(b)
+                        unknown.append(b)
 
         # do binary search on the branches we found
         while search:
@@ -1488,12 +1508,11 @@
 
             # pull off the changeset group
             self.ui.status(_("adding changesets\n"))
-            co = cl.tip()
+            cor = cl.count() - 1
             chunkiter = changegroup.chunkiter(source)
-            cn = cl.addgroup(chunkiter, csmap, tr, 1) # unique
-            cnr, cor = map(cl.rev, (cn, co))
-            if cn == nullid:
-                cnr = cor
+            if cl.addgroup(chunkiter, csmap, tr, 1) is None:
+                raise util.Abort(_("received changelog group is empty"))
+            cnr = cl.count() - 1
             changesets = cnr - cor
 
             mf = None
@@ -1503,9 +1522,12 @@
 
                 # pull off the manifest group
                 self.ui.status(_("adding manifests\n"))
-                mm = mf.tip()
                 chunkiter = changegroup.chunkiter(source)
-                mo = mf.addgroup(chunkiter, revmap, tr)
+                # no need to check for empty manifest group here:
+                # if the result of the merge of 1 and 2 is the same in 3 and 4,
+                # no new manifest will be created and the manifest group will
+                # be empty during the pull
+                mf.addgroup(chunkiter, revmap, tr)
 
                 # process the files
                 self.ui.status(_("adding file changes\n"))
@@ -1517,7 +1539,8 @@
                     fl = self.file(f)
                     o = fl.count()
                     chunkiter = changegroup.chunkiter(source)
-                    n = fl.addgroup(chunkiter, revmap, tr)
+                    if fl.addgroup(chunkiter, revmap, tr) is None:
+                        raise util.Abort(_("received file revlog group is empty"))
                     revisions += fl.count() - o
                     files += 1
 
--- a/mercurial/manifest.py	Mon May 22 16:47:40 2006 +0200
+++ b/mercurial/manifest.py	Thu Jun 01 13:47:34 2006 -0700
@@ -43,48 +43,61 @@
     def diff(self, a, b):
         return mdiff.textdiff(str(a), str(b))
 
+    def _search(self, m, s, lo=0, hi=None):
+        '''return a tuple (start, end) that says where to find s within m.
+
+        If the string is found m[start:end] are the line containing
+        that string.  If start == end the string was not found and
+        they indicate the proper sorted insertion point.  This was
+        taken from bisect_left, and modified to find line start/end as
+        it goes along.
+
+        m should be a buffer or a string
+        s is a string'''
+        def advance(i, c):
+            while i < lenm and m[i] != c:
+                i += 1
+            return i
+        lenm = len(m)
+        if not hi:
+            hi = lenm
+        while lo < hi:
+            mid = (lo + hi) // 2
+            start = mid
+            while start > 0 and m[start-1] != '\n':
+                start -= 1
+            end = advance(start, '\0')
+            if m[start:end] < s:
+                # we know that after the null there are 40 bytes of sha1
+                # this translates to the bisect lo = mid + 1
+                lo = advance(end + 40, '\n') + 1
+            else:
+                # this translates to the bisect hi = mid
+                hi = start
+        end = advance(lo, '\0')
+        found = m[lo:end]
+        if cmp(s, found) == 0:
+            # we know that after the null there are 40 bytes of sha1
+            end = advance(end + 40, '\n')
+            return (lo, end+1)
+        else:
+            return (lo, lo)
+
+    def find(self, node, f):
+        '''look up entry for a single file efficiently.
+        return (node, flag) pair if found, (None, None) if not.'''
+        if self.mapcache and node == self.mapcache[0]:
+            return self.mapcache[1].get(f), self.mapcache[2].get(f)
+        text = self.revision(node)
+        start, end = self._search(text, f)
+        if start == end:
+            return None, None
+        l = text[start:end]
+        f, n = l.split('\0')
+        return bin(n[:40]), n[40:-1] == 'x'
+
     def add(self, map, flags, transaction, link, p1=None, p2=None,
             changed=None):
-
-        # returns a tuple (start, end).  If the string is found
-        # m[start:end] are the line containing that string.  If start == end
-        # the string was not found and they indicate the proper sorted 
-        # insertion point.  This was taken from bisect_left, and modified
-        # to find line start/end as it goes along.
-        #
-        # m should be a buffer or a string
-        # s is a string
-        #
-        def manifestsearch(m, s, lo=0, hi=None):
-            def advance(i, c):
-                while i < lenm and m[i] != c:
-                    i += 1
-                return i
-            lenm = len(m)
-            if not hi:
-                hi = lenm
-            while lo < hi:
-                mid = (lo + hi) // 2
-                start = mid
-                while start > 0 and m[start-1] != '\n':
-                    start -= 1
-                end = advance(start, '\0')
-                if m[start:end] < s:
-                    # we know that after the null there are 40 bytes of sha1
-                    # this translates to the bisect lo = mid + 1
-                    lo = advance(end + 40, '\n') + 1
-                else:
-                    # this translates to the bisect hi = mid
-                    hi = start
-            end = advance(lo, '\0')
-            found = m[lo:end]
-            if cmp(s, found) == 0: 
-                # we know that after the null there are 40 bytes of sha1
-                end = advance(end + 40, '\n')
-                return (lo, end+1)
-            else:
-                return (lo, lo)
-
         # apply the changes collected during the bisect loop to our addlist
         # return a delta suitable for addrevision
         def addlistdelta(addlist, x):
@@ -137,7 +150,7 @@
             for w in work:
                 f = w[0]
                 # bs will either be the index of the item or the insert point
-                start, end = manifestsearch(addbuf, f, start)
+                start, end = self._search(addbuf, f, start)
                 if w[1] == 0:
                     l = "%s\000%s%s\n" % (f, hex(map[f]),
                                           flags[f] and "x" or '')
--- a/mercurial/packagescan.py	Mon May 22 16:47:40 2006 +0200
+++ b/mercurial/packagescan.py	Thu Jun 01 13:47:34 2006 -0700
@@ -1,5 +1,6 @@
 # packagescan.py - Helper module for identifing used modules.
 # Used for the py2exe distutil.
+# This module must be the first mercurial module imported in setup.py
 #
 # Copyright 2005 Volker Kleinfeld <Volker.Kleinfeld@gmx.de>
 #
@@ -8,25 +9,58 @@
 import glob
 import os
 import sys
-import demandload
 import ihooks
+import types
+import string
+
+# Install this module as fake demandload module
+sys.modules['mercurial.demandload'] = sys.modules[__name__]
 
-requiredmodules = {} # Will contain the modules imported by demandload
+# Requiredmodules contains the modules imported by demandload.
+# Please note that demandload can be invoked before the 
+# mercurial.packagescan.scan method is invoked in case a mercurial
+# module is imported.
+requiredmodules = {} 
 def demandload(scope, modules):
-    """ fake demandload function that collects the required modules """
+    """ fake demandload function that collects the required modules 
+        foo            import foo
+        foo bar        import foo, bar
+        foo.bar        import foo.bar
+        foo:bar        from foo import bar
+        foo:bar,quux   from foo import bar, quux
+        foo.bar:quux   from foo.bar import quux"""
+
     for m in modules.split():
         mod = None
         try:
-            module, submodules = m.split(':')
-            submodules = submodules.split(',')
+            module, fromlist = m.split(':')
+            fromlist = fromlist.split(',')
         except:
             module = m
-            submodules = []
-        mod = __import__(module, scope, scope, submodules)
-        scope[module] = mod
-        requiredmodules[mod.__name__] = 1
+            fromlist = []
+        mod = __import__(module, scope, scope, fromlist)
+        if fromlist == []:
+            # mod is only the top package, but we need all packages
+            comp = module.split('.')
+            i = 1
+            mn = comp[0]
+            while True:
+                # mn and mod.__name__ might not be the same
+                scope[mn] = mod
+                requiredmodules[mod.__name__] = 1
+                if len(comp) == i: break
+                mod = getattr(mod,comp[i]) 
+                mn = string.join(comp[:i+1],'.')
+                i += 1
+        else:
+            # mod is the last package in the component list
+            requiredmodules[mod.__name__] = 1
+            for f in fromlist:
+                scope[f] = getattr(mod,f)
+                if type(scope[f]) == types.ModuleType:
+                    requiredmodules[scope[f].__name__] = 1
 
-def getmodules(libpath,packagename):
+def scan(libpath,packagename):
     """ helper for finding all required modules of package <packagename> """
     # Use the package in the build directory
     libpath = os.path.abspath(libpath)
@@ -45,8 +79,6 @@
     pymodulefiles = glob.glob('*.py')
     extmodulefiles = glob.glob('*.pyd')
     os.chdir(cwd)
-    # Install a fake demandload module
-    sys.modules['mercurial.demandload'] = sys.modules['mercurial.packagescan']
     # Import all python modules and by that run the fake demandload
     for m in pymodulefiles:
         if m == '__init__.py': continue
@@ -62,8 +94,9 @@
         fullname = packagename+'.'+mname
         __import__(fullname,tmp,tmp)
         requiredmodules[fullname] = 1
-    includes = requiredmodules.keys()
-    return includes
+
+def getmodules():
+    return requiredmodules.keys()
 
 def importfrom(filename):
     """
--- a/mercurial/revlog.py	Mon May 22 16:47:40 2006 +0200
+++ b/mercurial/revlog.py	Thu Jun 01 13:47:34 2006 -0700
@@ -1196,8 +1196,6 @@
             start = self.start(base)
             end = self.end(t)
 
-        if node is None:
-            raise RevlogError(_("group to be added is empty"))
         return node
 
     def strip(self, rev, minlink):
--- a/mercurial/ui.py	Mon May 22 16:47:40 2006 +0200
+++ b/mercurial/ui.py	Thu Jun 01 13:47:34 2006 -0700
@@ -9,7 +9,7 @@
 from i18n import gettext as _
 from demandload import *
 demandload(globals(), "errno getpass os re smtplib socket sys tempfile")
-demandload(globals(), "templater util")
+demandload(globals(), "templater traceback util")
 
 class ui(object):
     def __init__(self, verbose=False, debug=False, quiet=False,
@@ -109,6 +109,10 @@
         else:
             return self.parentui.configbool(section, name, default)
 
+    def has_config(self, section):
+        '''tell whether section exists in config.'''
+        return self.cdata.has_section(section)
+
     def configitems(self, section):
         items = {}
         if self.parentui is not None:
@@ -179,7 +183,8 @@
         and stop searching if one of these is set.
         Abort if found username is an empty string to force specifying
         the commit user elsewhere, e.g. with line option or repo hgrc.
-        If not found, use $LOGNAME or $USERNAME +"@full.hostname".
+        If not found, use ($LOGNAME or $USER or $LNAME or
+        $USERNAME) +"@full.hostname".
         """
         user = os.environ.get("HGUSER")
         if user is None:
@@ -187,11 +192,10 @@
         if user is None:
             user = os.environ.get("EMAIL")
         if user is None:
-            user = os.environ.get("LOGNAME") or os.environ.get("USERNAME")
-            if user:
-                user = "%s@%s" % (user, socket.getfqdn())
-        if not user:
-            raise util.Abort(_("Please specify a username."))
+            try:
+                user = '%s@%s' % (getpass.getuser(), socket.getfqdn())
+            except KeyError:
+                raise util.Abort(_("Please specify a username."))
         return user
 
     def shortuser(self, user):
@@ -335,3 +339,11 @@
         else:
             mail = sendmail(self, method)
         return mail
+
+    def print_exc(self):
+        '''print exception traceback if traceback printing enabled.
+        only to call in exception handler. returns true if traceback
+        printed.'''
+        if self.traceback:
+            traceback.print_exc()
+        return self.traceback
--- a/mercurial/util.py	Mon May 22 16:47:40 2006 +0200
+++ b/mercurial/util.py	Thu Jun 01 13:47:34 2006 -0700
@@ -94,7 +94,7 @@
     """apply the patch <patchname> to the working directory.
     a list of patched files is returned"""
     patcher = find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
-    fp = os.popen('"%s" -p%d < "%s"' % (patcher, strip, patchname))
+    fp = os.popen('%s -p%d < "%s"' % (patcher, strip, patchname))
     files = {}
     for line in fp:
         line = line.rstrip()
@@ -734,7 +734,7 @@
         def rename(self):
             if not self.closed:
                 posixfile.close(self)
-                rename(self.temp, self.__name)
+                rename(self.temp, localpath(self.__name))
         def __del__(self):
             if not self.closed:
                 try:
--- a/mercurial/util_win32.py	Mon May 22 16:47:40 2006 +0200
+++ b/mercurial/util_win32.py	Thu Jun 01 13:47:34 2006 -0700
@@ -194,7 +194,7 @@
         # We are on win < nt: fetch the APPDATA directory location and use
         # the parent directory as the user home dir.
         appdir = shell.SHGetPathFromIDList(
-            qshell.SHGetSpecialFolderLocation(0, shellcon.CSIDL_APPDATA))
+            shell.SHGetSpecialFolderLocation(0, shellcon.CSIDL_APPDATA))
         userdir = os.path.dirname(appdir)
     return os.path.join(userdir, 'mercurial.ini')
 
--- a/setup.py	Mon May 22 16:47:40 2006 +0200
+++ b/setup.py	Thu Jun 01 13:47:34 2006 -0700
@@ -13,6 +13,8 @@
 from distutils.core import setup, Extension
 from distutils.command.install_data import install_data
 
+# mercurial.packagescan must be the first mercurial module imported
+import mercurial.packagescan
 import mercurial.version
 
 # py2exe needs to be installed to work
@@ -36,7 +38,6 @@
     # Due to the use of demandload py2exe is not finding the modules.
     # packagescan.getmodules creates a list of modules included in
     # the mercurial package plus depdent modules.
-    import mercurial.packagescan
     from py2exe.build_exe import py2exe as build_exe
 
     class py2exe_for_demandload(build_exe):
@@ -54,10 +55,10 @@
                 self.includes = []
             else:
                 self.includes = self.includes.split(',')
-            self.includes += mercurial.packagescan.getmodules(self.build_lib,
-                                                              'mercurial')
-            self.includes += mercurial.packagescan.getmodules(self.build_lib,
-                                                              'hgext')
+            mercurial.packagescan.scan(self.build_lib,'mercurial')
+            mercurial.packagescan.scan(self.build_lib,'mercurial/hgweb')
+            mercurial.packagescan.scan(self.build_lib,'hgext')
+            self.includes += mercurial.packagescan.getmodules()
             build_exe.finalize_options(self)
 except ImportError:
     py2exe_for_demandload = None
@@ -85,7 +86,7 @@
         url='http://selenic.com/mercurial',
         description='Scalable distributed SCM',
         license='GNU GPL',
-        packages=['mercurial', 'hgext'],
+        packages=['mercurial', 'mercurial.hgweb', 'hgext'],
         ext_modules=[Extension('mercurial.mpatch', ['mercurial/mpatch.c']),
                     Extension('mercurial.bdiff', ['mercurial/bdiff.c'])],
         data_files=[('mercurial/templates',
--- a/tests/test-backout.out	Mon May 22 16:47:40 2006 +0200
+++ b/tests/test-backout.out	Thu Jun 01 13:47:34 2006 -0700
@@ -1,19 +1,19 @@
 # basic operation
 adding a
-changeset 2:c86754337410 backs out changeset 1:a820f4f40a57
+changeset 2:b38a34ddfd9f backs out changeset 1:a820f4f40a57
 a
 # file that was removed is recreated
 adding a
 adding a
-changeset 2:d2d961bd79f2 backs out changeset 1:76862dcce372
+changeset 2:44cd84c7349a backs out changeset 1:76862dcce372
 content
 # backout of backout is as if nothing happened
 removing a
-changeset 3:8a7eeb5ab5ce backs out changeset 2:d2d961bd79f2
+changeset 3:0dd8a0ed5e99 backs out changeset 2:44cd84c7349a
 cat: a: No such file or directory
 # backout with merge
 adding a
-changeset 3:3c9e845b409c backs out changeset 1:314f55b1bf23
+changeset 3:6c77ecc28460 backs out changeset 1:314f55b1bf23
 merging with changeset 2:b66ea5b77abb
 merging a
 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
--- a/tests/test-command-template.out	Mon May 22 16:47:40 2006 +0200
+++ b/tests/test-command-template.out	Thu Jun 01 13:47:34 2006 -0700
@@ -6,40 +6,40 @@
 43a46
 > files:       
 # compact style works
-3[tip]   8c7f028fbabf   1970-01-16 01:06 +0000   person
+3[tip]   10e46f2dcbf4   1970-01-16 01:06 +0000   person
   no user, no domain
 
-2   259081bc29d1   1970-01-14 21:20 +0000   other
+2   97054abb4ab8   1970-01-14 21:20 +0000   other
   no person
 
-1   1c37ba774509   1970-01-13 17:33 +0000   other
+1   b608e9d1a3f0   1970-01-13 17:33 +0000   other
   other 1
 
-0   6eb5362d59ec   1970-01-12 13:46 +0000   user
+0   1e4e1b8f71e0   1970-01-12 13:46 +0000   user
   line 1
 
-3[tip]   8c7f028fbabf   1970-01-16 01:06 +0000   person
+3[tip]   10e46f2dcbf4   1970-01-16 01:06 +0000   person
   no user, no domain
 
-2   259081bc29d1   1970-01-14 21:20 +0000   other
+2   97054abb4ab8   1970-01-14 21:20 +0000   other
   no person
 
-1   1c37ba774509   1970-01-13 17:33 +0000   other
+1   b608e9d1a3f0   1970-01-13 17:33 +0000   other
   other 1
 
-0   6eb5362d59ec   1970-01-12 13:46 +0000   user
+0   1e4e1b8f71e0   1970-01-12 13:46 +0000   user
   line 1
 
-3[tip]:2,-1   8c7f028fbabf   1970-01-16 01:06 +0000   person
+3[tip]:2,-1   10e46f2dcbf4   1970-01-16 01:06 +0000   person
   no user, no domain
 
-2:1,-1   259081bc29d1   1970-01-14 21:20 +0000   other
+2:1,-1   97054abb4ab8   1970-01-14 21:20 +0000   other
   no person
 
-1:0,-1   1c37ba774509   1970-01-13 17:33 +0000   other
+1:0,-1   b608e9d1a3f0   1970-01-13 17:33 +0000   other
   other 1
 
-0:-1,-1   6eb5362d59ec   1970-01-12 13:46 +0000   user
+0:-1,-1   1e4e1b8f71e0   1970-01-12 13:46 +0000   user
   line 1
 
 # error if style not readable
@@ -103,30 +103,24 @@
 other 2
 
 other 3
-
 desc: line 1
 line 2
-
 desc--verbose: no user, no domain
 desc--verbose: no person
 desc--verbose: other 1
 other 2
 
 other 3
-
 desc--verbose: line 1
 line 2
-
 desc--debug: no user, no domain
 desc--debug: no person
 desc--debug: other 1
 other 2
 
 other 3
-
 desc--debug: line 1
 line 2
-
 file_adds: 
 file_adds: 
 file_adds: 
@@ -175,18 +169,18 @@
 manifest--debug: 2:6e0e82995c35
 manifest--debug: 1:4e8d705b1e53
 manifest--debug: 0:a0c8bcbbb45c
-node: 8c7f028fbabf93fde80ef788885370b36abeff33
-node: 259081bc29d176c6ae17af5dd01a3440b3288c97
-node: 1c37ba7745099d0f206b3a663abcfe127b037433
-node: 6eb5362d59ec784e4431d3e140c8cc6e1b77ce82
-node--verbose: 8c7f028fbabf93fde80ef788885370b36abeff33
-node--verbose: 259081bc29d176c6ae17af5dd01a3440b3288c97
-node--verbose: 1c37ba7745099d0f206b3a663abcfe127b037433
-node--verbose: 6eb5362d59ec784e4431d3e140c8cc6e1b77ce82
-node--debug: 8c7f028fbabf93fde80ef788885370b36abeff33
-node--debug: 259081bc29d176c6ae17af5dd01a3440b3288c97
-node--debug: 1c37ba7745099d0f206b3a663abcfe127b037433
-node--debug: 6eb5362d59ec784e4431d3e140c8cc6e1b77ce82
+node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
+node: 97054abb4ab824450e9164180baf491ae0078465
+node: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
+node: 1e4e1b8f71e05681d422154f5421e385fec3454f
+node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
+node--verbose: 97054abb4ab824450e9164180baf491ae0078465
+node--verbose: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
+node--verbose: 1e4e1b8f71e05681d422154f5421e385fec3454f
+node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
+node--debug: 97054abb4ab824450e9164180baf491ae0078465
+node--debug: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
+node--debug: 1e4e1b8f71e05681d422154f5421e385fec3454f
 parents: 
 parents: 
 parents: 
@@ -195,9 +189,9 @@
 parents--verbose: 
 parents--verbose: 
 parents--verbose: 
-parents--debug: 2:259081bc29d1 -1:000000000000 
-parents--debug: 1:1c37ba774509 -1:000000000000 
-parents--debug: 0:6eb5362d59ec -1:000000000000 
+parents--debug: 2:97054abb4ab8 -1:000000000000 
+parents--debug: 1:b608e9d1a3f0 -1:000000000000 
+parents--debug: 0:1e4e1b8f71e0 -1:000000000000 
 parents--debug: -1:000000000000 -1:000000000000 
 rev: 3
 rev: 2
@@ -252,10 +246,10 @@
 no person
 other 1
 line 1
-8c7f028fbabf
-259081bc29d1
-1c37ba774509
-6eb5362d59ec
+10e46f2dcbf4
+97054abb4ab8
+b608e9d1a3f0
+1e4e1b8f71e0
 # error on syntax
 abort: t:3: unmatched quotes
 # done
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-empty-group	Thu Jun 01 13:47:34 2006 -0700
@@ -0,0 +1,49 @@
+#!/bin/sh
+#
+#  A          B    
+#  
+#  3  4       3    
+#  |\/|       |\   
+#  |/\|       | \  
+#  1  2       1  2 
+#  \ /        \ /  
+#   0          0
+#
+# if the result of the merge of 1 and 2
+# is the same in 3 and 4, no new manifest
+# will be created and the manifest group
+# will be empty during the pull
+#
+# (plus we test a failure where outgoing
+# wrongly reported the number of csets)
+#
+
+hg init a
+cd a
+touch init
+hg ci -A -m 0 -d "1000000 0"
+touch x y
+hg ci -A -m 1 -d "1000000 0"
+hg update 0
+touch x y
+hg ci -A -m 2 -d "1000000 0"
+hg merge 1
+hg ci -A -m m1 -d "1000000 0"
+#hg log
+#hg debugindex .hg/00manifest.i
+hg update -C 1
+hg merge 2
+hg ci -A -m m2 -d "1000000 0"
+#hg log
+#hg debugindex .hg/00manifest.i
+
+cd ..
+hg clone -r 3 a b
+hg clone -r 4 a c
+hg -R a outgoing b
+hg -R a outgoing c
+hg -R b outgoing c
+hg -R c outgoing b
+
+hg -R b pull a
+hg -R c pull a
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-empty-group.out	Thu Jun 01 13:47:34 2006 -0700
@@ -0,0 +1,72 @@
+adding init
+adding x
+adding y
+0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+adding x
+adding y
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+(branch merge, don't forget to commit)
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+(branch merge, don't forget to commit)
+requesting all changes
+adding changesets
+adding manifests
+adding file changes
+added 4 changesets with 3 changes to 3 files
+3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+requesting all changes
+adding changesets
+adding manifests
+adding file changes
+added 4 changesets with 3 changes to 3 files
+3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+searching for changes
+changeset:   4:fdb3c546e859
+tag:         tip
+parent:      1:1f703b3fcbc6
+parent:      2:de997049e034
+user:        test
+date:        Mon Jan 12 13:46:40 1970 +0000
+summary:     m2
+
+searching for changes
+changeset:   3:f40f830c0024
+parent:      2:de997049e034
+parent:      1:1f703b3fcbc6
+user:        test
+date:        Mon Jan 12 13:46:40 1970 +0000
+summary:     m1
+
+searching for changes
+changeset:   3:f40f830c0024
+tag:         tip
+parent:      2:de997049e034
+parent:      1:1f703b3fcbc6
+user:        test
+date:        Mon Jan 12 13:46:40 1970 +0000
+summary:     m1
+
+searching for changes
+changeset:   3:fdb3c546e859
+tag:         tip
+parent:      1:1f703b3fcbc6
+parent:      2:de997049e034
+user:        test
+date:        Mon Jan 12 13:46:40 1970 +0000
+summary:     m2
+
+pulling from a
+searching for changes
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 0 changes to 0 files (+1 heads)
+(run 'hg heads' to see heads, 'hg merge' to merge)
+pulling from a
+searching for changes
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 0 changes to 0 files (+1 heads)
+(run 'hg heads' to see heads, 'hg merge' to merge)
--- a/tests/test-globalopts	Mon May 22 16:47:40 2006 +0200
+++ b/tests/test-globalopts	Thu Jun 01 13:47:34 2006 -0700
@@ -62,7 +62,7 @@
 hg --cwd a --time tip 2>&1 | grep '^Time:' | sed 's/[0-9][0-9]*/x/g'
 
 echo %% --version
-hg --version -q | sed 's/version [a-f0-9+]*/version xxx/'
+hg --version -q | sed 's/version \([a-f0-9+]*\|unknown\)/version xxx/'
 
 echo %% -h/--help
 hg -h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-http-proxy	Thu Jun 01 13:47:34 2006 -0700
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+hg init a
+cd a
+echo a > a
+hg ci -Ama -d '1123456789 0'
+hg serve -p 20059 -d --pid-file=hg.pid
+
+cd ..
+("$TESTDIR/tinyproxy.py" 20060 localhost >/dev/null 2>&1 </dev/null &
+echo $! > proxy.pid)
+sleep 2
+
+echo %% url for proxy
+http_proxy=http://localhost:20060/ hg --config http_proxy.always=True clone http://localhost:20059/ b
+
+echo %% host:port for proxy
+http_proxy=localhost:20060 hg clone --config http_proxy.always=True http://localhost:20059/ c
+
+echo %% proxy url with user name and password
+http_proxy=http://user:passwd@localhost:20060 hg clone --config http_proxy.always=True http://localhost:20059/ d
+
+echo %% url with user name and password
+http_proxy=http://user:passwd@localhost:20060 hg clone --config http_proxy.always=True http://user:passwd@localhost:20059/ e
+
+echo %% bad host:port for proxy
+http_proxy=localhost:20061 hg clone --config http_proxy.always=True http://localhost:20059/ f
+
+kill $(cat proxy.pid a/hg.pid)
+exit 0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-http-proxy.out	Thu Jun 01 13:47:34 2006 -0700
@@ -0,0 +1,31 @@
+adding a
+%% url for proxy
+requesting all changes
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 1 changes to 1 files
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+%% host:port for proxy
+requesting all changes
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 1 changes to 1 files
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+%% proxy url with user name and password
+requesting all changes
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 1 changes to 1 files
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+%% url with user name and password
+requesting all changes
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 1 changes to 1 files
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+%% bad host:port for proxy
+abort: error: Connection refused
--- a/tests/test-remove	Mon May 22 16:47:40 2006 +0200
+++ b/tests/test-remove	Thu Jun 01 13:47:34 2006 -0700
@@ -3,6 +3,7 @@
 hg init a
 cd a
 echo a > foo
+hg rm foo
 hg add foo
 hg commit -m 1 -d "1000000 0"
 hg remove
@@ -17,5 +18,15 @@
 hg log -p -r 0
 hg log -p -r 1
 
+echo a > a
+hg add a
+hg rm a
+hg rm -f a
+echo b > b
+hg ci -A -m 3 -d "1000001 0"
+echo c >> b
+hg rm b
+hg rm -f b
+
 cd ..
 hg clone a b
--- a/tests/test-remove.out	Mon May 22 16:47:40 2006 +0200
+++ b/tests/test-remove.out	Thu Jun 01 13:47:34 2006 -0700
@@ -1,8 +1,10 @@
+not removing foo: file is not managed
 abort: no files specified
 undeleting foo
 removing foo
 # HG changeset patch
 # User test
+# Date 1000000 0
 # Node ID 8ba83d44753d6259db5ce6524974dd1174e90f47
 # Parent  0000000000000000000000000000000000000000
 1
@@ -14,6 +16,7 @@
 +a
 # HG changeset patch
 # User test
+# Date 1000000 0
 # Node ID a1fce69c50d97881c5c014ab23f580f720c78678
 # Parent  8ba83d44753d6259db5ce6524974dd1174e90f47
 2
@@ -48,4 +51,8 @@
 -a
 
 
-0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+not removing a: file has been marked for add (use -f to force removal)
+adding a
+adding b
+not removing b: file is modified (use -f to force removal)
+2 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- a/tests/test-tags	Mon May 22 16:47:40 2006 +0200
+++ b/tests/test-tags	Thu Jun 01 13:47:34 2006 -0700
@@ -32,12 +32,31 @@
 hg status
 
 hg commit -m "merge" -d "1000000 0"
+
+# create fake head, make sure tag not visible afterwards
+cp .hgtags tags
+hg tag -d "1000000 0" last
+hg rm .hgtags
+hg commit -m "remove" -d "1000000 0"
+
+mv tags .hgtags
+hg add .hgtags
+hg commit -m "readd" -d "1000000 0"
+
+hg tags
+
 # invalid tags
 echo "spam" >> .hgtags
 echo >> .hgtags
 echo "foo bar" >> .hgtags
 echo "$T invalid" | sed "s/..../a5a5/" >> .hg/localtags
 hg commit -m "tags" -d "1000000 0"
+
+# report tag parse error on other head
+hg up 3
+echo 'x y' >> .hgtags
+hg commit -m "head" -d "1000000 0"
+
 hg tags
 hg tip
 
--- a/tests/test-tags.out	Mon May 22 16:47:40 2006 +0200
+++ b/tests/test-tags.out	Thu Jun 01 13:47:34 2006 -0700
@@ -16,17 +16,26 @@
 (branch merge, don't forget to commit)
 8216907a933d+8a3ca90d111d+ tip
 M .hgtags
-.hgtags:2: ignoring invalid tag
-.hgtags:4: ignoring invalid tag
-localtags:1: ignoring invalid tag
-tip                                4:fd868a874787a7b5af31e1675666ce691c803035
+tip                                6:c6af9d771a81bb9c7f267ec03491224a9f8ba1cd
 first                              0:0acdaf8983679e0aac16e811534eb49d7ee1f2b4
-changeset:   4:fd868a874787
-.hgtags:2: ignoring invalid tag
-.hgtags:4: ignoring invalid tag
-localtags:1: ignoring invalid tag
+.hgtags (rev 7:39bba1bbbc4c), line 2: cannot parse entry
+.hgtags (rev 7:39bba1bbbc4c), line 4: node 'foo' is not well formed
+localtags, line 1: tag 'invalid' refers to unknown node
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+.hgtags (rev 7:39bba1bbbc4c), line 2: cannot parse entry
+.hgtags (rev 7:39bba1bbbc4c), line 4: node 'foo' is not well formed
+.hgtags (rev 8:4ca6f1b1a68c), line 2: node 'x' is not well formed
+localtags, line 1: tag 'invalid' refers to unknown node
+tip                                8:4ca6f1b1a68c77be687a03aaeb1614671ba59b20
+first                              0:0acdaf8983679e0aac16e811534eb49d7ee1f2b4
+changeset:   8:4ca6f1b1a68c
+.hgtags (rev 7:39bba1bbbc4c), line 2: cannot parse entry
+.hgtags (rev 7:39bba1bbbc4c), line 4: node 'foo' is not well formed
+.hgtags (rev 8:4ca6f1b1a68c), line 2: node 'x' is not well formed
+localtags, line 1: tag 'invalid' refers to unknown node
 tag:         tip
+parent:      3:b2ef3841386b
 user:        test
 date:        Mon Jan 12 13:46:40 1970 +0000
-summary:     tags
+summary:     head
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/tinyproxy.py	Thu Jun 01 13:47:34 2006 -0700
@@ -0,0 +1,132 @@
+#!/usr/bin/env python
+
+__doc__ = """Tiny HTTP Proxy.
+
+This module implements GET, HEAD, POST, PUT and DELETE methods
+on BaseHTTPServer, and behaves as an HTTP proxy.  The CONNECT
+method is also implemented experimentally, but has not been
+tested yet.
+
+Any help will be greatly appreciated.           SUZUKI Hisao
+"""
+
+__version__ = "0.2.1"
+
+import BaseHTTPServer, select, socket, SocketServer, urlparse
+
+class ProxyHandler (BaseHTTPServer.BaseHTTPRequestHandler):
+    __base = BaseHTTPServer.BaseHTTPRequestHandler
+    __base_handle = __base.handle
+
+    server_version = "TinyHTTPProxy/" + __version__
+    rbufsize = 0                        # self.rfile Be unbuffered
+
+    def handle(self):
+        (ip, port) =  self.client_address
+        if hasattr(self, 'allowed_clients') and ip not in self.allowed_clients:
+            self.raw_requestline = self.rfile.readline()
+            if self.parse_request(): self.send_error(403)
+        else:
+            self.__base_handle()
+
+    def _connect_to(self, netloc, soc):
+        i = netloc.find(':')
+        if i >= 0:
+            host_port = netloc[:i], int(netloc[i+1:])
+        else:
+            host_port = netloc, 80
+        print "\t" "connect to %s:%d" % host_port
+        try: soc.connect(host_port)
+        except socket.error, arg:
+            try: msg = arg[1]
+            except: msg = arg
+            self.send_error(404, msg)
+            return 0
+        return 1
+
+    def do_CONNECT(self):
+        soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        try:
+            if self._connect_to(self.path, soc):
+                self.log_request(200)
+                self.wfile.write(self.protocol_version +
+                                 " 200 Connection established\r\n")
+                self.wfile.write("Proxy-agent: %s\r\n" % self.version_string())
+                self.wfile.write("\r\n")
+                self._read_write(soc, 300)
+        finally:
+            print "\t" "bye"
+            soc.close()
+            self.connection.close()
+
+    def do_GET(self):
+        (scm, netloc, path, params, query, fragment) = urlparse.urlparse(
+            self.path, 'http')
+        if scm != 'http' or fragment or not netloc:
+            self.send_error(400, "bad url %s" % self.path)
+            return
+        soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        try:
+            if self._connect_to(netloc, soc):
+                self.log_request()
+                soc.send("%s %s %s\r\n" % (
+                    self.command,
+                    urlparse.urlunparse(('', '', path, params, query, '')),
+                    self.request_version))
+                self.headers['Connection'] = 'close'
+                del self.headers['Proxy-Connection']
+                for key_val in self.headers.items():
+                    soc.send("%s: %s\r\n" % key_val)
+                soc.send("\r\n")
+                self._read_write(soc)
+        finally:
+            print "\t" "bye"
+            soc.close()
+            self.connection.close()
+
+    def _read_write(self, soc, max_idling=20):
+        iw = [self.connection, soc]
+        ow = []
+        count = 0
+        while 1:
+            count += 1
+            (ins, _, exs) = select.select(iw, ow, iw, 3)
+            if exs: break
+            if ins:
+                for i in ins:
+                    if i is soc:
+                        out = self.connection
+                    else:
+                        out = soc
+                    data = i.recv(8192)
+                    if data:
+                        out.send(data)
+                        count = 0
+            else:
+                print "\t" "idle", count
+            if count == max_idling: break
+
+    do_HEAD = do_GET
+    do_POST = do_GET
+    do_PUT  = do_GET
+    do_DELETE=do_GET
+
+class ThreadingHTTPServer (SocketServer.ThreadingMixIn,
+                           BaseHTTPServer.HTTPServer): pass
+
+if __name__ == '__main__':
+    from sys import argv
+    if argv[1:] and argv[1] in ('-h', '--help'):
+        print argv[0], "[port [allowed_client_name ...]]"
+    else:
+        if argv[2:]:
+            allowed = []
+            for name in argv[2:]:
+                client = socket.gethostbyname(name)
+                allowed.append(client)
+                print "Accept: %s (%s)" % (client, name)
+            ProxyHandler.allowed_clients = allowed
+            del argv[2:]
+        else:
+            print "Any clients will be served..."
+        BaseHTTPServer.test(ProxyHandler, ThreadingHTTPServer)