/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 commit.py

  • Committer: Curtis Hovey
  • Date: 2012-03-20 12:39:08 UTC
  • mfrom: (776.3.23 gpush-do-push)
  • Revision ID: sinzui.is@verizon.net-20120320123908-lclvaiasu0e50odg
Merged faster push.

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
import re
18
18
 
19
 
try:
20
 
    import pygtk
21
 
    pygtk.require("2.0")
22
 
except:
23
 
    pass
24
 
 
25
 
import gtk
26
 
import gobject
27
 
import pango
 
19
from gi.repository import Gdk
 
20
from gi.repository import Gtk
 
21
from gi.repository import GObject
 
22
from gi.repository import Pango
28
23
 
29
24
from bzrlib import (
 
25
    bencode,
30
26
    errors,
31
27
    osutils,
 
28
    revision as _mod_revision,
32
29
    trace,
 
30
    tsort,
33
31
    )
34
 
try:
35
 
    from bzrlib import bencode
36
 
except ImportError:
37
 
    from bzrlib.util import bencode
38
 
 
39
 
from bzrlib.plugins.gtk import _i18n
40
32
from bzrlib.plugins.gtk.dialog import question_dialog
41
33
from bzrlib.plugins.gtk.errors import show_bzr_error
 
34
from bzrlib.plugins.gtk.i18n import _i18n
 
35
from bzrlib.plugins.gtk.commitmsgs import SavedCommitMessagesManager
42
36
 
43
37
try:
44
38
    import dbus
48
42
    have_dbus = False
49
43
 
50
44
 
 
45
def _get_sorted_revisions(tip_revision, revision_ids, parent_map):
 
46
    """Get an iterator which will return the revisions in merge sorted order.
 
47
 
 
48
    This will build up a list of all nodes, such that only nodes in the list
 
49
    are referenced. It then uses MergeSorter to return them in 'merge-sorted'
 
50
    order.
 
51
 
 
52
    :param revision_ids: A set of revision_ids
 
53
    :param parent_map: The parent information for each node. Revisions which
 
54
        are considered ghosts should not be present in the map.
 
55
    :return: iterator from MergeSorter.iter_topo_order()
 
56
    """
 
57
    # MergeSorter requires that all nodes be present in the graph, so get rid
 
58
    # of any references pointing outside of this graph.
 
59
    parent_graph = {}
 
60
    for revision_id in revision_ids:
 
61
        if revision_id not in parent_map: # ghost
 
62
            parent_graph[revision_id] = []
 
63
        else:
 
64
            # Only include parents which are in this sub-graph
 
65
            parent_graph[revision_id] = [p for p in parent_map[revision_id]
 
66
                                            if p in revision_ids]
 
67
    sorter = tsort.MergeSorter(parent_graph, tip_revision)
 
68
    return sorter.iter_topo_order()
 
69
 
 
70
 
51
71
def pending_revisions(wt):
52
72
    """Return a list of pending merges or None if there are none of them.
53
73
 
58
78
    """
59
79
    parents = wt.get_parent_ids()
60
80
    if len(parents) < 2:
61
 
        return None
 
81
        return
62
82
 
63
83
    # The basic pending merge algorithm uses the same algorithm as
64
84
    # bzrlib.status.show_pending_merges
66
86
    branch = wt.branch
67
87
    last_revision = parents[0]
68
88
 
69
 
    if last_revision is not None:
70
 
        try:
71
 
            ignore = set(branch.repository.get_ancestry(last_revision,
72
 
                                                        topo_sorted=False))
73
 
        except errors.NoSuchRevision:
74
 
            # the last revision is a ghost : assume everything is new
75
 
            # except for it
76
 
            ignore = set([None, last_revision])
77
 
    else:
78
 
        ignore = set([None])
 
89
    graph = branch.repository.get_graph()
 
90
    other_revisions = [last_revision]
79
91
 
80
92
    pm = []
81
93
    for merge in pending:
82
 
        ignore.add(merge)
83
 
        try:
84
 
            rev = branch.repository.get_revision(merge)
85
 
            children = []
86
 
            pm.append((rev, children))
87
 
 
88
 
            # This does need to be topo sorted, so we search backwards
89
 
            inner_merges = branch.repository.get_ancestry(merge)
90
 
            assert inner_merges[0] is None
91
 
            inner_merges.pop(0)
92
 
            for mmerge in reversed(inner_merges):
93
 
                if mmerge in ignore:
94
 
                    continue
95
 
                rev = branch.repository.get_revision(mmerge)
96
 
                children.append(rev)
97
 
 
98
 
                ignore.add(mmerge)
99
 
        except errors.NoSuchRevision:
100
 
            print "DEBUG: NoSuchRevision:", merge
101
 
 
102
 
    return pm
 
94
        try:
 
95
            merge_rev = branch.repository.get_revision(merge)
 
96
        except errors.NoSuchRevision:
 
97
            # If we are missing a revision, just print out the revision id
 
98
            trace.mutter("ghost: %r", merge)
 
