/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
1
#!/usr/bin/env python
2
"""\
3
Read in a changeset output, and process it into a Changeset object.
4
"""
5
6
import bzrlib, bzrlib.changeset
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
7
import pprint
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
8
import common
9
0.5.57 by John Arbash Meinel
Simplified the header, only output base if it is not the expected one.
10
from bzrlib.trace import mutter
0.5.62 by John Arbash Meinel
Doing some internal validation before allowing processing to continue, additional checks at the command level.
11
from bzrlib.errors import BzrError
0.5.57 by John Arbash Meinel
Simplified the header, only output base if it is not the expected one.
12
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
13
class BadChangeset(Exception): pass
14
class MalformedHeader(BadChangeset): pass
15
class MalformedPatches(BadChangeset): pass
16
class MalformedFooter(BadChangeset): pass
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
17
0.5.11 by John Arbash Meinel
Working on properly representing renames.
18
def _unescape(name):
19
    """Now we want to find the filename effected.
20
    Unfortunately the filename is written out as
21
    repr(filename), which means that it surrounds
22
    the name with quotes which may be single or double
23
    (single is preferred unless there is a single quote in
24
    the filename). And some characters will be escaped.
25
26
    TODO:   There has to be some pythonic way of undo-ing the
27
            representation of a string rather than using eval.
28
    """
29
    delimiter = name[0]
30
    if name[-1] != delimiter:
31
        raise BadChangeset('Could not properly parse the'
32
                ' filename: %r' % name)
33
    # We need to handle escaped hexadecimals too.
34
    return name[1:-1].replace('\"', '"').replace("\'", "'")
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
35
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
36
class RevisionInfo(object):
37
    """Gets filled out for each revision object that is read.
38
    """
39
    def __init__(self, rev_id):
40
        self.rev_id = rev_id
41
        self.sha1 = None
42
        self.committer = None
0.5.39 by John Arbash Meinel
(broken) Working on changing the processing to use a ChangesetTree.
43
        self.date = None
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
44
        self.timestamp = None
45
        self.timezone = None
46
        self.inventory_id = None
47
        self.inventory_sha1 = None
48
49
        self.parents = None
50
        self.message = None
51
52
    def __str__(self):
53
        return pprint.pformat(self.__dict__)
54
0.5.37 by John Arbash Meinel
Made read_changeset able to spit out 'Revision' entities.
55
    def as_revision(self):
56
        from bzrlib.revision import Revision, RevisionReference
57
        rev = Revision(revision_id=self.rev_id,
58
            committer=self.committer,
59
            timestamp=float(self.timestamp),
60
            timezone=int(self.timezone),
61
            inventory_id=self.inventory_id,
62
            inventory_sha1=self.inventory_sha1,
63
            message='\n'.join(self.message))
64
0.5.59 by John Arbash Meinel
Several fixes for handling the case where you are doing a changeset against revno=0 (Null base)
65
        if self.parents:
66
            for parent in self.parents:
67
                rev_id, sha1 = parent.split('\t')
68
                rev.parents.append(RevisionReference(rev_id, sha1))
0.5.37 by John Arbash Meinel
Made read_changeset able to spit out 'Revision' entities.
69
70
        return rev
71
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
72
class ChangesetInfo(object):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
73
    """This contains the meta information. Stuff that allows you to
74
    recreate the revision or inventory XML.
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
75
    """
76
    def __init__(self):
77
        self.committer = None
78
        self.date = None
0.5.17 by John Arbash Meinel
adding apply-changset, plus more meta information.
79
        self.message = None
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
80
        self.base = None
81
        self.base_sha1 = None
82
0.5.39 by John Arbash Meinel
(broken) Working on changing the processing to use a ChangesetTree.
83
        # A list of RevisionInfo objects
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
84
        self.revisions = []
0.5.56 by John Arbash Meinel
A couple more fixups, it seems actually capable now of writing out a changeset, and reading it back.
85
86
        self.actions = []
87
88
        # The next entries are created during complete_info() and
89
        # other post-read functions.
90
91
        # A list of real Revision objects
92
        self.real_revisions = []
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
93
        self.text_ids = {} # file_id => text_id
94
95
        self.timestamp = None
96
        self.timezone = None
0.5.15 by John Arbash Meinel
Created an apply-changeset function, and modified output for better parsing.
97
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
98
    def __str__(self):
99
        return pprint.pformat(self.__dict__)
100
0.5.39 by John Arbash Meinel
(broken) Working on changing the processing to use a ChangesetTree.
101
    def complete_info(self):
102
        """This makes sure that all information is properly
103
        split up, based on the assumptions that can be made
104
        when information is missing.
105
        """
0.5.56 by John Arbash Meinel
A couple more fixups, it seems actually capable now of writing out a changeset, and reading it back.
106
        # Put in all of the guessable information.
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
107
        if not self.timestamp and self.date:
108
            self.timestamp, self.timezone = common.unpack_highres_date(self.date)
109
0.5.56 by John Arbash Meinel
A couple more fixups, it seems actually capable now of writing out a changeset, and reading it back.
110
        self.real_revisions = []
0.5.39 by John Arbash Meinel
(broken) Working on changing the processing to use a ChangesetTree.
111
        for rev in self.revisions:
0.5.60 by John Arbash Meinel
read_changeset now parses the date: subheader of revisions correctly.
112
            if rev.timestamp is None:
113
                if rev.date is not None:
114
                    rev.timestamp, rev.timezone = \
115
                            common.unpack_highres_date(rev.date)
116
                else:
117
                    rev.timestamp = self.timestamp
118
                    rev.timezone = self.timezone
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
119
            if rev.message is None and self.message:
120
                rev.message = self.message
121
            if rev.committer is None and self.committer:
122
                rev.committer = self.committer
0.5.56 by John Arbash Meinel
A couple more fixups, it seems actually capable now of writing out a changeset, and reading it back.
123
            if rev.inventory_id is None:
124
                rev.inventory_id = rev.rev_id
125
            self.real_revisions.append(rev.as_revision())
126
127
        if self.base is None:
128
            # When we don't have a base, then the real base
129
            # is the first parent of the first revision listed
130
            rev = self.real_revisions[0]
0.5.59 by John Arbash Meinel
Several fixes for handling the case where you are doing a changeset against revno=0 (Null base)
131
            if len(rev.parents) == 0:
132
                # There is no base listed, and
133
                # the lowest revision doesn't have a parent
134
                # so this is probably against the empty tree
135
                # and thus base truly is None
136
                self.base = None
137
                self.base_sha1 = None
138
            else:
139
                self.base = rev.parents[0].revision_id
140
                # In general, if self.base is None, self.base_sha1 should
141
                # also be None
142
                if self.base_sha1 is not None:
143
                    assert self.base_sha1 == rev.parents[0].revision_sha1
144
                self.base_sha1 = rev.parents[0].revision_sha1
0.5.56 by John Arbash Meinel
A couple more fixups, it seems actually capable now of writing out a changeset, and reading it back.
145
146
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
147
148
class ChangesetReader(object):
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
149
    """This class reads in a changeset from a file, and returns
150
    a Changeset object, which can then be applied against a tree.
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
151
    """
152
    def __init__(self, from_file):
153
        """Read in the changeset from the file.
154
155
        :param from_file: A file-like object (must have iterator support).
156
        """
157
        object.__init__(self)
158
        self.from_file = from_file
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
159
        self._next_line = None
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
160
        
161
        self.info = ChangesetInfo()
162
        # We put the actual inventory ids in the footer, so that the patch
163
        # is easier to read for humans.
164
        # Unfortunately, that means we need to read everything before we
165
        # can create a proper changeset.
0.5.62 by John Arbash Meinel
Doing some internal validation before allowing processing to continue, additional checks at the command level.
166
        self._read()
167
        self._validate()
168
169
    def _read(self):
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
170
        self._read_header()
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
171
        self._read_patches()
172
        self._read_footer()
173
0.5.62 by John Arbash Meinel
Doing some internal validation before allowing processing to continue, additional checks at the command level.
174
    def _validate(self):
175
        """Make sure that the information read in makes sense
176
        and passes appropriate checksums.
177
        """
178
        # Fill in all the missing blanks for the revisions
179
        # and generate the real_revisions list.
180
        self.info.complete_info()
181
        self._validate_revisions()
182
183
    def _validate_revisions(self):
184
        """Make sure all revision entries match their checksum."""
185
        from bzrlib.xml import pack_xml
186
        from cStringIO import StringIO
187
        from bzrlib.osutils import sha_file
188
189
        # This is a mapping from each revision id to it's sha hash
190
        rev_to_sha1 = {}
191
192
        for rev, rev_info in zip(self.info.real_revisions, self.info.revisions):
193
            sio = StringIO()
194
            pack_xml(rev, sio)
195
            sio.seek(0)
196
            sha1 = sha_file(sio)
197
            if sha1 != rev_info.sha1:
198
                raise BzrError('Revision checksum mismatch.'
199
                    ' For rev_id {%s} supplied sha1 (%s) != measured (%s)'
200
                    % (rev.revision_id, rev_info.sha1, sha1))
201
            if rev_to_sha1.has_key(rev.revision_id):
202
                raise BzrError('Revision {%s} given twice in the list'
203
                        % (rev.revision_id))
204
            rev_to_sha1[rev.revision_id] = sha1
205
206
        # Now that we've checked all the sha1 sums, we can make sure that
207
        # at least for the small list we have, all of the references are
208
        # valid.
209
        for rev in self.info.real_revisions:
210
            for parent in rev.parents:
211
                if parent.revision_id in rev_to_sha1:
212
                    if parent.revision_sha1 != rev_to_sha1[parent.revision_id]:
213
                        raise BzrError('Parent revision checksum mismatch.'
214
                                ' A parent was referenced with an incorrect checksum'
215
                                ': {%r} %s != %s' % (parent.revision_id,
216
                                                    parent.revision_sha1,
217
                                                    rev_to_sha1[parent.revision_id]))
218
219
220
221
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
222
    def get_info_and_tree(self, branch):
223
        """Return the meta information, and a Changeset tree which can
224
        be used to populate the local stores and working tree, respectively.
225
        """
0.5.59 by John Arbash Meinel
Several fixes for handling the case where you are doing a changeset against revno=0 (Null base)
226
        if self.info.base:
227
            store_base_sha1 = branch.get_revision_sha1(self.info.base) 
228
        else:
229
            store_base_sha1 = None
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
230
        if store_base_sha1 != self.info.base_sha1:
231
            raise BzrError('Base revision sha1 hash in store'
232
                    ' does not match the one read in the changeset'
233
                    ' (%s != %s)' % (store_base_sha1, self.info.base_sha1))
234
        tree = ChangesetTree(branch.revision_tree(self.info.base))
235
        self._update_tree(tree)
236
0.5.56 by John Arbash Meinel
A couple more fixups, it seems actually capable now of writing out a changeset, and reading it back.
237
        return self.info, tree
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
238
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
239
    def _next(self):
240
        """yield the next line, but secretly
241
        keep 1 extra line for peeking.
242
        """
243
        for line in self.from_file:
244
            last = self._next_line
245
            self._next_line = line
246
            if last is not None:
0.5.57 by John Arbash Meinel
Simplified the header, only output base if it is not the expected one.
247
                #mutter('yielding line: %r' % last)
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
248
                yield last
0.5.56 by John Arbash Meinel
A couple more fixups, it seems actually capable now of writing out a changeset, and reading it back.
249
        last = self._next_line
250
        self._next_line = None
0.5.57 by John Arbash Meinel
Simplified the header, only output base if it is not the expected one.
251
        #mutter('yielding line: %r' % last)
0.5.56 by John Arbash Meinel
A couple more fixups, it seems actually capable now of writing out a changeset, and reading it back.
252
        yield last
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
253
254
    def _read_header(self):
255
        """Read the bzr header"""
256
        header = common.get_header()
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
257
        found = False
258
        for line in self._next():
259
            if found:
