/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: Robert Collins
  • Date: 2010-05-06 23:41:35 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506234135-yivbzczw1sejxnxc
Lock methods on ``Tree``, ``Branch`` and ``Repository`` are now
expected to return an object which can be used to unlock them. This reduces
duplicate code when using cleanups. The previous 'tokens's returned by
``Branch.lock_write`` and ``Repository.lock_write`` are now attributes
on the result of the lock_write. ``repository.RepositoryWriteLockResult``
and ``branch.BranchWriteLockResult`` document this. (Robert Collins)

``log._get_info_for_log_files`` now takes an add_cleanup callable.
(Robert Collins)

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
 
        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')
 
244
        text_differ = diff.DiffText(old_tree, new_tree, diff_file)
 
245
        patch = text_differ.diff(file_id, old_path, new_path, 'file', 'file')
252
246
        diff_file.seek(0)
253
247
        return patches.parse_patch(diff_file)
254
248
 
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):
 
249
    def prompt(self, message):
 
250
        """Prompt the user for a character.
 
251
 
 
252
        :param message: The message to prompt a user with.
 
253
        :return: A character.
 
254
        """
 
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
        sys.stdout.write(message)
 
262
        char = osutils.getchar()
 
263
        sys.stdout.write("\r" + ' ' * len(message) + '\r')
 
264
        sys.stdout.flush()
 
265
        return char
 
266
 
 
267
    def prompt_bool(self, question, long=False, allow_editor=False):
259
268
        """Prompt the user with a yes/no question.
260
269
 
261
270
        This may be overridden by self.auto.  It may also *set* self.auto.  It
265
274
        """
266
275
        if self.auto:
267
276
            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'
 
277
        editor_string = ''
 
278
        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
279
282
        else:
280
 
            char = alternatives_chars[choice]
 
283
            if allow_editor:
 
284
                editor_string = 'e'
 
285
            prompt = ' [yN%sfq?]' % editor_string
 
286
        char = self.prompt(question + prompt)
281
287
        if char == 'y':
282
288
            return True
283
289
        elif char == 'e' and allow_editor:
285
291
        elif char == 'f':
286
292
            self.auto = True
287
293
            return True
 
294
        elif char == '?':
 
295
            return self.prompt_bool(question, long=True)
288
296
        if char == 'q':
289
297
            raise errors.UserAbort()
290
298
        else:
297
305
        :param file_id: The id of the file that was modified.
298
306
        :return: The number of changes.
299
307
        """
300
 
        path = self.work_tree.id2path(file_id)
301
 
        work_tree_lines = self.work_tree.get_file_lines(path, file_id)
 
308
        work_tree_lines = self.work_tree.get_file_lines(file_id)
302
309
        try:
303
310
            lines, change_count = self._select_hunks(creator, file_id,
304
311
                                                     work_tree_lines)
322
329
        if self.reporter.invert_diff:
323
330
            target_lines = work_tree_lines
324
331
        else:
325
 
            path = self.target_tree.id2path(file_id)
326
 
            target_lines = self.target_tree.get_file_lines(path)
 
332
            target_lines = self.target_tree.get_file_lines(file_id)
327
333
        textfile.check_text_lines(work_tree_lines)
328
334
        textfile.check_text_lines(target_lines)
329
335
        parsed = self.get_parsed_patch(file_id, self.reporter.invert_diff)
332
338
            offset = 0
333
339
            self.diff_writer.write(parsed.get_header())
334
340
            for hunk in parsed.hunks:
335
 
                self.diff_writer.write(hunk.as_bytes())
 
341
                self.diff_writer.write(str(hunk))
336
342
                selected = self.prompt_bool(self.reporter.vocab['hunk'],
337
343
                                            allow_editor=(self.change_editor
338
344
                                                          is not None))
361
367
            content of the file, and change_region_count is the number of
362
368
            changed regions.
363
369
        """
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)))
 
370
        lines = osutils.split_lines(self.change_editor.edit_file(file_id))
367
371
        return lines, self._count_changed_regions(work_tree_lines, lines)
368
372
 
369
373
    @staticmethod
399
403
                try:
400
404
                    shelf_id = int(shelf_id)
401
405
                except ValueError:
402
 
                    raise shelf.InvalidShelfId(shelf_id)
 
406
                    raise errors.InvalidShelfId(shelf_id)
403
407
            else:
404
408
                shelf_id = manager.last_shelf()
405
409
                if shelf_id is None:
406
 
                    raise errors.BzrCommandError(
407
 
                        gettext('No changes are shelved.'))
 
410
                    raise errors.BzrCommandError('No changes are shelved.')
408
411
            apply_changes = True
409
412
            delete_shelf = True
410
413
            read_shelf = True
458
461
 
459
462
    def run(self):
460
463
        """Perform the unshelving operation."""
461
 
        with cleanup.ExitStack() as exit_stack:
462
 
            exit_stack.enter_context(self.tree.lock_tree_write())
 
464
        self.tree.lock_tree_write()
 
465
        cleanups = [self.tree.unlock]
 
466
        try:
463
467
            if self.read_shelf:
464
 
                trace.note(gettext('Using changes with id "%d".') %
465
 
                           self.shelf_id)
 
468
                trace.note('Using changes with id "%d".' % self.shelf_id)
466
469
                unshelver = self.manager.get_unshelver(self.shelf_id)
467
 
                exit_stack.callback(unshelver.finalize)
 
470
                cleanups.append(unshelver.finalize)
468
471
                if unshelver.message is not None:
469
 
                    trace.note(gettext('Message: %s') % unshelver.message)
 
472
                    trace.note('Message: %s' % unshelver.message)
470
473
                change_reporter = delta._ChangeReporter()
471
 
                merger = unshelver.make_merger()
 
474
                merger = unshelver.make_merger(None)
472
475
                merger.change_reporter = change_reporter
473
476
                if self.apply_changes:
474
477
                    merger.do_merge()
478
481
                    self.show_changes(merger)
479
482
            if self.delete_shelf:
480
483
                self.manager.delete_shelf(self.shelf_id)
481
 
                trace.note(gettext('Deleted changes with id "%d".') %
482
 
                           self.shelf_id)
 
484
                trace.note('Deleted changes with id "%d".' % self.shelf_id)
 
485
        finally:
 
486
            for cleanup in reversed(cleanups):
 
487
                cleanup()
483
488
 
484
489
    def write_diff(self, merger):
485
490
        """Write this operation's diff to self.write_diff_to."""
487
492
        tt = tree_merger.make_preview_transform()
488
493
        new_tree = tt.get_preview_tree()
489
494
        if self.write_diff_to is None:
490
 
            self.write_diff_to = ui.ui_factory.make_output_stream(
491
 
                encoding_type='exact')
492
 
        path_encoding = osutils.get_diff_header_encoding()
493
 
        diff.show_diff_trees(merger.this_tree, new_tree, self.write_diff_to,
494
 
                             path_encoding=path_encoding)
 
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)
495
497
        tt.finalize()
496
498
 
497
499
    def show_changes(self, merger):