/b-gtk/fix-viz

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/b-gtk/fix-viz

« back to all changes in this revision

Viewing changes to diff.py

  • Committer: Curtis Hovey
  • Date: 2011-07-31 16:50:29 UTC
  • mto: This revision was merged to the branch mainline in revision 741.
  • Revision ID: sinzui.is@verizon.net-20110731165029-9gixuqypi3lwapzm
Removed import_pygtk because gi does not impicitly call Main(). Inlined checks for gtk availablility.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- coding: UTF-8 -*-
2
1
"""Difference window.
3
2
 
4
3
This module contains the code to manage the diff window which shows
5
4
the changes made between two revisions on a branch.
6
5
"""
7
6
 
8
 
__copyright__ = "Copyright © 2005 Canonical Ltd."
 
7
__copyright__ = "Copyright 2005 Canonical Ltd."
9
8
__author__    = "Scott James Remnant <scott@ubuntu.com>"
10
9
 
11
10
 
12
11
from cStringIO import StringIO
13
12
 
14
 
import pygtk
15
 
pygtk.require("2.0")
16
 
import gtk
17
 
import pango
 
13
from gi.repository import Gtk
 
14
from gi.repository import Pango
18
15
import os
19
16
import re
20
17
import sys
 
18
import inspect
 
19
try:
 
20
    from xml.etree.ElementTree import Element, SubElement, tostring
 
21
except ImportError:
 
22
    from elementtree.ElementTree import Element, SubElement, tostring
21
23
 
22
24
try:
23
 
    import gtksourceview
 
25
    from gi.repository import GtkSource
24
26
    have_gtksourceview = True
25
27
except ImportError:
26
28
    have_gtksourceview = False
27
29
try:
28
 
    import gconf
 
30
    from gi.repository import GConf
29
31
    have_gconf = True
30
32
except ImportError:
31
33
    have_gconf = False
32
34
 
33
35
from bzrlib import (
 
36
    errors,
34
37
    merge as _mod_merge,
35
38
    osutils,
36
 
    progress,
37
39
    urlutils,
38
40
    workingtree,
39
41
)
40
 
from bzrlib.diff import show_diff_trees, internal_diff
41
 
from bzrlib.errors import NoSuchFile
 
42
from bzrlib.diff import show_diff_trees
42
43
from bzrlib.patches import parse_patches
43
44
from bzrlib.trace import warning
44
 
from bzrlib.plugins.gtk import _i18n
 
45
from bzrlib.plugins.gtk.dialog import (
 
46
    error_dialog,
 
47
    info_dialog,
 
48
    warning_dialog,
 
49
    )
 
50
from bzrlib.plugins.gtk.i18n import _i18n
45
51
from bzrlib.plugins.gtk.window import Window
46
 
from dialog import error_dialog, info_dialog, warning_dialog
 
52
 
 
53
 
 
54
def fallback_guess_language(slm, content_type):
 
55
    for lang_id in slm.get_language_ids():
 
56
        lang = slm.get_language(lang_id)
 
57
        if "text/x-patch" in lang.get_mime_types():
 
58
            return lang
 
59
    return None
47
60
 
48
61
 
49
62
class SelectCancelled(Exception):
51
64
    pass
52
65
 
53
66
 
54
 
class DiffFileView(gtk.ScrolledWindow):
 
67
class DiffFileView(Gtk.ScrolledWindow):
55
68
    """Window for displaying diffs from a diff file"""
56
69
 
57
70
    def __init__(self):
58
 
        gtk.ScrolledWindow.__init__(self)
 
71
        GObject.GObject.__init__(self)
59
72
        self.construct()
60
73
        self._diffs = {}
61
74
 
62
75
    def construct(self):
63
 
        self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
64
 
        self.set_shadow_type(gtk.SHADOW_IN)
 
76
        self.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
 
77
        self.set_shadow_type(Gtk.ShadowType.IN)
65
78
 
66
79
        if have_gtksourceview:
67
 
            self.buffer = gtksourceview.SourceBuffer()
68
 
            slm = gtksourceview.SourceLanguagesManager()
69
 
            gsl = slm.get_language_from_mime_type("text/x-patch")
 
80
            self.buffer = GtkSource.Buffer()
 
81
            slm = GtkSource.LanguageManager()
 
82
            guess_language = getattr(GtkSource.LanguageManager, 
 
83
                "guess_language", fallback_guess_language)
 
