~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/shelf_ui.py

  • Committer: Tarmac
  • Author(s): Vincent Ladeuil
  • Date: 2017-01-30 14:42:05 UTC
  • mfrom: (6620.1.1 trunk)
  • Revision ID: tarmac-20170130144205-r8fh2xpmiuxyozpv
Merge  2.7 into trunk including fix for bug #1657238 [r=vila]

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2008 Canonical Ltd
 
1
# Copyright (C) 2008, 2009, 2010 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
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
 
17
from __future__ import absolute_import
17
18
 
18
19
from cStringIO import StringIO
19
20
import shutil
27
28
    errors,
28
29
    osutils,
29
30
    patches,
 
31
    patiencediff,
30
32
    shelf,
31
33
    textfile,
32
34
    trace,
33
35
    ui,
34
36
    workingtree,
35
37
)
 
38
from bzrlib.i18n import gettext
 
39
 
 
40
class UseEditor(Exception):
 
41
    """Use an editor instead of selecting hunks."""
 
42
 
 
43
 
 
44
class ShelfReporter(object):
 
45
 
 
46
    vocab = {'add file': gettext('Shelve adding file "%(path)s"?'),
 
47
             'binary': gettext('Shelve binary changes?'),
 
48
             'change kind': gettext('Shelve changing "%s" from %(other)s'
 
49
             ' to %(this)s?'),
 
50
             'delete file': gettext('Shelve removing file "%(path)s"?'),
 
51
             'final': gettext('Shelve %d change(s)?'),
 
52
             'hunk': gettext('Shelve?'),
 
53
             'modify target': gettext('Shelve changing target of'
 
54
             ' "%(path)s" from "%(other)s" to "%(this)s"?'),
 
55
             'rename': gettext('Shelve renaming "%(other)s" =>'
 
56
                        ' "%(this)s"?')
 
57
             }
 
58
 
 
59
    invert_diff = False
 
60
 
 
61
    def __init__(self):
 
62
        self.delta_reporter = delta._ChangeReporter()
 
63
 
 
64
    def no_changes(self):
 
65
        """Report that no changes were selected to apply."""
 
66
        trace.warning('No changes to shelve.')
 
67
 
 
68
    def shelved_id(self, shelf_id):
 
69
        """Report the id changes were shelved to."""
 
70
        trace.note(gettext('Changes shelved with id "%d".') % shelf_id)
 
71
 
 
72
    def changes_destroyed(self):
 
73
        """Report that changes were made without shelving."""
 
74
        trace.note(gettext('Selected changes destroyed.'))
 
75
 
 
76
    def selected_changes(self, transform):
 
77
        """Report the changes that were selected."""
 
78
        trace.note(gettext("Selected changes:"))
 
79
        changes = transform.iter_changes()
 
80
        delta.report_changes(changes, self.delta_reporter)
 
81
 
 
82
    def prompt_change(self, change):
 
83
        """Determine the prompt for a change to apply."""
 
84
        if change[0] == 'rename':
 
85
            vals = {'this': change[3], 'other': change[2]}
 
86
        elif change[0] == 'change kind':
 
87
            vals = {'path': change[4], 'other': change[2], 'this': change[3]}
 
88
        elif change[0] == 'modify target':
 
89
            vals = {'path': change[2], 'other': change[3], 'this': change[4]}
 
90
        else:
 
91
            vals = {'path': change[3]}
 
92
        prompt = self.vocab[change[0]] % vals
 
93
        return prompt
 
94
 
 
95
 
 
96
class ApplyReporter(ShelfReporter):
 
97
 
 
98
    vocab = {'add file': gettext('Delete file "%(path)s"?'),
 
99
             'binary': gettext('Apply binary changes?'),
 
100
             'change kind': gettext('Change "%(path)s" from %(this)s'
 
101
             ' to %(other)s?'),
 
102
             'delete file': gettext('Add file "%(path)s"?'),
 
103
             'final': gettext('Apply %d change(s)?'),
 
104
             'hunk': gettext('Apply change?'),
 
105
             'modify target': gettext('Change target of'
 
106
             ' "%(path)s" from "%(this)s" to "%(other)s"?'),
 
107
             'rename': gettext('Rename "%(this)s" => "%(other)s"?'),
 
108
             }
 