260
                if (line[:2] != '# ' or line[-1:] != '\n'
261
                        or line[2:-1] != header[0]):
262
                    raise MalformedHeader('Found a header, but it'
263
                        ' was improperly formatted')
264
                header.pop(0) # We read this line.
265
                if not header:
266
                    break # We found everything.
267
            elif (line[:1] == '#' and line[-1:] == '\n'):
268
                line = line[1:-1].strip()
269
                if line[:len(common.header_str)] == common.header_str:
270
                    if line == header[0]:
271
                        found = True
272
                    else:
273
                        raise MalformedHeader('Found what looks like'
274
                                ' a header, but did not match')
275
                    header.pop(0)
276
        else:
277
            raise MalformedHeader('Did not find an opening header')
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
278
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
279
        for line in self._next():
280
            # The bzr header is terminated with a blank line
281
            # which does not start with '#'
282
            if line == '\n':
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
283
                break
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
284
            self._handle_next(line)
285
286
    def _read_next_entry(self, line, indent=1):
287
        """Read in a key-value pair
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
288
        """
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
289
        if line[:1] != '#':
290
            raise MalformedHeader('Bzr header did not start with #')
291
        line = line[1:-1] # Remove the '#' and '\n'
292
        if line[:indent] == ' '*indent:
293
            line = line[indent:]
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
294
        if not line:
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
295
            return None, None# Ignore blank lines
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
296
297
        loc = line.find(': ')
298
        if loc != -1:
299
            key = line[:loc]
300
            value = line[loc+2:]
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
301
            if not value:
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
302
                value = self._read_many(indent=indent+3)
303
        elif line[-1:] == ':':
304
            key = line[:-1]
305
            value = self._read_many(indent=indent+3)
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
306
        else:
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
307
            raise MalformedHeader('While looking for key: value pairs,'
308
                    ' did not find the colon %r' % (line))
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
309
310
        key = key.replace(' ', '_')
0.5.57 by John Arbash Meinel
Simplified the header, only output base if it is not the expected one.
311
        #mutter('found %s: %s' % (key, value))
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
312
        return key, value
313
314
    def _handle_next(self, line):
315
        key, value = self._read_next_entry(line, indent=1)
316
        if key is None:
317
            return
318
319
        if key == 'revision':
320
            self._read_revision(value)
321
        elif hasattr(self.info, key):
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
322
            if getattr(self.info, key) is None:
323
                setattr(self.info, key, value)
324
            else:
325
                raise MalformedHeader('Duplicated Key: %s' % key)
326
        else:
327
            # What do we do with a key we don't recognize
328
            raise MalformedHeader('Unknown Key: %s' % key)
329
        
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
330
    def _read_many(self, indent):
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
331
        """If a line ends with no entry, that means that it should be
332
        followed with multiple lines of values.
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
333
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
334
        This detects the end of the list, because it will be a line that
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
335
        does not start properly indented.
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
336
        """
337
        values = []
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
338
        start = '#' + (' '*indent)
339
0.5.56 by John Arbash Meinel
A couple more fixups, it seems actually capable now of writing out a changeset, and reading it back.
340
        if self._next_line is None or self._next_line[:len(start)] != start:
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
341
            return values
342
343
        for line in self._next():
344
            values.append(line[len(start):-1])
0.5.56 by John Arbash Meinel
A couple more fixups, it seems actually capable now of writing out a changeset, and reading it back.
345
            if self._next_line is None or self._next_line[:len(start)] != start:
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
346
                break
347
        return values
348
349
    def _read_one_patch(self):
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
350
        """Read in one patch, return the complete patch, along with
351
        the next line.
352
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
353
        :return: action, lines, do_continue
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
354
        """
0.5.57 by John Arbash Meinel
Simplified the header, only output base if it is not the expected one.
355
        #mutter('_read_one_patch: %r' % self._next_line)
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
356
        # Peek and see if there are no patches
0.5.56 by John Arbash Meinel
A couple more fixups, it seems actually capable now of writing out a changeset, and reading it back.
357
        if self._next_line is None or self._next_line[:1] == '#':
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
358
            return None, [], False
359
360
        line = self._next().next()
361
        if line[:3] != '***':
362
            raise MalformedPatches('The first line of all patches'
0.5.56 by John Arbash Meinel
A couple more fixups, it seems actually capable now of writing out a changeset, and reading it back.
363
                ' should be a bzr meta line "***"'
364
                ': %r' % line)
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
365
        action = line[4:-1]
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
366
0.5.59 by John Arbash Meinel
Several fixes for handling the case where you are doing a changeset against revno=0 (Null base)
367
        if self._next_line is None or self._next_line[:1] == '#':
368
            return action, [], False
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
369
        lines = []
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
370
        for line in self._next():
371
            lines.append(line)
372
0.5.56 by John Arbash Meinel
A couple more fixups, it seems actually capable now of writing out a changeset, and reading it back.
373
            if self._next_line is not None and self._next_line[:3] == '***':
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
374
                return action, lines, True
0.5.56 by John Arbash Meinel
A couple more fixups, it seems actually capable now of writing out a changeset, and reading it back.
375
            elif self._next_line is None or self._next_line[:1] == '#':
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
376
                return action, lines, False
377
        return action, lines, False
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
378
            
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
379
    def _read_patches(self):
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
380
        do_continue = True
381
        while do_continue:
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
382
            action, lines, do_continue = self._read_one_patch()
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
383
            if action is not None:
384
                self.info.actions.append((action, lines))
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
385
386
    def _read_revision(self, rev_id):
387
        """Revision entries have extra information associated.
388
        """
389
        rev_info = RevisionInfo(rev_id)
390
        start = '#    '
391
        for line in self._next():
392
            key,value = self._read_next_entry(line, indent=4)
393
            #if key is None:
394
            #    continue
395
            if hasattr(rev_info, key):
396
                if getattr(rev_info, key) is None:
397
                    setattr(rev_info, key, value)
398
                else:
399
                    raise MalformedHeader('Duplicated Key: %s' % key)
400
            else:
401
                # What do we do with a key we don't recognize
