/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/shelf.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2009-07-20 08:56:45 UTC
  • mfrom: (4526.9.23 apply-inventory-delta)
  • Revision ID: pqm@pqm.ubuntu.com-20090720085645-54mtgybxua0yx6hw
(robertc) Add checks for inventory deltas which try to ensure that
        deltas that are not an exact fit are not applied. (Robert
        Collins, bug 397705, bug 367633)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2008 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
 
 
18
import errno
 
19
import re
 
20
 
 
21
from bzrlib import (
 
22
    bencode,
 
23
    errors,
 
24
    merge,
 
25
    merge3,
 
26
    osutils,
 
27
    pack,
 
28
    transform,
 
29
    ui,
 
30
    workingtree,
 
31
)
 
32
 
 
33
 
 
34
class ShelfCreator(object):
 
35
    """Create a transform to shelve objects and its inverse."""
 
36
 
 
37
    def __init__(self, work_tree, target_tree, file_list=None):
 
38
        """Constructor.
 
39
 
 
40
        :param work_tree: The working tree to apply changes to
 
41
        :param target_tree: The tree to make the working tree more similar to.
 
42
        :param file_list: The files to make more similar to the target.
 
43
        """
 
44
        self.work_tree = work_tree
 
45
        self.work_transform = transform.TreeTransform(work_tree)
 
46
        try:
 
47
            self.target_tree = target_tree
 
48
            self.shelf_transform = transform.TransformPreview(self.target_tree)
 
49
            try:
 
50
                self.renames = {}
 
51
                self.creation = {}
 
52
                self.deletion = {}
 
53
                self.iter_changes = work_tree.iter_changes(
 
54
                    self.target_tree, specific_files=file_list)
 
55
            except:
 
56
                self.shelf_transform.finalize()
 
57
                raise
 
58
        except:
 
59
            self.work_transform.finalize()
 
60
            raise
 
61
 
 
62
    def iter_shelvable(self):
 
63
        """Iterable of tuples describing shelvable changes.
 
64
 
 
65
        As well as generating the tuples, this updates several members.
 
66
        Tuples may be:
 
67
           ('add file', file_id, work_kind, work_path)
 
68
           ('delete file', file_id, target_kind, target_path)
 
69
           ('rename', file_id, target_path, work_path)
 
70
           ('change kind', file_id, target_kind, work_kind, target_path)
 
71
           ('modify text', file_id)
 
72
           ('modify target', file_id, target_target, work_target)
 
73
        """
 
74
        for (file_id, paths, changed, versioned, parents, names, kind,
 
75
             executable) in self.iter_changes:
 
76
            if kind[0] is None or versioned[0] == False:
 
77
                self.creation[file_id] = (kind[1], names[1], parents[1],
 
78
                                          versioned)
 
79
                yield ('add file', file_id, kind[1], paths[1])
 
80
            elif kind[1] is None or versioned[0] == False:
 
81
                self.deletion[file_id] = (kind[0], names[0], parents[0],
 
82
                                          versioned)
 
83
                yield ('delete file', file_id, kind[0], paths[0])
 
84
            else:
 
85
                if names[0] != names[1] or parents[0] != parents[1]:
 
86
                    self.renames[file_id] = (names, parents)
 
87
                    yield ('rename', file_id) + paths
 
88
 
 
89
                if kind[0] != kind [1]:
 
90
                    yield ('change kind', file_id, kind[0], kind[1], paths[0])
 
91
                elif kind[0] == 'symlink':
 
92
                    t_target = self.target_tree.get_symlink_target(file_id)
 
93
                    w_target = self.work_tree.get_symlink_target(file_id)
 
94
                    yield ('modify target', file_id, paths[0], t_target,
 
95
                            w_target)
 
96
                elif changed:
 
97
                    yield ('modify text', file_id)
 
98
 
 
99
    def shelve_change(self, change):
 
100
        """Shelve a change in the iter_shelvable format."""
 
101
        if change[0] == 'rename':
 
102
            self.shelve_rename(change[1])
 
103
        elif change[0] == 'delete file':
 
104
            self.shelve_deletion(change[1])
 
105
        elif change[0] == 'add file':
 
106
            self.shelve_creation(change[1])
 
107
        elif change[0] == 'change kind':
 
108
            self.shelve_content_change(change[1])
 
109
        elif change[0] == 'modify target':
 
110
            self.shelve_modify_target(change[1])
 
111
        else:
 
112
            raise ValueError('Unknown change kind: "%s"' % change[0])
 
113
 
 
114
    def shelve_rename(self, file_id):
 
115
        """Shelve a file rename.
 
116
 
 
117
        :param file_id: The file id of the file to shelve the renaming of.
 
118
        """
 