109
 
 
110
    invert_diff = True
 
111
 
 
112
    def changes_destroyed(self):
 
113
        pass
36
114
 
37
115
 
38
116
class Shelver(object):
39
117
    """Interactively shelve the changes in a working tree."""
40
118
 
41
119
    def __init__(self, work_tree, target_tree, diff_writer=None, auto=False,
42
 
                 auto_apply=False, file_list=None, message=None):
 
120
                 auto_apply=False, file_list=None, message=None,
 
121
                 destroy=False, manager=None, reporter=None):
43
122
        """Constructor.
44
123
 
45
124
        :param work_tree: The working tree to shelve changes from.
49
128
        :param auto_apply: If True, shelve changes with no final prompt.
50
129
        :param file_list: If supplied, only files in this list may be shelved.
51
130
        :param message: The message to associate with the shelved changes.
 
131
        :param destroy: Change the working tree without storing the shelved
 
132
            changes.
 
133
        :param manager: The shelf manager to use.
 
134
        :param reporter: Object for reporting changes to user.
52
135
        """
53
136
        self.work_tree = work_tree
54
137
        self.target_tree = target_tree
55
138
        self.diff_writer = diff_writer
56
139
        if self.diff_writer is None:
57
140
            self.diff_writer = sys.stdout
58
 
        self.manager = work_tree.get_shelf_manager()
 
141
        if manager is None:
 
142
            manager = work_tree.get_shelf_manager()
 
143
        self.manager = manager
59
144
        self.auto = auto
60
145
        self.auto_apply = auto_apply
61
146
        self.file_list = file_list
62
147
        self.message = message
 
148
        self.destroy = destroy
 
149
        if reporter is None:
 
150
            reporter = ShelfReporter()
 
151
        self.reporter = reporter
 
152
        config = self.work_tree.branch.get_config()
 
153
        self.change_editor = config.get_change_editor(target_tree, work_tree)
 
154
        self.work_tree.lock_tree_write()
63
155
 
64
156
    @classmethod
65
157
    def from_args(klass, diff_writer, revision=None, all=False, file_list=None,
66
 
                  message=None, directory='.'):
 
158
                  message=None, directory=None, destroy=False):
67
159
        """Create a shelver from commandline arguments.
68
160
 
 
161
        The returned shelver wil have a work_tree that is locked and should
 
162
        be unlocked.
 
163
 
69
164
        :param revision: RevisionSpec of the revision to compare to.
70
165
        :param all: If True, shelve all changes without prompting.
71
166
        :param file_list: If supplied, only files in this list may be  shelved.
72
167
        :param message: The message to associate with the shelved changes.
73
168
        :param directory: The directory containing the working tree.
 
169
        :param destroy: Change the working tree without storing the shelved
 
170
            changes.
74
171
        """
 
172
        if directory is None:
 
173
            directory = u'.'
 
174
        elif file_list:
 
175
            file_list = [osutils.pathjoin(directory, f) for f in file_list]
75
176
        tree, path = workingtree.WorkingTree.open_containing(directory)
76
 
        target_tree = builtins._get_one_revision_tree('shelf2', revision,
77
 
            tree.branch, tree)
78
 
        files = builtins.safe_relpath_files(tree, file_list)
79
 
        return klass(tree, target_tree, diff_writer, all, all, files, message)
 
177
        # Ensure that tree is locked for the lifetime of target_tree, as
 
178
        # target tree may be reading from the same dirstate.
 
179
        tree.lock_tree_write()
 
180
        try:
 
181
            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)
 
186
        finally:
 
187
            tree.unlock()
80
188
 
81
189
    def run(self):
82
190
        """Interactively shelve the changes."""
