# HG changeset patch # User Martin Geisler # Date 1253137566 -7200 # Node ID 812aaef4075795ee8b8bb9dbf228832f7ae0df89 # Parent 2388ba07449b38d7e1d6fcb64f4b9d4586211fe8# Parent 61e30f257c15cd4f7f1d66d8eae338b5c70b964b Merge with main diff -r 2388ba07449b -r 812aaef40757 doc/Makefile --- a/doc/Makefile Wed Sep 16 16:11:44 2009 +0200 +++ b/doc/Makefile Wed Sep 16 23:46:06 2009 +0200 @@ -5,8 +5,7 @@ MANDIR=$(PREFIX)/share/man INSTALL=install -c -m 644 PYTHON=python -RST2HTML=rst2html -RST2MAN=rst2man +RST2HTML=$(shell which rst2html 2> /dev/null || which rst2html.py) all: man html @@ -18,20 +17,18 @@ touch hg.1.txt hg.1.gendoc.txt: gendoc.py ../mercurial/commands.py ../mercurial/help.py - ${PYTHON} gendoc.py > $@ + ${PYTHON} gendoc.py > $@.tmp + mv $@.tmp $@ %: %.txt common.txt - # add newline after all literal blocks and fix backslash escape - $(RST2MAN) $*.txt \ - | sed -e 's/^\.fi$$/.fi\n/' \ - | sed -e 's/\\fB\\\\fP/\\fB\\e\\fP/' \ - > $* + $(PYTHON) rst2man.py --halt warning \ + --strip-elements-with-class htmlonly $*.txt $* %.html: %.txt common.txt - $(RST2HTML) $*.txt > $*.html + $(RST2HTML) --halt warning $*.txt $*.html MANIFEST: man html - # tracked files are already in the main MANIFEST +# tracked files are already in the main MANIFEST $(RM) $@ for i in $(MAN) $(HTML) hg.1.gendoc.txt; do \ echo "doc/$$i" >> $@ ; \ diff -r 2388ba07449b -r 812aaef40757 doc/README --- a/doc/README Wed Sep 16 16:11:44 2009 +0200 +++ b/doc/README Wed Sep 16 23:46:06 2009 +0200 @@ -4,14 +4,8 @@ http://docutils.sourceforge.net/rst.html It's also convertible to a variety of other formats including standard -UNIX man page format and HTML. - -To do this, you'll need to install the rst2html and rst2man tools, -which are part of Docutils: +UNIX man page format and HTML. You'll need to install Docutils: http://docutils.sourceforge.net/ -The rst2man tool is still in their so-called "sandbox". The above page -has links to tarballs of both Docutils and their sandbox. - Use the Makefile in this directory to generate the man and HTML pages. diff -r 2388ba07449b -r 812aaef40757 doc/hg.1.txt --- a/doc/hg.1.txt Wed Sep 16 16:11:44 2009 +0200 +++ b/doc/hg.1.txt Wed Sep 16 23:46:06 2009 +0200 @@ -11,6 +11,10 @@ :Manual section: 1 :Manual group: Mercurial Manual +.. contents:: + :backlinks: top + :class: htmlonly + SYNOPSIS -------- diff -r 2388ba07449b -r 812aaef40757 doc/hgrc.5.txt --- a/doc/hgrc.5.txt Wed Sep 16 16:11:44 2009 +0200 +++ b/doc/hgrc.5.txt Wed Sep 16 23:46:06 2009 +0200 @@ -11,6 +11,10 @@ :Manual section: 5 :Manual group: Mercurial Manual +.. contents:: + :backlinks: top + :class: htmlonly + SYNOPSIS -------- @@ -100,12 +104,12 @@ Lines beginning with "``#``" or "``;``" are ignored and may be used to provide comments. -A line of the form "`%include file`" will include `file` into the +A line of the form "``%include file``" will include ``file`` into the current configuration file. The inclusion is recursive, which means that included files can include other files. Filenames are relative to -the configuration file in which the `%include` directive is found. +the configuration file in which the ``%include`` directive is found. -A line with "`%unset name`" will remove `name` from the current +A line with "``%unset name``" will remove ``name`` from the current section, if it has been set previously. @@ -882,6 +886,10 @@ the list of repositories. The contents of the deny_read list have priority over (are examined before) the contents of the allow_read list. +``descend`` + hgwebdir indexes will not descend into subdirectories. Only repositories + directly in the current path will be shown (other repositories are still + available from the index corresponding to their containing path). ``description`` Textual description of the repository's purpose or contents. Default is "unknown". diff -r 2388ba07449b -r 812aaef40757 doc/rst2man.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/rst2man.py Wed Sep 16 23:46:06 2009 +0200 @@ -0,0 +1,1112 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: manpage.py 6110 2009-08-31 14:40:33Z grubert $ +# Author: Engelbert Gruber +# Copyright: This module is put into the public domain. + +""" +Simple man page writer for reStructuredText. + +Man pages (short for "manual pages") contain system documentation on unix-like +systems. The pages are grouped in numbered sections: + + 1 executable programs and shell commands + 2 system calls + 3 library functions + 4 special files + 5 file formats + 6 games + 7 miscellaneous + 8 system administration + +Man pages are written *troff*, a text file formatting system. + +See http://www.tldp.org/HOWTO/Man-Page for a start. + +Man pages have no subsection only parts. +Standard parts + + NAME , + SYNOPSIS , + DESCRIPTION , + OPTIONS , + FILES , + SEE ALSO , + BUGS , + +and + + AUTHOR . + +A unix-like system keeps an index of the DESCRIPTIONs, which is accesable +by the command whatis or apropos. + +""" + +__docformat__ = 'reStructuredText' + +import sys +import os +import time +import re +from types import ListType + +import docutils +from docutils import nodes, utils, writers, languages +import roman + +FIELD_LIST_INDENT = 7 +DEFINITION_LIST_INDENT = 7 +OPTION_LIST_INDENT = 7 +BLOCKQOUTE_INDENT = 3.5 + +# Define two macros so man/roff can calculate the +# indent/unindent margins by itself +MACRO_DEF = (r""". +.nr rst2man-indent-level 0 +. +.de1 rstReportMargin +\\$1 \\n[an-margin] +level \\n[rst2man-indent-level] +level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] +- +\\n[rst2man-indent0] +\\n[rst2man-indent1] +\\n[rst2man-indent2] +.. +.de1 INDENT +.\" .rstReportMargin pre: +. RS \\$1 +. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] +. nr rst2man-indent-level +1 +.\" .rstReportMargin post: +.. +.de UNINDENT +. RE +.\" indent \\n[an-margin] +.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] +.nr rst2man-indent-level -1 +.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] +.in \\n[rst2man-indent\\n[rst2man-indent-level]]u +.. +""") + +class Writer(writers.Writer): + + supported = ('manpage') + """Formats this writer supports.""" + + output = None + """Final translated form of `document`.""" + + def __init__(self): + writers.Writer.__init__(self) + self.translator_class = Translator + + def translate(self): + visitor = self.translator_class(self.document) + self.document.walkabout(visitor) + self.output = visitor.astext() + + +class Table: + def __init__(self): + self._rows = [] + self._options = ['center', ] + self._tab_char = '\t' + self._coldefs = [] + def new_row(self): + self._rows.append([]) + def append_separator(self, separator): + """Append the separator for table head.""" + self._rows.append([separator]) + def append_cell(self, cell_lines): + """cell_lines is an array of lines""" + start = 0 + if len(cell_lines)>0 and cell_lines[0] == '.sp\n': + start = 1 + self._rows[-1].append(cell_lines[start:]) + if len(self._coldefs) < len(self._rows[-1]): + self._coldefs.append('l') + def _minimize_cell(self, cell_lines): + """Remove leading and trailing blank and ``.sp`` lines""" + while (cell_lines and cell_lines[0] in ('\n', '.sp\n')): + del cell_lines[0] + while (cell_lines and cell_lines[-1] in ('\n', '.sp\n')): + del cell_lines[-1] + def as_list(self): + text = ['.TS\n'] + text.append(' '.join(self._options) + ';\n') + text.append('|%s|.\n' % ('|'.join(self._coldefs))) + for row in self._rows: + # row = array of cells. cell = array of lines. + text.append('_\n') # line above + text.append('T{\n') + for i in range(len(row)): + cell = row[i] + self._minimize_cell(cell) + text.extend(cell) + if not text[-1].endswith('\n'): + text[-1] += '\n' + if i < len(row)-1: + text.append('T}'+self._tab_char+'T{\n') + else: + text.append('T}\n') + text.append('_\n') + text.append('.TE\n') + return text + +class Translator(nodes.NodeVisitor): + """""" + + words_and_spaces = re.compile(r'\S+| +|\n') + document_start = """Man page generated from reStructeredText.""" + + def __init__(self, document): + nodes.NodeVisitor.__init__(self, document) + self.settings = settings = document.settings + lcode = settings.language_code + self.language = languages.get_language(lcode) + self.head = [] + self.body = [] + self.foot = [] + self.section_level = 0 + self.context = [] + self.topic_class = '' + self.colspecs = [] + self.compact_p = 1 + self.compact_simple = None + # the list style "*" bullet or "#" numbered + self._list_char = [] + # writing the header .TH and .SH NAME is postboned after + # docinfo. + self._docinfo = { + "title" : "", "title_upper": "", + "subtitle" : "", + "manual_section" : "", "manual_group" : "", + "author" : [], + "date" : "", + "copyright" : "", + "version" : "", + } + self._docinfo_keys = [] # a list to keep the sequence as in source. + self._docinfo_names = {} # to get name from text not normalized. + self._in_docinfo = None + self._active_table = None + self._in_literal = False + self.header_written = 0 + self._line_block = 0 + self.authors = [] + self.section_level = 0 + self._indent = [0] + # central definition of simple processing rules + # what to output on : visit, depart + # Do not use paragraph requests ``.PP`` because these set indentation. + # use ``.sp``. Remove superfluous ``.sp`` in ``astext``. + # + # Fonts are put on a stack, the top one is used. + # ``.ft P`` or ``\\fP`` pop from stack. + # ``B`` bold, ``I`` italic, ``R`` roman should be available. + # Hopefully ``C`` courier too. + self.defs = { + 'indent' : ('.INDENT %.1f\n', '.UNINDENT\n'), + 'definition_list_item' : ('.TP', ''), + 'field_name' : ('.TP\n.B ', '\n'), + 'literal' : ('\\fC', '\\fP'), + 'literal_block' : ('.sp\n.nf\n.ft C\n', '\n.ft P\n.fi\n'), + + 'option_list_item' : ('.TP\n', ''), + + 'reference' : (r'\fI\%', r'\fP'), + 'emphasis': ('\\fI', '\\fP'), + 'strong' : ('\\fB', '\\fP'), + 'term' : ('\n.B ', '\n'), + 'title_reference' : ('\\fI', '\\fP'), + + 'topic-title' : ('.SS ', ), + 'sidebar-title' : ('.SS ', ), + + 'problematic' : ('\n.nf\n', '\n.fi\n'), + } + # NOTE dont specify the newline before a dot-command, but ensure + # it is there. + + def comment_begin(self, text): + """Return commented version of the passed text WITHOUT end of + line/comment.""" + prefix = '.\\" ' + out_text = ''.join( + [(prefix + in_line + '\n') + for in_line in text.split('\n')]) + return out_text + + def comment(self, text): + """Return commented version of the passed text.""" + return self.comment_begin(text)+'.\n' + + def ensure_eol(self): + """Ensure the last line in body is terminated by new line.""" + if self.body[-1][-1] != '\n': + self.body.append('\n') + + def astext(self): + """Return the final formatted document as a string.""" + if not self.header_written: + # ensure we get a ".TH" as viewers require it. + self.head.append(self.header()) + # filter body + for i in xrange(len(self.body)-1,0,-1): + # remove superfluous vertical gaps. + if self.body[i] == '.sp\n': + if self.body[i-1][:4] in ('.BI ','.IP '): + self.body[i] = '.\n' + elif (self.body[i-1][:3] == '.B ' and + self.body[i-2][:4] == '.TP\n'): + self.body[i] = '.\n' + elif (self.body[i-1] == '\n' and + self.body[i-2][0] != '.' and + (self.body[i-3][:7] == '.TP\n.B ' + or self.body[i-3][:4] == '\n.B ') + ): + self.body[i] = '.\n' + return ''.join(self.head + self.body + self.foot) + + def deunicode(self, text): + text = text.replace(u'\xa0', '\\ ') + text = text.replace(u'\u2020', '\\(dg') + return text + + def visit_Text(self, node): + text = node.astext() + text = text.replace('\\','\\e') + replace_pairs = [ + (u'-', ur'\-'), + (u'\'', ur'\(aq'), + (u'ยด', ur'\''), + (u'`', ur'\(ga'), + ] + for (in_char, out_markup) in replace_pairs: + text = text.replace(in_char, out_markup) + # unicode + text = self.deunicode(text) + if self._in_literal: + # prevent interpretation of "." at line start + if text[0] == '.': + text = '\\&' + text + text = text.replace('\n.', '\n\\&.') + self.body.append(text) + + def depart_Text(self, node): + pass + + def list_start(self, node): + class enum_char: + enum_style = { + 'bullet' : '\\(bu', + 'emdash' : '\\(em', + } + + def __init__(self, style): + self._style = style + if node.has_key('start'): + self._cnt = node['start'] - 1 + else: + self._cnt = 0 + self._indent = 2 + if style == 'arabic': + # indentation depends on number of childrens + # and start value. + self._indent = len(str(len(node.children))) + self._indent += len(str(self._cnt)) + 1 + elif style == 'loweralpha': + self._cnt += ord('a') - 1 + self._indent = 3 + elif style == 'upperalpha': + self._cnt += ord('A') - 1 + self._indent = 3 + elif style.endswith('roman'): + self._indent = 5 + + def next(self): + if self._style == 'bullet': + return self.enum_style[self._style] + elif self._style == 'emdash': + return self.enum_style[self._style] + self._cnt += 1 + # TODO add prefix postfix + if self._style == 'arabic': + return "%d." % self._cnt + elif self._style in ('loweralpha', 'upperalpha'): + return "%c." % self._cnt + elif self._style.endswith('roman'): + res = roman.toRoman(self._cnt) + '.' + if self._style.startswith('upper'): + return res.upper() + return res.lower() + else: + return "%d." % self._cnt + def get_width(self): + return self._indent + def __repr__(self): + return 'enum_style-%s' % list(self._style) + + if node.has_key('enumtype'): + self._list_char.append(enum_char(node['enumtype'])) + else: + self._list_char.append(enum_char('bullet')) + if len(self._list_char) > 1: + # indent nested lists + self.indent(self._list_char[-2].get_width()) + else: + self.indent(self._list_char[-1].get_width()) + + def list_end(self): + self.dedent() + self._list_char.pop() + + def header(self): + tmpl = (".TH %(title_upper)s %(manual_section)s" + " \"%(date)s\" \"%(version)s\" \"%(manual_group)s\"\n" + ".SH NAME\n" + "%(title)s \- %(subtitle)s\n") + return tmpl % self._docinfo + + def append_header(self): + """append header with .TH and .SH NAME""" + # NOTE before everything + # .TH title_upper section date source manual + if self.header_written: + return + self.body.append(self.header()) + self.body.append(MACRO_DEF) + self.header_written = 1 + + def visit_address(self, node): + self.visit_docinfo_item(node, 'address') + + def depart_address(self, node): + pass + + def visit_admonition(self, node, name=None): + if name: + self.body.append('.IP %s\n' % + self.language.labels.get(name, name)) + + def depart_admonition(self, node): + self.body.append('.RE\n') + + def visit_attention(self, node): + self.visit_admonition(node, 'attention') + + depart_attention = depart_admonition + + def visit_docinfo_item(self, node, name): + if name == 'author': + self._docinfo[name].append(node.astext()) + else: + self._docinfo[name] = node.astext() + self._docinfo_keys.append(name) + raise nodes.SkipNode + + def depart_docinfo_item(self, node): + pass + + def visit_author(self, node): + self.visit_docinfo_item(node, 'author') + + depart_author = depart_docinfo_item + + def visit_authors(self, node): + # _author is called anyway. + pass + + def depart_authors(self, node): + pass + + def visit_block_quote(self, node): + # BUG/HACK: indent alway uses the _last_ indention, + # thus we need two of them. + self.indent(BLOCKQOUTE_INDENT) + self.indent(0) + + def depart_block_quote(self, node): + self.dedent() + self.dedent() + + def visit_bullet_list(self, node): + self.list_start(node) + + def depart_bullet_list(self, node): + self.list_end() + + def visit_caption(self, node): + pass + + def depart_caption(self, node): + pass + + def visit_caution(self, node): + self.visit_admonition(node, 'caution') + + depart_caution = depart_admonition + + def visit_citation(self, node): + num,text = node.astext().split(None,1) + num = num.strip() + self.body.append('.IP [%s] 5\n' % num) + + def depart_citation(self, node): + pass + + def visit_citation_reference(self, node): + self.body.append('['+node.astext()+']') + raise nodes.SkipNode + + def visit_classifier(self, node): + pass + + def depart_classifier(self, node): + pass + + def visit_colspec(self, node): + self.colspecs.append(node) + + def depart_colspec(self, node): + pass + + def write_colspecs(self): + self.body.append("%s.\n" % ('L '*len(self.colspecs))) + + def visit_comment(self, node, + sub=re.compile('-(?=-)').sub): + self.body.append(self.comment(node.astext())) + raise nodes.SkipNode + + def visit_contact(self, node): + self.visit_docinfo_item(node, 'contact') + + depart_contact = depart_docinfo_item + + def visit_container(self, node): + pass + + def depart_container(self, node): + pass + + def visit_compound(self, node): + pass + + def depart_compound(self, node): + pass + + def visit_copyright(self, node): + self.visit_docinfo_item(node, 'copyright') + + def visit_danger(self, node): + self.visit_admonition(node, 'danger') + + depart_danger = depart_admonition + + def visit_date(self, node): + self.visit_docinfo_item(node, 'date') + + def visit_decoration(self, node): + pass + + def depart_decoration(self, node): + pass + + def visit_definition(self, node): + pass + + def depart_definition(self, node): + pass + + def visit_definition_list(self, node): + self.indent(DEFINITION_LIST_INDENT) + + def depart_definition_list(self, node): + self.dedent() + + def visit_definition_list_item(self, node): + self.body.append(self.defs['definition_list_item'][0]) + + def depart_definition_list_item(self, node): + self.body.append(self.defs['definition_list_item'][1]) + + def visit_description(self, node): + pass + + def depart_description(self, node): + pass + + def visit_docinfo(self, node): + self._in_docinfo = 1 + + def depart_docinfo(self, node): + self._in_docinfo = None + # NOTE nothing should be written before this + self.append_header() + + def visit_doctest_block(self, node): + self.body.append(self.defs['literal_block'][0]) + self._in_literal = True + + def depart_doctest_block(self, node): + self._in_literal = False + self.body.append(self.defs['literal_block'][1]) + + def visit_document(self, node): + # no blank line between comment and header. + self.body.append(self.comment(self.document_start).rstrip()+'\n') + # writing header is postboned + self.header_written = 0 + + def depart_document(self, node): + if self._docinfo['author']: + self.body.append('.SH AUTHOR\n%s\n' + % ', '.join(self._docinfo['author'])) + skip = ('author', 'copyright', 'date', + 'manual_group', 'manual_section', + 'subtitle', + 'title', 'title_upper', 'version') + for name in self._docinfo_keys: + if name == 'address': + self.body.append("\n%s:\n%s%s.nf\n%s\n.fi\n%s%s" % ( + self.language.labels.get(name, name), + self.defs['indent'][0] % 0, + self.defs['indent'][0] % BLOCKQOUTE_INDENT, + self._docinfo[name], + self.defs['indent'][1], + self.defs['indent'][1], + ) ) + elif not name in skip: + if name in self._docinfo_names: + label = self._docinfo_names[name] + else: + label = self.language.labels.get(name, name) + self.body.append("\n%s: %s\n" % (label, self._docinfo[name]) ) + if self._docinfo['copyright']: + self.body.append('.SH COPYRIGHT\n%s\n' + % self._docinfo['copyright']) + self.body.append( self.comment( + 'Generated by docutils manpage writer.\n' ) ) + + def visit_emphasis(self, node): + self.body.append(self.defs['emphasis'][0]) + + def depart_emphasis(self, node): + self.body.append(self.defs['emphasis'][1]) + + def visit_entry(self, node): + # a cell in a table row + if 'morerows' in node: + self.document.reporter.warning('"table row spanning" not supported', + base_node=node) + if 'morecols' in node: + self.document.reporter.warning( + '"table cell spanning" not supported', base_node=node) + self.context.append(len(self.body)) + + def depart_entry(self, node): + start = self.context.pop() + self._active_table.append_cell(self.body[start:]) + del self.body[start:] + + def visit_enumerated_list(self, node): + self.list_start(node) + + def depart_enumerated_list(self, node): + self.list_end() + + def visit_error(self, node): + self.visit_admonition(node, 'error') + + depart_error = depart_admonition + + def visit_field(self, node): + pass + + def depart_field(self, node): + pass + + def visit_field_body(self, node): + if self._in_docinfo: + name_normalized = self._field_name.lower().replace(" ","_") + self._docinfo_names[name_normalized] = self._field_name + self.visit_docinfo_item(node, name_normalized) + raise nodes.SkipNode + + def depart_field_body(self, node): + pass + + def visit_field_list(self, node): + self.indent(FIELD_LIST_INDENT) + + def depart_field_list(self, node): + self.dedent() + + def visit_field_name(self, node): + if self._in_docinfo: + self._field_name = node.astext() + raise nodes.SkipNode + else: + self.body.append(self.defs['field_name'][0]) + + def depart_field_name(self, node): + self.body.append(self.defs['field_name'][1]) + + def visit_figure(self, node): + self.indent(2.5) + self.indent(0) + + def depart_figure(self, node): + self.dedent() + self.dedent() + + def visit_footer(self, node): + self.document.reporter.warning('"footer" not supported', + base_node=node) + + def depart_footer(self, node): + pass + + def visit_footnote(self, node): + num,text = node.astext().split(None,1) + num = num.strip() + self.body.append('.IP [%s] 5\n' % self.deunicode(num)) + + def depart_footnote(self, node): + pass + + def footnote_backrefs(self, node): + self.document.reporter.warning('"footnote_backrefs" not supported', + base_node=node) + + def visit_footnote_reference(self, node): + self.body.append('['+self.deunicode(node.astext())+']') + raise nodes.SkipNode + + def depart_footnote_reference(self, node): + pass + + def visit_generated(self, node): + pass + + def depart_generated(self, node): + pass + + def visit_header(self, node): + raise NotImplementedError, node.astext() + + def depart_header(self, node): + pass + + def visit_hint(self, node): + self.visit_admonition(node, 'hint') + + depart_hint = depart_admonition + + def visit_subscript(self, node): + self.body.append('\\s-2\\d') + + def depart_subscript(self, node): + self.body.append('\\u\\s0') + + def visit_superscript(self, node): + self.body.append('\\s-2\\u') + + def depart_superscript(self, node): + self.body.append('\\d\\s0') + + def visit_attribution(self, node): + self.body.append('\\(em ') + + def depart_attribution(self, node): + self.body.append('\n') + + def visit_image(self, node): + self.document.reporter.warning('"image" not supported', + base_node=node) + text = [] + if 'alt' in node.attributes: + text.append(node.attributes['alt']) + if 'uri' in node.attributes: + text.append(node.attributes['uri']) + self.body.append('[image: %s]\n' % ('/'.join(text))) + raise nodes.SkipNode + + def visit_important(self, node): + self.visit_admonition(node, 'important') + + depart_important = depart_admonition + + def visit_label(self, node): + # footnote and citation + if (isinstance(node.parent, nodes.footnote) + or isinstance(node.parent, nodes.citation)): + raise nodes.SkipNode + self.document.reporter.warning('"unsupported "label"', + base_node=node) + self.body.append('[') + + def depart_label(self, node): + self.body.append(']\n') + + def visit_legend(self, node): + pass + + def depart_legend(self, node): + pass + + # WHAT should we use .INDENT, .UNINDENT ? + def visit_line_block(self, node): + self._line_block += 1 + if self._line_block == 1: + self.body.append('.nf\n') + else: + self.body.append('.in +2\n') + + def depart_line_block(self, node): + self._line_block -= 1 + if self._line_block == 0: + self.body.append('.fi\n') + self.body.append('.sp\n') + else: + self.body.append('.in -2\n') + + def visit_line(self, node): + pass + + def depart_line(self, node): + self.body.append('\n') + + def visit_list_item(self, node): + # man 7 man argues to use ".IP" instead of ".TP" + self.body.append('.IP %s %d\n' % ( + self._list_char[-1].next(), + self._list_char[-1].get_width(),) ) + + def depart_list_item(self, node): + pass + + def visit_literal(self, node): + self.body.append(self.defs['literal'][0]) + + def depart_literal(self, node): + self.body.append(self.defs['literal'][1]) + + def visit_literal_block(self, node): + self.body.append(self.defs['literal_block'][0]) + self._in_literal = True + + def depart_literal_block(self, node): + self._in_literal = False + self.body.append(self.defs['literal_block'][1]) + + def visit_meta(self, node): + raise NotImplementedError, node.astext() + + def depart_meta(self, node): + pass + + def visit_note(self, node): + self.visit_admonition(node, 'note') + + depart_note = depart_admonition + + def indent(self, by=0.5): + # if we are in a section ".SH" there already is a .RS + step = self._indent[-1] + self._indent.append(by) + self.body.append(self.defs['indent'][0] % step) + + def dedent(self): + self._indent.pop() + self.body.append(self.defs['indent'][1]) + + def visit_option_list(self, node): + self.indent(OPTION_LIST_INDENT) + + def depart_option_list(self, node): + self.dedent() + + def visit_option_list_item(self, node): + # one item of the list + self.body.append(self.defs['option_list_item'][0]) + + def depart_option_list_item(self, node): + self.body.append(self.defs['option_list_item'][1]) + + def visit_option_group(self, node): + # as one option could have several forms it is a group + # options without parameter bold only, .B, -v + # options with parameter bold italic, .BI, -f file + # + # we do not know if .B or .BI + self.context.append('.B') # blind guess + self.context.append(len(self.body)) # to be able to insert later + self.context.append(0) # option counter + + def depart_option_group(self, node): + self.context.pop() # the counter + start_position = self.context.pop() + text = self.body[start_position:] + del self.body[start_position:] + self.body.append('%s%s\n' % (self.context.pop(), ''.join(text))) + + def visit_option(self, node): + # each form of the option will be presented separately + if self.context[-1]>0: + self.body.append(', ') + if self.context[-3] == '.BI': + self.body.append('\\') + self.body.append(' ') + + def depart_option(self, node): + self.context[-1] += 1 + + def visit_option_string(self, node): + # do not know if .B or .BI + pass + + def depart_option_string(self, node): + pass + + def visit_option_argument(self, node): + self.context[-3] = '.BI' # bold/italic alternate + if node['delimiter'] != ' ': + self.body.append('\\fB%s ' % node['delimiter'] ) + elif self.body[len(self.body)-1].endswith('='): + # a blank only means no blank in output, just changing font + self.body.append(' ') + else: + # blank backslash blank, switch font then a blank + self.body.append(' \\ ') + + def depart_option_argument(self, node): + pass + + def visit_organization(self, node): + self.visit_docinfo_item(node, 'organization') + + def depart_organization(self, node): + pass + + def visit_paragraph(self, node): + # ``.PP`` : Start standard indented paragraph. + # ``.LP`` : Start block paragraph, all except the first. + # ``.P [type]`` : Start paragraph type. + # NOTE dont use paragraph starts because they reset indentation. + # ``.sp`` is only vertical space + self.ensure_eol() + self.body.append('.sp\n') + + def depart_paragraph(self, node): + self.body.append('\n') + + def visit_problematic(self, node): + self.body.append(self.defs['problematic'][0]) + + def depart_problematic(self, node): + self.body.append(self.defs['problematic'][1]) + + def visit_raw(self, node): + if node.get('format') == 'manpage': + self.body.append(node.astext() + "\n") + # Keep non-manpage raw text out of output: + raise nodes.SkipNode + + def visit_reference(self, node): + """E.g. link or email address.""" + self.body.append(self.defs['reference'][0]) + + def depart_reference(self, node): + self.body.append(self.defs['reference'][1]) + + def visit_revision(self, node): + self.visit_docinfo_item(node, 'revision') + + depart_revision = depart_docinfo_item + + def visit_row(self, node): + self._active_table.new_row() + + def depart_row(self, node): + pass + + def visit_section(self, node): + self.section_level += 1 + + def depart_section(self, node): + self.section_level -= 1 + + def visit_status(self, node): + self.visit_docinfo_item(node, 'status') + + depart_status = depart_docinfo_item + + def visit_strong(self, node): + self.body.append(self.defs['strong'][0]) + + def depart_strong(self, node): + self.body.append(self.defs['strong'][1]) + + def visit_substitution_definition(self, node): + """Internal only.""" + raise nodes.SkipNode + + def visit_substitution_reference(self, node): + self.document.reporter.warning('"substitution_reference" not supported', + base_node=node) + + def visit_subtitle(self, node): + if isinstance(node.parent, nodes.sidebar): + self.body.append(self.defs['strong'][0]) + elif isinstance(node.parent, nodes.document): + self.visit_docinfo_item(node, 'subtitle') + elif isinstance(node.parent, nodes.section): + self.body.append(self.defs['strong'][0]) + + def depart_subtitle(self, node): + # document subtitle calls SkipNode + self.body.append(self.defs['strong'][1]+'\n.PP\n') + + def visit_system_message(self, node): + # TODO add report_level + #if node['level'] < self.document.reporter['writer'].report_level: + # Level is too low to display: + # raise nodes.SkipNode + attr = {} + backref_text = '' + if node.hasattr('id'): + attr['name'] = node['id'] + if node.hasattr('line'): + line = ', line %s' % node['line'] + else: + line = '' + self.body.append('.IP "System Message: %s/%s (%s:%s)"\n' + % (node['type'], node['level'], node['source'], line)) + + def depart_system_message(self, node): + pass + + def visit_table(self, node): + self._active_table = Table() + + def depart_table(self, node): + self.ensure_eol() + self.body.extend(self._active_table.as_list()) + self._active_table = None + + def visit_target(self, node): + # targets are in-document hyper targets, without any use for man-pages. + raise nodes.SkipNode + + def visit_tbody(self, node): + pass + + def depart_tbody(self, node): + pass + + def visit_term(self, node): + self.body.append(self.defs['term'][0]) + + def depart_term(self, node): + self.body.append(self.defs['term'][1]) + + def visit_tgroup(self, node): + pass + + def depart_tgroup(self, node): + pass + + def visit_thead(self, node): + # MAYBE double line '=' + pass + + def depart_thead(self, node): + # MAYBE double line '=' + pass + + def visit_tip(self, node): + self.visit_admonition(node, 'tip') + + depart_tip = depart_admonition + + def visit_title(self, node): + if isinstance(node.parent, nodes.topic): + self.body.append(self.defs['topic-title'][0]) + elif isinstance(node.parent, nodes.sidebar): + self.body.append(self.defs['sidebar-title'][0]) + elif isinstance(node.parent, nodes.admonition): + self.body.append('.IP "') + elif self.section_level == 0: + self._docinfo['title'] = node.astext() + # document title for .TH + self._docinfo['title_upper'] = node.astext().upper() + raise nodes.SkipNode + elif self.section_level == 1: + self.body.append('.SH ') + else: + self.body.append('.SS ') + + def depart_title(self, node): + if isinstance(node.parent, nodes.admonition): + self.body.append('"') + self.body.append('\n') + + def visit_title_reference(self, node): + """inline citation reference""" + self.body.append(self.defs['title_reference'][0]) + + def depart_title_reference(self, node): + self.body.append(self.defs['title_reference'][1]) + + def visit_topic(self, node): + pass + + def depart_topic(self, node): + pass + + def visit_sidebar(self, node): + pass + + def depart_sidebar(self, node): + pass + + def visit_rubric(self, node): + pass + + def depart_rubric(self, node): + pass + + def visit_transition(self, node): + # .PP Begin a new paragraph and reset prevailing indent. + # .sp N leaves N lines of blank space. + # .ce centers the next line + self.body.append('\n.sp\n.ce\n----\n') + + def depart_transition(self, node): + self.body.append('\n.ce 0\n.sp\n') + + def visit_version(self, node): + self.visit_docinfo_item(node, 'version') + + def visit_warning(self, node): + self.visit_admonition(node, 'warning') + + depart_warning = depart_admonition + + def unimplemented_visit(self, node): + raise NotImplementedError('visiting unimplemented node type: %s' + % node.__class__.__name__) + +# The following part is taken from the Docutils rst2man.py script: +if __name__ == "__main__": + from docutils.core import publish_cmdline, default_description + description = ("Generates plain unix manual documents. " + + default_description) + publish_cmdline(writer=Writer(), description=description) + +# vim: set fileencoding=utf-8 et ts=4 ai : diff -r 2388ba07449b -r 812aaef40757 hgext/churn.py --- a/hgext/churn.py Wed Sep 16 16:11:44 2009 +0200 +++ b/hgext/churn.py Wed Sep 16 23:46:06 2009 +0200 @@ -53,15 +53,17 @@ if opts.get('date'): df = util.matchdate(opts['date']) - get = util.cachefunc(lambda r: repo[r].changeset()) + get = util.cachefunc(lambda r: repo[r]) changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts) for st, rev, fns in changeiter: + if not st == 'add': continue - if df and not df(get(rev)[2][0]): # doesn't match date format + + ctx = get(rev) + if df and not df(ctx.date()[0]): # doesn't match date format continue - ctx = repo[rev] key = getkey(ctx) key = amap.get(key, key) # alias remap if opts.get('changesets'): @@ -147,8 +149,8 @@ rate.sort(key=sortkey) # Be careful not to have a zero maxcount (issue833) - maxcount = float(max([v for k, v in rate])) or 1.0 - maxname = max([len(k) for k, v in rate]) + maxcount = float(max(v for k, v in rate)) or 1.0 + maxname = max(len(k) for k, v in rate) ttywidth = util.termwidth() ui.debug(_("assuming %i character terminal\n") % ttywidth) diff -r 2388ba07449b -r 812aaef40757 hgext/color.py --- a/hgext/color.py Wed Sep 16 16:11:44 2009 +0200 +++ b/hgext/color.py Wed Sep 16 23:46:06 2009 +0200 @@ -59,7 +59,6 @@ ''' import os, sys -import itertools from mercurial import cmdutil, commands, extensions, error from mercurial.i18n import _ @@ -146,7 +145,7 @@ patchlines = ui.popbuffer().splitlines() patchnames = repo.mq.series - for patch, patchname in itertools.izip(patchlines, patchnames): + for patch, patchname in zip(patchlines, patchnames): if opts['missing']: effects = _patch_effects['missing'] # Determine if patch is applied. @@ -219,12 +218,8 @@ 'changed': ['white'], 'trailingwhitespace': ['bold', 'red_background']} -_ui = None - def uisetup(ui): '''Initialize the extension.''' - global _ui - _ui = ui _setupcmd(ui, 'diff', commands.table, colordiff, _diff_effects) _setupcmd(ui, 'incoming', commands.table, None, _diff_effects) _setupcmd(ui, 'log', commands.table, None, _diff_effects) @@ -232,17 +227,10 @@ _setupcmd(ui, 'tip', commands.table, None, _diff_effects) _setupcmd(ui, 'status', commands.table, colorstatus, _status_effects) -def extsetup(): try: mq = extensions.find('mq') - try: - # If we are loaded after mq, we must wrap commands.table - _setupcmd(_ui, 'qdiff', commands.table, colordiff, _diff_effects) - _setupcmd(_ui, 'qseries', commands.table, colorqseries, _patch_effects) - except error.UnknownCommand: - # Otherwise we wrap mq.cmdtable - _setupcmd(_ui, 'qdiff', mq.cmdtable, colordiff, _diff_effects) - _setupcmd(_ui, 'qseries', mq.cmdtable, colorqseries, _patch_effects) + _setupcmd(ui, 'qdiff', mq.cmdtable, colordiff, _diff_effects) + _setupcmd(ui, 'qseries', mq.cmdtable, colorqseries, _patch_effects) except KeyError: # The mq extension is not enabled pass diff -r 2388ba07449b -r 812aaef40757 hgext/convert/common.py --- a/hgext/convert/common.py Wed Sep 16 16:11:44 2009 +0200 +++ b/hgext/convert/common.py Wed Sep 16 23:46:06 2009 +0200 @@ -203,6 +203,8 @@ """Put tags into sink. tags: {tagname: sink_rev_id, ...} where tagname is an UTF-8 string. + Return a pair (tag_revision, tag_parent_revision), or (None, None) + if nothing was changed. """ raise NotImplementedError() diff -r 2388ba07449b -r 812aaef40757 hgext/convert/convcmd.py --- a/hgext/convert/convcmd.py Wed Sep 16 16:11:44 2009 +0200 +++ b/hgext/convert/convcmd.py Wed Sep 16 23:46:06 2009 +0200 @@ -336,11 +336,14 @@ ctags[k] = self.map[v] if c and ctags: - nrev = self.dest.puttags(ctags) - # write another hash correspondence to override the previous - # one so we don't end up with extra tag heads - if nrev: - self.map[c] = nrev + nrev, tagsparent = self.dest.puttags(ctags) + if nrev and tagsparent: + # write another hash correspondence to override the previous + # one so we don't end up with extra tag heads + tagsparents = [e for e in self.map.iteritems() + if e[1] == tagsparent] + if tagsparents: + self.map[tagsparents[0][0]] = nrev self.writeauthormap() finally: diff -r 2388ba07449b -r 812aaef40757 hgext/convert/gnuarch.py --- a/hgext/convert/gnuarch.py Wed Sep 16 16:11:44 2009 +0200 +++ b/hgext/convert/gnuarch.py Wed Sep 16 23:46:06 2009 +0200 @@ -284,7 +284,7 @@ self.changes[rev].summary = self.recode(self.changes[rev].summary) # Commit revision origin when dealing with a branch or tag - if catlog.has_key('Continuation-of'): + if 'Continuation-of' in catlog: self.changes[rev].continuationof = self.recode(catlog['Continuation-of']) except Exception: raise util.Abort(_('could not parse cat-log of %s') % rev) diff -r 2388ba07449b -r 812aaef40757 hgext/convert/hg.py --- a/hgext/convert/hg.py Wed Sep 16 16:11:44 2009 +0200 +++ b/hgext/convert/hg.py Wed Sep 16 23:46:06 2009 +0200 @@ -189,7 +189,7 @@ newlines = sorted([("%s %s\n" % (tags[tag], tag)) for tag in tags]) if newlines == oldlines: - return None + return None, None data = "".join(newlines) def getfilectx(repo, memctx, f): return context.memfilectx(f, data, False, False, None) @@ -201,7 +201,7 @@ [".hgtags"], getfilectx, "convert-repo", date, extra) self.repo.commitctx(ctx) - return hex(self.repo.changelog.tip()) + return hex(self.repo.changelog.tip()), hex(tagparent) def setfilemapmode(self, active): self.filemapmode = active diff -r 2388ba07449b -r 812aaef40757 hgext/graphlog.py --- a/hgext/graphlog.py Wed Sep 16 16:11:44 2009 +0200 +++ b/hgext/graphlog.py Wed Sep 16 23:46:06 2009 +0200 @@ -22,48 +22,31 @@ ASCIIDATA = 'ASC' -def asciiformat(ui, repo, revdag, opts, parentrepo=None): - """formats a changelog DAG walk for ASCII output""" - if parentrepo is None: - parentrepo = repo - showparents = [ctx.node() for ctx in parentrepo[None].parents()] - displayer = show_changeset(ui, repo, opts, buffered=True) - for (id, type, ctx, parentids) in revdag: - if type != graphmod.CHANGESET: - continue - displayer.show(ctx) - lines = displayer.hunk.pop(ctx.rev()).split('\n')[:-1] - char = ctx.node() in showparents and '@' or 'o' - yield (id, ASCIIDATA, (char, lines), parentids) - -def asciiedges(nodes): +def asciiedges(seen, rev, parents): """adds edge info to changelog DAG walk suitable for ascii()""" - seen = [] - for node, type, data, parents in nodes: - if node not in seen: - seen.append(node) - nodeidx = seen.index(node) + if rev not in seen: + seen.append(rev) + nodeidx = seen.index(rev) + + knownparents = [] + newparents = [] + for parent in parents: + if parent in seen: + knownparents.append(parent) + else: + newparents.append(parent) - knownparents = [] - newparents = [] - for parent in parents: - if parent in seen: - knownparents.append(parent) - else: - newparents.append(parent) + ncols = len(seen) + seen[nodeidx:nodeidx + 1] = newparents + edges = [(nodeidx, seen.index(p)) for p in knownparents] - ncols = len(seen) - nextseen = seen[:] - nextseen[nodeidx:nodeidx + 1] = newparents - edges = [(nodeidx, nextseen.index(p)) for p in knownparents] + if len(newparents) > 0: + edges.append((nodeidx, nodeidx)) + if len(newparents) > 1: + edges.append((nodeidx, nodeidx + 1)) - if len(newparents) > 0: - edges.append((nodeidx, nodeidx)) - if len(newparents) > 1: - edges.append((nodeidx, nodeidx + 1)) - nmorecols = len(nextseen) - ncols - seen = nextseen - yield (nodeidx, type, data, edges, ncols, nmorecols) + nmorecols = len(seen) - ncols + return nodeidx, edges, ncols, nmorecols def fix_long_right_edges(edges): for (i, (start, end)) in enumerate(edges): @@ -117,11 +100,13 @@ line.extend(["|", " "] * (n_columns - ni - 1)) return line -def ascii(ui, dag): +def ascii(ui, base, type, char, text, coldata): """prints an ASCII graph of the DAG - dag is a generator that emits tuples with the following elements: + takes the following arguments (one call per node in the graph): + - ui to write to + - A list we can keep the needed state in - Column of the current node in the set of ongoing edges. - Type indicator of node data == ASCIIDATA. - Payload: (char, lines): @@ -135,91 +120,87 @@ in the current revision. That is: -1 means one column removed; 0 means no columns added or removed; 1 means one column added. """ - prev_n_columns_diff = 0 - prev_node_index = 0 - for (node_index, type, (node_ch, node_lines), edges, n_columns, n_columns_diff) in dag: - assert -2 < n_columns_diff < 2 - if n_columns_diff == -1: - # Transform - # - # | | | | | | - # o | | into o---+ - # |X / |/ / - # | | | | - fix_long_right_edges(edges) - - # add_padding_line says whether to rewrite + idx, edges, ncols, coldiff = coldata + assert -2 < coldiff < 2 + if coldiff == -1: + # Transform # - # | | | | | | | | - # | o---+ into | o---+ - # | / / | | | # <--- padding line - # o | | | / / - # o | | - add_padding_line = (len(node_lines) > 2 and - n_columns_diff == -1 and - [x for (x, y) in edges if x + 1 < y]) + # | | | | | | + # o | | into o---+ + # |X / |/ / + # | | | | + fix_long_right_edges(edges) + + # add_padding_line says whether to rewrite + # + # | | | | | | | | + # | o---+ into | o---+ + # | / / | | | # <--- padding line + # o | | | / / + # o | | + add_padding_line = (len(text) > 2 and coldiff == -1 and + [x for (x, y) in edges if x + 1 < y]) - # fix_nodeline_tail says whether to rewrite - # - # | | o | | | | o | | - # | | |/ / | | |/ / - # | o | | into | o / / # <--- fixed nodeline tail - # | |/ / | |/ / - # o | | o | | - fix_nodeline_tail = len(node_lines) <= 2 and not add_padding_line + # fix_nodeline_tail says whether to rewrite + # + # | | o | | | | o | | + # | | |/ / | | |/ / + # | o | | into | o / / # <--- fixed nodeline tail + # | |/ / | |/ / + # o | | o | | + fix_nodeline_tail = len(text) <= 2 and not add_padding_line - # nodeline is the line containing the node character (typically o) - nodeline = ["|", " "] * node_index - nodeline.extend([node_ch, " "]) + # nodeline is the line containing the node character (typically o) + nodeline = ["|", " "] * idx + nodeline.extend([char, " "]) - nodeline.extend( - get_nodeline_edges_tail( - node_index, prev_node_index, n_columns, n_columns_diff, - prev_n_columns_diff, fix_nodeline_tail)) + nodeline.extend( + get_nodeline_edges_tail(idx, base[1], ncols, coldiff, + base[0], fix_nodeline_tail)) - # shift_interline is the line containing the non-vertical - # edges between this entry and the next - shift_interline = ["|", " "] * node_index - if n_columns_diff == -1: - n_spaces = 1 - edge_ch = "/" - elif n_columns_diff == 0: - n_spaces = 2 - edge_ch = "|" - else: - n_spaces = 3 - edge_ch = "\\" - shift_interline.extend(n_spaces * [" "]) - shift_interline.extend([edge_ch, " "] * (n_columns - node_index - 1)) + # shift_interline is the line containing the non-vertical + # edges between this entry and the next + shift_interline = ["|", " "] * idx + if coldiff == -1: + n_spaces = 1 + edge_ch = "/" + elif coldiff == 0: + n_spaces = 2 + edge_ch = "|" + else: + n_spaces = 3 + edge_ch = "\\" + shift_interline.extend(n_spaces * [" "]) + shift_interline.extend([edge_ch, " "] * (ncols - idx - 1)) - # draw edges from the current node to its parents - draw_edges(edges, nodeline, shift_interline) + # draw edges from the current node to its parents + draw_edges(edges, nodeline, shift_interline) - # lines is the list of all graph lines to print - lines = [nodeline] - if add_padding_line: - lines.append(get_padding_line(node_index, n_columns, edges)) - lines.append(shift_interline) + # lines is the list of all graph lines to print + lines = [nodeline] + if add_padding_line: + lines.append(get_padding_line(idx, ncols, edges)) + lines.append(shift_interline) - # make sure that there are as many graph lines as there are - # log strings - while len(node_lines) < len(lines): - node_lines.append("") - if len(lines) < len(node_lines): - extra_interline = ["|", " "] * (n_columns + n_columns_diff) - while len(lines) < len(node_lines): - lines.append(extra_interline) + # make sure that there are as many graph lines as there are + # log strings + while len(text) < len(lines): + text.append("") + if len(lines) < len(text): + extra_interline = ["|", " "] * (ncols + coldiff) + while len(lines) < len(text): + lines.append(extra_interline) - # print lines - indentation_level = max(n_columns, n_columns + n_columns_diff) - for (line, logstr) in zip(lines, node_lines): - ln = "%-*s %s" % (2 * indentation_level, "".join(line), logstr) - ui.write(ln.rstrip() + '\n') + # print lines + indentation_level = max(ncols, ncols + coldiff) + for (line, logstr) in zip(lines, text): + ln = "%-*s %s" % (2 * indentation_level, "".join(line), logstr) + ui.write(ln.rstrip() + '\n') - # ... and start over - prev_node_index = node_index - prev_n_columns_diff = n_columns_diff + # ... and start over + base[0] = coldiff + base[1] = idx def get_revs(repo, rev_opt): if rev_opt: @@ -235,6 +216,14 @@ if op in opts and opts[op]: raise util.Abort(_("--graph option is incompatible with --%s") % op) +def generate(ui, dag, displayer, showparents, edgefn): + seen, base = [], [0, 0] + for rev, type, ctx, parents in dag: + char = ctx.node() in showparents and '@' or 'o' + displayer.show(ctx) + lines = displayer.hunk.pop(rev).split('\n')[:-1] + ascii(ui, base, type, char, lines, edgefn(seen, rev, parents)) + def graphlog(ui, repo, path=None, **opts): """show revision history alongside an ASCII revision graph @@ -259,8 +248,9 @@ else: revdag = graphmod.revisions(repo, start, stop) - fmtdag = asciiformat(ui, repo, revdag, opts) - ascii(ui, asciiedges(fmtdag)) + displayer = show_changeset(ui, repo, opts, buffered=True) + showparents = [ctx.node() for ctx in repo[None].parents()] + generate(ui, revdag, displayer, showparents, asciiedges) def graphrevs(repo, nodes, opts): limit = cmdutil.loglimit(opts) @@ -294,8 +284,9 @@ o = repo.changelog.nodesbetween(o, revs)[0] revdag = graphrevs(repo, o, opts) - fmtdag = asciiformat(ui, repo, revdag, opts) - ascii(ui, asciiedges(fmtdag)) + displayer = show_changeset(ui, repo, opts, buffered=True) + showparents = [ctx.node() for ctx in repo[None].parents()] + generate(ui, revdag, displayer, showparents, asciiedges) def gincoming(ui, repo, source="default", **opts): """show the incoming changesets alongside an ASCII revision graph @@ -343,8 +334,9 @@ chlist = other.changelog.nodesbetween(incoming, revs)[0] revdag = graphrevs(other, chlist, opts) - fmtdag = asciiformat(ui, other, revdag, opts, parentrepo=repo) - ascii(ui, asciiedges(fmtdag)) + displayer = show_changeset(ui, other, opts, buffered=True) + showparents = [ctx.node() for ctx in repo[None].parents()] + generate(ui, revdag, displayer, showparents, asciiedges) finally: if hasattr(other, 'close'): diff -r 2388ba07449b -r 812aaef40757 hgext/highlight/__init__.py --- a/hgext/highlight/__init__.py Wed Sep 16 16:11:44 2009 +0200 +++ b/hgext/highlight/__init__.py Wed Sep 16 23:46:06 2009 +0200 @@ -53,8 +53,9 @@ req.respond(common.HTTP_OK, 'text/css') return ['/* pygments_style = %s */\n\n' % pg_style, fmter.get_style_defs('')] -# monkeypatch in the new version -extensions.wrapfunction(webcommands, '_filerevision', filerevision_highlight) -extensions.wrapfunction(webcommands, 'annotate', annotate_highlight) -webcommands.highlightcss = generate_css -webcommands.__all__.append('highlightcss') +def extsetup(): + # monkeypatch in the new version + extensions.wrapfunction(webcommands, '_filerevision', filerevision_highlight) + extensions.wrapfunction(webcommands, 'annotate', annotate_highlight) + webcommands.highlightcss = generate_css + webcommands.__all__.append('highlightcss') diff -r 2388ba07449b -r 812aaef40757 hgext/highlight/highlight.py --- a/hgext/highlight/highlight.py Wed Sep 16 16:11:44 2009 +0200 +++ b/hgext/highlight/highlight.py Wed Sep 16 23:46:06 2009 +0200 @@ -32,26 +32,27 @@ if util.binary(text): return - # avoid UnicodeDecodeError in pygments - text = encoding.tolocal(text) + # Pygments is best used with Unicode strings: + # + text = text.decode(encoding.encoding, 'replace') # To get multi-line strings right, we can't format line-by-line try: - lexer = guess_lexer_for_filename(fctx.path(), text[:1024], - encoding=encoding.encoding) + lexer = guess_lexer_for_filename(fctx.path(), text[:1024]) except (ClassNotFound, ValueError): try: - lexer = guess_lexer(text[:1024], encoding=encoding.encoding) + lexer = guess_lexer(text[:1024]) except (ClassNotFound, ValueError): - lexer = TextLexer(encoding=encoding.encoding) + lexer = TextLexer() - formatter = HtmlFormatter(style=style, encoding=encoding.encoding) + formatter = HtmlFormatter(style=style) colorized = highlight(text, lexer, formatter) # strip wrapping div colorized = colorized[:colorized.find('\n')] colorized = colorized[colorized.find('
')+5:]
-    coloriter = iter(colorized.splitlines())
+    coloriter = (s.encode(encoding.encoding, 'replace')
+                 for s in colorized.splitlines())
 
     tmpl.filters['colorize'] = lambda x: coloriter.next()
 
