/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: Vincent Ladeuil
  • Date: 2010-07-07 11:21:19 UTC
  • mto: (5193.7.1 unify-confs)
  • mto: This revision was merged to the branch mainline in revision 5349.
  • Revision ID: v.ladeuil+lp@free.fr-20100707112119-jwyh312df41w6l0o
Revert previous change as I can't reproduce the related problem anymore.

Show diffs side-by-side

added added

removed removed

Lines of Context:
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,
29
27
    errors,
30
28
    osutils,
31
29
    patches,
 
30
    patiencediff,
32
31
    shelf,
33
32
    textfile,
34
33
    trace,
35
34
    ui,
36
35
    workingtree,
37
36
)
38
 
from .i18n import gettext
39
37
 
40
38
 
41
39
class UseEditor(Exception):
44
42
 
45
43
class ShelfReporter(object):
46
44
 
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"?')
 
45
    vocab = {'add file': 'Shelve adding file "%(path)s"?',
 
46
             'binary': 'Shelve binary changes?',
 
47
             'change kind': 'Shelve changing "%s" from %(other)s'
 
48
             ' to %(this)s?',
 
49
             'delete file': 'Shelve removing file "%(path)s"?',
 
50
             'final': 'Shelve %d change(s)?',
 
51
             'hunk': 'Shelve?',
 
52
             'modify target': 'Shelve changing target of'
 
53
             ' "%(path)s" from "%(other)s" to "%(this)s"?',
 
54
             'rename': 'Shelve renaming "%(other)s" =>'
 
55
                        ' "%(this)s"?'
58
56
             }
59
57
 
60
58
    invert_diff = False
68
66
 
69
67
    def shelved_id(self, shelf_id):
70
68
        """Report the id changes were shelved to."""
71
 
        trace.note(gettext('Changes shelved with id "%d".') % shelf_id)
 
69
        trace.note('Changes shelved with id "%d".' % shelf_id)
72
70
 
73
71
    def changes_destroyed(self):
74
72
        """Report that changes were made without shelving."""
75
 
        trace.note(gettext('Selected changes destroyed.'))
 
73
        trace.note('Selected changes destroyed.')
76
74
 
77
75
    def selected_changes(self, transform):
78
76
        """Report the changes that were selected."""
79
 
        trace.note(gettext("Selected changes:"))
 
77
        trace.note("Selected changes:")
80
78
        changes = transform.iter_changes()
81
79
        delta.report_changes(changes, self.delta_reporter)
82
80
 
96
94
 
97
95
class ApplyReporter(ShelfReporter):
98
96
 
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"?'),
 
97
    vocab = {'add file': 'Delete file "%(path)s"?',
 
98
             'binary': 'Apply binary changes?',
 
99
             'change kind': 'Change "%(path)s" from %(this)s'
 
100
             ' to %(other)s?',
 
101
             'delete file': 'Add file "%(path)s"?',
 
102
             'final': 'Apply %d change(s)?',
 
103
             'hunk': 'Apply change?',
 
104
             'modify target': 'Change target of'
 
105
             ' "%(path)s" from "%(this)s" to "%(other)s"?',
 
106
             'rename': 'Rename "%(this)s" => "%(other)s"?',
109
107
             }
110
108
 
111
109
    invert_diff = True
156
154
 
157
155
    @classmethod
158
156
    def from_args(klass, diff_writer, revision=None, all=False, file_list=None,
159
 
                  message=None, directory=None, destroy=False):
 
157
                  message=None, directory='.', destroy=False):
160
158
        """Create a shelver from commandline arguments.
161
159
 
162
160
        The returned shelver wil have a work_tree that is locked and should
170
168
        :param destroy: Change the working tree without storing the shelved
171
169
            changes.
172
170
        """
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
171
        tree, path = workingtree.WorkingTree.open_containing(directory)
178
172
        # Ensure that tree is locked for the lifetime of target_tree, as
179
173
        # target tree may be reading from the same dirstate.
180
 
        with tree.lock_tree_write():
 
174
        tree.lock_tree_write()
 
175
        try:
181
176
            target_tree = builtins._get_one_revision_tree('shelf2', revision,
182
 
                                                          tree.branch, tree)
183
 
            files = tree.safe_relpath_files(file_list)
 
177
                tree.branch, tree)
 
178
            files = builtins.safe_relpath_files(tree, file_list)
184
179
            return klass(tree, target_tree, diff_writer, all, all, files,
185
180
                         message, destroy)
 
181
        finally:
 
182
            tree.unlock()
186
183
 
187
184
    def run(self):
188
185
        """Interactively shelve the changes."""
207
204
            if changes_shelved > 0:
208
205
                self.reporter.selected_changes(creator.work_transform)
209
206
                if (self.auto_apply or self.prompt_bool(
210
 
                        self.reporter.vocab['final'] % changes_shelved)):
 
207
                    self.reporter.vocab['final'] % changes_shelved)):
211
208
                    if self.destroy:
212
209
                        creator.transform()
213
210
                        self.reporter.changes_destroyed()
226
223
            self.change_editor.finish()
227
224
        self.work_tree.unlock()
228
225
 
 
226
 
229
227
    def get_parsed_patch(self, file_id, invert=False):
230
228
        """Return a parsed version of a file's patch.
231
229
 
234
232
            as removals, removals displayed as insertions).
235
233
        :return: A patches.Patch.
236
234
        """
237
 
        diff_file = BytesIO()
 
235
        diff_file = StringIO()
238
236
        if invert:
239
237
            old_tree = self.work_tree
240
238
            new_tree = self.target_tree
243
241
            new_tree = self.work_tree
244
242
        old_path = old_tree.id2path(file_id)
245
243
        new_path = new_tree.id2path(file_id)
246
 
        path_encoding = osutils.get_terminal_encoding()
247
244
        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')
 
245
            path_encoding=osutils.get_terminal_encoding())
 
246
        patch = text_differ.diff(file_id, old_path, new_path, 'file', 'file')
250
247
        diff_file.seek(0)
251
248
        return patches.parse_patch(diff_file)
252
249
 
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):
 
250
    def prompt(self, message):
 
251
        """Prompt the user for a character.
 
252
 
 
253
        :param message: The message to prompt a user with.
 
254
        :return: A character.
 
255
        """
 
256
        if not sys.stdin.isatty():
 
257
            # Since there is no controlling terminal we will hang when trying
 
258
            # to prompt the user, better abort now.  See
 
259
            # https://code.launchpad.net/~bialix/bzr/shelve-no-tty/+merge/14905
 
260
            # for more context.
 
261
            raise errors.BzrError("You need a controlling terminal.")
 
262
        sys.stdout.write(message)
 
263
        char = osutils.getchar()
 
264
        sys.stdout.write("\r" + ' ' * len(message) + '\r')
 
265
        sys.stdout.flush()
 
266
        return char
 
267
 
 
268
    def prompt_bool(self, question, long=False, allow_editor=False):
257
269
        """Prompt the user with a yes/no question.
258
270
 
259
271
        This may be overridden by self.auto.  It may also *set* self.auto.  It
263
275
        """
264
276
        if self.auto:
265
277
            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'
 
278
        editor_string = ''
 
279
        if long:
 
280
            if allow_editor:
 
281
                editor_string = '(E)dit manually, '
 
282
            prompt = ' [(y)es, (N)o, %s(f)inish, or (q)uit]' % editor_string
277
283
        else:
278
 
            char = alternatives_chars[choice]
 
284
            if allow_editor:
 
285
                editor_string = 'e'
 
286
            prompt = ' [yN%sfq?]' % editor_string
 
287
        char = self.prompt(question + prompt)
279
288
        if char == 'y':
280
289
            return True
281
290
        elif char == 'e' and allow_editor:
283
292
        elif char == 'f':
284
293
            self.auto = True
285
294
            return True
 
295
        elif char == '?':
 
296
            return self.prompt_bool(question, long=True)
286
297
        if char == 'q':
287
298
            raise errors.UserAbort()
288
299
        else:
295
306
        :param file_id: The id of the file that was modified.
296
307
        :return: The number of changes.