91
199
                        changes_shelved += self.handle_modify_text(creator,
92
200
                                                                   change[1])
93
201
                    except errors.BinaryFile:
94
 
                        if self.prompt_bool('Shelve binary changes?'):
 
202
                        if self.prompt_bool(self.reporter.vocab['binary']):
95
203
                            changes_shelved += 1
96
204
                            creator.shelve_content_change(change[1])
97
 
                if change[0] == 'add file':
98
 
                    if self.prompt_bool('Shelve adding file "%s"?'
99
 
                                        % change[3]):
100
 
                        creator.shelve_creation(change[1])
101
 
                        changes_shelved += 1
102
 
                if change[0] == 'delete file':
103
 
                    if self.prompt_bool('Shelve removing file "%s"?'
104
 
                                        % change[3]):
105
 
                        creator.shelve_deletion(change[1])
106
 
                        changes_shelved += 1
107
 
                if change[0] == 'change kind':
108
 
                    if self.prompt_bool('Shelve changing "%s" from %s to %s? '
109
 
                                        % (change[4], change[2], change[3])):
110
 
                        creator.shelve_content_change(change[1])
111
 
                        changes_shelved += 1
112
 
                if change[0] == 'rename':
113
 
                    if self.prompt_bool('Shelve renaming "%s" => "%s"?' %
114
 
                                   change[2:]):
115
 
                        creator.shelve_rename(change[1])
 
205
                else:
 
206
                    if self.prompt_bool(self.reporter.prompt_change(change)):
 
207
                        creator.shelve_change(change)
116
208
                        changes_shelved += 1
117
209
            if changes_shelved > 0:
118
 
                trace.note("Selected changes:")
119
 
                changes = creator.work_transform.iter_changes()
120
 
                reporter = delta._ChangeReporter()
121
 
                delta.report_changes(changes, reporter)
 
210
                self.reporter.selected_changes(creator.work_transform)
122
211
                if (self.auto_apply or self.prompt_bool(
123
 
                    'Shelve %d change(s)?' % changes_shelved)):
124
 
                    shelf_id = self.manager.shelve_changes(creator,
125
 
                                                           self.message)
126
 
                    trace.note('Changes shelved with id "%d".' % shelf_id)
 
212
                    self.reporter.vocab['final'] % changes_shelved)):
 
213
                    if self.destroy:
 
214
                        creator.transform()
 
215
                        self.reporter.changes_destroyed()
 
216
                    else:
 
217
                        shelf_id = self.manager.shelve_changes(creator,
 
218
                                                               self.message)
 
219
                        self.reporter.shelved_id(shelf_id)
127
220
            else:
128
 
                trace.warning('No changes to shelve.')
 
221
                self.reporter.no_changes()
129
222
        finally:
130
223
            shutil.rmtree(self.tempdir)
131
224
            creator.finalize()
132
225
 
133
 
    def get_parsed_patch(self, file_id):
 
226
    def finalize(self):
 
227
        if self.change_editor is not None:
 
228
            self.change_editor.finish()
 
229
        self.work_tree.unlock()
 
230
 
 
231
 
 
232
    def get_parsed_patch(self, file_id, invert=False):
134
233
        """Return a parsed version of a file's patch.
135
234
 
136
235
        :param file_id: The id of the file to generate a patch for.
 
236
        :param invert: If True, provide an inverted patch (insertions displayed
 
237
            as removals, removals displayed as insertions).
137
238
        :return: A patches.Patch.
138
239
        """
139
 
        old_path = self.target_tree.id2path(file_id)
140
 
        new_path = self.work_tree.id2path(file_id)
141
240
        diff_file = StringIO()
142
 
        text_differ = diff.DiffText(self.target_tree, self.work_tree,
143
 
                                    diff_file)
 
241
        if invert:
 
242
            old_tree = self.work_tree
 
243
            new_tree = self.target_tree
 
244
        else:
 
245
            old_tree = self.target_tree
 
