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-2008 Jelmer Vernooij <jelmer@samba.org>
7
# Published under the GNU GPL
12
from bzrlib.branch import Branch
13
from bzrlib.bzrdir import BzrDir
14
from bzrlib.errors import NotBranchError, NoWorkingTree, UnsupportedProtocol
15
from bzrlib.workingtree import WorkingTree
16
from bzrlib.config import GlobalConfig
37
18
from bzrlib.plugin import load_plugins
41
class BazaarExtension(Nautilus.MenuProvider, Nautilus.ColumnProvider,
42
Nautilus.InfoProvider, Nautilus.PropertyPageProvider, GObject.GObject):
43
"""Nautilus extension providing Bazaar integration."""
21
from bzrlib.plugins.gtk.commands import cmd_gannotate, start_viz_window
23
print "Bazaar nautilus module initialized"
26
class BzrExtension(nautilus.MenuProvider, nautilus.ColumnProvider, nautilus.InfoProvider):
45
27
def __init__(self):
49
def _open_bzrdir(cls, vfs_file):
50
uri = vfs_file.get_uri()
51
controldir, path = ControlDir.open_containing(uri)
52
return controldir, path
55
def _open_tree(cls, vfs_file):
56
controldir, path = cls._open_bzrdir(vfs_file)
57
return controldir.open_workingtree(), path
59
def add_cb(self, menu, tree, path):
30
def add_cb(self, menu, vfs_file):
31
# We can only cope with local files
32
if vfs_file.get_uri_scheme() != 'file':
35
file = vfs_file.get_uri()
37
tree, path = WorkingTree.open_containing(file)
38
except NotBranchError:
62
def ignore_cb(self, menu, tree, path):
63
# We can only cope with local files
64
tree_ignores_add_patterns(tree, [path])
65
#FIXME: Add path to ignore file
67
def unignore_cb(self, menu, tree, path):
69
# We can only cope with local files
72
def diff_cb(self, menu, tree, path=None):
45
def ignore_cb(self, menu, vfs_file):
46
# We can only cope with local files
47
if vfs_file.get_uri_scheme() != 'file':
50
file = vfs_file.get_uri()
52
tree, path = WorkingTree.open_containing(file)
53
except NotBranchError:
60
def unignore_cb(self, menu, vfs_file):
61
# We can only cope with local files
62
if vfs_file.get_uri_scheme() != 'file':
65
file = vfs_file.get_uri()
67
tree, path = WorkingTree.open_containing(file)
68
except NotBranchError:
75
def diff_cb(self, menu, vfs_file):
76
# We can only cope with local files
77
if vfs_file.get_uri_scheme() != 'file':
80
file = vfs_file.get_uri()
82
tree, path = WorkingTree.open_containing(file)
83
except NotBranchError:
73
86
from bzrlib.plugins.gtk.diff import DiffWindow
74
87
window = DiffWindow()
75
88
window.set_diff(tree.branch._get_nick(local=True), tree,
76
89
tree.branch.basis_tree())
79
94
def newtree_cb(self, menu, vfs_file):
80
controldir, path = self._open_bzrdir(vfs_file)
81
controldir.create_workingtree()
83
def remove_cb(self, menu, tree, path):
95
# We can only cope with local files
96
if vfs_file.get_uri_scheme() != 'file':
99
file = vfs_file.get_uri()
101
# We only want to continue here if we get a NotBranchError
103
tree, path = WorkingTree.open_containing(file)
104
except NotBranchError:
105
BzrDir.create_standalone_workingtree(file)
107
def remove_cb(self, menu, vfs_file):
108
# We can only cope with local files
109
if vfs_file.get_uri_scheme() != 'file':
112
file = vfs_file.get_uri()
114
tree, path = WorkingTree.open_containing(file)
115
except NotBranchError:
86
def annotate_cb(self, menu, tree, path, file_id):
87
from bzrlib.plugins.gtk.annotate.gannotate import GAnnotateWindow
88
win = GAnnotateWindow()
90
win.annotate(tree, tree.branch, file_id)
120
def annotate_cb(self, menu, vfs_file):
121
# We can only cope with local files
122
if vfs_file.get_uri_scheme() != 'file':
125
file = vfs_file.get_uri()
127
vis = cmd_gannotate()
93
130
def clone_cb(self, menu, vfs_file=None):
131
# We can only cope with local files
132
if vfs_file.get_uri_scheme() != 'file':
94
135
from bzrlib.plugins.gtk.branch import BranchDialog
95
controldir, path = self._open_bzrdir(vfs_file)
97
137
dialog = BranchDialog(vfs_file.get_name())
98
138
response = dialog.run()
99
if response != Gtk.ResponseType.NONE:
139
if response != gtk.RESPONSE_NONE:
103
def commit_cb(self, menu, tree, path=None):
143
def commit_cb(self, menu, vfs_file=None):
144
# We can only cope with local files
145
if vfs_file.get_uri_scheme() != 'file':
148
file = vfs_file.get_uri()
152
tree, path = WorkingTree.open_containing(file)
154
except NotBranchError, e:
157
except NoWorkingTree, e:
160
(branch, path) = Branch.open_containing(path)
161
except NotBranchError, e:
104
164
from bzrlib.plugins.gtk.commit import CommitDialog
105
165
dialog = CommitDialog(tree, path)
106
166
response = dialog.run()
107
if response != Gtk.ResponseType.NONE:
167
if response != gtk.RESPONSE_NONE:
111
def log_cb(self, menu, controldir, path=None):
112
from bzrlib.plugins.gtk.viz import BranchWindow
113
branch = controldir.open_branch()
114
pp = BranchWindow(branch, [branch.last_revision()], None)
171
def log_cb(self, menu, vfs_file):
172
# We can only cope with local files
173
if vfs_file.get_uri_scheme() != 'file':
176
file = vfs_file.get_uri()
178
# We only want to continue here if we get a NotBranchError
180
branch, path = Branch.open_containing(file)
181
except NotBranchError:
184
pp = start_viz_window(branch, [branch.last_revision()])
118
def pull_cb(self, menu, controldir, path=None):
188
def pull_cb(self, menu, vfs_file):
189
# We can only cope with local files
190
if vfs_file.get_uri_scheme() != 'file':
193
file = vfs_file.get_uri()
195
# We only want to continue here if we get a NotBranchError
197
tree, path = WorkingTree.open_containing(file)
198
except NotBranchError:
119
201
from bzrlib.plugins.gtk.pull import PullDialog
120
dialog = PullDialog(controldir.open_workingtree(), path)
202
dialog = PullDialog(tree, path)
124
def merge_cb(self, menu, tree, path=None):
206
def merge_cb(self, menu, vfs_file):
207
# We can only cope with local files
208
if vfs_file.get_uri_scheme() != 'file':
211
file = vfs_file.get_uri()
213
# We only want to continue here if we get a NotBranchError
215
tree, path = WorkingTree.open_containing(file)
216
except NotBranchError:
125
219
from bzrlib.plugins.gtk.merge import MergeDialog
126
220
dialog = MergeDialog(tree, path)
130
def create_tree_cb(self, menu, controldir):
131
controldir.create_workingtree()
133
224
def get_background_items(self, window, vfs_file):
226
file = vfs_file.get_uri()
135
controldir, path = self._open_bzrdir(vfs_file)
136
except NotBranchError:
229
tree, path = WorkingTree.open_containing(file)
230
disabled_flag = self.check_branch_enabled(tree.branch)
231
except UnsupportedProtocol:
139
branch = controldir.open_branch()
140
233
except NotBranchError:
142
item = Nautilus.MenuItem(name='BzrNautilus::newtree',
143
label='Make directory versioned',
144
tip='Create new Bazaar tree in this folder',
234
disabled_flag = self.check_branch_enabled()
235
item = nautilus.MenuItem('BzrNautilus::newtree',
236
'Make directory versioned',
237
'Create new Bazaar tree in this folder')
146
238
item.connect('activate', self.newtree_cb, vfs_file)
147
239
items.append(item)
149
item = Nautilus.MenuItem(name='BzrNautilus::clone',
150
label='Checkout Bazaar branch ...',
151
tip='Checkout Existing Bazaar Branch',
241
item = nautilus.MenuItem('BzrNautilus::clone',
242
'Checkout Bazaar branch ...',
243
'Checkout Existing Bazaar Branch')
153
244
item.connect('activate', self.clone_cb, vfs_file)
154
245
items.append(item)
159
nautilus_integration = self.check_branch_enabled(branch)
160
if not nautilus_integration:
161
item = Nautilus.MenuItem(name='BzrNautilus::enable',
162
label='Enable Bazaar Plugin for this Branch',
163
tip='Enable Bazaar plugin for nautilus',
165
item.connect('activate', self.toggle_integration, True, branch)
168
item = Nautilus.MenuItem(name='BzrNautilus::disable',
169
label='Disable Bazaar Plugin this Branch',
170
tip='Disable Bazaar plugin for nautilus',
172
item.connect('activate', self.toggle_integration, False, branch)
175
item = Nautilus.MenuItem(name='BzrNautilus::log',
177
tip='Show Bazaar history',
179
item.connect('activate', self.log_cb, controldir)
182
item = Nautilus.MenuItem(name='BzrNautilus::pull',
184
tip='Pull from another branch',
186
item.connect('activate', self.pull_cb, controldir)
190
tree = controldir.open_workingtree()
191
248
except NoWorkingTree:
192
item = Nautilus.MenuItem(name='BzrNautilus::create_tree',
193
label='Create working tree...',
194
tip='Create a working tree for this branch',
196
item.connect('activate', self.create_tree_cb, controldir)
251
if disabled_flag == 'False':
252
item = nautilus.MenuItem('BzrNautilus::enable',
253
'Enable Bazaar Plugin for this Branch',
254
'Enable Bazaar plugin for nautilus')
255
item.connect('activate', self.toggle_integration, 'True', vfs_file)
199
item = Nautilus.MenuItem(name='BzrNautilus::merge',
201
tip='Merge from another branch',
203
item.connect('activate', self.merge_cb, tree, path)
206
item = Nautilus.MenuItem(name='BzrNautilus::commit',
208
tip='Commit Changes',
210
item.connect('activate', self.commit_cb, tree, path)
258
item = nautilus.MenuItem('BzrNautilus::disable',
259
'Disable Bazaar Plugin this Branch',
260
'Disable Bazaar plugin for nautilus')
261
item.connect('activate', self.toggle_integration, 'False', vfs_file)
264
item = nautilus.MenuItem('BzrNautilus::log',
266
'Show Bazaar history')
267
item.connect('activate', self.log_cb, vfs_file)
270
item = nautilus.MenuItem('BzrNautilus::pull',
272
'Pull from another branch')
273
item.connect('activate', self.pull_cb, vfs_file)
276
item = nautilus.MenuItem('BzrNautilus::merge',
278
'Merge from another branch')
279
item.connect('activate', self.merge_cb, vfs_file)
282
item = nautilus.MenuItem('BzrNautilus::commit',
285
item.connect('activate', self.commit_cb, vfs_file)
215
def _get_file_menuitems(self, tree, intertree, path):
216
file_id = tree.path2id(path)
218
item = Nautilus.MenuItem(name='BzrNautilus::add',
220
tip='Add as versioned file',
222
item.connect('activate', self.add_cb, tree, path)
225
item = Nautilus.MenuItem(name='BzrNautilus::ignore',
227
tip='Ignore file for versioning',
229
item.connect('activate', self.ignore_cb, tree, path)
231
elif tree.is_ignored(path):
232
item = Nautilus.MenuItem(name='BzrNautilus::unignore',
234
tip='Unignore file for versioning',
236
item.connect('activate', self.unignore_cb, tree, path)
239
item = Nautilus.MenuItem(name='BzrNautilus::log',
243
item.connect('activate', self.log_cb, tree.bzrdir, path)
246
if not intertree.file_content_matches(file_id, file_id):
247
item = Nautilus.MenuItem(name='BzrNautilus::diff',
248
label='View Changes ...',
249
tip='Show differences',
251
item.connect('activate', self.diff_cb, tree, path)
254
item = Nautilus.MenuItem(name='BzrNautilus::commit',
256
tip='Commit Changes',
258
item.connect('activate', self.commit_cb, tree, path)
261
item = Nautilus.MenuItem(name='BzrNautilus::remove',
263
tip='Remove this file from versioning',
265
item.connect('activate', self.remove_cb, tree, path)
268
item = Nautilus.MenuItem(name='BzrNautilus::annotate',
269
label='Annotate ...',
270
tip='Annotate File Data',
272
item.connect('activate', self.annotate_cb, tree, path, file_id)
275
290
def get_file_items(self, window, files):
280
for vfs_file in files:
281
controldir, path = self._open_bzrdir(vfs_file)
284
tree = trees[controldir.user_url]
287
tree = controldir.open_workingtree()
288
except NoWorkingTree:
290
trees[controldir.user_url] = tree
293
nautilus_integration = self.check_branch_enabled(tree.branch)
294
if not nautilus_integration:
294
for vfs_file in files:
295
# We can only cope with local files
296
if vfs_file.get_uri_scheme() != 'file':
299
file = vfs_file.get_uri()
301
tree, path = WorkingTree.open_containing(file)
302
disabled_flag = self.check_branch_enabled(tree.branch)
303
except NotBranchError:
304
disabled_flag = self.check_branch_enabled()
305
if not vfs_file.is_directory():
297
intertree = InterTree.get(tree.basis_tree(), tree)
298
items.extend(list(self._get_file_menuitems(tree, intertree, path)))
300
for tree in trees.itervalues():
308
if disabled_flag == 'False':
311
item = nautilus.MenuItem('BzrNautilus::newtree',
312
'Make directory versioned',
313
'Create new Bazaar tree in %s' % vfs_file.get_name())
314
item.connect('activate', self.newtree_cb, vfs_file)
316
except NoWorkingTree:
318
# Refresh the list of filestatuses in the working tree
319
if path not in wtfiles.keys():
321
for rpath, file_class, kind, id, entry in tree.list_files():
322
wtfiles[rpath] = file_class
326
if wtfiles[path] == '?':
327
item = nautilus.MenuItem('BzrNautilus::add',
329
'Add as versioned file')
330
item.connect('activate', self.add_cb, vfs_file)
333
item = nautilus.MenuItem('BzrNautilus::ignore',
335
'Ignore file for versioning')
336
item.connect('activate', self.ignore_cb, vfs_file)
338
elif wtfiles[path] == 'I':
339
item = nautilus.MenuItem('BzrNautilus::unignore',
341
'Unignore file for versioning')
342
item.connect('activate', self.unignore_cb, vfs_file)
344
elif wtfiles[path] == 'V':
345
item = nautilus.MenuItem('BzrNautilus::log',
348
item.connect('activate', self.log_cb, vfs_file)
351
item = nautilus.MenuItem('BzrNautilus::diff',
354
item.connect('activate', self.diff_cb, vfs_file)
357
item = nautilus.MenuItem('BzrNautilus::remove',
359
'Remove this file from versioning')
360
item.connect('activate', self.remove_cb, vfs_file)
363
item = nautilus.MenuItem('BzrNautilus::annotate',
365
'Annotate File Data')
366
item.connect('activate', self.annotate_cb, vfs_file)
369
item = nautilus.MenuItem('BzrNautilus::commit',
372
item.connect('activate', self.commit_cb, vfs_file)
305
377
def get_columns(self):
307
Nautilus.Column(name="BzrNautilus::bzr_status",
308
attribute="bzr_status",
310
description="Version control status"),
311
Nautilus.Column(name="BzrNautilus::bzr_revision",
312
attribute="bzr_revision",
314
description="Last change revision"),
317
def _file_summary(self, tree, basis_tree, intertree, path):
378
return nautilus.Column("BzrNautilus::bzr_status",
381
"Version control status"),
383
def update_file_info(self, file):
385
if file.get_uri_scheme() != 'file':
389
tree, path = WorkingTree.open_containing(file.get_uri())
390
except NotBranchError:
392
except NoWorkingTree:
395
disabled_flag = self.check_branch_enabled(tree.branch)
396
if disabled_flag == 'False':
321
file_id = tree.path2id(path)
402
id = tree.path2id(path)
323
404
if tree.is_ignored(path):
324
405
status = 'ignored'
325
406
emblem = 'bzr-ignored'
327
408
status = 'unversioned'
328
file_revision = "N/A"
329
elif tree.has_filename(path): # Still present
330
if not intertree.file_content_matches(file_id, file_id):
331
if not basis_tree.has_id(file_id):
410
elif tree.has_filename(path):
411
emblem = 'bzr-controlled'
414
delta = tree.changes_from(tree.branch.basis_tree())
415
if delta.touches_file_id(id):
416
emblem = 'bzr-modified'
418
for f, _, _ in delta.added:
332
420
emblem = 'bzr-added'
334
file_revision = "new file"
335
elif basis_tree.path2id(file_id) != path:
336
status = 'bzr-renamed'
337
status = 'renamed from %s' % basis_tree.path2id(file_id)
339
emblem = 'bzr-modified'
342
emblem = 'bzr-controlled'
344
elif basis_tree.has_filename(path):
423
for of, f, _, _, _, _ in delta.renamed:
425
status = 'renamed from %s' % f
427
elif tree.branch.basis_tree().has_filename(path):
345
428
emblem = 'bzr-removed'
346
429
status = 'removed'
348
431
# FIXME: Check for ignored files
349
432
status = 'unversioned'
350
return (status, emblem, file_revision)
352
def update_file_info(self, vfs_file):
354
controldir, path = self._open_bzrdir(vfs_file)
355
except NotBranchError:
359
tree = controldir.open_workingtree()
360
except NoWorkingTree:
365
nautilus_integration = self.check_branch_enabled(tree.branch)
366
if not nautilus_integration:
369
basis_tree = tree.basis_tree()
370
intertree = InterTree.get(basis_tree, tree)
372
basis_tree.lock_read()
374
(status, emblem, file_revision) = self._file_summary(tree, basis_tree, intertree, path)
377
if emblem is not None:
378
vfs_file.add_emblem(emblem)
379
vfs_file.add_string_attribute('bzr_status', status)
380
vfs_file.add_string_attribute('bzr_revision', file_revision)
384
def check_branch_enabled(self, branch):
434
if emblem is not None:
435
file.add_emblem(emblem)
436
file.add_string_attribute('bzr_status', status)
438
def check_branch_enabled(self, branch=None):
385
439
# Supports global disable, but there is currently no UI to do this
386
config = branch.get_config_stack()
387
return config.get("nautilus_integration")
389
def toggle_integration(self, menu, action, branch):
390
config = branch.get_config_stack()
391
config.set("nautilus_integration", action)
393
def get_property_pages(self, files):
395
for vfs_file in files:
397
controldir, path = self._open_bzrdir(vfs_file)
398
except NotBranchError:
402
tree = controldir.open_workingtree()
403
except NoWorkingTree:
408
file_id = tree.path2id(path)
409
pages.append(PropertyPageFile(tree, file_id, path))
410
pages.append(PropertyPageBranch(tree.branch))
416
class PropertyPageFile(Nautilus.PropertyPage):
418
def __init__(self, tree, file_id, path):
420
self.file_id = file_id
422
label = Gtk.Label('File Version')
425
table = self._create_table()
427
super(PropertyPageFile, self).__init__(label=label,
428
name="BzrNautilus::file_page", page=table)
430
def _create_table(self):
431
table = Gtk.Table(homogeneous=False, columns=2, rows=3)
433
table.attach(Gtk.Label('File id:'), 0, 1, 0, 1)
434
table.attach(Gtk.Label(self.file_id), 1, 2, 0, 1)
436
table.attach(Gtk.Label('SHA1Sum:'), 0, 1, 1, 2)
437
table.attach(Gtk.Label(self.tree.get_file_sha1(self.file_id, self.path)), 1, 1, 1, 2)
439
basis_tree = self.tree.revision_tree(self.tree.last_revision())
440
last_revision = basis_tree.get_file_revision(self.file_id)
442
table.attach(Gtk.Label('Last Change Revision:'), 0, 1, 2, 3)
443
revno = ".".join([str(x) for x in
444
self.tree.branch.revision_id_to_dotted_revno(last_revision)])
445
table.attach(Gtk.Label(revno), 1, 1, 2, 3)
447
table.attach(Gtk.Label('Last Change Author:'), 0, 1, 3, 4)
448
rev = self.tree.branch.repository.get_revision(last_revision)
449
table.attach(Gtk.Label("\n".join(rev.get_apparent_authors())), 1, 1, 3, 4)
455
class PropertyPageBranch(Nautilus.PropertyPage):
457
def __init__(self, branch):
459
label = Gtk.Label('Branch')
462
table = self._create_table()
464
super(PropertyPageBranch, self).__init__(label=label,
465
name="BzrNautilus::branch_page", page=table)
467
def _create_table(self):
468
table = Gtk.Table(homogeneous=False, columns=2, rows=3)
470
table.attach(Gtk.Label('Push location:'), 0, 1, 0, 1)
471
table.attach(Gtk.Label(self.branch.get_push_location()), 1, 2, 0, 1)
473
table.attach(Gtk.Label('Parent location:'), 0, 1, 1, 2)
474
table.attach(Gtk.Label(self.branch.get_parent()), 1, 1, 1, 2)
440
config = GlobalConfig()
441
disabled_flag = config.get_user_option('nautilus_integration')
442
if disabled_flag != 'False':
443
if branch is not None:
444
config = branch.get_config()
445
disabled_flag = config.get_user_option('nautilus_integration')
448
def toggle_integration(self, menu, action, vfs_file=None):
450
tree, path = WorkingTree.open_containing(vfs_file.get_uri())
451
except NotBranchError:
453
except NoWorkingTree:
457
config = GlobalConfig()
459
config = branch.get_config()
460
config.set_user_option('nautilus_integration', action)