diff options
author | Lorry <lorry@roadtrain.codethink.co.uk> | 2012-08-22 15:47:16 +0100 |
---|---|---|
committer | Lorry <lorry@roadtrain.codethink.co.uk> | 2012-08-22 15:47:16 +0100 |
commit | 25335618bf8755ce6b116ee14f47f5a1f2c821e9 (patch) | |
tree | d889d7ab3f9f985d0c54c534cb8052bd2e6d7163 /bzrlib/version_info_formats | |
download | bzr-tarball-25335618bf8755ce6b116ee14f47f5a1f2c821e9.tar.gz |
Tarball conversion
Diffstat (limited to 'bzrlib/version_info_formats')
-rw-r--r-- | bzrlib/version_info_formats/__init__.py | 207 | ||||
-rw-r--r-- | bzrlib/version_info_formats/format_custom.py | 112 | ||||
-rw-r--r-- | bzrlib/version_info_formats/format_python.py | 107 | ||||
-rw-r--r-- | bzrlib/version_info_formats/format_rio.py | 96 |
4 files changed, 522 insertions, 0 deletions
diff --git a/bzrlib/version_info_formats/__init__.py b/bzrlib/version_info_formats/__init__.py new file mode 100644 index 0000000..d1f8653 --- /dev/null +++ b/bzrlib/version_info_formats/__init__.py @@ -0,0 +1,207 @@ +# Copyright (C) 2005, 2006 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 + +"""Routines for extracting all version information from a bzr branch.""" + +from __future__ import absolute_import + +import time + +from bzrlib.osutils import local_time_offset, format_date +from bzrlib import ( + registry, + revision as _mod_revision, + ) + + +def create_date_str(timestamp=None, offset=None): + """Just a wrapper around format_date to provide the right format. + + We don't want to use '%a' in the time string, because it is locale + dependant. We also want to force timezone original, and show_offset + + Without parameters this function yields the current date in the local + time zone. + """ + if timestamp is None and offset is None: + timestamp = time.time() + offset = local_time_offset() + return format_date(timestamp, offset, date_fmt='%Y-%m-%d %H:%M:%S', + timezone='original', show_offset=True) + + +class VersionInfoBuilder(object): + """A class which lets you build up information about a revision.""" + + def __init__(self, branch, working_tree=None, + check_for_clean=False, + include_revision_history=False, + include_file_revisions=False, + template=None, + revision_id=None): + """Build up information about the given branch. + If working_tree is given, it can be checked for changes. + + :param branch: The branch to work on + :param working_tree: If supplied, preferentially check + the working tree for changes. + :param check_for_clean: If False, we will skip the expense + of looking for changes. + :param include_revision_history: If True, the output + will include the full mainline revision history, including + date and message + :param include_file_revisions: The output should + include the explicit last-changed revision for each file. + :param template: Template for the output formatting, not used by + all builders. + :param revision_id: Revision id to print version for (optional) + """ + self._branch = branch + self._check = check_for_clean + self._include_history = include_revision_history + self._include_file_revs = include_file_revisions + self._template = template + + self._clean = None + self._file_revisions = {} + self._revision_id = revision_id + + if self._revision_id is None: + self._tree = working_tree + self._working_tree = working_tree + else: + self._tree = self._branch.repository.revision_tree(self._revision_id) + # the working tree is not relevant if an explicit revision was specified + self._working_tree = None + + def _extract_file_revisions(self): + """Extract the working revisions for all files""" + + # Things seem clean if we never look :) + self._clean = True + + if self._working_tree is self._tree: + basis_tree = self._working_tree.basis_tree() + # TODO: jam 20070215 The working tree should actually be locked at + # a higher level, but this will do for now. + self._working_tree.lock_read() + else: + basis_tree = self._branch.repository.revision_tree(self._revision_id) + + basis_tree.lock_read() + try: + # Build up the list from the basis inventory + for info in basis_tree.list_files(include_root=True): + self._file_revisions[info[0]] = info[-1].revision + + if not self._check or self._working_tree is not self._tree: + return + + delta = self._working_tree.changes_from(basis_tree, + include_root=True) + + # Using a 2-pass algorithm for renames. This is because you might have + # renamed something out of the way, and then created a new file + # in which case we would rather see the new marker + # Or you might have removed the target, and then renamed + # in which case we would rather see the renamed marker + for (old_path, new_path, file_id, + kind, text_mod, meta_mod) in delta.renamed: + self._clean = False + self._file_revisions[old_path] = u'renamed to %s' % (new_path,) + for path, file_id, kind in delta.removed: + self._clean = False + self._file_revisions[path] = 'removed' + for path, file_id, kind in delta.added: + self._clean = False + self._file_revisions[path] = 'new' + for (old_path, new_path, file_id, + kind, text_mod, meta_mod) in delta.renamed: + self._clean = False + self._file_revisions[new_path] = u'renamed from %s' % (old_path,) + for path, file_id, kind, text_mod, meta_mod in delta.modified: + self._clean = False + self._file_revisions[path] = 'modified' + + for path in self._working_tree.unknowns(): + self._clean = False + self._file_revisions[path] = 'unversioned' + finally: + basis_tree.unlock() + if self._working_tree is not None: + self._working_tree.unlock() + + def _iter_revision_history(self): + """Find the messages for all revisions in history.""" + + last_rev = self._get_revision_id() + + repository = self._branch.repository + repository.lock_read() + try: + graph = repository.get_graph() + revhistory = list(graph.iter_lefthand_ancestry( + last_rev, [_mod_revision.NULL_REVISION])) + for revision_id in reversed(revhistory): + rev = repository.get_revision(revision_id) + yield (rev.revision_id, rev.message, + rev.timestamp, rev.timezone) + finally: + repository.unlock() + + def _get_revision_id(self): + """Get the revision id we are working on.""" + if self._revision_id is not None: + return self._revision_id + if self._working_tree is not None: + return self._working_tree.last_revision() + return self._branch.last_revision() + + def _get_revno_str(self, revision_id): + numbers = self._branch.revision_id_to_dotted_revno(revision_id) + revno_str = '.'.join([str(num) for num in numbers]) + return revno_str + + def generate(self, to_file): + """Output the version information to the supplied file. + + :param to_file: The file to write the stream to. The output + will already be encoded, so to_file should not try + to change encodings. + :return: None + """ + raise NotImplementedError(VersionInfoBuilder.generate) + + +format_registry = registry.Registry() + + +format_registry.register_lazy( + 'rio', + 'bzrlib.version_info_formats.format_rio', + 'RioVersionInfoBuilder', + 'Version info in RIO (simple text) format (default).') +format_registry.default_key = 'rio' +format_registry.register_lazy( + 'python', + 'bzrlib.version_info_formats.format_python', + 'PythonVersionInfoBuilder', + 'Version info in Python format.') +format_registry.register_lazy( + 'custom', + 'bzrlib.version_info_formats.format_custom', + 'CustomVersionInfoBuilder', + 'Version info in Custom template-based format.') diff --git a/bzrlib/version_info_formats/format_custom.py b/bzrlib/version_info_formats/format_custom.py new file mode 100644 index 0000000..353301d --- /dev/null +++ b/bzrlib/version_info_formats/format_custom.py @@ -0,0 +1,112 @@ +# Copyright (C) 2007 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 + +"""A generator which creates a template-based output from the current + tree info.""" + +from __future__ import absolute_import + +from bzrlib import errors +from bzrlib.revision import ( + NULL_REVISION, + ) +from bzrlib.lazy_regex import lazy_compile +from bzrlib.version_info_formats import ( + create_date_str, + VersionInfoBuilder, + ) + + +class Template(object): + """A simple template engine. + + >>> t = Template() + >>> t.add('test', 'xxx') + >>> print list(t.process('{test}')) + ['xxx'] + >>> print list(t.process('{test} test')) + ['xxx', ' test'] + >>> print list(t.process('test {test}')) + ['test ', 'xxx'] + >>> print list(t.process('test {test} test')) + ['test ', 'xxx', ' test'] + >>> print list(t.process('{test}\\\\n')) + ['xxx', '\\n'] + >>> print list(t.process('{test}\\n')) + ['xxx', '\\n'] + """ + + _tag_re = lazy_compile('{(\w+)}') + + def __init__(self): + self._data = {} + + def add(self, name, value): + self._data[name] = value + + def process(self, tpl): + tpl = tpl.decode('string_escape') + pos = 0 + while True: + match = self._tag_re.search(tpl, pos) + if not match: + if pos < len(tpl): + yield tpl[pos:] + break + start, end = match.span() + if start > 0: + yield tpl[pos:start] + pos = end + name = match.group(1) + try: + data = self._data[name] + except KeyError: + raise errors.MissingTemplateVariable(name) + if not isinstance(data, basestring): + data = str(data) + yield data + + +class CustomVersionInfoBuilder(VersionInfoBuilder): + """Create a version file based on a custom template.""" + + def generate(self, to_file): + if self._template is None: + raise errors.NoTemplate() + + info = Template() + info.add('build_date', create_date_str()) + info.add('branch_nick', self._branch.nick) + + revision_id = self._get_revision_id() + if revision_id == NULL_REVISION: + info.add('revno', 0) + else: + info.add('revno', self._get_revno_str(revision_id)) + info.add('revision_id', revision_id) + rev = self._branch.repository.get_revision(revision_id) + info.add('date', create_date_str(rev.timestamp, rev.timezone)) + + if self._check: + self._extract_file_revisions() + + if self._check: + if self._clean: + info.add('clean', 1) + else: + info.add('clean', 0) + + to_file.writelines(info.process(self._template)) diff --git a/bzrlib/version_info_formats/format_python.py b/bzrlib/version_info_formats/format_python.py new file mode 100644 index 0000000..1ca9ab4 --- /dev/null +++ b/bzrlib/version_info_formats/format_python.py @@ -0,0 +1,107 @@ +# Copyright (C) 2006, 2009, 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 + +"""A generator which creates a python script from the current tree info""" + +from __future__ import absolute_import + +import pprint + +from bzrlib.revision import ( + NULL_REVISION, + ) +from bzrlib.version_info_formats import ( + create_date_str, + VersionInfoBuilder, + ) + + +# Header and footer for the python format +_py_version_header = '''#!/usr/bin/env python +"""This file is automatically generated by generate_version_info +It uses the current working tree to determine the revision. +So don't edit it. :) +""" + +''' + + +_py_version_footer = ''' +if __name__ == '__main__': + print 'revision: %(revno)s' % version_info + print 'nick: %(branch_nick)s' % version_info + print 'revision id: %(revision_id)s' % version_info +''' + + +class PythonVersionInfoBuilder(VersionInfoBuilder): + """Create a version file which is a python source module.""" + + def generate(self, to_file): + info = {'build_date':create_date_str() + , 'revno':None + , 'revision_id':None + , 'branch_nick':self._branch.nick + , 'clean':None + , 'date':None + } + revisions = [] + + revision_id = self._get_revision_id() + if revision_id == NULL_REVISION: + info['revno'] = '0' + else: + info['revno'] = self._get_revno_str(revision_id) + info['revision_id'] = revision_id + rev = self._branch.repository.get_revision(revision_id) + info['date'] = create_date_str(rev.timestamp, rev.timezone) + + if self._check or self._include_file_revs: + self._extract_file_revisions() + + if self._check: + if self._clean: + info['clean'] = True + else: + info['clean'] = False + + info_str = pprint.pformat(info) + to_file.write(_py_version_header) + to_file.write('version_info = ') + to_file.write(info_str) + to_file.write('\n\n') + + if self._include_history: + history = list(self._iter_revision_history()) + revision_str = pprint.pformat(history) + to_file.write('revisions = ') + to_file.write(revision_str) + to_file.write('\n\n') + else: + to_file.write('revisions = {}\n\n') + + if self._include_file_revs: + file_rev_str = pprint.pformat(self._file_revisions) + to_file.write('file_revisions = ') + to_file.write(file_rev_str) + to_file.write('\n\n') + else: + to_file.write('file_revisions = {}\n\n') + + to_file.write(_py_version_footer) + + + diff --git a/bzrlib/version_info_formats/format_rio.py b/bzrlib/version_info_formats/format_rio.py new file mode 100644 index 0000000..c42580a --- /dev/null +++ b/bzrlib/version_info_formats/format_rio.py @@ -0,0 +1,96 @@ +# Copyright (C) 2006, 2009, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +"""A generator which creates a rio stanza of the current tree info""" + +from __future__ import absolute_import + +from bzrlib import hooks +from bzrlib.revision import ( + NULL_REVISION, + ) +from bzrlib.rio import RioWriter, Stanza + +from bzrlib.version_info_formats import ( + create_date_str, + VersionInfoBuilder, + ) + + +class RioVersionInfoBuilder(VersionInfoBuilder): + """This writes a rio stream out.""" + + def generate(self, to_file): + info = Stanza() + revision_id = self._get_revision_id() + if revision_id != NULL_REVISION: + info.add('revision-id', revision_id) + rev = self._branch.repository.get_revision(revision_id) + info.add('date', create_date_str(rev.timestamp, rev.timezone)) + revno = self._get_revno_str(revision_id) + for hook in RioVersionInfoBuilder.hooks['revision']: + hook(rev, info) + else: + revno = '0' + + info.add('build-date', create_date_str()) + info.add('revno', revno) + + if self._branch.nick is not None: + info.add('branch-nick', self._branch.nick) + + if self._check or self._include_file_revs: + self._extract_file_revisions() + + if self._check: + if self._clean: + info.add('clean', 'True') + else: + info.add('clean', 'False') + + if self._include_history: + log = Stanza() + for (revision_id, message, + timestamp, timezone) in self._iter_revision_history(): + log.add('id', revision_id) + log.add('message', message) + log.add('date', create_date_str(timestamp, timezone)) + info.add('revisions', log.to_unicode()) + + if self._include_file_revs: + files = Stanza() + for path in sorted(self._file_revisions.keys()): + files.add('path', path) + files.add('revision', self._file_revisions[path]) + info.add('file-revisions', files.to_unicode()) + + writer = RioWriter(to_file=to_file) + writer.write_stanza(info) + + +class RioVersionInfoBuilderHooks(hooks.Hooks): + """Hooks for rio-formatted version-info output.""" + + def __init__(self): + super(RioVersionInfoBuilderHooks, self).__init__( + "bzrlib.version_info_formats.format_rio", "RioVersionInfoBuilder.hooks") + self.add_hook('revision', + "Invoked when adding information about a revision to the" + " RIO stanza that is printed. revision is called with a" + " revision object and a RIO stanza.", (1, 15)) + + +RioVersionInfoBuilder.hooks = RioVersionInfoBuilderHooks() |