/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_ui.py

  • Committer: Aaron Bentley
  • Date: 2009-07-12 14:03:30 UTC
  • mto: This revision was merged to the branch mainline in revision 4534.
  • Revision ID: aaron@aaronbentley.com-20090712140330-jltybz4e55neftpe
Update for changes in shelf_ui

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2008, 2009, 2010 Canonical Ltd
 
1
# Copyright (C) 2008 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
27
27
    errors,
28
28
    osutils,
29
29
    patches,
30
 
    patiencediff,
31
30
    shelf,
32
31
    textfile,
33
32
    trace,
36
35
)
37
36
 
38
37
 
39
 
class UseEditor(Exception):
40
 
    """Use an editor instead of selecting hunks."""
41
 
 
42
 
 
43
38
class ShelfReporter(object):
44
 
 
45
39
    vocab = {'add file': 'Shelve adding file "%(path)s"?',
46
40
             'binary': 'Shelve binary changes?',
47
41
             'change kind': 'Shelve changing "%s" from %(other)s'
61
55
        self.delta_reporter = delta._ChangeReporter()
62
56
 
63
57
    def no_changes(self):
64
 
        """Report that no changes were selected to apply."""
65
58
        trace.warning('No changes to shelve.')
66
59
 
67
60
    def shelved_id(self, shelf_id):
68
 
        """Report the id changes were shelved to."""
69
61
        trace.note('Changes shelved with id "%d".' % shelf_id)
70
62
 
71
63
    def changes_destroyed(self):
72
 
        """Report that changes were made without shelving."""
73
64
        trace.note('Selected changes destroyed.')
74
65
 
75
66
    def selected_changes(self, transform):
76
 
        """Report the changes that were selected."""
77
67
        trace.note("Selected changes:")
78
68
        changes = transform.iter_changes()
79
69
        delta.report_changes(changes, self.delta_reporter)
80
70
 
81
71
    def prompt_change(self, change):
82
 
        """Determine the prompt for a change to apply."""
83
72
        if change[0] == 'rename':
84
73
            vals = {'this': change[3], 'other': change[2]}
85
74
        elif change[0] == 'change kind':
130
119
        :param destroy: Change the working tree without storing the shelved
131
120
            changes.
132
121
        :param manager: The shelf manager to use.
133
 
        :param reporter: Object for reporting changes to user.
134
122
        """
135
123
        self.work_tree = work_tree
136
124
        self.target_tree = target_tree
148
136
        if reporter is None:
149
137
            reporter = ShelfReporter()
150
138
        self.reporter = reporter
151
 
        config = self.work_tree.branch.get_config()
152
 
        self.change_editor = config.get_change_editor(target_tree, work_tree)
153
 
        self.work_tree.lock_tree_write()
154
139
 
155
140
    @classmethod
156
141
    def from_args(klass, diff_writer, revision=None, all=False, file_list=None,
157
142
                  message=None, directory='.', destroy=False):
158
143
        """Create a shelver from commandline arguments.
159
144
 
160
 
        The returned shelver wil have a work_tree that is locked and should
161
 
        be unlocked.
162
 
 
163
145
        :param revision: RevisionSpec of the revision to compare to.
164
146
        :param all: If True, shelve all changes without prompting.
165
147
        :param file_list: If supplied, only files in this list may be  shelved.
169
151
            changes.
170
152
        """
171
153
        tree, path = workingtree.WorkingTree.open_containing(directory)
172
 
        # Ensure that tree is locked for the lifetime of target_tree, as
173
 
        # target tree may be reading from the same dirstate.
174
 
        tree.lock_tree_write()
175
 
        try:
176
 
            target_tree = builtins._get_one_revision_tree('shelf2', revision,
177
 
                tree.branch, tree)
178
 
            files = builtins.safe_relpath_files(tree, file_list)
179
 
            return klass(tree, target_tree, diff_writer, all, all, files,
180
 
                         message, destroy)
181
 
        finally:
182
 
            tree.unlock()
 
154
        target_tree = builtins._get_one_revision_tree('shelf2', revision,
 
155
            tree.branch, tree)
 
156
        files = builtins.safe_relpath_files(tree, file_list)
 
157
        return klass(tree, target_tree, diff_writer, all, all, files, message,
 
158
                     destroy)
183
159
 
184
160
    def run(self):
185
161
        """Interactively shelve the changes."""
218
194
            shutil.rmtree(self.tempdir)
219
195
            creator.finalize()
220
196
 
221
 
    def finalize(self):
222
 
        if self.change_editor is not None:
223
 
            self.change_editor.finish()
224
 
        self.work_tree.unlock()
225
 
 
226
 
 
227
197
    def get_parsed_patch(self, file_id, invert=False):
228
198
        """Return a parsed version of a file's patch.
229
199
 
230
200
        :param file_id: The id of the file to generate a patch for.
231
 
        :param invert: If True, provide an inverted patch (insertions displayed
232
 
            as removals, removals displayed as insertions).
233
201
        :return: A patches.Patch.
234
202
        """
235
203
        diff_file = StringIO()
252
220
        :param message: The message to prompt a user with.
253
221
        :return: A character.
254
222
        """
255
 
        if not sys.stdin.isatty():
256
 
            # Since there is no controlling terminal we will hang when trying
257
 
            # to prompt the user, better abort now.  See
258
 
            # https://code.launchpad.net/~bialix/bzr/shelve-no-tty/+merge/14905
259
 
            # for more context.
260
 
            raise errors.BzrError("You need a controlling terminal.")
261
223
        sys.stdout.write(message)
262
224
        char = osutils.getchar()
263
225
        sys.stdout.write("\r" + ' ' * len(message) + '\r')
264
226
        sys.stdout.flush()
265
227
        return char
266
228
 
267
 
    def prompt_bool(self, question, long=False, allow_editor=False):
 
229
    def prompt_bool(self, question, long=False):
268
230
        """Prompt the user with a yes/no question.
269
231
 
270
232
        This may be overridden by self.auto.  It may also *set* self.auto.  It
274
236
        """
275
237
        if self.auto:
276
238
            return True
277
 
        editor_string = ''
278
239
        if long:
279
 
            if allow_editor:
280
 
                editor_string = '(E)dit manually, '
281
 
            prompt = ' [(y)es, (N)o, %s(f)inish, or (q)uit]' % editor_string
 
240
            prompt = ' [(y)es, (N)o, (f)inish, or (q)uit]'
282
241
        else:
283
 
            if allow_editor:
284
 
                editor_string = 'e'
285
 
            prompt = ' [yN%sfq?]' % editor_string
 
242
            prompt = ' [yNfq?]'
286
243
        char = self.prompt(question + prompt)
287
244
        if char == 'y':
288
245
            return True
289
 
        elif char == 'e' and allow_editor:
290
 
            raise UseEditor
291
246
        elif char == 'f':
292
247
            self.auto = True
293
248
            return True
299
254
            return False
300
255
 
301
256
    def handle_modify_text(self, creator, file_id):
302
 
        """Handle modified text, by using hunk selection or file editing.
303
 
 
304
 
        :param creator: A ShelfCreator.
305
 
        :param file_id: The id of the file that was modified.
306
 
        :return: The number of changes.
307
 
        """
308
 
        work_tree_lines = self.work_tree.get_file_lines(file_id)
309
 
        try:
310
 
            lines, change_count = self._select_hunks(creator, file_id,
311
 
                                                     work_tree_lines)
312
 
        except UseEditor:
313
 
            lines, change_count = self._edit_file(file_id, work_tree_lines)
314
 
        if change_count != 0:
315
 
            creator.shelve_lines(file_id, lines)
316
 
        return change_count
317
 
 
318
 
    def _select_hunks(self, creator, file_id, work_tree_lines):
319
257
        """Provide diff hunk selection for modified text.
320
258
 
321
 
        If self.reporter.invert_diff is True, the diff is inverted so that
322
 
        insertions are displayed as removals and vice versa.
323
 
 
324
259
        :param creator: a ShelfCreator
325
260
        :param file_id: The id of the file to shelve.
326
 
        :param work_tree_lines: Line contents of the file in the working tree.
327
261
        :return: number of shelved hunks.
328
262
        """
329
263
        if self.reporter.invert_diff:
330
 
            target_lines = work_tree_lines
 
264
            target_lines = self.work_tree.get_file_lines(file_id)
331
265
        else:
332
266
            target_lines = self.target_tree.get_file_lines(file_id)
333
 
        textfile.check_text_lines(work_tree_lines)
 
267
        textfile.check_text_lines(self.work_tree.get_file_lines(file_id))
334
268
        textfile.check_text_lines(target_lines)
335
269
        parsed = self.get_parsed_patch(file_id, self.reporter.invert_diff)
336
270
        final_hunks = []
339
273
            self.diff_writer.write(parsed.get_header())
340
274
            for hunk in parsed.hunks:
341
275
                self.diff_writer.write(str(hunk))
342
 
                selected = self.prompt_bool(self.reporter.vocab['hunk'],
343
 
                                            allow_editor=(self.change_editor
344
 
                                                          is not None))
 
276
                selected = self.prompt_bool(self.reporter.vocab['hunk'])
345
277
                if not self.reporter.invert_diff:
346
278
                    selected = (not selected)
347
279
                if selected:
350
282
                else:
351
283
                    offset -= (hunk.mod_range - hunk.orig_range)
352
284
        sys.stdout.flush()
 
285
        if not self.reporter.invert_diff and (
 
286
            len(parsed.hunks) == len(final_hunks)):
 
287
            return 0
 
288
        if self.reporter.invert_diff and len(final_hunks) == 0:
 
289
            return 0
 
290
        patched = patches.iter_patched_from_hunks(target_lines, final_hunks)
 
291
        creator.shelve_lines(file_id, list(patched))
353
292
        if self.reporter.invert_diff:
354
 
            change_count = len(final_hunks)
355
 
        else:
356
 
            change_count = len(parsed.hunks) - len(final_hunks)
357
 
        patched = patches.iter_patched_from_hunks(target_lines,
358
 
                                                  final_hunks)
359
 
        lines = list(patched)
360
 
        return lines, change_count
361
 
 
362
 
    def _edit_file(self, file_id, work_tree_lines):
363
 
        """
364
 
        :param file_id: id of the file to edit.
365
 
        :param work_tree_lines: Line contents of the file in the working tree.
366
 
        :return: (lines, change_region_count), where lines is the new line
367
 
            content of the file, and change_region_count is the number of
368
 
            changed regions.
369
 
        """
370
 
        lines = osutils.split_lines(self.change_editor.edit_file(file_id))
371
 
        return lines, self._count_changed_regions(work_tree_lines, lines)
372
 
 
373
 
    @staticmethod
374
 
    def _count_changed_regions(old_lines, new_lines):
375
 
        matcher = patiencediff.PatienceSequenceMatcher(None, old_lines,
376
 
                                                       new_lines)
377
 
        blocks = matcher.get_matching_blocks()
378
 
        return len(blocks) - 2
 
293
            return len(final_hunks)
 
294
        return len(parsed.hunks) - len(final_hunks)
379
295
 
380
296
 
381
297
class Unshelver(object):
382
298
    """Unshelve changes into a working tree."""
383
299
 
384
300
    @classmethod
385
 
    def from_args(klass, shelf_id=None, action='apply', directory='.',
386
 
                  write_diff_to=None):
 
301
    def from_args(klass, shelf_id=None, action='apply', directory='.'):
387
302
        """Create an unshelver from commandline arguments.
388
303
 
389
 
        The returned shelver will have a tree that is locked and should
390
 
        be unlocked.
391
 
 
392
304
        :param shelf_id: Integer id of the shelf, as a string.
393
305
        :param action: action to perform.  May be 'apply', 'dry-run',
394
 
            'delete', 'preview'.
 
306
            'delete'.
395
307
        :param directory: The directory to unshelve changes into.
396
 
        :param write_diff_to: See Unshelver.__init__().
397
308
        """
398
309
        tree, path = workingtree.WorkingTree.open_containing(directory)
399
 
        tree.lock_tree_write()
400
 
        try:
401
 
            manager = tree.get_shelf_manager()
402
 
            if shelf_id is not None:
403
 
                try:
404
 
                    shelf_id = int(shelf_id)
405
 
                except ValueError:
406
 
                    raise errors.InvalidShelfId(shelf_id)
407
 
            else:
408
 
                shelf_id = manager.last_shelf()
409
 
                if shelf_id is None:
410
 
                    raise errors.BzrCommandError('No changes are shelved.')
411
 
            apply_changes = True
412
 
            delete_shelf = True
413
 
            read_shelf = True
414
 
            show_diff = False
415
 
            if action == 'dry-run':
416
 
                apply_changes = False
417
 
                delete_shelf = False
418
 
            elif action == 'preview':
419
 
                apply_changes = False
420
 
                delete_shelf = False
421
 
                show_diff = True
422
 
            elif action == 'delete-only':
423
 
                apply_changes = False
424
 
                read_shelf = False
425
 
            elif action == 'keep':
426
 
                apply_changes = True
427
 
                delete_shelf = False
428
 
        except:
429
 
            tree.unlock()
430
 
            raise
 
310
        manager = tree.get_shelf_manager()
 
311
        if shelf_id is not None:
 
312
            try:
 
313
                shelf_id = int(shelf_id)
 
314
            except ValueError:
 
315
                raise errors.InvalidShelfId(shelf_id)
 
316
        else:
 
317
            shelf_id = manager.last_shelf()
 
318
            if shelf_id is None:
 
319
                raise errors.BzrCommandError('No changes are shelved.')
 
320
            trace.note('Unshelving changes with id "%d".' % shelf_id)
 
321
        apply_changes = True
 
322
        delete_shelf = True
 
323
        read_shelf = True
 
324
        if action == 'dry-run':
 
325
            apply_changes = False
 
326
            delete_shelf = False
 
327
        if action == 'delete-only':
 
328
            apply_changes = False
 
329
            read_shelf = False
431
330
        return klass(tree, manager, shelf_id, apply_changes, delete_shelf,
432
 
                     read_shelf, show_diff, write_diff_to)
 
331
                     read_shelf)
