~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/shelf.py

  • Committer: John Arbash Meinel
  • Date: 2009-06-04 17:12:29 UTC
  • mto: This revision was merged to the branch mainline in revision 4410.
  • Revision ID: john@arbash-meinel.com-20090604171229-kbgfatt63y3u3uh1
Some small tweaks to decoding strings (avoid passing over the length 2x)

Down to 1.1s (from 1.4s) for decoding all of bzr.dev.
Also, favor decoding strings and then lists in _decode_object, since that is the
frequency we have those types inside Revisions.

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_rename(self, file_id):
 
100
        """Shelve a file rename.
 
101
 
 
102
        :param file_id: The file id of the file to shelve the renaming of.
 
103
        """
 
104
        names, parents = self.renames[file_id]
 
105
        w_trans_id = self.work_transform.trans_id_file_id(file_id)
 
106
        work_parent = self.work_transform.trans_id_file_id(parents[0])
 
107
        self.work_transform.adjust_path(names[0], work_parent, w_trans_id)
 
108
 
 
109
        s_trans_id = self.shelf_transform.trans_id_file_id(file_id)
 
110
        shelf_parent = self.shelf_transform.trans_id_file_id(parents[1])
 
111
        self.shelf_transform.adjust_path(names[1], shelf_parent, s_trans_id)
 
112
 
 
113
    def shelve_modify_target(self, file_id):
 
114
        """Shelve a change of symlink target.
 
115
 
 
116
        :param file_id: The file id of the symlink which changed target.
 
117
        :param new_target: The target that the symlink should have due
 
118
            to shelving.
 
119
        """
 
120
        new_target = self.target_tree.get_symlink_target(file_id)
 
121
        w_trans_id = self.work_transform.trans_id_file_id(file_id)
 
122
        self.work_transform.delete_contents(w_trans_id)
 
123
        self.work_transform.create_symlink(new_target, w_trans_id)
 
124
 
 
125
        old_target = self.work_tree.get_symlink_target(file_id)
 
126
        s_trans_id = self.shelf_transform.trans_id_file_id(file_id)
 
127
        self.shelf_transform.delete_contents(s_trans_id)
 
128
        self.shelf_transform.create_symlink(old_target, s_trans_id)
 
129
 
 
130
    def shelve_lines(self, file_id, new_lines):
 
131
        """Shelve text changes to a file, using provided lines.
 
132
 
 
133
        :param file_id: The file id of the file to shelve the text of.
 
134
        :param new_lines: The lines that the file should have due to shelving.
 
135
        """
 
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_file(new_lines, w_trans_id)
 
139
 
 
140
        s_trans_id = self.shelf_transform.trans_id_file_id(file_id)
 
141
        self.shelf_transform.delete_contents(s_trans_id)
 
142
        inverse_lines = self._inverse_lines(new_lines, file_id)
 
143
        self.shelf_transform.create_file(inverse_lines, s_trans_id)
 
144
 
 
145
    @staticmethod
 
146
    def _content_from_tree(tt, tree, file_id):
 
147
        trans_id = tt.trans_id_file_id(file_id)
 
148
        tt.delete_contents(trans_id)
 
149
        transform.create_from_tree(tt, trans_id, tree, file_id)
 
150
 
 
151
    def shelve_content_change(self, file_id):
 
152
        """Shelve a kind change or binary file content change.
 
153
 
 
154
        :param file_id: The file id of the file to shelve the content change
 
155
            of.
 
156
        """
 
157
        self._content_from_tree(self.work_transform, self.target_tree, file_id)
 
158
        self._content_from_tree(self.shelf_transform, self.work_tree, file_id)
 
159
 
 
160
    def shelve_creation(self, file_id):
 
161
        """Shelve creation of a file.
 
162
 
 
163
        This handles content and inventory id.
 
164
        :param file_id: The file_id of the file to shelve creation of.
 
165
        """
 
166
        kind, name, parent, versioned = self.creation[file_id]
 
167
        version = not versioned[0]
 
168
        self._shelve_creation(self.work_tree, file_id, self.work_transform,
 
169
                              self.shelf_transform, kind, name, parent,
 
170
                              version)
 
171
 
 
172
    def shelve_deletion(self, file_id):
 
173
        """Shelve deletion of a file.
 
174
 
 
175
        This handles content and inventory id.
 
176
        :param file_id: The file_id of the file to shelve deletion of.
 
177
        """
 
178
        kind, name, parent, versioned = self.deletion[file_id]
 
179
        existing_path = self.target_tree.id2path(file_id)
 
180
        if not self.work_tree.has_filename(existing_path):
 
181
            existing_path = None
 
182
        version = not versioned[1]
 
183
        self._shelve_creation(self.target_tree, file_id, self.shelf_transform,
 
184
                              self.work_transform, kind, name, parent,
 
185
                              version, existing_path=existing_path)
 
186
 
 
187
    def _shelve_creation(self, tree, file_id, from_transform, to_transform,
 
188
                         kind, name, parent, version, existing_path=None):
 
189
        w_trans_id = from_transform.trans_id_file_id(file_id)
 
190
        if parent is not None and kind is not None:
 
191
            from_transform.delete_contents(w_trans_id)
 
192
        from_transform.unversion_file(w_trans_id)
 
193
 
 
194
        if existing_path is not None:
 
195
            s_trans_id = to_transform.trans_id_tree_path(existing_path)
 
196
        else:
 
197
            s_trans_id = to_transform.trans_id_file_id(file_id)
 
198
        if parent is not None:
 
199
            s_parent_id = to_transform.trans_id_file_id(parent)
 
200
            to_transform.adjust_path(name, s_parent_id, s_trans_id)
 
201
            if existing_path is None:
 
202
                if kind is None:
 
203
                    to_transform.create_file('', s_trans_id)
 
204
                else:
 
205
                    transform.create_from_tree(to_transform, s_trans_id,
 
206
                                               tree, file_id)
 
207
        if version:
 
208
            to_transform.version_file(file_id, s_trans_id)
 
209
 
 
210
    def _inverse_lines(self, new_lines, file_id):
 
211
        """Produce a version with only those changes removed from new_lines."""
 
212
        target_lines = self.target_tree.get_file_lines(file_id)
 
213
        work_lines = self.work_tree.get_file_lines(file_id)
 
214
        return merge3.Merge3(new_lines, target_lines, work_lines).merge_lines()
 
215
 
 
216
    def finalize(self):
 
217
        """Release all resources used by this ShelfCreator."""
 
218
        self.work_transform.finalize()
 
219
        self.shelf_transform.finalize()
 
220
 
 
221
    def transform(self):
 
222
        """Shelve changes from working tree."""
 
223
        self.work_transform.apply()
 
224
 
 
225
    def write_shelf(self, shelf_file, message=None):
 
226
        """Serialize the shelved changes to a file.
 
227
 
 
228
        :param shelf_file: A file-like object to write the shelf to.
 
229
        :param message: An optional message describing the shelved changes.
 
230
        :return: the filename of the written file.
 
231
        """
 
232
        transform.resolve_conflicts(self.shelf_transform)
 
233
        serializer = pack.ContainerSerialiser()
 
234
        shelf_file.write(serializer.begin())
 
235
        metadata = {
 
236
            'revision_id': self.target_tree.get_revision_id(),
 
237
        }
 
238
        if message is not None:
 
239
            metadata['message'] = message.encode('utf-8')
 
240
        shelf_file.write(serializer.bytes_record(
 
241
            bencode.bencode(metadata), (('metadata',),)))
 
242
        for bytes in self.shelf_transform.serialize(serializer):
 
243
            shelf_file.write(bytes)
 
244
        shelf_file.write(serializer.end())
 
245
 
 
246
 
 
247
class Unshelver(object):
 
248
    """Unshelve shelved changes."""
 
249
 
 
250
    def __init__(self, tree, base_tree, transform, message):
 
251
        """Constructor.
 
252
 
 
253
        :param tree: The tree to apply the changes to.
 
254
        :param base_tree: The basis to apply the tranform to.
 
255
        :param message: A message from the shelved transform.
 
256
        """
 
257
        self.tree = tree
 
258
        self.base_tree = base_tree
 
259
        self.transform = transform
 
260
        self.message = message
 
261
 
 
262
    @staticmethod
 
263
    def iter_records(shelf_file):
 
264
        parser = pack.ContainerPushParser()
 
265
        parser.accept_bytes(shelf_file.read())
 
266
        return iter(parser.read_pending_records())
 
267
 
 
268
    @staticmethod
 
269
    def parse_metadata(records):
 
270
        names, metadata_bytes = records.next()
 
271
        if names[0] != ('metadata',):
 
272
            raise errors.ShelfCorrupt
 
273
        metadata = bencode.bdecode(metadata_bytes)
 
274
        message = metadata.get('message')
 
275
        if message is not None:
 
276
            metadata['message'] = message.decode('utf-8')
 
277
        return metadata
 
278
 
 
279
    @classmethod
 
280
    def from_tree_and_shelf(klass, tree, shelf_file):
 
281
        """Create an Unshelver from a tree and a shelf file.
 
282
 
 
283
        :param tree: The tree to apply shelved changes to.
 
284
        :param shelf_file: A file-like object containing shelved changes.
 
285
        :return: The Unshelver.
 
286
        """
 
287
        records = klass.iter_records(shelf_file)
 
288
        metadata = klass.parse_metadata(records)
 
289
        base_revision_id = metadata['revision_id']
 
290
        try:
 
291
            base_tree = tree.revision_tree(base_revision_id)
 
