bzr branch
http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
| 
1185.16.12
by Martin Pool
 - basic testament class  | 
1  | 
# Copyright (C) 2005 by Canonical Ltd
 | 
2  | 
#
 | 
|
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.
 | 
|
7  | 
#
 | 
|
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.
 | 
|
12  | 
#
 | 
|
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
 | 
|
16  | 
||
17  | 
"""Testament - a summary of a revision for signing.
 | 
|
18  | 
||
19  | 
A testament can be defined as "something that serves as tangible 
 | 
|
20  | 
proof or evidence."  In bzr we use them to allow people to certify
 | 
|
21  | 
particular revisions as authentic.  
 | 
|
22  | 
||
| 
1185.16.19
by Martin Pool
 - testament now contains summary of parents and inventory  | 
23  | 
The goal is that if two revisions are semantically equal, then they will
 | 
24  | 
have a byte-for-byte equal testament.  We can define different versions of
 | 
|
25  | 
"semantically equal" by using different testament classes; e.g. one that
 | 
|
26  | 
includes or ignores file-ids.
 | 
|
27  | 
||
| 
1185.16.12
by Martin Pool
 - basic testament class  | 
28  | 
We sign a testament rather than the revision XML itself for several reasons.
 | 
29  | 
The most important is that the form in which the revision is stored
 | 
|
30  | 
internally is designed for that purpose, and contains information which need
 | 
|
31  | 
not be attested to by the signer.  For example the inventory contains the
 | 
|
32  | 
last-changed revision for a file, but this is not necessarily something the
 | 
|
33  | 
user cares to sign.
 | 
|
34  | 
||
35  | 
Having unnecessary fields signed makes the signatures brittle when the same
 | 
|
36  | 
revision is stored in different branches or when the format is upgraded.
 | 
|
37  | 
||
38  | 
Handling upgrades is another motivation for using testaments separate from
 | 
|
39  | 
the stored revision.  We would like to be able to compare a signature
 | 
|
40  | 
generated from an old-format tree to newer tree, or vice versa.  This could
 | 
|
41  | 
be done by comparing the revisions but that makes it unclear about exactly
 | 
|
42  | 
what is being compared or not.
 | 
|
43  | 
||
44  | 
Different signing keys might indicate different levels of trust; we can in
 | 
|
45  | 
the future extend this to allow signatures indicating not just that a
 | 
|
46  | 
particular version is authentic but that it has other properties.
 | 
|
| 
1185.16.19
by Martin Pool
 - testament now contains summary of parents and inventory  | 
47  | 
|
48  | 
The signature can be applied to either the full testament or to just a
 | 
|
49  | 
hash of it.
 | 
|
50  | 
||
51  | 
Testament format 1
 | 
|
52  | 
~~~~~~~~~~~~~~~~~~
 | 
|
53  | 
||
54  | 
* timestamps are given as integers to avoid rounding errors
 | 
|
55  | 
* parents given in lexicographical order
 | 
|
56  | 
* indented-text form similar to log; intended to be human readable
 | 
|
57  | 
* paths are given with forward slashes
 | 
|
58  | 
* files are named using paths for ease of comparison/debugging
 | 
|
59  | 
* the testament uses unix line-endings (\n)
 | 
|
| 
1185.16.12
by Martin Pool
 - basic testament class  | 
60  | 
"""
 | 
61  | 
||
| 
1185.16.19
by Martin Pool
 - testament now contains summary of parents and inventory  | 
62  | 
# XXX: At the moment, clients trust that the graph described in a weave
 | 
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 
 | 
|
65  | 
# in the revision has compatible ancestry links.
 | 
|
66  | 
||
| 
1185.16.22
by Martin Pool
 - more testament development  | 
67  | 
# TODO: perhaps write timestamp in a more readable form
 | 
68  | 
||
| 
1185.16.24
by Martin Pool
 - add and test 'testament' builtin command  | 
69  | 
# TODO: Perhaps these should just be different formats in which inventories/
 | 
70  | 
# revisions can be serialized.
 | 
|
71  | 
||
| 
1185.16.59
by mbp at sourcefrog
 - store revprops in testaments  | 
72  | 
from copy import copy  | 
| 
1185.16.15
by Martin Pool
 - test text form for testaments  | 
73  | 
from cStringIO import StringIO  | 
74  | 
import string  | 
|
| 
1185.16.23
by Martin Pool
 - clean up imports  | 
75  | 
from sha import sha  | 
| 
1185.16.15
by Martin Pool
 - test text form for testaments  | 
76  | 
|
| 
1185.16.38
by Martin Pool
 - move contains_whitespace and contains_linebreaks to osutils  | 