84
            gsl = guess_language(slm, content_type="text/x-patch")
70
85
            if have_gconf:
71
 
                self.apply_gedit_colors(gsl)
72
 
            self.apply_colordiff_colors(gsl)
 
86
                self.apply_gedit_colors(self.buffer)
 
87
            self.apply_colordiff_colors(self.buffer)
73
88
            self.buffer.set_language(gsl)
74
 
            self.buffer.set_highlight(True)
 
89
            self.buffer.set_highlight_syntax(True)
75
90
 
76
 
            self.sourceview = gtksourceview.SourceView(self.buffer)
 
91
            self.sourceview = GtkSource.View(self.buffer)
77
92
        else:
78
 
            self.buffer = gtk.TextBuffer()
79
 
            self.sourceview = gtk.TextView(self.buffer)
 
93
            self.buffer = Gtk.TextBuffer()
 
94
            self.sourceview = Gtk.TextView(self.buffer)
80
95
 
81
96
        self.sourceview.set_editable(False)
82
 
        self.sourceview.modify_font(pango.FontDescription("Monospace"))
 
97
        self.sourceview.modify_font(Pango.FontDescription("Monospace"))
83
98
        self.add(self.sourceview)
84
99
        self.sourceview.show()
85
100
 
86
101
    @staticmethod
87
 
    def apply_gedit_colors(lang):
88
 
        """Set style for lang to that specified in gedit configuration.
 
102
    def apply_gedit_colors(buf):
 
103
        """Set style to that specified in gedit configuration.
89
104
 
90
105
        This method needs the gconf module.
91
106
 
92
 
        :param lang: a gtksourceview.SourceLanguage object.
 
107
        :param buf: a GtkSource.Buffer object.
93
108
        """
94
 
        GEDIT_SYNTAX_PATH = '/apps/gedit-2/preferences/syntax_highlighting'
95
 
        GEDIT_LANG_PATH = GEDIT_SYNTAX_PATH + '/' + lang.get_id()
96
 
 
97
 
        client = gconf.client_get_default()
98
 
        client.add_dir(GEDIT_LANG_PATH, gconf.CLIENT_PRELOAD_NONE)
99
 
 
100
 
        for tag in lang.get_tags():
101
 
            tag_id = tag.get_id()
102
 
            gconf_key = GEDIT_LANG_PATH + '/' + tag_id
103
 
            style_string = client.get_string(gconf_key)
104
 
 
105
 
            if style_string is None:
106
 
                continue
107
 
 
108
 
            # function to get a bool from a string that's either '0' or '1'
109
 
            string_bool = lambda x: bool(int(x))
110
 
 
111
 
            # style_string is a string like "2/#FFCCAA/#000000/0/1/0/0"
112
 
            # values are: mask, fg, bg, italic, bold, underline, strike
113
 
            # this packs them into (str_value, attr_name, conv_func) tuples
114
 
            items = zip(style_string.split('/'), ['mask', 'foreground',
115
 
                'background', 'italic', 'bold', 'underline', 'strikethrough' ],
116
 
                [ int, gtk.gdk.color_parse, gtk.gdk.color_parse, string_bool,
117
 
                    string_bool, string_bool, string_bool ]
118
 
            )
119
 
 
120
 
            style = gtksourceview.SourceTagStyle()
121
 
 
122
 
            # XXX The mask attribute controls whether the present values of
123
 
            # foreground and background color should in fact be used. Ideally
124
 
            # (and that's what gedit does), one could set all three attributes,
125
 
            # and let the TagStyle object figure out which colors to use.
126
 
            # However, in the GtkSourceview python bindings, the mask attribute
127
 
            # is read-only, and it's derived instead from the colors being
128
 
            # set or not. This means that we have to sometimes refrain from
129
 
            # setting fg or bg colors, depending on the value of the mask.
130
 
            # This code could go away if mask were writable.
131
 
            mask = int(items[0][0])
132
 
            if not (mask & 1): # GTK_SOURCE_TAG_STYLE_USE_BACKGROUND
133
 
                items[2:3] = []
134
 
            if not (mask & 2): # GTK_SOURCE_TAG_STYLE_USE_FOREGROUND
135
 
                items[1:2] = []
136
 
            items[0:1] = [] # skip the mask unconditionally
137
 
 
138
 
            for value, attr, func in items:
139
 
                try:
140
 
                    value = func(value)
141
 
                except ValueError:
142
 
                    warning('gconf key %s contains an invalid value: %s'
143
 
                            % gconf_key, value)
144
 
                else:
145
 
                    setattr(style, attr, value)
146
 
 
147
 
            lang.set_tag_style(tag_id, style)
 
109
        GEDIT_SCHEME_PATH = '/apps/gedit-2/preferences/editor/colors/scheme'
 
110
        GEDIT_USER_STYLES_PATH = os.path.expanduser('~/.gnome2/gedit/styles')
 
111
 
 
112
        client = GConf.Client.get_default()
 
113
        style_scheme_name = client.get_string(GEDIT_SCHEME_PATH)
 
114
        if style_scheme_name is not None:
 
115
            style_scheme_mgr = GtkSource.StyleSchemeManager()
 
116
            style_scheme_mgr.append_search_path(GEDIT_USER_STYLES_PATH)
 
117
            
 
118
            style_scheme = style_scheme_mgr.get_scheme(style_scheme_name)
 
119
            
 
120
            if style_scheme is not None:
 
121
                buf.set_style_scheme(style_scheme)
148
122
 
149
123
    @classmethod
150
 
    def apply_colordiff_colors(klass, lang):
 
124
    def apply_colordiff_colors(klass, buf):
151
125
        """Set style colors for lang using the colordiff configuration file.
