/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: Marius Kruger
  • Date: 2010-07-10 21:28:56 UTC
  • mto: (5384.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 5385.
  • Revision ID: marius.kruger@enerweb.co.za-20100710212856-uq4ji3go0u5se7hx
* Update documentation
* add NEWS

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