433
332
 
434
333
    def __init__(self, tree, manager, shelf_id, apply_changes=True,
435
 
                 delete_shelf=True, read_shelf=True, show_diff=False,
436
 
                 write_diff_to=None):
 
334
                 delete_shelf=True, read_shelf=True):
437
335
        """Constructor.
438
336
 
439
337
        :param tree: The working tree to unshelve into.
443
341
            working tree.
444
342
        :param delete_shelf: If True, delete the changes from the shelf.
445
343
        :param read_shelf: If True, read the changes from the shelf.
446
 
        :param show_diff: If True, show the diff that would result from
447
 
            unshelving the changes.
448
 
        :param write_diff_to: A file-like object where the diff will be
449
 
            written to. If None, ui.ui_factory.make_output_stream() will
450
 
            be used.
451
344
        """
452
345
        self.tree = tree
453
346
        manager = tree.get_shelf_manager()
456
349
        self.apply_changes = apply_changes
457
350
        self.delete_shelf = delete_shelf
458
351
        self.read_shelf = read_shelf
459
 
        self.show_diff = show_diff
460
 
        self.write_diff_to = write_diff_to
461
352
 
462
353
    def run(self):
463
354
        """Perform the unshelving operation."""
464
 
        self.tree.lock_tree_write()
 