152
126
 
153
127
        Both ~/.colordiffrc and ~/.colordiffrc.bzr-gtk are read.
154
128
 
155
 
        :param lang: a "Diff" gtksourceview.SourceLanguage object.
 
129
        :param buf: a "Diff" GtkSource.Buffer object.
156
130
        """
157
 
        colors = {}
158
 
 
159
 
        for f in ('~/.colordiffrc', '~/.colordiffrc.bzr-gtk'):
160
 
            f = os.path.expanduser(f)
161
 
            if os.path.exists(f):
162
 
                try:
163
 
                    f = file(f)
164
 
                except IOError, e:
165
 
                    warning('could not open file %s: %s' % (f, str(e)))
166
 
                else:
167
 
                    colors.update(klass.parse_colordiffrc(f))
168
 
                    f.close()
169
 
 
170
 
        if not colors:
171
 
            # ~/.colordiffrc does not exist
172
 
            return
173
 
 
174
 
        mapping = {
175
 
                # map GtkSourceView tags to colordiff names
 
131
        scheme_manager = GtkSource.StyleSchemeManager()
 
132
        style_scheme = scheme_manager.get_scheme('colordiff')
 
133
        
 
134
        # if style scheme not found, we'll generate it from colordiffrc
 
135
        # TODO: reload if colordiffrc has changed.
 
136
        if style_scheme is None:
 
137
            colors = {}
 
138
 
 
139
            for f in ('~/.colordiffrc', '~/.colordiffrc.bzr-gtk'):
 
140
                f = os.path.expanduser(f)
 
141
                if os.path.exists(f):
 
142
                    try:
 
143
                        f = file(f)
 
144
                    except IOError, e:
 
145
                        warning('could not open file %s: %s' % (f, str(e)))
 
146
                    else:
 
147
                        colors.update(klass.parse_colordiffrc(f))
 
148
                        f.close()
 
149
 
 
150
            if not colors:
 
151
                # ~/.colordiffrc does not exist
 
152
                return
 
153
            
 
154
            mapping = {
 
155
                # map GtkSourceView2 scheme styles to colordiff names
176
156
                # since GSV is richer, accept new names for extra bits,
177
157
                # defaulting to old names if they're not present
178
 
                'Added@32@line': ['newtext'],
179
 
                'Removed@32@line': ['oldtext'],
180
 
                'Location': ['location', 'diffstuff'],
181
 
                'Diff@32@file': ['file', 'diffstuff'],
182
 
                'Special@32@case': ['specialcase', 'diffstuff'],
183
 
        }
184
 
 
185
 
        for tag in lang.get_tags():
186
 
            tag_id = tag.get_id()
187
 
            keys = mapping.get(tag_id, [])
188
 
            color = None
189
 
 
190
 
            for key in keys:
191
 
                color = colors.get(key, None)
192
 
                if color is not None:
193
 
                    break
194
 
 
195
 
            if color is None:
196
 
                continue
197
 
 
198
 
            style = gtksourceview.SourceTagStyle()
199
 
            try:
200
 
                style.foreground = gtk.gdk.color_parse(color)
201
 
            except ValueError:
202
 
                warning('not a valid color: %s' % color)
203
 
            else:
204
 
                lang.set_tag_style(tag_id, style)
 
158
                'diff:added-line': ['newtext'],
 
159
                'diff:removed-line': ['oldtext'],
 
160
                'diff:location': ['location', 'diffstuff'],
 
161
                'diff:file': ['file', 'diffstuff'],
 
162
                'diff:special-case': ['specialcase', 'diffstuff'],
 
163
            }
 
164
            
 
165
            converted_colors = {}
 
166
            for name, values in mapping.items():
 
167
                color = None
 
168
                for value in values:
 
169
                    color = colors.get(value, None)
 
170
                    if color is not None:
 
171
                        break
 
172
                if color is None:
 
173
                    continue
 
174
                converted_colors[name] = color
 
175
            
 
176
            # some xml magic to produce needed style scheme description
 
177
            e_style_scheme = Element('style-scheme')
 
178
            e_style_scheme.set('id', 'colordiff')
 
179
            e_style_scheme.set('_name', 'ColorDiff')
 
180
            e_style_scheme.set('version', '1.0')
 
181
            for name, color in converted_colors.items():
 
182
                style = SubElement(e_style_scheme, 'style')
 
183
                style.set('name', name)
 
184
                style.set('foreground', '#%s' % color)
 
185
            
 
186
            scheme_xml = tostring(e_style_scheme, 'UTF-8')
 
187
            if not os.path.exists(os.path.expanduser('~/.local/share/gtksourceview-2.0/styles')):
 
188
                os.makedirs(os.path.expanduser('~/.local/share/gtksourceview-2.0/styles'))
 
189
            file(os.path.expanduser('~/.local/share/gtksourceview-2.0/styles/colordiff.xml'), 'w').write(scheme_xml)
 
190
            
 
191
            scheme_manager.force_rescan()
 
192
            style_scheme = scheme_manager.get_scheme('colordiff')
 
193
        
 
194
        buf.set_style_scheme(style_scheme)
205
195
 
206
196
    @staticmethod
207
197
    def parse_colordiffrc(fileobj):
292
282
        self.buffer.set_text(decoded.encode('UTF-8'))
293
283
 
294
284
 
295
 
class DiffWidget(gtk.HPaned):
 
285
class DiffWidget(Gtk.HPaned):
296
286
    """Diff widget