119
        names, parents = self.renames[file_id]
 
120
        w_trans_id = self.work_transform.trans_id_file_id(file_id)
 
121
        work_parent = self.work_transform.trans_id_file_id(parents[0])
 
122
        self.work_transform.adjust_path(names[0], work_parent, w_trans_id)
 
123
 
 
124
        s_trans_id = self.shelf_transform.trans_id_file_id(file_id)
 
125
        shelf_parent = self.shelf_transform.trans_id_file_id(parents[1])
 
126
        self.shelf_transform.adjust_path(names[1], shelf_parent, s_trans_id)
 
127
 
 
128
    def shelve_modify_target(self, file_id):
 
129
        """Shelve a change of symlink target.
 
130
 
 
131
        :param file_id: The file id of the symlink which changed target.
 
132
        :param new_target: The target that the symlink should have due
 
133
            to shelving.
 
134
        """
 
135
        new_target = self.target_tree.get_symlink_target(file_id)
 
136
        w_trans_id = self.work_transform.trans_id_file_id(file_id)
 
137
        self.work_transform.delete_contents(w_trans_id)
 
138
        self.work_transform.create_symlink(new_target, w_trans_id)
 
139
 
 
140
        old_target = self.work_tree.get_symlink_target(file_id)
 
141
        s_trans_id = self.shelf_transform.trans_id_file_id(file_id)
 
142
        self.shelf_transform.delete_contents(s_trans_id)
 
143
        self.shelf_transform.create_symlink(old_target, s_trans_id)
 
144
 
 
145
    def shelve_lines(self, file_id, new_lines):
 
146
        """Shelve text changes to a file, using provided lines.
 
147
 
 
148
        :param file_id: The file id of the file to shelve the text of.
 
149
        :param new_lines: The lines that the file should have due to shelving.
 
150
        """
 
151
        w_trans_id = self.work_transform.trans_id_file_id(file_id)
 
152
        self.work_transform.delete_contents(w_trans_id)
 
153
        self.work_transform.create_file(new_lines, w_trans_id)
 
154
 
 
155
        s_trans_id = self.shelf_transform.trans_id_file_id(file_id)
 
156
        self.shelf_transform.delete_contents(s_trans_id)
 
157
        inverse_lines = self._inverse_lines(new_lines, file_id)
 
158
        self.shelf_transform.create_file(inverse_lines, s_trans_id)
 
159
 
 
160
    @staticmethod
 
161
    def _content_from_tree(tt, tree, file_id):
 
162
        trans_id = tt.trans_id_file_id(file_id)
 
163
        tt.delete_contents(trans_id)
 
164
        transform.create_from_tree(tt, trans_id, tree, file_id)
 
165
 
 
166
    def shelve_content_change(self, file_id):
 
167
        """Shelve a kind change or binary file content change.
 
168
 
 
169
        :param file_id: The file id of the file to shelve the content change
 
170
            of.
 
171
        """
 
172
        self._content_from_tree(self.work_transform, self.target_tree, file_id)
 
173
        self._content_from_tree(self.shelf_transform, self.work_tree, file_id)
 
174
 
 
175
    def shelve_creation(self, file_id):
 
176
        """Shelve creation of a file.
 
177
 
 
178
        This handles content and inventory id.
 
179
        :param file_id: The file_id of the file to shelve creation of.
 
180
        """
 
181
        kind, name, parent, versioned = self.creation[file_id]
 
182
        version = not versioned[0]
 
183
        self._shelve_creation(self.work_tree, file_id, self.work_transform,
 
184
                              self.shelf_transform, kind, name, parent,
 
185
                              version)
 
186
 
 
187
    def shelve_deletion(self, file_id):
 
188
        """Shelve deletion of a file.
 
189
 
 
190
        This handles content and inventory id.
 
191
        :param file_id: The file_id of the file to shelve deletion of.
 
192
        """
 
193
        kind, name, parent, versioned = self.deletion[file_id]
 
194
        existing_path = self.target_tree.id2path(file_id)
 
195
        if not self.work_tree.has_filename(existing_path):
 
196
            existing_path = None
 
197
        version = not versioned[1]
 
198
        self._shelve_creation(self.target_tree, file_id, self.shelf_transform,
 
199
                              self.work_transform, kind, name, parent,
 
200
                              version, existing_path=existing_path)
 
201
 
 
202
    def _shelve_creation(self, tree, file_id, from_transform, to_transform,
 
203
                         kind, name, parent, version, existing_path=None):
 
204
        w_trans_id = from_transform.trans_id_file_id(file_id)
 
205
        if parent is not None and kind is not None:
 
