Rev 6283: First cut at a working plugin to avoid conflicts in .po files by shelling out to msgmerge. in file:///home/vila/src/bzr/bugs/884270-merge-po/

Vincent Ladeuil v.ladeuil+lp at free.fr
Thu Nov 24 10:47:44 UTC 2011


At file:///home/vila/src/bzr/bugs/884270-merge-po/

------------------------------------------------------------
revno: 6283
revision-id: v.ladeuil+lp at free.fr-20111124104743-rxqwhmzqu5k17f24
parent: pqm at pqm.ubuntu.com-20111121012651-prdfm12cb04wtdkm
committer: Vincent Ladeuil <v.ladeuil+lp at free.fr>
branch nick: 884270-merge-po
timestamp: Thu 2011-11-24 11:47:43 +0100
message:
  First cut at a working plugin to avoid conflicts in .po files by shelling out to msgmerge.
-------------- next part --------------
=== modified file 'bzrlib/merge.py'
--- a/bzrlib/merge.py	2011-10-27 15:38:14 +0000
+++ b/bzrlib/merge.py	2011-11-24 10:47:43 +0000
@@ -139,7 +139,7 @@
             params.winner == 'other' or
             # THIS and OTHER aren't both files.
             not params.is_file_merge() or
-            # The filename doesn't match *.xml
+            # The filename doesn't match
             not self.file_matches(params)):
             return 'not_applicable', None
         return self.merge_matching(params)
@@ -854,14 +854,18 @@
         else:
             entries = self._entries_lca()
             resolver = self._lca_multi_way
+        # Prepare merge hooks
+        factories = Merger.hooks['merge_file_content']
+        # One hook for each registered one plus our default merger
+        hooks = [factory(self) for factory in factories] + [self]
+        self.active_hooks = [hook for hook in hooks if hook is not None]
         child_pb = ui.ui_factory.nested_progress_bar()
         try:
-            factories = Merger.hooks['merge_file_content']
-            hooks = [factory(self) for factory in factories] + [self]
-            self.active_hooks = [hook for hook in hooks if hook is not None]
             for num, (file_id, changed, parents3, names3,
                       executable3) in enumerate(entries):
-                child_pb.update(gettext('Preparing file merge'), num, len(entries))
+                # Try merging each entry
+                child_pb.update(gettext('Preparing file merge'),
+                                num, len(entries))
                 self._merge_names(file_id, parents3, names3, resolver=resolver)
                 if changed:
                     file_status = self._do_merge_contents(file_id)