297
287
 
298
288
    """
300
290
        super(DiffWidget, self).__init__()
301
291
 
302
292
        # The file hierarchy: a scrollable treeview
303
 
        scrollwin = gtk.ScrolledWindow()
304
 
        scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
305
 
        scrollwin.set_shadow_type(gtk.SHADOW_IN)
 
293
        scrollwin = Gtk.ScrolledWindow()
 
294
        scrollwin.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
 
295
        scrollwin.set_shadow_type(Gtk.ShadowType.IN)
306
296
        self.pack1(scrollwin)
307
297
        scrollwin.show()
308
298
        
309
 
        self.model = gtk.TreeStore(str, str)
310
 
        self.treeview = gtk.TreeView(self.model)
 
299
        self.model = Gtk.TreeStore(str, str)
 
300
        self.treeview = Gtk.TreeView(self.model)
311
301
        self.treeview.set_headers_visible(False)
312
302
        self.treeview.set_search_column(1)
313
303
        self.treeview.connect("cursor-changed", self._treeview_cursor_cb)
314
304
        scrollwin.add(self.treeview)
315
305
        self.treeview.show()
316
306
 
317
 
        cell = gtk.CellRendererText()
 
307
        cell = Gtk.CellRendererText()
318
308
        cell.set_property("width-chars", 20)
319
 
        column = gtk.TreeViewColumn()
320
 
        column.pack_start(cell, expand=True)
 
309
        column = Gtk.TreeViewColumn()
 
310
        column.pack_start(cell, True, True, 0)
321
311
        column.add_attribute(cell, "text", 0)
322
312
        self.treeview.append_column(column)
323
313
 
393
383
                    tv_path = child.path
394
384
                    break
395
385
        if tv_path is None:
396
 
            raise NoSuchFile(file_path)
 
386
            raise errors.NoSuchFile(file_path)
397
387
        self.treeview.set_cursor(tv_path)
398
388
        self.treeview.scroll_to_cell(tv_path)
399
389
 
411
401
    def _on_wraplines_toggled(self, widget=None, wrap=False):
412
402
        """Callback for when the wrap lines checkbutton is toggled"""
413
403
        if wrap or widget.get_active():
414
 
            self.diff_view.sourceview.set_wrap_mode(gtk.WRAP_WORD)
 
404
            self.diff_view.sourceview.set_wrap_mode(Gtk.WrapMode.WORD)
415
405
        else:
416
 
            self.diff_view.sourceview.set_wrap_mode(gtk.WRAP_NONE)
 
406
            self.diff_view.sourceview.set_wrap_mode(Gtk.WrapMode.NONE)
417
407
 
418
408
class DiffWindow(Window):
419
409
    """Diff window.