246
            new_tree = self.work_tree
 
247
        old_path = old_tree.id2path(file_id)
 
248
        new_path = new_tree.id2path(file_id)
 
249
        text_differ = diff.DiffText(old_tree, new_tree, diff_file,
 
250
            path_encoding=osutils.get_terminal_encoding())
144
251
        patch = text_differ.diff(file_id, old_path, new_path, 'file', 'file')
145
252
        diff_file.seek(0)
146
253
        return patches.parse_patch(diff_file)
147
254
 
148
 
    def prompt(self, message):
149
 
        """Prompt the user for a character.
150
 
 
151
 
        :param message: The message to prompt a user with.
152
 
        :return: A character.
153
 
        """
154
 
        sys.stdout.write(message)
155
 
        char = osutils.getchar()
156
 
        sys.stdout.write("\r" + ' ' * len(message) + '\r')
157
 
        sys.stdout.flush()
158
 
        return char
159
 
 
160
 
    def prompt_bool(self, question):
 
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):
161
259
        """Prompt the user with a yes/no question.
162
260
 
163
261
        This may be overridden by self.auto.  It may also *set* self.auto.  It
167
265
        """
168
266
        if self.auto:
169
267
            return True
170
 
        char = self.prompt(question + ' [yNfq]')
 
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'
 
279
        else:
 
280
            char = alternatives_chars[choice]
171
281
        if char == 'y':
172
282
            return True
 
283
        elif char == 'e' and allow_editor:
 
284
            raise UseEditor
173
285
        elif char == 'f':
174
286
            self.auto = True
175
287
            return True
179
291
            return False
180
292
 
181
293
    def handle_modify_text(self, creator, file_id):
 
294
        """Handle modified text, by using hunk selection or file editing.
 
295
 
 
296
        :param creator: A ShelfCreator.
 
297
        :param file_id: The id of the file that was modified.
 
298
        :return: The number of changes.
 
299
        """
 
300
        work_tree_lines = self.work_tree.get_file_lines(file_id)
 
301
        try:
 
302
            lines, change_count = self._select_hunks(creator, file_id,
 
303
                                                     work_tree_lines)
 
304
        except UseEditor:
 
305
            lines, change_count = self._edit_file(file_id, work_tree_lines)
 
306
        if change_count != 0:
 
307
            creator.shelve_lines(file_id, lines)
 
308
        return change_count
 
309
 
 
310
    def _select_hunks(self, creator, file_id, work_tree_lines):
182
311
        """Provide diff hunk selection for modified text.
183
312
 
 
313
        If self.reporter.invert_diff is True, the diff is inverted so that
 
314
        insertions are displayed as removals and vice versa.
 
315
 
184
316
        :param creator: a ShelfCreator
185
317
        :param file_id: The id of the file to shelve.
 
318
        :param work_tree_lines: Line contents of the file in the working tree.
186
319
        :return: number of shelved hunks.
187
320
        """
188
 
        target_lines = self.target_tree.get_file_lines(file_id)
189
 
        textfile.check_text_lines(self.work_tree.get_file_lines(file_id))
 
321
        if self.reporter.invert_diff:
 
322
            target_lines = work_tree_lines
 
323
        else:
 
324
            target_lines = self.target_tree.get_file_lines(file_id)
 
325
        textfile.check_text_lines(work_tree_lines)
190
326
        textfile.check_text_lines(target_lines)
191
 
        parsed = self.get_parsed_patch(file_id)
 
327
        parsed = self.get_parsed_patch(file_id, self.reporter.invert_diff)
192
328
        final_hunks = []
193
329
        if not self.auto:
194
330
            offset = 0
195
331
            self.diff_writer.write(parsed.get_header())
196
332
            for hunk in parsed.hunks:
197
333
                self.diff_writer.write(str(hunk))
198
 
                if not self.prompt_bool('Shelve?'):
 
334
                selected = self.prompt_bool(self.reporter.vocab['hunk'],
 
335
                                            allow_editor=(self.change_editor
 
336
                                                          is not None))
 