diff -r 2388ba07449b -r 812aaef40757 hgext/inotify/__init__.py
--- a/hgext/inotify/__init__.py	Wed Sep 16 16:11:44 2009 +0200
+++ b/hgext/inotify/__init__.py	Wed Sep 16 23:46:06 2009 +0200
@@ -13,7 +13,6 @@
 from mercurial.i18n import _
 from mercurial import cmdutil, util
 import server
-from weakref import proxy
 from client import client, QueryFailed
 
 def serve(ui, repo, **opts):
@@ -25,7 +24,8 @@
     class service(object):
         def init(self):
             try:
-                self.master = server.master(ui, repo, timeout)
+                self.master = server.master(ui, repo.dirstate,
+                                            repo.root, timeout)
             except server.AlreadyStartedException, inst:
                 raise util.Abort(str(inst))
 
@@ -56,9 +56,6 @@
     if not hasattr(repo, 'dirstate'):
         return
 
-    # XXX: weakref until hg stops relying on __del__
-    repo = proxy(repo)
-
     class inotifydirstate(repo.dirstate.__class__):
 
         # We'll set this to false after an unsuccessful attempt so that
diff -r 2388ba07449b -r 812aaef40757 hgext/inotify/client.py
--- a/hgext/inotify/client.py	Wed Sep 16 16:11:44 2009 +0200
+++ b/hgext/inotify/client.py	Wed Sep 16 23:46:06 2009 +0200
@@ -29,12 +29,12 @@
             if err[0] == errno.ECONNREFUSED:
                 self.ui.warn(_('(found dead inotify server socket; '
                                'removing it)\n'))