99
            other_revisions.append(merge)
 
100
            continue
 
101
 
 
102
        # Find all of the revisions in the merge source, which are not in the
 
103
        # last committed revision.
 
104
        merge_extra = graph.find_unique_ancestors(merge, other_revisions)
 
105
        other_revisions.append(merge)
 
106
        merge_extra.discard(_mod_revision.NULL_REVISION)
 
107
 
 
108
        # Get a handle to all of the revisions we will need
 
109
        try:
 
110
            revisions = dict((rev.revision_id, rev) for rev in
 
111
                             branch.repository.get_revisions(merge_extra))
 
112
        except errors.NoSuchRevision:
 
113
            # One of the sub nodes is a ghost, check each one
 
114
            revisions = {}
 
115
            for revision_id in merge_extra:
 
116
                try:
 
117
                    rev = branch.repository.get_revisions([revision_id])[0]
 
118
                except errors.NoSuchRevision:
 
119
                    revisions[revision_id] = None
 
120
                else:
 
121
                    revisions[revision_id] = rev
 
122
 
 
123
         # Display the revisions brought in by this merge.
 
124
        rev_id_iterator = _get_sorted_revisions(merge, merge_extra,
 
125
                            branch.repository.get_parent_map(merge_extra))
 
126
        # Skip the first node
 
127
        num, first, depth, eom = rev_id_iterator.next()
 
128
        if first != merge:
 
129
            raise AssertionError('Somehow we misunderstood how'
 
130
                ' iter_topo_order works %s != %s' % (first, merge))
 
131
        children = []
 
132
        for num, sub_merge, depth, eom in rev_id_iterator:
 
133
            rev = revisions[sub_merge]
 
134
            if rev is None:
 
135
                trace.warning("ghost: %r", sub_merge)
 
136
                continue
 
137
            children.append(rev)
 
138
        yield (merge_rev, children)
103
139
 
104
140
 
105
141
_newline_variants_re = re.compile(r'\r\n?')
106
142
def _sanitize_and_decode_message(utf8_message):
107
143
    """Turn a utf-8 message into a sanitized Unicode message."""
108
144
    fixed_newline = _newline_variants_re.sub('\n', utf8_message)
109
 
    return fixed_newline.decode('utf-8')
110
 
 
111
 
 
112
 
class CommitDialog(gtk.Dialog):
 
145
    return osutils.safe_unicode(fixed_newline)
 
146
 
 
147
 
 
148
class CommitDialog(Gtk.Dialog):
113
149
    """Implementation of Commit."""
114
150
 
115
151
    def __init__(self, wt, selected=None, parent=None):
116
 
        gtk.Dialog.__init__(self, title="Commit to %s" % wt.basedir,
117
 
                            parent=parent, flags=0,)
 
152
        super(CommitDialog, self).__init__(
 
153
            title="Commit to %s" % wt.basedir, parent=parent, flags=0)
118
154
        self.connect('delete-event', self._on_delete_window)
119
155
        self._question_dialog = question_dialog
120
156
 
121
 
        self.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_NORMAL)
 
157
        self.set_type_hint(Gdk.WindowTypeHint.NORMAL)
122
158
 
123
159
        self._wt = wt
124
160
        # TODO: Do something with this value, it is used by Olive
127
163
        self._enable_per_file_commits = True
128
164
        self._commit_all_changes = True
129
165
        self.committed_revision_id = None # Nothing has been committed yet
130
 
        self._saved_commit_messages_manager = SavedCommitMessagesManager(self._wt, self._wt.branch)
 
166
        self._last_selected_file = None
 
167
        self._saved_commit_messages_manager = SavedCommitMessagesManager(
 
168
            self._wt, self._wt.branch)
131
169
 
132
170
        self.setup_params()
133
171
        self.construct()
137
175
        """Setup the member variables for state."""
138
176
        self._basis_tree = self._wt.basis_tree()
139
177
        self._delta = None
140
 
        self._pending = pending_revisions(self._wt)
 
178
        self._wt.lock_read()
 
179
        try:
 
180
            self._pending = list(pending_revisions(self._wt))
 
181
        finally:
 
182
            self._wt.unlock()
141
183
 
142
184
        self._is_checkout = (self._wt.branch.get_bound_location() is not None)
143
185
 
195
237
 
196
238
        all_enabled = (self._selected is None)
197
239
        # The first entry is always the 'whole tree'
198
 
        all_iter = store.append([None, None, all_enabled, 'All Files', '', ''])
 
240
        all_iter = store.append(["", "", all_enabled, 'All Files', '', ''])
199
241
        initial_cursor = store.get_path(all_iter)
200
242
        # should we pass specific_files?
201
243
        self._wt.lock_read()
232
274
        # This sets the cursor, which causes the expander to close, which
233
275
        # causes the _file_message_text_view to never get realized. So we have
234
276
        # to give it a little kick, or it warns when we try to grab the focus
235
 
        self._treeview_files.set_cursor(initial_cursor)
 