355
        self.tree.lock_write()
465
356
        cleanups = [self.tree.unlock]
466
357
        try:
467
358
            if self.read_shelf:
468
 
                trace.note('Using changes with id "%d".' % self.shelf_id)
469
359
                unshelver = self.manager.get_unshelver(self.shelf_id)
470
360
                cleanups.append(unshelver.finalize)
471
361
                if unshelver.message is not None:
472
362
                    trace.note('Message: %s' % unshelver.message)
473
363
                change_reporter = delta._ChangeReporter()
474
 
                merger = unshelver.make_merger(None)
475
 
                merger.change_reporter = change_reporter
476
 
                if self.apply_changes:
477
 
                    merger.do_merge()
478
 
                elif self.show_diff:
479
 
                    self.write_diff(merger)
480
 
                else:
481
 
                    self.show_changes(merger)
 
364
                task = ui.ui_factory.nested_progress_bar()
 
365
                try:
 
366
                    merger = unshelver.make_merger(task)
 
367
                    merger.change_reporter = change_reporter
 
368
                    if self.apply_changes:
 
369
                        merger.do_merge()
 
370
                    else:
 
371
                        self.show_changes(merger)
 
372
                finally:
 
373
                    task.finished()
482
374
            if self.delete_shelf:
483
375
                self.manager.delete_shelf(self.shelf_id)
484
 
                trace.note('Deleted changes with id "%d".' % self.shelf_id)
485
376
        finally:
486
377
            for cleanup in reversed(cleanups):
487
378
                cleanup()
488
379
 
489
 
    def write_diff(self, merger):
490
 
        """Write this operation's diff to self.write_diff_to."""
491
 
        tree_merger = merger.make_merger()
492
 
        tt = tree_merger.make_preview_transform()
493
 
        new_tree = tt.get_preview_tree()
494
 
        if self.write_diff_to is None:
495
 
            self.write_diff_to = ui.ui_factory.make_output_stream()
496
 
        diff.show_diff_trees(merger.this_tree, new_tree, self.write_diff_to)
497
 
        tt.finalize()
498
 
 
499
380
    def show_changes(self, merger):
500
381
        """Show the changes that this operation specifies."""
501
382
        tree_merger = merger.make_merger()