merge with crew.
--- a/contrib/mercurial.el Fri May 19 08:54:28 2006 -0700
+++ b/contrib/mercurial.el Fri May 19 08:57:12 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."
--- a/contrib/win32/ReadMe.html Fri May 19 08:54:28 2006 -0700
+++ b/contrib/win32/ReadMe.html Fri May 19 08:57:12 2006 -0700
@@ -89,6 +89,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 Fri May 19 08:54:28 2006 -0700
+++ b/doc/hgrc.5.txt Fri May 19 08:57:12 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
--- a/hgext/bugzilla.py Fri May 19 08:54:28 2006 -0700
+++ b/hgext/bugzilla.py Fri May 19 08:57:12 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)
--- a/hgext/hbisect.py Fri May 19 08:54:28 2006 -0700
+++ b/hgext/hbisect.py Fri May 19 08:57:12 2006 -0700
@@ -173,7 +173,7 @@
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 Fri May 19 08:54:28 2006 -0700
+++ b/hgext/mq.py Fri May 19 08:57:12 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/mercurial/commands.py Fri May 19 08:54:28 2006 -0700
+++ b/mercurial/commands.py Fri May 19 08:57:12 2006 -0700
@@ -1392,6 +1392,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 +1688,7 @@
message = []
user = None
+ date = None
hgpatch = False
for line in file(pf):
line = line.rstrip()
@@ -1703,27 +1705,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 +2189,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
--- a/mercurial/demandload.py Fri May 19 08:54:28 2006 -0700
+++ b/mercurial/demandload.py Fri May 19 08:57:12 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 Fri May 19 08:54:28 2006 -0700
+++ /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 Fri May 19 08:57:12 2006 -0700
@@ -0,0 +1,1142 @@
+# 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 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
+
+ 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)))
--- a/mercurial/localrepo.py Fri May 19 08:54:28 2006 -0700
+++ b/mercurial/localrepo.py Fri May 19 08:57:12 2006 -0700
@@ -166,37 +166,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
@@ -550,12 +557,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)
--- a/mercurial/manifest.py Fri May 19 08:54:28 2006 -0700
+++ b/mercurial/manifest.py Fri May 19 08:57:12 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/util.py Fri May 19 08:54:28 2006 -0700
+++ b/mercurial/util.py Fri May 19 08:57:12 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 Fri May 19 08:54:28 2006 -0700
+++ b/mercurial/util_win32.py Fri May 19 08:57:12 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 Fri May 19 08:54:28 2006 -0700
+++ b/setup.py Fri May 19 08:57:12 2006 -0700
@@ -56,6 +56,7 @@
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 @@
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 Fri May 19 08:54:28 2006 -0700
+++ b/tests/test-backout.out Fri May 19 08:57:12 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 Fri May 19 08:54:28 2006 -0700
+++ b/tests/test-command-template.out Fri May 19 08:57:12 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
--- a/tests/test-globalopts Fri May 19 08:54:28 2006 -0700
+++ b/tests/test-globalopts Fri May 19 08:57:12 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
--- a/tests/test-remove Fri May 19 08:54:28 2006 -0700
+++ b/tests/test-remove Fri May 19 08:57:12 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 Fri May 19 08:54:28 2006 -0700
+++ b/tests/test-remove.out Fri May 19 08:57:12 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 Fri May 19 08:54:28 2006 -0700
+++ b/tests/test-tags Fri May 19 08:57:12 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 Fri May 19 08:54:28 2006 -0700
+++ b/tests/test-tags.out Fri May 19 08:57:12 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