70
70
# revisions can be serialized.
72
72
from copy import copy
73
from cStringIO import StringIO
75
73
from sha import sha
77
75
from bzrlib.osutils import contains_whitespace, contains_linebreaks
79
78
class Testament(object):
80
79
"""Reduced summary of a revision.
87
86
- compared to a revision
89
long_header = 'bazaar-ng testament version 1\n'
90
short_header = 'bazaar-ng testament short form 1\n'
91
def from_revision(cls, branch, revision_id):
93
def from_revision(cls, repository, revision_id):
92
94
"""Produce a new testament from a historical revision"""
93
rev = branch.get_revision(revision_id)
94
inventory = branch.get_inventory(revision_id)
95
rev = repository.get_revision(revision_id)
96
inventory = repository.get_inventory(revision_id)
95
97
return cls(rev, inventory)
97
99
def __init__(self, rev, inventory):
130
131
for l in self.message.splitlines():
132
133
a('inventory:\n')
133
for path, ie in self.inventory.iter_entries():
134
entries = self.inventory.iter_entries()
136
for path, ie in entries:
134
137
a(self._entry_to_line(path, ie))
135
138
r.extend(self._revprops_to_lines())
138
141
assert isinstance(l, basestring), \
139
142
'%r of type %s is not a plain string' % (l, type(l))
143
return [line.encode('utf-8') for line in r]
142
145
def _escape_path(self, path):
143
146
assert not contains_linebreaks(path)
144
return unicode(path.replace('\\', '/').replace(' ', '\ ')).encode('utf-8')
147
return unicode(path.replace('\\', '/').replace(' ', '\ '))
146
149
def _entry_to_line(self, path, ie):
147
150
"""Turn an inventory entry into a testament line"""
148
l = ' ' + str(ie.kind)
149
l += ' ' + self._escape_path(path)
150
151
assert not contains_whitespace(ie.file_id)
151
l += ' ' + unicode(ie.file_id).encode('utf-8')
152
155
if ie.kind == 'file':
153
156
# TODO: avoid switching on kind
154
157
assert ie.text_sha1
155
l += ' ' + ie.text_sha1
158
content = ie.text_sha1
156
160
elif ie.kind == 'symlink':
157
161
assert ie.symlink_target
158
l += ' ' + self._escape_path(ie.symlink_target)
162
content = self._escape_path(ie.symlink_target)
165
l = u' %s %s %s%s%s\n' % (ie.kind, self._escape_path(path),
167
content_spacer, content)
162
170
def as_text(self):
165
173
def as_short_text(self):
166
174
"""Return short digest-based testament."""
168
map(s.update, self.as_text_lines())
169
return ('bazaar-ng testament short form 1\n'
175
return (self.short_header +
170
176
'revision-id: %s\n'
172
% (self.revision_id, s.hexdigest()))
178
% (self.revision_id, self.as_sha1()))
174
180
def _revprops_to_lines(self):
175
181
"""Pack up revision properties."""
181
187
assert not contains_whitespace(name)
182
188
r.append(' %s:\n' % name)
183
189
for line in value.splitlines():
184
if not isinstance(line, str):
185
line = line.encode('utf-8')
186
r.append(' %s\n' % line)
190
r.append(u' %s\n' % line)
195
map(s.update, self.as_text_lines())
199
class StrictTestament(Testament):
200
"""This testament format is for use as a checksum in changesets"""
202
long_header = 'bazaar-ng testament version 2.1\n'
203
short_header = 'bazaar-ng testament short form 2.1\n'
204
def _entry_to_line(self, path, ie):
205
l = Testament._entry_to_line(self, path, ie)[:-1]
206
l += ' ' + ie.revision
207
l += {True: ' yes\n', False: ' no\n'}[ie.executable]