-                os.unlink(self.repo.join('inotify.sock'))
+                os.unlink(os.path.join(self.root, '.hg', 'inotify.sock'))
             if err[0] in (errno.ECONNREFUSED, errno.ENOENT) and autostart:
                 self.ui.debug(_('(starting inotify server)\n'))
                 try:
                     try:
-                        server.start(self.ui, self.repo)
+                        server.start(self.ui, self.dirstate, self.root)
                     except server.AlreadyStartedException, inst:
                         # another process may have started its own
                         # inotify server while this one was starting.
@@ -64,11 +64,12 @@
 class client(object):
     def __init__(self, ui, repo):
         self.ui = ui
-        self.repo = repo
+        self.dirstate = repo.dirstate
+        self.root = repo.root
         self.sock = socket.socket(socket.AF_UNIX)
 
     def _connect(self):
-        sockpath = self.repo.join('inotify.sock')
+        sockpath = os.path.join(self.root, '.hg', 'inotify.sock')
         try:
             self.sock.connect(sockpath)
         except socket.error, err:
diff -r 2388ba07449b -r 812aaef40757 hgext/inotify/linux/_inotify.c
--- a/hgext/inotify/linux/_inotify.c	Wed Sep 16 16:11:44 2009 +0200
+++ b/hgext/inotify/linux/_inotify.c	Wed Sep 16 23:46:06 2009 +0200
@@ -106,13 +106,12 @@
 
 static PyObject *remove_watch(PyObject *self, PyObject *args)
 {
-    PyObject *ret = NULL;
     uint32_t wd;
     int fd;
     int r;
 
     if (!PyArg_ParseTuple(args, "iI:remove_watch", &fd, &wd))
-	goto bail;
+	return NULL;
 
     Py_BEGIN_ALLOW_THREADS
     r = inotify_rm_watch(fd, wd);
@@ -120,18 +119,11 @@
 
     if (r == -1) {
 	PyErr_SetFromErrno(PyExc_OSError);
-	goto bail;
+	return NULL;
     }
 
     Py_INCREF(Py_None);
-
-    goto done;
-
-bail:
-    Py_CLEAR(ret);
-
-done:
-    return ret;
+    return Py_None;
 }
 
 PyDoc_STRVAR(
diff -r 2388ba07449b -r 812aaef40757 hgext/inotify/server.py
--- a/hgext/inotify/server.py	Wed Sep 16 16:11:44 2009 +0200
+++ b/hgext/inotify/server.py	Wed Sep 16 23:46:06 2009 +0200
@@ -34,13 +34,11 @@
 
 walk_ignored_errors = (errno.ENOENT, errno.ENAMETOOLONG)
 
-def walkrepodirs(repo):
+def walkrepodirs(dirstate, absroot):
     '''Iterate over all subdirectories of this repo.
     Exclude the .hg directory, any nested repos, and ignored dirs.'''
-    rootslash = repo.root + os.sep
-
     def walkit(dirname, top):
-        fullpath = rootslash + dirname
+        fullpath = join(absroot, dirname)
         try:
             for name, kind in osutil.listdir(fullpath):
                 if kind == stat.S_IFDIR:
@@ -49,7 +47,7 @@
                             return
                     else:
                         d = join(dirname, name)
-                        if repo.dirstate._ignore(d):
+                        if dirstate._ignore(d):
                             continue
                         for subdir in walkit(d, False):
                             yield subdir
@@ -60,18 +58,16 @@
 
     return walkit('', True)
 
-def walk(repo, root):
+def walk(dirstate, absroot, root):
     '''Like os.walk, but only yields regular files.'''
 
     # This function is critical to performance during startup.
 
-    rootslash = repo.root + os.sep
-
     def walkit(root, reporoot):
         files, dirs = [], []
 
         try:
-            fullpath = rootslash + root
+            fullpath = join(absroot, root)
             for name, kind in osutil.listdir(fullpath):
                 if kind == stat.S_IFDIR:
                     if name == '.hg':
@@ -80,7 +76,7 @@
                     else:
                         dirs.append(name)
                         path = join(root, name)
-                        if repo.dirstate._ignore(path):
+                        if dirstate._ignore(path):
                             continue
                         for result in walkit(path, False):
                             yield result
@@ -98,7 +94,7 @@
 
     return walkit(root, root == '')
 
-def _explain_watch_limit(ui, repo):
+def _explain_watch_limit(ui, dirstate, rootabs):
     path = '/proc/sys/fs/inotify/max_user_watches'
     try:
         limit = int(file(path).read())
@@ -112,7 +108,7 @@
     ui.warn(_('*** this limit is too low to watch every '
               'directory in this repository\n'))
     ui.warn(_('*** counting directories: '))
-    ndirs = len(list(walkrepodirs(repo)))
+    ndirs = len(list(walkrepodirs(dirstate, rootabs)))
     ui.warn(_('found %d\n') % ndirs)
     newlimit = min(limit, 1024)
     while newlimit < ((limit + ndirs) * 1.1):
@@ -121,7 +117,7 @@
             (limit, newlimit))
     ui.warn(_('***  echo %d > %s\n') % (newlimit, path))
     raise util.Abort(_('cannot watch %s until inotify watch limit is raised')
-                     % repo.root)
+                     % rootabs)
 
 class pollable(object):
     """
@@ -309,10 +305,12 @@
         inotify.IN_UNMOUNT |
         0)
 
-    def __init__(self, ui, repo):
+    def __init__(self, ui, dirstate, root):
         self.ui = ui
-        self.repo = repo
-        self.wprefix = self.repo.wjoin('')
+        self.dirstate = dirstate
+
+        self.wprefix = join(root, '')
+        self.prefixlen = len(self.wprefix)
         try:
             self.watcher = watcher.watcher()
         except OSError, err:
@@ -351,7 +349,7 @@
 
     def dirstate_info(self):
         try:
-            st = os.lstat(self.repo.join('dirstate'))
+            st = os.lstat(self.wprefix + '.hg/dirstate')
             return st.st_mtime, st.st_ino
         except OSError, err:
             if err.errno != errno.ENOENT:
@@ -363,7 +361,7 @@
             return
         if self.watcher.path(path) is None:
             if self.ui.debugflag:
-                self.ui.note(_('watching %r\n') % path[len(self.wprefix):])
+                self.ui.note(_('watching %r\n') % path[self.prefixlen:])
             try:
                 self.watcher.add(path, mask)
             except OSError, err:
@@ -371,16 +369,16 @@
                     return
                 if err.errno != errno.ENOSPC:
                     raise
-                _explain_watch_limit(self.ui, self.repo)
+                _explain_watch_limit(self.ui, self.dirstate, self.wprefix)
 
     def setup(self):
-        self.ui.note(_('watching directories under %r\n') % self.repo.root)
-        self.add_watch(self.repo.path, inotify.IN_DELETE)
+        self.ui.note(_('watching directories under %r\n') % self.wprefix)
+        self.add_watch(self.wprefix + '.hg', inotify.IN_DELETE)
         self.check_dirstate()
 
     def filestatus(self, fn, st):
         try:
-            type_, mode, size, time = self.repo.dirstate._map[fn][:4]
+            type_, mode, size, time = self.dirstate._map[fn][:4]
         except KeyError:
             type_ = '?'
         if type_ == 'n':
@@ -392,7 +390,7 @@
             if time != int(st_mtime):
                 return 'l'
             return 'n'
-        if type_ == '?' and self.repo.dirstate._ignore(fn):
+        if type_ == '?' and self.dirstate._ignore(fn):
             return 'i'
         return type_
 
@@ -443,14 +441,13 @@
         if oldstatus and oldstatus in self.statuskeys \
             and oldstatus != newstatus:
             del self.statustrees[oldstatus].dir(root).files[fn]
-        if newstatus and newstatus != 'i':
+
+        if newstatus in (None, 'i'):
+            d.files.pop(fn, None)
+        elif oldstatus != newstatus:
             d.files[fn] = newstatus
-            if newstatus in self.statuskeys:
-                dd = self.statustrees[newstatus].dir(root)
-                if oldstatus != newstatus or fn not in dd.files:
-                    dd.files[fn] = newstatus
-        else:
-            d.files.pop(fn, None)
+            if newstatus != 'n':
+                self.statustrees[newstatus].dir(root).files[fn] = newstatus
 
 
     def check_deleted(self, key):
@@ -458,7 +455,7 @@
         # may have vanished from the dirstate; we must clean them up.
         nuke = []
         for wfn, ignore in self.statustrees[key].walk(key):
-            if wfn not in self.repo.dirstate:
+            if wfn not in self.dirstate:
                 nuke.append(wfn)
         for wfn in nuke:
             root, fn = split(wfn)
@@ -466,12 +463,12 @@
             del self.tree.dir(root).files[fn]
 
     def scan(self, topdir=''):
-        ds = self.repo.dirstate._map.copy()
-        self.add_watch(join(self.repo.root, topdir), self.mask)
-        for root, dirs, files in walk(self.repo, topdir):
+        ds = self.dirstate._map.copy()
+        self.add_watch(join(self.wprefix, topdir), self.mask)
+        for root, dirs, files in walk(self.dirstate, self.wprefix, topdir):
             for d in dirs:
                 self.add_watch(join(root, d), self.mask)
-            wroot = root[len(self.wprefix):]
+            wroot = root[self.prefixlen:]
             for fn in files:
                 wfn = join(wroot, fn)
                 self.updatefile(wfn, self.getstat(wfn))
@@ -500,7 +497,7 @@
         if not self.ui.debugflag:
             self.last_event = None
         self.ui.note(_('%s dirstate reload\n') % self.event_time())
-        self.repo.dirstate.invalidate()
+        self.dirstate.invalidate()
         self.handle_timeout()
         self.scan()
         self.ui.note(_('%s end dirstate reload\n') % self.event_time())
@@ -516,8 +513,8 @@
         # But it's easier to do nothing than to open that can of
         # worms.
 
-        if '_ignore' in self.repo.dirstate.__dict__:
-            delattr(self.repo.dirstate, '_ignore')
+        if '_ignore' in self.dirstate.__dict__:
+            delattr(self.dirstate, '_ignore')
             self.ui.note(_('rescanning due to .hgignore change\n'))
             self.handle_timeout()
             self.scan()
@@ -560,7 +557,7 @@
         try:
             st = self.stat(wpath)
             if stat.S_ISREG(st[0]):
-                if self.repo.dirstate[wpath] in 'lmn':
+                if self.dirstate[wpath] in 'lmn':
                     self.updatefile(wpath, st)
         except OSError:
             pass
@@ -574,7 +571,7 @@
                 self.check_dirstate()
             return
 
-        self.deletefile(wpath, self.repo.dirstate[wpath])
+        self.deletefile(wpath, self.dirstate[wpath])
 
     def process_create(self, wpath, evt):
         if self.ui.debugflag:
@@ -634,7 +631,7 @@
                          (self.event_time(), len(events)))
         for evt in events:
             assert evt.fullpath.startswith(self.wprefix)
-            wpath = evt.fullpath[len(self.wprefix):]
+            wpath = evt.fullpath[self.prefixlen:]
 
             # paths have been normalized, wpath never ends with a '/'
 
@@ -672,18 +669,17 @@
         Returns a sorted list of relatives paths currently watched,
         for debugging purposes.
         """