206
            from_transform.delete_contents(w_trans_id)
 
207
        from_transform.unversion_file(w_trans_id)
 
208
 
 
209
        if existing_path is not None:
 
210
            s_trans_id = to_transform.trans_id_tree_path(existing_path)
 
211
        else:
 
212
            s_trans_id = to_transform.trans_id_file_id(file_id)
 
213
        if parent is not None:
 
214
            s_parent_id = to_transform.trans_id_file_id(parent)
 
215
            to_transform.adjust_path(name, s_parent_id, s_trans_id)
 
216
            if existing_path is None:
 
217
                if kind is None:
 
218
                    to_transform.create_file('', s_trans_id)
 
219
                else:
 
220
                    transform.create_from_tree(to_transform, s_trans_id,
 
221
                                               tree, file_id)
 
222
        if version:
 
223
            to_transform.version_file(file_id, s_trans_id)
 
224
 
 
225
    def _inverse_lines(self, new_lines, file_id):
 
226
        """Produce a version with only those changes removed from new_lines."""
 
227
        target_lines = self.target_tree.get_file_lines(file_id)
 
228
        work_lines = self.work_tree.get_file_lines(file_id)
 
229
        return merge3.Merge3(new_lines, target_lines, work_lines).merge_lines()
 
230
 
 
231
    def finalize(self):
 
232
        """Release all resources used by this ShelfCreator."""
 
233
        self.work_transform.finalize()
 
234
        self.shelf_transform.finalize()
 
235
 
 
236
    def transform(self):
 
237
        """Shelve changes from working tree."""
 
238
        self.work_transform.apply()
 
239
 
 
240
    def write_shelf(self, shelf_file, message=None):
 
241
        """Serialize the shelved changes to a file.
 
242
 
 
243
        :param shelf_file: A file-like object to write the shelf to.
 
244
        :param message: An optional message describing the shelved changes.
 
245
        :return: the filename of the written file.
 
246
        """
 
247
        transform.resolve_conflicts(self.shelf_transform)
 
248
        serializer = pack.ContainerSerialiser()
 
249
        shelf_file.write(serializer.begin())
 
250
        metadata = {
 
251
            'revision_id': self.target_tree.get_revision_id(),
 
252
        }
 
253
        if message is not None:
 
254
            metadata['message'] = message.encode('utf-8')
 
255
        shelf_file.write(serializer.bytes_record(
 
256
            bencode.bencode(metadata), (('metadata',),)))
 
257
        for bytes in self.shelf_transform.serialize(serializer):
 
258
            shelf_file.write(bytes)
 
259
        shelf_file.write(serializer.end())
 
260
 
 
261
 
 
262
class Unshelver(object):
 
263
    """Unshelve shelved changes."""
 
264
 
 
265
    def __init__(self, tree, base_tree, transform, message):
 
266
        """Constructor.
 
267
 
 
268
        :param tree: The tree to apply the changes to.
 
269
        :param base_tree: The basis to apply the tranform to.
 
270
        :param message: A message from the shelved transform.
 
271
        """
 
272
        self.tree = tree
 
273
        self.base_tree = base_tree
 
274
        self.transform = transform
 
275
        self.message = message
 
276
 
 
277
    @staticmethod
 
278
    def iter_records(shelf_file):
 
279
        parser = pack.ContainerPushParser()
 
280
        parser.accept_bytes(shelf_file.read())
 
281
        return iter(parser.read_pending_records())
 
282
 
 
283
    @staticmethod
 
284
    def parse_metadata(records):
 
285
        names, metadata_bytes = records.next()
 
286
        if names[0] != ('metadata',):
 
287
            raise errors.ShelfCorrupt
 
288
        metadata = bencode.bdecode(metadata_bytes)
 
289
        message = metadata.get('message')
 
290
        if message is not None:
 
291
            metadata['message'] = message.decode('utf-8')
 
292
        return metadata
 
293
 
 
294
    @classmethod
 
295
    def from_tree_and_shelf(klass, tree, shelf_file):
 
296
        """Create an Unshelver from a tree and a shelf file.
 
297
 
 
298
        :param tree: The tree to apply shelved changes to.
 
299
        :param shelf_file: A file-like object containing shelved changes.
 
300
        :return: The Unshelver.
 
301
        """
 
302
        records = klass.iter_records(shelf_file)
 
303
        metadata = klass.parse_metadata(records)
 
304
        base_revision_id = metadata['revision_id']
 
305
        try:
 
306
            base_tree = tree.revision_tree(base_revision_id)
 
307
        except errors.NoSuchRevisionInTree:
 
308
            base_tree = tree.branch.repository.revision_tree(base_revision_id)
 
309
        tt = transform.TransformPreview(base_tree)
 
