/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: 2008-11-25 18:51:48 UTC
  • mto: This revision was merged to the branch mainline in revision 3854.
  • Revision ID: john@arbash-meinel.com-20081125185148-jsfkqnzfjjqsleds
It seems we have some direct tests that don't use strings and expect a value error as well.

They would be sanitized later on by Revision. We could use that code, but this test
depends on the serializer, which Revision wouldn't know about.

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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
 
 
18
import errno
 
19
import re
 
20
 
 
21
from bzrlib import (
 
22
    errors,
 
23
    merge,
 
24
    merge3,
 
25
    osutils,
 
26
    pack,
 
27
    transform,
 
28
    ui,
 
29
    workingtree,
 
30
)
 
31
from bzrlib.util import bencode
 
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
        self.target_tree = target_tree
 
47
        self.shelf_transform = transform.TransformPreview(self.target_tree)
 
48
        self.renames = {}
 
49
        self.creation = {}
 
50
        self.deletion = {}
 
51
        self.iter_changes = work_tree.iter_changes(self.target_tree,
 
52
                                                   specific_files=file_list)
 
53
 
 
54
    def iter_shelvable(self):
 
55
        """Iterable of tuples describing shelvable changes.
 
56
 
 
57
        As well as generating the tuples, this updates several members.
 
58
        Tuples may be:
 
59
           ('add file', file_id, work_kind, work_path)
 
60
           ('delete file', file_id, target_kind, target_path)
 
61
           ('rename', file_id, target_path, work_path)
 
62
           ('change kind', file_id, target_kind, work_kind, target_path)
 
63
           ('modify text', file_id)
 
64
        """
 
65
        for (file_id, paths, changed, versioned, parents, names, kind,
 
66
             executable) in self.iter_changes:
 
67
            if kind[0] is None or versioned[0] == False:
 
68
                self.creation[file_id] = (kind[1], names[1], parents[1],
 
69
                                          versioned)
 
70
                yield ('add file', file_id, kind[1], paths[1])
 
71
            elif kind[1] is None or versioned[0] == False:
 
72
                self.deletion[file_id] = (kind[0], names[0], parents[0],
 
73
                                          versioned)
 
74
                yield ('delete file', file_id, kind[0], paths[0])
 
75
            else:
 
76
                if names[0] != names[1] or parents[0] != parents[1]:
 
77
                    self.renames[file_id] = (names, parents)
 
78
                    yield ('rename', file_id) + paths
 
79
 
 
80
                if kind[0] != kind [1]:
 
81
                    yield ('change kind', file_id, kind[0], kind[1], paths[0])
 
82
                elif changed:
 
83
                    yield ('modify text', file_id)
 
84
 
 
85
    def shelve_rename(self, file_id):
 
86
        """Shelve a file rename.
 
87
 
 
88
        :param file_id: The file id of the file to shelve the renaming of.
 
89
        """
 
90
        names, parents = self.renames[file_id]
 
91
        w_trans_id = self.work_transform.trans_id_file_id(file_id)
 
92
        work_parent = self.work_transform.trans_id_file_id(parents[0])
 
93
        self.work_transform.adjust_path(names[0], work_parent, w_trans_id)
 
94
 
 
95
        s_trans_id = self.shelf_transform.trans_id_file_id(file_id)
 
96
        shelf_parent = self.shelf_transform.trans_id_file_id(parents[1])
 
97
        self.shelf_transform.adjust_path(names[1], shelf_parent, s_trans_id)
 
98
 
 
99
    def shelve_lines(self, file_id, new_lines):
 
100
        """Shelve text changes to a file, using provided lines.
 
101
 
 
102
        :param file_id: The file id of the file to shelve the text of.
 
103
        :param new_lines: The lines that the file should have due to shelving.
 
104
        """
 
105
        w_trans_id = self.work_transform.trans_id_file_id(file_id)
 
106
        self.work_transform.delete_contents(w_trans_id)
 
107
        self.work_transform.create_file(new_lines, w_trans_id)
 
108
 
 
109
        s_trans_id = self.shelf_transform.trans_id_file_id(file_id)
 
110
        self.shelf_transform.delete_contents(s_trans_id)
 
111
        inverse_lines = self._inverse_lines(new_lines, file_id)
 
112
        self.shelf_transform.create_file(inverse_lines, s_trans_id)
 
113
 
 
114
    @staticmethod
 
115
    def _content_from_tree(tt, tree, file_id):
 
116
        trans_id = tt.trans_id_file_id(file_id)
 
117
        tt.delete_contents(trans_id)
 
118
        transform.create_from_tree(tt, trans_id, tree, file_id)
 
119
 
 
120
    def shelve_content_change(self, file_id):
 
121
        """Shelve a kind change or binary file content change.
 
122
 
 
123
        :param file_id: The file id of the file to shelve the content change
 
124
            of.
 
125
        """
 
126
        self._content_from_tree(self.work_transform, self.target_tree, file_id)
 
127
        self._content_from_tree(self.shelf_transform, self.work_tree, file_id)
 
128
 
 
129
    def shelve_creation(self, file_id):
 
