~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/shelf_ui.py

  • Committer: Patch Queue Manager
  • Date: 2014-02-12 18:22:22 UTC
  • mfrom: (6589.2.1 trunk)
  • Revision ID: pqm@pqm.ubuntu.com-20140212182222-beouo25gaf1cny76
(vila) The XDG Base Directory Specification uses the XDG_CACHE_HOME,
 not XDG_CACHE_DIR. (Andrew Starr-Bochicchio)

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
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
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):
40
118
 
41
119
    def __init__(self, work_tree, target_tree, diff_writer=None, auto=False,
42
120
                 auto_apply=False, file_list=None, message=None,
43
 
                 destroy=False):
 
121
                 destroy=False, manager=None, reporter=None):
44
122
        """Constructor.
45
123
 
46
124
        :param work_tree: The working tree to shelve changes from.
52
130
        :param message: The message to associate with the shelved changes.
53
131
        :param destroy: Change the working tree without storing the shelved
54
132
            changes.
 
133
        :param manager: The shelf manager to use.
 
134
        :param reporter: Object for reporting changes to user.
55
135
        """
56
136
        self.work_tree = work_tree
57
137
        self.target_tree = target_tree
58
138
        self.diff_writer = diff_writer
59
139
        if self.diff_writer is None:
60
140
            self.diff_writer = sys.stdout
61
 
        self.manager = work_tree.get_shelf_manager()
 
141
        if manager is None:
 
142
            manager = work_tree.get_shelf_manager()
 
143
        self.manager = manager
62
144
        self.auto = auto
63
145
        self.auto_apply = auto_apply
64
146
        self.file_list = file_list
65
147
        self.message = message
66
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()
67
155
 
68
156
    @classmethod
69
157
    def from_args(klass, diff_writer, revision=None, all=False, file_list=None,
70
 
                  message=None, directory='.', destroy=False):
 
158
                  message=None, directory=None, destroy=False):
71
159
        """Create a shelver from commandline arguments.
72
160
 
 
161
        The returned shelver wil have a work_tree that is locked and should
 
162
        be unlocked.
 
163
 
73
164
        :param revision: RevisionSpec of the revision to compare to.
74
165
        :param all: If True, shelve all changes without prompting.
75
166
        :param file_list: If supplied, only files in this list may be  shelved.
78
169
        :param destroy: Change the working tree without storing the shelved
79
170
            changes.
80
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]
81
176
        tree, path = workingtree.WorkingTree.open_containing(directory)
82
 
        target_tree = builtins._get_one_revision_tree('shelf2', revision,
83
 
            tree.branch, tree)
84
 
        files = builtins.safe_relpath_files(tree, file_list)
85
 
        return klass(tree, target_tree, diff_writer, all, all, files, message,
86
 
                     destroy)
 
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()
87
188
 
88
189
    def run(self):
89
190
        """Interactively shelve the changes."""
98
199
                        changes_shelved += self.handle_modify_text(creator,
99
200
                                                                   change[1])
100
201
                    except errors.BinaryFile:
101
 
                        if self.prompt_bool('Shelve binary changes?'):
 
202
                        if self.prompt_bool(self.reporter.vocab['binary']):
102
203
                            changes_shelved += 1
103
204
                            creator.shelve_content_change(change[1])
104
 
                if change[0] == 'add file':
105
 
                    if self.prompt_bool('Shelve adding file "%s"?'
106
 
                                        % change[3]):
107
 
                        creator.shelve_creation(change[1])
108
 
                        changes_shelved += 1
109
 
                if change[0] == 'delete file':
110
 
                    if self.prompt_bool('Shelve removing file "%s"?'
111
 
                                        % change[3]):
112
 
                        creator.shelve_deletion(change[1])
113
 
                        changes_shelved += 1
114
 
                if change[0] == 'change kind':
115
 
                    if self.prompt_bool('Shelve changing "%s" from %s to %s? '
116
 
                                        % (change[4], change[2], change[3])):
117
 
                        creator.shelve_content_change(change[1])
118
 
                        changes_shelved += 1
119
 
                if change[0] == 'rename':
120
 
                    if self.prompt_bool('Shelve renaming "%s" => "%s"?' %
121
 
                                   change[2:]):
122
 
                        creator.shelve_rename(change[1])
123
 
                        changes_shelved += 1
124
 
                if change[0] == 'modify target':
125
 
                    if self.prompt_bool('Shelve changing target of "%s" '
126
 
                            'from "%s" to "%s"?' % change[2:]):
127
 
                        creator.shelve_modify_target(change[1])
 
205
                else:
 
206
                    if self.prompt_bool(self.reporter.prompt_change(change)):
 
207
                        creator.shelve_change(change)
128
208
                        changes_shelved += 1
129
209
            if changes_shelved > 0:
130
 
                trace.note("Selected changes:")
131
 
                changes = creator.work_transform.iter_changes()
132
 
                reporter = delta._ChangeReporter()
133
 
                delta.report_changes(changes, reporter)
 
210
                self.reporter.selected_changes(creator.work_transform)
134
211
                if (self.auto_apply or self.prompt_bool(
135
 
                    'Shelve %d change(s)?' % changes_shelved)):
 
212
                    self.reporter.vocab['final'] % changes_shelved)):
136
213
                    if self.destroy:
137
214
                        creator.transform()
138
 
                        trace.note('Selected changes destroyed.')
 
215
                        self.reporter.changes_destroyed()
139
216
                    else:
140
217
                        shelf_id = self.manager.shelve_changes(creator,
141
218
                                                               self.message)
142
 
                        trace.note('Changes shelved with id "%d".' % shelf_id)
 
219
                        self.reporter.shelved_id(shelf_id)
143
220
            else:
144
 
                trace.warning('No changes to shelve.')
 
221
                self.reporter.no_changes()
145
222
        finally:
146
223
            shutil.rmtree(self.tempdir)
147
224
            creator.finalize()
148
225
 
149
 
    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):
150
233
        """Return a parsed version of a file's patch.
