/b-gtk/fix-viz

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/b-gtk/fix-viz
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
1
# -*- coding: UTF-8 -*-
2
"""Difference window.
3
4
This module contains the code to manage the diff window which shows
5
the changes made between two revisions on a branch.
6
"""
7
8
__copyright__ = "Copyright © 2005 Canonical Ltd."
9
__author__    = "Scott James Remnant <scott@ubuntu.com>"
10
11
12
from cStringIO import StringIO
13
252 by Aaron Bentley
Fix test suite
14
import pygtk
15
pygtk.require("2.0")
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
16
import gtk
17
import pango
232.1.1 by Adeodato Simó
Read ~/.colordiffrc to set colors in the diff window.
18
import os
19
import re
76 by Jelmer Vernooij
Replace non-UTF8 characters rather than generating an exception (fixes #44677).
20
import sys
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
21
22
try:
23
    import gtksourceview
24
    have_gtksourceview = True
25
except ImportError:
26
    have_gtksourceview = False
232.1.3 by Adeodato Simó
Support setting diff colors from gedit's syntax highlighting config too.
27
try:
28
    import gconf
29
    have_gconf = True
30
except ImportError:
31
    have_gconf = False
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
32
428 by Aaron Bentley
Get merging working
33
from bzrlib import merge as _mod_merge, osutils, progress, workingtree
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
34
from bzrlib.diff import show_diff_trees, internal_diff
59.2.4 by Aaron Bentley
Teach gdiff to accept a single file argument
35
from bzrlib.errors import NoSuchFile
426 by Aaron Bentley
Start support for Merge Directives
36
from bzrlib.patches import parse_patches
232.1.1 by Adeodato Simó
Read ~/.colordiffrc to set colors in the diff window.
37
from bzrlib.trace import warning
298.2.1 by Daniel Schierbeck
Refactored the GTK window code, creating a single base window class that handles keyboard events.
38
from bzrlib.plugins.gtk.window import Window
428 by Aaron Bentley
Get merging working
39
from dialog import error_dialog
40
41
42
class SelectCancelled(Exception):
43
44
    pass
298.2.1 by Daniel Schierbeck
Refactored the GTK window code, creating a single base window class that handles keyboard events.
45
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
46
424 by Aaron Bentley
Add ghandle-patch
47
class DiffFileView(gtk.ScrolledWindow):
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
48
51 by Jelmer Vernooij
Rework some of the parameters to DiffWindow.set_diff() to be
49
    def __init__(self):
278.1.4 by John Arbash Meinel
Just playing around.
50
        gtk.ScrolledWindow.__init__(self)
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
51
        self.construct()
424 by Aaron Bentley
Add ghandle-patch
52
        self._diffs = {}
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
53
54
    def construct(self):
278.1.4 by John Arbash Meinel
Just playing around.
55
        self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
56
        self.set_shadow_type(gtk.SHADOW_IN)
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
57
58
        if have_gtksourceview:
59
            self.buffer = gtksourceview.SourceBuffer()
60
            slm = gtksourceview.SourceLanguagesManager()
61
            gsl = slm.get_language_from_mime_type("text/x-patch")
232.1.3 by Adeodato Simó
Support setting diff colors from gedit's syntax highlighting config too.
62
            if have_gconf:
63
                self.apply_gedit_colors(gsl)
232.1.2 by Adeodato Simó
Rename apply_colordiffrc to apply_colordiff_colors, improve docstring.
64
            self.apply_colordiff_colors(gsl)
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
65
            self.buffer.set_language(gsl)
66
            self.buffer.set_highlight(True)
67
68
            sourceview = gtksourceview.SourceView(self.buffer)
69
        else:
70
            self.buffer = gtk.TextBuffer()
71
            sourceview = gtk.TextView(self.buffer)
72
73
        sourceview.set_editable(False)
74
        sourceview.modify_font(pango.FontDescription("Monospace"))
278.1.4 by John Arbash Meinel
Just playing around.
75
        self.add(sourceview)
10 by Scott James Remnant
Add an extra window type, clicking the little icons next to a parent
76
        sourceview.show()
77
232.1.1 by Adeodato Simó
Read ~/.colordiffrc to set colors in the diff window.
78
    @staticmethod
232.1.3 by Adeodato Simó
Support setting diff colors from gedit's syntax highlighting config too.
79
    def apply_gedit_colors(lang):
80
        """Set style for lang to that specified in gedit configuration.
81
82
        This method needs the gconf module.
278.1.4 by John Arbash Meinel
Just playing around.
83
232.1.3 by Adeodato Simó
Support setting diff colors from gedit's syntax highlighting config too.
84
        :param lang: a gtksourceview.SourceLanguage object.
85
        """
86
        GEDIT_SYNTAX_PATH = '/apps/gedit-2/preferences/syntax_highlighting'
87
        GEDIT_LANG_PATH = GEDIT_SYNTAX_PATH + '/' + lang.get_id()
88
89
        client = gconf.client_get_default()
90
        client.add_dir(GEDIT_LANG_PATH, gconf.CLIENT_PRELOAD_NONE)
91
92
        for tag in lang.get_tags():
93
            tag_id = tag.get_id()
94
            gconf_key = GEDIT_LANG_PATH + '/' + tag_id
95
            style_string = client.get_string(gconf_key)
96
97
            if style_string is None:
98
                continue
99
100
            # function to get a bool from a string that's either '0' or '1'
101
            string_bool = lambda x: bool(int(x))
102
103
            # style_string is a string like "2/#FFCCAA/#000000/0/1/0/0"
104
            # values are: mask, fg, bg, italic, bold, underline, strike
105
            # this packs them into (str_value, attr_name, conv_func) tuples
106
            items = zip(style_string.split('/'), ['mask', 'foreground',
107
                'background', 'italic', 'bold', 'underline', 'strikethrough' ],
108
                [ int, gtk.gdk.color_parse, gtk.gdk.color_parse, string_bool,
109
                    string_bool, string_bool, string_bool ]
110
            )
111
112
            style = gtksourceview.SourceTagStyle()
113
114
            # XXX The mask attribute controls whether the present values of
115
            # foreground and background color should in fact be used. Ideally
116
            # (and that's what gedit does), one could set all three attributes,
117
            # and let the TagStyle object figure out which colors to use.
118
            # However, in the GtkSourceview python bindings, the mask attribute
119
            # is read-only, and it's derived instead from the colors being
120
            # set or not. This means that we have to sometimes refrain from
121
            # setting fg or bg colors, depending on the value of the mask.
122
            # This code could go away if mask were writable.
123
            mask = int(items[0][0])
124
            if not (mask & 1): # GTK_SOURCE_TAG_STYLE_USE_BACKGROUND
125
                items[2:3] = []
126
            if not (mask & 2): # GTK_SOURCE_TAG_STYLE_USE_FOREGROUND
127
                items[1:2] = []
128
            items[0:1] = [] # skip the mask unconditionally
129
130
            for value, attr, func in items:
131
                try:
132
                    value = func(value)
133
                except ValueError:
134
                    warning('gconf key %s contains an invalid value: %s'
135
                            % gconf_key, value)
136
                else:
137
                    setattr(style, attr, value)
138
139
            lang.set_tag_style(tag_id, style)
140
424 by Aaron Bentley
Add ghandle-patch
141
    @classmethod
142
    def apply_colordiff_colors(klass, lang):
232.1.2 by Adeodato Simó
Rename apply_colordiffrc to apply_colordiff_colors, improve docstring.
143
        """Set style colors for lang using the colordiff configuration file.
232.1.1 by Adeodato Simó
Read ~/.colordiffrc to set colors in the diff window.
144
145
        Both ~/.colordiffrc and ~/.colordiffrc.bzr-gtk are read.
146
232.1.2 by Adeodato Simó
Rename apply_colordiffrc to apply_colordiff_colors, improve docstring.
147
        :param lang: a "Diff" gtksourceview.SourceLanguage object.
232.1.1 by Adeodato Simó
Read ~/.colordiffrc to set colors in the diff window.
148
        """
149
        colors = {}
150
151
        for f in ('~/.colordiffrc', '~/.colordiffrc.bzr-gtk'):
152
            f = os.path.expanduser(f)
153
            if os.path.exists(f):
154
                try:
155
                    f = file(f)
156
                except IOError, e:
157
                    warning('could not open file %s: %s' % (f, str(e)))
158
                else:
424 by Aaron Bentley
Add ghandle-patch
159
                    colors.update(klass.parse_colordiffrc(f))
232.1.1 by Adeodato Simó
Read ~/.colordiffrc to set colors in the diff window.
160
                    f.close()
161
162
        if not colors:
163
            # ~/.colordiffrc does not exist
164
            return
165
166
        mapping = {
167
                # map GtkSourceView tags to colordiff names
168
                # since GSV is richer, accept new names for extra bits,
169
                # defaulting to old names if they're not present
170
                'Added@32@line': ['newtext'],
171
                'Removed@32@line': ['oldtext'],
172
                'Location': ['location', 'diffstuff'],
173
                'Diff@32@file': ['file', 'diffstuff'],
174
                'Special@32@case': ['specialcase', 'diffstuff'],
175
        }
176
177
        for tag in lang.get_tags():
178
            tag_id = tag.get_id()
179
            keys = mapping.get(tag_id, [])
180
            color = None
181
182
            for key in keys:
183
                color = colors.get(key, None)
184
                if color is not None:
185
                    break
186
187
            if color is None:
188
                continue
189
190
            style = gtksourceview.SourceTagStyle()
191
            try:
192
                style.foreground = gtk.gdk.color_parse(color)
193
            except ValueError:
194
                warning('not a valid color: %s' % color)
195
            else:
196
                lang.set_tag_style(tag_id, style)
232.1.4 by Adeodato Simó
Add a test for parse_colordiffrc, and fix the function to handle empty lines.
197
198
    @staticmethod
199
    def parse_colordiffrc(fileobj):
200
        """Parse fileobj as a colordiff configuration file.
278.1.4 by John Arbash Meinel
Just playing around.
201
232.1.4 by Adeodato Simó
Add a test for parse_colordiffrc, and fix the function to handle empty lines.
202
        :return: A dict with the key -> value pairs.
203
        """
204
        colors = {}
205
        for line in fileobj:
206
            if re.match(r'^\s*#', line):
207
                continue
208
            if '=' not in line:
209
                continue
210
            key, val = line.split('=', 1)
211
            colors[key.strip()] = val.strip()
212
        return colors
213
278.1.4 by John Arbash Meinel
Just playing around.
214
    def set_trees(self, rev_tree, parent_tree):
215
        self.rev_tree = rev_tree
216
        self.parent_tree = parent_tree
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
217
#        self._build_delta()
218
219
#    def _build_delta(self):
220
#        self.parent_tree.lock_read()
221
#        self.rev_tree.lock_read()
222
#        try:
223
#            self.delta = _iter_changes_to_status(self.parent_tree, self.rev_tree)
224
#            self.path_to_status = {}
225
#            self.path_to_diff = {}
226
#            source_inv = self.parent_tree.inventory
227
#            target_inv = self.rev_tree.inventory
228
#            for (file_id, real_path, change_type, display_path) in self.delta:
229
#                self.path_to_status[real_path] = u'=== %s %s' % (change_type, display_path)
230
#                if change_type in ('modified', 'renamed and modified'):
231
#                    source_ie = source_inv[file_id]
232
#                    target_ie = target_inv[file_id]
233
#                    sio = StringIO()
234
#                    source_ie.diff(internal_diff, *old path, *old_tree,
235
#                                   *new_path, target_ie, self.rev_tree,
236
#                                   sio)
237
#                    self.path_to_diff[real_path] = 
238
#
239
#        finally:
240
#            self.rev_tree.unlock()
241
#            self.parent_tree.unlock()
278.1.4 by John Arbash Meinel
Just playing around.
242
243
    def show_diff(self, specific_files):
426 by Aaron Bentley
Start support for Merge Directives
244
        sections = []
245
        if specific_files is None:
246
            self.buffer.set_text(self._diffs[None])
247
        else:
248
            for specific_file in specific_files:
249
                sections.append(self._diffs[specific_file])
250
            self.buffer.set_text(''.join(sections))
424 by Aaron Bentley
Add ghandle-patch
251
252
253
class DiffView(DiffFileView):
254
    """This is the soft and chewy filling for a DiffWindow."""
255
256
    def __init__(self):
257
        DiffFileView.__init__(self)
258
        self.rev_tree = None
259
        self.parent_tree = None
260
261
    def show_diff(self, specific_files):
278.1.4 by John Arbash Meinel
Just playing around.
262
        s = StringIO()
278.1.18 by John Arbash Meinel
Start checking the diff view is correct.
263
        show_diff_trees(self.parent_tree, self.rev_tree, s, specific_files,
264
                        old_label='', new_label='',
265
                        # path_encoding=sys.getdefaultencoding()
266
                        # The default is utf-8, but we interpret the file
267
                        # contents as getdefaultencoding(), so we should
268
                        # probably try to make the paths in the same encoding.
269
                        )
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
270
        # str.decode(encoding, 'replace') doesn't do anything. Because if a
271
        # character is not valid in 'encoding' there is nothing to replace, the
272
        # 'replace' is for 'str.encode()'
273
        try:
274
            decoded = s.getvalue().decode(sys.getdefaultencoding())
275
        except UnicodeDecodeError:
276
            try:
277
                decoded = s.getvalue().decode('UTF-8')
278
            except UnicodeDecodeError:
279
                decoded = s.getvalue().decode('iso-8859-1')
280
                # This always works, because every byte has a valid
281
                # mapping from iso-8859-1 to Unicode
282
        # TextBuffer must contain pure UTF-8 data
283
        self.buffer.set_text(decoded.encode('UTF-8'))
278.1.4 by John Arbash Meinel
Just playing around.
284
285
423.8.1 by Jelmer Vernooij
Allow using the diff control as a widget
286
class DiffWidget(gtk.HPaned):
287
    """Diff widget
278.1.4 by John Arbash Meinel
Just playing around.
288
289
    """
423.8.1 by Jelmer Vernooij
Allow using the diff control as a widget
290
    def __init__(self):
291
        super(DiffWidget, self).__init__()
278.1.4 by John Arbash Meinel
Just playing around.
292
293
        # The file hierarchy: a scrollable treeview
294
        scrollwin = gtk.ScrolledWindow()
295
        scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
296
        scrollwin.set_shadow_type(gtk.SHADOW_IN)
423.8.1 by Jelmer Vernooij
Allow using the diff control as a widget
297
        self.pack1(scrollwin)
278.1.4 by John Arbash Meinel
Just playing around.
298
        scrollwin.show()
299
300
        self.model = gtk.TreeStore(str, str)
301
        self.treeview = gtk.TreeView(self.model)
302
        self.treeview.set_headers_visible(False)
303
        self.treeview.set_search_column(1)
304
        self.treeview.connect("cursor-changed", self._treeview_cursor_cb)
305
        scrollwin.add(self.treeview)
306
        self.treeview.show()
307
308
        cell = gtk.CellRendererText()
309
        cell.set_property("width-chars", 20)
310
        column = gtk.TreeViewColumn()
311
        column.pack_start(cell, expand=True)
312
        column.add_attribute(cell, "text", 0)
313
        self.treeview.append_column(column)
314
429 by Aaron Bentley
Merge from mainline
315
    def set_diff_text(self, lines):
278.1.4 by John Arbash Meinel
Just playing around.
316
        # The diffs of the  selected file: a scrollable source or
317
        # text view
424 by Aaron Bentley
Add ghandle-patch
318
        self.diff_view = DiffFileView()
278.1.12 by John Arbash Meinel
Delay computing the delta, and clean up some of the diff view names.
319
        self.diff_view.show()
429 by Aaron Bentley
Merge from mainline
320
        self.pack2(self.diff_view)
424 by Aaron Bentley
Add ghandle-patch
321
        self.model.append(None, [ "Complete Diff", "" ])
426 by Aaron Bentley
Start support for Merge Directives
322
        self.diff_view._diffs[None] = ''.join(lines)
323
        for patch in parse_patches(lines):
324
            oldname = patch.oldname.split('\t')[0]
325
            newname = patch.newname.split('\t')[0]
326
            self.model.append(None, [oldname, newname])
327
            self.diff_view._diffs[newname] = str(patch)
427 by Aaron Bentley
Add merge button when displaying merge directives
328
        self.diff_view.show_diff(None)
278.1.4 by John Arbash Meinel
Just playing around.
329
429 by Aaron Bentley
Merge from mainline
330
    def set_diff(self, rev_tree, parent_tree):
278.1.4 by John Arbash Meinel
Just playing around.
331
        """Set the differences showed by this window.
332
333
        Compares the two trees and populates the window with the
334
        differences.
335
        """
424 by Aaron Bentley
Add ghandle-patch
336
        self.diff_view = DiffView()
423.8.1 by Jelmer Vernooij
Allow using the diff control as a widget
337
        self.pack2(self.diff_view)
424 by Aaron Bentley
Add ghandle-patch
338
        self.diff_view.show()
278.1.12 by John Arbash Meinel
Delay computing the delta, and clean up some of the diff view names.
339
        self.diff_view.set_trees(rev_tree, parent_tree)
278.1.4 by John Arbash Meinel
Just playing around.
340
        self.rev_tree = rev_tree
341
        self.parent_tree = parent_tree
342
343
        self.model.clear()
344
        delta = self.rev_tree.changes_from(self.parent_tree)
345
346
        self.model.append(None, [ "Complete Diff", "" ])
347
348
        if len(delta.added):
349
            titer = self.model.append(None, [ "Added", None ])
350
            for path, id, kind in delta.added:
351
                self.model.append(titer, [ path, path ])
352
353
        if len(delta.removed):
354
            titer = self.model.append(None, [ "Removed", None ])
355
            for path, id, kind in delta.removed:
356
                self.model.append(titer, [ path, path ])
357
358
        if len(delta.renamed):
359
            titer = self.model.append(None, [ "Renamed", None ])
360
            for oldpath, newpath, id, kind, text_modified, meta_modified \
361
                    in delta.renamed:
362
                self.model.append(titer, [ oldpath, newpath ])
363
364
        if len(delta.modified):
365
            titer = self.model.append(None, [ "Modified", None ])
366
            for path, id, kind, text_modified, meta_modified in delta.modified:
367
                self.model.append(titer, [ path, path ])
368
369
        self.treeview.expand_all()
370
371
    def set_file(self, file_path):
372
        tv_path = None
373
        for data in self.model:
374
            for child in data.iterchildren():
375
                if child[0] == file_path or child[1] == file_path:
376
                    tv_path = child.path
377
                    break
378
        if tv_path is None:
379
            raise NoSuchFile(file_path)
380
        self.treeview.set_cursor(tv_path)
381
        self.treeview.scroll_to_cell(tv_path)
382
383
    def _treeview_cursor_cb(self, *args):
384
        """Callback for when the treeview cursor changes."""
385
        (path, col) = self.treeview.get_cursor()
386
        specific_files = [ self.model[path][1] ]
387
        if specific_files == [ None ]:
388
            return
389
        elif specific_files == [ "" ]:
390
            specific_files = None
391
278.1.12 by John Arbash Meinel
Delay computing the delta, and clean up some of the diff view names.
392
        self.diff_view.show_diff(specific_files)
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
393
394
423.8.1 by Jelmer Vernooij
Allow using the diff control as a widget
395
class DiffWindow(Window):
396
    """Diff window.
397
398
    This object represents and manages a single window containing the
399
    differences between two revisions on a branch.
400
    """
401
402
    def __init__(self, parent=None):
403
        Window.__init__(self, parent)
404
        self.set_border_width(0)
405
        self.set_title("bzrk diff")
406
407
        # Use two thirds of the screen by default
408
        screen = self.get_screen()
409
        monitor = screen.get_monitor_geometry(0)
410
        width = int(monitor.width * 0.66)
411
        height = int(monitor.height * 0.66)
412
        self.set_default_size(width, height)
413
414
        self.construct()
415
416
    def construct(self):
417
        """Construct the window contents."""
429 by Aaron Bentley
Merge from mainline
418
        self.vbox = gtk.VBox()
419
        self.add(self.vbox)
420
        self.vbox.show()
421
        hbox = self._get_button_bar()
422
        if hbox is not None:
423
            self.vbox.pack_start(hbox, expand=False, fill=True)
423.8.1 by Jelmer Vernooij
Allow using the diff control as a widget
424
        self.diff = DiffWidget()
429 by Aaron Bentley
Merge from mainline
425
        self.vbox.add(self.diff)
423.8.1 by Jelmer Vernooij
Allow using the diff control as a widget
426
        self.diff.show_all()
427
429 by Aaron Bentley
Merge from mainline
428
    def _get_button_bar(self):
429
        return None
430
431
    def set_diff_text(self, description, lines):
432
        self.diff.set_diff_text(lines)
433
        self.set_title(description + " - bzrk diff")
434
423.8.1 by Jelmer Vernooij
Allow using the diff control as a widget
435
    def set_diff(self, description, rev_tree, parent_tree):
436
        """Set the differences showed by this window.
437
438
        Compares the two trees and populates the window with the
439
        differences.
440
        """
441
        self.diff.set_diff(rev_tree, parent_tree)
442
        self.set_title(description + " - bzrk diff")
443
444
    def set_file(self, file_path):
445
        self.diff.set_file(file_path)
446
447
427 by Aaron Bentley
Add merge button when displaying merge directives
448
class MergeDirectiveWindow(DiffWindow):
449
428 by Aaron Bentley
Get merging working
450
    def __init__(self, directive, parent=None):
451
        DiffWindow.__init__(self, parent)
452
        self._merge_target = None
453
        self.directive = directive
454
427 by Aaron Bentley
Add merge button when displaying merge directives
455
    def _get_button_bar(self):
456
        merge_button = gtk.Button('Merge')
457
        merge_button.show()
458
        merge_button.set_relief(gtk.RELIEF_NONE)
428 by Aaron Bentley
Get merging working
459
        merge_button.connect("clicked", self.perform_merge)
460
427 by Aaron Bentley
Add merge button when displaying merge directives
461
        hbox = gtk.HButtonBox()
462
        hbox.set_layout(gtk.BUTTONBOX_START)
463
        hbox.pack_start(merge_button, expand=False, fill=True)
464
        hbox.show()
465
        return hbox
466
428 by Aaron Bentley
Get merging working
467
    def perform_merge(self, window):
468
        try:
469
            tree = self._get_merge_target()
470
        except SelectCancelled:
471
            return
472
        tree.lock_write()
473
        try:
474
            try:
475
                merger, verified = _mod_merge.Merger.from_mergeable(tree,
476
                    self.directive, progress.DummyProgress())
477
                merger.check_basis(True)
478
                merger.merge_type = _mod_merge.Merge3Merger
479
                merger.set_pending()
480
                conflict_count = merger.do_merge()
481
                self.destroy()
482
            except Exception, e:
483
                error_dialog('Error', str(e))
484
        finally:
485
            tree.unlock()
486
487
    def _get_merge_target(self):
488
        if self._merge_target is not None:
489
            return self._merge_target
490
        d = gtk.FileChooserDialog('Merge branch', self,
491
                                  gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
492
                                  buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
493
                                           gtk.STOCK_CANCEL,
494
                                           gtk.RESPONSE_CANCEL,))
