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
gsend GTK+ send merge directive.
30
gstatus GTK+ status dialog.
31
gtags Manage branch tags.
32
visualise Graphically visualise this branch.
39
version_info = (0, 95, 0, 'dev', 1)
41
if version_info[3] == 'final':
42
version_string = '%d.%d.%d' % version_info[:3]
44
version_string = '%d.%d.%d%s%d' % version_info
45
__version__ = version_string
47
required_bzrlib = (1, 3)
49
def check_bzrlib_version(desired):
50
"""Check that bzrlib is compatible.
52
If version is < bzr-gtk version, assume incompatible.
54
bzrlib_version = bzrlib.version_info[:2]
56
from bzrlib.trace import warning
58
# get the message out any way we can
59
from warnings import warn as warning
60
if bzrlib_version < desired:
61
from bzrlib.errors import BzrError
62
warning('Installed Bazaar version %s is too old to be used with bzr-gtk'
63
' %s.' % (bzrlib.__version__, __version__))
64
raise BzrError('Version mismatch: %r, %r' % (version_info, bzrlib.version_info) )
67
if version_info[2] == "final":
68
check_bzrlib_version(required_bzrlib)
70
from bzrlib.trace import warning
71
if __name__ != 'bzrlib.plugins.gtk':
72
warning("Not running as bzrlib.plugins.gtk, things may break.")
74
from bzrlib.lazy_import import lazy_import
75
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
def icon_path(*args):
112
basedirs = [os.path.join(data_path()),
113
"/usr/share/bzr-gtk",
114
"/usr/local/share/bzr-gtk"]
115
for basedir in basedirs:
116
path = os.path.join(basedir, 'icons', *args)
117
if os.path.exists(path):
123
pygtk = import_pygtk()
126
except RuntimeError, e:
127
if str(e) == "could not open display":
133
class GTKCommand(Command):
134
"""Abstract class providing GTK specific run commands."""
138
dialog = self.get_gtk_dialog(os.path.abspath('.'))
142
class cmd_gbranch(GTKCommand):
27
143
"""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):
147
def get_gtk_dialog(self, path):
148
from bzrlib.plugins.gtk.branch import BranchDialog
149
return BranchDialog(path)
152
class cmd_gcheckout(GTKCommand):
157
def get_gtk_dialog(self, path):
158
from bzrlib.plugins.gtk.checkout import CheckoutDialog
159
return CheckoutDialog(path)
163
class cmd_gpush(GTKCommand):
167
takes_args = [ "location?" ]
169
def run(self, location="."):
170
(br, path) = branch.Branch.open_containing(location)
172
from push import PushDialog
173
dialog = PushDialog(br.repository, br.last_revision(), br)
178
class cmd_gdiff(GTKCommand):
50
179
"""Show differences in working tree in a GTK+ Window.
52
181
Otherwise, all changes for the tree are listed.
183
takes_args = ['filename?']
55
184
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:
187
def run(self, revision=None, filename=None):
189
wt = workingtree.WorkingTree.open_containing(".")[0]
193
if revision is not None:
194
if len(revision) == 1:
196
revision_id = revision[0].as_revision_id(tree1.branch)
197
tree2 = branch.repository.revision_tree(revision_id)
198
elif len(revision) == 2:
199
revision_id_0 = revision[0].as_revision_id(branch)
200
tree2 = branch.repository.revision_tree(revision_id_0)
201
revision_id_1 = revision[1].as_revision_id(branch)
202
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)
205
tree2 = tree1.basis_tree()
207
from diff import DiffWindow
209
window = DiffWindow()
210
window.connect("destroy", gtk.main_quit)
211
window.set_diff("Working Tree", tree1, tree2)
212
if filename is not None:
213
tree_filename = wt.relpath(filename)
215
window.set_file(tree_filename)
217
if (tree1.path2id(tree_filename) is None and
218
tree2.path2id(tree_filename) is None):
219
raise NotVersionedError(filename)
220
raise BzrCommandError('No changes found for file "%s"' %
229
def start_viz_window(branch, revisions, limit=None):
230
"""Start viz on branch with revision revision.
232
:return: The viz window object.
234
from viz import BranchWindow
235
return BranchWindow(branch, revisions, limit)
86
238
class cmd_visualise(Command):
87
239
"""Graphically visualise this branch.
97
Option('limit', "maximum number of revisions to display",
249
Option('limit', "Maximum number of revisions to display.",
99
takes_args = [ "location?" ]
251
takes_args = [ "locations*" ]
100
252
aliases = [ "visualize", "vis", "viz" ]
102
def run(self, location=".", revision=None, limit=None):
103
(branch, path) = Branch.open_containing(location)
105
branch.repository.lock_read()
254
def run(self, locations_list, revision=None, limit=None):
256
if locations_list is None:
257
locations_list = ["."]
259
for location in locations_list:
260
(br, path) = branch.Branch.open_containing(location)
107
261
if revision is None:
108
revid = branch.last_revision()
262
revids.append(br.last_revision())
112
(revno, revid) = revision[0].in_history(branch)
114
from viz.bzrkapp import BzrkApp
117
app.show(branch, revid, limit)
119
branch.repository.unlock()
124
register_command(cmd_visualise)
126
class cmd_gannotate(Command):
264
revids.append(revision[0].as_revision_id(br))
266
pp = start_viz_window(br, revids, limit)
267
pp.connect("destroy", lambda w: gtk.main_quit())
272
class cmd_gannotate(GTKCommand):
127
273
"""GTK+ annotate.
129
275
Browse changes to FILENAME line by line in a GTK+ window.
132
takes_args = ["filename"]
278
takes_args = ["filename", "line?"]
133
279
takes_options = [
134
Option("all", help="show annotations on all lines"),
135
Option("plain", help="don't highlight annotation lines"),
280
Option("all", help="Show annotations on all lines."),
281
Option("plain", help="Don't highlight annotation lines."),
136
282
Option("line", type=int, argname="lineno",
137
help="jump to specified line number")
283
help="Jump to specified line number."),
139
286
aliases = ["gblame", "gpraise"]
141
def run(self, filename, all=False, plain=False, line=1):
288
def run(self, filename, all=False, plain=False, line='1', revision=None):
147
except RuntimeError, e:
148
if str(e) == "could not open display":
294
raise BzrCommandError('Line argument ("%s") is not a number.' %
151
297
from annotate.gannotate import GAnnotateWindow
152
298
from annotate.config import GAnnotateConfig
154
(wt, path) = WorkingTree.open_containing(filename)
157
file_id = wt.path2id(path)
299
from bzrlib.bzrdir import BzrDir
301
wt, br, path = BzrDir.open_containing_tree_or_branch(filename)
305
tree = br.basis_tree()
307
file_id = tree.path2id(path)
159
309
if file_id is None:
160
310
raise NotVersionedError(filename)
311
if revision is not None:
312
if len(revision) != 1:
313
raise BzrCommandError("Only 1 revion may be specified.")
314
revision_id = revision[0].as_revision_id(br)
315
tree = br.repository.revision_tree(revision_id)
317
revision_id = getattr(tree, 'get_revision_id', lambda: None)()
162
window = GAnnotateWindow(all, plain)
319
window = GAnnotateWindow(all, plain, branch=br)
163
320
window.connect("destroy", lambda w: gtk.main_quit())
164
window.set_title(path + " - gannotate")
165
321
config = GAnnotateConfig(window)
169
window.annotate(branch, file_id)
327
window.annotate(tree, br, file_id)
328
window.jump_to_line(line)
172
window.jump_to_line(line)
176
register_command(cmd_gannotate)
178
class cmd_gcommit(Command):
337
class cmd_gcommit(GTKCommand):
179
338
"""GTK+ commit dialog
181
340
Graphical user interface for committing revisions"""
184
344
takes_options = []
186
346
def run(self, filename=None):
349
from commit import CommitDialog
350
from bzrlib.errors import (BzrCommandError,
357
(wt, path) = workingtree.WorkingTree.open_containing(filename)
359
except NoWorkingTree, e:
360
from dialog import error_dialog
361
error_dialog(_i18n('Directory does not have a working tree'),
362
_i18n('Operation aborted.'))
363
return 1 # should this be retval=3?
365
# It is a good habit to keep things locked for the duration, but it
366
# could cause difficulties if someone wants to do things in another
367
# window... We could lock_read() until we actually go to commit
368
# changes... Just a thought.
371
dlg = CommitDialog(wt)
377
class cmd_gstatus(GTKCommand):
378
"""GTK+ status dialog
380
Graphical user interface for showing status
384
takes_args = ['PATH?']
385
takes_options = ['revision']
387
def run(self, path='.', revision=None):
390
from status import StatusDialog
391
(wt, wt_path) = workingtree.WorkingTree.open_containing(path)
393
if revision is not None:
395
revision_id = revision[0].as_revision_id(wt.branch)
397
from bzrlib.errors import BzrError
398
raise BzrError('Revision %r doesn\'t exist' % revision[0].user_spec )
402
status = StatusDialog(wt, wt_path, revision_id)
403
status.connect("destroy", gtk.main_quit)
407
class cmd_gsend(GTKCommand):
408
"""GTK+ send merge directive.
412
(br, path) = branch.Branch.open_containing(".")
414
from bzrlib.plugins.gtk.mergedirective import SendMergeDirectiveDialog
415
from StringIO import StringIO
416
dialog = SendMergeDirectiveDialog(br)
417
if dialog.run() == gtk.RESPONSE_OK:
419
outf.writelines(dialog.get_merge_directive().to_lines())
420
mail_client = br.get_config().get_mail_client()
421
mail_client.compose_merge_request(dialog.get_mail_to(), "[MERGE]",
427
class cmd_gconflicts(GTKCommand):
430
Select files from the list of conflicts and run an external utility to
434
(wt, path) = workingtree.WorkingTree.open_containing('.')
436
from bzrlib.plugins.gtk.conflicts import ConflictsDialog
437
dialog = ConflictsDialog(wt)
441
class cmd_gpreferences(GTKCommand):
442
""" GTK+ preferences dialog.
447
from bzrlib.plugins.gtk.preferences import PreferencesWindow
448
dialog = PreferencesWindow()
452
class cmd_gmissing(Command):
453
""" GTK+ missing revisions dialog.
456
takes_args = ["other_branch?"]
457
def run(self, other_branch=None):
458
pygtk = import_pygtk()
192
461
except RuntimeError, e:
193
462
if str(e) == "could not open display":
194
463
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)
465
from bzrlib.plugins.gtk.missing import MissingWindow
466
from bzrlib.branch import Branch
468
local_branch = Branch.open_containing(".")[0]
469
if other_branch is None:
470
other_branch = local_branch.get_parent()
472
if other_branch is None:
473
raise errors.BzrCommandError("No peer location known or specified.")
474
remote_branch = Branch.open_containing(other_branch)[0]
476
local_branch.lock_read()
478
remote_branch.lock_read()
480
dialog = MissingWindow(local_branch, remote_branch)
483
remote_branch.unlock()
485
local_branch.unlock()
488
class cmd_ginit(GTKCommand):
491
from initialize import InitDialog
492
dialog = InitDialog(os.path.abspath(os.path.curdir))
496
class cmd_gtags(GTKCommand):
498
br = branch.Branch.open_containing('.')[0]
501
from tags import TagsWindow
502
window = TagsWindow(br)
525
register_command(cmd)
528
class cmd_commit_notify(GTKCommand):
529
"""Run the bzr commit notifier.
531
This is a background program which will pop up a notification on the users
532
screen when a commit occurs.
536
from notify import NotifyPopupMenu
538
menu = NotifyPopupMenu()
539
icon = gtk.status_icon_new_from_file(icon_path("bzr-icon-64.png"))
540
icon.connect('popup-menu', menu.display)
546
from bzrlib.bzrdir import BzrDir
547
from bzrlib import errors
548
from bzrlib.osutils import format_date
549
from bzrlib.transport import get_transport
550
if getattr(dbus, 'version', (0,0,0)) >= (0,41,0):
552
BROADCAST_INTERFACE = "org.bazaarvcs.plugins.dbus.Broadcast"
553
bus = dbus.SessionBus()
555
def catch_branch(revision_id, urls):
556
# TODO: show all the urls, or perhaps choose the 'best'.
559
if isinstance(revision_id, unicode):
560
revision_id = revision_id.encode('utf8')
561
transport = get_transport(url)
562
a_dir = BzrDir.open_from_transport(transport)
563
branch = a_dir.open_branch()
564
revno = branch.revision_id_to_revno(revision_id)
565
revision = branch.repository.get_revision(revision_id)
566
summary = 'New revision %d in %s' % (revno, url)
567
body = 'Committer: %s\n' % revision.committer
568
body += 'Date: %s\n' % format_date(revision.timestamp,
571
body += revision.message
572
body = cgi.escape(body)
573
nw = pynotify.Notification(summary, body)
574
def start_viz(notification=None, action=None, data=None):
575
"""Start the viz program."""
576
pp = start_viz_window(branch, revision_id)
578
def start_branch(notification=None, action=None, data=None):
579
"""Start a Branch dialog"""
580
from bzrlib.plugins.gtk.branch import BranchDialog
581
bd = BranchDialog(remote_path=url)
583
nw.add_action("inspect", "Inspect", start_viz, None)
584
nw.add_action("branch", "Branch", start_branch, None)
590
bus.add_signal_receiver(catch_branch,
591
dbus_interface=BROADCAST_INTERFACE,
592
signal_name="Revision")
593
pynotify.init("bzr commit-notify")
596
register_command(cmd_commit_notify)
599
class cmd_gselftest(GTKCommand):
600
"""Version of selftest that displays a notification at the end"""
602
takes_args = builtins.cmd_selftest.takes_args
603
takes_options = builtins.cmd_selftest.takes_options
604
_see_also = ['selftest']
606
def run(self, *args, **kwargs):
609
default_encoding = sys.getdefaultencoding()
610
# prevent gtk from blowing up later
612
# prevent gtk from messing with default encoding
614
if sys.getdefaultencoding() != default_encoding:
616
sys.setdefaultencoding(default_encoding)
617
result = builtins.cmd_selftest().run(*args, **kwargs)
620
body = 'Selftest succeeded in "%s"' % os.getcwd()
623
body = 'Selftest failed in "%s"' % os.getcwd()
624
pynotify.init("bzr gselftest")
625
note = pynotify.Notification(cgi.escape(summary), cgi.escape(body))
626
note.set_timeout(pynotify.EXPIRES_NEVER)
630
register_command(cmd_gselftest)
633
class cmd_test_gtk(GTKCommand):
634
"""Version of selftest that just runs the gtk test suite."""
636
takes_options = ['verbose',
637
Option('one', short_name='1',
638
help='Stop when one test fails.'),
639
Option('benchmark', help='Run the benchmarks.'),
640
Option('lsprof-timed',
641
help='Generate lsprof output for benchmarked'
642
' sections of code.'),
644
help='List the tests instead of running them.'),
645
Option('randomize', type=str, argname="SEED",
646
help='Randomize the order of tests using the given'
647
' seed or "now" for the current time.'),
649
takes_args = ['testspecs*']
651
def run(self, verbose=None, one=False, benchmark=None,
652
lsprof_timed=None, list_only=False, randomize=None,
653
testspecs_list=None):
654
from bzrlib import __path__ as bzrlib_path
655
from bzrlib.tests import selftest
657
print '%10s: %s' % ('bzrlib', bzrlib_path[0])
659
print 'No benchmarks yet'
662
test_suite_factory = bench_suite
665
# TODO: should possibly lock the history file...
666
benchfile = open(".perf_history", "at", buffering=1)
668
test_suite_factory = test_suite
673
if testspecs_list is not None:
674
pattern = '|'.join(testspecs_list)
679
result = selftest(verbose=verbose,
682
test_suite_factory=test_suite_factory,
683
lsprof_timed=lsprof_timed,
684
bench_history=benchfile,
686
random_seed=randomize,
689
if benchfile is not None:
692
register_command(cmd_test_gtk)
697
gettext.install('olive-gtk')
699
# Let's create a specialized alias to protect '_' from being erased by other
700
# uses of '_' as an anonymous variable (think pdb for one).
701
_i18n = gettext.gettext
217
703
class NoDisplayError(BzrCommandError):
218
704
"""gtk could not find a proper display"""
220
706
def __str__(self):
221
return "No DISPLAY. gannotate is disabled."
707
return "No DISPLAY. Unable to run GTK+ application."
711
from unittest import TestSuite
714
default_encoding = sys.getdefaultencoding()
719
except errors.BzrCommandError:
721
result.addTest(tests.test_suite())
723
if sys.getdefaultencoding() != default_encoding:
725
sys.setdefaultencoding(default_encoding)