151
234
 
152
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).
153
238
        :return: A patches.Patch.
154
239
        """
155
 
        old_path = self.target_tree.id2path(file_id)
156
 
        new_path = self.work_tree.id2path(file_id)
157
240
        diff_file = StringIO()
158
 
        text_differ = diff.DiffText(self.target_tree, self.work_tree,
159
 
                                    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())
160
251
        patch = text_differ.diff(file_id, old_path, new_path, 'file', 'file')
161
252
        diff_file.seek(0)
162
253
        return patches.parse_patch(diff_file)
163
254
 
164
 
    def prompt(self, message):
165
 
        """Prompt the user for a character.
166
 
 
167
 
        :param message: The message to prompt a user with.
168
 
        :return: A character.
169
 
        """
170
 
        sys.stdout.write(message)
171
 
        char = osutils.getchar()
172
 
        sys.stdout.write("\r" + ' ' * len(message) + '\r')
173
 
        sys.stdout.flush()
174
 
        return char
175
 
 
176
 
    def prompt_bool(self, question, long=False):
 
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):
177
259
        """Prompt the user with a yes/no question.
178
260
 
179
261
        This may be overridden by self.auto.  It may also *set* self.auto.  It
183
265
        """
184
266
        if self.auto:
185
267
            return True
186
 
        if long:
187
 
            prompt = ' [(y)es, (N)o, (f)inish, or (q)uit]'
 
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'
188
279
        else:
189
 
            prompt = ' [yNfq?]'
190
 
        char = self.prompt(question + prompt)
 
280
            char = alternatives_chars[choice]
191
281
        if char == 'y':
192
282
            return True
 
283
        elif char == 'e' and allow_editor:
 
284
            raise UseEditor
193
285
        elif char == 'f':
194
286
            self.auto = True
195
287
            return True
196
 
        elif char == '?':
197
 
            return self.prompt_bool(question, long=True)
198
288
        if char == 'q':
199
289
            raise errors.UserAbort()
200
290
        else:
201
291
            return False
202
292
 
203
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):
204
311
        """Provide diff hunk selection for modified text.
205
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
 
206
316
        :param creator: a ShelfCreator
207
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.
208
319
        :return: number of shelved hunks.
209
320
        """
210
 
        target_lines = self.target_tree.get_file_lines(file_id)
211
 
        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)
212
326
        textfile.check_text_lines(target_lines)
213
 
        parsed = self.get_parsed_patch(file_id)
 
327
        parsed = self.get_parsed_patch(file_id, self.reporter.invert_diff)
214
328
        final_hunks = []
215
329
        if not self.auto:
216
330
            offset = 0
217
331
            self.diff_writer.write(parsed.get_header())
218
332
            for hunk in parsed.hunks:
219
333
                self.diff_writer.write(str(hunk))
220
 
                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:
221
340
                    hunk.mod_pos += offset
222
341
                    final_hunks.append(hunk)
223
342
                else:
224
343
                    offset -= (hunk.mod_range - hunk.orig_range)
225
344
        sys.stdout.flush()
226
 
        if len(parsed.hunks) == len(final_hunks):
227
 
            return 0
228
 
        patched = patches.iter_patched_from_hunks(target_lines, final_hunks)
229
 
        creator.shelve_lines(file_id, list(patched))
230
 
        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
231
371
 
232
372
 
233
373
class Unshelver(object):
234
374
    """Unshelve changes into a working tree."""
235
375
 
236
376
    @classmethod
237
 
    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):
238
379
        """Create an unshelver from commandline arguments.
