summaryrefslogtreecommitdiff
path: root/bzrlib/version_info_formats
diff options
context:
space:
mode:
authorLorry <lorry@roadtrain.codethink.co.uk>2012-08-22 15:47:16 +0100
committerLorry <lorry@roadtrain.codethink.co.uk>2012-08-22 15:47:16 +0100
commit25335618bf8755ce6b116ee14f47f5a1f2c821e9 (patch)
treed889d7ab3f9f985d0c54c534cb8052bd2e6d7163 /bzrlib/version_info_formats
downloadbzr-tarball-25335618bf8755ce6b116ee14f47f5a1f2c821e9.tar.gz
Tarball conversion
Diffstat (limited to 'bzrlib/version_info_formats')
-rw-r--r--bzrlib/version_info_formats/__init__.py207
-rw-r--r--bzrlib/version_info_formats/format_custom.py112
-rw-r--r--bzrlib/version_info_formats/format_python.py107
-rw-r--r--bzrlib/version_info_formats/format_rio.py96
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()