402
                raise MalformedHeader('Unknown Key: %s' % key)
403
0.5.56 by John Arbash Meinel
A couple more fixups, it seems actually capable now of writing out a changeset, and reading it back.
404
            if self._next_line is None or self._next_line[:len(start)] != start:
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
405
                break
406
407
        self.info.revisions.append(rev_info)
408
409
    def _read_footer(self):
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
410
        """Read the rest of the meta information.
411
0.5.9 by John Arbash Meinel
Now adding the patch information to the ChangesetInfo
412
        :param first_line:  The previous step iterates past what it
413
                            can handle. That extra line is given here.
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
414
        """
0.5.36 by John Arbash Meinel
Updated so that read_changeset is able to parse the output
415
        for line in self._next():
416
            self._handle_next(line)
0.5.56 by John Arbash Meinel
A couple more fixups, it seems actually capable now of writing out a changeset, and reading it back.
417
            if self._next_line is None or self._next_line[:1] != '#':
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
418
                break
419
420
    def _update_tree(self, tree):
421
        """This fills out a ChangesetTree based on the information
422
        that was read in.
423
424
        :param tree: A ChangesetTree to update with the new information.
425
        """
426
        from common import decode
427
428
        def get_text_id(info, file_id):
429
            if info is not None:
430
                if info[:8] != 'text-id:':
431
                    raise BzrError("Text ids should be prefixed with 'text-id:'"
432
                        ': %r' % info)
433
                text_id = decode(info[8:])
434
            elif self.info.text_ids.has_key(file_id):
435
                return self.info.text_ids[file_id]
436
            else:
437
                # If text_id was not explicitly supplied
438
                # then it should be whatever we would guess it to be
439
                # based on the base revision, and what we know about
440
                # the target revision
441
                text_id = common.guess_text_id(tree.base_tree, 
442
                        file_id, self.info.base, True)
443
            if (self.info.text_ids.has_key(file_id)
444
                    and self.info.text_ids[file_id] != text_id):
445
                raise BzrError('Mismatched text_ids for file_id {%s}'
446
                        ': %s != %s' % (file_id,
447
                                        self.info.text_ids[file_id],
448
                                        text_id))
449
            # The Info object makes more sense for where
450
            # to store something like text_id, since it is
451
            # what will be used to generate stored inventory
452
            # entries.
453
            # The problem is that we are parsing the
454
            # ChangesetTree right now, we really modifying
455
            # the ChangesetInfo object
456
            self.info.text_ids[file_id] = text_id
457
            return text_id
458
459
        def renamed(kind, extra, lines):
460
            info = extra.split('\t')
461
            if len(info) < 2:
462
                raise BzrError('renamed action lines need both a from and to'
463
                        ': %r' % extra)
464
            old_path = decode(info[0])
465
            if info[1][:3] == '=> ':
466
                new_path = decode(info[1][3:])
467
            else:
468
                new_path = decode(info[1][3:])
469
470
            file_id = tree.path2id(new_path)
471
            if len(info) > 2:
472
                text_id = get_text_id(info[2], file_id)
473
            else:
474
                text_id = get_text_id(None, file_id)
475
            tree.note_rename(old_path, new_path)
476
            if lines:
477
                tree.note_patch(new_path, lines)
478
479
        def removed(kind, extra, lines):
480
            info = extra.split('\t')
481
            if len(info) > 1:
482
                # TODO: in the future we might allow file ids to be
483
                # given for removed entries
484
                raise BzrError('removed action lines should only have the path'
485
                        ': %r' % extra)
486
            path = decode(info[0])
487
            tree.note_deletion(path)
488
489
        def added(kind, extra, lines):
490
            info = extra.split('\t')
491
            if len(info) <= 1:
492
                raise BzrError('add action lines require the path and file id'
493
                        ': %r' % extra)
494
            elif len(info) > 3:
495
                raise BzrError('add action lines have fewer than 3 entries.'
496
                        ': %r' % extra)
497
            path = decode(info[0])
0.5.59 by John Arbash Meinel
Several fixes for handling the case where you are doing a changeset against revno=0 (Null base)
498
            if info[1][:8] != 'file-id:':
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
499
                raise BzrError('The file-id should follow the path for an add'
500
                        ': %r' % extra)
501
            file_id = decode(info[1][8:])
502
503
            if len(info) > 2:
504
                text_id = get_text_id(info[2], file_id)
505
            else:
506
                text_id = get_text_id(None, file_id)
507
            tree.note_id(file_id, path)
508
            tree.note_patch(path, lines)
509
510
        def modified(kind, extra, lines):
511
            info = extra.split('\t')
512
            if len(info) < 1:
513
                raise BzrError('modified action lines have at least'
514
                        'the path in them: %r' % extra)
515
            path = decode(info[0])
516
517
            file_id = tree.path2id(path)
518
            if len(info) > 1:
519
                text_id = get_text_id(info[1], file_id)
520
            else:
521
                text_id = get_text_id(None, file_id)
522
            tree.note_patch(path, lines)
523
            
524
525
        valid_actions = {
526
            'renamed':renamed,
527
            'removed':removed,
528
            'added':added,
529
            'modified':modified
530
        }
531
        for action_line, lines in self.info.actions:
532
            first = action_line.find(' ')
533
            if first == -1:
534
                raise BzrError('Bogus action line'
535
                        ' (no opening space): %r' % action_line)
0.5.56 by John Arbash Meinel
A couple more fixups, it seems actually capable now of writing out a changeset, and reading it back.
536
            second = action_line.find(' ', first+1)
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
537
            if second == -1:
538
                raise BzrError('Bogus action line'
539
                        ' (missing second space): %r' % action_line)
540
            action = action_line[:first]
541
            kind = action_line[first+1:second]
542
            if kind not in ('file', 'directory'):
543
                raise BzrError('Bogus action line'
0.5.56 by John Arbash Meinel
A couple more fixups, it seems actually capable now of writing out a changeset, and reading it back.
544
                        ' (invalid object kind %r): %r' % (kind, action_line))
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
545
            extra = action_line[second+1:]
