changeset 0:a97b17f1bac7 0.12

TracMercurial: started new Mercurial repository branch:0.12 was initialized from: {{{ svn export https://svn.edgewall.org/repos/trac/plugins/0.12/mercurial-plugin }}} at revision r10936. i.e. http://trac.edgewall.org/changeset/10936/plugins/0.12/mercurial-plugin
author Christian Boos <christian.boos@free.fr>
date Sat, 09 Jun 2012 19:27:02 +0200
parents
children b30690538379 dcdc4d05f5fa
files COPYING README setup.cfg setup.py tracext/__init__.py tracext/hg/__init__.py tracext/hg/backend.py tracext/hg/hooks.py tracext/hg/locale/fr/LC_MESSAGES/tracmercurial.po tracext/hg/locale/messages.pot
diffstat 10 files changed, 2099 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/COPYING	Sat Jun 09 19:27:02 2012 +0200
@@ -0,0 +1,340 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year  name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README	Sat Jun 09 19:27:02 2012 +0200
@@ -0,0 +1,94 @@
+= Mercurial Plugin for Trac (#1847) =
+
+Please see the online Wiki page for this extension:
+
+  http://trac.edgewall.org/wiki/TracMercurial
+
+== Minimal Installation Instructions ==
+
+=== Trac ===
+
+This plugin for Trac 0.12 doesn't yet work with Trac ''trunk'',
+but requires the ''multirepos'' branch (to be integrated in trunk 
+shortly):
+{{{
+svn co http://svn.edgewall.com/repos/trac/sandbox/multirepos
+}}}
+
+and install from there:
+{{{
+$ cd multirepos
+$ python setup.py egg_info
+$ python setup.py install
+}}}
+
+=== TracMercurial ===
+
+Go into the working copy root (i.e. the folder containing this README file),
+then:
+ - either do `python setup.py bdist_egg`, which creates
+   an .egg file in the `dist` subfolder. Copy that .egg file in the
+   `plugins` subfolder of your Trac environment
+ - or do `python setup.py install`, which creates the .egg and install it
+   below the site-packages folder of your Python installation.
+   You'll then need to enable the plugin in the `[components]` section of
+   your trac.ini file, see below.
+
+
+== Configuration ==
+
+The configuration has to be done on the Trac side, 
+there's nothing to do on the Mercurial repository side,
+except for the fact that the repository should be made 
+accessible as a local repository. 
+Thanks to the distributed nature of Mercurial, that's
+always possible (if the repository is not already local,
+simply `hg clone` it).
+
+
+=== Setting up the mercurial plugin ===
+
+For general instructions about plugins, see also TracPlugins.
+
+If you installed the egg globally and you're modifying an 
+existing Trac environment to use the Mercurial backend,
+then you have to explicitely ''enable'' the plugin in TracIni:
+{{{
+[components]
+tracext.hg.* = enabled
+}}}
+
+
+=== Setting up a Trac environment ===
+
+It is now recommended to use the new set of trac-admin commands 
+`repository ...` to create, delete and rename references to repositories
+(note that Trac will never do anything to the actual Mercurial repositories 
+beyond accessing them for read operations).
+
+The old default settings for defining the default repository are still
+supported but deprecated (use an empty repository name or create an empty 
+alias, see `trac-admin help repository alias`).
+
+(Old-style) the trac.ini file contains `[trac]` section similar to the 
+following:
+{{{
+[trac]
+repository_type = hg
+repository_dir = /path/to/my/hg/repository
+}}}
+
+There's also a few Mercurial specific settings in TracIni:
+{{{
+[hg]
+# -- Show revision number in addition to the changeset hash
+show_rev = yes
+
+# -- Changeset hash format
+node_format = short
+# hex:   Show the full SHA1 hash 
+# short: Show a shortened hash for the changesets 
+}}}
+
+
+'' -- ChristianBoos ''
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup.cfg	Sat Jun 09 19:27:02 2012 +0200
@@ -0,0 +1,24 @@
+[egg_info]
+tag_build = dev
+tag_svn_revision = true
+
+[extract_messages]
+add_comments = TRANSLATOR:
+msgid_bugs_address = cboos@edgewall.org
+output_file = tracext/hg/locale/messages.pot
+keywords = _ ngettext:1,2 N_ tag_
+width = 72
+
+[init_catalog]
+input_file = tracext/hg/locale/messages.pot
+output_dir = tracext/hg/locale
+domain = tracmercurial
+
+[compile_catalog]
+directory = tracext/hg/locale
+domain = tracmercurial
+
+[update_catalog]
+input_file = tracext/hg/locale/messages.pot
+output_dir = tracext/hg/locale
+domain = tracmercurial
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/setup.py	Sat Jun 09 19:27:02 2012 +0200
@@ -0,0 +1,59 @@
+#!/usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+#
+# Copyright (C) 2005-2012 Edgewall Software
+# Copyright (C) 2005-2012 Christian Boos <cboos@edgewall.org>
+# All rights reserved.
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://trac.edgewall.org/log/.
+#
+# Author: Christian Boos <cboos@edgewall.org>
+
+from setuptools import setup, find_packages
+
+extra = {}
+
+try:
+    import babel
+
+    extra['message_extractors'] = {
+        'tracext': [
+            ('**.py',                'python', None),
+        ],
+    }
+
+    from trac.util.dist import get_l10n_cmdclass
+    extra['cmdclass'] = get_l10n_cmdclass()
+
+except ImportError:
+    pass
+    
+TracMercurial = 'http://trac.edgewall.org/wiki/TracMercurial'
+
+setup(name='TracMercurial',
+      install_requires='Trac >=0.12dev-r9125',
+      description='Mercurial plugin for Trac multirepos branch',
+      keywords='trac scm plugin mercurial hg',
+      version='0.12.0.29',
+      url=TracMercurial,
+      license='GPL',
+      author='Christian Boos',
+      author_email='cboos@edgewall.org',
+      long_description="""
+      This plugin for Trac 0.12 provides support for the Mercurial SCM.
+
+      See %s for more details.
+      """ % TracMercurial,
+      namespace_packages=['tracext'],
+      packages=['tracext', 'tracext.hg'],
+      package_data={
+          '': ['COPYING', 'README'],
+          'tracext.hg': ['locale/*.*', 'locale/*/LC_MESSAGES/*.*'],
+          },
+      entry_points={'trac.plugins': 'hg = tracext.hg.backend'},
+      **extra)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tracext/__init__.py	Sat Jun 09 19:27:02 2012 +0200
@@ -0,0 +1,1 @@
+__import__('pkg_resources').declare_namespace(__name__)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tracext/hg/__init__.py	Sat Jun 09 19:27:02 2012 +0200
@@ -0,0 +1,2 @@
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tracext/hg/backend.py	Sat Jun 09 19:27:02 2012 +0200
@@ -0,0 +1,1242 @@
+# -*- coding: iso-8859-1 -*-
+#
+# Copyright (C) 2005-2012 Edgewall Software
+# Copyright (C) 2005-2012 Christian Boos <cboos@edgewall.org>
+# All rights reserved.
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://trac.edgewall.org/log/.
+#
+# Author: Christian Boos <cboos@edgewall.org>
+
+from bisect import bisect
+from datetime import datetime
+import os
+import time
+import posixpath
+import re
+import sys
+
+import pkg_resources
+
+from genshi.builder import tag
+
+from trac.core import *
+from trac.config import BoolOption, ChoiceOption, ListOption, PathOption
+from trac.env import ISystemInfoProvider
+from trac.util import arity
+from trac.util.datefmt import FixedOffset, utc
+from trac.util.text import exception_to_unicode, shorten_line, to_unicode
+from trac.util.translation import domain_functions
+from trac.versioncontrol.api import Changeset, Node, Repository, \
+                                    IRepositoryConnector, RepositoryManager, \
+                                    NoSuchChangeset, NoSuchNode
+from trac.versioncontrol.web_ui import IPropertyRenderer, RenderedProperty
+from trac.wiki import IWikiSyntaxProvider
+
+# -- plugin i18n
+
+gettext, _, tag_, N_, add_domain = \
+    domain_functions('tracmercurial', 
+                     ('gettext', '_', 'tag_', 'N_', 'add_domain'))
+
+# -- Using internal Mercurial API, see:
+#     * http://mercurial.selenic.com/wiki/MercurialApi
+#     * http://mercurial.selenic.com/wiki/ApiChanges
+
+hg_import_error = []
+try:
+    # The new `demandimport` mechanism doesn't play well with code relying
+    # on the `ImportError` exception being caught.
+    # OTOH, we can't disable `demandimport` because mercurial relies on it
+    # (circular reference issue). So for now, we activate `demandimport`
+    # before loading mercurial modules, and desactivate it afterwards.
+    #
+    # See http://www.selenic.com/mercurial/bts/issue605
+    
+    try:
+        from mercurial import demandimport
+        demandimport.enable();
+    except ImportError, hg_import_error:
+        demandimport = None
+
+    from mercurial import hg
+    from mercurial.context import filectx
+    from mercurial.ui import ui
+    from mercurial.node import hex, short, nullid, nullrev
+    from mercurial.util import pathto, cachefunc
+    from mercurial import cmdutil
+    from mercurial import encoding
+    from mercurial import extensions
+    from mercurial.extensions import loadall
+    from mercurial.error import RepoLookupError
+
+    # Note: due to the nature of demandimport, there will be no actual 
+    # import error until those symbols get accessed, so here we go:
+    for sym in ("filectx ui hex short nullid pathto "
+                "cachefunc loadall".split()):
+        if repr(globals()[sym]) == "<unloaded module '%s'>" % sym:
+            hg_import_error.append(sym)
+    if hg_import_error:
+        hg_import_error = "Couldn't import symbols: "+','.join(hg_import_error)
+
+    # Mercurial versions >= 1.2 won't have mercurial.repo.RepoError anymore
+    from mercurial.repo import RepoError
+    from mercurial.revlog import LookupError as HgLookupError
+    if repr(RepoError) == "<unloaded module 'RepoError'>":
+        from mercurial.error import RepoError, LookupError as HgLookupError
+
+    # Force local encoding to be non-lossy (#7217)
+    os.environ['HGENCODING'] = 'utf-8'
+    encoding.tolocal = str
+    
+    if demandimport:
+        demandimport.disable()
+
+    # API compatibility (watch http://mercurial.selenic.com/wiki/ApiChanges)
+
+    if hasattr(cmdutil, 'match'):
+        def match(ctx, *args, **kwargs):
+            return cmdutil.match(ctx._repo, *args, **kwargs)
+    else:
+        from mercurial.scmutil import match
+
+    
+except ImportError, e:
+    hg_import_error = e
+    ui = object
+
+
+### Helpers
+
+def checked_encode(u, encodings, check):
+    """Convert `unicode` to `str` trying several encodings until a
+    condition is met.
+
+    :param u: the `unicode` input
+    :param encodings: the list of possible encodings
+    :param check: the predicate to satisfy
+
+    :return: the first converted `str` if `check(s)` is `True`,
+             otherwise `None`.  Note that if no encoding is able to
+             successfully convert the input, the empty string will be
+             given to `check`, which can accept it as valid or not.
+    """
+    s = u
+    if isinstance(u, unicode): 
+        for enc in encodings:
+            try:
+                s = u.encode(enc)
+                if check(s):
+                    return s
+            except UnicodeEncodeError:
+                pass
+        else:
+            s = ''
+    if check(s):
+        return s
+
+        
+class trac_ui(ui):
+    # Note: will be dropped in 0.13, see MercurialConnector._setup_ui
+    def __init__(self, *args, **kwargs):
+        ui.__init__(self, *args)
+        self.setconfig('ui', 'interactive', 'off')
+        self.log = kwargs.get('log', args and args[0].log or None)
+        
+    def write(self, *args, **opts):
+        for a in args:
+            self.log.info('(mercurial status) %s', a)
+
+    def write_err(self, *args, **opts):
+        for a in args:
+            self.log.warn('(mercurial warning) %s', a)
+
+    def plain(self, *args, **kw):
+        return False # so that '[hg] hgrc' file can specify [ui] options
+
+    def interactive(self): 
+        return False
+
+    def readline(self):
+        raise TracError('*** Mercurial ui.readline called ***')
+
+
+### Components
+
+class CsetPropertyRenderer(Component):
+
+    implements(IPropertyRenderer)
+
+    def match_property(self, name, mode):
+        return (name.startswith('hg-') and
+                name[3:] in ('Parents', 'Children', 'Tags', 'Branch') and
+                mode == 'revprop') and 4 or 0
+    
+    def render_property(self, name, mode, context, props):
+        return RenderedProperty(name=gettext(name[3:] + ':'), 
+                name_attributes=[("class", "property")],
+                content=self._render_property(name, mode, context, props))
+
+    def _render_property(self, name, mode, context, props):
+        repos, revs = props[name]
+        
+        if name in ('hg-Parents', 'hg-Children'):
+            label = repos.display_rev
+        else:
+            label = lambda rev: rev
+        
+        def link(rev):
+            chgset = repos.get_changeset(rev)
+            return tag.a(label(rev), class_="changeset",
+                         title=shorten_line(chgset.message),
+                         href=context.href.changeset(rev, repos.reponame))
+        
+        if name == 'hg-Parents' and len(revs) == 2: # merge
+            new = context.resource.id
+            parent_links = [
+                    (link(rev), ' (',
+                     tag.a('diff', title=_("Diff against this parent "
+                           "(show the changes merged from the other parents)"),
+                           href=context.href.changeset(new, repos.reponame, 
+                                                       old=rev)), ')')
+                           for rev in revs]
+            return tag([(parent, ', ') for parent in parent_links[:-1]],
+                       parent_links[-1], tag.br(),
+                       tag.span(tag_("Note: this is a %(merge)s changeset, "
+                                     "the changes displayed below correspond "
+                                     "to the merge itself.",
+                                     merge=tag.strong('merge')),
+                                class_='hint'), tag.br(), 
+                       # TODO: only keep chunks present in both parents 
+                       #       (conflicts) or in none (extra changes)
+                       # tag.span('No changes means the merge was clean.',
+                       #         class_='hint'), tag.br(), 
+                       tag.span(tag_("Use the %(diff)s links above to see all "
+                                     "the changes relative to each parent.",
+                                     diff=tag.tt('(diff)')),
+                                class_='hint'))
+        return tag([tag(link(rev), ', ') for rev in revs[:-1]],
+                   link(revs[-1]))
+
+
+class HgExtPropertyRenderer(Component):
+
+    implements(IPropertyRenderer)
+
+    def match_property(self, name, mode):
+       return name in ('hg-transplant_source', 'hg-convert_revision') and \
+           mode == 'revprop' and 4 or 0
+    
+    def render_property(self, name, mode, context, props):
+        repos, value = props[name]
+        if name == 'hg-transplant_source':
+            try:
+                ctx = self.changectx(value)
+                chgset = MercurialChangeset(repos, ctx)
+                href = context.href.changeset(ctx.hex(), repos.reponame)
+                link = tag.a(repos._display(ctx), class_="changeset",
+                             title=shorten_line(chgset.message), href=href)
+            except NoSuchChangeset:
+                link = tag.a(hex(value), class_="missing changeset",
+                             title=_("no such changeset"), rel="nofollow")
+            return RenderedProperty(name=_("Transplant:"), content=link,
+                                    name_attributes=[("class", "property")])
+        
+        elif name == 'hg-convert_revision':
+            text = repos.to_u(value)
+            if value.startswith('svn:'):
+                # e.g. 'svn:af82e41b-90c4-0310-8c96-b1721e28e2e2/trunk@9517'
+                uuid = value[:40]
+                rev = value.rsplit('@', 1)[-1]
+                for r in RepositoryManager(self.env).get_real_repositories():
+                    if r.name.startswith(uuid + ':'):
+                        path = r.reponame
+                        href = context.href.changeset(rev, path or None)
+                        text = tag.a('[%s%s]' % (rev, path and '/' + path),
+                                     class_='changeset', href=href,
+                                     title=_('Changeset in source repository'))
+                        break
+            return RenderedProperty(name=_('Convert:'), content=text,
+                                    name_attributes=[("class", "property")])
+
+
+class HgDefaultPropertyRenderer(Component):
+
+    implements(IPropertyRenderer)
+
+    def match_property(self, name, mode):
+       return name.startswith('hg-') and mode == 'revprop' and 1 or 0
+    
+    def render_property(self, name, mode, context, props):
+        return RenderedProperty(name=name[3:] + ':', 
+                                name_attributes=[("class", "property")],
+                                content=self._render_property(name, mode, 
+                                                              context, props))
+
+    def _render_property(self, name, mode, context, props):
+        repos, value = props[name]
+        try:
+            return unicode(value)
+        except UnicodeDecodeError:
+            if len(value) <= 100:
+                return tag.tt(''.join(("%02x" % ord(c)) for c in value))
+            else:
+                return tag.em(_("(binary, size greater than 100 bytes)"))
+
+
+class MercurialConnector(Component):
+
+    implements(ISystemInfoProvider, IRepositoryConnector, IWikiSyntaxProvider)
+
+    encoding = ListOption('hg', 'encoding', 'utf-8', doc="""
+        Encoding that should be used to decode filenames, file
+        content, and changeset metadata.  If multiple encodings are
+        used for these different situations (or even multiple
+        encodings were used for filenames), simply specify a list of
+        encodings which will be tried in turn (''since 0.12.0.24'').
+        """)
+
+    show_rev = BoolOption('hg', 'show_rev', True, doc="""
+        Show decimal revision in front of the commit SHA1 hash.  While
+        this number is specific to the particular clone used to browse
+        the repository, this can sometimes give an useful hint about
+        the relative "age" of a revision.
+        """)
+
+    node_format = ChoiceOption('hg', 'node_format', ['short', 'hex'], doc="""
+        Specify how the commit SHA1 hashes should be
+        displayed. Possible choices are: 'short', the SHA1 hash is
+        abbreviated to its first 12 digits, or 'hex', the hash is
+        shown in full.
+        """)
+
+    hgrc = PathOption('hg', 'hgrc', '', doc="""
+        Optional path to an hgrc file which will be used to specify
+        extra Mercurial configuration options (see
+        http://www.selenic.com/mercurial/hgrc.5.html).
+        """)
+
+    def __init__(self):
+        self.ui = None
+        locale_dir = pkg_resources.resource_filename(__name__, 'locale')
+        add_domain(self.env.path, locale_dir)
+        self._version = self._version_info = None
+        if not hg_import_error:
+            try:
+                from mercurial.version import get_version
+                self._version = get_version()
+            except ImportError: # gone in Mercurial 1.2 (hg:9626819b2e3d)
+                from mercurial.util import version
+                self._version = version()
+            # development version assumed to be always the ''newest'' one,
+            # i.e. old development version won't be supported
+            self._version_info = (999, 0, 0) 
+            m = re.match(r'(\d+)\.(\d+)(?:\.(\d+))?', self._version or '')
+            if m:
+                self._version_info = tuple([int(n or 0) for n in m.groups()])
+
+    def _setup_ui(self, hgrc_path):
+        # Starting with Mercurial 1.3 we can probably do simply:
+        #
+        #   ui = baseui.copy() # there's no longer a parent/child concept
+        #   ui.setconfig('ui', 'interactive', 'off')
+        # 
+        self.ui = trac_ui(log=self.log)
+
+        # (code below adapted from mercurial.dispatch._dispatch)
+
+        # read the local repository .hgrc into a local ui object
+        if hgrc_path:
+            if not os.path.exists(hgrc_path):
+                self.log.warn("'[hg] hgrc' file (%s) not found ", hgrc_path)
+            try:
+                self.ui = trac_ui(self.ui, log=self.log)
+                self.ui.check_trusted = False
+                self.ui.readconfig(hgrc_path)
+            except IOError, e:
+                self.log.warn("'[hg] hgrc' file (%s) can't be read: %s", 
+                              hgrc_path, e)
+
+        extensions.loadall(self.ui)
+        if hasattr(extensions, 'extensions'):
+            for name, module in extensions.extensions():
+                # setup extensions
+                extsetup = getattr(module, 'extsetup', None)
+                if extsetup:
+                    if arity(extsetup) == 1:
+                        extsetup(self.ui)
+                    else:
+                        extsetup()
+
+    # ISystemInfoProvider methods
+
+    def get_system_info(self):
+        if self._version is not None:
+            yield 'Mercurial', self._version
+
+    # IRepositoryConnector methods
+    
+    def get_supported_types(self):
+        """Support for `repository_type = hg`"""
+        if hg_import_error:
+            self.error = hg_import_error
+            yield ("hg", -1)
+        else:
+            yield ("hg", 8)
+
+    def get_repository(self, type, dir, params):
+        """Return a `MercurialRepository`"""
+        if not self.ui:
+            self._setup_ui(self.hgrc)
+        repos = MercurialRepository(dir, params, self.log, self)
+        repos.version_info = self._version_info
+        return repos
+
+    # IWikiSyntaxProvider methods
+    
+    def get_wiki_syntax(self):
+        yield (r'!?(?P<hgrev>[0-9a-f]{12,40})(?P<hgpath>/\S+\b)?',
+               lambda formatter, label, match:
+                   self._format_link(formatter, 'cset', match.group(0),
+                                     match.group(0), match))
+
+    def get_link_resolvers(self):
+        yield ('cset', self._format_link)
+        yield ('chgset', self._format_link)
+        yield ('branch', self._format_link)    # go to the corresponding head
+        yield ('tag', self._format_link)
+
+    def _format_link(self, formatter, ns, rev, label, fullmatch=None):
+        reponame = path = ''
+        repos = None
+        rm = RepositoryManager(self.env)
+        try:
+            if fullmatch:
+                rev = fullmatch.group('hgrev')
+                path = fullmatch.group('hgpath')
+                if path:
+                    reponame, repos, path = \
+                        rm.get_repository_by_path(path.strip('/'))
+            if not repos:
+                context = formatter.context
+                while context:
+                    if context.resource.realm in ('source', 'changeset'):
+                        reponame = context.resource.parent.id
+                        break
+                    context = context.parent
+                repos = rm.get_repository(reponame)
+            if repos:
+                if ns == 'branch':
+                    for b, n in repos.repo.branchtags().items():
+                        if repos.to_u(b) == rev:
+                            rev = repos.repo.changelog.rev(n)
+                            break
+                    else:
+                        raise NoSuchChangeset(rev)
+                chgset = repos.get_changeset(rev)
+                return tag.a(label, class_="changeset",
+                             title=shorten_line(chgset.message),
+                             href=formatter.href.changeset(rev, reponame,
+                                                           path))
+            raise TracError("Repository not found")
+        except NoSuchChangeset, e:
+            errmsg = to_unicode(e)
+        except TracError:
+            errmsg = _("Repository '%(repo)s' not found", repo=reponame)
+        return tag.a(label, class_="missing changeset",
+                     title=errmsg, rel="nofollow")
+
+
+
+### Version Control API
+    
+class MercurialRepository(Repository):
+    """Repository implementation based on the mercurial API.
+
+    This wraps an hg.repository object.  The revision navigation
+    follows the branches, and defaults to the first parent/child in
+    case there are many.  The eventual other parents/children are
+    listed as additional changeset properties.
+    """
+
+    def __init__(self, path, params, log, connector):
+        self.ui = connector.ui
+        self._show_rev = connector.show_rev
+        self._node_fmt = connector.node_format
+        # TODO 0.13: per repository ui and options
+
+        # -- encoding
+        encoding = connector.encoding
+        if not encoding:
+            encoding = ['utf-8']
+        # verify given encodings
+        for enc in encoding:
+            try:
+                u''.encode(enc)
+            except LookupError, e:
+                log.warning("'[hg] encoding' (%r) not valid", e)
+        if 'latin1' not in encoding:
+            encoding.append('latin1')
+        self.encoding = encoding
+
+        def to_u(s):
+            if isinstance(s, unicode):
+                return s
+            for enc in encoding:
+                try:
+                    return unicode(s, enc)
+                except UnicodeDecodeError:
+                    pass
+        def to_s(u):
+            if isinstance(u, str):
+                return u
+            for enc in encoding:
+                try:
+                    return u.encode(enc)
+                except UnicodeEncodeError:
+                    pass
+        self.to_u = to_u
+        self.to_s = to_s
+
+        # -- repository path
+        self.path = str_path = path
+        # Note: `path` is a filesystem path obtained either from the
+        #       trac.ini file or from the `repository` table, so it's
+        #       normally an `unicode` instance. '[hg] encoding'
+        #       shouldn't play a role here, but we can nevertheless
+        #       use that as secondary choices.
+        fsencoding = [sys.getfilesystemencoding() or 'utf-8'] + encoding
+        str_path = checked_encode(path, fsencoding, os.path.exists)
+        if str_path is None:
+            raise TracError(_("Repository path '%(path)s' does not exist.", 
+                              path=path))
+        try:
+            self.repo = hg.repository(ui=self.ui, path=str_path)
+        except RepoError, e:
+            version = connector._version
+            error = exception_to_unicode(e)
+            log.error("Mercurial %s can't open repository (%s)", version, error)
+            raise TracError(_("'%(path)s' does not appear to contain a"
+                              " repository (Mercurial %(version)s says "
+                              "%(error)s)",
+                              path=path, version=version, error=error))
+        Repository.__init__(self, 'hg:%s' % path, params, log)
+
+    def from_hg_time(self, timeinfo):
+        time, tz = timeinfo
+        tzinfo = FixedOffset(tz / 60, 'GMT %d:00' % (tz / 3600))
+        return datetime.fromtimestamp(time, tzinfo)
+
+    def changectx(self, rev=None):
+        """Produce a Mercurial `context.changectx` from given Trac revision."""
+        return self.repo[self.short_rev(rev)]
+
+    def close(self):
+        self.repo = None
+
+    def normalize_path(self, path):
+        """Remove leading "/" (even at root)"""
+        return path and path.strip('/') or ''
+
+    def normalize_rev(self, rev):
+        """Return the full hash for the specified rev."""
+        return self.changectx(rev).hex()
+
+    def short_rev(self, rev):
+        """Find Mercurial revision number corresponding to given Trac revision.
+
+        :param rev: any kind of revision specification, either an
+                    `unicode` string, or a revision number.  If `None`
+                    or '', latest revision will be returned.
+
+        :return: an integer revision
+        """
+        repo = self.repo
+        if rev == 0:
+            return rev
+        if not rev:
+            return len(repo) - 1
+        if isinstance(rev, (long, int)):
+            return rev
+        if rev[0] != "'": # "'11:11'" can be a tag name?
+            rev = rev.split(':', 1)[0]
+            if rev == '-1':
+                return nullrev
+            if rev.isdigit():
+                r = int(rev)
+                if 0 <= r < len(repo):
+                    return r
+        try:
+            return repo[repo.lookup(self.to_s(rev))].rev()
+        except (HgLookupError, RepoError):
+            raise NoSuchChangeset(rev)
+
+    def display_rev(self, rev):
+        return self._display(self.changectx(rev))
+
+    def _display(self, ctx):
+        """Return user-readable revision information for node `n`.
+
+        The specific format depends on the `node_format` and
+        `show_rev` options.
+        """
+        nodestr = self._node_fmt == "hex" and ctx.hex() or str(ctx)
+        if self._show_rev:
+            return '%s:%s' % (ctx.rev(), nodestr)
+        else:
+            return nodestr
+
+    def get_quickjump_entries(self, rev):
+        # map ctx to (unicode) branch
+        branches = {}
+        closed_branches = {}
+        for b, n in self.repo.branchtags().items():
+            b = self.to_u(b)
+            ctx = self.repo[n]
+            if 'close' in ctx.extra():
+                closed_branches[ctx] = b
+            else:
+                branches[ctx] = b
+        # map node to tag names
+        tags = {}
+        tagslist = self.repo.tagslist()
+        for tag, n in tagslist:
+            tags.setdefault(n, []).append(self.to_u(tag))
+        def taginfo(ctx):
+            t = tags.get(ctx.node())
+            if t:
+                return ' (%s)' % ', '.join(t)
+            else:
+                return ''
+        # branches
+        for ctx, b in sorted(branches.items(), reverse=True, 
+                             key=lambda (ctx, b): ctx.rev()):
+            yield ('branches', b + taginfo(ctx), '/', self._display(ctx))
+        # heads
+        for n in self.repo.heads():
+            ctx = self.repo[n]
+            if ctx not in branches and ctx not in closed_branches:
+                h = self._display(ctx)
+                yield ('extra heads', h + taginfo(ctx), '/', h)
+        # tags
+        for t, n in reversed(tagslist):
+            try:
+                yield ('tags', ', '.join(tags.pop(n)),  # FIXME: pop?
+                       '/', self._display(self.repo[n]))
+            except (KeyError, RepoLookupError):
+                pass
+        # closed branches
+        for ctx, b in sorted(closed_branches.items(), reverse=True, 
+                             key=lambda (ctx, b): ctx.rev()):
+            yield ('closed branches', b + taginfo(ctx), '/', self._display(ctx))
+
+    def get_path_url(self, path, rev):
+        url = self.params.get('url')
+        if url and (not path or path == '/'):
+            if not rev:
+                return url
+            branch = self.changectx(rev).branch()
+            if branch == 'default':
+                return url
+            return url + '#' + self.to_u(branch) # URL for cloning that branch
+
+            # Note: link to matching location in Mercurial's file browser 
+            #rev = rev is not None and short(n) or 'tip'
+            #return '/'.join([url, 'file', rev, path])
+  
+    def get_changeset(self, rev):
+        return MercurialChangeset(self, self.changectx(rev))
+
+    def get_changeset_uid(self, rev):
+        return self.changectx(rev).hex()
+
+    def get_changesets(self, start, stop):
+        """Follow each head and parents in order to get all changesets
+
+        FIXME: this can only be handled correctly and efficiently by
+        using the db repository cache.
+        
+        The code below is only an heuristic, and doesn't work in the
+        general case. E.g. look at the mercurial repository timeline
+        for 2006-10-18, you need to give ''38'' daysback in order to
+        see the changesets from 2006-10-17...
+
+        This is because of the following '''linear''' sequence of csets:
+          - 3445:233c733e4af5         10/18/2006 9:08:36 AM mpm
+          - 3446:0b450267cf47         9/10/2006 3:25:06 AM  hopper
+          - 3447:ef1032c223e7         9/10/2006 3:25:06 AM  hopper
+          - 3448:6ca49c5fe268         9/10/2006 3:25:07 AM  hopper
+          - 3449:c8686e3f0291         10/18/2006 9:14:26 AM hopper
+
+          This is most probably because [3446:3448] correspond to old
+          changesets that have been ''hg import''ed, with their
+          original dates.
+        """
+        seen = {nullrev: 1}
+        seeds = [self.repo[n] for n in self.repo.heads()]
+        while seeds:
+            ctx = seeds.pop(0)
+            time = self.from_hg_time(ctx.date())
+            if time < start:
+                continue # assume no ancestor is younger and use next seed
+                # (and that assumption is wrong for 3448 in the example above)
+            elif time < stop:
+                yield MercurialChangeset(self, ctx)
+            for p in ctx.parents():
+                if p.rev() not in seen:
+                    seen[p.rev()] = 1
+                    seeds.append(p)
+
+    def get_node(self, path, rev=None):
+        return MercurialNode(self, self.normalize_path(path), 
+                             self.changectx(rev))
+
+    def get_oldest_rev(self):
+        return 0
+
+    def get_youngest_rev(self):
+        return self.changectx().hex()
+    
+    def previous_rev(self, rev, path=''): # FIXME: path ignored for now
+        for p in self.changectx(rev).parents():
+            if p:
+                return p.hex() # always follow first parent
+    
+    def next_rev(self, rev, path=''):
+        ctx = self.changectx(rev)
+        if path: # might be a file
+            fc = filectx(self.repo, self.to_s(path), ctx.node())
+            # Note: the simpler form below raises an HgLookupError for a dir
+            # fc = ctx.filectx(self.to_s(path))
+            if fc: # it is a file
+                for c in fc.children():
+                    return c.hex()
+                else:
+                    return None
+        # it might be a directory (not supported for now) FIXME
+        for c in ctx.children():
+            return c.hex() # always follow first child
+   
+    def rev_older_than(self, rev1, rev2):
+        # FIXME use == and ancestors?
+        return self.short_rev(rev1) < self.short_rev(rev2)
+
+#    def get_path_history(self, path, rev=None, limit=None):
+#      (not really relevant for Mercurial)
+
+    def get_changes(self, old_path, old_rev, new_path, new_rev,
+                    ignore_ancestry=1):
+        """Generates changes corresponding to generalized diffs.
+        
+        Generator that yields change tuples (old_node, new_node, kind,
+        change) for each node change between the two arbitrary
+        (path,rev) pairs.
+
+        The old_node is assumed to be None when the change is an ADD,
+        the new_node is assumed to be None when the change is a
+        DELETE.
+        """
+        old_node = new_node = None
+        old_node = self.get_node(old_path, old_rev)
+        new_node = self.get_node(new_path, new_rev)
+        # check kind, both should be same.
+        if new_node.kind != old_node.kind:
+            raise TracError(
+                _("Diff mismatch: "
+                  "Base is a %(okind)s (%(opath)s in revision %(orev)s) "
+                  "and Target is a %(nkind)s (%(npath)s in revision %(nrev)s).",
+                  okind=old_node.kind, opath=old_path, orev=old_rev,
+                  nkind=new_node.kind, npath=new_path, nrev=new_rev))
+        # Correct change info from changelog(revlog)
+        # Finding changes between two revs requires tracking back
+        # several routes.
+                              
+        if new_node.isdir:
+            # TODO: Should we follow rename and copy?
+            # As temporary workaround, simply compare entry names.
+            changes = []
+            str_new_path = self.to_s(new_path)
+            str_old_path = self.to_s(old_path)
+            # additions and edits
+            for str_path in new_node.manifest:
+                # changes out of scope
+                if str_new_path and not str_path.startswith(str_new_path + '/'):
+                    continue
+                # 'added' if not present in old manifest
+                str_op = str_old_path + str_path[len(str_new_path):]
+                if str_op not in old_node.manifest:
+                    changes.append((str_path, None, new_node.subnode(str_path),
+                                    Node.FILE, Changeset.ADD))
+                elif old_node.manifest[str_op] != new_node.manifest[str_path]:
+                    changes.append((str_path, old_node.subnode(str_op),
+                                    new_node.subnode(str_path),
+                                    Node.FILE, Changeset.EDIT))
+            # deletions
+            for str_path in old_node.manifest:
+                # changes out of scope
+                if str_old_path and not str_path.startswith(str_old_path + '/'):
+                    continue
+                # 'deleted' if not present in new manifest
+                str_np = str_new_path + str_path[len(str_old_path):]
+                if str_np not in new_node.manifest:
+                    changes.append((str_path, old_node.subnode(str_np), None,
+                                    Node.FILE, Changeset.DELETE))
+            # Note: `str_path` only used as a key, no need to convert to_u
+            for change in sorted(changes, key=lambda c: c[0]):
+                yield(change[1], change[2], change[3], change[4])
+        else:
+            if old_node.manifest[old_node.str_path] != \
+                   new_node.manifest[new_node.str_path]:
+                yield(old_node, new_node, Node.FILE, Changeset.EDIT)
+            
+
+class MercurialNode(Node):
+    """A path in the repository, at a given revision.
+
+    It encapsulates the repository manifest for the given revision.
+
+    As directories are not first-class citizens in Mercurial,
+    retrieving revision information for directory can be much slower
+    than for files, except when created as a `subnode()` of an
+    existing MercurialNode.
+    """
+    
+    filectx = dirnode = None
+
+    def __init__(self, repos, path, changectx, 
+                 manifest=None, dirctx=None, str_entry=None):
+        """
+        :param repos: the `MercurialRepository`
+        :param path: the `unicode` path corresponding to this node
+        :param rev: requested revision (i.e. "browsing at")
+        :param changectx: the `changectx` for the  "requested" revision
+
+        The following parameters are passed when creating a subnode
+        instance:
+
+        :param manifest: `manifest` object from parent `MercurialNode`
+        :param dirctx: `changectx` for a directory determined by
+                       parent `MercurialNode`
+        :param str_entry: entry name if node created from parent node
+        """
+        repo = repos.repo
+        self.repos = repos
+        self.changectx = changectx
+        self.manifest = manifest or changectx.manifest()
+        str_entries = []
+
+        if path == '' or path == '/':
+            str_path = ''
+        elif dirctx:
+            str_path = str_entry
+        else:
+            # Fast path: check for existing file
+            str_path = checked_encode(path, repos.encoding, 
+                                      lambda s: s in self.manifest)
+            if str_path is None:
+                # Slow path: this might be a directory node
+                str_files = sorted(self.manifest)
+                idx = [-1]
+                def has_dir_node(str_dir):
+                    if not str_dir: # no encoding matched, i.e. not existing
+                        return False
+                    idx[0] = lo = bisect(str_files, str_dir)
+                    return lo < len(str_files) \
+                           and str_files[lo].startswith(str_dir)
+                str_path = checked_encode(path + '/', repos.encoding,
+                                          has_dir_node)
+                if str_path is None:
+                    raise NoSuchNode(path, changectx.hex())
+                lo = idx[0]
+                for hi in xrange(lo, len(str_files)):
+                    if not str_files[hi].startswith(str_path):
+                        break
+                str_path = str_path[:-1]
+                str_entries = str_files[lo:hi]
+        self.str_path = str_path
+
+        # Determine `kind`, `rev` (requested rev) and `created_rev`
+        # (last changed revision before requested rev)
+
+        kind = None
+        rev = changectx.rev()
+        if str_path == '':
+            kind = Node.DIRECTORY
+            dirctx = changectx
+        elif str_path in self.manifest: # then it's a file
+            kind = Node.FILE
+            self.filectx = changectx.filectx(str_path)
+            created_rev = self.filectx.linkrev()
+            # FIXME (0.13) this is a hack, we should fix that at the
+            #       Trac level, which should really show the
+            #       created_rev value for files in the browser.
+            rev = created_rev
+        else: # we already know it's a dir
+            kind = Node.DIRECTORY
+            if not dirctx:
+                # we need to find the most recent change for a file below dir
+                str_dir = str_path + '/'
+                dirctxs = self.find_dirctx(changectx.rev(), [str_dir,],
+                                           {str_dir: str_entries})
+                dirctx = dirctxs.values()[0]
+
+        if not kind:
+            if repo.changelog.tip() == nullid or \
+                    not (self.manifest or str_path):
+                # empty or emptied repository
+                kind = Node.DIRECTORY
+                dirctx = changectx
+            else:
+                raise NoSuchNode(path, changectx.hex())
+
+        self.time = self.repos.from_hg_time(changectx.date())
+        if dirctx is not None:
+            # FIXME (0.13) same remark as above
+            rev = created_rev = dirctx.rev()
+        Node.__init__(self, self.repos, path, rev or '0', kind)
+        self.created_path = path
+        self.created_rev = created_rev
+        self.data = None
+
+    def find_dirctx(self, max_rev, str_dirnames, str_entries):
+        """Find most recent modification for each given directory path.
+        
+        :param max_rev: find no revision more recent than this one
+        :param str_dirnames: directory paths to consider 
+                             (as `str` ending with '/')
+        :param str_entries: optionally maps directories to their file content
+
+        :return: a `dict` with `str_dirnames` as keys, `changectx` as values
+
+        As directories are not first-class citizens in Mercurial, this
+        operation is not trivial. There are basically two strategies:
+
+         - for each file below the given directories, retrieve the
+           linkrev (most recent modification for this file), and take
+           the max; this approach is very inefficient for repositories
+           containing many files (#7746)
+
+         - retrieve the files modified when going backward through the
+           changelog and detect the first occurrence of a change in
+           each directory; this is much faster but can still be slow
+           if some folders are only modified in the distant past
+           
+        It is possible to combine both approach, and this can yield
+        excellent results in some cases (e.g. browsing the Linux repos
+        @ 118733 takes several minutes with the first approach, 11s
+        with the second, but only 1.2s with the hybrid approach)
+
+        Note that the specialized scan of the changelog we do below is
+        more efficient than the general cmdutil.walkchangerevs here.
+        """
+        str_dirctxs = {}
+        repo = self.repos.repo
+        max_ctx = repo[max_rev]
+        for r in xrange(max_rev, -1, -1):
+            ctx = repo[r]
+            # lookup changes to str_dirnames in current cset
+            for str_file in ctx.files():
+                for str_dir in str_dirnames[:]:
+                    if str_file.startswith(str_dir):
+                        str_dirctxs[str_dir] = ctx
+                        str_dirnames.remove(str_dir)
+                        if not str_dirnames: # if nothing left to find
+                            return str_dirctxs
+            # in parallel, try the filelog strategy (the 463, 2, 40
+            # values below look a bit like magic numbers; actually
+            # they were selected by testing the plugin on the Linux
+            # and NetBeans repositories)
+            if r % 463 == 0:
+                k = max(2, 40 / len(str_dirnames))
+                for str_dir in str_dirnames[:]:
+                    str_files = str_entries[str_dir]
+                    dr = str_dirctxs.get(str_dir, 0)
+                    for f in str_files[:k]:
+                        try:
+                            dr = max(dr, max_ctx.filectx(f).linkrev())
+                        except LookupError:
+                            pass # that file was not on this revision `r`
+                    str_files = str_files[k:]
+                    if str_files:
+                        str_entries[str_dir] = str_files
+                        str_dirctxs[str_dir] = dr
+                    else:
+                        str_dirctxs[str_dir] = repo[dr]
+                        str_dirnames.remove(str_dir)
+                        if not str_dirnames:
+                            return str_dirctxs
+                        
+                
+    def subnode(self, str_path, subctx=None):
+        """Return a node with the same revision information but for
+        another path
+
+        :param str_path: should be the an existing entry in the manifest
+        """
+        return MercurialNode(self.repos, self.repos.to_u(str_path),
+                             self.changectx, self.manifest, subctx, str_path)
+
+    def get_content(self):
+        if self.isdir:
+            return None
+        self.pos = 0 # reset the read()
+        return self # something that can be `read()` ...
+
+    def read(self, size=None):
+        if self.isdir:
+            return TracError(_("Can't read from directory %(path)s", 
+                               path=self.path))
+        if self.data is None:
+            self.data = self.filectx.data()
+            self.pos = 0
+        if size:
+            prev_pos = self.pos
+            self.pos += size
+            return self.data[prev_pos:self.pos]
+        return self.data
+
+    def get_entries(self):
+        if self.isfile:
+            return
+       
+        # dirnames are entries which are sub-directories
+        str_entries = {}
+        str_dirnames = []
+        def add_entry(str_file, idx):
+            str_entry = str_file
+            if idx > -1: # directory
+                str_entry = str_file[:idx + 1]
+                str_files = str_entries.setdefault(str_entry, [])
+                if not str_files:
+                    str_dirnames.append(str_entry)
+                str_files.append(str_file)
+            else:
+                str_entries[str_entry] = 1
+
+        if self.str_path:
+            str_dir = self.str_path + '/'
+            for str_file in self.manifest:
+                if str_file.startswith(str_dir):
+                    add_entry(str_file, str_file.find('/', len(str_dir)))
+        else:
+            for str_file in self.manifest:
+                add_entry(str_file, str_file.find('/'))
+
+        # pre-computing the changectx for the last change in each sub-directory
+        if str_dirnames:
+            dirctxs = self.find_dirctx(self.created_rev, str_dirnames, 
+                                       str_entries)
+        else:
+            dirctxs = {}
+
+        for str_entry in str_entries:
+            yield self.subnode(str_entry.rstrip('/'),
+                               dirctxs.get(str_entry, None))
+
+    def get_history(self, limit=None):
+        repo = self.repos.repo
+        pats = []
+        if self.str_path:
+            pats.append('path:' + self.str_path)
+        opts = {'rev': ['%s:0' % self.changectx.hex()]}
+        if self.isfile:
+            opts['follow'] = True
+        if arity(cmdutil.walkchangerevs) == 4:
+            return self._get_history_1_4(repo, pats, opts, limit)
+        else:
+            return self._get_history_1_3(repo, pats, opts, limit)
+
+    def _get_history_1_4(self, repo, pats, opts, limit):
+        matcher = match(repo[None], pats, opts)
+        if self.isfile:
+            fncache = {}
+            def prep(ctx, fns):
+                if self.isfile:
+                    fncache[ctx.rev()] = self.repos.to_u(fns[0])
+        else:
+            def prep(ctx, fns):
+                pass
+
+        # keep one lookahead entry so that we can detect renames
+        path = self.path
+        entry = None
+        count = 0
+        for ctx in cmdutil.walkchangerevs(repo, matcher, opts, prep):
+            if self.isfile and entry:
+                path = fncache[ctx.rev()]
+                if path != entry[0]:
+                    entry = entry[0:2] + (Changeset.COPY,)
+            if entry:
+                yield entry
+                count += 1
+                if limit is not None and count >= limit:
+                    return
+            entry = (path, ctx.hex(), Changeset.EDIT)
+        if entry:
+            if limit is None or count < limit:
+                entry = entry[0:2] + (Changeset.ADD,)
+            yield entry
+
+    def _get_history_1_3(self, repo, pats, opts, limit):
+        if self.repos.version_info > (1, 3, 999):
+            changefn = lambda r: repo[r]
+        else:
+            changefn = lambda r: repo[r].changeset()
+        get = cachefunc(changefn)
+        if self.isfile:
+            fncache = {}
+        chgiter, matchfn = cmdutil.walkchangerevs(self.repos.ui, repo, pats, 
+                                                  get, opts)
+        # keep one lookahead entry so that we can detect renames
+        path = self.path
+        entry = None
+        count = 0
+        for st, rev, fns in chgiter:
+            if st == 'add' and self.isfile:
+                fncache[rev] = self.repos.to_u(fns[0])
+            elif st == 'iter':
+                if self.isfile and entry:
+                    path = fncache[rev]
+                    if path != entry[0]:
+                        entry = entry[0:2] + (Changeset.COPY,)
+                if entry:
+                    yield entry
+                    count += 1
+                    if limit is not None and count >= limit:
+                        return
+                n = repo.changelog.node(rev)
+                entry = (path, hex(n), Changeset.EDIT)
+        if entry:
+            if limit is None or count < limit:
+                entry = entry[0:2] + (Changeset.ADD,)
+            yield entry
+
+    def get_annotations(self):
+        annotations = []
+        if self.filectx:
+            for fc, line in self.filectx.annotate(follow=True):
+                annotations.append(fc.rev() or '0')
+        return annotations
+        
+    def get_properties(self):
+        if self.isfile and 'x' in self.manifest.flags(self.str_path):
+            return {'exe': '*'}
+        else:
+            return {}
+
+    def get_content_length(self):
+        if self.isdir:
+            return None
+        return self.filectx.size()
+
+    def get_content_type(self):
+        if self.isdir:
+            return None
+        if 'mq' in self.repos.params: # FIXME
+            if self.str_path not in ('.hgignore', 'series'):
+                return 'text/x-diff'
+        return ''
+
+    def get_last_modified(self):
+        return self.time
+
+
+class MercurialChangeset(Changeset):
+    """A changeset in the repository.
+
+    This wraps the corresponding information from the changelog.  The
+    files changes are obtained by comparing the current manifest to
+    the parent manifest(s).
+    """
+    
+    def __init__(self, repos, ctx):
+        self.repos = repos
+        self.ctx = ctx
+        self.branch = self.repos.to_u(ctx.branch())
+        # Note: desc and time are already processed by hg's
+        # `encoding.tolocal`; by setting $HGENCODING to latin1, we are
+        # however guaranteed to get back the bytes as they were
+        # stored.
+        desc = repos.to_u(ctx.description())
+        user = repos.to_u(ctx.user())
+        time = repos.from_hg_time(ctx.date())
+        Changeset.__init__(self, repos, ctx.hex(), desc, user, time)
+
+    hg_properties = [
+        N_("Parents:"), N_("Children:"), N_("Branch:"), N_("Tags:")
+    ]
+
+    def get_properties(self):
+        properties = {}
+        parents = self.ctx.parents()
+        if len(parents) > 1:
+            properties['hg-Parents'] = (self.repos, 
+                                        [p.hex() for p in parents if p])
+        children = self.ctx.children()
+        if len(children) > 1:
+            properties['hg-Children'] = (self.repos,
+                                         [c.hex() for c in children])
+        if self.branch:
+            properties['hg-Branch'] = (self.repos, [self.branch])
+        tags = self.ctx.tags()
+        if len(tags):
+            properties['hg-Tags'] = (self.repos, 
+                                     [self.repos.to_u(t) for t in tags])
+        for k, v in self.ctx.extra().iteritems():
+            if k != 'branch':
+                properties['hg-' + k] = (self.repos, v)
+        return properties
+
+    def get_changes(self):
+        u = self.repos.to_u
+        repo = self.repos.repo
+        manifest = self.ctx.manifest()
+        parents = self.ctx.parents()
+
+        renames = []
+        str_deletions = {}
+        changes = []
+        for str_file in self.ctx.files(): # added, edited and deleted files
+            f = u(str_file)
+            # TODO: find a way to detect conflicts and show how they were 
+            #       solved (kind of 3-way diff - theirs/mine/merged)
+            edits = [p for p in parents if str_file in p.manifest()]
+
+            if str_file not in manifest:
+                str_deletions[str_file] = edits[0]
+            elif edits:
+                for p in edits:
+                    changes.append((f, Node.FILE, Changeset.EDIT, f, p.rev()))
+            else:
+                renamed = repo.file(str_file).renamed(manifest[str_file])
+                if renamed:
+                    renames.append((f, renamed))
+                else:
+                    changes.append((f, Node.FILE, Changeset.ADD, '', None))
+        # distinguish between move and copy
+        for f, (str_base_path, base_filenode) in renames:
+            base_ctx = repo.filectx(str_base_path, fileid=base_filenode)
+            if str_base_path in str_deletions:
+                del str_deletions[str_base_path]
+                action = Changeset.MOVE
+            else:
+                action = Changeset.COPY
+            changes.append((f, Node.FILE, action, u(str_base_path), 
+                            base_ctx.rev()))
+        # remaining str_deletions are real deletions
+        for str_file, p in str_deletions.items():
+            f = u(str_file)
+            changes.append((f, Node.FILE, Changeset.DELETE, f, p.rev()))
+        changes.sort()
+        for change in changes:
+            yield change
+
+    def get_branches(self):
+        """Yield branch names to which this changeset belong."""
+        return self.branch and [(self.branch, 
+                                 len(self.ctx.children()) == 0)] or []
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tracext/hg/hooks.py	Sat Jun 09 19:27:02 2012 +0200
@@ -0,0 +1,126 @@
+# -*- coding: iso-8859-1 -*-
+#
+# Copyright (C) 2011-2012 Edgewall Software
+# All rights reserved.
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://trac.edgewall.org/log/.
+
+
+"""
+Mercurial hook calling `trac-admin $ENV changeset added` for newly added
+changesets
+
+The Trac environments to be notified are configured in the `[trac]` section
+of `hgrc`. The path to an environment is specified in the `env` key. More
+environments can be specified by adding a suffix separated by a dot.
+
+The path to the `trac-admin` executable can be specified in the `trac-admin`
+key. A specific path can be set for each environment, by adding the same
+suffix to the `trac-admin` key.
+
+The maximum number of changesets to pass per call to `trac-admin` can be
+configured with the `revs_per_call` key. The default is relatively low (80
+on Windows, 500 on other systems) for maximum compatibility, and can be
+increased if you often push thousands of changesets and the system supports
+it.
+
+If the `trac-admin` option is left empty, the hook opens the Trac environment
+and calls the relevant Trac hook directly in the same process. This will only
+work if Trac can be imported by the Python interpreter running Mercurial (e.g.
+if both are installed globally). Note that this may result in increased memory
+usage if Mercurial is executed as a long-running process (e.g. hgweb in WSGI
+mode). In this last case, the option `cache_env` can be set to `true` to
+cache the Trac environments across invocations, therefore avoiding the
+environment setup time on each invocation.
+
+If the Mercurial plugin is installed globally (i.e. not in one of Trac's
+plugins directories), the hooks can be configured as follows:
+{{{
+[hooks]
+commit = python:tracext.hg.hooks.add_changesets
+changegroup = python:tracext.hg.hooks.add_changesets
+}}}
+
+Otherwise, place this file somewhere accessible, and configure the hooks as
+follows:
+{{{
+[hooks]
+commit = python:/path/to/hooks.py:add_changesets
+changegroup = python:/path/to/hooks.py:add_changesets
+}}}
+
+A typical configuration for three environments looks like this:
+{{{
+[trac]
+; For a single Trac environment
+env = /path/to/env
+trac-admin = /path/to/trac-admin
+
+; Two more environments, with a specific trac-admin for the second
+env.other = /path/to/other/env
+env.third = /path/to/third/env
+trac-admin.third = /path/to/third/trac-admin
+}}}
+"""
+
+import os.path
+import subprocess
+
+close_fds = os.name == 'posix'
+
+
+def expand_path(path):
+    """Expand user references and environment variables in a path."""
+    return os.path.expanduser(os.path.expandvars(path))
+
+
+def add_changesets(ui, repo, node, **kwargs):
+    """Commit hook calling `trac-admin $ENV changeset added $REPO $NODE ...`
+    on all configured Trac environments, for all newly added changesets.
+    """
+    revs = range(repo[node].rev(), len(repo))
+    error = False
+    for name, env in ui.configitems('trac'):
+        p = name.split('.', 1)
+        if p[0] != 'env' or not env:
+            continue
+        env = expand_path(env)
+
+        trac_admin = ui.config('trac', 'trac-admin', '')
+        if len(p) > 1:
+            trac_admin = ui.config('trac', 'trac-admin.' + p[1], trac_admin)
+
+        if not trac_admin:
+            cache_env = ui.configbool('trac', 'cache_env')
+            from trac.env import open_environment
+            from trac.versioncontrol.api import RepositoryManager
+            env = open_environment(env, use_cache=cache_env)
+            RepositoryManager(env).notify('changeset_added', repo.root, revs)
+            continue
+
+        try:
+            revs_per_call = int(ui.config('trac', 'revs_per_call'))
+        except (TypeError, ValueError):
+            revs_per_call = os.name == 'nt' and 160 or 1000
+
+        trac_admin = expand_path(trac_admin)
+        for i in xrange(0, len(revs), revs_per_call):
+            command = [trac_admin, env, 'changeset', 'added', repo.root]
+            command.extend(str(r) for r in revs[i : i + revs_per_call])
+            ui.debug("Calling %r\n" % (command,))
+            proc = subprocess.Popen(command, close_fds=close_fds,
+                                    stdout=subprocess.PIPE,
+                                    stderr=subprocess.STDOUT)
+            for line in proc.stdout:
+                ui.write(line)
+            proc.wait()
+            if proc.returncode != 0:
+                error = True
+                ui.warn("trac-admin failed with return code %d for "
+                        "environment:\n  %s\n" % (proc.returncode, env))
+    return error
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tracext/hg/locale/fr/LC_MESSAGES/tracmercurial.po	Sat Jun 09 19:27:02 2012 +0200
@@ -0,0 +1,109 @@
+# French (France) translations for TracMercurial.
+# Copyright (C) 2008 ORGANIZATION
+# This file is distributed under the same license as the TracMercurial
+# project.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2008.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: TracMercurial 0.12.0.4\n"
+"Report-Msgid-Bugs-To: cboos@edgewall.org\n"
+"POT-Creation-Date: 2008-11-13 12:33+0100\n"
+"PO-Revision-Date: 2009-11-19 16:22+0100\n"
+"Last-Translator: Christian Boos <cboos@edgewall.org>\n"
+"Language-Team: fr_FR <LL@li.org>\n"
+"Plural-Forms: nplurals=2; plural=(n > 1)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.4\n"
+
+#: tracext/hg/backend.py:99
+msgid ":"
+msgstr ""
+
+#: tracext/hg/backend.py:114
+msgid "Diff against this parent (show the changes merged from the other parents)"
+msgstr ""
+"Différences vis-à-vis de ce parent (montre les changements apportés par "
+"l'autre parent)"
+
+#: tracext/hg/backend.py:124
+#, python-format
+msgid ""
+"Note: this is a %(merge)s changeset, the changes displayed below "
+"correspond to the merge itself."
+msgstr ""
+"Note: ceci est un changeset de type %(merge)s, les modifications ci-"
+"dessous correspondent au merge lui-même."
+
+#: tracext/hg/backend.py:132
+#, python-format
+msgid ""
+"Use the %(diff)s links above to see all the changes relative to each "
+"parent."
+msgstr ""
+"Utilisez les liens %(diff)s ci-dessus pour voir les modifications "
+"relatives à chaque parent."
+
+#: tracext/hg/backend.py:155
+msgid "no such changeset"
+msgstr "pas de révisio"
+
+#: tracext/hg/backend.py:156
+msgid "Transplant:"
+msgstr ""
+
+#: tracext/hg/backend.py:173
+msgid "(binary, size greater than 100 bytes)"
+msgstr "(binaire, taille supérieure à 100 octets)"
+
+#: tracext/hg/backend.py:285
+#, python-format
+msgid "Repository '%(repo)s' not found"
+msgstr "Dépôt '%(repo)s' non trouvé"
+
+#: tracext/hg/backend.py:344
+#, python-format
+msgid "%(path)s does not appear to contain a Mercurial repository."
+msgstr "%(path)s ne semble pas contenir un dépôt Mercurial"
+
+#: tracext/hg/backend.py:566
+msgid "The Base for Diff is invalid"
+msgstr ""
+
+#: tracext/hg/backend.py:571
+msgid "The Target for Diff is invalid"
+msgstr ""
+
+#: tracext/hg/backend.py:575
+#, python-format
+msgid ""
+"Diff mismatch: Base is a %(okind)s (%(opath)s in revision %(orev)s) and "
+"Target is a %(nkind)s (%(npath)s in revision %(nrev)s)."
+msgstr ""
+"Différences incompatibles : Source est un %(okind)s (%(opath)s dans la "
+"révision %(orev)s) et Destination est un %(nkind)s (%(npath)s dans la "
+"révision %(nrev)s)."
+
+#: tracext/hg/backend.py:719
+#, python-format
+msgid "Can't read from directory %(path)s"
+msgstr "Lecture du répertoire %(path)s impossible"
+
+#: tracext/hg/backend.py:892
+msgid "Parents:"
+msgstr "Ascendants :"
+
+#: tracext/hg/backend.py:892
+msgid "Children:"
+msgstr "Descendants :"
+
+#: tracext/hg/backend.py:892
+msgid "Branch:"
+msgstr "Branche :"
+
+#: tracext/hg/backend.py:892
+msgid "Tags:"
+msgstr "Tags :"
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tracext/hg/locale/messages.pot	Sat Jun 09 19:27:02 2012 +0200
@@ -0,0 +1,102 @@
+# Translations template for TracMercurial.
+# Copyright (C) 2009 ORGANIZATION
+# This file is distributed under the same license as the TracMercurial
+# project.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2009.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: TracMercurial 0.12.0.9\n"
+"Report-Msgid-Bugs-To: cboos@edgewall.org\n"
+"POT-Creation-Date: 2009-11-19 16:21+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.4\n"
+
+#: tracext/hg/backend.py:99
+msgid ":"
+msgstr ""
+
+#: tracext/hg/backend.py:114
+msgid ""
+"Diff against this parent (show the changes merged from the other "
+"parents)"
+msgstr ""
+
+#: tracext/hg/backend.py:124
+#, python-format
+msgid ""
+"Note: this is a %(merge)s changeset, the changes displayed below "
+"correspond to the merge itself."
+msgstr ""
+
+#: tracext/hg/backend.py:132
+#, python-format
+msgid ""
+"Use the %(diff)s links above to see all the changes relative to each "
+"parent."
+msgstr ""
+
+#: tracext/hg/backend.py:155
+msgid "no such changeset"
+msgstr ""
+
+#: tracext/hg/backend.py:156
+msgid "Transplant:"
+msgstr ""
+
+#: tracext/hg/backend.py:173
+msgid "(binary, size greater than 100 bytes)"
+msgstr ""
+
+#: tracext/hg/backend.py:285
+#, python-format
+msgid "Repository '%(repo)s' not found"
+msgstr ""
+
+#: tracext/hg/backend.py:344
+#, python-format
+msgid "%(path)s does not appear to contain a Mercurial repository."
+msgstr ""
+
+#: tracext/hg/backend.py:566
+msgid "The Base for Diff is invalid"
+msgstr ""
+
+#: tracext/hg/backend.py:571
+msgid "The Target for Diff is invalid"
+msgstr ""
+
+#: tracext/hg/backend.py:575
+#, python-format
+msgid ""
+"Diff mismatch: Base is a %(okind)s (%(opath)s in revision %(orev)s) "
+"and Target is a %(nkind)s (%(npath)s in revision %(nrev)s)."
+msgstr ""
+
+#: tracext/hg/backend.py:719
+#, python-format
+msgid "Can't read from directory %(path)s"
+msgstr ""
+
+#: tracext/hg/backend.py:892
+msgid "Parents:"
+msgstr ""
+
+#: tracext/hg/backend.py:892
+msgid "Children:"
+msgstr ""
+
+#: tracext/hg/backend.py:892
+msgid "Branch:"
+msgstr ""
+
+#: tracext/hg/backend.py:892
+msgid "Tags:"
+msgstr ""
+