310
        tt.deserialize(records)
 
311
        return klass(tree, base_tree, tt, metadata.get('message'))
 
312
 
 
313
    def make_merger(self, task=None):
 
314
        """Return a merger that can unshelve the changes."""
 
315
        target_tree = self.transform.get_preview_tree()
 
316
        merger = merge.Merger.from_uncommitted(self.tree, target_tree,
 
317
            task, self.base_tree)
 
318
        merger.merge_type = merge.Merge3Merger
 
319
        return merger
 
320
 
 
321
    def finalize(self):
 
322
        """Release all resources held by this Unshelver."""
 
323
        self.transform.finalize()
 
324
 
 
325
 
 
326
class ShelfManager(object):
 
327
    """Maintain a list of shelved changes."""
 
328
 
 
329
    def __init__(self, tree, transport):
 
330
        self.tree = tree
 
331
        self.transport = transport.clone('shelf')
 
332
        self.transport.ensure_base()
 
333
 
 
334
    def get_shelf_filename(self, shelf_id):
 
335
        return 'shelf-%d' % shelf_id
 
336
 
 
337
    def get_shelf_ids(self, filenames):
 
338
        matcher = re.compile('shelf-([1-9][0-9]*)')
 
339
        shelf_ids = []
 
340
        for filename in filenames:
 
341
            match = matcher.match(filename)
 
342
            if match is not None:
 
343
                shelf_ids.append(int(match.group(1)))
 
344
        return shelf_ids
 
345
 
 
346
    def new_shelf(self):
 
347
        """Return a file object and id for a new set of shelved changes."""
 
348
        last_shelf = self.last_shelf()
 
349
        if last_shelf is None:
 
350
            next_shelf = 1
 
351
        else:
 
352
            next_shelf = last_shelf + 1
 
353
        filename = self.get_shelf_filename(next_shelf)
 
354
        shelf_file = open(self.transport.local_abspath(filename), 'wb')
 
355
        return next_shelf, shelf_file
 
356
 
 
357
    def shelve_changes(self, creator, message=None):
 
358
        """Store the changes in a ShelfCreator on a shelf."""
 
359
        next_shelf, shelf_file = self.new_shelf()
 
360
        try:
 
361
            creator.write_shelf(shelf_file, message)
 
362
        finally:
 
363
            shelf_file.close()
 
364
        creator.transform()
 
365
        return next_shelf
 
366
 
 
367
    def read_shelf(self, shelf_id):
 
368
        """Return the file associated with a shelf_id for reading.
 
369
 
 
370
        :param shelf_id: The id of the shelf to retrive the file for.
 
371
        """
 
372
        filename = self.get_shelf_filename(shelf_id)
 
373
        try:
 
374
            return open(self.transport.local_abspath(filename), 'rb')
 
375
        except IOError, e:
 
376
            if e.errno != errno.ENOENT:
 
377
                raise
 
378
            from bzrlib import errors
 
379
            raise errors.NoSuchShelfId(shelf_id)
 
380
 
 
381
    def get_unshelver(self, shelf_id):
 
382
        """Return an unshelver for a given shelf_id.
 
383
 
 
384
        :param shelf_id: The shelf id to return the unshelver for.
 
385
        """
 
386
        shelf_file = self.read_shelf(shelf_id)
 
387
        try:
 
388
            return Unshelver.from_tree_and_shelf(self.tree, shelf_file)
 
389
        finally:
 
390
            shelf_file.close()
 
391
 
 
392
    def get_metadata(self, shelf_id):
 
393
        """Return the metadata associated with a given shelf_id."""
 
394
        shelf_file = self.read_shelf(shelf_id)
 
395
        try:
 
396
            records = Unshelver.iter_records(shelf_file)
 
397
        finally:
 
398
            shelf_file.close()
 
399
        return Unshelver.parse_metadata(records)
 
400
 
 
401
    def delete_shelf(self, shelf_id):
 
402
        """Delete the shelved changes for a given id.
 
403
 
 
404
        :param shelf_id: id of the shelved changes to delete.
 
405
        """
 
406
        filename = self.get_shelf_filename(shelf_id)
 
407
        self.transport.delete(filename)
 
408
 
 
409
    def active_shelves(self):
 
410
        """Return a list of shelved changes."""
 
411
        active = self.get_shelf_ids(self.transport.list_dir('.'))
 
412
        active.sort()
 
413
        return active
 
414
 
 
415
    def last_shelf(self):
 
416
        """Return the id of the last-created shelved change."""
 
417
        active = self.active_shelves()
 
418
        if len(active) > 0:
 
419
            return active[-1]
 
420
        else:
 
421
            return None