546
547
            if action not in valid_actions:
548
                raise BzrError('Bogus action line'
549
                        ' (unrecognized action): %r' % action_line)
550
            valid_actions[action](kind, extra, lines)
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
551
0.5.56 by John Arbash Meinel
A couple more fixups, it seems actually capable now of writing out a changeset, and reading it back.
552
def read_changeset(from_file, branch):
553
    """Read in a changeset from a iterable object (such as a file object)
554
555
    :param from_file: A file-like object to read the changeset information.
556
    :param branch: This will be used to build the changeset tree, it needs
557
                   to contain the base of the changeset. (Which you probably
558
                   won't know about until after the changeset is parsed.)
0.5.7 by John Arbash Meinel
Added a bunch more information about changesets. Can now read back in all of the meta information.
559
    """
560
    cr = ChangesetReader(from_file)
0.5.56 by John Arbash Meinel
A couple more fixups, it seems actually capable now of writing out a changeset, and reading it back.
561
    return cr.get_info_and_tree(branch)
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
562
563
class ChangesetTree:
0.5.48 by aaron.bentley at utoronto
Implemented deletion for ChangesetTrees
564
    def __init__(self, base_tree=None):
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
565
        self.base_tree = base_tree
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
566
        self._renamed = {} # Mapping from old_path => new_path
567
        self._renamed_r = {} # new_path => old_path
568
        self._new_id = {} # new_path => new_id
569
        self._new_id_r = {} # new_id => new_path
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
570
        self.patches = {}
0.5.48 by aaron.bentley at utoronto
Implemented deletion for ChangesetTrees
571
        self.deleted = []
0.5.52 by aaron.bentley at utoronto
Make contents-addressing configurable
572
        self.contents_by_id = True
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
573
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
574
    def __str__(self):
575
        return pprint.pformat(self.__dict__)
576
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
577
    def note_rename(self, old_path, new_path):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
578
        """A file/directory has been renamed from old_path => new_path"""
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
579
        assert not self._renamed.has_key(old_path)
580
        assert not self._renamed_r.has_key(new_path)
581
        self._renamed[new_path] = old_path
582
        self._renamed_r[old_path] = new_path
583
0.5.43 by aaron.bentley at utoronto
Handled moves and adds properly
584
    def note_id(self, new_id, new_path):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
585
        """Files that don't exist in base need a new id."""
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
586
        self._new_id[new_path] = new_id
587
        self._new_id_r[new_id] = new_path
588
0.5.44 by aaron.bentley at utoronto
Got get_file working for new files
589
    def note_patch(self, new_path, patch):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
590
        """There is a patch for a given filename."""
0.5.44 by aaron.bentley at utoronto
Got get_file working for new files
591
        self.patches[new_path] = patch
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
592
0.5.48 by aaron.bentley at utoronto
Implemented deletion for ChangesetTrees
593
    def note_deletion(self, old_path):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
594
        """The file at old_path has been deleted."""
0.5.48 by aaron.bentley at utoronto
Implemented deletion for ChangesetTrees
595
        self.deleted.append(old_path)
596
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
597
    def old_path(self, new_path):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
598
        """Get the old_path (path in the base_tree) for the file at new_path"""
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
599
        import os.path
600
        old_path = self._renamed.get(new_path)
601
        if old_path is not None:
602
            return old_path
603
        dirname,basename = os.path.split(new_path)
0.5.56 by John Arbash Meinel
A couple more fixups, it seems actually capable now of writing out a changeset, and reading it back.
604
        # dirname is not '' doesn't work, because
605
        # dirname may be a unicode entry, and is
606
        # requires the objects to be identical
607
        if dirname != '':
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
608
            old_dir = self.old_path(dirname)
609
            if old_dir is None:
0.5.42 by aaron.bentley at utoronto
Improved rename handling
610
                old_path = None
611
            else:
612
                old_path = os.path.join(old_dir, basename)
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
613
        else:
0.5.42 by aaron.bentley at utoronto
Improved rename handling
614
            old_path = new_path
615
        #If the new path wasn't in renamed, the old one shouldn't be in
616
        #renamed_r
617
        if self._renamed_r.has_key(old_path):
618
            return None
619
        return old_path 
620
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
621
622
    def new_path(self, old_path):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
623
        """Get the new_path (path in the target_tree) for the file at old_path
624
        in the base tree.
625
        """
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
626
        import os.path
627
        new_path = self._renamed_r.get(old_path)
628
        if new_path is not None:
629
            return new_path
630
        if self._renamed.has_key(new_path):
631
            return None
632
        dirname,basename = os.path.split(old_path)
633
        if dirname is not '':
634
            new_dir = self.new_path(dirname)
635
            if new_dir is None:
0.5.42 by aaron.bentley at utoronto
Improved rename handling
636
                new_path = None
637
            else:
638
                new_path = os.path.join(new_dir, basename)
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
639
        else:
0.5.42 by aaron.bentley at utoronto
Improved rename handling
640
            new_path = old_path
641
        #If the old path wasn't in renamed, the new one shouldn't be in
642
        #renamed_r
643
        if self._renamed.has_key(new_path):
644
            return None
645
        return new_path 
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
646
647
    def path2id(self, path):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
648
        """Return the id of the file present at path in the target tree."""
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
649
        file_id = self._new_id.get(path)
650
        if file_id is not None:
651
            return file_id
0.5.43 by aaron.bentley at utoronto
Handled moves and adds properly
652
        old_path = self.old_path(path)
653
        if old_path is None:
654
            return None
0.5.48 by aaron.bentley at utoronto
Implemented deletion for ChangesetTrees
655
        if old_path in self.deleted:
656
            return None
0.5.56 by John Arbash Meinel
A couple more fixups, it seems actually capable now of writing out a changeset, and reading it back.
657
        return self.base_tree.inventory.path2id(old_path)
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
658
659
    def id2path(self, file_id):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
660
        """Return the new path in the target tree of the file with id file_id"""
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
661
        path = self._new_id_r.get(file_id)
662
        if path is not None:
663
            return path
