/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: John Arbash Meinel
  • Date: 2010-01-12 22:51:31 UTC
  • mto: This revision was merged to the branch mainline in revision 4955.
  • Revision ID: john@arbash-meinel.com-20100112225131-he8h411p6aeeb947
Delay grabbing an output stream until we actually go to show a diff.

This makes the test suite happy, but it also seems to be reasonable.
If we aren't going to write anything, we don't need to hold an
output stream open.

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