~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/testament.py

  • Committer: Ian Clatworthy
  • Date: 2007-08-13 14:33:10 UTC
  • mto: (2733.1.1 ianc-integration)
  • mto: This revision was merged to the branch mainline in revision 2734.
  • Revision ID: ian.clatworthy@internode.on.net-20070813143310-twhj4la0qnupvze8
Added Quick Start Summary

Show diffs side-by-side

added added

removed removed

Lines of Context:
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
"""Testament - a summary of a revision for signing.
18
18
 
19
 
A testament can be defined as "something that serves as tangible
 
19
A testament can be defined as "something that serves as tangible 
20
20
proof or evidence."  In bzr we use them to allow people to certify
21
 
particular revisions as authentic.
 
21
particular revisions as authentic.  
22
22
 
23
23
The goal is that if two revisions are semantically equal, then they will
24
24
have a byte-for-byte equal testament.  We can define different versions of
61
61
 
62
62
# XXX: At the moment, clients trust that the graph described in a weave
63
63
# is accurate, but that's not covered by the testament.  Perhaps the best
64
 
# fix is when verifying a revision to make sure that every file mentioned
 
64
# fix is when verifying a revision to make sure that every file mentioned 
65
65
# in the revision has compatible ancestry links.
66
66
 
67
67
# TODO: perhaps write timestamp in a more readable form
70
70
# revisions can be serialized.
71
71
 
72
72
from copy import copy
 
73
from sha import sha
73
74
 
74
 
from bzrlib.osutils import (
75
 
    contains_whitespace,
76
 
    contains_linebreaks,
77
 
    sha_strings,
78
 
    )
79
 
from bzrlib.tree import Tree
 
75
from bzrlib.osutils import contains_whitespace, contains_linebreaks
80
76
 
81
77
 
82
78
class Testament(object):
83
79
    """Reduced summary of a revision.
84
80
 
85
 
    Testaments can be
 
81
    Testaments can be 
86
82
 
87
83
      - produced from a revision
88
84
      - written to a stream
92
88
 
93
89
    long_header = 'bazaar-ng testament version 1\n'
94
90
    short_header = 'bazaar-ng testament short form 1\n'
95
 
    include_root = False
96
91
 
97
92
    @classmethod
98
93
    def from_revision(cls, repository, revision_id):
99
 
        """Produce a new testament from a historical revision."""
 
94
        """Produce a new testament from a historical revision"""
100
95
        rev = repository.get_revision(revision_id)
101
 
        tree = repository.revision_tree(revision_id)
102
 
        return cls(rev, tree)
103
 
 
104
 
    @classmethod
105
 
    def from_revision_tree(cls, tree):
106
 
        """Produce a new testament from a revision tree."""
107
 
        rev = tree._repository.get_revision(tree.get_revision_id())
108
 
        return cls(rev, tree)
109
 
 
110
 
    def __init__(self, rev, tree):
111
 
        """Create a new testament for rev using tree."""
 
96
        inventory = repository.get_inventory(revision_id)
 
97
        return cls(rev, inventory)
 
98
 
 
99
    def __init__(self, rev, inventory):
 
100
        """Create a new testament for rev using inventory."""
112
101
        self.revision_id = rev.revision_id
113
102
        self.committer = rev.committer
114
103
        self.timezone = rev.timezone or 0
115
104
        self.timestamp = rev.timestamp
116
105
        self.message = rev.message
117
106
        self.parent_ids = rev.parent_ids[:]
118
 
        if not isinstance(tree, Tree):
119
 
            raise TypeError("As of bzr 2.4 Testament.__init__() takes a "
120
 
                "Revision and a Tree.")
121
 
        self.tree = tree
 
107
        self.inventory = inventory
122
108
        self.revprops = copy(rev.properties)
123
 
        if contains_whitespace(self.revision_id):
124
 
            raise ValueError(self.revision_id)
125
 
        if contains_linebreaks(self.committer):
126
 
            raise ValueError(self.committer)
 
109
        assert not contains_whitespace(self.revision_id)
 
110
        assert not contains_linebreaks(self.committer)
127
111
 
128
112
    def as_text_lines(self):