0.5.43 by aaron.bentley at utoronto
Handled moves and adds properly
664
        old_path = self.base_tree.id2path(file_id)
665
        if old_path is None:
666
            return None
0.5.48 by aaron.bentley at utoronto
Implemented deletion for ChangesetTrees
667
        if old_path in self.deleted:
668
            return None
0.5.43 by aaron.bentley at utoronto
Handled moves and adds properly
669
        return self.new_path(old_path)
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
670
0.5.52 by aaron.bentley at utoronto
Make contents-addressing configurable
671
    def old_contents_id(self, file_id):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
672
        """Return the id in the base_tree for the given file_id,
673
        or None if the file did not exist in base.
674
675
        FIXME:  Something doesn't seem right here. It seems like this function
676
                should always either return None or file_id. Even if
677
                you are doing the by-path lookup, you are doing a
678
                id2path lookup, just to do the reverse path2id lookup.
679
        """
0.5.52 by aaron.bentley at utoronto
Make contents-addressing configurable
680
        if self.contents_by_id:
681
            if self.base_tree.has_id(file_id):
682
                return file_id
683
            else:
684
                return None
685
        new_path = self.id2path(file_id)
686
        return self.base_tree.path2id(new_path)
687
        
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
688
    def get_file(self, file_id):
0.5.55 by John Arbash Meinel
Lots of updates. Using a minimized annotations for changesets.
689
        """Return a file-like object containing the new contents of the
690
        file given by file_id.
691
692
        TODO:   It might be nice if this actually generated an entry
693
                in the text-store, so that the file contents would
694
                then be cached.
695
        """
0.5.52 by aaron.bentley at utoronto
Make contents-addressing configurable
696
        base_id = self.old_contents_id(file_id)
0.5.50 by aaron.bentley at utoronto
Evaluate patches against file paths, not file ids
697
        if base_id is not None:
698
            patch_original = self.base_tree.get_file(base_id)
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
699
        else:
700
            patch_original = None
0.5.52 by aaron.bentley at utoronto
Make contents-addressing configurable
701
        file_patch = self.patches.get(self.id2path(file_id))
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
702
        if file_patch is None:
0.5.44 by aaron.bentley at utoronto
Got get_file working for new files
703
            return patch_original
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
704
        return patched_file(file_patch, patch_original)
705
0.5.49 by aaron.bentley at utoronto
Implemented iteration over ids
706
    def __iter__(self):
707
        for file_id in self._new_id_r.iterkeys():
708
            yield file_id
709
        for file_id in self.base_tree:
710
            if self.id2path(file_id) is None:
711
                continue
712
            yield file_id
713
714
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
715
def patched_file(file_patch, original):
716
    from bzrlib.patch import patch
717
    from tempfile import mkdtemp
718
    from shutil import rmtree
719
    from StringIO import StringIO
0.5.44 by aaron.bentley at utoronto
Got get_file working for new files
720
    from bzrlib.osutils import pumpfile
721
    import os.path
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
722
    temp_dir = mkdtemp()
723
    try:
0.5.44 by aaron.bentley at utoronto
Got get_file working for new files
724
        original_path = os.path.join(temp_dir, "originalfile")
725
        temp_original = file(original_path, "wb")
726
        if original is not None:
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
727
            pumpfile(original, temp_original)
0.5.44 by aaron.bentley at utoronto
Got get_file working for new files
728
        temp_original.close()
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
729
        patched_path = os.path.join(temp_dir, "patchfile")
0.5.47 by aaron.bentley at utoronto
Added safety check to patch call
730
        assert patch(file_patch, original_path, patched_path) == 0
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
731
        result = StringIO()
0.5.44 by aaron.bentley at utoronto
Got get_file working for new files
732
        temp_patched = file(patched_path, "rb")
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
733
        pumpfile(temp_patched, result)
734
        temp_patched.close()
735
        result.seek(0,0)
736
737
    finally:
738
        rmtree(temp_dir)
739
740
    return result
741
742
def test():
743
    import unittest
744
    from StringIO import StringIO
0.5.44 by aaron.bentley at utoronto
Got get_file working for new files
745
    from bzrlib.diff import internal_diff
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
746
    class MockTree(object):
747
        def __init__(self):
748
            object.__init__(self)
749
            self.paths = {}
750
            self.ids = {}
751
            self.contents = {}
752
0.5.49 by aaron.bentley at utoronto
Implemented iteration over ids
753
        def __iter__(self):
754
            return self.paths.iterkeys()
755
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
756
        def add_dir(self, file_id, path):
757
            self.paths[file_id] = path
758
            self.ids[path] = file_id
759
        
760
        def add_file(self, file_id, path, contents):
761
            self.add_dir(file_id, path)
762
            self.contents[file_id] = contents
763
764
        def path2id(self, path):
765
            return self.ids.get(path)
766
767
        def id2path(self, file_id):
768
            return self.paths.get(file_id)
769
0.5.44 by aaron.bentley at utoronto
Got get_file working for new files
770
        def has_id(self, file_id):
771
            return self.id2path(file_id) is not None
772
0.5.46 by aaron.bentley at utoronto
Got file gets working
773
        def get_file(self, file_id):
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
774
            result = StringIO()
775
            result.write(self.contents[file_id])
776
            result.seek(0,0)
777
            return result
778
779
    class CTreeTester(unittest.TestCase):
0.5.43 by aaron.bentley at utoronto
Handled moves and adds properly
780
781
        def make_tree_1(self):
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
782
            mtree = MockTree()
783
            mtree.add_dir("a", "grandparent")
784
            mtree.add_dir("b", "grandparent/parent")
0.5.50 by aaron.bentley at utoronto
Evaluate patches against file paths, not file ids
785
            mtree.add_file("c", "grandparent/parent/file", "Hello\n")
0.5.43 by aaron.bentley at utoronto
Handled moves and adds properly
786
            mtree.add_dir("d", "grandparent/alt_parent")
787
            return ChangesetTree(mtree), mtree
788
            
0.5.45 by aaron.bentley at utoronto
fixed method names
789
        def test_renames(self):