77  | 
from bzrlib.osutils import contains_whitespace, contains_linebreaks  | 
78  | 
||
| 
1185.16.12
by Martin Pool
 - basic testament class  | 
79  | 
class Testament(object):  | 
80  | 
"""Reduced summary of a revision.  | 
|
81  | 
||
82  | 
    Testaments can be 
 | 
|
83  | 
||
84  | 
      - produced from a revision
 | 
|
| 
1185.21.6
by Jelmer Vernooij
 Fix typo  | 
85  | 
      - written to a stream
 | 
| 
1185.16.12
by Martin Pool
 - basic testament class  | 
86  | 
      - loaded from a stream
 | 
87  | 
      - compared to a revision
 | 
|
88  | 
    """
 | 
|
89  | 
||
90  | 
    @classmethod
 | 
|
| 
1185.67.2
by Aaron Bentley
 Renamed Branch.storage to Branch.repository  | 
91  | 
def from_revision(cls, repository, revision_id):  | 
| 
1185.16.12
by Martin Pool
 - basic testament class  | 
92  | 
"""Produce a new testament from a historical revision"""  | 
| 
1185.67.2
by Aaron Bentley
 Renamed Branch.storage to Branch.repository  | 
93  | 
rev = repository.get_revision(revision_id)  | 
94  | 
inventory = repository.get_inventory(revision_id)  | 
|
| 
1442.1.62
by Robert Collins
 Allow creation of testaments from uncommitted data, and use that to get signatures before committing revisions.  | 
95  | 
return cls(rev, inventory)  | 
96  | 
||
97  | 
def __init__(self, rev, inventory):  | 
|
98  | 
"""Create a new testament for rev using inventory."""  | 
|
99  | 
self.revision_id = str(rev.revision_id)  | 
|
100  | 
self.committer = rev.committer  | 
|
101  | 
self.timezone = rev.timezone or 0  | 
|
102  | 
self.timestamp = rev.timestamp  | 
|
103  | 
self.message = rev.message  | 
|
104  | 
self.parent_ids = rev.parent_ids[:]  | 
|
105  | 
self.inventory = inventory  | 
|
106  | 
self.revprops = copy(rev.properties)  | 
|
107  | 
assert not contains_whitespace(self.revision_id)  | 
|
108  | 
assert not contains_linebreaks(self.committer)  | 
|
| 
1185.16.15
by Martin Pool
 - test text form for testaments  | 
109  | 
|
| 
1185.16.22
by Martin Pool
 - more testament development  | 
110  | 
def as_text_lines(self):  | 
111  | 
"""Yield text form as a sequence of lines.  | 
|
| 
1185.16.15
by Martin Pool
 - test text form for testaments  | 
112  | 
|
113  | 
        The result is returned in utf-8, because it should be signed or
 | 
|
114  | 
        hashed in that encoding.
 | 
|
115  | 
        """
 | 
|
| 
1185.16.22
by Martin Pool
 - more testament development  | 
116  | 
r = []  | 
| 
1553.3.2
by Marien Zwart
 Remove a useless local function.  | 
117  | 
a = r.append  | 
| 
1185.16.22
by Martin Pool
 - more testament development  | 
118  | 
a('bazaar-ng testament version 1\n')  | 
119  | 
a('revision-id: %s\n' % self.revision_id)  | 
|
120  | 
a('committer: %s\n' % self.committer)  | 
|
121  | 
a('timestamp: %d\n' % self.timestamp)  | 
|
122  | 
a('timezone: %d\n' % self.timezone)  | 
|
| 
1185.16.15
by Martin Pool
 - test text form for testaments  | 
123  | 
        # inventory length contains the root, which is not shown here
 | 
| 
1185.16.22
by Martin Pool
 - more testament development  | 
124  | 
a('parents:\n')  | 
| 
1185.16.19
by Martin Pool
 - testament now contains summary of parents and inventory  | 
125  | 
for parent_id in sorted(self.parent_ids):  | 
126  | 
assert not contains_whitespace(parent_id)  | 
|
| 
1185.16.22
by Martin Pool
 - more testament development  | 
127  | 
a(' %s\n' % parent_id)  | 
128  | 
a('message:\n')  | 
|
| 
1185.16.15
by Martin Pool
 - test text form for testaments  | 
129  | 
for l in self.message.splitlines():  | 
| 
1185.16.22
by Martin Pool
 - more testament development  | 
130  | 
a(' %s\n' % l)  | 
131  | 
a('inventory:\n')  | 
|
| 
1185.16.19
by Martin Pool
 - testament now contains summary of parents and inventory  | 
