70
70
# revisions can be serialized.
72
72
from copy import copy
74
from bzrlib.osutils import (
73
from cStringIO import StringIO
77
from bzrlib.osutils import contains_whitespace, contains_linebreaks
81
79
class Testament(object):
82
80
"""Reduced summary of a revision.
89
87
- compared to a revision
92
long_header = 'bazaar-ng testament version 1\n'
93
short_header = 'bazaar-ng testament short form 1\n'
96
def from_revision(cls, repository, revision_id):
91
def from_revision(cls, branch, revision_id):
97
92
"""Produce a new testament from a historical revision"""
98
rev = repository.get_revision(revision_id)
99
inventory = repository.get_inventory(revision_id)
93
rev = branch.get_revision(revision_id)
94
inventory = branch.get_inventory(revision_id)
100
95
return cls(rev, inventory)
102
97
def __init__(self, rev, inventory):
103
98
"""Create a new testament for rev using inventory."""
104
self.revision_id = rev.revision_id
99
self.revision_id = str(rev.revision_id)
105
100
self.committer = rev.committer
106
101
self.timezone = rev.timezone or 0
107
102
self.timestamp = rev.timestamp
109
104
self.parent_ids = rev.parent_ids[:]
110
105
self.inventory = inventory
111
106
self.revprops = copy(rev.properties)
112
if contains_whitespace(self.revision_id):
113
raise ValueError(self.revision_id)
114
if contains_linebreaks(self.committer):
115
raise ValueError(self.committer)
107
assert not contains_whitespace(self.revision_id)
108
assert not contains_linebreaks(self.committer)
117
110
def as_text_lines(self):
118
111
"""Yield text form as a sequence of lines.
130
124
# inventory length contains the root, which is not shown here
132
126
for parent_id in sorted(self.parent_ids):
133
if contains_whitespace(parent_id):
134
raise ValueError(parent_id)
127
assert not contains_whitespace(parent_id)
135
128
a(' %s\n' % parent_id)
137
130
for l in self.message.splitlines():
139
132
a('inventory:\n')
140
for path, ie in self._get_entries():
133
for path, ie in self.inventory.iter_entries():
141
134
a(self._entry_to_line(path, ie))
142
135
r.extend(self._revprops_to_lines())
143
return [line.encode('utf-8') for line in r]
145
def _get_entries(self):
146
entries = self.inventory.iter_entries()
138
assert isinstance(l, basestring), \
139
'%r of type %s is not a plain string' % (l, type(l))
150
142
def _escape_path(self, path):
151
if contains_linebreaks(path):
152
raise ValueError(path)
153
return unicode(path.replace('\\', '/').replace(' ', '\ '))
143
assert not contains_linebreaks(path)
144
return unicode(path.replace('\\', '/').replace(' ', '\ ')).encode('utf-8')
155
146
def _entry_to_line(self, path, ie):
156
147
"""Turn an inventory entry into a testament line"""
157
if contains_whitespace(ie.file_id):
158
raise ValueError(ie.file_id)
148
l = ' ' + str(ie.kind)
149
l += ' ' + self._escape_path(path)
150
assert not contains_whitespace(ie.file_id)
151
l += ' ' + unicode(ie.file_id).encode('utf-8')
161
152
if ie.kind == 'file':
162
153
# TODO: avoid switching on kind
164
raise AssertionError()
165
content = ie.text_sha1
155
l += ' ' + ie.text_sha1
167
156
elif ie.kind == 'symlink':
168
if not ie.symlink_target:
169
raise AssertionError()
170
content = self._escape_path(ie.symlink_target)
173
l = u' %s %s %s%s%s\n' % (ie.kind, self._escape_path(path),
174
ie.file_id.decode('utf8'),
175
content_spacer, content)
157
assert ie.symlink_target
158
l += ' ' + self._escape_path(ie.symlink_target)
178
162
def as_text(self):
181
165
def as_short_text(self):
182
166
"""Return short digest-based testament."""
183
return (self.short_header +
168
map(s.update, self.as_text_lines())
169
return ('bazaar-ng testament short form 1\n'
184
170
'revision-id: %s\n'
186
% (self.revision_id, self.as_sha1()))
172
% (self.revision_id, s.hexdigest()))
188
174
def _revprops_to_lines(self):
189
175
"""Pack up revision properties."""
192
178
r = ['properties:\n']
193
179
for name, value in sorted(self.revprops.items()):
194
if contains_whitespace(name):
195
raise ValueError(name)
180
assert isinstance(name, str)
181
assert not contains_whitespace(name)
196
182
r.append(' %s:\n' % name)
197
183
for line in value.splitlines():
198
r.append(u' %s\n' % line)
184
if not isinstance(line, str):
185
line = line.encode('utf-8')
186
r.append(' %s\n' % line)
203
map(s.update, self.as_text_lines())
207
class StrictTestament(Testament):
208
"""This testament format is for use as a checksum in bundle format 0.8"""
210
long_header = 'bazaar-ng testament version 2.1\n'
211
short_header = 'bazaar-ng testament short form 2.1\n'
212
def _entry_to_line(self, path, ie):
213
l = Testament._entry_to_line(self, path, ie)[:-1]
214
l += ' ' + ie.revision
215
l += {True: ' yes\n', False: ' no\n'}[ie.executable]
219
class StrictTestament3(StrictTestament):
220
"""This testament format is for use as a checksum in bundle format 0.9+
222
It differs from StrictTestament by including data about the tree root.
225
long_header = 'bazaar testament version 3 strict\n'
226
short_header = 'bazaar testament short form 3 strict\n'
227
def _get_entries(self):
228
return self.inventory.iter_entries()
230
def _escape_path(self, path):
231
if contains_linebreaks(path):
232
raise ValueError(path)
235
return unicode(path.replace('\\', '/').replace(' ', '\ '))