~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/shelf_ui.py

  • Committer: Aaron Bentley
  • Date: 2008-11-07 18:10:32 UTC
  • mto: This revision was merged to the branch mainline in revision 3825.
  • Revision ID: aaron@aaronbentley.com-20081107181032-wggaw45wukuk4u54
Implement copy_tree on copy_tree_to_transport

Show diffs side-by-side

added added

removed removed

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