~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/shelf_ui.py

merge 2.0 branch rev 4647

Show diffs side-by-side

added added

removed removed

Lines of Context:
35
35
)
36
36
 
37
37
 
 
38
class ShelfReporter(object):
 
39
 
 
40
    vocab = {'add file': 'Shelve adding file "%(path)s"?',
 
41
             'binary': 'Shelve binary changes?',
 
42
             'change kind': 'Shelve changing "%s" from %(other)s'
 
43
             ' to %(this)s?',
 
44
             'delete file': 'Shelve removing file "%(path)s"?',
 
45
             'final': 'Shelve %d change(s)?',
 
46
             'hunk': 'Shelve?',
 
47
             'modify target': 'Shelve changing target of'
 
48
             ' "%(path)s" from "%(other)s" to "%(this)s"?',
 
49
             'rename': 'Shelve renaming "%(other)s" =>'
 
50
                        ' "%(this)s"?'
 
51
             }
 
52
 
 
53
    invert_diff = False
 
54
 
 
55
    def __init__(self):
 
56
        self.delta_reporter = delta._ChangeReporter()
 
57
 
 
58
    def no_changes(self):
 
59
        """Report that no changes were selected to apply."""
 
60
        trace.warning('No changes to shelve.')
 
61
 
 
62
    def shelved_id(self, shelf_id):
 
63
        """Report the id changes were shelved to."""
 
64
        trace.note('Changes shelved with id "%d".' % shelf_id)
 
65
 
 
66
    def changes_destroyed(self):
 
67
        """Report that changes were made without shelving."""
 
68
        trace.note('Selected changes destroyed.')
 
69
 
 
70
    def selected_changes(self, transform):
 
71
        """Report the changes that were selected."""
 
72
        trace.note("Selected changes:")
 
73
        changes = transform.iter_changes()
 
74
        delta.report_changes(changes, self.delta_reporter)
 
75
 
 
76
    def prompt_change(self, change):
 
77
        """Determine the prompt for a change to apply."""
 
78
        if change[0] == 'rename':
 
79
            vals = {'this': change[3], 'other': change[2]}
 
80
        elif change[0] == 'change kind':
 
81
            vals = {'path': change[4], 'other': change[2], 'this': change[3]}
 
82
        elif change[0] == 'modify target':
 
83
            vals = {'path': change[2], 'other': change[3], 'this': change[4]}
 
84
        else:
 
85
            vals = {'path': change[3]}
 
86
        prompt = self.vocab[change[0]] % vals
 
87
        return prompt
 
88
 
 
89
 
 
90
class ApplyReporter(ShelfReporter):
 
91
 
 
92
    vocab = {'add file': 'Delete file "%(path)s"?',
 
93
             'binary': 'Apply binary changes?',
 
94
             'change kind': 'Change "%(path)s" from %(this)s'
 
95
             ' to %(other)s?',
 
96
             'delete file': 'Add file "%(path)s"?',
 
97
             'final': 'Apply %d change(s)?',
 
98
             'hunk': 'Apply change?',
 
99
             'modify target': 'Change target of'
 
100
             ' "%(path)s" from "%(this)s" to "%(other)s"?',
 
101
             'rename': 'Rename "%(this)s" => "%(other)s"?',
 
102
             }
 
103
 
 
104
    invert_diff = True
 
105
 
 
106
    def changes_destroyed(self):
 
107
        pass
 
108
 
 
109
 
38
110
class Shelver(object):
39
111
    """Interactively shelve the changes in a working tree."""
40
112
 
41
113
    def __init__(self, work_tree, target_tree, diff_writer=None, auto=False,
42
114
                 auto_apply=False, file_list=None, message=None,
43
 
                 destroy=False):
 
115
                 destroy=False, manager=None, reporter=None):
44
116
        """Constructor.
45
117
 
46
118
        :param work_tree: The working tree to shelve changes from.
52
124
        :param message: The message to associate with the shelved changes.
53
125
        :param destroy: Change the working tree without storing the shelved
54
126
            changes.
 
127
        :param manager: The shelf manager to use.
 
128
        :param reporter: Object for reporting changes to user.
55
129
        """