130
        """Shelve creation of a file.
 
131
 
 
132
        This handles content and inventory id.
 
133
        :param file_id: The file_id of the file to shelve creation of.
 
134
        """
 
135
        kind, name, parent, versioned = self.creation[file_id]
 
136
        version = not versioned[0]
 
137
        self._shelve_creation(self.work_tree, file_id, self.work_transform,
 
138
                              self.shelf_transform, kind, name, parent,
 
139
                              version)
 
140
 
 
141
    def shelve_deletion(self, file_id):
 
142
        """Shelve deletion of a file.
 
143
 
 
144
        This handles content and inventory id.
 
145
        :param file_id: The file_id of the file to shelve deletion of.
 
146
        """
 
147
        kind, name, parent, versioned = self.deletion[file_id]
 
148
        existing_path = self.target_tree.id2path(file_id)
 
149
        if not self.work_tree.has_filename(existing_path):
 
150
            existing_path = None
 
151
        version = not versioned[1]
 
152
        self._shelve_creation(self.target_tree, file_id, self.shelf_transform,
 
153
                              self.work_transform, kind, name, parent,
 
154
                              version, existing_path=existing_path)
 
155
 
 
156
    def _shelve_creation(self, tree, file_id, from_transform, to_transform,
 
157
                         kind, name, parent, version, existing_path=None):
 
158
        w_trans_id = from_transform.trans_id_file_id(file_id)
 
159
        if parent is not None and kind is not None:
 
160
            from_transform.delete_contents(w_trans_id)
 
161
        from_transform.unversion_file(w_trans_id)
 
162
 
 
163
        if existing_path is not None:
 
164
            s_trans_id = to_transform.trans_id_tree_path(existing_path)
 
165
        else:
 
166
            s_trans_id = to_transform.trans_id_file_id(file_id)
 
167
        if parent is not None:
 
168
            s_parent_id = to_transform.trans_id_file_id(parent)
 
169
            to_transform.adjust_path(name, s_parent_id, s_trans_id)
 
170
            if existing_path is None:
 
171
                if kind is None:
 
172
                    to_transform.create_file('', s_trans_id)
 
173
                else:
 
174
                    transform.create_from_tree(to_transform, s_trans_id,
 
175
                                               tree, file_id)
 
176
        if version:
 
177
            to_transform.version_file(file_id, s_trans_id)
 
178
 
 
179
    def _inverse_lines(self, new_lines, file_id):
 
180
        """Produce a version with only those changes removed from new_lines."""
 
181
        target_lines = self.target_tree.get_file_lines(file_id)
 
182
        work_lines = self.work_tree.get_file_lines(file_id)
 
183
        return merge3.Merge3(new_lines, target_lines, work_lines).merge_lines()
 
184
 
 
185
    def finalize(self):
 
186
        """Release all resources used by this ShelfCreator."""
 
187
        self.work_transform.finalize()
 
188
        self.shelf_transform.finalize()
 
189
 
 
190
    def transform(self):
 
191
        """Shelve changes from working tree."""
 
192
        self.work_transform.apply()
 
193
 
 
194
    def write_shelf(self, shelf_file, message=None):
 
195
        """Serialize the shelved changes to a file.
 
196
 
 
197
        :param shelf_file: A file-like object to write the shelf to.
 
198
        :param message: An optional message describing the shelved changes.
 
199
        :return: the filename of the written file.
 
200
        """
 
201
        transform.resolve_conflicts(self.shelf_transform)
 
202
        serializer = pack.ContainerSerialiser()
 
203
        shelf_file.write(serializer.begin())
 
204
        metadata = {
 
205
            'revision_id': self.target_tree.get_revision_id(),
 
206
        }
 
207
        if message is not None:
 
208
            metadata['message'] = message.encode('utf-8')
 
209
        shelf_file.write(serializer.bytes_record(
 
210
            bencode.bencode(metadata), (('metadata',),)))
 
211
        for bytes in self.shelf_transform.serialize(serializer):
 
212
            shelf_file.write(bytes)
 
213
        shelf_file.write(serializer.end())
 
214
 
 
215
 
 
216
class Unshelver(object):
 
217
    """Unshelve shelved changes."""
 
218
 
 
219
    def __init__(self, tree, base_tree, transform, message):
 
220
        """Constructor.
 
221
 
 
222
        :param tree: The tree to apply the changes to.
 
223
        :param base_tree: The basis to apply the tranform to.
 
224
        :param message: A message from the shelved transform.
 
225
        """
 
226
        self.tree = tree
 
227
        self.base_tree = base_tree
 
228
        self.transform = transform
 
229
        self.message = message
 
230
 
 
231
    @classmethod
 
232
    def from_tree_and_shelf(klass, tree, shelf_file):
 
233
        """Create an Unshelver from a tree and a shelf file.
 
234
 
 
235
        :param tree: The tree to apply shelved changes to.
 
236
        :param shelf_file: A file-like object containing shelved changes.
 
237
        :return: The Unshelver.
 
238
        """
 
239
        parser = pack.ContainerPushParser()
 
240
        parser.accept_bytes(shelf_file.read())
 
