bzr-stats-0.1.0+bzr54/Makefile0000644000000000000000000000254311657376304014211 0ustar 00000000000000DEBUGGER ?= BZR ?= $(shell which bzr) PYTHON ?= $(shell which python) SETUP ?= ./setup.py CTAGS ?= ctags PYLINT ?= pylint TESTS ?= DESTDIR ?= all:: build build:: $(SETUP) build install:: ifneq ($(DESTDIR),) $(SETUP) install --root "$(DESTDIR)" else $(SETUP) install endif clean:: $(SETUP) clean check:: BZR_PLUGINS_AT=stats@$(shell pwd) $(DEBUGGER) $(PYTHON) $(BZR) $(BZR_OPTIONS) selftest $(TEST_OPTIONS) --starting-with=bzrlib.plugins.stats $(TESTS) check-verbose:: $(MAKE) check TEST_OPTIONS=-v coverage:: $(MAKE) check BZR_OPTIONS="--coverage coverage" check-one:: $(MAKE) check TEST_OPTIONS=--one show-plugins:: BZR_PLUGINS_AT=stats@$(shell pwd) $(BZR) plugins -v lint:: $(PYLINT) -f parseable *.py */*.py tags:: $(CTAGS) -R . ctags:: tags .PHONY: update-pot po/bzr-stats.pot update-pot: po/bzr-stats.pot TRANSLATABLE_PYFILES:=$(shell find . -name '*.py' \ | grep -v 'tests/' \ ) po/bzr-stats.pot: $(PYFILES) $(DOCFILES) BZR_PLUGINS_AT=stats@$(shell pwd) bzr export-pot \ --plugin=stats > po/bzr-stats.pot echo $(TRANSLATABLE_PYFILES) | xargs \ xgettext --package-name "bzr-stats" \ --msgid-bugs-address "" \ --copyright-holder "Bazaar Developers " \ --from-code ISO-8859-1 --sort-by-file --join --add-comments=i18n: \ -d bzr-stats -p po -o bzr-stats.pot bzr-stats-0.1.0+bzr54/NEWS0000644000000000000000000000006611532251710013227 0ustar 000000000000000.2.0 UNRELEASED 0.1.0 2011-02-24 Initial release. bzr-stats-0.1.0+bzr54/__init__.py0000644000000000000000000000350111673236416014652 0ustar 00000000000000# Copyright (C) 2006-2010 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """A Simple bzr plugin to generate statistics about the history.""" from __future__ import absolute_import from bzrlib import _format_version_tuple from bzrlib.plugins.stats.info import ( bzr_plugin_version as version_info, ) try: from bzrlib.i18n import load_plugin_translations except ImportError: # No translations for bzr < 2.5 gettext = lambda x: x else: translation = load_plugin_translations("bzr-stats") gettext = translation.ugettext __version__ = _format_version_tuple(version_info) from bzrlib.commands import plugin_cmds plugin_cmds.register_lazy("cmd_credits", [], "bzrlib.plugins.stats.cmds") plugin_cmds.register_lazy("cmd_committer_statistics", ['stats', 'committer-stats'], "bzrlib.plugins.stats.cmds") plugin_cmds.register_lazy("cmd_ancestor_growth", [], "bzrlib.plugins.stats.cmds") def load_tests(basic_tests, module, loader): testmod_names = [__name__ + '.' + x for x in [ 'test_classify', 'test_stats', ]] suite = loader.suiteClass() suite.addTest(loader.loadTestsFromModuleNames(testmod_names)) return suite bzr-stats-0.1.0+bzr54/classify.py0000644000000000000000000000467511726170403014735 0ustar 00000000000000# Copyright (C) 2008, 2010 Jelmer Vernooij # 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 """Classify a commit based on the types of files it changed.""" from __future__ import absolute_import import os.path from bzrlib import urlutils from bzrlib.trace import mutter def classify_filename(name): """Classify a file based on its name. :param name: File path. :return: One of code, documentation, translation or art. None if determining the file type failed. """ # FIXME: Use mime types? Ohcount? # TODO: It will be better move those filters to properties file # and have possibility to determining own types !? extension = os.path.splitext(name)[1] if extension in (".c", ".h", ".py", ".cpp", ".rb", ".pm", ".pl", ".ac", ".java", ".cc", ".proto", ".yy", ".l"): return "code" if extension in (".html", ".xml", ".txt", ".rst", ".TODO"): return "documentation" if extension in (".po",): return "translation" if extension in (".svg", ".png", ".jpg"): return "art" if not extension: basename = urlutils.basename(name) if basename in ("README", "NEWS", "TODO", "AUTHORS", "COPYING"): return "documentation" if basename in ("Makefile",): return "code" mutter("don't know how to classify %s", name) return None def classify_delta(delta): """Determine what sort of changes a delta contains. :param delta: A TreeDelta to inspect :return: List with classes found (see classify_filename) """ # TODO: This is inaccurate, since it doesn't look at the # number of lines changed in a file. types = [] for d in delta.added + delta.modified: types.append(classify_filename(d[0])) return types bzr-stats-0.1.0+bzr54/cmds.py0000644000000000000000000003635413016615704014047 0ustar 00000000000000# Copyright (C) 2006-2010 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """A Simple bzr plugin to generate statistics about the history.""" from __future__ import absolute_import from bzrlib import ( branch, commands, config, errors, option, trace, tsort, ui, workingtree, ) from bzrlib.revision import NULL_REVISION from bzrlib.plugins.stats.classify import classify_delta from itertools import izip def collapse_by_person(revisions, canonical_committer): """The committers list is sorted by email, fix it up by person. Some people commit with a similar username, but different email address. Which makes it hard to sort out when they have multiple entries. Email is actually more stable, though, since people frequently forget to set their name properly. So take the most common username for each email address, and combine them into one new list. """ # Map from canonical committer to # {committer: ([rev_list], {email: count}, {fname:count})} committer_to_info = {} for rev in revisions: authors = rev.get_apparent_authors() for author in authors: username, email = config.parse_username(author) if len(username) == 0 and len(email) == 0: continue canon_author = canonical_committer[(username, email)] info = committer_to_info.setdefault(canon_author, ([], {}, {})) info[0].append(rev) info[1][email] = info[1].setdefault(email, 0) + 1 info[2][username] = info[2].setdefault(username, 0) + 1 res = [(len(revs), revs, emails, fnames) for revs, emails, fnames in committer_to_info.itervalues()] res.sort(reverse=True) return res def collapse_email_and_users(email_users, combo_count): """Combine the mapping of User Name to email and email to User Name. If a given User Name is used for multiple emails, try to map it all to one entry. """ id_to_combos = {} username_to_id = {} email_to_id = {} id_counter = 0 def collapse_ids(old_id, new_id, new_combos): old_combos = id_to_combos.pop(old_id) new_combos.update(old_combos) for old_user, old_email in old_combos: if (old_user and old_user != user): low_old_user = old_user.lower() old_user_id = username_to_id[low_old_user] assert old_user_id in (old_id, new_id) username_to_id[low_old_user] = new_id if (old_email and old_email != email): old_email_id = email_to_id[old_email] assert old_email_id in (old_id, new_id) email_to_id[old_email] = cur_id for email, usernames in email_users.iteritems(): assert email not in email_to_id if not email: # We use a different algorithm for usernames that have no email # address, we just try to match by username, and not at all by # email for user in usernames: if not user: continue # The mysterious ('', '') user # When mapping, use case-insensitive names low_user = user.lower() user_id = username_to_id.get(low_user) if user_id is None: id_counter += 1 user_id = id_counter username_to_id[low_user] = user_id id_to_combos[user_id] = id_combos = set() else: id_combos = id_to_combos[user_id] id_combos.add((user, email)) continue id_counter += 1 cur_id = id_counter id_to_combos[cur_id] = id_combos = set() email_to_id[email] = cur_id for user in usernames: combo = (user, email) id_combos.add(combo) if not user: # We don't match on empty usernames continue low_user = user.lower() user_id = username_to_id.get(low_user) if user_id is not None: # This UserName was matched to an cur_id if user_id != cur_id: # And it is a different identity than the current email collapse_ids(user_id, cur_id, id_combos) username_to_id[low_user] = cur_id combo_to_best_combo = {} for cur_id, combos in id_to_combos.iteritems(): best_combo = sorted(combos, key=lambda x:combo_count[x], reverse=True)[0] for combo in combos: combo_to_best_combo[combo] = best_combo return combo_to_best_combo def get_revisions_and_committers(a_repo, revids): """Get the Revision information, and the best-match for committer.""" email_users = {} # user@email.com => User Name combo_count = {} pb = ui.ui_factory.nested_progress_bar() try: trace.note('getting revisions') revisions = a_repo.get_revisions(revids) for count, rev in enumerate(revisions): pb.update('checking', count, len(revids)) for author in rev.get_apparent_authors(): # XXX: There is a chance sometimes with svn imports that the # full name and email can BOTH be blank. username, email = config.parse_username(author) email_users.setdefault(email, set()).add(username) combo = (username, email) combo_count[combo] = combo_count.setdefault(combo, 0) + 1 finally: pb.finished() return revisions, collapse_email_and_users(email_users, combo_count) def get_info(a_repo, revision): """Get all of the information for a particular revision""" pb = ui.ui_factory.nested_progress_bar() a_repo.lock_read() try: trace.note('getting ancestry') graph = a_repo.get_graph() ancestry = [ r for (r, ps) in graph.iter_ancestry([revision]) if ps is not None and r != NULL_REVISION] revs, canonical_committer = get_revisions_and_committers(a_repo, ancestry) finally: a_repo.unlock() pb.finished() return collapse_by_person(revs, canonical_committer) def get_diff_info(a_repo, start_rev, end_rev): """Get only the info for new revisions between the two revisions This lets us figure out what has actually changed between 2 revisions. """ pb = ui.ui_factory.nested_progress_bar() a_repo.lock_read() try: graph = a_repo.get_graph() trace.note('getting ancestry diff') ancestry = graph.find_difference(start_rev, end_rev)[1] revs, canonical_committer = get_revisions_and_committers(a_repo, ancestry) finally: a_repo.unlock() pb.finished() return collapse_by_person(revs, canonical_committer) def display_info(info, to_file, gather_class_stats=None): """Write out the information""" for count, revs, emails, fullnames in info: # Get the most common email name sorted_emails = sorted(((count, email) for email,count in emails.iteritems()), reverse=True) sorted_fullnames = sorted(((count, fullname) for fullname,count in fullnames.iteritems()), reverse=True) if sorted_fullnames[0][1] == '' and sorted_emails[0][1] == '': to_file.write('%4d %s\n' % (count, 'Unknown')) else: to_file.write('%4d %s <%s>\n' % (count, sorted_fullnames[0][1], sorted_emails[0][1])) if len(sorted_fullnames) > 1: to_file.write(' Other names:\n') for count, fname in sorted_fullnames: to_file.write(' %4d ' % (count,)) if fname == '': to_file.write("''\n") else: to_file.write("%s\n" % (fname,)) if len(sorted_emails) > 1: to_file.write(' Other email addresses:\n') for count, email in sorted_emails: to_file.write(' %4d ' % (count,)) if email == '': to_file.write("''\n") else: to_file.write("%s\n" % (email,)) if gather_class_stats is not None: to_file.write(' Contributions:\n') classes, total = gather_class_stats(revs) for name,count in sorted(classes.items(), lambda x,y: cmp((x[1], x[0]), (y[1], y[0]))): if name is None: name = "Unknown" to_file.write(" %4.0f%% %s\n" % ((float(count) / total) * 100.0, name)) class cmd_committer_statistics(commands.Command): """Generate statistics for LOCATION.""" aliases = ['stats', 'committer-stats'] takes_args = ['location?'] takes_options = ['revision', option.Option('show-class', help="Show the class of contributions.")] encoding_type = 'replace' def run(self, location='.', revision=None, show_class=False): alternate_rev = None try: wt = workingtree.WorkingTree.open_containing(location)[0] except errors.NoWorkingTree: a_branch = branch.Branch.open(location) last_rev = a_branch.last_revision() else: a_branch = wt.branch last_rev = wt.last_revision() if revision is not None: last_rev = revision[0].in_history(a_branch).rev_id if len(revision) > 1: alternate_rev = revision[1].in_history(a_branch).rev_id a_branch.lock_read() try: if alternate_rev: info = get_diff_info(a_branch.repository, last_rev, alternate_rev) else: info = get_info(a_branch.repository, last_rev) finally: a_branch.unlock() if show_class: def fetch_class_stats(revs): return gather_class_stats(a_branch.repository, revs) else: fetch_class_stats = None display_info(info, self.outf, fetch_class_stats) class cmd_ancestor_growth(commands.Command): """Figure out the ancestor graph for LOCATION""" takes_args = ['location?'] encoding_type = 'replace' def run(self, location='.'): try: wt = workingtree.WorkingTree.open_containing(location)[0] except errors.NoWorkingTree: a_branch = branch.Branch.open(location) last_rev = a_branch.last_revision() else: a_branch = wt.branch last_rev = wt.last_revision() a_branch.lock_read() try: graph = a_branch.repository.get_graph() revno = 0 cur_parents = 0 sorted_graph = tsort.merge_sort(graph.iter_ancestry([last_rev]), last_rev) for num, node_name, depth, isend in reversed(sorted_graph): cur_parents += 1 if depth == 0: revno += 1 self.outf.write('%4d, %4d\n' % (revno, cur_parents)) finally: a_branch.unlock() def gather_class_stats(repository, revs): ret = {} total = 0 pb = ui.ui_factory.nested_progress_bar() try: repository.lock_read() try: i = 0 for delta in repository.get_deltas_for_revisions(revs): pb.update("classifying commits", i, len(revs)) for c in classify_delta(delta): if not c in ret: ret[c] = 0 ret[c] += 1 total += 1 i += 1 finally: repository.unlock() finally: pb.finished() return ret, total def display_credits(credits, to_file): (coders, documenters, artists, translators) = credits def print_section(name, lst): if len(lst) == 0: return to_file.write("%s:\n" % name) for name in lst: to_file.write("%s\n" % name) to_file.write('\n') print_section("Code", coders) print_section("Documentation", documenters) print_section("Art", artists) print_section("Translations", translators) def find_credits(repository, revid): """Find the credits of the contributors to a revision. :return: tuple with (authors, documenters, artists, translators) """ ret = {"documentation": {}, "code": {}, "art": {}, "translation": {}, None: {} } repository.lock_read() try: graph = repository.get_graph() ancestry = [r for (r, ps) in graph.iter_ancestry([revid]) if ps is not None and r != NULL_REVISION] revs = repository.get_revisions(ancestry) pb = ui.ui_factory.nested_progress_bar() try: iterator = izip(revs, repository.get_deltas_for_revisions(revs)) for i, (rev,delta) in enumerate(iterator): pb.update("analysing revisions", i, len(revs)) # Don't count merges if len(rev.parent_ids) > 1: continue for c in set(classify_delta(delta)): for author in rev.get_apparent_authors(): if not author in ret[c]: ret[c][author] = 0 ret[c][author] += 1 finally: pb.finished() finally: repository.unlock() def sort_class(name): return map(lambda (x,y): x, sorted(ret[name].items(), lambda x,y: cmp((x[1], x[0]), (y[1], y[0])), reverse=True)) return (sort_class("code"), sort_class("documentation"), sort_class("art"), sort_class("translation")) class cmd_credits(commands.Command): """Determine credits for LOCATION.""" takes_args = ['location?'] takes_options = ['revision'] encoding_type = 'replace' def run(self, location='.', revision=None): try: wt = workingtree.WorkingTree.open_containing(location)[0] except errors.NoWorkingTree: a_branch = branch.Branch.open(location) last_rev = a_branch.last_revision() else: a_branch = wt.branch last_rev = wt.last_revision() if revision is not None: last_rev = revision[0].in_history(a_branch).rev_id a_branch.lock_read() try: credits = find_credits(a_branch.repository, last_rev) display_credits(credits, self.outf) finally: a_branch.unlock() bzr-stats-0.1.0+bzr54/info.py0000644000000000000000000000026311726170403014040 0ustar 00000000000000from __future__ import absolute_import bzr_plugin_name = 'stats' bzr_plugin_version = (0, 2, 0, 'dev', 0) bzr_commands = ['credits', 'committer-statistics', 'ancestor-growth'] bzr-stats-0.1.0+bzr54/po/0000755000000000000000000000000011657376304013163 5ustar 00000000000000bzr-stats-0.1.0+bzr54/setup.py0000755000000000000000000000127211532251415014247 0ustar 00000000000000#!/usr/bin/env python from info import * if __name__ == '__main__': from distutils.core import setup version_string = ".".join([str(v) for v in bzr_plugin_version[:3]]) setup(name='bzr-stats', description='Statistics plugin for Bazaar', keywords='plugin bzr stats', version=version_string, license='GPL', author='John Arbash Meinel', author_email="john@arbash-meinel.com", url="http://launchpad.net/bzr-stats", long_description=""" Simple statistics plugin for Bazaar. """, package_dir={'bzrlib.plugins.stats':'.'}, packages=['bzrlib.plugins.stats'] ) bzr-stats-0.1.0+bzr54/test_classify.py0000644000000000000000000000345411726170403015766 0ustar 00000000000000# Copyright (C) 2008, 2010 Jelmer Vernooij # 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 from __future__ import absolute_import from bzrlib.tests import TestCase from bzrlib.plugins.stats.classify import classify_filename class TestClassify(TestCase): def test_classify_code(self): self.assertEquals("code", classify_filename("foo/bar.c")) self.assertEquals("code", classify_filename("foo/bar.pl")) self.assertEquals("code", classify_filename("foo/bar.pm")) def test_classify_documentation(self): self.assertEquals("documentation", classify_filename("bla.html")) def test_classify_translation(self): self.assertEquals("translation", classify_filename("nl.po")) def test_classify_art(self): self.assertEquals("art", classify_filename("icon.png")) def test_classify_unknown(self): self.assertEquals(None, classify_filename("something.bar")) def test_classify_doc_hardcoded(self): self.assertEquals("documentation", classify_filename("README")) def test_classify_multiple_periods(self): self.assertEquals("documentation", classify_filename("foo.bla.html")) bzr-stats-0.1.0+bzr54/test_stats.py0000644000000000000000000001144211726170403015303 0ustar 00000000000000from __future__ import absolute_import from bzrlib.tests import TestCase, TestCaseWithTransport from bzrlib.revision import Revision from bzrlib.plugins.stats.cmds import get_revisions_and_committers, collapse_by_person class TestGetRevisionsAndCommitters(TestCaseWithTransport): def test_simple(self): wt = self.make_branch_and_tree('.') wt.commit(message='1', committer='Fero ', rev_id='1') wt.commit(message='2', committer='Fero ', rev_id='2') wt.commit(message='3', committer='Jano ', rev_id='3') wt.commit(message='4', committer='Jano ', authors=['Vinco '], rev_id='4') wt.commit(message='5', committer='Ferko ', rev_id='5') revs, committers = get_revisions_and_committers(wt.branch.repository, ['1', '2', '3', '4', '5']) fero = ('Fero', 'fero@example.com') jano = ('Jano', 'jano@example.com') vinco = ('Vinco', 'vinco@example.com') ferok = ('Ferko', 'fero@example.com') self.assertEqual({fero: fero, jano: jano, vinco:vinco, ferok: fero}, committers) def test_empty_email(self): wt = self.make_branch_and_tree('.') wt.commit(message='1', committer='Fero', rev_id='1') wt.commit(message='2', committer='Fero', rev_id='2') wt.commit(message='3', committer='Jano', rev_id='3') revs, committers = get_revisions_and_committers(wt.branch.repository, ['1', '2', '3']) self.assertEqual({('Fero', ''): ('Fero', ''), ('Jano', ''): ('Jano', ''), }, committers) def test_different_case(self): wt = self.make_branch_and_tree('.') wt.commit(message='1', committer='Fero', rev_id='1') wt.commit(message='2', committer='Fero', rev_id='2') wt.commit(message='3', committer='FERO', rev_id='3') revs, committers = get_revisions_and_committers(wt.branch.repository, ['1', '2', '3']) self.assertEqual({('Fero', ''): ('Fero', ''), ('FERO', ''): ('Fero', ''), }, committers) class TestCollapseByPerson(TestCase): def test_no_conflicts(self): revisions = [ Revision('1', {}, committer='Foo '), Revision('2', {}, committer='Bar '), Revision('3', {}, committer='Bar '), ] foo = ('Foo', 'foo@example.com') bar = ('Bar', 'bar@example.com') committers = {foo: foo, bar: bar} info = collapse_by_person(revisions, committers) self.assertEquals(2, info[0][0]) self.assertEquals({'bar@example.com': 2}, info[0][2]) self.assertEquals({'Bar': 2}, info[0][3]) def test_different_email(self): revisions = [ Revision('1', {}, committer='Foo '), Revision('2', {}, committer='Foo '), Revision('3', {}, committer='Foo '), ] foo = ('Foo', 'foo@example.com') bar = ('Foo', 'bar@example.com') committers = {foo: foo, bar: foo} info = collapse_by_person(revisions, committers) self.assertEquals(3, info[0][0]) self.assertEquals({'foo@example.com': 1, 'bar@example.com': 2}, info[0][2]) self.assertEquals({'Foo': 3}, info[0][3]) def test_different_name(self): revisions = [ Revision('1', {}, committer='Foo '), Revision('2', {}, committer='Bar '), Revision('3', {}, committer='Bar '), ] foo = ('Foo', 'foo@example.com') bar = ('Bar', 'foo@example.com') committers = {foo: foo, bar: foo} info = collapse_by_person(revisions, committers) self.assertEquals(3, info[0][0]) self.assertEquals({'foo@example.com': 3}, info[0][2]) self.assertEquals({'Foo': 1, 'Bar': 2}, info[0][3]) def test_different_name_case(self): revisions = [ Revision('1', {}, committer='Foo '), Revision('2', {}, committer='Foo '), Revision('3', {}, committer='FOO '), ] foo = ('Foo', 'foo@example.com') FOO = ('FOO', 'bar@example.com') committers = {foo: foo, FOO: foo} info = collapse_by_person(revisions, committers) self.assertEquals(3, info[0][0]) self.assertEquals({'foo@example.com': 2, 'bar@example.com': 1}, info[0][2]) self.assertEquals({'Foo': 2, 'FOO': 1}, info[0][3]) bzr-stats-0.1.0+bzr54/po/bzr-stats.pot0000644000000000000000000000170211657376304015640 0ustar 00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Bazaar Developers # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: bzr-stats\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2011-11-12 05:56+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" #: cmds.py:243 msgid "Generate statistics for LOCATION." msgstr "" # help of 'show-class' option of 'committer-statistics' command #: cmds.py:248 msgid "Show the class of contributions." msgstr "" #: cmds.py:286 msgid "Figure out the ancestor graph for LOCATION" msgstr "" #: cmds.py:395 msgid "Determine credits for LOCATION." msgstr "" bzr-stats-0.1.0+bzr54/po/en_GB.po0000644000000000000000000000217512020037157014463 0ustar 00000000000000# English (United Kingdom) translation for bzr-stats # Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 # This file is distributed under the same license as the bzr-stats package. # FIRST AUTHOR , 2012. # msgid "" msgstr "" "Project-Id-Version: bzr-stats\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2011-11-12 05:56+0100\n" "PO-Revision-Date: 2012-05-01 11:31+0000\n" "Last-Translator: Dan Bishop \n" "Language-Team: English (United Kingdom) \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2012-08-31 04:32+0000\n" "X-Generator: Launchpad (build 15887)\n" #: cmds.py:243 msgid "Generate statistics for LOCATION." msgstr "Generate statistics for LOCATION." #: cmds.py:248 msgid "Show the class of contributions." msgstr "Show the class of contributions." #: cmds.py:286 msgid "Figure out the ancestor graph for LOCATION" msgstr "Figure out the ancestor graph for LOCATION" #: cmds.py:395 msgid "Determine credits for LOCATION." msgstr "Determine credits for LOCATION." bzr-stats-0.1.0+bzr54/po/fr.po0000644000000000000000000000217412107335400014115 0ustar 00000000000000# French translation for bzr-stats # Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013 # This file is distributed under the same license as the bzr-stats package. # FIRST AUTHOR , 2013. # msgid "" msgstr "" "Project-Id-Version: bzr-stats\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2011-11-12 05:56+0100\n" "PO-Revision-Date: 2013-02-14 13:34+0000\n" "Last-Translator: Hélion du Mas des Bourboux \n" "Language-Team: French \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2013-02-15 04:35+0000\n" "X-Generator: Launchpad (build 16491)\n" #: cmds.py:243 msgid "Generate statistics for LOCATION." msgstr "Générer les statistiques pour LOCATION." #: cmds.py:248 msgid "Show the class of contributions." msgstr "Montrer la classe des contributions." #: cmds.py:286 msgid "Figure out the ancestor graph for LOCATION" msgstr "Trouver le graphique ancêtre pour LOCATION" #: cmds.py:395 msgid "Determine credits for LOCATION." msgstr "Trouver les crédits pour LOCATION"