0.5.43 by aaron.bentley at utoronto
Handled moves and adds properly
790
            """Ensure that file renames have the proper effect on children"""
791
            ctree = self.make_tree_1()[0]
0.5.54 by John Arbash Meinel
Changed all test code into using self.assertEquals instead of assert
792
            self.assertEqual(ctree.old_path("grandparent"), "grandparent")
793
            self.assertEqual(ctree.old_path("grandparent/parent"), "grandparent/parent")
794
            self.assertEqual(ctree.old_path("grandparent/parent/file"),
795
                "grandparent/parent/file")
796
797
            self.assertEqual(ctree.id2path("a"), "grandparent")
798
            self.assertEqual(ctree.id2path("b"), "grandparent/parent")
799
            self.assertEqual(ctree.id2path("c"), "grandparent/parent/file")
800
801
            self.assertEqual(ctree.path2id("grandparent"), "a")
802
            self.assertEqual(ctree.path2id("grandparent/parent"), "b")
803
            self.assertEqual(ctree.path2id("grandparent/parent/file"), "c")
804
805
            self.assertEqual(ctree.path2id("grandparent2"), None)
806
            self.assertEqual(ctree.path2id("grandparent2/parent"), None)
807
            self.assertEqual(ctree.path2id("grandparent2/parent/file"), None)
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
808
809
            ctree.note_rename("grandparent", "grandparent2")
0.5.54 by John Arbash Meinel
Changed all test code into using self.assertEquals instead of assert
810
            self.assertEqual(ctree.old_path("grandparent"), None)
811
            self.assertEqual(ctree.old_path("grandparent/parent"), None)
812
            self.assertEqual(ctree.old_path("grandparent/parent/file"), None)
813
814
            self.assertEqual(ctree.id2path("a"), "grandparent2")
815
            self.assertEqual(ctree.id2path("b"), "grandparent2/parent")
816
            self.assertEqual(ctree.id2path("c"), "grandparent2/parent/file")
817
818
            self.assertEqual(ctree.path2id("grandparent2"), "a")
819
            self.assertEqual(ctree.path2id("grandparent2/parent"), "b")
820
            self.assertEqual(ctree.path2id("grandparent2/parent/file"), "c")
821
822
            self.assertEqual(ctree.path2id("grandparent"), None)
823
            self.assertEqual(ctree.path2id("grandparent/parent"), None)
824
            self.assertEqual(ctree.path2id("grandparent/parent/file"), None)
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
825
826
            ctree.note_rename("grandparent/parent", "grandparent2/parent2")
0.5.54 by John Arbash Meinel
Changed all test code into using self.assertEquals instead of assert
827
            self.assertEqual(ctree.id2path("a"), "grandparent2")
828
            self.assertEqual(ctree.id2path("b"), "grandparent2/parent2")
829
            self.assertEqual(ctree.id2path("c"), "grandparent2/parent2/file")
830
831
            self.assertEqual(ctree.path2id("grandparent2"), "a")
832
            self.assertEqual(ctree.path2id("grandparent2/parent2"), "b")
833
            self.assertEqual(ctree.path2id("grandparent2/parent2/file"), "c")
834
835
            self.assertEqual(ctree.path2id("grandparent2/parent"), None)
836
            self.assertEqual(ctree.path2id("grandparent2/parent/file"), None)
0.5.43 by aaron.bentley at utoronto
Handled moves and adds properly
837
838
            ctree.note_rename("grandparent/parent/file", 
839
                              "grandparent2/parent2/file2")
0.5.54 by John Arbash Meinel
Changed all test code into using self.assertEquals instead of assert
840
            self.assertEqual(ctree.id2path("a"), "grandparent2")
841
            self.assertEqual(ctree.id2path("b"), "grandparent2/parent2")
842
            self.assertEqual(ctree.id2path("c"), "grandparent2/parent2/file2")
843
844
            self.assertEqual(ctree.path2id("grandparent2"), "a")
845
            self.assertEqual(ctree.path2id("grandparent2/parent2"), "b")
846
            self.assertEqual(ctree.path2id("grandparent2/parent2/file2"), "c")
847
848
            self.assertEqual(ctree.path2id("grandparent2/parent2/file"), None)
0.5.43 by aaron.bentley at utoronto
Handled moves and adds properly
849
0.5.45 by aaron.bentley at utoronto
fixed method names
850
        def test_moves(self):
0.5.43 by aaron.bentley at utoronto
Handled moves and adds properly
851
            """Ensure that file moves have the proper effect on children"""
852
            ctree = self.make_tree_1()[0]
853
            ctree.note_rename("grandparent/parent/file", 
854
                              "grandparent/alt_parent/file")
0.5.54 by John Arbash Meinel
Changed all test code into using self.assertEquals instead of assert
855
            self.assertEqual(ctree.id2path("c"), "grandparent/alt_parent/file")
856
            self.assertEqual(ctree.path2id("grandparent/alt_parent/file"), "c")
857
            self.assertEqual(ctree.path2id("grandparent/parent/file"), None)
0.5.43 by aaron.bentley at utoronto
Handled moves and adds properly
858
0.5.44 by aaron.bentley at utoronto
Got get_file working for new files
859
        def unified_diff(self, old, new):
860
            out = StringIO()
861
            internal_diff("old", old, "new", new, out)
862
            out.seek(0,0)
863
            return out.read()
864
0.5.52 by aaron.bentley at utoronto
Make contents-addressing configurable
865
        def make_tree_2(self):
0.5.43 by aaron.bentley at utoronto
Handled moves and adds properly
866
            ctree = self.make_tree_1()[0]
867
            ctree.note_rename("grandparent/parent/file", 
868
                              "grandparent/alt_parent/file")
0.5.54 by John Arbash Meinel
Changed all test code into using self.assertEquals instead of assert
869
            self.assertEqual(ctree.id2path("e"), None)
870
            self.assertEqual(ctree.path2id("grandparent/parent/file"), None)
0.5.43 by aaron.bentley at utoronto
Handled moves and adds properly
871
            ctree.note_id("e", "grandparent/parent/file")