495
        try:
496
            result = d.run()
497
            if result == gtk.RESPONSE_OK:
498
                uri = d.get_current_folder_uri()
499
                return workingtree.WorkingTree.open(uri)
500
            else:
501
                raise SelectCancelled()
502
        finally:
503
            d.destroy()
504
427 by Aaron Bentley
Add merge button when displaying merge directives
505
278.1.29 by John Arbash Meinel
Start testing with Unicode data.
506
def _iter_changes_to_status(source, target):
507
    """Determine the differences between trees.
508
509
    This is a wrapper around _iter_changes which just yields more
510
    understandable results.
511
512
    :param source: The source tree (basis tree)
513
    :param target: The target tree
514
    :return: A list of (file_id, real_path, change_type, display_path)
515
    """
516
    added = 'added'
517
    removed = 'removed'
518
    renamed = 'renamed'
519
    renamed_and_modified = 'renamed and modified'
520
    modified = 'modified'
521
    kind_changed = 'kind changed'
522
523
    # TODO: Handle metadata changes
524
525
    status = []
526
    target.lock_read()
527
    try:
528
        source.lock_read()
529
        try:
530
            for (file_id, paths, changed_content, versioned, parent_ids, names,
531
                 kinds, executables) in target._iter_changes(source):
532
533
                # Skip the root entry if it isn't very interesting