56
130
        self.work_tree = work_tree
57
131
        self.target_tree = target_tree
58
132
        self.diff_writer = diff_writer
59
133
        if self.diff_writer is None:
60
134
            self.diff_writer = sys.stdout
61
 
        self.manager = work_tree.get_shelf_manager()
 
135
        if manager is None:
 
136
            manager = work_tree.get_shelf_manager()
 
137
        self.manager = manager
62
138
        self.auto = auto
63
139
        self.auto_apply = auto_apply
64
140
        self.file_list = file_list
65
141
        self.message = message
66
142
        self.destroy = destroy
 
143
        if reporter is None:
 
144
            reporter = ShelfReporter()
 
145
        self.reporter = reporter
67
146
 
68
147
    @classmethod
69
148
    def from_args(klass, diff_writer, revision=None, all=False, file_list=None,
70
149
                  message=None, directory='.', destroy=False):
71
150
        """Create a shelver from commandline arguments.
72
151
 
 
152
        The returned shelver wil have a work_tree that is locked and should
 
153
        be unlocked.
 
154
 
73
155
        :param revision: RevisionSpec of the revision to compare to.
74
156
        :param all: If True, shelve all changes without prompting.
75
157
        :param file_list: If supplied, only files in this list may be  shelved.
79
161
            changes.
80
162
        """
81
163
        tree, path = workingtree.WorkingTree.open_containing(directory)
82
 
        target_tree = builtins._get_one_revision_tree('shelf2', revision,
83
 
            tree.branch, tree)
84
 
        files = builtins.safe_relpath_files(tree, file_list)
 
164
        # Ensure that tree is locked for the lifetime of target_tree, as
 
165
        # target tree may be reading from the same dirstate.
 
166
        tree.lock_tree_write()
 
167
        try:
 
168
            target_tree = builtins._get_one_revision_tree('shelf2', revision,
 
169
                tree.branch, tree)
 
170
            files = builtins.safe_relpath_files(tree, file_list)
 
171
        except:
 
172
            tree.unlock()
 
173
            raise
85
174
        return klass(tree, target_tree, diff_writer, all, all, files, message,
86
175
                     destroy)
87
176
 
98
187
                        changes_shelved += self.handle_modify_text(creator,
99
188
                                                                   change[1])
100
189
                    except errors.BinaryFile:
101
 
                        if self.prompt_bool('Shelve binary changes?'):
 
190
                        if self.prompt_bool(self.reporter.vocab['binary']):
102
191
                            changes_shelved += 1
103
192
                            creator.shelve_content_change(change[1])
104
 
                if change[0] == 'add file':
105
 
                    if self.prompt_bool('Shelve adding file "%s"?'
106
 
                                        % change[3]):
107
 
                        creator.shelve_creation(change[1])
108
 
                        changes_shelved += 1
109
 
                if change[0] == 'delete file':
110
 
                    if self.prompt_bool('Shelve removing file "%s"?'
111
 
                                        % change[3]):
112
 
                        creator.shelve_deletion(change[1])
113
 
                        changes_shelved += 1
114
 
                if change[0] == 'change kind':
115
 
                    if self.prompt_bool('Shelve changing "%s" from %s to %s? '
116
 
                                        % (change[4], change[2], change[3])):
117
 
                        creator.shelve_content_change(change[1])
118
 
                        changes_shelved += 1
119
 
                if change[0] == 'rename':
120
 
                    if self.prompt_bool('Shelve renaming "%s" => "%s"?' %
121
 
                                   change[2:]):
122
 
                        creator.shelve_rename(change[1])
123
 
                        changes_shelved += 1
124
 
                if change[0] == 'modify target':
125
 
                    if self.prompt_bool('Shelve changing target of "%s" '
126
 
                            'from "%s" to "%s"?' % change[2:]):
127
 
                        creator.shelve_modify_target(change[1])
 