277
        self._treeview_files.set_cursor(initial_cursor, None, False)
236
278
 
237
279
        def _realize_file_message_tree_view(*args):
238
280
            self._file_message_text_view.realize()
246
288
            self._check_local.hide()
247
289
            return
248
290
        if have_dbus:
249
 
            bus = dbus.SystemBus()
 
291
            try:
 
292
                bus = dbus.SystemBus()
 
293
            except dbus.DBusException:
 
294
                trace.mutter("DBus system bus not available")
 
295
                self._check_local.show()
 
296
                return
250
297
            try:
251
298
                proxy_obj = bus.get_object('org.freedesktop.NetworkManager',
252
299
                                           '/org/freedesktop/NetworkManager')
254
301
                trace.mutter("networkmanager not available.")
255
302
                self._check_local.show()
256
303
                return
257
 
            
 
304
 
258
305
            dbus_iface = dbus.Interface(proxy_obj,
259
306
                                        'org.freedesktop.NetworkManager')
260
307
            try:
286
333
        """Build up the dialog widgets."""
287
334
        # The primary pane which splits it into left and right (adjustable)
288
335
        # sections.
289
 
        self._hpane = gtk.HPaned()
 
336
        self._hpane = Gtk.Paned.new(Gtk.Orientation.HORIZONTAL)
290
337
 
291
338
        self._construct_left_pane()
292
339
        self._construct_right_pane()
293
340
        self._construct_action_pane()
294
341
 
295
 
        self.vbox.pack_start(self._hpane)
 
342
        self.get_content_area().pack_start(self._hpane, True, True, 0)
296
343
        self._hpane.show()
297
344
        self.set_focus(self._global_message_text_view)
298
345
 
317
364
        self._hpane.set_position(300)
318
365
 
319
366
    def _construct_accelerators(self):
320
 
        group = gtk.AccelGroup()
321
 
        group.connect_group(gtk.gdk.keyval_from_name('N'),
322
 
                            gtk.gdk.CONTROL_MASK, 0, self._on_accel_next)
 
367
        group = Gtk.AccelGroup()
 
368
        group.connect(Gdk.keyval_from_name('N'),
 
369
                      Gdk.ModifierType.CONTROL_MASK, 0, self._on_accel_next)
323
370
        self.add_accel_group(group)
324
371
 
325
372
        # ignore the escape key (avoid closing the window)
326
373
        self.connect_object('close', self.emit_stop_by_name, 'close')
327
374
 
328
375
    def _construct_left_pane(self):
329
 
        self._left_pane_box = gtk.VBox(homogeneous=False, spacing=5)
 
376
        self._left_pane_box = Gtk.VBox(homogeneous=False, spacing=5)
330
377
        self._construct_file_list()
331
378
        self._construct_pending_list()
332
379
 