-        return sorted(tuple[0][len(self.wprefix):] for tuple in self.watcher)
+        return sorted(tuple[0][self.prefixlen:] for tuple in self.watcher)
 
 class server(pollable):
     """
     Listens for client queries on unix socket inotify.sock
     """
-    def __init__(self, ui, repo, repowatcher, timeout):
+    def __init__(self, ui, root, repowatcher, timeout):
         self.ui = ui
-        self.repo = repo
         self.repowatcher = repowatcher
         self.sock = socket.socket(socket.AF_UNIX)
-        self.sockpath = self.repo.join('inotify.sock')
+        self.sockpath = join(root, '.hg/inotify.sock')
         self.realsockpath = None
         try:
             self.sock.bind(self.sockpath)
@@ -811,11 +807,10 @@
                 raise
 
 class master(object):
-    def __init__(self, ui, repo, timeout=None):
+    def __init__(self, ui, dirstate, root, timeout=None):
         self.ui = ui
-        self.repo = repo
-        self.repowatcher = repowatcher(ui, repo)
-        self.server = server(ui, repo, self.repowatcher, timeout)
+        self.repowatcher = repowatcher(ui, dirstate, root)
+        self.server = server(ui, root, self.repowatcher, timeout)
 
     def shutdown(self):
         for obj in pollable.instances.itervalues():
