/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: Vincent Ladeuil
  • Date: 2012-01-18 14:09:19 UTC
  • mto: This revision was merged to the branch mainline in revision 6468.
  • Revision ID: v.ladeuil+lp@free.fr-20120118140919-rlvdrhpc0nq1lbwi
Change set/remove to require a lock for the branch config files.

This means that tests (or any plugin for that matter) do not requires an
explicit lock on the branch anymore to change a single option. This also
means the optimisation becomes "opt-in" and as such won't be as
spectacular as it may be and/or harder to get right (nothing fails
anymore).

This reduces the diff by ~300 lines.

Code/tests that were updating more than one config option is still taking
a lock to at least avoid some IOs and demonstrate the benefits through
the decreased number of hpss calls.

The duplication between BranchStack and BranchOnlyStack will be removed
once the same sharing is in place for local config files, at which point
the Stack class itself may be able to host the changes.

Show diffs side-by-side

added added

removed removed

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