333
 
        self._check_local = gtk.CheckButton(_i18n("_Only commit locally"),
 
380
        self._check_local = Gtk.CheckButton(_i18n("_Only commit locally"),
334
381
                                            use_underline=True)
335
 
        self._left_pane_box.pack_end(self._check_local, False, False)
 
382
        self._left_pane_box.pack_end(self._check_local, False, False, 0)
336
383
        self._check_local.set_active(False)
337
384
 
338
385
        self._hpane.pack1(self._left_pane_box, resize=False, shrink=False)
345
392
        # commit, and 1 for file commit, and it looked good. But I don't seem
346
393
        # to have a way to do that with the gtk boxes... :( (Which is extra
347
394
        # weird since wx uses gtk on Linux...)
348
 
        self._right_pane_table = gtk.Table(rows=10, columns=1, homogeneous=False)
 
395
        self._right_pane_table = Gtk.Table(rows=10, columns=1, homogeneous=False)
349
396
        self._right_pane_table.set_row_spacings(5)
350
397
        self._right_pane_table.set_col_spacings(5)
351
398
        self._right_pane_table_row = 0
357
404
        self._hpane.pack2(self._right_pane_table, resize=True, shrink=True)
358
405
 
359
406
    def _construct_action_pane(self):
360
 
        self._button_cancel = gtk.Button(stock=gtk.STOCK_CANCEL)
 
407
        self._button_cancel = Gtk.Button(stock=Gtk.STOCK_CANCEL)
361
408
        self._button_cancel.connect('clicked', self._on_cancel_clicked)
362
409
        self._button_cancel.show()
363
 
        self.action_area.pack_end(self._button_cancel)
364
 
        self._button_commit = gtk.Button(_i18n("Comm_it"), use_underline=True)
 
410
        self.get_action_area().pack_end(
 
411
            self._button_cancel, True, True, 0)
 
412
        self._button_commit = Gtk.Button(_i18n("Comm_it"), use_underline=True)
365
413
        self._button_commit.connect('clicked', self._on_commit_clicked)
366
 
        self._button_commit.set_flags(gtk.CAN_DEFAULT)
 
414
        self._button_commit.set_can_default(True)
367
415
        self._button_commit.show()
368
 
        self.action_area.pack_end(self._button_commit)
 
416
        self.get_action_area().pack_end(
 
417
            self._button_commit, True, True, 0)
369
418
        self._button_commit.grab_default()
370
419
 
371
420
    def _add_to_right_table(self, widget, weight, expanding=False):
377
426
        """
378
427
        end_row = self._right_pane_table_row + weight
379
428
        options = 0
380
 
        expand_opts = gtk.EXPAND | gtk.FILL | gtk.SHRINK
 
429
        expand_opts = Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL | Gtk.AttachOptions.SHRINK
381
430
        if expanding:
382
431
            options = expand_opts
383
432
        self._right_pane_table.attach(widget, 0, 1,
386
435
        self._right_pane_table_row = end_row
387
436
 
388
437
    def _construct_file_list(self):
389
 
        self._files_box = gtk.VBox(homogeneous=False, spacing=0)
390
 
        file_label = gtk.Label(_i18n('Files'))
 
438
        self._files_box = Gtk.VBox(homogeneous=False, spacing=0)
 
439
        file_label = Gtk.Label(label=_i18n('Files'))
391
440
        # file_label.show()
392
 
        self._files_box.pack_start(file_label, expand=False)
 
441
        self._files_box.pack_start(file_label, False, True, 0)
393
442
 
394
 
        self._commit_all_files_radio = gtk.RadioButton(
 
443
        self._commit_all_files_radio = Gtk.RadioButton.new_with_label(
395
444
            None, _i18n("Commit all changes"))
396
 
        self._files_box.pack_start(self._commit_all_files_radio, expand=False)
 
445
        self._files_box.pack_start(self._commit_all_files_radio, False, True, 0)
397
446
        self._commit_all_files_radio.show()
398
447
        self._commit_all_files_radio.connect('toggled',
399
448
            self._toggle_commit_selection)
400
 
        self._commit_selected_radio = gtk.RadioButton(
 
449
        self._commit_selected_radio = Gtk.RadioButton.new_with_label_from_widget(
401
450
            self._commit_all_files_radio, _i18n("Only commit selected changes"))
402
 
        self._files_box.pack_start(self._commit_selected_radio, expand=False)
 
451
        self._files_box.pack_start(self._commit_selected_radio, False, True, 0)
403
452
        self._commit_selected_radio.show()
404
453
        self._commit_selected_radio.connect('toggled',
405
454
            self._toggle_commit_selection)
408
457
            self._commit_all_files_radio.set_sensitive(False)
409
458
            self._commit_selected_radio.set_sensitive(False)
410
459
 
411
 
        scroller = gtk.ScrolledWindow()
412
 
        scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
413
 
        self._treeview_files = gtk.TreeView()
 
460
        scroller = Gtk.ScrolledWindow()
 
461
        scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
 
462
        self._treeview_files = Gtk.TreeView()
414
463
        self._treeview_files.show()
415
464
        scroller.add(self._treeview_files)
416
 
        scroller.set_shadow_type(gtk.SHADOW_IN)
 
465
        scroller.set_shadow_type(Gtk.ShadowType.IN)
417
466
        scroller.show()
418
 
        self._files_box.pack_start(scroller,
419
 
                                   expand=True, fill=True)
 
467
        self._files_box.pack_start(scroller, True, True, 0)
420
468
        self._files_box.show()
421
 
        self._left_pane_box.pack_start(self._files_box)
 
469
        self._left_pane_box.pack_start(self._files_box, True, True, 0)
422
470
 
423
471
        # Keep note that all strings stored in a ListStore must be UTF-8
424
472
        # strings. GTK does not support directly setting and restoring Unicode
425
473
        # objects.
426
 
        liststore = gtk.ListStore(
427
 
            gobject.TYPE_STRING,  # [0] file_id
428
 
            gobject.TYPE_STRING,  # [1] real path
429
 
            gobject.TYPE_BOOLEAN, # [2] checkbox
430
 
            gobject.TYPE_STRING,  # [3] display path
431
 
            gobject.TYPE_STRING,  # [4] changes type
432
 
            gobject.TYPE_STRING,  # [5] commit message
 
474
        liststore = Gtk.ListStore(
 
475
            GObject.TYPE_STRING,  # [0] file_id
 
476
            GObject.TYPE_STRING,  # [1] real path
 
477
            GObject.TYPE_BOOLEAN, # [2] checkbox
 
478
            GObject.TYPE_STRING,  # [3] display path
 
479
            GObject.TYPE_STRING,  # [4] changes type
 
480
            GObject.TYPE_STRING,  # [5] commit message
433
481
            )
434
482
        self._files_store = liststore
435
483
        self._treeview_files.set_model(liststore)
436
 
        crt = gtk.CellRendererToggle()
 
484
        crt = Gtk.CellRendererToggle()
437
485
        crt.set_property('activatable', not bool(self._pending))
438
486
        crt.connect("toggled", self._toggle_commit, self._files_store)
439
487
        if self._pending:
440
488
            name = _i18n('Commit*')
441
489
        else:
442
490
            name = _i18n('Commit')
443
 
        commit_col = gtk.TreeViewColumn(name, crt, active=2)
 
491
        commit_col = Gtk.TreeViewColumn(name, crt, active=2)
444
492
        commit_col.set_visible(False)
445
493
        self._treeview_files.append_column(commit_col)
446
 
        self._treeview_files.append_column(gtk.TreeViewColumn(_i18n('Path'),
447
 
                                           gtk.CellRendererText(), text=3))
448
 
        self._treeview_files.append_column(gtk.TreeViewColumn(_i18n('Type'),
449
 
                                           gtk.CellRendererText(), text=4))
 
494
        self._treeview_files.append_column(Gtk.TreeViewColumn(_i18n('Path'),
 
495
                                           Gtk.CellRendererText(), text=3))
 
496
        self._treeview_files.append_column(Gtk.TreeViewColumn(_i18n('Type'),
 
497
                                           Gtk.CellRendererText(), text=4))
450
498
        self._treeview_files.connect('cursor-changed',
451
499
                                     self._on_treeview_files_cursor_changed)
452
500
 
453
501
    def _toggle_commit(self, cell, path, model):
454
 
        if model[path][0] is None: # No file_id means 'All Files'
 
502
        if model[path][0] == "": # No file_id means 'All Files'
455
503
            new_val = not model[path][2]
456
504
            for node in model:
457
505
                node[2] = new_val
467
515
                checked_col.set_visible(False)
468
516
            else:
469
517
                checked_col.set_visible(True)
470
 
            renderer = checked_col.get_cell_renderers()[0]
 
518
            renderer = checked_col.get_cells()[0]
471
519
            renderer.set_property('activatable', not all_files)
472
520
 
473
521
    def _construct_pending_list(self):
474
522
        # Pending information defaults to hidden, we put it all in 1 box, so
475
523
        # that we can show/hide all of them at once
476
 
        self._pending_box = gtk.VBox()
 
524
        self._pending_box = Gtk.VBox()
477
525
        self._pending_box.hide()
478
526
 
479
 
        pending_message = gtk.Label()
 
527
        pending_message = Gtk.Label()
480
528
        pending_message.set_markup(
481
529
            _i18n('<i>* Cannot select specific files when merging</i>'))
482
 
        self._pending_box.pack_start(pending_message, expand=False, padding=5)
 
530
        self._pending_box.pack_start(pending_message, False, True, 5)
483
531
        pending_message.show()
484
532
 
485
 
        pending_label = gtk.Label(_i18n('Pending Revisions'))
486
 
        self._pending_box.pack_start(pending_label, expand=False, padding=0)
 
533
        pending_label = Gtk.Label(label=_i18n('Pending Revisions'))
 
534
        self._pending_box.pack_start(pending_label, False, True, 0)
487
535
        pending_label.show()
488
536
 
489
 
        scroller = gtk.ScrolledWindow()
490
 
        scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
491
 
        self._treeview_pending = gtk.TreeView()
 
537
        scroller = Gtk.ScrolledWindow()
 
538
        scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
 
539
        self._treeview_pending = Gtk.TreeView()
492
540
        scroller.add(self._treeview_pending)
493
 
        scroller.set_shadow_type(gtk.SHADOW_IN)
 
541
        scroller.set_shadow_type(Gtk.ShadowType.IN)
494
542
        scroller.show()
495
 
        self._pending_box.pack_start(scroller,
496
 
                                     expand=True, fill=True, padding=5)
 
543
        self._pending_box.pack_start(scroller, True, True, 5)
497
544
        self._treeview_pending.show()
498
 
        self._left_pane_box.pack_start(self._pending_box)
 
545
        self._left_pane_box.pack_start(self._pending_box, True, True, 0)
499
546
 
500
 
        liststore = gtk.ListStore(gobject.TYPE_STRING, # revision_id
501
 
                                  gobject.TYPE_STRING, # date
502
 
                                  gobject.TYPE_STRING, # committer
503
 
                                  gobject.TYPE_STRING, # summary
 
547
        liststore = Gtk.ListStore(GObject.TYPE_STRING, # revision_id
 
548
                                  GObject.TYPE_STRING, # date
 
549
                                  GObject.TYPE_STRING, # committer
 
550
                                  GObject.TYPE_STRING, # summary
504
551
                                 )
505
552
        self._pending_store = liststore
506
553
        self._treeview_pending.set_model(liststore)
507
 
        self._treeview_pending.append_column(gtk.TreeViewColumn(_i18n('Date'),
508
 
                                             gtk.CellRendererText(), text=1))
509
 
        self._treeview_pending.append_column(gtk.TreeViewColumn(_i18n('Committer'),
510
 
                                             gtk.CellRendererText(), text=2))
511
 
        self._treeview_pending.append_column(gtk.TreeViewColumn(_i18n('Summary'),
512
 
                                             gtk.CellRendererText(), text=3))
 
554
        self._treeview_pending.append_column(Gtk.TreeViewColumn(_i18n('Date'),
 
555
                                             Gtk.CellRendererText(), text=1))
 
556
        self._treeview_pending.append_column(Gtk.TreeViewColumn(_i18n('Committer'),
 
557
                                             Gtk.CellRendererText(), text=2))
 
558
        self._treeview_pending.append_column(Gtk.TreeViewColumn(_i18n('Summary'),
 
559
                                             Gtk.CellRendererText(), text=3))
513
560
 
514
561
    def _construct_diff_view(self):
515
 
        from diff import DiffView
 
562
        from bzrlib.plugins.gtk.diff import DiffView
516
563
 
517
564
        # TODO: jam 2007-10-30 The diff label is currently disabled. If we
518
565
        #       decide that we really don't ever want to display it, we should
519
566
        #       actually remove it, and other references to it, along with the
520
567
        #       tests that it is set properly.
521
 
        self._diff_label = gtk.Label(_i18n('Diff for whole tree'))
 
568
        self._diff_label = Gtk.Label(label=_i18n('Diff for whole tree'))
522
569
        self._diff_label.set_alignment(0, 0)
523
570
        self._right_pane_table.set_row_spacing(self._right_pane_table_row, 0)
524
571
        self._add_to_right_table(self._diff_label, 1, False)
529
576
        self._diff_view.show()
530
577
 
531
578
    def _construct_file_message(self):
532
 
        scroller = gtk.ScrolledWindow()
533
 
        scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 
579
        scroller = Gtk.ScrolledWindow()
 
580
        scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
534
581
 
535
 
        self._file_message_text_view = gtk.TextView()
 
582
        self._file_message_text_view = Gtk.TextView()
536
583
        scroller.add(self._file_message_text_view)
537
 
        scroller.set_shadow_type(gtk.SHADOW_IN)
 
584
        scroller.set_shadow_type(Gtk.ShadowType.IN)
538
585
        scroller.show()
539
586
 
540
 
        self._file_message_text_view.modify_font(pango.FontDescription("Monospace"))
541
 
        self._file_message_text_view.set_wrap_mode(gtk.WRAP_WORD)
 
587
        self._file_message_text_view.modify_font(Pango.FontDescription("Monospace"))
 
588
        self._file_message_text_view.set_wrap_mode(Gtk.WrapMode.WORD)
542
589
        self._file_message_text_view.set_accepts_tab(False)
543
590
        self._file_message_text_view.show()
544
591
 
545
 
        self._file_message_expander = gtk.Expander(_i18n('File commit message'))
 
592
        self._file_message_expander = Gtk.Expander(
 
593
            label=_i18n('File commit message'))
546
594
        self._file_message_expander.set_expanded(True)
547
595
        self._file_message_expander.add(scroller)
548
596
        self._add_to_right_table(self._file_message_expander, 1, False)
549
597
        self._file_message_expander.show()
550
598
 
551
599
    def _construct_global_message(self):
552
 
        self._global_message_label = gtk.Label(_i18n('Global Commit Message'))
 
600
        self._global_message_label = Gtk.Label(label=_i18n('Global Commit Message'))
553
601
        self._global_message_label.set_markup(
554
602
            _i18n('<b>Global Commit Message</b>'))
555
603
        self._global_message_label.set_alignment(0, 0)
558
606
        # Can we remove the spacing between the label and the box?
559
607
        self._global_message_label.show()
560
608
 
561
 
        scroller = gtk.ScrolledWindow()
562
 
        scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 
609
        scroller = Gtk.ScrolledWindow()
 
610
        scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
563
611
 
564
 
        self._global_message_text_view = gtk.TextView()
 
612
        self._global_message_text_view = Gtk.TextView()
565
613
        self._set_global_commit_message(self._saved_commit_messages_manager.get()[0])
566
 
        self._global_message_text_view.modify_font(pango.FontDescription("Monospace"))
 
614
        self._global_message_text_view.modify_font(Pango.FontDescription("Monospace"))
567
615
        scroller.add(self._global_message_text_view)
568
 
        scroller.set_shadow_type(gtk.SHADOW_IN)
 
616
        scroller.set_shadow_type(Gtk.ShadowType.IN)
569
617
        scroller.show()
570
618
        self._add_to_right_table(scroller, 2, True)
571
 
        self._file_message_text_view.set_wrap_mode(gtk.WRAP_WORD)
 
619
        self._file_message_text_view.set_wrap_mode(Gtk.WrapMode.WORD)
572
620
        self._file_message_text_view.set_accepts_tab(False)
573
621
        self._global_message_text_view.show()
574
622
 
575
623
    def _on_treeview_files_cursor_changed(self, treeview):
576
624
        treeselection = treeview.get_selection()
 
625
        if treeselection is None:
 
626
            # The treeview was probably destroyed as the dialog closes.
 
627
            return
577
628
        (model, selection) = treeselection.get_selected()
578
629
 
579
630
        if selection is not None:
580
631
            path, display_path = model.get(selection, 1, 3)
581
632
            self._diff_label.set_text(_i18n('Diff for ') + display_path)
582
 
            if path is None:
 
633
            if path == "":
583
634
                self._diff_view.show_diff(None)
584
635
            else:
585
 
                self._diff_view.show_diff([path.decode('UTF-8')])
 
636
                self._diff_view.show_diff([osutils.safe_unicode(path)])
586
637
            self._update_per_file_info(selection)
587
638
 
588
639
    def _on_accel_next(self, accel_group, window, keyval, modifier):
599
650
            # We have either made it to the end of the list, or nothing was
600
651
            # selected. Either way, select All Files, and jump to the global
601
652
            # commit message.
602
 
            self._treeview_files.set_cursor((0,))
 
653
            self._treeview_files.set_cursor(
 
654
                Gtk.TreePath(path=0), "", False)
603
655
            self._global_message_text_view.grab_focus()
604
656
        else:
605
657
            # Set the cursor to this entry, and jump to the per-file commit
606
658
            # message
607
 
            self._treeview_files.set_cursor(model.get_path(next))
 
659
            self._treeview_files.set_cursor(model.get_path(next), None, False)
608
660
            self._file_message_text_view.grab_focus()
609
661
 
610
662
    def _save_current_file_message(self):
612
664
            return # Nothing to save
613
665
        text_buffer = self._file_message_text_view.get_buffer()
614
666
        cur_text = text_buffer.get_text(text_buffer.get_start_iter(),
615
 
                                        text_buffer.get_end_iter())
 
667
                                        text_buffer.get_end_iter(), True)
616
668
        last_selected = self._files_store.get_iter(self._last_selected_file)
617
669
        self._files_store.set_value(last_selected, 5, cur_text)
618
670
 
624
676
        self._save_current_file_message()
625
677
        text_buffer = self._file_message_text_view.get_buffer()
626
678
        file_id, display_path, message = self._files_store.get(selection, 0, 3, 5)
627
 
        if file_id is None: # Whole tree
 
679
        if file_id == "": # Whole tree
628
680
            self._file_message_expander.set_label(_i18n('File commit message'))
629
681
            self._file_message_expander.set_expanded(False)
630
682
            self._file_message_expander.set_sensitive(False)
647
699
        files = []
648
700
        records = iter(self._files_store)
649
701
        rec = records.next() # Skip the All Files record
650
 
        assert rec[0] is None, "Are we skipping the wrong record?"
 
702
        assert rec[0] == "", "Are we skipping the wrong record?"
651
703
 
652
704
        file_info = []
653
705
        for record in records:
654
706
            if self._commit_all_changes or record[2]:# [2] checkbox
655
 
                file_id = record[0] # [0] file_id
656
 
                path = record[1]    # [1] real path
 
707
                file_id = osutils.safe_utf8(record[0]) # [0] file_id
 
708
                path = osutils.safe_utf8(record[1])    # [1] real path
657
709
                # [5] commit message
658
710
                file_message = _sanitize_and_decode_message(record[5])
659
711
                files.append(path.decode('UTF-8'))
689
741
                _i18n('Commit cancelled'),
690
742
                _i18n('Do you want to save your commit messages ?'),
691
743
                parent=self)
692
 
            if response == gtk.RESPONSE_NO:
 
744
            if response == Gtk.ResponseType.NO:
693
745
                 # save nothing and destroy old comments if any
694
746
                mgr = SavedCommitMessagesManager()
695
747
        mgr.save(self._wt, self._wt.branch)
696
 
        self.response(gtk.RESPONSE_CANCEL) # close window
 
748
        self.response(Gtk.ResponseType.CANCEL) # close window
697
749
 
698
750
    @show_bzr_error
699
751
    def _on_commit_clicked(self, button):
708
760
                _i18n('Commit with an empty message?'),
709
761
                _i18n('You can describe your commit intent in the message.'),
710
762
                parent=self)
711
 
            if response == gtk.RESPONSE_NO:
 
763
            if response == Gtk.ResponseType.NO:
712
764
                # Kindly give focus to message area
713
765
                self._global_message_text_view.grab_focus()
714
766
                return
730
782
                _i18n("Unknown files exist in the working tree. Commit anyway?"),
731
783
                parent=self)
732
784
                # Doesn't set a parent for the dialog..
733
 
            if response == gtk.RESPONSE_NO:
 
785
            if response == Gtk.ResponseType.NO:
734
786
                return
735
787
            break
736
788
 
751
803
                _i18n('There are no changes in the working tree.'
752
804
                      ' Do you want to commit anyway?'),
753
805
                parent=self)
754
 
            if response == gtk.RESPONSE_YES:
 
806
            if response == Gtk.ResponseType.YES:
755
807
                rev_id = self._wt.commit(message,
756
808
                               allow_pointless=True,
757
809
                               strict=False,
761
813
        self.committed_revision_id = rev_id
762
814
        # destroy old comments if any
763
815
        SavedCommitMessagesManager().save(self._wt, self._wt.branch)
764
 
        self.response(gtk.RESPONSE_OK)
 
816
        self.response(Gtk.ResponseType.OK)
765
817
 
766
818
    def _get_global_commit_message(self):
767
819
        buf = self._global_message_text_view.get_buffer()
768
820
        start, end = buf.get_bounds()
769
 
        text = buf.get_text(start, end)
 
821
        text = buf.get_text(start, end, True)
770
822
        return _sanitize_and_decode_message(text)
771
823
 
772
824
    def _set_global_commit_message(self, message):
795
847
        rev_dict['revision_id'] = rev.revision_id
796
848
        return rev_dict
797
849
 
798
 
 
799
 
class SavedCommitMessagesManager:
800
 
    """Save glogal and per-file commit messages.
801
 
 
802
 
    Saves global commit message and utf-8 file_id->message dictionary
803
 
    of per-file commit messages on disk. Re-reads them later for re-using.
804
 
    """
805
 
 
806
 
    def __init__(self, tree=None, branch=None):
807
 
        """If branch is None, builds empty messages, otherwise reads them
808
 
        from branch's disk storage. 'tree' argument is for the future."""
809
 
        if branch is None:
810
 
            self.global_message = u''
811
 
            self.file_messages = {}
812
 
        else:
813
 
            config = branch.get_config()
814
 
            self.global_message = config.get_user_option(
815
 
                'gtk_global_commit_message')
816
 
            if self.global_message is None:
817
 
                self.global_message = u''
818
 
            file_messages = config.get_user_option('gtk_file_commit_messages')
819
 
            if file_messages: # unicode and B-encoded:
820
 
                self.file_messages = bencode.bdecode(
821
 
                    file_messages.encode('UTF-8'))
822
 
            else:
823
 
                self.file_messages = {}
824
 
 
825
 
    def get(self):
826
 
        return self.global_message, self.file_messages
827
 
 
828
 
    def is_not_empty(self):
829
 
        return bool(self.global_message or self.file_messages)
830
 
 
831
 
    def insert(self, global_message, file_info):
832
 
        """Formats per-file commit messages (list of dictionaries, one per file)
833
 
        into one utf-8 file_id->message dictionary and merges this with
834
 
        previously existing dictionary. Merges global commit message too."""
835
 
        file_messages = {}
836
 
        for fi in file_info:
837
 
            file_message = fi['message']
838
 
            if file_message:
839
 
                file_messages[fi['file_id']] = file_message # utf-8 strings
840
 
        for k,v in file_messages.iteritems():
841
 
            try:
842
 
                self.file_messages[k] = v + '\n******\n' + self.file_messages[k]
843
 
            except KeyError:
844
 
                self.file_messages[k] = v
845
 
        if self.global_message:
846
 
            self.global_message = global_message + '\n******\n' \
847
 
                + self.global_message
848
 
        else:
849
 
            self.global_message = global_message
850
 
 
851
 
    def save(self, tree, branch):
852
 
        # We store in branch's config, which can be a problem if two gcommit
853
 
        # are done in two checkouts of one single branch (comments overwrite
854
 
        # each other). Ideally should be in working tree. But uncommit does
855
 
        # not always have a working tree, though it always has a branch.
856
 
        # 'tree' argument is for the future
857
 
        config = branch.get_config()
858
 
        # should it be named "gtk_" or some more neutral name ("gui_" ?) to
859
 
        # be compatible with qbzr in the future?
860
 
        config.set_user_option('gtk_global_commit_message', self.global_message)
861
 
        # bencode() does not know unicode objects but set_user_option()
862
 
        # requires one:
863
 
        config.set_user_option(
864
 
            'gtk_file_commit_messages',
865
 
            bencode.bencode(self.file_messages).decode('UTF-8'))
866
 
 
867
 
 
868
 
def save_commit_messages(local, master, old_revno, old_revid,
869
 
                         new_revno, new_revid):
870
 
    b = local
871
 
    if b is None:
872
 
        b = master
873
 
    mgr = SavedCommitMessagesManager(None, b)
874
 
    revid_iterator = b.repository.iter_reverse_revision_history(old_revid)
875
 
    cur_revno = old_revno
876
 
    new_revision_id = old_revid
877
 
    graph = b.repository.get_graph()
878
 
    for rev_id in revid_iterator:
879
 
        if cur_revno == new_revno:
880
 
            break
881
 
        cur_revno -= 1
882
 
        rev = b.repository.get_revision(rev_id)
883
 
        file_info = rev.properties.get('file-info', None)
884
 
        if file_info is None:
885
 
            file_info = {}
886
 
        else:
887
 
            file_info = bencode.bdecode(file_info.encode('UTF-8'))
888
 
        global_message = osutils.safe_unicode(rev.message)
889
 
        # Concatenate comment of the uncommitted revision
890
 
        mgr.insert(global_message, file_info)
891
 
 
892
 
        parents = graph.get_parent_map([rev_id]).get(rev_id, None)
893
 
        if not parents:
894
 
            continue
895
 
    mgr.save(None, b)