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