437
427
 
438
428
    def construct(self, operations):
439
429
        """Construct the window contents."""
440
 
        self.vbox = gtk.VBox()
 
430
        self.vbox = Gtk.VBox()
441
431
        self.add(self.vbox)
442
432
        self.vbox.show()
443
433
        self.diff = DiffWidget()
452
442
        
453
443
    
454
444
    def _get_menu_bar(self):
455
 
        menubar = gtk.MenuBar()
 
445
        menubar = Gtk.MenuBar()
456
446
        # View menu
457
 
        mb_view = gtk.MenuItem(_i18n("_View"))
458
 
        mb_view_menu = gtk.Menu()
459
 
        mb_view_wrapsource = gtk.CheckMenuItem(_i18n("Wrap _Long Lines"))
 
447
        mb_view = Gtk.MenuItem(_i18n("_View"))
 
448
        mb_view_menu = Gtk.Menu()
 
449
        mb_view_wrapsource = Gtk.CheckMenuItem(_i18n("Wrap _Long Lines"))
460
450
        mb_view_wrapsource.connect('activate', self.diff._on_wraplines_toggled)
461
451
        mb_view_wrapsource.show()
462
452
        mb_view_menu.append(mb_view_wrapsource)
474
464
        """
475
465
        if operations is None:
476
466
            return None
477
 
        hbox = gtk.HButtonBox()
478
 
        hbox.set_layout(gtk.BUTTONBOX_START)
 
467
        hbox = Gtk.HButtonBox()
 
468
        hbox.set_layout(Gtk.ButtonBoxStyle.START)
479
469
        for title, method in operations:
480
 
            merge_button = gtk.Button(title)
 
470
            merge_button = Gtk.Button(title)
481
471
            merge_button.show()
482
 
            merge_button.set_relief(gtk.RELIEF_NONE)
 
472
            merge_button.set_relief(Gtk.ReliefStyle.NONE)
483
473
            merge_button.connect("clicked", method)
484
474
            hbox.pack_start(merge_button, expand=False, fill=True)
485
475
        hbox.show()
486
476
        return hbox
487
477
 
488
478
    def _get_merge_target(self):
489
 
        d = gtk.FileChooserDialog('Merge branch', self,
490
 
                                  gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
491
 
                                  buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
492
 
                                           gtk.STOCK_CANCEL,
493
 
                                           gtk.RESPONSE_CANCEL,))
 
479
        d = Gtk.FileChooserDialog('Merge branch', self,
 
480
                                  Gtk.FileChooserAction.SELECT_FOLDER,
 
481
                                  buttons=(Gtk.STOCK_OK, Gtk.ResponseType.OK,
 
482
                                           Gtk.STOCK_CANCEL,
 
483
                                           Gtk.ResponseType.CANCEL,))
494
484
        try:
495
485
            result = d.run()
496
 
            if result != gtk.RESPONSE_OK:
 
486
            if result != Gtk.ResponseType.OK:
497
487
                raise SelectCancelled()
498
488
            return d.get_current_folder_uri()
499
489
        finally:
513
503
        error_dialog('Error', str(e))
514
504
 
515
505
    def _get_save_path(self, basename):
516
 
        d = gtk.FileChooserDialog('Save As', self,
517
 
                                  gtk.FILE_CHOOSER_ACTION_SAVE,
518
 
                                  buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
519
 
                                           gtk.STOCK_CANCEL,
520
 
                                           gtk.RESPONSE_CANCEL,))
 
506
        d = Gtk.FileChooserDialog('Save As', self,
 
507
                                  Gtk.FileChooserAction.SAVE,
 
508
                                  buttons=(Gtk.STOCK_OK, Gtk.ResponseType.OK,
 
509
                                           Gtk.STOCK_CANCEL,
 
510
                                           Gtk.ResponseType.CANCEL,))
521
511
        d.set_current_name(basename)
522
512
        try:
523
513
            result = d.run()
524
 
            if result != gtk.RESPONSE_OK:
 
514
            if result != Gtk.ResponseType.OK:
525
515
                raise SelectCancelled()
526
516
            return urlutils.local_path_from_url(d.get_uri())
527
517
        finally:
542
532
 
543
533
class DiffController(object):
544
534
 
545
 
    def __init__(self, path, patch, window=None):
 
535
    def __init__(self, path, patch, window=None, allow_dirty=False):
546
536
        self.path = path
547
537
        self.patch = patch
 
538
        self.allow_dirty = allow_dirty
548
539
        if window is None:
549
540
            window = DiffWindow(operations=self._provide_operations())
550
541
            self.initialize_window(window)
556
547
 
557
548
    def get_diff_sections(self):
558
549
        yield "Complete Diff", None, ''.join(self.patch)
559
 
        for patch in parse_patches(self.patch):
 
550
        # allow_dirty was added to parse_patches in bzrlib 2.2b1
 
551
        if 'allow_dirty' in inspect.getargspec(parse_patches).args:
 
552
            patches = parse_patches(self.patch, allow_dirty=self.allow_dirty)
 
553
        else:
 
554
            patches = parse_patches(self.patch)
 
555
        for patch in patches:
560
556
            oldname = patch.oldname.split('\t')[0]
561
557
            newname = patch.newname.split('\t')[0]
562
558
            yield oldname, newname, str(patch)
601
597
        tree.lock_write()
602
598
        try:
603
599
            try:
604
 
                merger, verified = _mod_merge.Merger.from_mergeable(tree,
605
 
                    self.directive, progress.DummyProgress())
606
 
                merger.check_basis(True)
 
600
                if tree.has_changes():
 
601
                    raise errors.UncommittedChanges(tree)
 
602
                merger, verified = _mod_merge.Merger.from_mergeable(
 
603
                    tree, self.directive, pb=None)
607
604
                merger.merge_type = _mod_merge.Merge3Merger
608
605
                conflict_count = merger.do_merge()
609
606
                merger.set_pending()
635
632
    renamed_and_modified = 'renamed and modified'
636
633
    modified = 'modified'
637
634
    kind_changed = 'kind changed'
 
635
    missing = 'missing'
638
636
 
639
637
    # TODO: Handle metadata changes
640
638
 
655
653
                    source_marker = ''
656
654
                else:
657
655
                    source_marker = osutils.kind_marker(kinds[0])
 
656
 
658
657
                if kinds[1] is None:
659
 
                    assert kinds[0] is not None
660
 
                    marker = osutils.kind_marker(kinds[0])
 
658
                    if kinds[0] is None:
 
659
                        # We assume bzr will flag only files in that case,
 
660
                        # there may be a bzr bug there as only files seems to
 
661
                        # not receive any kind.
 
662
                        marker = osutils.kind_marker('file')
 
663
                    else:
 
664
                        marker = osutils.kind_marker(kinds[0])
661
665
                else:
662
666
                    marker = osutils.kind_marker(kinds[1])
663
667
 
665
669
                if real_path is None:
666
670
                    real_path = paths[0]
667
671
                assert real_path is not None
668
 
                display_path = real_path + marker
669
672
 
670
673
                present_source = versioned[0] and kinds[0] is not None
671
674
                present_target = versioned[1] and kinds[1] is not None
672
675
 
673
 
                if present_source != present_target:
 
676
                if kinds[0] is None and kinds[1] is None:
 
677
                    change_type = missing
 
678
                    display_path = real_path + marker
 
679
                elif present_source != present_target:
674
680
                    if present_target:
675
681
                        change_type = added
676
682
                    else:
677
683
                        assert present_source
678
684
                        change_type = removed
 
685
                    display_path = real_path + marker
679
686
                elif names[0] != names[1] or parent_ids[0] != parent_ids[1]:
680
687
                    # Renamed
681
688
                    if changed_content or executables[0] != executables[1]:
689
696
                    change_type = kind_changed
690
697
                    display_path = (paths[0] + source_marker
691
698
                                    + ' => ' + paths[1] + marker)
692
 
                elif changed_content is True or executables[0] != executables[1]:
 
699
                elif changed_content or executables[0] != executables[1]:
693
700
                    change_type = modified
 
701
                    display_path = real_path + marker
694
702
                else:
695
703
                    assert False, "How did we get here?"
696
704