239
380
 
 
381
        The returned shelver will have a tree that is locked and should
 
382
        be unlocked.
 
383
 
240
384
        :param shelf_id: Integer id of the shelf, as a string.
241
385
        :param action: action to perform.  May be 'apply', 'dry-run',
242
 
            'delete'.
 
386
            'delete', 'preview'.
243
387
        :param directory: The directory to unshelve changes into.
 
388
        :param write_diff_to: See Unshelver.__init__().
244
389
        """
245
390
        tree, path = workingtree.WorkingTree.open_containing(directory)
246
 
        manager = tree.get_shelf_manager()
247
 
        if shelf_id is not None:
248
 
            try:
249
 
                shelf_id = int(shelf_id)
250
 
            except ValueError:
251
 
                raise errors.InvalidShelfId(shelf_id)
252
 
        else:
253
 
            shelf_id = manager.last_shelf()
254
 
            if shelf_id is None:
255
 
                raise errors.BzrCommandError('No changes are shelved.')
256
 
            trace.note('Unshelving changes with id "%d".' % shelf_id)
257
 
        apply_changes = True
258
 
        delete_shelf = True
259
 
        read_shelf = True
260
 
        if action == 'dry-run':
261
 
            apply_changes = False
262
 
            delete_shelf = False
263
 
        if action == 'delete-only':
264
 
            apply_changes = False
265
 
            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
266
423
        return klass(tree, manager, shelf_id, apply_changes, delete_shelf,
267
 
                     read_shelf)
 
424
                     read_shelf, show_diff, write_diff_to)
268
425
 
269
426
    def __init__(self, tree, manager, shelf_id, apply_changes=True,
270
 
                 delete_shelf=True, read_shelf=True):
 
427
                 delete_shelf=True, read_shelf=True, show_diff=False,
 
428
                 write_diff_to=None):
271
429
        """Constructor.
272
430
 
273
431
        :param tree: The working tree to unshelve into.
277
435
            working tree.
278
436
        :param delete_shelf: If True, delete the changes from the shelf.
279
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.
280
443
        """
281
444
        self.tree = tree
282
445
        manager = tree.get_shelf_manager()
285
448
        self.apply_changes = apply_changes
286
449
        self.delete_shelf = delete_shelf
287
450
        self.read_shelf = read_shelf
 
451
        self.show_diff = show_diff
 
452
        self.write_diff_to = write_diff_to
288
453
 
289
454
    def run(self):
290
455
        """Perform the unshelving operation."""
291
 
        self.tree.lock_write()
 
456
        self.tree.lock_tree_write()
292
457
        cleanups = [self.tree.unlock]
293
458
        try:
294
459
            if self.read_shelf:
 
460
                trace.note(gettext('Using changes with id "%d".') % self.shelf_id)
295
461
                unshelver = self.manager.get_unshelver(self.shelf_id)
296
462
                cleanups.append(unshelver.finalize)
297
463
                if unshelver.message is not None:
298
 
                    trace.note('Message: %s' % unshelver.message)
 
464
                    trace.note(gettext('Message: %s') % unshelver.message)
299
465
                change_reporter = delta._ChangeReporter()
300
 
                task = ui.ui_factory.nested_progress_bar()
301
 
                try:
302
 
                    merger = unshelver.make_merger(task)
303
 
                    merger.change_reporter = change_reporter
304
 
                    if self.apply_changes:
305
 
                        merger.do_merge()
306
 
                    else:
307
 
                        self.show_changes(merger)
308
 
                finally:
309
 
                    task.finished()
 
466
                merger = unshelver.make_merger(None)
 
467
                merger.change_reporter = change_reporter
 
468
                if self.apply_changes:
 
469
                    merger.do_merge()
 
470
                elif self.show_diff:
 
471
                    self.write_diff(merger)
 
472
                else:
 
473
                    self.show_changes(merger)
310
474
            if self.delete_shelf:
311
475
                self.manager.delete_shelf(self.shelf_id)
 
476
                trace.note(gettext('Deleted changes with id "%d".') % self.shelf_id)
312
477
        finally:
313
478
            for cleanup in reversed(cleanups):
314
479
                cleanup()
315
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
 
316
493
    def show_changes(self, merger):
317
494
        """Show the changes that this operation specifies."""
318
495
        tree_merger = merger.make_merger()