@@ -828,7 +823,7 @@
             sys.exit(0)
         pollable.run()
 
-def start(ui, repo):
+def start(ui, dirstate, root):
     def closefds(ignore):
         # (from python bug #1177468)
         # close all inherited file descriptors
@@ -849,7 +844,7 @@
             except OSError:
                 pass
 
-    m = master(ui, repo)
+    m = master(ui, dirstate, root)
     sys.stdout.flush()
     sys.stderr.flush()
 
diff -r 2388ba07449b -r 812aaef40757 hgext/mq.py
--- a/hgext/mq.py	Wed Sep 16 16:11:44 2009 +0200
+++ b/hgext/mq.py	Wed Sep 16 23:46:06 2009 +0200
@@ -22,7 +22,6 @@
 
   print patch series                        qseries
   print applied patches                     qapplied
-  print name of top applied patch           qtop
 
   add known patch to applied stack          qpush
   remove patch from applied stack           qpop
@@ -1682,17 +1681,35 @@
 
 def applied(ui, repo, patch=None, **opts):
     """print the patches already applied"""
+
     q = repo.mq
+    l = len(q.applied)
+
     if patch:
         if patch not in q.series:
             raise util.Abort(_("patch %s is not in series file") % patch)
         end = q.series.index(patch) + 1
     else:
         end = q.series_end(True)
-    return q.qseries(repo, length=end, status='A', summary=opts.get('summary'))
+
+    if opts.get('last') and not end:
+        ui.write(_("no patches applied\n"))
+        return 1
+    elif opts.get('last') and end == 1:
+        ui.write(_("only one patch applied\n"))
+        return 1
+    elif opts.get('last'):
+        start = end - 2
+        end = 1
+    else:
+        start = 0
+
+    return q.qseries(repo, length=end, start=start, status='A',
+                     summary=opts.get('summary'))
 
 def unapplied(ui, repo, patch=None, **opts):
     """print the patches not yet applied"""
+
     q = repo.mq
     if patch:
         if patch not in q.series:
@@ -1700,7 +1717,14 @@
         start = q.series.index(patch) + 1
     else:
         start = q.series_end(True)
-    q.qseries(repo, start=start, status='U', summary=opts.get('summary'))
+
+    if start == len(q.series) and opts.get('first'):
+        ui.write(_("all patches applied\n"))
+        return 1
+
+    length = opts.get('first') and 1 or None
+    return q.qseries(repo, start=start, length=length, status='U',
+                     summary=opts.get('summary'))
 
 def qimport(ui, repo, *filename, **opts):
     """import a patch
@@ -2522,7 +2546,10 @@
 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
 
 cmdtable = {
-    "qapplied": (applied, [] + seriesopts, _('hg qapplied [-s] [PATCH]')),
+    "qapplied":
+        (applied,
+         [('1', 'last', None, _('show only the last patch'))] + seriesopts,
+         _('hg qapplied [-1] [-s] [PATCH]')),
     "qclone":
         (clone,
          [('', 'pull', None, _('use pull protocol to copy metadata')),
@@ -2645,7 +2672,10 @@
           ('n', 'nobackup', None, _('no backups'))],
          _('hg strip [-f] [-b] [-n] REV')),
     "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
-    "qunapplied": (unapplied, [] + seriesopts, _('hg qunapplied [-s] [PATCH]')),
+    "qunapplied":
+        (unapplied,
+         [('1', 'first', None, _('show only the first patch'))] + seriesopts,
+         _('hg qunapplied [-1] [-s] [PATCH]')),
     "qfinish":
         (finish,
          [('a', 'applied', None, _('finish all applied changesets'))],
diff -r 2388ba07449b -r 812aaef40757 hgext/patchbomb.py
--- a/hgext/patchbomb.py	Wed Sep 16 16:11:44 2009 +0200
+++ b/hgext/patchbomb.py	Wed Sep 16 23:46:06 2009 +0200
@@ -162,12 +162,16 @@
         body += '\n'.join(patch)
         msg = mail.mimetextpatch(body, display=opts.get('test'))
 
+    flag = ' '.join(opts.get('flag'))
+    if flag:
+        flag = ' ' + flag
+
     subj = desc[0].strip().rstrip('. ')
     if total == 1 and not opts.get('intro'):
-        subj = '[PATCH] ' + (opts.get('subject') or subj)
+        subj = '[PATCH%s] %s' % (flag, opts.get('subject') or subj)
     else:
         tlen = len(str(total))
-        subj = '[PATCH %0*d of %d] %s' % (tlen, idx, total, subj)
+        subj = '[PATCH %0*d of %d%s] %s' % (tlen, idx, total, flag, subj)
     msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
     msg['X-Mercurial-Node'] = node
     return msg, subj
@@ -322,11 +326,13 @@
         if len(patches) > 1 or opts.get('intro'):
             tlen = len(str(len(patches)))
 
-            subj = '[PATCH %0*d of %d] %s' % (
-                tlen, 0, len(patches),
-                opts.get('subject') or
-                prompt(ui, 'Subject:',
-                       rest=' [PATCH %0*d of %d] ' % (tlen, 0, len(patches))))
+            flag = ' '.join(opts.get('flag'))
+            if flag:
+                subj = '[PATCH %0*d of %d %s] ' % (tlen, 0, len(patches), flag)
+            else:
+                subj = '[PATCH %0*d of %d] ' % (tlen, 0, len(patches))
+            subj += opts.get('subject') or prompt(ui, 'Subject:', rest=subj,
+                                                    default='None')
 
             body = ''
             if opts.get('diffstat'):
@@ -477,6 +483,7 @@
            _('subject of first message (intro or single patch)')),
           ('', 'in-reply-to', '',
            _('message identifier to reply to')),
+          ('', 'flag', [], _('flags to add in subject prefixes')),
           ('t', 'to', [], _('email addresses of recipients')),
          ]
 
diff -r 2388ba07449b -r 812aaef40757 hgext/transplant.py
--- a/hgext/transplant.py	Wed Sep 16 16:11:44 2009 +0200
+++ b/hgext/transplant.py	Wed Sep 16 23:46:06 2009 +0200
@@ -182,7 +182,7 @@
         fp.write("# HG changeset patch\n")
         fp.write("# User %s\n" % user)
         fp.write("# Date %d %d\n" % date)
-        fp.write(changelog[4])
+        fp.write(msg + '\n')
         fp.close()
 
         try:
diff -r 2388ba07449b -r 812aaef40757 hgext/win32mbcs.py
--- a/hgext/win32mbcs.py	Wed Sep 16 16:11:44 2009 +0200
+++ b/hgext/win32mbcs.py	Wed Sep 16 23:46:06 2009 +0200
@@ -101,7 +101,7 @@
     if args:
         args = list(args)
         args[0] = appendsep(args[0])
-    if kwds.has_key('path'):
+    if 'path' in kwds:
         kwds['path'] = appendsep(kwds['path'])
     return func(*args, **kwds)
 
@@ -123,7 +123,7 @@
 funcs = '''os.path.join os.path.split os.path.splitext
  os.path.splitunc os.path.normpath os.path.normcase os.makedirs
  mercurial.util.endswithsep mercurial.util.splitpath mercurial.util.checkcase
- mercurial.util.fspath mercurial.windows.pconvert'''
+ mercurial.util.fspath mercurial.util.pconvert'''
 
 # codec and alias names of sjis and big5 to be faked.
 problematic_encodings = '''big5 big5-tw csbig5 big5hkscs big5-hkscs
diff -r 2388ba07449b -r 812aaef40757 hgext/zeroconf/__init__.py
--- a/hgext/zeroconf/__init__.py	Wed Sep 16 16:11:44 2009 +0200
+++ b/hgext/zeroconf/__init__.py	Wed Sep 16 23:46:06 2009 +0200
@@ -101,17 +101,20 @@
     def __init__(self, repo, name=None):
         super(hgwebzc, self).__init__(repo, name)
         name = self.reponame or os.path.basename(repo.root)
+        path = self.repo.ui.config("web", "prefix", "").strip('/')
         desc = self.repo.ui.config("web", "description", name)
-        publish(name, desc, name, int(repo.ui.config("web", "port", 8000)))
+        publish(name, desc, path, int(repo.ui.config("web", "port", 8000)))
 
 class hgwebdirzc(hgwebdir_mod.hgwebdir):
-    def run(self):
+    def __init__(self, conf, baseui=None):
+        super(hgwebdirzc, self).__init__(conf, baseui)
+        prefix = self.ui.config("web", "prefix", "").strip('/') + '/'
         for r, p in self.repos:
             u = self.ui.copy()
             u.readconfig(os.path.join(p, '.hg', 'hgrc'))
             n = os.path.basename(r)
-            publish(n, "hgweb", p, int(u.config("web", "port", 8000)))
-        return super(hgwebdirzc, self).run()
+            path = (prefix + r).strip('/')
+            publish(n, "hgweb", path, int(u.config("web", "port", 8000)))
 
 # listen
 
diff -r 2388ba07449b -r 812aaef40757 mercurial/changegroup.py
--- a/mercurial/changegroup.py	Wed Sep 16 16:11:44 2009 +0200
+++ b/mercurial/changegroup.py	Wed Sep 16 23:46:06 2009 +0200
@@ -10,7 +10,7 @@
 import struct, os, bz2, zlib, tempfile
 
 def getchunk(source):
-    """get a chunk from a changegroup"""
+    """return the next chunk from changegroup 'source' as a string"""
     d = source.read(4)
     if not d:
         return ""
@@ -25,7 +25,8 @@
     return d
 
 def chunkiter(source):
-    """iterate through the chunks in source"""
+    """iterate through the chunks in source, yielding a sequence of chunks
+    (strings)"""
     while 1:
         c = getchunk(source)
         if not c:
@@ -33,10 +34,11 @@
         yield c
 
 def chunkheader(length):
-    """build a changegroup chunk header"""
+    """return a changegroup chunk header (string)"""
     return struct.pack(">l", length + 4)
 
 def closechunk():
+    """return a changegroup chunk header (string) for a zero-length chunk"""
     return struct.pack(">l", 0)
 
 class nocompress(object):
diff -r 2388ba07449b -r 812aaef40757 mercurial/cmdutil.py
--- a/mercurial/cmdutil.py	Wed Sep 16 16:11:44 2009 +0200
+++ b/mercurial/cmdutil.py	Wed Sep 16 23:46:06 2009 +0200
@@ -987,12 +987,12 @@
 def finddate(ui, repo, date):
     """Find the tipmost changeset that matches the given date spec"""
     df = util.matchdate(date)
-    get = util.cachefunc(lambda r: repo[r].changeset())
+    get = util.cachefunc(lambda r: repo[r])
     changeiter, matchfn = walkchangerevs(ui, repo, [], get, {'rev':None})
     results = {}
     for st, rev, fns in changeiter:
         if st == 'add':
-            d = get(rev)[2]
+            d = get(rev).date()
             if df(d[0]):
                 results[rev] = d
         elif st == 'iter':
@@ -1118,13 +1118,13 @@
         def changerevgen():
             for i, window in increasing_windows(len(repo) - 1, nullrev):
                 for j in xrange(i - window, i + 1):
-                    yield j, change(j)[3]
+                    yield change(j)
 
-        for rev, changefiles in changerevgen():
-            matches = filter(m, changefiles)
+        for ctx in changerevgen():
+            matches = filter(m, ctx.files())
             if matches:
-                fncache[rev] = matches
-                wanted.add(rev)
+                fncache[ctx.rev()] = matches
+                wanted.add(ctx.rev())
 
     class followfilter(object):
         def __init__(self, onlyfirst=False):
@@ -1189,7 +1189,7 @@
                 fns = fncache.get(rev)
                 if not fns:
                     def fns_generator():
-                        for f in change(rev)[3]:
+                        for f in change(rev).files():
                             if m(f):
                                 yield f
                     fns = fns_generator()
diff -r 2388ba07449b -r 812aaef40757 mercurial/commands.py
--- a/mercurial/commands.py	Wed Sep 16 16:11:44 2009 +0200
+++ b/mercurial/commands.py	Wed Sep 16 23:46:06 2009 +0200
@@ -1275,9 +1275,9 @@
             if opts.get('all'):
                 cols.append(change)
             if opts.get('user'):
-                cols.append(ui.shortuser(get(r)[1]))
+                cols.append(ui.shortuser(get(r).user()))
             if opts.get('date'):
-                cols.append(datefunc(get(r)[2]))
+                cols.append(datefunc(get(r).date()))
             if opts.get('files_with_matches'):
                 c = (fn, r)
                 if c in filerevmatches:
@@ -1291,7 +1291,7 @@
 
     skip = {}
     revfiles = {}
-    get = util.cachefunc(lambda r: repo[r].changeset())
+    get = util.cachefunc(lambda r: repo[r])
     changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
     found = False
     follow = opts.get('follow')
@@ -1300,7 +1300,7 @@
             matches.clear()
             revfiles.clear()
         elif st == 'add':
-            ctx = repo[rev]
+            ctx = get(rev)
             pctx = ctx.parents()[0]
             parent = pctx.rev()
             matches.setdefault(rev, {})
@@ -1323,18 +1323,18 @@
                     continue
                 files.append(fn)
 
-                if not matches[rev].has_key(fn):
+                if fn not in matches[rev]:
                     grepbody(fn, rev, flog.read(fnode))
 
                 pfn = copy or fn
-                if not matches[parent].has_key(pfn):
+                if pfn not in matches[parent]:
                     try:
                         fnode = pctx.filenode(pfn)
                         grepbody(pfn, parent, flog.read(fnode))
                     except error.LookupError:
                         pass
         elif st == 'iter':
-            parent = repo[rev].parents()[0].rev()
+            parent = get(rev).parents()[0].rev()
             for fn in sorted(revfiles.get(rev, [])):
                 states = matches[rev][fn]
                 copy = copies.get(rev, {}).get(fn)
@@ -1982,7 +1982,7 @@
     will appear in files:.
     """
 