297
308
        """
298
 
        path = self.work_tree.id2path(file_id)
299
 
        work_tree_lines = self.work_tree.get_file_lines(path, file_id)
 
309
        work_tree_lines = self.work_tree.get_file_lines(file_id)
300
310
        try:
301
311
            lines, change_count = self._select_hunks(creator, file_id,
302
312
                                                     work_tree_lines)
320
330
        if self.reporter.invert_diff:
321
331
            target_lines = work_tree_lines
322
332
        else:
323
 
            path = self.target_tree.id2path(file_id)
324
 
            target_lines = self.target_tree.get_file_lines(path)
 
333
            target_lines = self.target_tree.get_file_lines(file_id)
325
334
        textfile.check_text_lines(work_tree_lines)
326
335
        textfile.check_text_lines(target_lines)
327
336
        parsed = self.get_parsed_patch(file_id, self.reporter.invert_diff)
330
339
            offset = 0
331
340
            self.diff_writer.write(parsed.get_header())
332
341
            for hunk in parsed.hunks:
333
 
                self.diff_writer.write(hunk.as_bytes())
 
342
                self.diff_writer.write(str(hunk))
334
343
                selected = self.prompt_bool(self.reporter.vocab['hunk'],
335
344
                                            allow_editor=(self.change_editor
336
345
                                                          is not None))
359
368
            content of the file, and change_region_count is the number of
360
369
            changed regions.
361
370
        """
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)))
 
371
        lines = osutils.split_lines(self.change_editor.edit_file(file_id))
365
372
        return lines, self._count_changed_regions(work_tree_lines, lines)
366
373
 
367
374
    @staticmethod
397
404
                try:
398
405
                    shelf_id = int(shelf_id)
399
406
                except ValueError:
400
 
                    raise shelf.InvalidShelfId(shelf_id)
 
407
                    raise errors.InvalidShelfId(shelf_id)
401
408
            else:
402
409
                shelf_id = manager.last_shelf()
403
410
                if shelf_id is None:
404
 
                    raise errors.CommandError(
405
 
                        gettext('No changes are shelved.'))
 
411
                    raise errors.BzrCommandError('No changes are shelved.')
406
412
            apply_changes = True
407
413
            delete_shelf = True
408
414
            read_shelf = True
456
462
 
457
463
    def run(self):
458
464
        """Perform the unshelving operation."""
459
 
        with contextlib.ExitStack() as exit_stack:
460
 
            exit_stack.enter_context(self.tree.lock_tree_write())
 
465
        self.tree.lock_tree_write()
 
466
        cleanups = [self.tree.unlock]
 
467
        try:
461
468
            if self.read_shelf:
462
 
                trace.note(gettext('Using changes with id "%d".') %
463
 
                           self.shelf_id)
 
469
                trace.note('Using changes with id "%d".' % self.shelf_id)
464
470
                unshelver = self.manager.get_unshelver(self.shelf_id)
465
 
                exit_stack.callback(unshelver.finalize)
 
471
                cleanups.append(unshelver.finalize)
466
472
                if unshelver.message is not None:
467
 
                    trace.note(gettext('Message: %s') % unshelver.message)
 
473
                    trace.note('Message: %s' % unshelver.message)
468
474
                change_reporter = delta._ChangeReporter()
469
 
                merger = unshelver.make_merger()
 
475
                merger = unshelver.make_merger(None)
470
476
                merger.change_reporter = change_reporter
471
477
                if self.apply_changes:
472
478
                    merger.do_merge()
476
482
                    self.show_changes(merger)
477
483
            if self.delete_shelf:
478
484
                self.manager.delete_shelf(self.shelf_id)
479
 
                trace.note(gettext('Deleted changes with id "%d".') %
480
 
                           self.shelf_id)
 
485
                trace.note('Deleted changes with id "%d".' % self.shelf_id)
 
486
        finally:
 
487
            for cleanup in reversed(cleanups):
 
488
                cleanup()
481
489
 
482
490
    def write_diff(self, merger):
483
491
        """Write this operation's diff to self.write_diff_to."""
485
493
        tt = tree_merger.make_preview_transform()
486
494
        new_tree = tt.get_preview_tree()
487
495
        if self.write_diff_to is None:
488
 
            self.write_diff_to = ui.ui_factory.make_output_stream(
489
 
                encoding_type='exact')
 
496
            self.write_diff_to = ui.ui_factory.make_output_stream()
490
497
        path_encoding = osutils.get_diff_header_encoding()
491
498
        diff.show_diff_trees(merger.this_tree, new_tree, self.write_diff_to,
492
 
                             path_encoding=path_encoding)
 
499
            path_encoding=path_encoding)
493
500
        tt.finalize()
494
501
 
495
502
    def show_changes(self, merger):