=== added directory 'bzrlib/plugins/po_merge'
=== added file 'bzrlib/plugins/po_merge/README'
--- a/bzrlib/plugins/po_merge/README	1970-01-01 00:00:00 +0000
+++ b/bzrlib/plugins/po_merge/README	2011-11-24 10:47:43 +0000
@@ -0,0 +1,7 @@
+A plugin for merging .po files.
+
+This plugin is activated via configuration variables, see 'bzr help po_merge'.
+
+This hook can avoid conflicts in ``.po` files by invoking msgmerge with the
+appropriate options, if it can't apply, it falls back to the default bzr
+merge algorithm.

=== added file 'bzrlib/plugins/po_merge/__init__.py'
--- a/bzrlib/plugins/po_merge/__init__.py	1970-01-01 00:00:00 +0000
+++ b/bzrlib/plugins/po_merge/__init__.py	2011-11-24 10:47:43 +0000
@@ -0,0 +1,80 @@
+# Copyright (C) 2011 Canonical Ltd
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+__doc__ = """Merge hook for ``.po`` files.
+
+To enable this plugin, add a section to your branch.conf or location.conf
+like::
+
+    [/home/user/code/bzr]
+    po_merge.pot_file = po/xxx.pot
+    po_merge.po_files = po/*.po
+
+The ``po_merge.pot_file`` config option takes a list of file paths, separated
+by commas.
+
+The ``po_merge.po_files`` config option takes a list of file globs, separated
+by commas.
+
+The ``po_merge.command`` is the command whose output is used as the result of
+the merge. It defaults to::
+
+   msgmerge -N "{other}" "{pot_file}" -C "{this}" -o "{result}"
+
+where:
+
+* ``this`` is the ``.po`` file content before the merge in the current branch,
+* ``other`` is the ``.po`` file content in the branch merged from,
+* ``pot_file`` is the path to the ``.pot`` file corresponding to the ``.po``
+  file being merged.
+
+In the simple case where a single ``.pot`` file and a single set of ``.po``
+files exist, each config option can specify a single value.
+
+When several ``(.pot file, .po fileset)`` exist, both lists should be
+synchronized. For example::
+
+    [/home/user/code/bzr]
+    po_merge.pot_file = po/adduser.pot,doc/po4a/po/adduser.pot
+    po_merge.po_files = po/*.po,doc/po4a/po/*.po
+
+``po/adduser.pot`` will be used for ``po/*.po`` and ``doc/po4a/po/adduser.pot``
+will be used for ``doc/po4a/po/*.po``.
+"""
+
+# Since we are a built-in plugin we share the bzrlib version
+from bzrlib import version_info
+from bzrlib.hooks import install_lazy_named_hook
+
+
+def po_merge_hook(merger):
+    """Merger.merge_file_content hook for bzr-format NEWS files."""
+    from bzrlib.plugins.po_merge.po_merge import PoMerger
+    return PoMerger(merger)
+
+
+install_lazy_named_hook("bzrlib.merge", "Merger.hooks", "merge_file_content",
+    po_merge_hook, ".po file merge")
+
+
+def load_tests(basic_tests, module, loader):
+    testmod_names = [
+        'tests',
+        ]
+    basic_tests.addTest(loader.loadTestsFromModuleNames(
+            ["%s.%s" % (__name__, tmn) for tmn in testmod_names]))
+    return basic_tests
+

=== added file 'bzrlib/plugins/po_merge/po_merge.py'
--- a/bzrlib/plugins/po_merge/po_merge.py	1970-01-01 00:00:00 +0000
+++ b/bzrlib/plugins/po_merge/po_merge.py	2011-11-24 10:47:43 +0000
@@ -0,0 +1,165 @@
+# Copyright (C) 2011 Canonical Ltd
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+"""Merge logic for po_merge plugin."""
+
+
+from bzrlib import (
+    config,
+    merge,
+    )
+
+
+from bzrlib.lazy_import import lazy_import
+lazy_import(globals(), """
+import fnmatch
+import subprocess
+import tempfile
+import sys
+
+from bzrlib import (
+    cmdline,
+    osutils,
+    trace,
+    )
+""")
+
+
+config.option_registry.register(config.Option(
+        'po_merge.command',
+        default='msgmerge -N "{other}" "{pot_file}" -C "{this}" -o "{result}"',
+        help='''\
+Command used to create a conflict-free .po file during merge.
+
+The following parameters are provided by the hook:
+``this`` is the ``.po`` file content before the merge in the current branch,
+``other`` is the ``.po`` file content in the branch merged from,
+``pot_file`` is the path to the ``.pot`` file corresponding to the ``.po``
+file being merged.
+``result`` is the path where ``msgmerge`` will output its result. The hook will
+use the content of this file to produce the resulting ``.po`` file.
+
+The command is invoked at the root of the working tree so all paths are
+relative.
+'''))
+
+
+config.option_registry.register(config.Option(
+        'po_merge.po_files', default=[],
+        from_unicode=config.list_from_store,
+        help='List of globs the po_merge hook applies to.'))
+
+
+config.option_registry.register(config.Option(
+        'po_merge.pot_file', default=[],
+        from_unicode=config.list_from_store,
+        help='List of ``.pot`` filenames related to ``po_merge.po_files``.'))
+
+
+class PoMerger(merge.PerFileMerger):
+    """Merge .po files."""
+
+    def __init__(self, merger):
+        super(merge.PerFileMerger, self).__init__(merger)
+        # config options are cached locally until config files are (see
+        # http://pad.lv/832042)
+
+        # FIXME: We use the branch config as there is no tree config
+        # -- vila 2011-11-23
+        self.conf = merger.this_branch.get_config_stack()
+        # Which files are targeted by the hook 
+        self.po_files = self.conf.get('po_merge.po_files')
+        # Which .pot file should be used
+        self.pot_file = self.conf.get('po_merge.pot_file')
+        self.command = self.conf.get('po_merge.command', expand=False)
+        # file_matches() will set the following for merge_text()
+        self.selected_po_file = None
+        self.selected_pot_file = None
+
+    def file_matches(self, params):
+        """Return True if merge_matching should be called on this file."""
+        if not self.po_files or not self.pot_file:
+            # Return early if there is no options defined
+            return False
+        match = False
+        po_path = self.get_filepath(params, self.merger.this_tree)
+        # Does the merged file match one of the globs
+        for idx, glob in enumerate(self.po_files):
+            if fnmatch.fnmatch(po_path, glob):
+                match = True
+                break
+        if not match:
+            return False
+        # Do we have the corresponding .pot file
+        try:
+            pot_path = self.pot_file[idx]
+        except KeyError:
+            trace.note('po_merge.po_files and po_merge.pot_file mismatch'
+                       ' for index %d' %d)
+            return False
+        if self.merger.this_tree.has_filename(pot_path):
+            self.selected_pot_file = pot_path
+            self.selected_po_file = po_path
+            # FIXME: I can't find a way to know if the .pot file has conflicts
+            # *during* the merge itself. So either the actual content on disk
+            # is fine and msgmerge will work OR it's not and it will
+            # fail. Conversely, either the result is ok for the user and he's
+            # happy OR the user needs to resolve the conflicts in the .pot file
+            # and use remerge. -- vila 2011-11-24
+            return True
+        return False
+
+    def _invoke(self, command):
+        proc = subprocess.Popen(cmdline.split(command),
+                                # FIXME: cwd= ? -- vila 2011-11-24
+                                stdout=subprocess.PIPE,
+                                stderr=subprocess.PIPE,
+                                stdin=subprocess.PIPE)
+        out, err = proc.communicate()
+        return proc.returncode, out, err
+
+    def merge_matching(self, params):
+        return self.merge_text(params)
+
+    def merge_text(self, params):
+        """Calls msgmerge when .po files conflict.
+
+        This requires a valid .pot file to reconcile both sides.
+        """
+        # Create tmp files with the 'this' and 'other' content
+        tmpdir = tempfile.mkdtemp(prefix='po_merge')
+        env = {}
+        env['this'] = osutils.pathjoin(tmpdir, 'this')
+        env['other'] = osutils.pathjoin(tmpdir, 'other')
+        env['result'] = osutils.pathjoin(tmpdir, 'result')
+        env['pot_file'] = self.selected_pot_file
+        try:
+            with osutils.open_file(env['this'], 'wb') as f:
+                f.writelines(params.this_lines)
+            with osutils.open_file(env['other'], 'wb') as f:
+                f.writelines(params.other_lines)
+            command = self.conf.expand_options(self.command, env)
+            retcode, out, err = self._invoke(command)
+            with osutils.open_file(env['result']) as f:
+                # FIXME: To avoid the list() construct below which means the
+                # whole 'result' file is kept in memory, there may be a way to
+                # use an iterator that will close the file when it's done, but
+                # there is still the issue of removing the tmp dir...
+                # -- vila 2011-11-24
+                return 'success', list(f.readlines())
+        finally:
+            osutils.rmtree(tmpdir)
+        return 'not applicable', []

=== added directory 'bzrlib/plugins/po_merge/tests'
=== added file 'bzrlib/plugins/po_merge/tests/__init__.py'
--- a/bzrlib/plugins/po_merge/tests/__init__.py	1970-01-01 00:00:00 +0000
+++ b/bzrlib/plugins/po_merge/tests/__init__.py	2011-11-24 10:47:43 +0000
@@ -0,0 +1,24 @@
+# Copyright (C) 2011 by Canonical Ltd
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+def load_tests(basic_tests, module, loader):
+    testmod_names = [
+        'test_blackbox_po_merge',
+        'test_po_merge',
+        ]
+    basic_tests.addTest(loader.loadTestsFromModuleNames(
+            ["%s.%s" % (__name__, tmn) for tmn in testmod_names]))
+    return basic_tests

=== added file 'bzrlib/plugins/po_merge/tests/test_blackbox_po_merge.py'
--- a/bzrlib/plugins/po_merge/tests/test_blackbox_po_merge.py	1970-01-01 00:00:00 +0000
+++ b/bzrlib/plugins/po_merge/tests/test_blackbox_po_merge.py	2011-11-24 10:47:43 +0000
@@ -0,0 +1,460 @@
+# -*- coding: utf8
+# Copyright (C) 2011 by Canonical Ltd
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+
+from bzrlib import (
+    merge,
+    tests,
+    )
+from bzrlib.tests import (
+    features,
+    script,
+    )
+
+from bzrlib.plugins import po_merge
+
+class BlackboxTestPoMerger(script.TestCaseWithTransportAndScript):
+
+    _test_needs_features = [features.msgmerge_feature]
+
+    def setUp(self):
+        super(BlackboxTestPoMerger, self).setUp()
+        self.builder = make_adduser_branch(self, 'adduser')
+        # We need to install our hook as the test framework cleared it as part
+        # of the initialization
+        merge.Merger.hooks.install_named_hook(
+            "merge_file_content", po_merge.po_merge_hook, ".po file merge")
+
+    def test_merge_with_hook_gives_unexpected_results(self):
+        # Since the conflicts in .pot are not seen *during* the merge, the .po
+        # merge triggers the hook and create no conflicts. But the .pot used is
+        # the one present in the tree *before* the merge.
+        self.run_script("""\
+$ bzr branch adduser -rrevid:this work
+2>Branched 2 revisions.
+$ cd work
+$ bzr config po_merge.pot_file=adduser.pot
+$ bzr config po_merge.po_files=fr.po
+$ bzr merge ../adduser -rrevid:other
+2> M  adduser.pot
+2> M  fr.po
+2>Text conflict in adduser.pot
+2>1 conflicts encountered.
+""")
+
+    def test_called_on_remerge(self):
+        # Merge with no config for the hook to create the conflicts
+        self.run_script("""\
+$ bzr branch adduser -rrevid:this work
+2>Branched 2 revisions.
+$ cd work
+$ bzr merge ../adduser -rrevid:other
+2> M  adduser.pot
+2> M  fr.po
+2>Text conflict in adduser.pot
+2>Text conflict in fr.po
+2>2 conflicts encountered.
+""")
+        # Fix the conflicts in the .pot file
+        with open('adduser.pot', 'w') as f:
+            f.write(_adduser_branch['resolved_pot'])
+        # Tell bzr the conflict is resolved
+        self.run_script("""\
+$ bzr resolve adduser.pot
+2>1 conflict resolved, 1 remaining
+""")
+        # set config options to activate the hook
+        self.run_script("""\
+$ bzr config po_merge.pot_file=adduser.pot
+$ bzr config po_merge.po_files=fr.po
+""")
+        # Use remerge to trigger the hook, 
+        self.run_script("""\
+$ bzr remerge *.po
+2>All changes applied successfully.
+""")
+        # There should be no conflicts anymore
+        self.run_script("""\
+$ bzr conflicts
+""")
+
+
+def make_adduser_branch(test, relpath):
+    """Helper for po_merge blackbox tests.
+
+    This creates a branch containing the needed base revisions so tests can
+    attempt merges and conflict resolutions.
+    """
+    builder = test.make_branch_builder(relpath)
+    builder.start_series()
+    builder.build_snapshot('base', None,
+                           [('add', ('', 'root-id', 'directory', '')),
+                            # Create empty files
+                            ('add', ('adduser.pot', 'pot-id', 'file',
+                                     _adduser_branch['base_pot'])),
+                            ('add', ('fr.po', 'po-id', 'file',
+                                     _adduser_branch['base_po'])),
+            ])
+    # The 'other' branch
+    builder.build_snapshot('other', ['base'],
+                           [('modify', ('pot-id',
+                                        _adduser_branch['other_pot'])),
+                            ('modify', ('po-id',
+                                        _adduser_branch['other_po'])),
+                            ])
+    # The 'this' branch
+    builder.build_snapshot('this', ['base'],
+                           [('modify', ('pot-id', _adduser_branch['this_pot'])),
+                            ('modify', ('po-id', _adduser_branch['this_po'])),
+                            ])
+    # builder.get_branch() tip is now 'this'
+    builder.finish_series()
+    return builder
+
+
+class TestAdduserBranch(script.TestCaseWithTransportAndScript):
+    """Sanity checks on the adduser branch content."""
+
+    def setUp(self):
+        super(TestAdduserBranch, self).setUp()
+        self.builder = make_adduser_branch(self, 'adduser')
+
+    def assertAdduserBranchContent(self, revid):
+        env = dict(revid=revid, branch_name=revid)
+        self.run_script("""\
+$ bzr branch adduser -rrevid:%(revid)s %(branch_name)s
+""" % env, null_output_matches_anything=True)
+        self.assertFileEqual(_adduser_branch['%(revid)s_pot' % env],
+                             '%(branch_name)s/adduser.pot' % env)
+        self.assertFileEqual(_adduser_branch['%(revid)s_po' % env],
+                             '%(branch_name)s/fr.po' % env )
+
+    def test_base(self):
+        self.assertAdduserBranchContent('base')
+
+    def test_this(self):
+        self.assertAdduserBranchContent('this')
+
+    def test_other(self):
+        self.assertAdduserBranchContent('other')
+
+
+# Real content from the adduser package so we don't have to guess about format
+# details. This is declared at the end of the file to avoid cluttering the
+# beginning of the file.
+
+_adduser_branch = dict(
+    base_pot = r"""# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: adduser-devel at lists.alioth.debian.org\n"
+"POT-Creation-Date: 2007-01-17 21:50+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
+"Language-Team: LANGUAGE <LL at li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#. everyone can issue "--help" and "--version", but only root can go on
+#: ../adduser:135
+msgid "Only root may add a user or group to the system.\n"
+msgstr ""
+
+#: ../adduser:188
+msgid "Warning: The home dir you specified already exists.\n"
+msgstr ""
+
+""",
+    this_pot = r"""# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: adduser-devel at lists.alioth.debian.org\n"
+"POT-Creation-Date: 2011-01-06 21:06+0000\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
+"Language-Team: LANGUAGE <LL at li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#. everyone can issue "--help" and "--version", but only root can go on
+#: ../adduser:152
+msgid "Only root may add a user or group to the system.\n"
+msgstr ""
+
+#: ../adduser:208
+#, perl-format
+msgid "Warning: The home dir %s you specified already exists.\n"
+msgstr ""
+
+#: ../adduser:210
+#, perl-format
+msgid "Warning: The home dir %s you specified can't be accessed: %s\n"
+msgstr ""
+
+""",
+    other_pot = r"""# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: adduser-devel at lists.alioth.debian.org\n"
+"POT-Creation-Date: 2010-11-21 17:13-0400\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
+"Language-Team: LANGUAGE <LL at li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#. everyone can issue "--help" and "--version", but only root can go on
+#: ../adduser:150
+msgid "Only root may add a user or group to the system.\n"
+msgstr ""
+
+#: ../adduser:206
+#, perl-format
+msgid "Warning: The home dir %s you specified already exists.\n"
+msgstr ""
+
+#: ../adduser:208
+#, perl-format
+msgid "Warning: The home dir %s you specified can't be accessed: %s\n"
+msgstr ""
+
+""",
+    resolved_pot = r"""# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: adduser-devel at lists.alioth.debian.org\n"
+"POT-Creation-Date: 2011-10-19 12:50-0700\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
+"Language-Team: LANGUAGE <LL at li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#. everyone can issue "--help" and "--version", but only root can go on
+#: ../adduser:152
+msgid "Only root may add a user or group to the system.\n"
+msgstr ""
+
+#: ../adduser:208
+#, perl-format
+msgid "Warning: The home dir %s you specified already exists.\n"
+msgstr ""
+
+#: ../adduser:210
+#, perl-format
+msgid "Warning: The home dir %s you specified can't be accessed: %s\n"
+msgstr ""
+
+""",
+    base_po = r"""# adduser's manpages translation to French
+# Copyright (C) 2004 Software in the Public Interest
+# This file is distributed under the same license as the adduser package
+#
+# Translators:
+# Jean-Baka Domelevo Entfellner <domelevo at gmail.com>, 2009.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: adduser 3.111\n"
+"Report-Msgid-Bugs-To: adduser-devel at lists.alioth.debian.org\n"
+"POT-Creation-Date: 2007-01-17 21:50+0100\n"
+"PO-Revision-Date: 2010-01-21 10:36+0100\n"
+"Last-Translator: Jean-Baka Domelevo Entfellner <domelevo at gmail.com>\n"
+"Language-Team: Debian French Team <debian-l10n-french at lists.debian.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Poedit-Language: French\n"
+"X-Poedit-Country: FRANCE\n"
+
+# type: Plain text
+#. everyone can issue "--help" and "--version", but only root can go on
+#: ../adduser:135
+msgid "Only root may add a user or group to the system.\n"
+msgstr ""
+"Seul le superutilisateur est autoris�� �� ajouter un utilisateur ou un groupe "
+"au syst��me.\n"
+
+#: ../adduser:188
+msgid "Warning: The home dir you specified already exists.\n"
+msgstr ""
+"Attention��! Le r��pertoire personnel que vous avez indiqu�� existe d��j��.\n"
+
+""",
+    this_po = r"""# adduser's manpages translation to French
+# Copyright (C) 2004 Software in the Public Interest
+# This file is distributed under the same license as the adduser package
+#
+# Translators:
+# Jean-Baka Domelevo Entfellner <domelevo at gmail.com>, 2009.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: adduser 3.111\n"
+"Report-Msgid-Bugs-To: adduser-devel at lists.alioth.debian.org\n"
+"POT-Creation-Date: 2010-10-12 15:48+0200\n"
+"PO-Revision-Date: 2010-01-21 10:36+0100\n"
+"Last-Translator: Jean-Baka Domelevo Entfellner <domelevo at gmail.com>\n"
+"Language-Team: Debian French Team <debian-l10n-french at lists.debian.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Poedit-Language: French\n"
+"X-Poedit-Country: FRANCE\n"
+
+# type: Plain text
+#. everyone can issue "--help" and "--version", but only root can go on
+#: ../adduser:152
+msgid "Only root may add a user or group to the system.\n"
+msgstr ""
+"Seul le superutilisateur est autoris�� �� ajouter un utilisateur ou un groupe "
+"au syst��me.\n"
+
+#: ../adduser:208
+#, fuzzy, perl-format
+msgid "Warning: The home dir %s you specified already exists.\n"
+msgstr ""
+"Attention��! Le r��pertoire personnel que vous avez indiqu�� existe d��j��.\n"
+
+#: ../adduser:210
+#, fuzzy, perl-format
+msgid "Warning: The home dir %s you specified can't be accessed: %s\n"
+msgstr ""
+"Attention��! Le r��pertoire personnel que vous avez indiqu�� existe d��j��.\n"
+
+""",
+    other_po = r"""# adduser's manpages translation to French
+# Copyright (C) 2004 Software in the Public Interest
+# This file is distributed under the same license as the adduser package
+#
+# Translators:
+# Jean-Baka Domelevo Entfellner <domelevo at gmail.com>, 2009, 2010.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: adduser 3.112+nmu2\n"
+"Report-Msgid-Bugs-To: adduser-devel at lists.alioth.debian.org\n"
+"POT-Creation-Date: 2010-11-21 17:13-0400\n"
+"PO-Revision-Date: 2010-11-10 11:08+0100\n"
+"Last-Translator: Jean-Baka Domelevo-Entfellner <domelevo at gmail.com>\n"
+"Language-Team: Debian French Team <debian-l10n-french at lists.debian.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Poedit-Country: FRANCE\n"
+
+# type: Plain text
+#. everyone can issue "--help" and "--version", but only root can go on
+#: ../adduser:150
+msgid "Only root may add a user or group to the system.\n"
+msgstr ""
+"Seul le superutilisateur est autoris�� �� ajouter un utilisateur ou un groupe "
+"au syst��me.\n"
+
+#: ../adduser:206
+#, perl-format
+msgid "Warning: The home dir %s you specified already exists.\n"
+msgstr ""
+"Attention��! Le r��pertoire personnel que vous avez indiqu�� (%s) existe d��j��.\n"
+
+#: ../adduser:208
+#, perl-format
+msgid "Warning: The home dir %s you specified can't be accessed: %s\n"
+msgstr ""
+"Attention��! Impossible d'acc��der au r��pertoire personnel que vous avez "
+"indiqu�� (%s)��: %s.\n"
+
+""",
+    resolved_po = r"""# adduser's manpages translation to French
+# Copyright (C) 2004 Software in the Public Interest
+# This file is distributed under the same license as the adduser package
+#
+# Translators:
+# Jean-Baka Domelevo Entfellner <domelevo at gmail.com>, 2009, 2010.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: adduser 3.112+nmu2\n"
+"Report-Msgid-Bugs-To: adduser-devel at lists.alioth.debian.org\n"
+"POT-Creation-Date: 2011-10-19 12:50-0700\n"
+"PO-Revision-Date: 2010-11-10 11:08+0100\n"
+"Last-Translator: Jean-Baka Domelevo-Entfellner <domelevo at gmail.com>\n"
+"Language-Team: Debian French Team <debian-l10n-french at lists.debian.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Poedit-Country: FRANCE\n"
+
+# type: Plain text
+#. everyone can issue "--help" and "--version", but only root can go on
+#: ../adduser:152
+msgid "Only root may add a user or group to the system.\n"
+msgstr ""
+"Seul le superutilisateur est autoris�� �� ajouter un utilisateur ou un groupe "
+"au syst��me.\n"
+
+#: ../adduser:208
+#, perl-format
+msgid "Warning: The home dir %s you specified already exists.\n"
+msgstr ""
+"Attention��! Le r��pertoire personnel que vous avez indiqu�� (%s) existe d��j��.\n"
+
+#: ../adduser:210
+#, perl-format
+msgid "Warning: The home dir %s you specified can't be accessed: %s\n"
+msgstr ""
+"Attention��! Impossible d'acc��der au r��pertoire personnel que vous avez "
+"indiqu�� (%s)��: %s.\n"
+
+""",
+)

=== added file 'bzrlib/plugins/po_merge/tests/test_po_merge.py'
--- a/bzrlib/plugins/po_merge/tests/test_po_merge.py	1970-01-01 00:00:00 +0000
+++ b/bzrlib/plugins/po_merge/tests/test_po_merge.py	2011-11-24 10:47:43 +0000
@@ -0,0 +1,42 @@
+# Copyright (C) 2011 by Canonical Ltd
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+
+from bzrlib import (
+    tests,
+    )
+
+class TestPoMerger(tests.TestCaseWithTransport):
+
+    def test_bad_config_options(self):
+        # pot_file and po_files lengths should match
+        pass
+
+    def test_match_po_files(self):
+        # hook will fire if the merged file matches one of the globs
+        pass
+
+    def test_no_pot_file(self):
+        # hook won't fire if there is no pot file
+        # - not present
+        # - doesn't match
+        pass
+
+    def test_no_pot_file(self):
+        # hook won't fire if there are conflicts in the pot file
+        pass
+

=== modified file 'bzrlib/tests/features.py'
--- a/bzrlib/tests/features.py	2011-11-21 00:06:30 +0000
+++ b/bzrlib/tests/features.py	2011-11-24 10:47:43 +0000
@@ -408,8 +408,9 @@
 
 
 bash_feature = ExecutableFeature('bash')
+diff_feature = ExecutableFeature('diff')
 sed_feature = ExecutableFeature('sed')
-diff_feature = ExecutableFeature('diff')
+msgmerge_feature = ExecutableFeature('msgmerge')
 
 
 class _PosixPermissionsFeature(Feature):



More information about the bazaar-commits mailing list