337
                if not self.reporter.invert_diff:
 
338
                    selected = (not selected)
 
339
                if selected:
199
340
                    hunk.mod_pos += offset
200
341
                    final_hunks.append(hunk)
201
342
                else:
202
343
                    offset -= (hunk.mod_range - hunk.orig_range)
203
344
        sys.stdout.flush()
204
 
        if len(parsed.hunks) == len(final_hunks):
205
 
            return 0
206
 
        patched = patches.iter_patched_from_hunks(target_lines, final_hunks)
207
 
        creator.shelve_lines(file_id, list(patched))
208
 
        return len(parsed.hunks) - len(final_hunks)
 
345
        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(file_id))
 
363
        return lines, self._count_changed_regions(work_tree_lines, lines)
 
364
 
 
365
    @staticmethod
 
366
    def _count_changed_regions(old_lines, new_lines):
 
367
        matcher = patiencediff.PatienceSequenceMatcher(None, old_lines,
 
368
                                                       new_lines)
 
369
        blocks = matcher.get_matching_blocks()
 
370
        return len(blocks) - 2
209
371
 
210
372
 
211
373
class Unshelver(object):
212
374
    """Unshelve changes into a working tree."""
213
375
 
214
376
    @classmethod
215
 
    def from_args(klass, shelf_id=None, action='apply', directory='.'):
 
377
    def from_args(klass, shelf_id=None, action='apply', directory='.',
 
378
                  write_diff_to=None):
216
379
        """Create an unshelver from commandline arguments.
217
380
 
 
381
        The returned shelver will have a tree that is locked and should
 
382
        be unlocked.
 
383
 
218
384
        :param shelf_id: Integer id of the shelf, as a string.
219
385
        :param action: action to perform.  May be 'apply', 'dry-run',
220
 
            'delete'.
 
386
            'delete', 'preview'.
221
387
        :param directory: The directory to unshelve changes into.
 
388
        :param write_diff_to: See Unshelver.__init__().
222
389
        """
223
390
        tree, path = workingtree.WorkingTree.open_containing(directory)
224
 
        manager = tree.get_shelf_manager()
225
 
        if shelf_id is not None:
226
 
            try:
227
 
                shelf_id = int(shelf_id)
228
 
            except ValueError:
229
 
                raise errors.InvalidShelfId(shelf_id)
230
 
        else:
231
 
            shelf_id = manager.last_shelf()
232
 
            if shelf_id is None:
233
 
                raise errors.BzrCommandError('No changes are shelved.')
234
 
            trace.note('Unshelving changes with id "%d".' % shelf_id)
235
 
        apply_changes = True
236
 
        delete_shelf = True
237
 
        read_shelf = True
238
 
        if action == 'dry-run':
239
 
            apply_changes = False
240
 
            delete_shelf = False
241
 
        if action == 'delete-only':
242
 
            apply_changes = False
243
 
            read_shelf = False
 
391
        tree.lock_tree_write()
 
392
        try:
 
393
            manager = tree.get_shelf_manager()
 
394
            if shelf_id is not None:
 
395
                try:
 
396
                    shelf_id = int(shelf_id)
 
397
                except ValueError:
 
398
                    raise errors.InvalidShelfId(shelf_id)
 
399
            else:
 
400
                shelf_id = manager.last_shelf()
 
401
                if shelf_id is None:
 
402
                    raise errors.BzrCommandError(gettext('No changes are shelved.'))
 
403
            apply_changes = True
 
404
            delete_shelf = True
 
405
            read_shelf = True
 
406
            show_diff = False
 
407
            if action == 'dry-run':
 
408
                apply_changes = False
 
409
                delete_shelf = False
 
410
            elif action == 'preview':
 
411
                apply_changes = False
 
412
                delete_shelf = False
 
413
                show_diff = True
 
414
            elif action == 'delete-only':
 
415
                apply_changes = False
 
416
                read_shelf = False
 
