1
# Copyright (C) 2005, 2006 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
"""Routines for extracting all version information from a bzr branch."""
21
from bzrlib.osutils import local_time_offset, format_date
24
# This contains a map of format id => formatter
25
# None is considered the default formatter
28
def create_date_str(timestamp=None, offset=None):
29
"""Just a wrapper around format_date to provide the right format.
31
We don't want to use '%a' in the time string, because it is locale
32
dependant. We also want to force timezone original, and show_offset
34
Without parameters this function yields the current date in the local
37
if timestamp is None and offset is None:
38
timestamp = time.time()
39
offset = local_time_offset()
40
return format_date(timestamp, offset, date_fmt='%Y-%m-%d %H:%M:%S',
41
timezone='original', show_offset=True)
44
class VersionInfoBuilder(object):
45
"""A class which lets you build up information about a revision."""
47
def __init__(self, branch, working_tree=None,
48
check_for_clean=False,
49
include_revision_history=False,
50
include_file_revisions=False,
52
"""Build up information about the given branch.
53
If working_tree is given, it can be checked for changes.
55
:param branch: The branch to work on
56
:param working_tree: If supplied, preferentially check
57
the working tree for changes.
58
:param check_for_clean: If False, we will skip the expense
59
of looking for changes.
60
:param include_revision_history: If True, the output
61
will include the full mainline revision history, including
63
:param include_file_revisions: The output should
64
include the explicit last-changed revision for each file.
67
self._working_tree = working_tree
68
self._check = check_for_clean
69
self._include_history = include_revision_history
70
self._include_file_revs = include_file_revisions
73
self._file_revisions = {}
74
self._revision_history_info= []
76
def _extract_file_revisions(self):
77
"""Extract the working revisions for all files"""
79
# Things seem clean if we never look :)
82
if self._working_tree is not None:
83
basis_tree = self._working_tree.basis_tree()
85
basis_tree = self._branch.basis_tree()
87
# Build up the list from the basis inventory
88
for info in basis_tree.list_files():
89
self._file_revisions[info[0]] = info[-1].revision
91
if not self._check or self._working_tree is None:
94
# We have both a working tree, and we are checking
96
# bzr < 0.9 did not have Tree.changes_from
98
delta = self._working_tree.changes_from(basis_tree)
99
except AttributeError:
101
delta = bzrlib.delta.compare_trees(basis_tree, self._working_tree)
103
# Using a 2-pass algorithm for renames. This is because you might have
104
# renamed something out of the way, and then created a new file
105
# in which case we would rather see the new marker
106
# Or you might have removed the target, and then renamed
107
# in which case we would rather see the renamed marker
108
for (old_path, new_path, file_id,
109
kind, text_mod, meta_mod) in delta.renamed:
111
self._file_revisions[old_path] = u'renamed to %s' % (new_path,)
112
for path, file_id, kind in delta.removed:
114
self._file_revisions[path] = 'removed'
115
for path, file_id, kind in delta.added:
117
self._file_revisions[path] = 'new'
118
for (old_path, new_path, file_id,
119
kind, text_mod, meta_mod) in delta.renamed:
121
self._file_revisions[new_path] = u'renamed from %s' % (old_path,)
122
for path, file_id, kind, text_mod, meta_mod in delta.modified:
124
self._file_revisions[path] = 'modified'
126
for path in self._working_tree.unknowns():
128
self._file_revisions[path] = 'unversioned'
130
def _extract_revision_history(self):
131
"""Find the messages for all revisions in history."""
133
# Unfortunately, there is no WorkingTree.revision_history
134
rev_hist = self._branch.revision_history()
135
if self._working_tree is not None:
136
last_rev = self._working_tree.last_revision()
137
assert last_rev in rev_hist, \
138
"Working Tree's last revision not in branch.revision_history"
139
rev_hist = rev_hist[:rev_hist.index(last_rev)+1]
141
repository = self._branch.repository
142
repository.lock_read()
144
for revision_id in rev_hist:
145
rev = repository.get_revision(revision_id)
146
self._revision_history_info.append(
147
(rev.revision_id, rev.message,
148
rev.timestamp, rev.timezone))
152
def _get_revision_id(self):
153
"""Get the revision id we are working on."""
154
if self._working_tree is not None:
155
return self._working_tree.last_revision()
156
return self._branch.last_revision()
158
def generate(self, to_file):
159
"""Output the version information to the supplied file.
161
:param to_file: The file to write the stream to. The output
162
will already be encoded, so to_file should not try
166
raise NotImplementedError(VersionInfoBuilder.generate)
170
def register_builder(format, module, class_name):
171
"""Register a version info format.
173
:param format: The short name of the format, this will be used as the
175
:param module: The string name to the module where the format class
177
:param class_name: The string name of the class to instantiate
179
if len(_version_formats) == 0:
180
_version_formats[None] = (module, class_name)
181
_version_formats[format] = (module, class_name)
184
def get_builder(format):
185
"""Get a handle to the version info builder class
187
:param format: The lookup key supplied to register_builder
188
:return: A class, which follows the VersionInfoBuilder api.
190
builder_module, builder_class_name = _version_formats[format]
191
module = __import__(builder_module, globals(), locals(),
192
[builder_class_name])
193
klass = getattr(module, builder_class_name)
197
def get_builder_formats():
198
"""Get the possible list of formats"""
199
formats = _version_formats.keys()
204
register_builder('rio',
205
'bzrlib.version_info_formats.format_rio',
206
'RioVersionInfoBuilder')
207
register_builder('python',
208
'bzrlib.version_info_formats.format_python',
209
'PythonVersionInfoBuilder')