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