-    get = util.cachefunc(lambda r: repo[r].changeset())
+    get = util.cachefunc(lambda r: repo[r])
     changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
 
     limit = cmdutil.loglimit(opts)
@@ -2040,40 +2040,37 @@
             if opts.get('only_merges') and len(parents) != 2:
                 continue
 
-            if only_branches:
-                revbranch = get(rev)[5]['branch']
-                if revbranch not in only_branches:
-                    continue
-
-            if df:
-                changes = get(rev)
-                if not df(changes[2][0]):
-                    continue
+            ctx = get(rev)
+            if only_branches and ctx.branch() not in only_branches:
+                continue
+
+            if df and not df(ctx.date()[0]):
+                continue
 
             if opts.get('keyword'):
-                changes = get(rev)
                 miss = 0
                 for k in [kw.lower() for kw in opts['keyword']]:
-                    if not (k in changes[1].lower() or
-                            k in changes[4].lower() or
-                            k in " ".join(changes[3]).lower()):
+                    if not (k in ctx.user().lower() or
+                            k in ctx.description().lower() or
+                            k in " ".join(ctx.files()).lower()):
                         miss = 1
                         break
                 if miss:
                     continue
 
             if opts['user']:
-                changes = get(rev)
-                if not [k for k in opts['user'] if k in changes[1]]:
+                if not [k for k in opts['user'] if k in ctx.user()]:
                     continue
 
             copies = []
             if opts.get('copies') and rev:
-                for fn in get(rev)[3]:
+                for fn in ctx.files():
                     rename = getrenamed(fn, rev)
                     if rename:
                         copies.append((fn, rename[0]))
-            displayer.show(context.changectx(repo, rev), copies=copies)
+
+            displayer.show(ctx, copies=copies)
+
         elif st == 'iter':
             if count == limit: break
             if displayer.flush(rev):
@@ -2158,7 +2155,8 @@
         roots, heads = [common.node()], [p2.node()]
         displayer = cmdutil.show_changeset(ui, repo, opts)
         for node in repo.changelog.nodesbetween(roots=roots, heads=heads)[0]:
-            displayer.show(repo[node])
+            if node not in roots:
+                displayer.show(repo[node])
         return 0
 
     return hg.merge(repo, node, force=opts.get('force'))
diff -r 2388ba07449b -r 812aaef40757 mercurial/dispatch.py
--- a/mercurial/dispatch.py	Wed Sep 16 16:11:44 2009 +0200
+++ b/mercurial/dispatch.py	Wed Sep 16 23:46:06 2009 +0200
@@ -335,7 +335,7 @@
     path = _findrepo(os.getcwd()) or ""
     if not path:
         lui = ui
-    if path:
+    else:
         try:
             lui = ui.copy()
             lui.readconfig(os.path.join(path, ".hg", "hgrc"))
@@ -349,19 +349,25 @@
         lui = ui.copy()
         lui.readconfig(os.path.join(path, ".hg", "hgrc"))
 
+    # Configure extensions in phases: uisetup, extsetup, cmdtable, and
+    # reposetup. Programs like TortoiseHg will call _dispatch several
+    # times so we keep track of configured extensions in _loaded.
     extensions.loadall(lui)
-    for name, module in extensions.extensions():
-        if name in _loaded:
-            continue
+    exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
 
-        # setup extensions
-        # TODO this should be generalized to scheme, where extensions can
-        #      redepend on other extensions.  then we should toposort them, and
-        #      do initialization in correct order
+    # (uisetup is handled in extensions.loadall)
+
+    for name, module in exts:
         extsetup = getattr(module, 'extsetup', None)
         if extsetup:
-            extsetup()
+            try:
+                extsetup(ui)
+            except TypeError:
+                if extsetup.func_code.co_argcount != 0:
+                    raise
+                extsetup() # old extsetup with no ui argument
 
+    for name, module in exts:
         cmdtable = getattr(module, 'cmdtable', {})
         overrides = [cmd for cmd in cmdtable if cmd in commands.table]
         if overrides:
@@ -370,6 +376,8 @@
         commands.table.update(cmdtable)
         _loaded.add(name)
 
+    # (reposetup is handled in hg.repository)
+
     addaliases(lui, commands.table)
 
     # check for fallback encoding
diff -r 2388ba07449b -r 812aaef40757 mercurial/extensions.py
--- a/mercurial/extensions.py	Wed Sep 16 16:11:44 2009 +0200
+++ b/mercurial/extensions.py	Wed Sep 16 23:46:06 2009 +0200
@@ -40,6 +40,7 @@
         return imp.load_source(module_name, path)
 
 def load(ui, name, path):
+    # unused ui argument kept for backwards compatibility
     if name.startswith('hgext.') or name.startswith('hgext/'):
         shortname = name[6:]
     else:
@@ -66,12 +67,9 @@
     _extensions[shortname] = mod
     _order.append(shortname)
 
-    uisetup = getattr(mod, 'uisetup', None)
-    if uisetup:
-        uisetup(ui)
-
 def loadall(ui):
     result = ui.configitems("extensions")
+    newindex = len(_order)
     for (name, path) in result:
         if path:
             if path[0] == '!':
@@ -90,6 +88,11 @@
             if ui.traceback():
                 return 1
 
+    for name in _order[newindex:]:
+        uisetup = getattr(_extensions[name], 'uisetup', None)
+        if uisetup:
+            uisetup(ui)
+
 def wrapcommand(table, command, wrapper):
     aliases, entry = cmdutil.findcmd(command, table)
     for alias, e in table.iteritems():
diff -r 2388ba07449b -r 812aaef40757 mercurial/help.py
--- a/mercurial/help.py	Wed Sep 16 16:11:44 2009 +0200
+++ b/mercurial/help.py	Wed Sep 16 23:46:06 2009 +0200
@@ -405,59 +405,59 @@
 
     List of filters:
 
