3
3
# Copyright (C) 2006 Jeff Bailey
4
4
# Copyright (C) 2006 Wouter van Heyst
5
# Copyright (C) 2006-2011 Jelmer Vernooij <jelmer@samba.org>
7
# This program is free software; you can redistribute it and/or modify
8
# it under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
12
# This program is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
17
# You should have received a copy of the GNU General Public License
18
# along with this program; if not, write to the Free Software
19
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22
# setup.py can install nautilus-bzr to the right system folder, if pkg-config
25
# You can also install nautilus-bzr manually by copying it (or linking it from)
26
# ~/.local/share/nautilus-python/extensions/nautilus-bzr.py
28
from gi.repository import Gtk, GObject, Nautilus
29
from bzrlib.controldir import ControlDir
30
from bzrlib.errors import (
34
from bzrlib.ignores import tree_ignores_add_patterns
35
from bzrlib.tree import InterTree
5
# Copyright (C) 2006 Jelmer Vernooij
7
# Published under the GNU GPL
12
from bzrlib.bzrdir import BzrDir
13
from bzrlib.errors import NotBranchError
14
from bzrlib.errors import NoWorkingTree
15
from bzrlib.errors import UnsupportedProtocol
16
from bzrlib.workingtree import WorkingTree
17
from bzrlib.branch import Branch
18
from bzrlib.tree import file_status
37
20
from bzrlib.plugin import load_plugins
40
from bzrlib.plugins.gtk.i18n import _i18n
43
class BazaarExtension(Nautilus.MenuProvider, Nautilus.ColumnProvider,
44
Nautilus.InfoProvider, Nautilus.PropertyPageProvider,
45
Nautilus.LocationWidgetProvider, GObject.GObject):
46
"""Nautilus extension providing Bazaar integration."""
23
from bzrlib.plugins.gtk import cmd_visualise, cmd_gannotate
25
class BzrExtension(nautilus.MenuProvider, nautilus.ColumnProvider, nautilus.InfoProvider):
48
26
def __init__(self):
52
def _open_bzrdir(cls, vfs_file):
53
uri = vfs_file.get_uri()
54
controldir, path = ControlDir.open_containing(uri)
55
return controldir, path
58
def _open_tree(cls, vfs_file):
59
controldir, path = cls._open_bzrdir(vfs_file)
60
return controldir.open_workingtree(), path
62
def add_cb(self, menu, tree, path):
29
def add_cb(self, menu, vfs_file):
30
# We can only cope with local files
31
if vfs_file.get_uri_scheme() != 'file':
34
file = vfs_file.get_uri()
36
tree, path = WorkingTree.open_containing(file)
37
except NotBranchError:
65
def ignore_cb(self, menu, tree, path):
66
# We can only cope with local files
67
tree_ignores_add_patterns(tree, [path])
68
#FIXME: Add path to ignore file
70
def unignore_cb(self, menu, tree, path):
72
# We can only cope with local files
75
def diff_cb(self, menu, tree, path=None):
44
def ignore_cb(self, menu, vfs_file):
45
# We can only cope with local files
46
if vfs_file.get_uri_scheme() != 'file':
49
file = vfs_file.get_uri()
51
tree, path = WorkingTree.open_containing(file)
52
except NotBranchError:
59
def unignore_cb(self, menu, vfs_file):
60
# We can only cope with local files
61
if vfs_file.get_uri_scheme() != 'file':
64
file = vfs_file.get_uri()
66
tree, path = WorkingTree.open_containing(file)
67
except NotBranchError:
74
def diff_cb(self, menu, vfs_file):
75
# We can only cope with local files
76
if vfs_file.get_uri_scheme() != 'file':
79
file = vfs_file.get_uri()
81
tree, path = WorkingTree.open_containing(file)
82
except NotBranchError:
76
85
from bzrlib.plugins.gtk.diff import DiffWindow
77
86
window = DiffWindow()
78
window.set_diff(tree.branch._get_nick(local=True), tree,
79
tree.branch.basis_tree())
87
window.set_diff(tree.branch.nick, tree, tree.branch.basis_tree())
82
92
def newtree_cb(self, menu, vfs_file):
83
controldir, path = self._open_bzrdir(vfs_file)
84
controldir.create_workingtree()
86
def remove_cb(self, menu, tree, path):
93
# We can only cope with local files
94
if vfs_file.get_uri_scheme() != 'file':
97
file = vfs_file.get_uri()
99
# We only want to continue here if we get a NotBranchError
101
tree, path = WorkingTree.open_containing(file)
102
except NotBranchError:
103
BzrDir.create_standalone_workingtree(file)
105
def remove_cb(self, menu, vfs_file):
106
# We can only cope with local files
107
if vfs_file.get_uri_scheme() != 'file':
110
file = vfs_file.get_uri()
112
tree, path = WorkingTree.open_containing(file)
113
except NotBranchError:
89
def annotate_cb(self, menu, tree, path, file_id):
90
from bzrlib.plugins.gtk.annotate.gannotate import GAnnotateWindow
91
win = GAnnotateWindow()
93
win.annotate(tree, tree.branch, file_id)
118
def annotate_cb(self, menu, vfs_file):
119
# We can only cope with local files
120
if vfs_file.get_uri_scheme() != 'file':
123
file = vfs_file.get_uri()
125
vis = cmd_gannotate()
96
128
def clone_cb(self, menu, vfs_file=None):
129
# We can only cope with local files
130
if vfs_file.get_uri_scheme() != 'file':
97
133
from bzrlib.plugins.gtk.branch import BranchDialog
98
controldir, path = self._open_bzrdir(vfs_file)
100
135
dialog = BranchDialog(vfs_file.get_name())
101
136
response = dialog.run()
102
if response != Gtk.ResponseType.NONE:
137
if response != gtk.RESPONSE_NONE:
106
def commit_cb(self, menu, tree, path=None):
141
def commit_cb(self, menu, vfs_file=None):
142
# We can only cope with local files
143
if vfs_file.get_uri_scheme() != 'file':
146
file = vfs_file.get_uri()
150
tree, path = WorkingTree.open_containing(file)
152
except NotBranchError, e:
155
except NoWorkingTree, e:
158
(branch, path) = Branch.open_containing(path)
159
except NotBranchError, e:
107
162
from bzrlib.plugins.gtk.commit import CommitDialog
108
dialog = CommitDialog(tree, path)
163
dialog = CommitDialog(tree, path, not branch)
109
164
response = dialog.run()
110
if response != Gtk.ResponseType.NONE:
165
if response != gtk.RESPONSE_NONE:
114
def log_cb(self, menu, controldir, path=None):
115
from bzrlib.plugins.gtk.viz import BranchWindow
116
branch = controldir.open_branch()
117
pp = BranchWindow(branch, [branch.last_revision()], None)
121
def pull_cb(self, menu, controldir, path=None):
169
def log_cb(self, menu, vfs_file):
170
# We can only cope with local files
171
if vfs_file.get_uri_scheme() != 'file':
174
file = vfs_file.get_uri()
176
# We only want to continue here if we get a NotBranchError
178
tree, path = WorkingTree.open_containing(file)
179
except NotBranchError:
182
vis = cmd_visualise()
187
def pull_cb(self, menu, vfs_file):
188
# We can only cope with local files
189
if vfs_file.get_uri_scheme() != 'file':
192
file = vfs_file.get_uri()
194
# We only want to continue here if we get a NotBranchError
196
tree, path = WorkingTree.open_containing(file)
197
except NotBranchError:
122
200
from bzrlib.plugins.gtk.pull import PullDialog
123
dialog = PullDialog(controldir.open_workingtree(), path)
201
dialog = PullDialog(tree, path)
127
def merge_cb(self, menu, tree, path=None):
205
def merge_cb(self, menu, vfs_file):
206
# We can only cope with local files
207
if vfs_file.get_uri_scheme() != 'file':
210
file = vfs_file.get_uri()
212
# We only want to continue here if we get a NotBranchError
214
tree, path = WorkingTree.open_containing(file)
215
except NotBranchError:
128
218
from bzrlib.plugins.gtk.merge import MergeDialog
129
219
dialog = MergeDialog(tree, path)
133
def create_tree_cb(self, menu, controldir):
134
controldir.create_workingtree()
136
223
def get_background_items(self, window, vfs_file):
225
file = vfs_file.get_uri()
138
controldir, path = self._open_bzrdir(vfs_file)
139
except NotBranchError:
227
tree, path = WorkingTree.open_containing(file)
228
except UnsupportedProtocol:
142
branch = controldir.open_branch()
143
230
except NotBranchError:
145
item = Nautilus.MenuItem(name='BzrNautilus::newtree',
146
label='Make directory versioned',
147
tip='Create new Bazaar tree in this folder',
231
item = nautilus.MenuItem('BzrNautilus::newtree',
232
'Make directory versioned',
233
'Create new Bazaar tree in this folder')
149
234
item.connect('activate', self.newtree_cb, vfs_file)
150
235
items.append(item)
152
item = Nautilus.MenuItem(name='BzrNautilus::clone',
153
label='Checkout Bazaar branch ...',
154
tip='Checkout Existing Bazaar Branch',
237
item = nautilus.MenuItem('BzrNautilus::clone',
238
'Checkout Bazaar branch',
239
'Checkout Existing Bazaar Branch')
156
240
item.connect('activate', self.clone_cb, vfs_file)
157
241
items.append(item)
162
nautilus_integration = self.check_branch_enabled(branch)
163
if not nautilus_integration:
164
item = Nautilus.MenuItem(name='BzrNautilus::enable',
165
label='Enable Bazaar Plugin for this Branch',
166
tip='Enable Bazaar plugin for nautilus',
168
item.connect('activate', self.toggle_integration, True, branch)
171
item = Nautilus.MenuItem(name='BzrNautilus::disable',
172
label='Disable Bazaar Plugin this Branch',
173
tip='Disable Bazaar plugin for nautilus',
175
item.connect('activate', self.toggle_integration, False, branch)
178
item = Nautilus.MenuItem(name='BzrNautilus::log',
180
tip='Show Bazaar history',
182
item.connect('activate', self.log_cb, controldir)
185
item = Nautilus.MenuItem(name='BzrNautilus::pull',
187
tip='Pull from another branch',
189
item.connect('activate', self.pull_cb, controldir)
193
tree = controldir.open_workingtree()
194
except NoWorkingTree:
195
item = Nautilus.MenuItem(name='BzrNautilus::create_tree',
196
label='Create working tree...',
197
tip='Create a working tree for this branch',
199
item.connect('activate', self.create_tree_cb, controldir)
202
item = Nautilus.MenuItem(name='BzrNautilus::merge',
204
tip='Merge from another branch',
206
item.connect('activate', self.merge_cb, tree, path)
209
item = Nautilus.MenuItem(name='BzrNautilus::commit',
211
tip='Commit Changes',
213
item.connect('activate', self.commit_cb, tree, path)
245
item = nautilus.MenuItem('BzrNautilus::log',
247
'Show Bazaar history')
248
item.connect('activate', self.log_cb, vfs_file)
251
item = nautilus.MenuItem('BzrNautilus::pull',
253
'Pull from another branch')
254
item.connect('activate', self.pull_cb, vfs_file)
257
item = nautilus.MenuItem('BzrNautilus::merge',
259
'Merge from another branch')
260
item.connect('activate', self.merge_cb, vfs_file)
263
item = nautilus.MenuItem('BzrNautilus::commit',
266
item.connect('activate', self.commit_cb, vfs_file)
218
def _get_file_menuitems(self, tree, intertree, path):
219
file_id = tree.path2id(path)
221
item = Nautilus.MenuItem(name='BzrNautilus::add',
223
tip='Add as versioned file',
225
item.connect('activate', self.add_cb, tree, path)
228
item = Nautilus.MenuItem(name='BzrNautilus::ignore',
230
tip='Ignore file for versioning',
232
item.connect('activate', self.ignore_cb, tree, path)
234
elif tree.is_ignored(path):
235
item = Nautilus.MenuItem(name='BzrNautilus::unignore',
237
tip='Unignore file for versioning',
239
item.connect('activate', self.unignore_cb, tree, path)
242
item = Nautilus.MenuItem(name='BzrNautilus::log',
246
item.connect('activate', self.log_cb, tree.bzrdir, path)
249
if not intertree.file_content_matches(file_id, file_id):
250
item = Nautilus.MenuItem(name='BzrNautilus::diff',
251
label='View Changes ...',
252
tip='Show differences',
254
item.connect('activate', self.diff_cb, tree, path)
257
item = Nautilus.MenuItem(name='BzrNautilus::commit',
259
tip='Commit Changes',
261
item.connect('activate', self.commit_cb, tree, path)
264
item = Nautilus.MenuItem(name='BzrNautilus::remove',
266
tip='Remove this file from versioning',
268
item.connect('activate', self.remove_cb, tree, path)
271
item = Nautilus.MenuItem(name='BzrNautilus::annotate',
272
label='Annotate ...',
273
tip='Annotate File Data',
275
item.connect('activate', self.annotate_cb, tree, path, file_id)
278
272
def get_file_items(self, window, files):
283
for vfs_file in files:
284
controldir, path = self._open_bzrdir(vfs_file)
287
tree = trees[controldir.user_url]
290
tree = controldir.open_workingtree()
291
except NoWorkingTree:
293
trees[controldir.user_url] = tree
296
nautilus_integration = self.check_branch_enabled(tree.branch)
297
if not nautilus_integration:
300
intertree = InterTree.get(tree.basis_tree(), tree)
301
items.extend(list(self._get_file_menuitems(tree, intertree, path)))
303
for tree in trees.itervalues():
276
for vfs_file in files:
277
# We can only cope with local files
278
if vfs_file.get_uri_scheme() != 'file':
281
file = vfs_file.get_uri()
283
tree, path = WorkingTree.open_containing(file)
284
except NotBranchError:
285
if not vfs_file.is_directory():
287
item = nautilus.MenuItem('BzrNautilus::newtree',
288
'Make directory versioned',
289
'Create new Bazaar tree in %s' % vfs_file.get_name())
290
item.connect('activate', self.newtree_cb, vfs_file)
292
# Refresh the list of filestatuses in the working tree
293
if path not in wtfiles.keys():
295
for rpath, file_class, kind, id, entry in tree.list_files():
296
wtfiles[rpath] = file_class
300
if wtfiles[path] == '?':
301
item = nautilus.MenuItem('BzrNautilus::add',
303
'Add as versioned file')
304
item.connect('activate', self.add_cb, vfs_file)
307
item = nautilus.MenuItem('BzrNautilus::ignore',
309
'Ignore file for versioning')
310
item.connect('activate', self.ignore_cb, vfs_file)
312
elif wtfiles[path] == 'I':
313
item = nautilus.MenuItem('BzrNautilus::unignore',
315
'Unignore file for versioning')
316
item.connect('activate', self.unignore_cb, vfs_file)
318
elif wtfiles[path] == 'V':
319
item = nautilus.MenuItem('BzrNautilus::log',
322
item.connect('activate', self.log_cb, vfs_file)
325
item = nautilus.MenuItem('BzrNautilus::diff',
328
item.connect('activate', self.diff_cb, vfs_file)
331
item = nautilus.MenuItem('BzrNautilus::remove',
333
'Remove this file from versioning')
334
item.connect('activate', self.remove_cb, vfs_file)
337
item = nautilus.MenuItem('BzrNautilus::annotate',
339
'Annotate File Data')
340
item.connect('activate', self.annotate_cb, vfs_file)
343
item = nautilus.MenuItem('BzrNautilus::commit',
346
item.connect('activate', self.commit_cb, vfs_file)
308
351
def get_columns(self):
310
Nautilus.Column(name="BzrNautilus::bzr_status",
311
attribute="bzr_status",
313
description="Version control status"),
314
Nautilus.Column(name="BzrNautilus::bzr_revision",
315
attribute="bzr_revision",
317
description="Last change revision"),
320
def _file_summary(self, tree, basis_tree, intertree, path):
352
return nautilus.Column("BzrNautilus::bzr_status",
355
"Version control status"),
357
def update_file_info(self, file):
358
if file.get_uri_scheme() != 'file':
362
tree, path = WorkingTree.open_containing(file.get_uri())
363
except NotBranchError:
324
file_id = tree.path2id(path)
326
if tree.is_ignored(path):
328
emblem = 'bzr-ignored'
330
status = 'unversioned'
331
file_revision = "N/A"
332
elif tree.has_filename(path): # Still present
333
if not intertree.file_content_matches(file_id, file_id):
334
if not basis_tree.has_id(file_id):
369
if tree.has_filename(path):
370
emblem = 'cvs-controlled'
372
id = tree.path2id(path)
374
delta = tree.changes_from(tree.branch.basis_tree())
375
if delta.touches_file_id(id):
376
emblem = 'cvs-modified'
378
for f, _, _ in delta.added:
337
file_revision = "new file"
338
elif basis_tree.path2id(file_id) != path:
339
status = 'bzr-renamed'
340
status = 'renamed from %s' % basis_tree.path2id(file_id)
342
emblem = 'bzr-modified'
345
emblem = 'bzr-controlled'
347
elif basis_tree.has_filename(path):
348
emblem = 'bzr-removed'
383
for of, f, _, _, _, _ in delta.renamed:
385
status = 'renamed from %s' % f
387
elif tree.branch.basis_tree().has_filename(path):
388
emblem = 'cvs-removed'
349
389
status = 'removed'
351
391
# FIXME: Check for ignored files
352
392
status = 'unversioned'
353
return (status, emblem, file_revision)
355
def update_file_info(self, vfs_file):
357
controldir, path = self._open_bzrdir(vfs_file)
358
except NotBranchError:
362
tree = controldir.open_workingtree()
363
except NoWorkingTree:
368
nautilus_integration = self.check_branch_enabled(tree.branch)
369
if not nautilus_integration:
372
basis_tree = tree.basis_tree()
373
intertree = InterTree.get(basis_tree, tree)
375
basis_tree.lock_read()
377
(status, emblem, file_revision) = self._file_summary(tree, basis_tree, intertree, path)
380
if emblem is not None:
381
vfs_file.add_emblem(emblem)
382
vfs_file.add_string_attribute('bzr_status', status)
383
vfs_file.add_string_attribute('bzr_revision', file_revision)
387
def check_branch_enabled(self, branch):
388
# Supports global disable, but there is currently no UI to do this
389
config = branch.get_config_stack()
390
return config.get("nautilus_integration")
392
def toggle_integration(self, menu, action, branch):
393
config = branch.get_config_stack()
394
config.set("nautilus_integration", action)
396
def get_property_pages(self, files):
398
for vfs_file in files:
400
controldir, path = self._open_bzrdir(vfs_file)
401
except NotBranchError:
405
tree = controldir.open_workingtree()
406
except NoWorkingTree:
411
file_id = tree.path2id(path)
412
pages.append(PropertyPageFile(tree, file_id, path))
413
pages.append(PropertyPageBranch(tree.branch))
418
def get_widget(self, uri, window):
419
controldir, path = ControlDir.open_containing(uri)
421
tree = controldir.open_workingtree()
422
except NoWorkingTree:
424
ret = Gtk.HBox(False, 4)
425
text = 'This is a Bazaar working tree. '
426
get_shelf_manager = getattr(tree, 'get_shelf_manager', None)
427
if get_shelf_manager is not None:
428
manager = get_shelf_manager()
429
shelves = manager.active_shelves()
430
if len(shelves) == 0:
432
elif len(shelves) == 1:
433
text += '1 shelf exists. '
435
text += '%d shelf exists. ' % len(shelves)
436
label = Gtk.Label(text)
438
ret.pack_start(label, True, True, 0)
443
class PropertyPageFile(Nautilus.PropertyPage):
445
def __init__(self, tree, file_id, path):
447
self.file_id = file_id
449
label = Gtk.Label('File Version')
452
table = self._create_table()
454
super(PropertyPageFile, self).__init__(label=label,
455
name="BzrNautilus::file_page", page=table)
457
def _create_table(self):
458
table = Gtk.Table(homogeneous=False, columns=2, rows=3)
460
table.attach(Gtk.Label(_i18n('File id:')), 0, 1, 0, 1)
461
table.attach(Gtk.Label(self.file_id), 1, 2, 0, 1)
463
table.attach(Gtk.Label(_i18n('SHA1Sum:')), 0, 1, 1, 2)
464
table.attach(Gtk.Label(self.tree.get_file_sha1(self.file_id, self.path)), 1, 1, 1, 2)
466
basis_tree = self.tree.revision_tree(self.tree.last_revision())
467
last_revision = basis_tree.get_file_revision(self.file_id)
469
table.attach(Gtk.Label(_i18n('Last Change Revision:')), 0, 1, 2, 3)
470
revno = ".".join([str(x) for x in
471
self.tree.branch.revision_id_to_dotted_revno(last_revision)])
472
table.attach(Gtk.Label(revno), 1, 1, 2, 3)
474
table.attach(Gtk.Label(_i18n('Last Change Author:')), 0, 1, 3, 4)
475
rev = self.tree.branch.repository.get_revision(last_revision)
476
table.attach(Gtk.Label("\n".join(rev.get_apparent_authors())), 1, 1, 3, 4)
482
class PropertyPageBranch(Nautilus.PropertyPage):
484
def __init__(self, branch):
486
label = Gtk.Label('Branch')
489
table = self._create_table()
491
super(PropertyPageBranch, self).__init__(label=label,
492
name="BzrNautilus::branch_page", page=table)
494
def _create_location_entry(self, get_location, set_location):
495
location = get_location()
497
if location is not None:
498
ret.set_text(location)
501
def _create_table(self):
502
table = Gtk.Table(homogeneous=False, columns=2, rows=6)
504
self._push_location_entry = self._create_location_entry(
505
self.branch.get_push_location, self.branch.set_push_location)
506
self._parent_location_entry = self._create_location_entry(
507
self.branch.get_parent, self.branch.set_parent)
508
self._bound_location_entry = self._create_location_entry(
509
self.branch.get_bound_location, self.branch.set_bound_location)
510
self._public_location_entry = self._create_location_entry(
511
self.branch.get_public_branch, self.branch.set_public_branch)
512
self._submit_location_entry = self._create_location_entry(
513
self.branch.get_submit_branch, self.branch.set_submit_branch)
515
table.attach(Gtk.Label(_i18n('Push location:')), 0, 1, 0, 1)
516
table.attach(self._push_location_entry, 1, 2, 0, 1)
518
table.attach(Gtk.Label(_i18n('Parent location:')), 0, 1, 1, 2)
519
table.attach(self._parent_location_entry, 1, 1, 1, 2)
521
table.attach(Gtk.Label(_i18n('Bound location:')), 0, 1, 2, 3)
522
table.attach(self._bound_location_entry, 1, 1, 2, 3)
524
table.attach(Gtk.Label(_i18n('Public location:')), 0, 1, 3, 4)
525
table.attach(self._public_location_entry, 1, 1, 3, 4)
527
table.attach(Gtk.Label(_i18n('Submit location:')), 0, 1, 4, 5)
528
table.attach(self._submit_location_entry, 1, 1, 4, 5)
530
self._append_revisions_only = Gtk.CheckButton(_i18n('Append revisions only'))
531
value = self.branch.get_append_revisions_only()
534
self._append_revisions_only.set_active(value)
535
table.attach(self._append_revisions_only, 0, 2, 5, 6)
394
if emblem is not None:
395
file.add_emblem(emblem)
396
file.add_string_attribute('bzr_status', status)