~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/shelf_ui.py

  • Committer: Joe Julian
  • Date: 2010-01-10 02:25:31 UTC
  • mto: (4634.119.7 2.0)
  • mto: This revision was merged to the branch mainline in revision 4959.
  • Revision ID: joe@julianfamily.org-20100110022531-wqk61rsagz8xsiga
Added MANIFEST.in to allow bdist_rpm to have all the required include files and tools. bdist_rpm will still fail to build correctly on some distributions due to a disttools bug http://bugs.python.org/issue644744

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
39
 
45
40
    vocab = {'add file': 'Shelve adding file "%(path)s"?',
148
143
        if reporter is None:
149
144
            reporter = ShelfReporter()
150
145
        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
146
 
155
147
    @classmethod
156
148
    def from_args(klass, diff_writer, revision=None, all=False, file_list=None,
176
168
            target_tree = builtins._get_one_revision_tree('shelf2', revision,
177
169
                tree.branch, tree)
178
170
            files = builtins.safe_relpath_files(tree, file_list)
179
 
            return klass(tree, target_tree, diff_writer, all, all, files,
180
 
                         message, destroy)
181
 
        finally:
 
171
        except:
182
172
            tree.unlock()
 
173
            raise
 
174
        return klass(tree, target_tree, diff_writer, all, all, files, message,
 
175
                     destroy)
183
176
 
184
177
    def run(self):
185
178
        """Interactively shelve the changes."""
218
211
            shutil.rmtree(self.tempdir)
219
212
            creator.finalize()
220
213
 
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
214
    def get_parsed_patch(self, file_id, invert=False):
228
215
        """Return a parsed version of a file's patch.
229
216
 
252
239
        :param message: The message to prompt a user with.
253
240
        :return: A character.
254
241
        """
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
242
        sys.stdout.write(message)
262
243
        char = osutils.getchar()
263
244
        sys.stdout.write("\r" + ' ' * len(message) + '\r')
264
245
        sys.stdout.flush()
265
246
        return char
266
247
 
267
 
    def prompt_bool(self, question, long=False, allow_editor=False):
 
248
    def prompt_bool(self, question, long=False):
268
249
        """Prompt the user with a yes/no question.
269
250
 
270
251
        This may be overridden by self.auto.  It may also *set* self.auto.  It
274
255
        """
275
256
        if self.auto:
276
257
            return True
277
 
        editor_string = ''
278
258
        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
 
259
            prompt = ' [(y)es, (N)o, (f)inish, or (q)uit]'
282
260
        else:
283
 
            if allow_editor:
284
 
                editor_string = 'e'
285
 
            prompt = ' [yN%sfq?]' % editor_string
 
261
            prompt = ' [yNfq?]'
286
262
        char = self.prompt(question + prompt)
287
263
        if char == 'y':
288
264
            return True
289
 
        elif char == 'e' and allow_editor:
290
 
            raise UseEditor
291
265
        elif char == 'f':
292
266
            self.auto = True
293
267
            return True
299
273
            return False
300
274
 
301
275
    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
276
        """Provide diff hunk selection for modified text.
320
277
 
321
278
        If self.reporter.invert_diff is True, the diff is inverted so that
323
280
 
324
281
        :param creator: a ShelfCreator
325
282
        :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
283
        :return: number of shelved hunks.
328
284
        """
329
285
        if self.reporter.invert_diff:
330
 
            target_lines = work_tree_lines
 
286
            target_lines = self.work_tree.get_file_lines(file_id)
331
287
        else:
332
288
            target_lines = self.target_tree.get_file_lines(file_id)
333
 
        textfile.check_text_lines(work_tree_lines)
 
289
        textfile.check_text_lines(self.work_tree.get_file_lines(file_id))
334
290
        textfile.check_text_lines(target_lines)
335
291
        parsed = self.get_parsed_patch(file_id, self.reporter.invert_diff)
336
292
        final_hunks = []
339
295
            self.diff_writer.write(parsed.get_header())
340
296
            for hunk in parsed.hunks:
341
297
                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))
 
298
                selected = self.prompt_bool(self.reporter.vocab['hunk'])
345
299
                if not self.reporter.invert_diff:
346
300
                    selected = (not selected)
347
301
                if selected:
350
304
                else:
351
305
                    offset -= (hunk.mod_range - hunk.orig_range)
352
306
        sys.stdout.flush()
 
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:
 
311
            return 0
 
312
        patched = patches.iter_patched_from_hunks(target_lines, final_hunks)
 
313
        creator.shelve_lines(file_id, list(patched))
353
314
        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
 
315
            return len(final_hunks)
 
316
        return len(parsed.hunks) - len(final_hunks)
379
317
 
380
318
 
381
319
class Unshelver(object):
382
320
    """Unshelve changes into a working tree."""
383
321
 
384
322
    @classmethod
385
 
    def from_args(klass, shelf_id=None, action='apply', directory='.',
386
 
                  write_diff_to=None):
 
323
    def from_args(klass, shelf_id=None, action='apply', directory='.'):
387
324
        """Create an unshelver from commandline arguments.
388
325
 
389
 
        The returned shelver will have a tree that is locked and should
 
326
        The returned shelver wil have a tree that is locked and should
390
327
        be unlocked.
391
328
 
392
329
        :param shelf_id: Integer id of the shelf, as a string.
393
330
        :param action: action to perform.  May be 'apply', 'dry-run',
394
 
            'delete', 'preview'.
 
331
            'delete'.
395
332
        :param directory: The directory to unshelve changes into.
396
 
        :param write_diff_to: See Unshelver.__init__().
397
333
        """
398
334
        tree, path = workingtree.WorkingTree.open_containing(directory)
399
335
        tree.lock_tree_write()
408
344
                shelf_id = manager.last_shelf()
409
345
                if shelf_id is None:
410
346
                    raise errors.BzrCommandError('No changes are shelved.')
 
347
                trace.note('Unshelving changes with id "%d".' % shelf_id)
411
348
            apply_changes = True
412
349
            delete_shelf = True
413
350
            read_shelf = True
414
 
            show_diff = False
415
351
            if action == 'dry-run':
416
352
                apply_changes = False
417
353
                delete_shelf = False
418
 
            elif action == 'preview':
419
 
                apply_changes = False
420
 
                delete_shelf = False
421
 
                show_diff = True
422
 
            elif action == 'delete-only':
 
354
            if action == 'delete-only':
423
355
                apply_changes = False
424
356
                read_shelf = False
425
 
            elif action == 'keep':
426
 
                apply_changes = True
427
 
                delete_shelf = False
428
357
        except:
429
358
            tree.unlock()
430
359
            raise
431
360
        return klass(tree, manager, shelf_id, apply_changes, delete_shelf,
432
 
                     read_shelf, show_diff, write_diff_to)
 
361
                     read_shelf)
433
362
 
434
363
    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):
 
364
                 delete_shelf=True, read_shelf=True):
437
365
        """Constructor.
438
366
 
439
367
        :param tree: The working tree to unshelve into.
443
371
            working tree.
444
372
        :param delete_shelf: If True, delete the changes from the shelf.
445
373
        :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
374
        """
452
375
        self.tree = tree
453
376
        manager = tree.get_shelf_manager()
456
379
        self.apply_changes = apply_changes
457
380
        self.delete_shelf = delete_shelf
458
381
        self.read_shelf = read_shelf
459
 
        self.show_diff = show_diff
460
 
        self.write_diff_to = write_diff_to
461
382
 
462
383
    def run(self):
463
384
        """Perform the unshelving operation."""
465
386
        cleanups = [self.tree.unlock]
466
387
        try:
467
388
            if self.read_shelf:
468
 
                trace.note('Using changes with id "%d".' % self.shelf_id)
469
389
                unshelver = self.manager.get_unshelver(self.shelf_id)
470
390
                cleanups.append(unshelver.finalize)
471
391
                if unshelver.message is not None:
472
392
                    trace.note('Message: %s' % unshelver.message)
473
393
                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)
 
394
                task = ui.ui_factory.nested_progress_bar()
 
395
                try:
 
396
                    merger = unshelver.make_merger(task)
 
397
                    merger.change_reporter = change_reporter
 
398
                    if self.apply_changes:
 
399
                        merger.do_merge()
 
400
                    else:
 
401
                        self.show_changes(merger)
 
402
                finally:
 
403
                    task.finished()
482
404
            if self.delete_shelf:
483
405
                self.manager.delete_shelf(self.shelf_id)
484
 
                trace.note('Deleted changes with id "%d".' % self.shelf_id)
485
406
        finally:
486
407
            for cleanup in reversed(cleanups):
487
408
                cleanup()
488
409
 
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
410
    def show_changes(self, merger):
500
411
        """Show the changes that this operation specifies."""
501
412
        tree_merger = merger.make_merger()