132  | 
for path, ie in self.inventory.iter_entries():  | 
| 
1185.16.22
by Martin Pool
 - more testament development  | 
133  | 
a(self._entry_to_line(path, ie))  | 
| 
1185.16.59
by mbp at sourcefrog
 - store revprops in testaments  | 
134  | 
r.extend(self._revprops_to_lines())  | 
| 
1185.16.22
by Martin Pool
 - more testament development  | 
135  | 
if __debug__:  | 
136  | 
for l in r:  | 
|
| 
1442.1.62
by Robert Collins
 Allow creation of testaments from uncommitted data, and use that to get signatures before committing revisions.  | 
137  | 
assert isinstance(l, basestring), \  | 
| 
1185.16.22
by Martin Pool
 - more testament development  | 
138  | 
'%r of type %s is not a plain string' % (l, type(l))  | 
| 
1553.3.1
by Marien Zwart
 Make Testament.as_text_lines return utf-8 instead of unicode objects and add a test for this.  | 
139  | 
return [line.encode('utf-8') for line in r]  | 
| 
1185.16.22
by Martin Pool
 - more testament development  | 
140  | 
|
141  | 
def _escape_path(self, path):  | 
|
142  | 
assert not contains_linebreaks(path)  | 
|
| 
1185.16.25
by Martin Pool
 - testament symlink support  | 
143  | 
return unicode(path.replace('\\', '/').replace(' ', '\ ')).encode('utf-8')  | 
| 
1185.16.22
by Martin Pool
 - more testament development  | 
144  | 
|
145  | 
def _entry_to_line(self, path, ie):  | 
|
146  | 
"""Turn an inventory entry into a testament line"""  | 
|
147  | 
l = ' ' + str(ie.kind)  | 
|
148  | 
l += ' ' + self._escape_path(path)  | 
|
149  | 
assert not contains_whitespace(ie.file_id)  | 
|
150  | 
l += ' ' + unicode(ie.file_id).encode('utf-8')  | 
|
151  | 
if ie.kind == 'file':  | 
|
152  | 
            # TODO: avoid switching on kind
 | 
|
153  | 
assert ie.text_sha1  | 
|
154  | 
l += ' ' + ie.text_sha1  | 
|
| 
1185.16.25
by Martin Pool
 - testament symlink support  | 
155  | 
elif ie.kind == 'symlink':  | 
156  | 
assert ie.symlink_target  | 
|
157  | 
l += ' ' + self._escape_path(ie.symlink_target)  | 
|
| 
1185.16.22
by Martin Pool
 - more testament development  | 
158  | 
l += '\n'  | 
159  | 
return l  | 
|
160  | 
||
161  | 
def as_text(self):  | 
|
162  | 
return ''.join(self.as_text_lines())  | 
|
| 
1185.16.15
by Martin Pool
 - test text form for testaments  | 
163  | 
|
| 
1185.16.20
by Martin Pool
 - add short-form of testaments  | 
164  | 
def as_short_text(self):  | 
165  | 
"""Return short digest-based testament."""  | 
|
| 
1185.16.23
by Martin Pool
 - clean up imports  | 
166  | 
s = sha()  | 
| 
1185.16.22
by Martin Pool
 - more testament development  | 
167  | 
map(s.update, self.as_text_lines())  | 
| 
1185.16.20
by Martin Pool
 - add short-form of testaments  | 
168  | 
return ('bazaar-ng testament short form 1\n'  | 
| 
1185.16.25
by Martin Pool
 - testament symlink support  | 
169  | 
'revision-id: %s\n'  | 
170  | 
'sha1: %s\n'  | 
|
| 
1185.16.20
by Martin Pool
 - add short-form of testaments  | 
171  | 
% (self.revision_id, s.hexdigest()))  | 
172  | 
||
| 
1185.16.59
by mbp at sourcefrog
 - store revprops in testaments  | 
173  | 
def _revprops_to_lines(self):  | 
174  | 
"""Pack up revision properties."""  | 
|
175  | 
if not self.revprops:  | 
|
176  | 
return []  | 
|
177  | 
r = ['properties:\n']  | 
|
178  | 
for name, value in sorted(self.revprops.items()):  | 
|
179  | 
assert isinstance(name, str)  | 
|
180  | 
assert not contains_whitespace(name)  | 
|
181  | 
r.append(' %s:\n' % name)  | 
|
182  | 
for line in value.splitlines():  | 
|
183  | 
if not isinstance(line, str):  | 
|
184  | 
line = line.encode('utf-8')  | 
|
185  | 
r.append(' %s\n' % line)  | 
|
186  | 
return r  |