14
12
# along with this program; if not, write to the Free Software
15
13
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
"""GTK+ frontends to Bazaar commands """
15
"""Graphical support for Bazaar using GTK.
18
commit-notify Start the graphical notifier of commits.
19
gannotate GTK+ annotate.
20
gbranch GTK+ branching.
21
gcheckout GTK+ checkout.
22
gcommit GTK+ commit dialog
23
gconflicts GTK+ conflicts.
24
gdiff Show differences in working tree in a GTK+ Window.
25
ginit Initialise a new branch.
26
gmissing GTK+ missing revisions dialog.
27
gpreferences GTK+ preferences dialog.
29
gstatus GTK+ status dialog
30
gtags Manage branch tags.
31
visualise Graphically visualise this branch.
36
__version__ = '0.91.0'
37
version_info = tuple(int(n) for n in __version__.split('.'))
40
def check_bzrlib_version(desired):
41
"""Check that bzrlib is compatible.
43
If version is < bzr-gtk version, assume incompatible.
44
If version == bzr-gtk version, assume completely compatible
45
If version == bzr-gtk version + 1, assume compatible, with deprecations
46
Otherwise, assume incompatible.
48
desired_plus = (desired[0], desired[1]+1)
49
bzrlib_version = bzrlib.version_info[:2]
50
if bzrlib_version == desired or (bzrlib_version == desired_plus and
51
bzrlib.version_info[3] == 'dev'):
54
from bzrlib.trace import warning
56
# get the message out any way we can
57
from warnings import warn as warning
58
if bzrlib_version < desired:
59
from bzrlib.errors import BzrError
60
warning('Installed Bazaar version %s is too old to be used with bzr-gtk'
61
' %s.' % (bzrlib.__version__, __version__))
62
raise BzrError('Version mismatch: %r' % (version_info,) )
64
warning('bzr-gtk is not up to date with installed bzr version %s.'
65
' \nThere should be a newer version available, e.g. %i.%i.'
66
% (bzrlib.__version__, bzrlib_version[0], bzrlib_version[1]))
69
check_bzrlib_version(version_info[:2])
71
from bzrlib.trace import warning
72
if __name__ != 'bzrlib.plugins.gtk':
73
warning("Not running as bzrlib.plugins.gtk, things may break.")
75
from bzrlib.lazy_import import lazy_import
76
lazy_import(globals(), """
18
85
from bzrlib.commands import Command, register_command, display_command
19
from bzrlib.errors import NotVersionedError, BzrCommandError
20
from bzrlib.commands import Command, register_command
86
from bzrlib.errors import NotVersionedError, BzrCommandError, NoSuchFile
21
87
from bzrlib.option import Option
22
from bzrlib.branch import Branch
23
from bzrlib.workingtree import WorkingTree
24
from bzrlib.bzrdir import BzrDir
26
class cmd_gbranch(Command):
95
raise errors.BzrCommandError("PyGTK not installed.")
100
def set_ui_factory():
102
from ui import GtkUIFactory
104
bzrlib.ui.ui_factory = GtkUIFactory()
108
return os.path.dirname(__file__)
111
class GTKCommand(Command):
112
"""Abstract class providing GTK specific run commands."""
114
def open_display(self):
115
pygtk = import_pygtk()
118
except RuntimeError, e:
119
if str(e) == "could not open display":
126
dialog = self.get_gtk_dialog(os.path.abspath('.'))
130
class cmd_gbranch(GTKCommand):
27
131
"""GTK+ branching.
36
except RuntimeError, e:
37
if str(e) == "could not open display":
40
from clone import CloneDialog
42
window = CloneDialog()
43
if window.run() == gtk.RESPONSE_OK:
44
bzrdir = BzrDir.open(window.url)
45
bzrdir.sprout(window.dest_path)
47
register_command(cmd_gbranch)
49
class cmd_gdiff(Command):
135
def get_gtk_dialog(self, path):
136
from bzrlib.plugins.gtk.branch import BranchDialog
137
return BranchDialog(path)
140
class cmd_gcheckout(GTKCommand):
145
def get_gtk_dialog(self, path):
146
from bzrlib.plugins.gtk.checkout import CheckoutDialog
147
return CheckoutDialog(path)
151
class cmd_gpush(GTKCommand):
155
takes_args = [ "location?" ]
157
def run(self, location="."):
158
(br, path) = branch.Branch.open_containing(location)
160
from push import PushDialog
161
dialog = PushDialog(br.repository, br.last_revision(), br)
166
class cmd_gdiff(GTKCommand):
50
167
"""Show differences in working tree in a GTK+ Window.
52
169
Otherwise, all changes for the tree are listed.
171
takes_args = ['filename?']
55
172
takes_options = ['revision']
58
def run(self, revision=None, file_list=None):
59
wt = WorkingTree.open_containing(".")[0]
61
if revision is not None:
62
if len(revision) == 1:
175
def run(self, revision=None, filename=None):
177
wt = workingtree.WorkingTree.open_containing(".")[0]
181
if revision is not None:
182
if len(revision) == 1:
184
revision_id = revision[0].in_history(branch).rev_id
185
tree2 = branch.repository.revision_tree(revision_id)
186
elif len(revision) == 2:
187
revision_id_0 = revision[0].in_history(branch).rev_id
188
tree2 = branch.repository.revision_tree(revision_id_0)
189
revision_id_1 = revision[1].in_history(branch).rev_id
190
tree1 = branch.repository.revision_tree(revision_id_1)
64
revision_id = revision[0].in_history(branch).rev_id
65
tree2 = branch.repository.revision_tree(revision_id)
66
elif len(revision) == 2:
67
revision_id_0 = revision[0].in_history(branch).rev_id
68
tree2 = branch.repository.revision_tree(revision_id_0)
69
revision_id_1 = revision[1].in_history(branch).rev_id
70
tree1 = branch.repository.revision_tree(revision_id_1)
73
tree2 = tree1.basis_tree()
75
from bzrlib.plugins.gtk.viz.diffwin import DiffWindow
78
window.connect("destroy", lambda w: gtk.main_quit())
79
window.set_diff("Working Tree", tree1, tree2)
84
register_command(cmd_gdiff)
193
tree2 = tree1.basis_tree()
195
from diff import DiffWindow
197
window = DiffWindow()
198
window.connect("destroy", gtk.main_quit)
199
window.set_diff("Working Tree", tree1, tree2)
200
if filename is not None:
201
tree_filename = wt.relpath(filename)
203
window.set_file(tree_filename)
205
if (tree1.path2id(tree_filename) is None and
206
tree2.path2id(tree_filename) is None):
207
raise NotVersionedError(filename)
208
raise BzrCommandError('No changes found for file "%s"' %
217
def start_viz_window(branch, revision, limit=None):
218
"""Start viz on branch with revision revision.
220
:return: The viz window object.
222
from viz.branchwin import BranchWindow
225
pp.set_branch(branch, revision, limit)
226
# cleanup locks when the window is closed
227
pp.connect("destroy", lambda w: branch.unlock())
86
231
class cmd_visualise(Command):
87
232
"""Graphically visualise this branch.
97
Option('limit', "maximum number of revisions to display",
242
Option('limit', "Maximum number of revisions to display.",
99
244
takes_args = [ "location?" ]
100
245
aliases = [ "visualize", "vis", "viz" ]
102
247
def run(self, location=".", revision=None, limit=None):
103
(branch, path) = Branch.open_containing(location)
105
branch.repository.lock_read()
249
(br, path) = branch.Branch.open_containing(location)
107
252
if revision is None:
108
revid = branch.last_revision()
253
revid = br.last_revision()
109
254
if revid is None:
112
(revno, revid) = revision[0].in_history(branch)
257
(revno, revid) = revision[0].in_history(br)
114
from viz.bzrkapp import BzrkApp
117
app.show(branch, revid, limit)
260
pp = start_viz_window(br, revid, limit)
261
pp.connect("destroy", lambda w: gtk.main_quit())
119
branch.repository.unlock()
124
register_command(cmd_visualise)
126
class cmd_gannotate(Command):
268
class cmd_gannotate(GTKCommand):
127
269
"""GTK+ annotate.
129
271
Browse changes to FILENAME line by line in a GTK+ window.
132
takes_args = ["filename"]
274
takes_args = ["filename", "line?"]
133
275
takes_options = [
134
Option("all", help="show annotations on all lines"),
135
Option("plain", help="don't highlight annotation lines"),
276
Option("all", help="Show annotations on all lines."),
277
Option("plain", help="Don't highlight annotation lines."),
136
278
Option("line", type=int, argname="lineno",
137
help="jump to specified line number")
279
help="Jump to specified line number."),
139
282
aliases = ["gblame", "gpraise"]
141
def run(self, filename, all=False, plain=False, line=1):
284
def run(self, filename, all=False, plain=False, line='1', revision=None):
285
gtk = self.open_display()
147
except RuntimeError, e:
148
if str(e) == "could not open display":
290
raise BzrCommandError('Line argument ("%s") is not a number.' %
151
293
from annotate.gannotate import GAnnotateWindow
152
294
from annotate.config import GAnnotateConfig
154
(wt, path) = WorkingTree.open_containing(filename)
157
file_id = wt.path2id(path)
295
from bzrlib.bzrdir import BzrDir
297
wt, br, path = BzrDir.open_containing_tree_or_branch(filename)
301
tree = br.basis_tree()
303
file_id = tree.path2id(path)
159
305
if file_id is None:
160
306
raise NotVersionedError(filename)
307
if revision is not None:
308
if len(revision) != 1:
309
raise BzrCommandError("Only 1 revion may be specified.")
310
revision_id = revision[0].in_history(br).rev_id
311
tree = br.repository.revision_tree(revision_id)
313
revision_id = getattr(tree, 'get_revision_id', lambda: None)()
162
315
window = GAnnotateWindow(all, plain)
163
316
window.connect("destroy", lambda w: gtk.main_quit())
164
317
window.set_title(path + " - gannotate")
165
318
config = GAnnotateConfig(window)
169
window.annotate(branch, file_id)
324
window.annotate(tree, br, file_id)
325
window.jump_to_line(line)
172
window.jump_to_line(line)
176
register_command(cmd_gannotate)
178
class cmd_gcommit(Command):
334
class cmd_gcommit(GTKCommand):
179
335
"""GTK+ commit dialog
181
337
Graphical user interface for committing revisions"""
184
341
takes_options = []
186
343
def run(self, filename=None):
346
from commit import CommitDialog
347
from bzrlib.errors import (BzrCommandError,
354
(wt, path) = workingtree.WorkingTree.open_containing(filename)
356
except NoWorkingTree, e:
358
(br, path) = branch.Branch.open_containing(path)
360
commit = CommitDialog(wt, path, not br)
365
class cmd_gstatus(GTKCommand):
366
"""GTK+ status dialog
368
Graphical user interface for showing status
372
takes_args = ['PATH?']
375
def run(self, path='.'):
377
gtk = self.open_display()
378
from status import StatusDialog
379
(wt, wt_path) = workingtree.WorkingTree.open_containing(path)
380
status = StatusDialog(wt, wt_path)
381
status.connect("destroy", gtk.main_quit)
386
class cmd_gconflicts(GTKCommand):
389
Select files from the list of conflicts and run an external utility to
393
(wt, path) = workingtree.WorkingTree.open_containing('.')
395
from bzrlib.plugins.gtk.conflicts import ConflictsDialog
396
dialog = ConflictsDialog(wt)
401
class cmd_gpreferences(GTKCommand):
402
""" GTK+ preferences dialog.
407
from bzrlib.plugins.gtk.preferences import PreferencesWindow
408
dialog = PreferencesWindow()
413
class cmd_gmissing(Command):
414
""" GTK+ missing revisions dialog.
417
takes_args = ["other_branch?"]
418
def run(self, other_branch=None):
419
pygtk = import_pygtk()
192
422
except RuntimeError, e:
193
423
if str(e) == "could not open display":
194
424
raise NoDisplayError
196
from commit import GCommitDialog
197
from bzrlib.commit import Commit
198
from bzrlib.errors import (BzrCommandError, PointlessCommit, ConflictsInTree,
201
(wt, path) = WorkingTree.open_containing(filename)
204
file_id = wt.path2id(path)
207
raise NotVersionedError(filename)
209
dialog = GCommitDialog(wt)
210
dialog.set_title(path + " - Commit")
211
if dialog.run() != gtk.RESPONSE_CANCEL:
212
Commit().commit(working_tree=wt,message=dialog.message,
213
specific_files=dialog.specific_files)
215
register_command(cmd_gcommit)
426
from bzrlib.plugins.gtk.missing import MissingWindow
427
from bzrlib.branch import Branch
429
local_branch = Branch.open_containing(".")[0]
430
if other_branch is None:
431
other_branch = local_branch.get_parent()
433
if other_branch is None:
434
raise errors.BzrCommandError("No peer location known or specified.")
435
remote_branch = Branch.open_containing(other_branch)[0]
437
local_branch.lock_read()
439
remote_branch.lock_read()
441
dialog = MissingWindow(local_branch, remote_branch)
444
remote_branch.unlock()
446
local_branch.unlock()
449
class cmd_ginit(GTKCommand):
452
from initialize import InitDialog
453
dialog = InitDialog(os.path.abspath(os.path.curdir))
457
class cmd_gtags(GTKCommand):
459
br = branch.Branch.open_containing('.')[0]
461
gtk = self.open_display()
462
from tags import TagsWindow
463
window = TagsWindow(br)
485
register_command(cmd)
488
class cmd_commit_notify(GTKCommand):
489
"""Run the bzr commit notifier.
491
This is a background program which will pop up a notification on the users
492
screen when a commit occurs.
496
from notify import NotifyPopupMenu
497
gtk = self.open_display()
498
menu = NotifyPopupMenu()
499
icon = gtk.status_icon_new_from_file(os.path.join(data_path(), "bzr-icon-64.png"))
500
icon.connect('popup-menu', menu.display)
506
from bzrlib.bzrdir import BzrDir
507
from bzrlib import errors
508
from bzrlib.osutils import format_date
509
from bzrlib.transport import get_transport
510
if getattr(dbus, 'version', (0,0,0)) >= (0,41,0):
512
from bzrlib.plugins.dbus import activity
513
bus = dbus.SessionBus()
514
# get the object so we can subscribe to callbacks from it.
515
broadcast_service = bus.get_object(
516
activity.Broadcast.DBUS_NAME,
517
activity.Broadcast.DBUS_PATH)
519
def catch_branch(revision_id, urls):
520
# TODO: show all the urls, or perhaps choose the 'best'.
523
if isinstance(revision_id, unicode):
524
revision_id = revision_id.encode('utf8')
525
transport = get_transport(url)
526
a_dir = BzrDir.open_from_transport(transport)
527
branch = a_dir.open_branch()
528
revno = branch.revision_id_to_revno(revision_id)
529
revision = branch.repository.get_revision(revision_id)
530
summary = 'New revision %d in %s' % (revno, url)
531
body = 'Committer: %s\n' % revision.committer
532
body += 'Date: %s\n' % format_date(revision.timestamp,
535
body += revision.message
536
body = cgi.escape(body)
537
nw = pynotify.Notification(summary, body)
538
def start_viz(notification=None, action=None, data=None):
539
"""Start the viz program."""
540
pp = start_viz_window(branch, revision_id)
542
def start_branch(notification=None, action=None, data=None):
543
"""Start a Branch dialog"""
544
from bzrlib.plugins.gtk.branch import BranchDialog
545
bd = BranchDialog(remote_path=url)
547
nw.add_action("inspect", "Inspect", start_viz, None)
548
nw.add_action("branch", "Branch", start_branch, None)
554
broadcast_service.connect_to_signal("Revision", catch_branch,
555
dbus_interface=activity.Broadcast.DBUS_INTERFACE)
556
pynotify.init("bzr commit-notify")
559
register_command(cmd_commit_notify)
562
class cmd_gselftest(GTKCommand):
563
"""Version of selftest that displays a notification at the end"""
565
takes_args = builtins.cmd_selftest.takes_args
566
takes_options = builtins.cmd_selftest.takes_options
567
_see_also = ['selftest']
569
def run(self, *args, **kwargs):
572
default_encoding = sys.getdefaultencoding()
573
# prevent gtk from blowing up later
575
# prevent gtk from messing with default encoding
577
if sys.getdefaultencoding() != default_encoding:
579
sys.setdefaultencoding(default_encoding)
580
result = builtins.cmd_selftest().run(*args, **kwargs)
583
body = 'Selftest succeeded in "%s"' % os.getcwd()
586
body = 'Selftest failed in "%s"' % os.getcwd()
587
pynotify.init("bzr gselftest")
588
note = pynotify.Notification(cgi.escape(summary), cgi.escape(body))
589
note.set_timeout(pynotify.EXPIRES_NEVER)
593
register_command(cmd_gselftest)
597
gettext.install('olive-gtk')
217
600
class NoDisplayError(BzrCommandError):
218
601
"""gtk could not find a proper display"""
220
603
def __str__(self):
221
return "No DISPLAY. gannotate is disabled."
604
return "No DISPLAY. Unable to run GTK+ application."
608
from unittest import TestSuite
611
default_encoding = sys.getdefaultencoding()
614
result.addTest(tests.test_suite())
616
if sys.getdefaultencoding() != default_encoding:
618
sys.setdefaultencoding(default_encoding)