417
            elif action == 'keep':
 
418
                apply_changes = True
 
419
                delete_shelf = False
 
420
        except:
 
421
            tree.unlock()
 
422
            raise
244
423
        return klass(tree, manager, shelf_id, apply_changes, delete_shelf,
245
 
                     read_shelf)
 
424
                     read_shelf, show_diff, write_diff_to)
246
425
 
247
426
    def __init__(self, tree, manager, shelf_id, apply_changes=True,
248
 
                 delete_shelf=True, read_shelf=True):
 
427
                 delete_shelf=True, read_shelf=True, show_diff=False,
 
428
                 write_diff_to=None):
249
429
        """Constructor.
250
430
 
251
431
        :param tree: The working tree to unshelve into.
255
435
            working tree.
256
436
        :param delete_shelf: If True, delete the changes from the shelf.
257
437
        :param read_shelf: If True, read the changes from the shelf.
 
438
        :param show_diff: If True, show the diff that would result from
 
439
            unshelving the changes.
 
440
        :param write_diff_to: A file-like object where the diff will be
 
441
            written to. If None, ui.ui_factory.make_output_stream() will
 
442
            be used.
258
443
        """
259
444
        self.tree = tree
260
445
        manager = tree.get_shelf_manager()
263
448
        self.apply_changes = apply_changes
264
449
        self.delete_shelf = delete_shelf
265
450
        self.read_shelf = read_shelf
 
451
        self.show_diff = show_diff
 
452
        self.write_diff_to = write_diff_to
266
453
 
267
454
    def run(self):
268
455
        """Perform the unshelving operation."""
269
 
        self.tree.lock_write()
 
456
        self.tree.lock_tree_write()
270
457
        cleanups = [self.tree.unlock]
271
458
        try:
272
459
            if self.read_shelf:
 
460
                trace.note(gettext('Using changes with id "%d".') % self.shelf_id)
273
461
                unshelver = self.manager.get_unshelver(self.shelf_id)
274
462
                cleanups.append(unshelver.finalize)
275
463
                if unshelver.message is not None:
276
 
                    trace.note('Message: %s' % unshelver.message)
 
464
                    trace.note(gettext('Message: %s') % unshelver.message)
277
465
                change_reporter = delta._ChangeReporter()
278
 
                merger = unshelver.make_merger()
 
466
                merger = unshelver.make_merger(None)
279
467
                merger.change_reporter = change_reporter
280
468
                if self.apply_changes:
281
 
                    pb = ui.ui_factory.nested_progress_bar()
282
 
                    try:
283
 
                        merger.do_merge()
284
 
                    finally:
285
 
                        pb.finished()
 
469
                    merger.do_merge()
 
470
                elif self.show_diff:
 
471
                    self.write_diff(merger)
286
472
                else:
287
473
                    self.show_changes(merger)
288
474
            if self.delete_shelf:
289
475
                self.manager.delete_shelf(self.shelf_id)
 
476
                trace.note(gettext('Deleted changes with id "%d".') % self.shelf_id)
290
477
        finally:
291
478
            for cleanup in reversed(cleanups):
292
479
                cleanup()
293
480
 
 
481
    def write_diff(self, merger):
 
482
        """Write this operation's diff to self.write_diff_to."""
 
483
        tree_merger = merger.make_merger()
 
484
        tt = tree_merger.make_preview_transform()
 
485
        new_tree = tt.get_preview_tree()
 
486
        if self.write_diff_to is None:
 
487
            self.write_diff_to = ui.ui_factory.make_output_stream(encoding_type='exact')
 
488
        path_encoding = osutils.get_diff_header_encoding()
 
489
        diff.show_diff_trees(merger.this_tree, new_tree, self.write_diff_to,
 
490
            path_encoding=path_encoding)
 
491
        tt.finalize()
 
492
 
294
493
    def show_changes(self, merger):
295
494
        """Show the changes that this operation specifies."""
296
495
        tree_merger = merger.make_merger()