changeset 2324:7cbe8cd69d6b

merge with crew.
author Vadim Gelfer <vadim.gelfer@gmail.com>
date Fri, 19 May 2006 08:57:12 -0700
parents c58a403aa830 (current diff) d9ca698e3c5a (diff)
children c4ea7f927dab
files mercurial/hgweb.py setup.py
diffstat 21 files changed, 334 insertions(+), 180 deletions(-) [+]
line wrap: on
line diff
--- a/contrib/mercurial.el
+++ b/contrib/mercurial.el
@@ -382,14 +382,27 @@ Handle frickin' frackin' gratuitous even
       (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 @@ With a prefix argument, prompt for the p
     (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."
--- a/contrib/win32/ReadMe.html
+++ b/contrib/win32/ReadMe.html
@@ -89,6 +89,16 @@ hg
     <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
+++ b/doc/hgrc.5.txt
@@ -36,11 +36,14 @@ installed.
     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
--- a/hgext/bugzilla.py
+++ b/hgext/bugzilla.py
@@ -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 @@ class bugzilla_2_16(object):
         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 @@ class bugzilla_2_16(object):
             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)
--- a/hgext/hbisect.py
+++ b/hgext/hbisect.py
@@ -173,7 +173,7 @@ class bisect(object):
                 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))
+                "The first bad revision is: %s\n" % hg.hex(self.badrev))
             sys.exit(0)
         self.ui.write("%d revisions left\n" % tot)
         best_rev = None
--- a/hgext/mq.py
+++ b/hgext/mq.py
@@ -102,6 +102,7 @@ class queue:
         message = []
         comments = []
         user = None
+        date = None
         format = None
         subject = None
         diffstart = 0
@@ -119,6 +120,8 @@ class queue:
                 # 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 @@ class queue:
                 # 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 @@ class queue:
         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 @@ class queue:
             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 @@ class queue:
             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 @@ class queue:
             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 @@ class queue:
         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/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -1392,6 +1392,7 @@ def doexport(ui, repo, changeset, seqno,
 
     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 +1688,7 @@ def import_(ui, repo, patch1, *patches, 
 
         message = []
         user = None
+        date = None
         hgpatch = False
         for line in file(pf):
             line = line.rstrip()
@@ -1703,27 +1705,29 @@ def import_(ui, repo, patch1, *patches, 
                 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 +2189,42 @@ def remove(ui, repo, *pats, **opts):
     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
--- a/mercurial/demandload.py
+++ b/mercurial/demandload.py
@@ -81,6 +81,10 @@ class _replacer_from(_replacer):
 
         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.
 
rename from mercurial/hgweb.py
rename to mercurial/hgweb/__init__.py
--- a/mercurial/hgweb.py
+++ b/mercurial/hgweb/__init__.py
@@ -8,12 +8,12 @@
 
 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 _
+from mercurial.demandload import demandload
+demandload(globals(), "time re socket zlib errno ConfigParser tempfile")
+demandload(globals(), "StringIO BaseHTTPServer SocketServer urllib")
+demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,templater")
+from mercurial.node import *
+from mercurial.i18n import gettext as _
 
 def splitURI(uri):
     """ Return path and query splited from uri
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -166,37 +166,44 @@ class localrepository(object):
                     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
 
@@ -550,12 +557,15 @@ class localrepository(object):
             # 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)
--- a/mercurial/manifest.py
+++ b/mercurial/manifest.py
@@ -43,48 +43,61 @@ class manifest(revlog):
     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 @@ class manifest(revlog):
             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/util.py
+++ b/mercurial/util.py
@@ -94,7 +94,7 @@ def patch(strip, patchname, ui):
     """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 opener(base, audit=True):
         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
+++ b/mercurial/util_win32.py
@@ -194,7 +194,7 @@ def user_rcpath():
         # 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
+++ b/setup.py
@@ -56,6 +56,7 @@ try:
             else:
                 self.includes = self.includes.split(',')
             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)
@@ -85,7 +86,7 @@ setup(name='mercurial',
         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
+++ b/tests/test-backout.out
@@ -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
+++ b/tests/test-command-template.out
@@ -6,40 +6,40 @@ 29a31
 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 @@ desc: other 1
 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: 3:cb5a1327723b
 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--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 user, no domain
 no person
 other 1
 line 1
-8c7f028fbabf
-259081bc29d1
-1c37ba774509
-6eb5362d59ec
+10e46f2dcbf4
+97054abb4ab8
+b608e9d1a3f0
+1e4e1b8f71e0
 # error on syntax
 abort: t:3: unmatched quotes
 # done
--- a/tests/test-globalopts
+++ b/tests/test-globalopts
@@ -62,7 +62,7 @@ echo %% --time
 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
--- a/tests/test-remove
+++ b/tests/test-remove
@@ -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 export 1
 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
+++ b/tests/test-remove.out
@@ -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 @@ diff -r 000000000000 -r 8ba83d44753d foo
 +a
 # HG changeset patch
 # User test
+# Date 1000000 0
 # Node ID a1fce69c50d97881c5c014ab23f580f720c78678
 # Parent  8ba83d44753d6259db5ce6524974dd1174e90f47
 2
@@ -48,4 +51,8 @@ diff -r 8ba83d44753d -r a1fce69c50d9 foo
 -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
+++ b/tests/test-tags
@@ -32,12 +32,31 @@ hg id
 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
+++ b/tests/test-tags.out
@@ -16,17 +16,26 @@ 1 files updated, 0 files merged, 0 files
 (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