~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/shelf_ui.py

(jameinel) Allow 'bzr serve' to interpret SIGHUP as a graceful shutdown.
 (bug #795025) (John A Meinel)

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