/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 breezy/shelf_ui.py

  • Committer: Gustav Hartvigsson
  • Date: 2021-01-09 21:36:27 UTC
  • Revision ID: gustav.hartvigsson@gmail.com-20210109213627-h1xwcutzy9m7a99b
Added 'Case Preserving Working Tree Use Cases' from Canonical Wiki

* Addod a page from the Canonical Bazaar wiki
  with information on the scmeatics of case
  perserving filesystems an a case insensitive
  filesystem works.
  
  * Needs re-work, but this will do as it is the
    same inforamoton as what was on the linked
    page in the currint documentation.

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