241
        records = iter(parser.read_pending_records())
 
242
        names, metadata_bytes = records.next()
 
243
        if names[0] != ('metadata',):
 
244
            raise errors.ShelfCorrupt
 
245
        metadata = bencode.bdecode(metadata_bytes)
 
246
        base_revision_id = metadata['revision_id']
 
247
        message = metadata.get('message')
 
248
        if message is not None:
 
249
            message = message.decode('utf-8')
 
250
        try:
 
251
            base_tree = tree.revision_tree(base_revision_id)
 
252
        except errors.NoSuchRevisionInTree:
 
253
            base_tree = tree.branch.repository.revision_tree(base_revision_id)
 
254
        tt = transform.TransformPreview(base_tree)
 
255
        tt.deserialize(records)
 
256
        return klass(tree, base_tree, tt, message)
 
257
 
 
258
    def make_merger(self):
 
259
        """Return a merger that can unshelve the changes."""
 
260
        pb = ui.ui_factory.nested_progress_bar()
 
261
        try:
 
262
            target_tree = self.transform.get_preview_tree()
 
263
            merger = merge.Merger.from_uncommitted(self.tree, target_tree, pb,
 
264
                                                   self.base_tree)
 
265
            merger.merge_type = merge.Merge3Merger
 
266
            return merger
 
267
        finally:
 
268
            pb.finished()
 
269
 
 
270
    def finalize(self):
 
271
        """Release all resources held by this Unshelver."""
 
272
        self.transform.finalize()
 
273
 
 
274
 
 
275
class ShelfManager(object):
 
276
    """Maintain a list of shelved changes."""
 
277
 
 
278
    def __init__(self, tree, transport):
 
279
        self.tree = tree
 
280
        self.transport = transport.clone('shelf')
 
281
        self.transport.ensure_base()
 
282
 
 
283
    def get_shelf_filename(self, shelf_id):
 
284
        return 'shelf-%d' % shelf_id
 
285
 
 
286
    def get_shelf_ids(self, filenames):
 
287
        matcher = re.compile('shelf-([1-9][0-9]*)')
 
288
        shelf_ids = []
 
289
        for filename in filenames:
 
290
            match = matcher.match(filename)
 
291
            if match is not None:
 
292
                shelf_ids.append(int(match.group(1)))
 
293
        return shelf_ids
 
294
 
 
295
    def new_shelf(self):
 
296
        """Return a file object and id for a new set of shelved changes."""
 
297
        last_shelf = self.last_shelf()
 
298
        if last_shelf is None:
 
299
            next_shelf = 1
 
300
        else:
 
301
            next_shelf = last_shelf + 1
 
302
        filename = self.get_shelf_filename(next_shelf)
 
303
        shelf_file = open(self.transport.local_abspath(filename), 'wb')
 
304
        return next_shelf, shelf_file
 
305
 
 
306
    def shelve_changes(self, creator, message=None):
 
307
        """Store the changes in a ShelfCreator on a shelf."""
 
308
        next_shelf, shelf_file = self.new_shelf()
 
309
        try:
 
310
            creator.write_shelf(shelf_file, message)
 
311
        finally:
 
312
            shelf_file.close()
 
313
        creator.transform()
 
314
        return next_shelf
 
315
 
 
316
    def read_shelf(self, shelf_id):
 
317
        """Return the file associated with a shelf_id for reading.
 
318
 
 
319
        :param shelf_id: The id of the shelf to retrive the file for.
 
320
        """
 
321
        filename = self.get_shelf_filename(shelf_id)
 
322
        try:
 
323
            return open(self.transport.local_abspath(filename), 'rb')
 
324
        except IOError, e:
 
325
            if e.errno != errno.ENOENT:
 
326
                raise
 
327
            from bzrlib import errors
 
328
            raise errors.NoSuchShelfId(shelf_id)
 
329
 
 
330
    def get_unshelver(self, shelf_id):
 
331
        """Return an unshelver for a given shelf_id.
 
332
 
 
333
        :param shelf_id: The shelf id to return the unshelver for.
 
334
        """
 
335
        shelf_file = self.read_shelf(shelf_id)
 
336
        try:
 
337
            return Unshelver.from_tree_and_shelf(self.tree, shelf_file)
 
338
        finally:
 
339
            shelf_file.close()
 
340
 
 
341
    def delete_shelf(self, shelf_id):
 
342
        """Delete the shelved changes for a given id.
 
343
 
 
344
        :param shelf_id: id of the shelved changes to delete.
 
345
        """
 
346
        filename = self.get_shelf_filename(shelf_id)
 
347
        self.transport.delete(filename)
 
348
 
 
349
    def active_shelves(self):
 
350
        """Return a list of shelved changes."""
 
351
        active = self.get_shelf_ids(self.transport.list_dir('.'))
 
352
        active.sort()
 
353
        return active
 
354
 
 
355
    def last_shelf(self):
 
356
        """Return the id of the last-created shelved change."""
 
357
        active = self.active_shelves()
 
358
        if len(active) > 0:
 
359
            return active[-1]
 
360
        else:
 
361
            return None