193
                else:
 
194
                    if self.prompt_bool(self.reporter.prompt_change(change)):
 
195
                        creator.shelve_change(change)
128
196
                        changes_shelved += 1
129
197
            if changes_shelved > 0:
130
 
                trace.note("Selected changes:")
131
 
                changes = creator.work_transform.iter_changes()
132
 
                reporter = delta._ChangeReporter()
133
 
                delta.report_changes(changes, reporter)
 
198
                self.reporter.selected_changes(creator.work_transform)
134
199
                if (self.auto_apply or self.prompt_bool(
135
 
                    'Shelve %d change(s)?' % changes_shelved)):
 
200
                    self.reporter.vocab['final'] % changes_shelved)):
136
201
                    if self.destroy:
137
202
                        creator.transform()
138
 
                        trace.note('Selected changes destroyed.')
 
203
                        self.reporter.changes_destroyed()
139
204
                    else:
140
205
                        shelf_id = self.manager.shelve_changes(creator,
141
206
                                                               self.message)
142
 
                        trace.note('Changes shelved with id "%d".' % shelf_id)
 
207
                        self.reporter.shelved_id(shelf_id)
143
208
            else:
144
 
                trace.warning('No changes to shelve.')
 
209
                self.reporter.no_changes()
145
210
        finally:
146
211
            shutil.rmtree(self.tempdir)
147
212
            creator.finalize()
148
213
 
149
 
    def get_parsed_patch(self, file_id):
 
214
    def get_parsed_patch(self, file_id, invert=False):
150
215
        """Return a parsed version of a file's patch.
151
216
 
152
217
        :param file_id: The id of the file to generate a patch for.
 
218
        :param invert: If True, provide an inverted patch (insertions displayed
 
219
            as removals, removals displayed as insertions).
153
220
        :return: A patches.Patch.
154
221
        """
155
 
        old_path = self.target_tree.id2path(file_id)
156
 
        new_path = self.work_tree.id2path(file_id)
157
222
        diff_file = StringIO()
158
 
        text_differ = diff.DiffText(self.target_tree, self.work_tree,
159
 
                                    diff_file)
 
223
        if invert:
 
224
            old_tree = self.work_tree
 
225
            new_tree = self.target_tree
 
226
        else:
 
227
            old_tree = self.target_tree
 
228
            new_tree = self.work_tree
 
229
        old_path = old_tree.id2path(file_id)
 
230
        new_path = new_tree.id2path(file_id)
 
231
        text_differ = diff.DiffText(old_tree, new_tree, diff_file)
160
232
        patch = text_differ.diff(file_id, old_path, new_path, 'file', 'file')
161
233
        diff_file.seek(0)
162
234
        return patches.parse_patch(diff_file)
203
275
    def handle_modify_text(self, creator, file_id):
204
276
        """Provide diff hunk selection for modified text.
205
277
 
 
278
        If self.reporter.invert_diff is True, the diff is inverted so that
 
279
        insertions are displayed as removals and vice versa.
 
280
 
206
281
        :param creator: a ShelfCreator
207
282
        :param file_id: The id of the file to shelve.
208
283
        :return: number of shelved hunks.
209
284
        """
210
 
        target_lines = self.target_tree.get_file_lines(file_id)
 
285
        if self.reporter.invert_diff:
 
286
            target_lines = self.work_tree.get_file_lines(file_id)
 
287
        else:
 
288
            target_lines = self.target_tree.get_file_lines(file_id)
211
289
        textfile.check_text_lines(self.work_tree.get_file_lines(file_id))
212
290
        textfile.check_text_lines(target_lines)
213
 
        parsed = self.get_parsed_patch(file_id)
 
291
        parsed = self.get_parsed_patch(file_id, self.reporter.invert_diff)
214
292
        final_hunks = []
215
293
        if not self.auto:
216
294
            offset = 0
217
295
            self.diff_writer.write(parsed.get_header())
218
296
            for hunk in parsed.hunks:
219
297
                self.diff_writer.write(str(hunk))
220
 
                if not self.prompt_bool('Shelve?'):
 
298
                selected = self.prompt_bool(self.reporter.vocab['hunk'])
 
299
                if not self.reporter.invert_diff:
 
300
                    selected = (not selected)
 
301
                if selected:
221
302
                    hunk.mod_pos += offset
222
303
                    final_hunks.append(hunk)
223
304
                else:
224
305
                    offset -= (hunk.mod_range - hunk.orig_range)
225
306
        sys.stdout.flush()
226
 
        if len(parsed.hunks) == len(final_hunks):
 
307
        if not self.reporter.invert_diff and (
 
308
            len(parsed.hunks) == len(final_hunks)):
 
309
            return 0
 
310
        if self.reporter.invert_diff and len(final_hunks) == 0:
227
311
            return 0
228
312
        patched = patches.iter_patched_from_hunks(target_lines, final_hunks)
229
313
        creator.shelve_lines(file_id, list(patched))
 
314
        if self.reporter.invert_diff:
 
315
            return len(final_hunks)
230
316
        return len(parsed.hunks) - len(final_hunks)
231
317
 
232
318
 
237
323
    def from_args(klass, shelf_id=None, action='apply', directory='.'):
238
324
        """Create an unshelver from commandline arguments.
239
325
 
 
326
        The returned shelver wil have a tree that is locked and should
 
327
        be unlocked.
 
328
 
240
329
        :param shelf_id: Integer id of the shelf, as a string.
241
330
        :param action: action to perform.  May be 'apply', 'dry-run',
242
331
            'delete'.
243
332
        :param directory: The directory to unshelve changes into.
244
333
        """
245
334
        tree, path = workingtree.WorkingTree.open_containing(directory)
246
 
        manager = tree.get_shelf_manager()
247
 
        if shelf_id is not None:
248
 
            try:
249
 
                shelf_id = int(shelf_id)
250
 
            except ValueError:
251
 
                raise errors.InvalidShelfId(shelf_id)
252
 
        else:
253
 
            shelf_id = manager.last_shelf()
254
 
            if shelf_id is None:
255
 
                raise errors.BzrCommandError('No changes are shelved.')
256
 
            trace.note('Unshelving changes with id "%d".' % shelf_id)
257
 
        apply_changes = True
258
 
        delete_shelf = True
259
 
        read_shelf = True
260
 
        if action == 'dry-run':
261
 
            apply_changes = False
262
 
            delete_shelf = False
263
 
        if action == 'delete-only':
264
 
            apply_changes = False
265
 
            read_shelf = False
 
335
        tree.lock_tree_write()
 
336
        try:
 
337
            manager = tree.get_shelf_manager()
 
338
            if shelf_id is not None:
 
339
                try:
 
340
                    shelf_id = int(shelf_id)
 
341
                except ValueError:
 
342
                    raise errors.InvalidShelfId(shelf_id)
 
343
            else:
 
344
                shelf_id = manager.last_shelf()
 
345
                if shelf_id is None:
 
346
                    raise errors.BzrCommandError('No changes are shelved.')
 
347
                trace.note('Unshelving changes with id "%d".' % shelf_id)
 
348
            apply_changes = True
 
349
            delete_shelf = True
 
350
            read_shelf = True
 
351
            if action == 'dry-run':
 
352
                apply_changes = False
 
353
                delete_shelf = False
 
354
            if action == 'delete-only':
 
355
                apply_changes = False
 
356
                read_shelf = False
 
357
        except:
 
358
            tree.unlock()
 
359
            raise
266
360
        return klass(tree, manager, shelf_id, apply_changes, delete_shelf,
267
361
                     read_shelf)
268
362
 
288
382
 
289
383
    def run(self):
290
384
        """Perform the unshelving operation."""
291
 
        self.tree.lock_write()
 
385
        self.tree.lock_tree_write()
292
386
        cleanups = [self.tree.unlock]
293
387
        try:
294
388
            if self.read_shelf: