/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: John Ferlito
  • Date: 2009-09-02 04:31:45 UTC
  • mto: (4665.7.1 serve-init)
  • mto: This revision was merged to the branch mainline in revision 4913.
  • Revision ID: johnf@inodes.org-20090902043145-gxdsfw03ilcwbyn5
Add a debian init script for bzr --serve

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
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
import contextlib
18
 
import patiencediff
 
17
 
 
18
from cStringIO import StringIO
19
19
import shutil
20
20
import sys
21
21
import tempfile
22
22
 
23
 
from io import BytesIO
24
 
 
25
 
from . import (
 
23
from bzrlib import (
26
24
    builtins,
27
25
    delta,
28
26
    diff,
35
33
    ui,
36
34
    workingtree,
37
35
)
38
 
from .i18n import gettext
39
 
 
40
 
 
41
 
class UseEditor(Exception):
42
 
    """Use an editor instead of selecting hunks."""
43
36
 
44
37
 
45
38
class ShelfReporter(object):
46
39
 
47
 
    vocab = {'add file': gettext('Shelve adding file "%(path)s"?'),
48
 
             'binary': gettext('Shelve binary changes?'),
49
 
             'change kind': gettext('Shelve changing "%s" from %(other)s'
50
 
                                    ' to %(this)s?'),
51
 
             'delete file': gettext('Shelve removing file "%(path)s"?'),
52
 
             'final': gettext('Shelve %d change(s)?'),
53
 
             'hunk': gettext('Shelve?'),
54
 
             'modify target': gettext('Shelve changing target of'
55
 
                                      ' "%(path)s" from "%(other)s" to "%(this)s"?'),
56
 
             'rename': gettext('Shelve renaming "%(other)s" =>'
57
 
                               ' "%(this)s"?')
 
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"?'
58
51
             }
59
52
 
60
53
    invert_diff = False
68
61
 
69
62
    def shelved_id(self, shelf_id):
70
63
        """Report the id changes were shelved to."""
71
 
        trace.note(gettext('Changes shelved with id "%d".') % shelf_id)
 
64
        trace.note('Changes shelved with id "%d".' % shelf_id)
72
65
 
73
66
    def changes_destroyed(self):
74
67
        """Report that changes were made without shelving."""
75
 
        trace.note(gettext('Selected changes destroyed.'))
 
68
        trace.note('Selected changes destroyed.')
76
69
 
77
70
    def selected_changes(self, transform):
78
71
        """Report the changes that were selected."""
79
 
        trace.note(gettext("Selected changes:"))
 
72
        trace.note("Selected changes:")
80
73
        changes = transform.iter_changes()
81
74
        delta.report_changes(changes, self.delta_reporter)
82
75
 
96
89
 
97
90
class ApplyReporter(ShelfReporter):
98
91
 
99
 
    vocab = {'add file': gettext('Delete file "%(path)s"?'),
100
 
             'binary': gettext('Apply binary changes?'),
101
 
             'change kind': gettext('Change "%(path)s" from %(this)s'
102
 
                                    ' to %(other)s?'),
103
 
             'delete file': gettext('Add file "%(path)s"?'),
104
 
             'final': gettext('Apply %d change(s)?'),
105
 
             'hunk': gettext('Apply change?'),
106
 
             'modify target': gettext('Change target of'
107
 
                                      ' "%(path)s" from "%(this)s" to "%(other)s"?'),
108
 
             'rename': gettext('Rename "%(this)s" => "%(other)s"?'),
 
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"?',
109
102
             }
110
103
 
111
104
    invert_diff = True
150
143
        if reporter is None:
151
144
            reporter = ShelfReporter()
152
145
        self.reporter = reporter
153
 
        config = self.work_tree.branch.get_config()
154
 
        self.change_editor = config.get_change_editor(target_tree, work_tree)
155
 
        self.work_tree.lock_tree_write()
156
146
 
157
147
    @classmethod
158
148
    def from_args(klass, diff_writer, revision=None, all=False, file_list=None,
159
 
                  message=None, directory=None, destroy=False):
 
149
                  message=None, directory='.', destroy=False):
160
150
        """Create a shelver from commandline arguments.
161
151
 
162
152
        The returned shelver wil have a work_tree that is locked and should
170
160
        :param destroy: Change the working tree without storing the shelved
171
161
            changes.
172
162
        """
173
 
        if directory is None:
174
 
            directory = u'.'
175
 
        elif file_list:
176
 
            file_list = [osutils.pathjoin(directory, f) for f in file_list]
177
163
        tree, path = workingtree.WorkingTree.open_containing(directory)
178
164
        # Ensure that tree is locked for the lifetime of target_tree, as
179
165
        # target tree may be reading from the same dirstate.
180
 
        with tree.lock_tree_write():
 
166
        tree.lock_tree_write()
 
167
        try:
181
168
            target_tree = builtins._get_one_revision_tree('shelf2', revision,
182
 
                                                          tree.branch, tree)
183
 
            files = tree.safe_relpath_files(file_list)
184
 
            return klass(tree, target_tree, diff_writer, all, all, files,
185
 
                         message, destroy)
 
169
                tree.branch, tree)
 
170
            files = builtins.safe_relpath_files(tree, file_list)
 
171
        except:
 
172
            tree.unlock()
 
173
            raise
 
174
        return klass(tree, target_tree, diff_writer, all, all, files, message,
 
175
                     destroy)
186
176
 
187
177
    def run(self):
188
178
        """Interactively shelve the changes."""
207
197
            if changes_shelved > 0:
208
198
                self.reporter.selected_changes(creator.work_transform)
209
199
                if (self.auto_apply or self.prompt_bool(
210
 
                        self.reporter.vocab['final'] % changes_shelved)):
 
200
                    self.reporter.vocab['final'] % changes_shelved)):
211
201
                    if self.destroy:
212
202
                        creator.transform()
213
203
                        self.reporter.changes_destroyed()
221
211
            shutil.rmtree(self.tempdir)
222
212
            creator.finalize()
223
213
 
224
 
    def finalize(self):
225
 
        if self.change_editor is not None:
226
 
            self.change_editor.finish()
227
 
        self.work_tree.unlock()
228
 
 
229
214
    def get_parsed_patch(self, file_id, invert=False):
230
215
        """Return a parsed version of a file's patch.
231
216
 
234
219
            as removals, removals displayed as insertions).
235
220
        :return: A patches.Patch.
236
221
        """
237
 
        diff_file = BytesIO()
 
222
        diff_file = StringIO()
238
223
        if invert:
239
224
            old_tree = self.work_tree
240
225
            new_tree = self.target_tree
243
228
            new_tree = self.work_tree
244
229
        old_path = old_tree.id2path(file_id)
245
230
        new_path = new_tree.id2path(file_id)
246
 
        path_encoding = osutils.get_terminal_encoding()
247
 
        text_differ = diff.DiffText(old_tree, new_tree, diff_file,
248
 
                                    path_encoding=path_encoding)
249
 
        patch = text_differ.diff(old_path, new_path, 'file', 'file')
 
231
        text_differ = diff.DiffText(old_tree, new_tree, diff_file)
 
232
        patch = text_differ.diff(file_id, old_path, new_path, 'file', 'file')
250
233
        diff_file.seek(0)
251
234
        return patches.parse_patch(diff_file)
252
235
 
253
 
    def prompt(self, message, choices, default):
254
 
        return ui.ui_factory.choose(message, choices, default=default)
255
 
 
256
 
    def prompt_bool(self, question, allow_editor=False):
 
236
    def prompt(self, message):
 
237
        """Prompt the user for a character.
 
238
 
 
239
        :param message: The message to prompt a user with.
 
240
        :return: A character.
 
241
        """
 
242
        sys.stdout.write(message)
 
243
        char = osutils.getchar()
 
244
        sys.stdout.write("\r" + ' ' * len(message) + '\r')
 
245
        sys.stdout.flush()
 
246
        return char
 
247
 
 
248
    def prompt_bool(self, question, long=False):
257
249
        """Prompt the user with a yes/no question.
258
250
 
259
251
        This may be overridden by self.auto.  It may also *set* self.auto.  It
263
255
        """
264
256
        if self.auto:
265
257
            return True
266
 
        alternatives_chars = 'yn'
267
 
        alternatives = '&yes\n&No'
268
 
        if allow_editor:
269
 
            alternatives_chars += 'e'
270
 
            alternatives += '\n&edit manually'
271
 
        alternatives_chars += 'fq'
272
 
        alternatives += '\n&finish\n&quit'
273
 
        choice = self.prompt(question, alternatives, 1)
274
 
        if choice is None:
275
 
            # EOF.
276
 
            char = 'n'
 
258
        if long:
 
259
            prompt = ' [(y)es, (N)o, (f)inish, or (q)uit]'
277
260
        else:
278
 
            char = alternatives_chars[choice]
 
261
            prompt = ' [yNfq?]'
 
262
        char = self.prompt(question + prompt)
279
263
        if char == 'y':
280
264
            return True
281
 
        elif char == 'e' and allow_editor:
282
 
            raise UseEditor
283
265
        elif char == 'f':
284
266
            self.auto = True
285
267
            return True
 
268
        elif char == '?':
 
269
            return self.prompt_bool(question, long=True)
286
270
        if char == 'q':
287
271
            raise errors.UserAbort()
288
272
        else:
289
273
            return False
290
274
 
291
275
    def handle_modify_text(self, creator, file_id):
292
 
        """Handle modified text, by using hunk selection or file editing.
293
 
 
294
 
        :param creator: A ShelfCreator.
295
 
        :param file_id: The id of the file that was modified.
296
 
        :return: The number of changes.
297
 
        """
298
 
        path = self.work_tree.id2path(file_id)
299
 
        work_tree_lines = self.work_tree.get_file_lines(path, file_id)
300
 
        try:
301
 
            lines, change_count = self._select_hunks(creator, file_id,
302
 
                                                     work_tree_lines)
303
 
        except UseEditor:
304
 
            lines, change_count = self._edit_file(file_id, work_tree_lines)
305
 
        if change_count != 0:
306
 
            creator.shelve_lines(file_id, lines)
307
 
        return change_count
308
 
 
309
 
    def _select_hunks(self, creator, file_id, work_tree_lines):
310
276
        """Provide diff hunk selection for modified text.
311
277
 
312
278
        If self.reporter.invert_diff is True, the diff is inverted so that
314
280
 
315
281
        :param creator: a ShelfCreator
316
282
        :param file_id: The id of the file to shelve.
317
 
        :param work_tree_lines: Line contents of the file in the working tree.
318
283
        :return: number of shelved hunks.
319
284
        """
320
285
        if self.reporter.invert_diff:
321
 
            target_lines = work_tree_lines
 
286
            target_lines = self.work_tree.get_file_lines(file_id)
322
287
        else:
323
 
            path = self.target_tree.id2path(file_id)
324
 
            target_lines = self.target_tree.get_file_lines(path)
325
 
        textfile.check_text_lines(work_tree_lines)
 
288
            target_lines = self.target_tree.get_file_lines(file_id)
 
289
        textfile.check_text_lines(self.work_tree.get_file_lines(file_id))
326
290
        textfile.check_text_lines(target_lines)
327
291
        parsed = self.get_parsed_patch(file_id, self.reporter.invert_diff)
328
292
        final_hunks = []
330
294
            offset = 0
331
295
            self.diff_writer.write(parsed.get_header())
332
296
            for hunk in parsed.hunks:
333
 
                self.diff_writer.write(hunk.as_bytes())
334
 
                selected = self.prompt_bool(self.reporter.vocab['hunk'],
335
 
                                            allow_editor=(self.change_editor
336
 
                                                          is not None))
 
297
                self.diff_writer.write(str(hunk))
 
298
                selected = self.prompt_bool(self.reporter.vocab['hunk'])
337
299
                if not self.reporter.invert_diff:
338
300
                    selected = (not selected)
339
301
                if selected:
342
304
                else:
343
305
                    offset -= (hunk.mod_range - hunk.orig_range)
344
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))
345
314
        if self.reporter.invert_diff:
346
 
            change_count = len(final_hunks)
347
 
        else:
348
 
            change_count = len(parsed.hunks) - len(final_hunks)
349
 
        patched = patches.iter_patched_from_hunks(target_lines,
350
 
                                                  final_hunks)
351
 
        lines = list(patched)
352
 
        return lines, change_count
353
 
 
354
 
    def _edit_file(self, file_id, work_tree_lines):
355
 
        """
356
 
        :param file_id: id of the file to edit.
357
 
        :param work_tree_lines: Line contents of the file in the working tree.
358
 
        :return: (lines, change_region_count), where lines is the new line
359
 
            content of the file, and change_region_count is the number of
360
 
            changed regions.
361
 
        """
362
 
        lines = osutils.split_lines(self.change_editor.edit_file(
363
 
            self.change_editor.old_tree.id2path(file_id),
364
 
            self.change_editor.new_tree.id2path(file_id)))
365
 
        return lines, self._count_changed_regions(work_tree_lines, lines)
366
 
 
367
 
    @staticmethod
368
 
    def _count_changed_regions(old_lines, new_lines):
369
 
        matcher = patiencediff.PatienceSequenceMatcher(None, old_lines,
370
 
                                                       new_lines)
371
 
        blocks = matcher.get_matching_blocks()
372
 
        return len(blocks) - 2
 
315
            return len(final_hunks)
 
316
        return len(parsed.hunks) - len(final_hunks)
373
317
 
374
318
 
375
319
class Unshelver(object):
376
320
    """Unshelve changes into a working tree."""
377
321
 
378
322
    @classmethod
379
 
    def from_args(klass, shelf_id=None, action='apply', directory='.',
380
 
                  write_diff_to=None):
 
323
    def from_args(klass, shelf_id=None, action='apply', directory='.'):
381
324
        """Create an unshelver from commandline arguments.
382
325
 
383
 
        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
384
327
        be unlocked.
385
328
 
386
329
        :param shelf_id: Integer id of the shelf, as a string.
387
330
        :param action: action to perform.  May be 'apply', 'dry-run',
388
 
            'delete', 'preview'.
 
331
            'delete'.
389
332
        :param directory: The directory to unshelve changes into.
390
 
        :param write_diff_to: See Unshelver.__init__().
391
333
        """
392
334
        tree, path = workingtree.WorkingTree.open_containing(directory)
393
335
        tree.lock_tree_write()
397
339
                try:
398
340
                    shelf_id = int(shelf_id)
399
341
                except ValueError:
400
 
                    raise shelf.InvalidShelfId(shelf_id)
 
342
                    raise errors.InvalidShelfId(shelf_id)
401
343
            else:
402
344
                shelf_id = manager.last_shelf()
403
345
                if shelf_id is None:
404
 
                    raise errors.CommandError(
405
 
                        gettext('No changes are shelved.'))
 
346
                    raise errors.BzrCommandError('No changes are shelved.')
 
347
                trace.note('Unshelving changes with id "%d".' % shelf_id)
406
348
            apply_changes = True
407
349
            delete_shelf = True
408
350
            read_shelf = True
409
 
            show_diff = False
410
351
            if action == 'dry-run':
411
352
                apply_changes = False
412
353
                delete_shelf = False
413
 
            elif action == 'preview':
414
 
                apply_changes = False
415
 
                delete_shelf = False
416
 
                show_diff = True
417
 
            elif action == 'delete-only':
 
354
            if action == 'delete-only':
418
355
                apply_changes = False
419
356
                read_shelf = False
420
 
            elif action == 'keep':
421
 
                apply_changes = True
422
 
                delete_shelf = False
423
357
        except:
424
358
            tree.unlock()
425
359
            raise
426
360
        return klass(tree, manager, shelf_id, apply_changes, delete_shelf,
427
 
                     read_shelf, show_diff, write_diff_to)
 
361
                     read_shelf)
428
362
 
429
363
    def __init__(self, tree, manager, shelf_id, apply_changes=True,
430
 
                 delete_shelf=True, read_shelf=True, show_diff=False,
431
 
                 write_diff_to=None):
 
364
                 delete_shelf=True, read_shelf=True):
432
365
        """Constructor.
433
366
 
434
367
        :param tree: The working tree to unshelve into.
438
371
            working tree.
439
372
        :param delete_shelf: If True, delete the changes from the shelf.
440
373
        :param read_shelf: If True, read the changes from the shelf.
441
 
        :param show_diff: If True, show the diff that would result from
442
 
            unshelving the changes.
443
 
        :param write_diff_to: A file-like object where the diff will be
444
 
            written to. If None, ui.ui_factory.make_output_stream() will
445
 
            be used.
446
374
        """
447
375
        self.tree = tree
448
376
        manager = tree.get_shelf_manager()
451
379
        self.apply_changes = apply_changes
452
380
        self.delete_shelf = delete_shelf
453
381
        self.read_shelf = read_shelf
454
 
        self.show_diff = show_diff
455
 
        self.write_diff_to = write_diff_to
456
382
 
457
383
    def run(self):
458
384
        """Perform the unshelving operation."""
459
 
        with contextlib.ExitStack() as exit_stack:
460
 
            exit_stack.enter_context(self.tree.lock_tree_write())
 
385
        self.tree.lock_tree_write()
 
386
        cleanups = [self.tree.unlock]
 
387
        try:
461
388
            if self.read_shelf:
462
 
                trace.note(gettext('Using changes with id "%d".') %
463
 
                           self.shelf_id)
464
389
                unshelver = self.manager.get_unshelver(self.shelf_id)
465
 
                exit_stack.callback(unshelver.finalize)
 
390
                cleanups.append(unshelver.finalize)
466
391
                if unshelver.message is not None:
467
 
                    trace.note(gettext('Message: %s') % unshelver.message)
 
392
                    trace.note('Message: %s' % unshelver.message)
468
393
                change_reporter = delta._ChangeReporter()
469
 
                merger = unshelver.make_merger()
470
 
                merger.change_reporter = change_reporter
471
 
                if self.apply_changes:
472
 
                    merger.do_merge()
473
 
                elif self.show_diff:
474
 
                    self.write_diff(merger)
475
 
                else:
476
 
                    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()
477
404
            if self.delete_shelf:
478
405
                self.manager.delete_shelf(self.shelf_id)
479
 
                trace.note(gettext('Deleted changes with id "%d".') %
480
 
                           self.shelf_id)
481
 
 
482
 
    def write_diff(self, merger):
483
 
        """Write this operation's diff to self.write_diff_to."""
484
 
        tree_merger = merger.make_merger()
485
 
        tt = tree_merger.make_preview_transform()
486
 
        new_tree = tt.get_preview_tree()
487
 
        if self.write_diff_to is None:
488
 
            self.write_diff_to = ui.ui_factory.make_output_stream(
489
 
                encoding_type='exact')
490
 
        path_encoding = osutils.get_diff_header_encoding()
491
 
        diff.show_diff_trees(merger.this_tree, new_tree, self.write_diff_to,
492
 
                             path_encoding=path_encoding)
493
 
        tt.finalize()
 
406
        finally:
 
407
            for cleanup in reversed(cleanups):
 
408
                cleanup()
494
409
 
495
410
    def show_changes(self, merger):
496
411
        """Show the changes that this operation specifies."""