534
                if parent_ids == (None, None):
535
                    continue
536
537
                change_type = None
538
                if kinds[0] is None:
539
                    source_marker = ''
540
                else:
541
                    source_marker = osutils.kind_marker(kinds[0])
542
                if kinds[1] is None:
543
                    assert kinds[0] is not None
544
                    marker = osutils.kind_marker(kinds[0])
545
                else:
546
                    marker = osutils.kind_marker(kinds[1])
547
548
                real_path = paths[1]
549
                if real_path is None:
550
                    real_path = paths[0]
551
                assert real_path is not None
552
                display_path = real_path + marker
553
554
                present_source = versioned[0] and kinds[0] is not None
555
                present_target = versioned[1] and kinds[1] is not None
556
557
                if present_source != present_target:
558
                    if present_target:
559
                        change_type = added
560
                    else:
561
                        assert present_source
562
                        change_type = removed
563
                elif names[0] != names[1] or parent_ids[0] != parent_ids[1]:
564
                    # Renamed
565
                    if changed_content or executables[0] != executables[1]:
566
                        # and modified
567
                        change_type = renamed_and_modified
568
                    else:
569
                        change_type = renamed
570
                    display_path = (paths[0] + source_marker
571
                                    + ' => ' + paths[1] + marker)
572
                elif kinds[0] != kinds[1]:
573
                    change_type = kind_changed
574
                    display_path = (paths[0] + source_marker
575
                                    + ' => ' + paths[1] + marker)
576
                elif changed_content is True or executables[0] != executables[1]:
577
                    change_type = modified
578
                else:
579
                    assert False, "How did we get here?"
580
581
                status.append((file_id, real_path, change_type, display_path))
582
        finally:
583
            source.unlock()
584
    finally:
585
        target.unlock()
586
587
    return status