129
113
        """Yield text form as a sequence of lines.
141
125
        # inventory length contains the root, which is not shown here
142
126
        a('parents:\n')
143
127
        for parent_id in sorted(self.parent_ids):
144
 
            if contains_whitespace(parent_id):
145
 
                raise ValueError(parent_id)
 
128
            assert not contains_whitespace(parent_id)
146
129
            a('  %s\n' % parent_id)
147
130
        a('message:\n')
148
131
        for l in self.message.splitlines():
151
134
        for path, ie in self._get_entries():
152
135
            a(self._entry_to_line(path, ie))
153
136
        r.extend(self._revprops_to_lines())
 
137
        if __debug__:
 
138
            for l in r:
 
139
                assert isinstance(l, basestring), \
 
140
                    '%r of type %s is not a plain string' % (l, type(l))
154
141
        return [line.encode('utf-8') for line in r]
155
142
 
156
143
    def _get_entries(self):
157
 
        return ((path, ie) for (path, versioned, kind, file_id, ie) in
158
 
                self.tree.list_files(include_root=self.include_root))
 
144
        entries = self.inventory.iter_entries()
 
145
        entries.next()
 
146
        return entries
159
147
 
160
148
    def _escape_path(self, path):
161
 
        if contains_linebreaks(path):
162
 
            raise ValueError(path)
 
149
        assert not contains_linebreaks(path)
163
150
        return unicode(path.replace('\\', '/').replace(' ', '\ '))
164
151
 
165
152
    def _entry_to_line(self, path, ie):
166
153
        """Turn an inventory entry into a testament line"""
167
 
        if contains_whitespace(ie.file_id):
168
 
            raise ValueError(ie.file_id)
 
154
        assert not contains_whitespace(ie.file_id)
 
155
 
169
156
        content = ''
170
157
        content_spacer=''
171
158
        if ie.kind == 'file':
172
159
            # TODO: avoid switching on kind
173
 
            if not ie.text_sha1:
174
 
                raise AssertionError()
 
160
            assert ie.text_sha1
175
161
            content = ie.text_sha1
176
162
            content_spacer = ' '
177
163
        elif ie.kind == 'symlink':
178
 
            if not ie.symlink_target:
179
 
                raise AssertionError()
 
164
            assert ie.symlink_target
180
165
            content = self._escape_path(ie.symlink_target)
181
166
            content_spacer = ' '
182
167
 
190
175
 
191
176
    def as_short_text(self):
192
177
        """Return short digest-based testament."""
193
 
        return (self.short_header +
 
178
        return (self.short_header + 
194
179
                'revision-id: %s\n'
195
180
                'sha1: %s\n'
196
181
                % (self.revision_id, self.as_sha1()))
201
186
            return []
202
187
        r = ['properties:\n']
203
188
        for name, value in sorted(self.revprops.items()):
204
 
            if contains_whitespace(name):
205
 
                raise ValueError(name)
 
189
            assert isinstance(name, str)
 
190
            assert not contains_whitespace(name)
206
191
            r.append('  %s:\n' % name)
207
192
            for line in value.splitlines():
208
193
                r.append(u'    %s\n' % line)
209
194
        return r
210
195
 
211
196
    def as_sha1(self):
212
 
        return sha_strings(self.as_text_lines())
 
197
        s = sha()
 
198
        map(s.update, self.as_text_lines())
 
199
        return s.hexdigest()
213
200
 
214
201
 
215
202
class StrictTestament(Testament):
217
204
 
218
205
    long_header = 'bazaar-ng testament version 2.1\n'
219
206
    short_header = 'bazaar-ng testament short form 2.1\n'
220
 
    include_root = False
221
207
    def _entry_to_line(self, path, ie):
222
208
        l = Testament._entry_to_line(self, path, ie)[:-1]
223
209
        l += ' ' + ie.revision
227
213
 
228
214
class StrictTestament3(StrictTestament):
229
215
    """This testament format is for use as a checksum in bundle format 0.9+
230
 
 
 
216
    
231
217
    It differs from StrictTestament by including data about the tree root.
232
218
    """
233
219
 
234
220
    long_header = 'bazaar testament version 3 strict\n'
235
221
    short_header = 'bazaar testament short form 3 strict\n'
236
 
    include_root = True
 
222
    def _get_entries(self):
 
223
        return self.inventory.iter_entries()
237
224
 
238
225
    def _escape_path(self, path):
239
 
        if contains_linebreaks(path):
240
 
            raise ValueError(path)
 
226
        assert not contains_linebreaks(path)
241
227
        if path == '':
242
228
            path = '.'
243
229
        return unicode(path.replace('\\', '/').replace(' ', '\ '))