/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/testament.py

  • Committer: Robert Collins
  • Date: 2007-04-19 02:27:44 UTC
  • mto: This revision was merged to the branch mainline in revision 2426.
  • Revision ID: robertc@robertcollins.net-20070419022744-pfdqz42kp1wizh43
``make docs`` now creates a man page at ``man1/bzr.1`` fixing bug 107388.
(Robert Collins)

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 ..osutils import (
75
 
    contains_whitespace,
76
 
    contains_linebreaks,
77
 
    sha_strings,
78
 
    )
79
 
from ..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.
134
118
        r = []
135
119
        a = r.append
136
120
        a(self.long_header)
137
 
        a('revision-id: %s\n' % self.revision_id.decode('utf-8'))
 
121
        a('revision-id: %s\n' % self.revision_id)
138
122
        a('committer: %s\n' % self.committer)
139
123
        a('timestamp: %d\n' % self.timestamp)
140
124
        a('timezone: %d\n' % self.timezone)
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)
146
 
            a('  %s\n' % parent_id.decode('utf-8'))
 
128
            assert not contains_whitespace(parent_id)
 
129
            a('  %s\n' % parent_id)
147
130
        a('message:\n')
148
131
        for l in self.message.splitlines():
149
132
            a('  %s\n' % l)
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, file_class, kind, 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)
163
 
        if not isinstance(path, str):
164
 
            # TODO(jelmer): Clean this up for pad.lv/1696545
165
 
            path = path.decode('ascii')
166
 
        return path.replace(u'\\', u'/').replace(u' ', u'\\ ')
 
149
        assert not contains_linebreaks(path)
 
150
        return unicode(path.replace('\\', '/').replace(' ', '\ '))
167
151
 
168
152
    def _entry_to_line(self, path, ie):
169
153
        """Turn an inventory entry into a testament line"""
170
 
        if contains_whitespace(ie.file_id):
171
 
            raise ValueError(ie.file_id)
 
154
        assert not contains_whitespace(ie.file_id)
 
155
 
172
156
        content = ''
173
 
        content_spacer = ''
 
157
        content_spacer=''
174
158
        if ie.kind == 'file':
175
159
            # TODO: avoid switching on kind
176
 
            if not ie.text_sha1:
177
 
                raise AssertionError()
178
 
            content = ie.text_sha1.decode('ascii')
 
160
            assert ie.text_sha1
 
161
            content = ie.text_sha1
179
162
            content_spacer = ' '
180
163
        elif ie.kind == 'symlink':
181
 
            if not ie.symlink_target:
182
 
                raise AssertionError()
 
164
            assert ie.symlink_target
183
165
            content = self._escape_path(ie.symlink_target)
184
166
            content_spacer = ' '
185
167
 
189
171
        return l
190
172
 
191
173
    def as_text(self):
192
 
        return b''.join(self.as_text_lines())
 
174
        return ''.join(self.as_text_lines())
193
175
 
194
176
    def as_short_text(self):
195
177
        """Return short digest-based testament."""
196
 
        return (self.short_header.encode('ascii') +
197
 
                b'revision-id: %s\n'
198
 
                b'sha1: %s\n'
 
178
        return (self.short_header + 
 
179
                'revision-id: %s\n'
 
180
                'sha1: %s\n'
199
181
                % (self.revision_id, self.as_sha1()))
200
182
 
201
183
    def _revprops_to_lines(self):
204
186
            return []
205
187
        r = ['properties:\n']
206
188
        for name, value in sorted(self.revprops.items()):
207
 
            if contains_whitespace(name):
208
 
                raise ValueError(name)
 
189
            assert isinstance(name, str)
 
190
            assert not contains_whitespace(name)
209
191
            r.append('  %s:\n' % name)
210
192
            for line in value.splitlines():
211
193
                r.append(u'    %s\n' % line)
212
194
        return r
213
195
 
214
196
    def as_sha1(self):
215
 
        return sha_strings(self.as_text_lines())
 
197
        s = sha()
 
198
        map(s.update, self.as_text_lines())
 
199
        return s.hexdigest()
216
200
 
217
201
 
218
202
class StrictTestament(Testament):
220
204
 
221
205
    long_header = 'bazaar-ng testament version 2.1\n'
222
206
    short_header = 'bazaar-ng testament short form 2.1\n'
223
 
    include_root = False
224
 
 
225
207
    def _entry_to_line(self, path, ie):
226
208
        l = Testament._entry_to_line(self, path, ie)[:-1]
227
 
        l += ' ' + ie.revision.decode('utf-8')
 
209
        l += ' ' + ie.revision
228
210
        l += {True: ' yes\n', False: ' no\n'}[ie.executable]
229
211
        return l
230
212
 
231
213
 
232
214
class StrictTestament3(StrictTestament):
233
215
    """This testament format is for use as a checksum in bundle format 0.9+
234
 
 
 
216
    
235
217
    It differs from StrictTestament by including data about the tree root.
236
218
    """
237
219
 
238
220
    long_header = 'bazaar testament version 3 strict\n'
239
221
    short_header = 'bazaar testament short form 3 strict\n'
240
 
    include_root = True
 
222
    def _get_entries(self):
 
223
        return self.inventory.iter_entries()
241
224
 
242
225
    def _escape_path(self, path):
243
 
        if contains_linebreaks(path):
244
 
            raise ValueError(path)
245
 
        if not isinstance(path, str):
246
 
            # TODO(jelmer): Clean this up for pad.lv/1696545
247
 
            path = path.decode('ascii')
248
 
        if path == u'':
249
 
            path = u'.'
250
 
        return path.replace(u'\\', u'/').replace(u' ', u'\\ ')
 
226
        assert not contains_linebreaks(path)
 
227
        if path == '':
 
228
            path = '.'
 
229
        return unicode(path.replace('\\', '/').replace(' ', '\ '))