0.5.52 by aaron.bentley at utoronto
Make contents-addressing configurable
872
            return ctree
873
874
        def test_adds(self):
875
            """File/inventory adds"""
876
            ctree = self.make_tree_2()
877
            add_patch = self.unified_diff([], ["Extra cheese\n"])
0.5.44 by aaron.bentley at utoronto
Got get_file working for new files
878
            ctree.note_patch("grandparent/parent/file", add_patch)
0.5.52 by aaron.bentley at utoronto
Make contents-addressing configurable
879
            self.adds_test(ctree)
0.5.44 by aaron.bentley at utoronto
Got get_file working for new files
880
0.5.52 by aaron.bentley at utoronto
Make contents-addressing configurable
881
        def adds_test(self, ctree):
0.5.54 by John Arbash Meinel
Changed all test code into using self.assertEquals instead of assert
882
            self.assertEqual(ctree.id2path("e"), "grandparent/parent/file")
883
            self.assertEqual(ctree.path2id("grandparent/parent/file"), "e")
884
            self.assertEqual(ctree.get_file("e").read(), "Extra cheese\n")
0.5.45 by aaron.bentley at utoronto
fixed method names
885
0.5.52 by aaron.bentley at utoronto
Make contents-addressing configurable
886
        def test_adds2(self):
887
            """File/inventory adds, with patch-compatibile renames"""
888
            ctree = self.make_tree_2()
889
            ctree.contents_by_id = False
890
            add_patch = self.unified_diff(["Hello\n"], ["Extra cheese\n"])
891
            ctree.note_patch("grandparent/parent/file", add_patch)
892
            self.adds_test(ctree)
893
894
        def make_tree_3(self):
0.5.46 by aaron.bentley at utoronto
Got file gets working
895
            ctree, mtree = self.make_tree_1()
896
            mtree.add_file("e", "grandparent/parent/topping", "Anchovies\n")
897
            ctree.note_rename("grandparent/parent/file", 
898
                              "grandparent/alt_parent/file")
899
            ctree.note_rename("grandparent/parent/topping", 
900
                              "grandparent/alt_parent/stopping")
0.5.52 by aaron.bentley at utoronto
Make contents-addressing configurable
901
            return ctree
902
903
        def get_file_test(self, ctree):
0.5.54 by John Arbash Meinel
Changed all test code into using self.assertEquals instead of assert
904
            self.assertEqual(ctree.get_file("e").read(), "Lemon\n")
905
            self.assertEqual(ctree.get_file("c").read(), "Hello\n")
0.5.52 by aaron.bentley at utoronto
Make contents-addressing configurable
906
907
        def test_get_file(self):
908
            """Get file contents"""
909
            ctree = self.make_tree_3()
910
            mod_patch = self.unified_diff(["Anchovies\n"], ["Lemon\n"])
911
            ctree.note_patch("grandparent/alt_parent/stopping", mod_patch)
912
            self.get_file_test(ctree)
913
914
        def test_get_file2(self):
915
            """Get file contents, with patch-compatibile renames"""
916
            ctree = self.make_tree_3()
917
            ctree.contents_by_id = False
0.5.50 by aaron.bentley at utoronto
Evaluate patches against file paths, not file ids
918
            mod_patch = self.unified_diff([], ["Lemon\n"])
0.5.46 by aaron.bentley at utoronto
Got file gets working
919
            ctree.note_patch("grandparent/alt_parent/stopping", mod_patch)
0.5.50 by aaron.bentley at utoronto
Evaluate patches against file paths, not file ids
920
            mod_patch = self.unified_diff([], ["Hello\n"])
921
            ctree.note_patch("grandparent/alt_parent/file", mod_patch)
0.5.52 by aaron.bentley at utoronto
Make contents-addressing configurable
922
            self.get_file_test(ctree)
0.5.46 by aaron.bentley at utoronto
Got file gets working
923
0.5.48 by aaron.bentley at utoronto
Implemented deletion for ChangesetTrees
924
        def test_delete(self):
925
            "Deletion by changeset"
926
            ctree = self.make_tree_1()[0]
0.5.54 by John Arbash Meinel
Changed all test code into using self.assertEquals instead of assert
927
            self.assertEqual(ctree.get_file("c").read(), "Hello\n")
0.5.48 by aaron.bentley at utoronto
Implemented deletion for ChangesetTrees
928
            ctree.note_deletion("grandparent/parent/file")
0.5.54 by John Arbash Meinel
Changed all test code into using self.assertEquals instead of assert
929
            self.assertEqual(ctree.id2path("c"), None)
930
            self.assertEqual(ctree.path2id("grandparent/parent/file"), None)
0.5.48 by aaron.bentley at utoronto
Implemented deletion for ChangesetTrees
931
0.5.49 by aaron.bentley at utoronto
Implemented iteration over ids
932
        def sorted_ids(self, tree):
933
            ids = list(tree)
934
            ids.sort()
935
            return ids
936
937
        def test_iteration(self):
938
            """Ensure that iteration through ids works properly"""
939
            ctree = self.make_tree_1()[0]
0.5.54 by John Arbash Meinel
Changed all test code into using self.assertEquals instead of assert
940
            self.assertEqual(self.sorted_ids(ctree), ['a', 'b', 'c', 'd'])
0.5.49 by aaron.bentley at utoronto
Implemented iteration over ids
941
            ctree.note_deletion("grandparent/parent/file")
942
            ctree.note_id("e", "grandparent/alt_parent/fool")
0.5.54 by John Arbash Meinel
Changed all test code into using self.assertEquals instead of assert
943
            self.assertEqual(self.sorted_ids(ctree), ['a', 'b', 'd', 'e'])
0.5.49 by aaron.bentley at utoronto
Implemented iteration over ids
944
            
945
0.5.45 by aaron.bentley at utoronto
fixed method names
946
    patchesTestSuite = unittest.makeSuite(CTreeTester,'test_')
0.5.41 by aaron.bentley at utoronto
Added non-working ChangesetTree
947
    runner = unittest.TextTestRunner()
948
    runner.run(patchesTestSuite)
949