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?']
184
takes_options = ['revision']
58
def run(self, revision=None, file_list=None):
59
bzrdir = BzrDir.open_containing(".")[0]
60
repos = bzrdir.open_repository()
61
branch = bzrdir.open_branch()
62
tree2 = repos.revision_tree(branch.last_revision())
63
tree1 = WorkingTree.open_containing(".")[0]
65
from viz.diffwin import DiffWindow
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)
205
tree2 = tree1.basis_tree()
207
from diff import DiffWindow
68
except RuntimeError, e:
69
if str(e) == "could not open display":
72
window.connect("destroy", lambda w: gtk.main_quit())
73
window.set_diff("Working Tree", tree1, tree2)
78
register_command(cmd_gdiff)
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)
80
238
class cmd_visualise(Command):
81
239
"""Graphically visualise this branch.
91
Option('limit', "maximum number of revisions to display",
249
Option('limit', "Maximum number of revisions to display.",
93
takes_args = [ "location?" ]
251
takes_args = [ "locations*" ]
94
252
aliases = [ "visualize", "vis", "viz" ]
96
def run(self, location=".", revision=None, limit=None):
97
(branch, path) = Branch.open_containing(location)
99
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)
101
261
if revision is None:
102
revid = branch.last_revision()
262
revids.append(br.last_revision())
106
(revno, revid) = revision[0].in_history(branch)
108
from viz.bzrkapp import BzrkApp
111
app.show(branch, revid, limit)
113
branch.repository.unlock()
118
register_command(cmd_visualise)
120
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):
121
273
"""GTK+ annotate.
123
275
Browse changes to FILENAME line by line in a GTK+ window.
126
takes_args = ["filename"]
278
takes_args = ["filename", "line?"]
127
279
takes_options = [
128
Option("all", help="show annotations on all lines"),
129
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."),
130
282
Option("line", type=int, argname="lineno",
131
help="jump to specified line number")
283
help="Jump to specified line number."),
133
286
aliases = ["gblame", "gpraise"]
135
def run(self, filename, all=False, plain=False, line=1):
288
def run(self, filename, all=False, plain=False, line='1', revision=None):
141
except RuntimeError, e:
142
if str(e) == "could not open display":
294
raise BzrCommandError('Line argument ("%s") is not a number.' %
145
297
from annotate.gannotate import GAnnotateWindow
146
298
from annotate.config import GAnnotateConfig
148
(wt, path) = WorkingTree.open_containing(filename)
151
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)
153
309
if file_id is None:
154
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)()
156
window = GAnnotateWindow(all, plain)
319
window = GAnnotateWindow(all, plain, branch=br)
157
320
window.connect("destroy", lambda w: gtk.main_quit())
158
window.set_title(path + " - gannotate")
159
321
config = GAnnotateConfig(window)
163
window.annotate(branch, file_id)
327
window.annotate(tree, br, file_id)
328
window.jump_to_line(line)
166
window.jump_to_line(line)
170
register_command(cmd_gannotate)
172
class cmd_gcommit(Command):
337
class cmd_gcommit(GTKCommand):
173
338
"""GTK+ commit dialog
175
340
Graphical user interface for committing revisions"""
178
344
takes_options = []
180
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()
186
461
except RuntimeError, e:
187
462
if str(e) == "could not open display":
188
463
raise NoDisplayError
190
from commit import GCommitDialog
191
from bzrlib.commit import Commit
192
from bzrlib.errors import (BzrCommandError, PointlessCommit, ConflictsInTree,
195
(wt, path) = WorkingTree.open_containing(filename)
198
file_id = wt.path2id(path)
201
raise NotVersionedError(filename)
203
dialog = GCommitDialog(wt)
204
dialog.set_title(path + " - Commit")
205
if dialog.run() != gtk.RESPONSE_CANCEL:
206
Commit().commit(working_tree=wt,message=dialog.message,
207
specific_files=dialog.specific_files)
209
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
211
703
class NoDisplayError(BzrCommandError):
212
704
"""gtk could not find a proper display"""
214
706
def __str__(self):
215
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)