292
        except errors.NoSuchRevisionInTree:
 
293
            base_tree = tree.branch.repository.revision_tree(base_revision_id)
 
294
        tt = transform.TransformPreview(base_tree)
 
295
        tt.deserialize(records)
 
296
        return klass(tree, base_tree, tt, metadata.get('message'))
 
297
 
 
298
    def make_merger(self, task=None):
 
299
        """Return a merger that can unshelve the changes."""
 
300
        target_tree = self.transform.get_preview_tree()
 
301
        merger = merge.Merger.from_uncommitted(self.tree, target_tree,
 
302
            task, self.base_tree)
 
303
        merger.merge_type = merge.Merge3Merger
 
304
        return merger
 
305
 
 
306
    def finalize(self):
 
307
        """Release all resources held by this Unshelver."""
 
308
        self.transform.finalize()
 
309
 
 
310
 
 
311
class ShelfManager(object):
 
312
    """Maintain a list of shelved changes."""
 
313
 
 
314
    def __init__(self, tree, transport):
 
315
        self.tree = tree
 
316
        self.transport = transport.clone('shelf')
 
317
        self.transport.ensure_base()
 
318
 
 
319
    def get_shelf_filename(self, shelf_id):
 
320
        return 'shelf-%d' % shelf_id
 
321
 
 
322
    def get_shelf_ids(self, filenames):
 
323
        matcher = re.compile('shelf-([1-9][0-9]*)')
 
324
        shelf_ids = []
 
325
        for filename in filenames:
 
326
            match = matcher.match(filename)
 
327
            if match is not None:
 
328
                shelf_ids.append(int(match.group(1)))
 
329
        return shelf_ids
 
330
 
 
331
    def new_shelf(self):
 
332
        """Return a file object and id for a new set of shelved changes."""
 
333
        last_shelf = self.last_shelf()
 
334
        if last_shelf is None:
 
335
            next_shelf = 1
 
336
        else:
 
337
            next_shelf = last_shelf + 1
 
338
        filename = self.get_shelf_filename(next_shelf)
 
339
        shelf_file = open(self.transport.local_abspath(filename), 'wb')
 
340
        return next_shelf, shelf_file
 
341
 
 
342
    def shelve_changes(self, creator, message=None):
 
343
        """Store the changes in a ShelfCreator on a shelf."""
 
344
        next_shelf, shelf_file = self.new_shelf()
 
345
        try:
 
346
            creator.write_shelf(shelf_file, message)
 
347
        finally:
 
348
            shelf_file.close()
 
349
        creator.transform()
 
350
        return next_shelf
 
351
 
 
352
    def read_shelf(self, shelf_id):
 
353
        """Return the file associated with a shelf_id for reading.
 
354
 
 
355
        :param shelf_id: The id of the shelf to retrive the file for.
 
356
        """
 
357
        filename = self.get_shelf_filename(shelf_id)
 
358
        try:
 
359
            return open(self.transport.local_abspath(filename), 'rb')
 
360
        except IOError, e:
 
361
            if e.errno != errno.ENOENT:
 
362
                raise
 
363
            from bzrlib import errors
 
364
            raise errors.NoSuchShelfId(shelf_id)
 
365
 
 
366
    def get_unshelver(self, shelf_id):
 
367
        """Return an unshelver for a given shelf_id.
 
368
 
 
369
        :param shelf_id: The shelf id to return the unshelver for.
 
370
        """
 
371
        shelf_file = self.read_shelf(shelf_id)
 
372
        try:
 
373
            return Unshelver.from_tree_and_shelf(self.tree, shelf_file)
 
374
        finally:
 
375
            shelf_file.close()
 
376
 
 
377
    def get_metadata(self, shelf_id):
 
378
        """Return the metadata associated with a given shelf_id."""
 
379
        shelf_file = self.read_shelf(shelf_id)
 
380
        try:
 
381
            records = Unshelver.iter_records(shelf_file)
 
382
        finally:
 
383
            shelf_file.close()
 
384
        return Unshelver.parse_metadata(records)
 
385
 
 
386
    def delete_shelf(self, shelf_id):
 
387
        """Delete the shelved changes for a given id.
 
388
 
 
389
        :param shelf_id: id of the shelved changes to delete.
 
390
        """
 
391
        filename = self.get_shelf_filename(shelf_id)
 
392
        self.transport.delete(filename)
 
393
 
 
394
    def active_shelves(self):
 
395
        """Return a list of shelved changes."""
 
396
        active = self.get_shelf_ids(self.transport.list_dir('.'))
 
397
        active.sort()
 
398
        return active
 
399
 
 
400
    def last_shelf(self):
 
401
        """Return the id of the last-created shelved change."""
 
402
        active = self.active_shelves()
 
403
        if len(active) > 0:
 
404
            return active[-1]
 
405
        else:
 
406
            return None