-    :addbreaks:  Any text. Add an XHTML "
" tag before the end of - every line except the last. - :age: Date. Returns a human-readable date/time difference - between the given date/time and the current - date/time. - :basename: Any text. Treats the text as a path, and returns the - last component of the path after splitting by the - path separator (ignoring trailing separators). For - example, "foo/bar/baz" becomes "baz" and "foo/bar//" - becomes "bar". - :stripdir: Treat the text as path and strip a directory level, - if possible. For example, "foo" and "foo/bar" becomes - "foo". - :date: Date. Returns a date in a Unix date format, including - the timezone: "Mon Sep 04 15:13:13 2006 0700". - :domain: Any text. Finds the first string that looks like an - email address, and extracts just the domain - component. Example: 'User ' becomes - 'example.com'. - :email: Any text. Extracts the first string that looks like - an email address. Example: 'User ' - becomes 'user@example.com'. - :escape: Any text. Replaces the special XML/XHTML characters - "&", "<" and ">" with XML entities. - :fill68: Any text. Wraps the text to fit in 68 columns. - :fill76: Any text. Wraps the text to fit in 76 columns. - :firstline: Any text. Returns the first line of text. - :nonempty: Any text. Returns '(none)' if the string is empty. - :hgdate: Date. Returns the date as a pair of numbers: - "1157407993 25200" (Unix timestamp, timezone offset). - :isodate: Date. Returns the date in ISO 8601 format: - "2009-08-18 13:00 +0200". - :isodatesec: Date. Returns the date in ISO 8601 format, including - seconds: "2009-08-18 13:00:13 +0200". See also the - rfc3339date filter. - :localdate: Date. Converts a date to local date. - :obfuscate: Any text. Returns the input text rendered as a - sequence of XML entities. - :person: Any text. Returns the text before an email address. - :rfc822date: Date. Returns a date using the same format used in - email headers: "Tue, 18 Aug 2009 13:00:13 +0200". + :addbreaks: Any text. Add an XHTML "
" tag before the end of + every line except the last. + :age: Date. Returns a human-readable date/time difference + between the given date/time and the current + date/time. + :basename: Any text. Treats the text as a path, and returns the + last component of the path after splitting by the + path separator (ignoring trailing separators). For + example, "foo/bar/baz" becomes "baz" and "foo/bar//" + becomes "bar". + :stripdir: Treat the text as path and strip a directory level, + if possible. For example, "foo" and "foo/bar" becomes + "foo". + :date: Date. Returns a date in a Unix date format, including + the timezone: "Mon Sep 04 15:13:13 2006 0700". + :domain: Any text. Finds the first string that looks like an + email address, and extracts just the domain + component. Example: 'User ' becomes + 'example.com'. + :email: Any text. Extracts the first string that looks like + an email address. Example: 'User ' + becomes 'user@example.com'. + :escape: Any text. Replaces the special XML/XHTML characters + "&", "<" and ">" with XML entities. + :fill68: Any text. Wraps the text to fit in 68 columns. + :fill76: Any text. Wraps the text to fit in 76 columns. + :firstline: Any text. Returns the first line of text. + :nonempty: Any text. Returns '(none)' if the string is empty. + :hgdate: Date. Returns the date as a pair of numbers: + "1157407993 25200" (Unix timestamp, timezone offset). + :isodate: Date. Returns the date in ISO 8601 format: + "2009-08-18 13:00 +0200". + :isodatesec: Date. Returns the date in ISO 8601 format, including + seconds: "2009-08-18 13:00:13 +0200". See also the + rfc3339date filter. + :localdate: Date. Converts a date to local date. + :obfuscate: Any text. Returns the input text rendered as a + sequence of XML entities. + :person: Any text. Returns the text before an email address. + :rfc822date: Date. Returns a date using the same format used in + email headers: "Tue, 18 Aug 2009 13:00:13 +0200". :rfc3339date: Date. Returns a date using the Internet date format specified in RFC 3339: "2009-08-18T13:00:13+02:00". - :short: Changeset hash. Returns the short form of a changeset - hash, i.e. a 12-byte hexadecimal string. - :shortdate: Date. Returns a date like "2006-09-18". - :strip: Any text. Strips all leading and trailing whitespace. - :tabindent: Any text. Returns the text, with every line except - the first starting with a tab character. - :urlescape: Any text. Escapes all "special" characters. For - example, "foo bar" becomes "foo%20bar". - :user: Any text. Returns the user portion of an email - address. + :short: Changeset hash. Returns the short form of a changeset + hash, i.e. a 12-byte hexadecimal string. + :shortdate: Date. Returns a date like "2006-09-18". + :strip: Any text. Strips all leading and trailing whitespace. + :tabindent: Any text. Returns the text, with every line except + the first starting with a tab character. + :urlescape: Any text. Escapes all "special" characters. For + example, "foo bar" becomes "foo%20bar". + :user: Any text. Returns the user portion of an email + address. ''')), (['urls'], _('URL Paths'), diff -r 2388ba07449b -r 812aaef40757 mercurial/hgweb/hgwebdir_mod.py --- a/mercurial/hgweb/hgwebdir_mod.py Wed Sep 16 16:11:44 2009 +0200 +++ b/mercurial/hgweb/hgwebdir_mod.py Wed Sep 16 23:46:06 2009 +0200 @@ -198,12 +198,17 @@ sortdefault = 'name', False def entries(sortcolumn="", descending=False, subdir="", **map): + rows = [] parity = paritygen(self.stripecount) + descend = self.ui.configbool('web', 'descend', True) for name, path in self.repos: + if not name.startswith(subdir): continue name = name[len(subdir):] + if not descend and '/' in name: + continue u = self.ui.copy() try: diff -r 2388ba07449b -r 812aaef40757 mercurial/localrepo.py --- a/mercurial/localrepo.py Wed Sep 16 16:11:44 2009 +0200 +++ b/mercurial/localrepo.py Wed Sep 16 23:46:06 2009 +0200 @@ -1457,6 +1457,12 @@ return self.push_addchangegroup(remote, force, revs) def prepush(self, remote, force, revs): + '''Analyze the local and remote repositories and determine which + changesets need to be pushed to the remote. Return a tuple + (changegroup, remoteheads). changegroup is a readable file-like + object whose read() returns successive changegroup chunks ready to + be sent over the wire. remoteheads is the list of remote heads. + ''' common = {} remote_heads = remote.heads() inc = self.findincoming(remote, common, remote_heads, force=force) @@ -1601,9 +1607,10 @@ self.ui.debug("%s\n" % hex(node)) def changegroupsubset(self, bases, heads, source, extranodes=None): - """This function generates a changegroup consisting of all the nodes - that are descendents of any of the bases, and ancestors of any of - the heads. + """Compute a changegroup consisting of all the nodes that are + descendents of any of the bases and ancestors of any of the heads. + Return a chunkbuffer object whose read() method will return + successive changegroup chunks. It is fairly complex as determining which filenodes and which manifest nodes need to be included for the changeset to be complete @@ -1902,8 +1909,9 @@ return self.changegroupsubset(basenodes, self.heads(), source) def _changegroup(self, common, source): - """Generate a changegroup of all nodes that we have that a recipient - doesn't. + """Compute the changegroup of all nodes that we have that a recipient + doesn't. Return a chunkbuffer object whose read() method will return + successive changegroup chunks. This is much easier than the previous function as we can assume that the recipient has any changenode we aren't sending them. @@ -1937,6 +1945,7 @@ return lookuprevlink def gengroup(): + '''yield a sequence of changegroup chunks (strings)''' # construct a list of all changed files changedfiles = set() diff -r 2388ba07449b -r 812aaef40757 mercurial/manifest.py --- a/mercurial/manifest.py Wed Sep 16 16:11:44 2009 +0200 +++ b/mercurial/manifest.py Wed Sep 16 23:46:06 2009 +0200 @@ -20,12 +20,11 @@ def set(self, f, flags): self._flags[f] = flags def copy(self): - return manifestdict(dict.copy(self), dict.copy(self._flags)) + return manifestdict(self, dict.copy(self._flags)) class manifest(revlog.revlog): def __init__(self, opener): - self.mapcache = None - self.listcache = None + self._mancache = None revlog.revlog.__init__(self, opener, "00manifest.i") def parse(self, lines): @@ -40,12 +39,12 @@ def read(self, node): if node == revlog.nullid: return manifestdict() # don't upset local cache - if self.mapcache and self.mapcache[0] == node: - return self.mapcache[1] + if self._mancache and self._mancache[0] == node: + return self._mancache[1] text = self.revision(node) - self.listcache = array.array('c', text) + arraytext = array.array('c', text) mapping = self.parse(text) - self.mapcache = (node, mapping) + self._mancache = (node, mapping, arraytext) return mapping def _search(self, m, s, lo=0, hi=None): @@ -93,8 +92,8 @@ def find(self, node, f): '''look up entry for a single file efficiently. return (node, flags) pair if found, (None, None) if not.''' - if self.mapcache and node == self.mapcache[0]: - return self.mapcache[1].get(f), self.mapcache[1].flags(f) + if self._mancache and self._mancache[0] == node: + return self._mancache[1].get(f), self._mancache[1].flags(f) text = self.revision(node) start, end = self._search(text, f) if start == end: @@ -110,17 +109,13 @@ def addlistdelta(addlist, x): # start from the bottom up # so changes to the offsets don't mess things up. - i = len(x) - while i > 0: - i -= 1 - start = x[i][0] - end = x[i][1] - if x[i][2]: - addlist[start:end] = array.array('c', x[i][2]) + for start, end, content in reversed(x): + if content: + addlist[start:end] = array.array('c', content) else: del addlist[start:end] - return "".join([struct.pack(">lll", d[0], d[1], len(d[2])) + d[2] - for d in x ]) + return "".join(struct.pack(">lll", start, end, len(content)) + content + for start, end, content in x) def checkforbidden(l): for f in l: @@ -128,26 +123,29 @@ raise error.RevlogError( _("'\\n' and '\\r' disallowed in filenames: %r") % f) - # if we're using the listcache, make sure it is valid and + # if we're using the cache, make sure it is valid and # parented by the same node we're diffing against - if not (changed and self.listcache and p1 and self.mapcache[0] == p1): + if not (changed and self._mancache and p1 and self._mancache[0] == p1): files = sorted(map) checkforbidden(files) # if this is changed to support newlines in filenames, # be sure to check the templates/ dir again (especially *-raw.tmpl) hex, flags = revlog.hex, map.flags - text = ["%s\000%s%s\n" % (f, hex(map[f]), flags(f)) - for f in files] - self.listcache = array.array('c', "".join(text)) + text = ''.join("%s\000%s%s\n" % (f, hex(map[f]), flags(f)) + for f in files) + arraytext = array.array('c', text) cachedelta = None else: - addlist = self.listcache + added, removed = changed + addlist = self._mancache[2] - checkforbidden(changed[0]) + checkforbidden(added) # combine the changed lists into one list for sorting - work = [[x, 0] for x in changed[0]] - work[len(work):] = [[x, 1] for x in changed[1]] + work = [(x, False) for x in added] + work.extend((x, True) for x in removed) + # this could use heapq.merge() (from python2.6+) or equivalent + # since the lists are already sorted work.sort() delta = [] @@ -160,18 +158,17 @@ # start with a readonly loop that finds the offset of # each line and creates the deltas - for w in work: - f = w[0] + for f, todelete in work: # bs will either be the index of the item or the insert point start, end = self._search(addbuf, f, start) - if w[1] == 0: + if not todelete: l = "%s\000%s%s\n" % (f, revlog.hex(map[f]), map.flags(f)) else: + if start == end: + # item we want to delete was not found, error out + raise AssertionError( + _("failed to remove %s from manifest") % f) l = "" - if start == end and w[1] == 1: - # item we want to delete was not found, error out - raise AssertionError( - _("failed to remove %s from manifest") % f) if dstart != None and dstart <= start and dend >= start: if dend < end: dend = end @@ -190,12 +187,12 @@ cachedelta = addlistdelta(addlist, delta) # the delta is only valid if we've been processing the tip revision - if self.mapcache[0] != self.tip(): + if p1 != self.tip(): cachedelta = None - self.listcache = addlist + arraytext = addlist + text = buffer(arraytext) - n = self.addrevision(buffer(self.listcache), transaction, link, - p1, p2, cachedelta) - self.mapcache = (n, map) + n = self.addrevision(text, transaction, link, p1, p2, cachedelta) + self._mancache = (n, map, arraytext) return n diff -r 2388ba07449b -r 812aaef40757 mercurial/minirst.py --- a/mercurial/minirst.py Wed Sep 16 16:11:44 2009 +0200 +++ b/mercurial/minirst.py Wed Sep 16 23:46:06 2009 +0200 @@ -279,6 +279,8 @@ def formatblock(block, width): """Format a block according to width.""" + if width <= 0: + width = 78 indent = ' ' * block['indent'] if block['type'] == 'margin': return '' diff -r 2388ba07449b -r 812aaef40757 mercurial/parsers.c --- a/mercurial/parsers.c Wed Sep 16 16:11:44 2009 +0200 +++ b/mercurial/parsers.c Wed Sep 16 23:46:06 2009 +0200 @@ -92,8 +92,6 @@ goto bail; if (nlen > 40) { - PyObject *flags; - flags = PyString_FromStringAndSize(zero + 41, nlen - 40); if (!flags) diff -r 2388ba07449b -r 812aaef40757 mercurial/patch.py --- a/mercurial/patch.py Wed Sep 16 16:11:44 2009 +0200 +++ b/mercurial/patch.py Wed Sep 16 23:46:06 2009 +0200 @@ -188,7 +188,7 @@ if m: if gp: gitpatches.append(gp) - src, dst = m.group(1, 2) + dst = m.group(2) gp = patchmeta(dst) gp.lineno = lineno elif gp: @@ -378,15 +378,13 @@ self.write() self.write_rej() - def apply(self, h, reverse): + def apply(self, h): if not h.complete(): raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") % (h.number, h.desc, len(h.a), h.lena, len(h.b), h.lenb)) self.hunks += 1 - if reverse: - h.reverse() if self.missing: self.rej.append(h) @@ -600,31 +598,6 @@ self.startb, self.lenb) self.hunk[0] = self.desc - def reverse(self): - self.create, self.remove = self.remove, self.create - origlena = self.lena - origstarta = self.starta - self.lena = self.lenb - self.starta = self.startb - self.lenb = origlena - self.startb = origstarta - self.a = [] - self.b = [] - # self.hunk[0] is the @@ description - for x in xrange(1, len(self.hunk)): - o = self.hunk[x] - if o.startswith('-'): - n = '+' + o[1:] - self.b.append(o[1:]) - elif o.startswith('+'): - n = '-' + o[1:] - self.a.append(n) - else: - n = o - self.b.append(o[1:]) - self.a.append(o) - self.hunk[x] = o - def fix_newline(self): diffhelpers.fix_newline(self.hunk, self.a, self.b) @@ -762,7 +735,7 @@ return s return s[:i] -def selectfile(afile_orig, bfile_orig, hunk, strip, reverse): +def selectfile(afile_orig, bfile_orig, hunk, strip): def pathstrip(path, count=1): pathlen = len(path) i = 0 @@ -790,8 +763,6 @@ else: goodb = not nullb and os.path.exists(bfile) createfunc = hunk.createfile - if reverse: - createfunc = hunk.rmfile missing = not goodb and not gooda and not createfunc() # some diff programs apparently produce create patches where the @@ -977,8 +948,7 @@ if hunknum == 0 and dopatch and not gitworkdone: raise NoHunks -def applydiff(ui, fp, changed, strip=1, sourcefile=None, reverse=False, - eol=None): +def applydiff(ui, fp, changed, strip=1, sourcefile=None, eol=None): """ Reads a patch from fp and tries to apply it. @@ -1008,7 +978,7 @@ if not current_file: continue current_hunk = values - ret = current_file.apply(current_hunk, reverse) + ret = current_file.apply(current_hunk) if ret >= 0: changed.setdefault(current_file.fname, None) if ret > 0: @@ -1021,7 +991,7 @@ current_file = patchfile(ui, sourcefile, opener, eol=eol) else: current_file, missing = selectfile(afile, bfile, first_hunk, - strip, reverse) + strip) current_file = patchfile(ui, current_file, opener, missing, eol) except PatchError, err: ui.warn(str(err) + '\n') diff -r 2388ba07449b -r 812aaef40757 mercurial/repo.py --- a/mercurial/repo.py Wed Sep 16 16:11:44 2009 +0200 +++ b/mercurial/repo.py Wed Sep 16 23:46:06 2009 +0200 @@ -40,4 +40,5 @@ url = self.url() if url.endswith('/'): return url + path - return url + '/' + path + else: + return url + '/' + path diff -r 2388ba07449b -r 812aaef40757 mercurial/revlog.py --- a/mercurial/revlog.py Wed Sep 16 16:11:44 2009 +0200 +++ b/mercurial/revlog.py Wed Sep 16 23:46:06 2009 +0200 @@ -973,7 +973,7 @@ if node == nullid: return "" if self._cache and self._cache[0] == node: - return str(self._cache[2]) + return self._cache[2] # look up what we need to read text = None @@ -988,7 +988,7 @@ # do we have useful data cached? if self._cache and self._cache[1] >= base and self._cache[1] < rev: base = self._cache[1] - text = str(self._cache[2]) + text = self._cache[2] self._loadindex(base, rev + 1) self._chunkraw(base, rev) @@ -1111,7 +1111,8 @@ ifh.write(data[1]) self.checkinlinesize(transaction, ifh) - self._cache = (node, curr, text) + if type(text) == str: # only accept immutable objects + self._cache = (node, curr, text) return node def ancestor(self, a, b): @@ -1127,7 +1128,8 @@ return self.node(c) def group(self, nodelist, lookup, infocollect=None): - """calculate a delta group + """Calculate a delta group, yielding a sequence of changegroup chunks + (strings). Given a list of changeset revs, return a set of deltas and metadata corresponding to nodes. the first delta is diff -r 2388ba07449b -r 812aaef40757 mercurial/tags.py --- a/mercurial/tags.py Wed Sep 16 16:11:44 2009 +0200 +++ b/mercurial/tags.py Wed Sep 16 23:46:06 2009 +0200 @@ -301,7 +301,10 @@ def _writetagcache(ui, repo, heads, tagfnode, cachetags): - cachefile = repo.opener('tags.cache', 'w', atomictemp=True) + try: + cachefile = repo.opener('tags.cache', 'w', atomictemp=True) + except (OSError, IOError): + return _debug(ui, 'writing cache file %s\n' % cachefile.name) realheads = repo.heads() # for sanity checks below diff -r 2388ba07449b -r 812aaef40757 mercurial/templatefilters.py --- a/mercurial/templatefilters.py Wed Sep 16 16:11:44 2009 +0200 +++ b/mercurial/templatefilters.py Wed Sep 16 23:46:06 2009 +0200 @@ -105,13 +105,14 @@ '''indent each non-empty line of text after first with prefix.''' lines = text.splitlines() num_lines = len(lines) + endswithnewline = text[-1:] == '\n' def indenter(): for i in xrange(num_lines): l = lines[i] if i and l.strip(): yield prefix yield l - if i < num_lines - 1 or text.endswith('\n'): + if i < num_lines - 1 or endswithnewline: yield '\n' return "".join(indenter()) diff -r 2388ba07449b -r 812aaef40757 mercurial/templater.py --- a/mercurial/templater.py Wed Sep 16 16:11:44 2009 +0200 +++ b/mercurial/templater.py Wed Sep 16 23:46:06 2009 +0200 @@ -42,7 +42,7 @@ filter uses function to transform value. syntax is {key|filter1|filter2|...}.''' - template_re = re.compile(r'{([\w\|%]+)}|#([\w\|%]+)#') + template_re = re.compile(r'{([\w\|%]+)}') def __init__(self, loader, filters={}, defaults={}): self.loader = loader diff -r 2388ba07449b -r 812aaef40757 mercurial/ui.py --- a/mercurial/ui.py Wed Sep 16 16:11:44 2009 +0200 +++ b/mercurial/ui.py Wed Sep 16 23:46:06 2009 +0200 @@ -199,7 +199,7 @@ def _path(self, loc): p = self.config('paths', loc) if p and '%%' in p: - self.warn('(deprecated \'%%\' in path %s=%s from %s)\n' % + self.warn("(deprecated '%%' in path %s=%s from %s)\n" % (loc, p, self.configsource('paths', loc))) p = p.replace('%%', '%') return p @@ -358,7 +358,7 @@ visible. 'topic' is the current operation, 'item' is a non-numeric marker of the current position (ie the currently in-process file), 'pos' is the current numeric position (ie - revision, bytes, etc.), units is a corresponding unit label, + revision, bytes, etc.), unit is a corresponding unit label, and total is the highest expected pos. Multiple nested topics may be active at a time. All topics @@ -368,14 +368,14 @@ if pos == None or not self.debugflag: return - if units: - units = ' ' + units + if unit: + unit = ' ' + unit if item: item = ' ' + item if total: pct = 100.0 * pos / total ui.debug('%s:%s %s/%s%s (%4.2g%%)\n' - % (topic, item, pos, total, units, pct)) + % (topic, item, pos, total, unit, pct)) else: - ui.debug('%s:%s %s%s\n' % (topic, item, pos, units)) + ui.debug('%s:%s %s%s\n' % (topic, item, pos, unit)) diff -r 2388ba07449b -r 812aaef40757 mercurial/url.py --- a/mercurial/url.py Wed Sep 16 16:11:44 2009 +0200 +++ b/mercurial/url.py Wed Sep 16 23:46:06 2009 +0200 @@ -487,6 +487,8 @@ authinfo = None return url, authinfo +handlerfuncs = [] + def opener(ui, authinfo=None): ''' construct an opener suitable for urllib2 @@ -507,6 +509,7 @@ handlers.extend((urllib2.HTTPBasicAuthHandler(passmgr), httpdigestauthhandler(passmgr))) + handlers.extend([h(ui, passmgr) for h in handlerfuncs]) opener = urllib2.build_opener(*handlers) # 1.0 here is the _protocol_ version diff -r 2388ba07449b -r 812aaef40757 mercurial/util.py --- a/mercurial/util.py Wed Sep 16 16:11:44 2009 +0200 +++ b/mercurial/util.py Wed Sep 16 23:46:06 2009 +0200 @@ -266,9 +266,7 @@ def canonpath(root, cwd, myname): """return the canonical path of myname, given cwd and root""" - if root == os.sep: - rootsep = os.sep - elif endswithsep(root): + if endswithsep(root): rootsep = root else: rootsep = root + os.sep @@ -663,8 +661,9 @@ contents = _fspathcache[dir] lpart = part.lower() + lenp = len(part) for n in contents: - if n.lower() == lpart: + if lenp == len(n) and n.lower() == lpart: result.append(n) break else: @@ -1275,6 +1274,9 @@ def wrap(line, hangindent, width=None): if width is None: width = termwidth() - 2 + if width <= hangindent: + # adjust for weird terminal size + width = max(78, hangindent + 1) padding = '\n' + ' ' * hangindent return padding.join(textwrap.wrap(line, width=width - hangindent)) diff -r 2388ba07449b -r 812aaef40757 mercurial/windows.py --- a/mercurial/windows.py Wed Sep 16 16:11:44 2009 +0200 +++ b/mercurial/windows.py Wed Sep 16 23:46:06 2009 +0200 @@ -17,7 +17,7 @@ try: return osutil.posixfile(name, mode, buffering) except WindowsError, err: - raise IOError(err.errno, err.strerror) + raise IOError(err.errno, '%s: %s' % (name, err.strerror)) posixfile.__doc__ = osutil.posixfile.__doc__ class winstdout(object): diff -r 2388ba07449b -r 812aaef40757 tests/hghave --- a/tests/hghave Wed Sep 16 16:11:44 2009 +0200 +++ b/tests/hghave Wed Sep 16 23:46:06 2009 +0200 @@ -78,7 +78,7 @@ def has_icasefs(): # Stolen from mercurial.util - fd, path = tempfile.mkstemp(prefix=tempprefix) + fd, path = tempfile.mkstemp(prefix=tempprefix, dir='.') os.close(fd) try: s1 = os.stat(path) @@ -123,6 +123,10 @@ def has_git(): return matchoutput('git --version 2>&1', r'^git version') +def has_rst2html(): + return matchoutput('rst2html --version', r'^rst2html \(Docutils') or \ + matchoutput('rst2html.py --version', r'^rst2html.py \(Docutils') + def has_svn(): return matchoutput('svn --version 2>&1', r'^svn, version') and \ matchoutput('svnadmin --version 2>&1', r'^svnadmin, version') @@ -195,6 +199,7 @@ "outer-repo": (has_outer_repo, "outer repo"), "p4": (has_p4, "Perforce server and client"), "pygments": (has_pygments, "Pygments source highlighting library"), + "rst2html": (has_rst2html, "Docutils rst2html tool"), "svn": (has_svn, "subversion client and admin tools"), "svn-bindings": (has_svn_bindings, "subversion python bindings"), "symlink": (has_symlink, "symbolic links"), diff -r 2388ba07449b -r 812aaef40757 tests/printenv.py --- a/tests/printenv.py Wed Sep 16 16:11:44 2009 +0200 +++ b/tests/printenv.py Wed Sep 16 23:46:06 2009 +0200 @@ -46,12 +46,9 @@ elif url.startswith("remote:http"): os.environ["HG_URL"] = "remote:http" -if "HG_PENDING" in os.environ: - os.environ["HG_PENDING"] = os.environ["HG_PENDING"] and "true" - out.write("%s hook: " % name) for v in env: - out.write("%s=%s " % (v, os.environ[v])) + out.write("%s=%s " % (v, os.environ[v].replace(os.environ["HGTMP"], '$HGTMP'))) out.write("\n") out.close() diff -r 2388ba07449b -r 812aaef40757 tests/run-tests.py --- a/tests/run-tests.py Wed Sep 16 16:11:44 2009 +0200 +++ b/tests/run-tests.py Wed Sep 16 23:46:06 2009 +0200 @@ -167,16 +167,22 @@ else: vlog = lambda *msg: None + if options.tmpdir: + options.tmpdir = os.path.expanduser(options.tmpdir) + try: + os.makedirs(options.tmpdir) + except OSError, err: + if err.errno != errno.EEXIST: + raise + if options.jobs < 1: - print >> sys.stderr, 'ERROR: -j/--jobs must be positive' - sys.exit(1) + parser.error('--jobs must be positive') if options.interactive and options.jobs > 1: print '(--interactive overrides --jobs)' options.jobs = 1 if options.py3k_warnings: if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0): - print 'ERROR: Py3k warnings switch can only be used on Python 2.6+' - sys.exit(1) + parser.error('--py3k-warnings can only be used on Python 2.6+') return (options, args) diff -r 2388ba07449b -r 812aaef40757 tests/test-casefolding --- a/tests/test-casefolding Wed Sep 16 16:11:44 2009 +0200 +++ b/tests/test-casefolding Wed Sep 16 23:46:06 2009 +0200 @@ -31,6 +31,9 @@ hg rm a hg ci -Am removea echo A > A +# on linux hfs keeps the old case stored, force it +mv a aa +mv aa A hg ci -Am addA # Used to fail under case insensitive fs hg up -C 0 diff -r 2388ba07449b -r 812aaef40757 tests/test-convert-clonebranches.out --- a/tests/test-convert-clonebranches.out Wed Sep 16 16:11:44 2009 +0200 +++ b/tests/test-convert-clonebranches.out Wed Sep 16 23:46:06 2009 +0200 @@ -18,12 +18,12 @@ % incremental conversion 2 c1 pulling from branch0 into branch1 -2 changesets found +4 changesets found 1 c2 pulling from branch0 into branch2 -2 changesets found +4 changesets found 0 c3 pulling from branch2 into branch3 -3 changesets found +5 changesets found pulling from branch1 into branch3 1 changesets found diff -r 2388ba07449b -r 812aaef40757 tests/test-convert-git --- a/tests/test-convert-git Wed Sep 16 16:11:44 2009 +0200 +++ b/tests/test-convert-git Wed Sep 16 23:46:06 2009 +0200 @@ -42,7 +42,7 @@ echo b >> a commit -a -m t4.1 -git checkout -b other HEAD^ >/dev/null 2>/dev/null +git checkout -b other HEAD~ >/dev/null 2>/dev/null echo c > a echo a >> a commit -a -m t4.2 @@ -68,7 +68,7 @@ echo >> foo commit -a -m 'change foo' -git checkout -b Bar HEAD^ >/dev/null 2>/dev/null +git checkout -b Bar HEAD~ >/dev/null 2>/dev/null echo quux >> quux git add quux commit -a -m 'add quux' @@ -77,7 +77,7 @@ git add bar commit -a -m 'add bar' -git checkout -b Baz HEAD^ >/dev/null 2>/dev/null +git checkout -b Baz HEAD~ >/dev/null 2>/dev/null echo baz > baz git add baz commit -a -m 'add baz' @@ -89,7 +89,7 @@ echo bar >> bar commit -a -m 'change bar' -git checkout -b Foo HEAD^ >/dev/null 2>/dev/null +git checkout -b Foo HEAD~ >/dev/null 2>/dev/null echo >> foo commit -a -m 'change foo' diff -r 2388ba07449b -r 812aaef40757 tests/test-convert-tagsbranch-topology --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-convert-tagsbranch-topology Wed Sep 16 23:46:06 2009 +0200 @@ -0,0 +1,64 @@ +#!/bin/sh + +"$TESTDIR/hghave" git || exit 80 + +echo "[extensions]" >> $HGRCPATH +echo "convert=" >> $HGRCPATH +echo 'hgext.graphlog =' >> $HGRCPATH +echo '[convert]' >> $HGRCPATH +echo 'hg.usebranchnames = True' >> $HGRCPATH +echo 'hg.tagsbranch = tags-update' >> $HGRCPATH + +GIT_AUTHOR_NAME='test'; export GIT_AUTHOR_NAME +GIT_AUTHOR_EMAIL='test@example.org'; export GIT_AUTHOR_EMAIL +GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0000"; export GIT_AUTHOR_DATE +GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"; export GIT_COMMITTER_NAME +GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"; export GIT_COMMITTER_EMAIL +GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"; export GIT_COMMITTER_DATE + +count=10 +action() +{ + GIT_AUTHOR_DATE="2007-01-01 00:00:$count +0000" + GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" + git "$@" >/dev/null 2>/dev/null || echo "git command error" + count=`expr $count + 1` +} + +glog() +{ + hg glog --template '{rev} "{desc|firstline}" files: {files}\n' "$@" +} + +convertrepo() +{ + hg convert --datesort git-repo hg-repo +} + +# Build a GIT repo with at least 1 tag +mkdir git-repo +cd git-repo +git init >/dev/null 2>&1 +echo a > a +git add a +action commit -m "rev1" +action tag -m "tag1" tag1 +cd .. + +# Do a first conversion +convertrepo + +# Simulate upstream updates after first conversion +cd git-repo +echo b > a +git add a +action commit -m "rev2" +action tag -m "tag2" tag2 +cd .. + +# Perform an incremental conversion +convertrepo + +# Print the log +cd hg-repo +glog diff -r 2388ba07449b -r 812aaef40757 tests/test-convert-tagsbranch-topology.out --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-convert-tagsbranch-topology.out Wed Sep 16 23:46:06 2009 +0200 @@ -0,0 +1,19 @@ +initializing destination hg-repo repository +scanning source... +sorting... +converting... +0 rev1 +updating tags +scanning source... +sorting... +converting... +0 rev2 +updating tags +o 3 "update tags" files: .hgtags +| +| o 2 "rev2" files: a +| | +o | 1 "update tags" files: .hgtags + / +o 0 "rev1" files: a + diff -r 2388ba07449b -r 812aaef40757 tests/test-double-merge.out --- a/tests/test-double-merge.out Wed Sep 16 16:11:44 2009 +0200 +++ b/tests/test-double-merge.out Wed Sep 16 23:46:06 2009 +0200 @@ -1,9 +1,4 @@ created new head -changeset: 0:310fd17130da -user: test -date: Mon Jan 12 13:46:40 1970 +0000 -summary: add foo - changeset: 1:7731dad1c2b9 user: test date: Mon Jan 12 13:46:40 1970 +0000 diff -r 2388ba07449b -r 812aaef40757 tests/test-extension --- a/tests/test-extension Wed Sep 16 16:11:44 2009 +0200 +++ b/tests/test-extension Wed Sep 16 23:46:06 2009 +0200 @@ -55,6 +55,29 @@ hg foo echo 'barfoo = !' >> $HGRCPATH +# check that extensions are loaded in phases +cat > foo.py <> $HGRCPATH +echo 'bar = bar.py' >> $HGRCPATH + +# command with no output, we just want to see the extensions loaded +hg paths + +echo 'foo = !' >> $HGRCPATH +echo 'bar = !' >> $HGRCPATH + cd .. cat > empty.py < /dev/null || which rst2html.py) + +echo "checking for syntax errors in gendoc.py" +python $TESTDIR/../doc/gendoc.py > gendoc.txt || exit + +# We run rst2html over the file without adding "--halt warning" to +# make it report all errors instead of stopping on the first one. +echo "checking for parse errors with rst2html" +$RST2HTML gendoc.txt /dev/null diff -r 2388ba07449b -r 812aaef40757 tests/test-gendoc.out --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-gendoc.out Wed Sep 16 23:46:06 2009 +0200 @@ -0,0 +1,2 @@ +checking for syntax errors in gendoc.py +checking for parse errors with rst2html diff -r 2388ba07449b -r 812aaef40757 tests/test-grep --- a/tests/test-grep Wed Sep 16 16:11:44 2009 +0200 +++ b/tests/test-grep Wed Sep 16 23:46:06 2009 +0200 @@ -22,14 +22,14 @@ echo % simple hg grep port port echo % all -hg grep --all -nu port port +hg grep --traceback --all -nu port port echo % other hg grep import port hg cp port port2 hg commit -m 4 -u spam -d '5 0' -echo '% follow' -hg grep -f 'import$' port2 +echo % follow +hg grep --traceback -f 'import$' port2 echo deport >> port2 hg commit -m 5 -u eggs -d '6 0' hg grep -f --all -nu port port2 diff -r 2388ba07449b -r 812aaef40757 tests/test-hgwebdir --- a/tests/test-hgwebdir Wed Sep 16 16:11:44 2009 +0200 +++ b/tests/test-hgwebdir Wed Sep 16 23:46:06 2009 +0200 @@ -29,6 +29,7 @@ root=`pwd` cd .. + cat > paths.conf < paths.conf < paths.conf <> $DAEMON_PIDS +echo % test descend = False +"$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/?style=raw' +"$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '/t/?style=raw' + + cat > collections.conf <> eucjp.txt # Japanese kanji "Kyo" + +hg ci -Ama + +hgserveget () { + "$TESTDIR/killdaemons.py" + echo % HGENCODING="$1" hg serve + HGENCODING="$1" hg serve -p $HGPORT -d -n test --pid-file=hg.pid -E errors.log + cat hg.pid >> $DAEMON_PIDS + + echo % hgweb filerevision, html + "$TESTDIR/get-with-headers.py" localhost:$HGPORT "/file/tip/$2" \ + | grep '
' | $TESTDIR/printrepr.py + echo % errors encountered + cat errors.log +} + +hgserveget euc-jp eucjp.txt +hgserveget utf-8 eucjp.txt +hgserveget us-ascii eucjp.txt diff -r 2388ba07449b -r 812aaef40757 tests/test-highlight.out --- a/tests/test-highlight.out Wed Sep 16 16:11:44 2009 +0200 +++ b/tests/test-highlight.out Wed Sep 16 23:46:06 2009 +0200 @@ -1,4 +1,3 @@ -adding isolatin.txt adding primes.py % hg serve % hgweb filerevision, html @@ -12,7 +11,7 @@ -test: 3e1445510fe7 primes.py +test: 853dcd4de2a6 primes.py @@ -23,27 +22,27 @@ mercurial

test

-

view primes.py @ 0:3e1445510fe7

+

view primes.py @ 0:853dcd4de2a6

- - - - - - % hgweb fileannotate, html 200 Script output follows @@ -228,28 +140,28 @@ mercurial

test

-

annotate primes.py @ 0:3e1445510fe7

+

annotate primes.py @ 0:853dcd4de2a6