by
Changes to 13 files · Browse files at 83a5518d07b8 Showing diff from parent b165f766c4f0 36853de1c202 Diff from another changeset...
@@ -50,6 +50,9 @@ Launch the web server dialog in a separate process
:guilabel:`Shelve`
Launch the shelve tool in a separate process
+ :guilabel:`Patch Branch`
+ Toggles the display of the Patch Branch pane. This button is only
+ visible when the pbranch extension has been enabled by the user.
:guilabel:`Load more`
Load the next N revisions into the graph
:guilabel:`Load all`
|
@@ -304,3 +304,44 @@ remote_repository_url combination).
.. vim: noet ts=4
+
+pbranch
+=======
+
+Patch Branches (`pbranch <http://mercurial.selenic.com/wiki/PatchBranchExtension>`_)
+is a way to develop a series of patches for submission into a main
+repo. It is based on topic branches, one per patch, and is thus highly
+suitable for collaborative and/or long-term patch development and
+maintenance.
+
+'A detailed manual <http://arrenbrecht.ch/mercurial/pbranch/>'_ can be found online.
+
+It adds a number of commands which can be listed with
+:command:`hg help pbranch`::
+
+ pbackout - backs out the current patch branch (undoes all its changes)
+ pdiff - prints the final diff for the current or given patch branch
+ peditmessage - edit the patch message
+ pemail - send patches by email
+ pexport - exports patches
+ pextdiff - combines pdiff and extdiff
+ pgraph - print an ASCII art rendering of the patch dependency graph
+ pmerge - merge pending heads from dependencies into patch branches
+ pmessage - print the patch message(s)
+ pnew - start a new patch branch
+ pstatus - print status of current (or given) patch branch
+ reapply - reverts the working copy of all files touched by REV to REV
+
+**Installation**
+
+To test the use of this plugin, you can specify it on the Mercurial
+command line like this::
+
+ hg --config "extensions.pbranch=" pstatus
+
+You may want to add it to your Mercurial.ini or a repository's hgrc like this::
+
+ [extensions]
+ pbranch=
+
+If you do this, you can omit the --config command-line option.
|
|
@@ -191,6 +191,16 @@
def load_patch_details(self, patchfile):
'Load specified patch details into buffer and file list'
+ pf = open(patchfile)
+ self.load_patch_details_from_file_object(pf, patchfile)
+
+ def load_patch_details_from_file_object(self, pf, patchfile, isTemp=False):
+ """ Load patch details into buffer and file list
+ :param pf: open file object
+ :param patchfile: path and name of patch file
+ :param isTemp: if True, then pf is a temporary file
+ and patchfile does not exist
+ """
self._filelist.clear()
self._filelist.append(('*', _('[All Files]'), ''))
@@ -198,7 +208,11 @@ self.currev = -1
self.curphunks = {}
self.curpatch = patchfile
- pf = open(self.curpatch)
+ if isTemp:
+ # pf is a temporary, so update panel cache while we can
+ patch_ctx = csinfo.patchctx(patchfile, self.repo, patchHandle=pf)
+ self.summarypanel.update(patch_ctx, self.patchstyle)
+ pf.seek(0)
def get_path(a, b):
type = (a == '/dev/null') and 'A' or 'M'
type = (b == '/dev/null') and 'R' or type
@@ -340,7 +354,6 @@ tags, lines = self.prepare_diff(lines, offset, wfile)
for l in lines:
buf.insert(eob, l)
-
# inserts the tags
for name, p0, p1 in tags:
i0 = buf.get_iter_at_offset(p0)
@@ -419,7 +432,7 @@ tag = 'red'
l = hglib.diffexpand(l)
else:
- tag = 'black'
+ tag = 'normal'
l = hglib.diffexpand(l)
l = l+"\n"
length = len(l.decode('utf-8'))
@@ -570,7 +583,7 @@ def widget_func(widget, item, markups):
def linkwidget(revnum, revid, summary, highlight=None, branch=None):
# revision label
- opts = dict(underline='single', color='blue')
+ opts = dict(underline='single', color=gtklib.BLUE)
if highlight:
opts['weight'] = 'bold'
rev = '%s (%s)' % (gtklib.markup(revnum, **opts),
@@ -582,7 +595,7 @@ # summary & branch label
sum = gtklib.markup(summary)
if branch:
- sum = gtklib.markup(branch, color='black',
+ sum = gtklib.markup(branch, color=gtklib.NORMAL,
background=gtklib.PGREEN) + ' ' + sum
sumlabel = gtk.Label()
sumlabel.set_markup(sum)
@@ -785,7 +798,7 @@ tag_table = self._buffer.get_tag_table()
tag_table.add(make_texttag('diff', font=self.rawfonts['fontdiff']))
- tag_table.add(make_texttag('blue', foreground='blue'))
+ tag_table.add(make_texttag('blue', foreground=gtklib.BLUE))
if self.colorstyle == 'background':
tag_table.add(make_texttag('red',
paragraph_background=gtklib.PRED))
@@ -797,21 +810,21 @@ else:
tag_table.add(make_texttag('red', foreground=gtklib.DRED))
tag_table.add(make_texttag('green', foreground=gtklib.DGREEN))
- tag_table.add(make_texttag('black', foreground='black'))
+ tag_table.add(make_texttag('normal', foreground=gtklib.NORMAL))
tag_table.add(make_texttag('greybg',
- paragraph_background='grey',
+ paragraph_background=gtklib.CHANGE_HEADER,
weight=pango.WEIGHT_BOLD))
- tag_table.add(make_texttag('yellowbg', background='yellow'))
+ tag_table.add(make_texttag('yellowbg', background=gtklib.YELLOW))
- issuelink_tag = make_texttag('issuelink', foreground='blue',
+ issuelink_tag = make_texttag('issuelink', foreground=gtklib.BLUE,
underline=pango.UNDERLINE_SINGLE)
issuelink_tag.connect('event', self.issuelink_event)
tag_table.add(issuelink_tag)
- urllink_tag = make_texttag('urllink', foreground='blue',
+ urllink_tag = make_texttag('urllink', foreground=gtklib.BLUE,
underline=pango.UNDERLINE_SINGLE)
urllink_tag.connect('event', self.urllink_event)
tag_table.add(urllink_tag)
- csetlink_tag = make_texttag('csetlink', foreground='blue',
+ csetlink_tag = make_texttag('csetlink', foreground=gtklib.BLUE,
underline=pango.UNDERLINE_SINGLE)
csetlink_tag.connect('event', self.csetlink_event)
tag_table.add(csetlink_tag)
|
@@ -136,18 +136,31 @@
class patchctx(object):
- def __init__(self, patchpath, repo):
+ def __init__(self, patchpath, repo, patchHandle=None):
+ """ Read patch context from file
+ :param patchHandle: If set, then the patch is a temporary.
+ The provided handle is used to read the patch and
+ the patchpath contains the name of the patch.
+ The handle is NOT closed.
+ """
self._path = patchpath
self._patchname = os.path.basename(patchpath)
self._repo = repo
- pf = open(patchpath)
+ if patchHandle:
+ pf = patchHandle
+ pf_start_pos = pf.tell()
+ else:
+ pf = open(patchpath)
try:
data = patch.extract(self._repo.ui, pf)
tmpfile, msg, user, date, branch, node, p1, p2 = data
if tmpfile:
os.unlink(tmpfile)
finally:
- pf.close()
+ if patchHandle:
+ pf.seek(pf_start_pos)
+ else:
+ pf.close()
if not msg and hasattr(repo, 'mq'):
# attempt to get commit message
from hgext import mq
@@ -349,10 +362,10 @@ elif item in ('revnum', 'p4', 'svn'):
return str(value)
elif item in ('rawbranch', 'branch'):
- return gtklib.markup(' %s ' % value, color='black',
+ return gtklib.markup(' %s ' % value, color=gtklib.BLACK,
background=gtklib.PGREEN)
elif item in ('rawtags', 'tags'):
- opts = dict(color='black', background=gtklib.PYELLOW)
+ opts = dict(color=gtklib.BLACK, background=gtklib.PYELLOW)
tags = [gtklib.markup(' %s ' % tag, **opts) for tag in value]
return ' '.join(tags)
elif item in ('desc', 'summary', 'user', 'shortuser',
@@ -469,6 +482,13 @@ return self.info.get_widget(item, self, self.ctx, self.custom, **kargs)
def update(self, target=None, custom=None, repo=None):
+ self.ctx = None
+ if type(target) == patchctx:
+ # If a patchctx is specified as target, use it instead
+ # of creating a context from revision or patch file
+ self.ctx = target
+ target = None
+ self.target = None
if target is None:
target = self.target
if target is not None:
@@ -480,7 +500,8 @@ repo = self.repo
if repo is not None:
self.repo = repo
- self.ctx = create_context(repo, target)
+ if self.ctx is None:
+ self.ctx = create_context(repo, target)
if self.ctx is None:
return False # cannot update
return True
|
|
@@ -37,6 +37,9 @@ DRED = '#900000'
DGREEN = '#006400'
DBLUE = '#000090'
+DYELLOW = '#6A6A00'
+DORANGE = '#AA5000'
+# DORANGE = '#FF8000'
PRED = '#ffcccc'
PGREEN = '#aaffaa'
@@ -44,6 +47,102 @@PYELLOW = '#ffffaa'
PORANGE = '#ffddaa'
+RED = 'red'
+GREEN = 'green'
+BLUE = 'blue'
+YELLOW = 'yellow'
+BLACK = 'black'
+WHITE = 'white'
+GREY = 'grey'
+
+NORMAL = BLACK
+NEW_REV_COLOR = DGREEN
+CHANGE_HEADER = GREY
+
+UP_ARROW_COLOR = '#feaf3e'
+DOWN_ARROW_COLOR = '#8ae234'
+STAR_COLOR = '#fce94f'
+CELL_GREY = '#2e3436'
+STATUS_HEADER = '#DDDDDD'
+STATUS_REJECT_BACKGROUND = '#EEEEEE'
+STATUS_REJECT_FOREGROUND = '#888888'
+
+# line colors
+MAINLINE_COLOR = ( 0.0, 0.0, 0.0 )
+LINE_COLORS = [
+ ( 1.0, 0.0, 0.0 ),
+ ( 1.0, 1.0, 0.0 ),
+ ( 0.0, 1.0, 0.0 ),
+ ( 0.0, 1.0, 1.0 ),
+ ( 0.0, 0.0, 1.0 ),
+ ( 1.0, 0.0, 1.0 ),
+ ]
+
+def get_gtk_colors():
+ color_scheme = gtk.settings_get_default().get_property('gtk-color-scheme')
+ colors = {}
+ for color in color_scheme.split('\n'):
+ color = color.strip()
+ if color:
+ name, color = color.split(':')
+ colors[name.strip()] = gtk.gdk.color_parse(color.strip())
+ return colors
+
+def _init_colors():
+ global NORMAL, MAINLINE_COLOR
+
+ gtk_colors = get_gtk_colors()
+
+ try:
+ normal = gtk_colors['fg_color']
+ except KeyError:
+ # TODO: find out how to log such errors
+ pass
+ else:
+ NORMAL = str(normal)
+ MAINLINE_COLOR = (
+ normal.red_float,
+ normal.green_float,
+ normal.blue_float
+ )
+
+ # adjust colors for a dark color scheme:
+ if normal.value > 0.5:
+ global RED, GREEN, BLUE, BLACK, WHITE, \
+ DRED, DGREEN, DBLUE, DYELLOW, DORANGE, \
+ PRED, PGREEN, PBLUE, PYELLOW, PORANGE, \
+ NEW_REV_COLOR, LINE_COLORS, CHANGE_HEADER
+
+ RED = PRED
+ GREEN = NEW_REV_COLOR = PGREEN
+ BLUE = PBLUE
+ PRED = DRED
+ DRED = '#FF6161'
+# DRED, PRED = PRED, DRED
+ DGREEN, PGREEN = PGREEN, DGREEN
+ DBLUE, PBLUE = PBLUE, DBLUE
+ DYELLOW, PYELLOW = PYELLOW, DYELLOW
+ DORANGE, PORANGE = PORANGE, DORANGE
+ BLACK, WHITE = WHITE, BLACK
+
+ CHANGE_HEADER = '#404040'
+
+ LINE_COLORS = [
+ ( 1.0, 0.3804, 0.3804 ),
+ ( 1.0, 1.0, 0.0 ),
+ ( 0.0, 1.0, 0.0 ),
+ ( 0.0, 1.0, 1.0 ),
+ ( 0.2902, 0.4863, 0.851 ),
+ ( 1.0, 0.3882, 1.0 ),
+ ]
+
+ # TODO: dark color scheme for:
+ # UP_ARROW_COLOR, DOWN_ARROW_COLOR, STAR_COLOR,
+ # CELL_GREY, STATUS_HEADER, STATUS_REJECT_BACKGROUND,
+ # STATUS_REJECT_FOREGROUND
+
+_init_colors()
+
def set_tortoise_icon(window, thgicon):
ico = paths.get_tortoise_icon(thgicon)
if ico: window.set_icon_from_file(ico)
|
@@ -196,7 +196,7 @@ self.buf = gtk.TextBuffer()
self.buf.create_tag('removed', foreground=gtklib.DRED)
self.buf.create_tag('added', foreground=gtklib.DGREEN)
- self.buf.create_tag('position', foreground='#FF8000')
+ self.buf.create_tag('position', foreground=gtklib.DORANGE)
self.buf.create_tag('header', foreground=gtklib.DBLUE)
diffview = gtk.TextView(self.buf)
scroller.add(diffview)
|
@@ -406,7 +406,7 @@ markup = markup % (gtklib.DRED, 'bold')
icons = {'error': True}
else:
- markup = markup % ('black', 'normal')
+ markup = markup % (gtklib.NORMAL, 'normal')
icons = {}
text = gtklib.markup_escape_text(text)
self.rlabel.set_markup(markup % text)
|
|
|
@@ -28,6 +28,7 @@ from tortoisehg.hgtk import backout, status, hgemail, tagadd, update, merge
from tortoisehg.hgtk import archive, changeset, thgconfig, thgmq, histdetails
from tortoisehg.hgtk import statusbar, bookmark, thgimport
+from tortoisehg.hgtk import thgpbranch
MODE_REVRANGE = 0
MODE_FILEPATS = 1
@@ -215,7 +216,17 @@ tip=_('Show/Hide Patch Queue'),
toggle=True,
icon='menupatch.ico')
- tbar += [gtk.SeparatorToolItem(), self.mqtb]
+ tbar += [self.mqtb]
+ if 'pbranch' in self.exs:
+ self.pbranchtb = self.make_toolbutton(gtk.STOCK_DIRECTORY,
+ _('Patch Branch'),
+ self.pbranch_clicked, name='pbranch',
+ tip=_('Show/Hide Patch Branch'),
+ toggle=True,
+ icon='menupatch.ico')
+ tbar += [self.pbranchtb]
+ if 'mq' in self.exs or 'pbranch' in self.exs:
+ tbar += [gtk.SeparatorToolItem()]
sep = gtk.SeparatorToolItem()
sep.set_expand(True)
sep.set_draw(False)
@@ -322,6 +333,12 @@ else:
p4menu = []
+ if 'pbranch' in self.exs:
+ pbranch_item = [dict(text=_('Patch Branch'), name='pbranch', ascheck=True,
+ func=self.pbranch_clicked, check=self.setting_pbranchvis) ]
+ else:
+ pbranch_item = []
+
return [
dict(text=_('_View'), subitems=[
dict(text=_('Load more Revisions'), name='load-more',
@@ -334,7 +351,7 @@ ] + sync_bar_item + [
dict(text=_('Filter Bar'), ascheck=True,
func=self.toggle_show_filterbar, check=self.show_filterbar),
- ] + mq_item + [
+ ] + mq_item + pbranch_item + [
dict(text='----'),
dict(text=_('Refresh'), func=refresh, args=[False],
icon=gtk.STOCK_REFRESH),
@@ -817,7 +834,24 @@ else:
self.goto_rev(revid)
- def repo_invalidated(self, mqwidget):
+ def pbranch_selected(self, pbranchwidget, revid, patchname):
+ 'if revid < 0 then the patch is listed in .hg/pgraph but not in repo'
+ self.stbar.set_text('')
+ pf = tempfile.TemporaryFile()
+ try:
+ try:
+ pf.writelines(pbranchwidget.pdiff(patchname))
+ except (util.Abort, error.RepoError), e:
+ self.stbar.set_text(str(e))
+ return
+ self.currevid = self.lastrevid = None
+ pf.seek(0)
+ self.changeview.load_patch_details_from_file_object(pf, patchname, isTemp=True)
+ finally:
+ pf.close()
+
+ def repo_invalidated(self, widget):
+ 'Emitted from MQWidget and PBranchWidget'
self.reload_log()
def files_dropped(self, mqwidget, files, *args):
@@ -889,8 +923,9 @@ else:
item.set_sensitive(False)
- # enable MQ panel
+ # enable panels
self.enable_mqpanel()
+ self.enable_pbranchpanel()
def get_proxy_args(self):
item = self.get_menuitem('use-proxy-server')
@@ -922,6 +957,13 @@ else:
settings['glog-mqpane'] = self.setting_mqhpos
settings['glog-mqvis'] = self.setting_mqvis
+ if hasattr(self, 'pbranchpaned') and self.pbranchwidget.has_patch():
+ curpos = self.pbranchpaned.get_position()
+ settings['glog-pbranchpane'] = curpos or self.setting_pbranchhpos
+ settings['glog-pbranchvis'] = bool(curpos)
+ else:
+ settings['glog-pbranchpane'] = self.setting_pbranchhpos
+ settings['glog-pbranchvis'] = self.setting_pbranchvis
settings['branch-color'] = self.graphview.get_property('branch-color')
settings['show-output'] = self.showoutput
settings['show-toolbar'] = self.show_toolbar
@@ -943,6 +985,8 @@ self.setting_hpos = settings.get('glog-hpane', -1)
self.setting_mqhpos = settings.get('glog-mqpane', 140) or 140
self.setting_mqvis = settings.get('glog-mqvis', False)
+ self.setting_pbranchhpos = settings.get('glog-pbranchpane', 140) or 140
+ self.setting_pbranchvis = settings.get('glog-pbranchvis', False)
self.branch_color = settings.get('branch-color', False)
self.showoutput = settings.get('show-output', False)
self.show_toolbar = settings.get('show-toolbar', True)
@@ -982,6 +1026,10 @@ if hasattr(self, 'mqwidget'):
self.mqwidget.refresh()
+ # refresh pbranch widget if exists
+ if hasattr(self, 'pbranchwidget'):
+ self.pbranchwidget.refresh()
+
# force a redraw of the visible rows
self.graphview.hide()
self.graphview.show()
@@ -1116,6 +1164,10 @@ 'count': ncount, 'total': ntotal}
self.stbar.set_text(mq_text, name='mq')
+ # refresh pbranch widget if exists
+ if hasattr(self, 'pbranchwidget'):
+ self.pbranchwidget.refresh()
+
# Remember options to next time reload_log is called
self.filteropts = opts
@@ -1305,6 +1357,7 @@ # prepare statusbar
self.stbar = statusbar.StatusBar()
self.stbar.append_field('mq')
+ self.stbar.append_field('pbranch')
self.stbar.append_field('filter')
self.stbar.append_field('rev')
@@ -1516,6 +1569,25 @@ midpane.pack_start(self.graphview)
midpane.show_all()
+ # pbranch widget
+ if 'pbranch' in self.exs:
+ # create PBranchWidget
+ self.pbranchwidget = thgpbranch.PBranchWidget(
+ self, self.repo, self.stbar, accelgroup, self.tooltips)
+ self.pbranchwidget.connect('patch-selected', self.pbranch_selected)
+ self.pbranchwidget.connect('repo-invalidated', self.repo_invalidated)
+
+ def wrapframe(widget):
+ frame = gtk.Frame()
+ frame.set_shadow_type(gtk.SHADOW_ETCHED_IN)
+ frame.add(widget)
+ return frame
+ self.pbranchpaned = gtk.HPaned()
+ self.pbranchpaned.add1(wrapframe(self.pbranchwidget))
+ self.pbranchpaned.add2(wrapframe(midpane))
+
+ midpane = self.pbranchpaned
+
# MQ widget
if 'mq' in self.exs:
# create MQWidget
@@ -2038,6 +2110,8 @@ self.hpaned.set_position(self.setting_hpos)
if hasattr(self, 'mqpaned') and self.mqtb.get_active():
self.mqpaned.set_position(self.setting_mqhpos)
+ if hasattr(self, 'pbranchpaned') and self.pbranchtb.get_active():
+ self.pbranchpaned.set_position(self.setting_pbranchhpos)
def thgdiff(self, treeview):
'ctrl-d handler'
@@ -2626,6 +2700,26 @@ def mq_clicked(self, widget, *args):
self.enable_mqpanel(widget.get_active())
+ def enable_pbranchpanel(self, enable=None):
+ if not hasattr(self, 'pbranchpaned'):
+ return
+ if enable is None:
+ enable = self.setting_pbranchvis and self.pbranchwidget.has_patch()
+ oldpos = self.pbranchpaned.get_position()
+ self.pbranchpaned.set_position(enable and self.setting_pbranchhpos or 0)
+ if not enable and oldpos:
+ self.setting_pbranchhpos = oldpos
+
+ # set the state of PBranch toolbutton
+ if hasattr(self, 'pbranchtb'):
+ self.pbranchtb.handler_block_by_func(self.pbranch_clicked)
+ self.cmd_set_active('pbranch', enable)
+ self.pbranchtb.handler_unblock_by_func(self.pbranch_clicked)
+ self.cmd_set_sensitive('pbranch', self.pbranchwidget.has_pbranch())
+
+ def pbranch_clicked(self, widget, data=None):
+ self.enable_pbranchpanel(widget.get_active())
+
def tree_button_press(self, tree, event):
if event.button == 3 and not (event.state & (gtk.gdk.SHIFT_MASK |
gtk.gdk.CONTROL_MASK)):
|
@@ -19,6 +19,8 @@ import pango
import cairo
+from tortoisehg.hgtk import gtklib
+
# Styles used when rendering revision graph edges
style_SOLID = 0
style_DASHED = 1
@@ -87,24 +89,15 @@ colours and the fg parameter provides the multiplier that should be
applied to the foreground colours.
"""
- mainline_color = ( 0.0, 0.0, 0.0 )
- colours = [
- ( 1.0, 0.0, 0.0 ),
- ( 1.0, 1.0, 0.0 ),
- ( 0.0, 1.0, 0.0 ),
- ( 0.0, 1.0, 1.0 ),
- ( 0.0, 0.0, 1.0 ),
- ( 1.0, 0.0, 1.0 ),
- ]
if isinstance(colour, str):
r, g, b = colour[1:3], colour[3:5], colour[5:7]
colour_rgb = int(r, 16) / 255., int(g, 16) / 255., int(b, 16) / 255.
else:
if colour == 0:
- colour_rgb = mainline_color
+ colour_rgb = gtklib.MAINLINE_COLOR
else:
- colour_rgb = colours[colour % len(colours)]
+ colour_rgb = gtklib.LINE_COLORS[colour % len(gtklib.LINE_COLORS)]
red = (colour_rgb[0] * fg) or bg
green = (colour_rgb[1] * fg) or bg
@@ -187,7 +180,7 @@ # Possible node status
if status != 0:
def draw_arrow(x, y, dir):
- self.set_colour(ctx, '#2e3436', 0.0, 1.0)
+ self.set_colour(ctx, gtklib.CELL_GREY, 0.0, 1.0)
ctx.rectangle(x, y, 2, 5)
ax, ay = x, y + (dir == 'down' and 5 or 0)
inc = 3 * (dir == 'up' and -1 or 1)
@@ -196,12 +189,12 @@ ctx.line_to(ax + 1, ay + inc)
ctx.line_to(ax - 2, ay)
ctx.stroke_preserve()
- fillcolor = dir == 'up' and '#feaf3e' or '#8ae234'
+ fillcolor = dir == 'up' and gtklib.UP_ARROW_COLOR or gtklib.DOWN_ARROW_COLOR
self.set_colour(ctx, fillcolor, 0.0, 1.0)
ctx.fill()
def draw_star(x, y, radius, nodes, offset=False):
- self.set_colour(ctx, '#2e3436', 0.0, 1.0)
+ self.set_colour(ctx, gtklib.CELL_GREY, 0.0, 1.0)
total_nodes = nodes * 2 #inner + outer nodes
angle = 2 * math.pi / total_nodes;
offset = offset and angle / 2 or 0
@@ -216,7 +209,7 @@ else:
ctx.line_to(arc_x, arc_y)
ctx.stroke_preserve()
- self.set_colour(ctx, '#fce94f', 0.0, 1.0)
+ self.set_colour(ctx, gtklib.STAR_COLOR, 0.0, 1.0)
ctx.fill()
arrow_y = arc_start_position_y - box_size / 4
|
@@ -179,7 +179,7 @@ if parent is None:
parent = ctx.parents()[0].node()
M, A, R = self.repo.status(parent, ctx.node())[:3]
- common = dict(color='black')
+ common = dict(color=gtklib.BLACK)
M = M and gtklib.markup(' %s ' % len(M),
background=gtklib.PORANGE, **common) or ''
A = A and gtklib.markup(' %s ' % len(A),
@@ -217,13 +217,13 @@ bg = gtklib.PORANGE
elif tag in self.mqpatches:
bg = gtklib.PBLUE
- style = {'color': 'black', 'background': bg}
+ style = {'color': gtklib.BLACK, 'background': bg}
tstr += gtklib.markup(' %s ' % tag, **style) + ' '
branch = ctx.branch()
bstr = ''
if self.branchtags.get(branch) == node:
- bstr += gtklib.markup(' %s ' % branch, color='black',
+ bstr += gtklib.markup(' %s ' % branch, color=gtklib.BLACK,
background=gtklib.PGREEN) + ' '
if revid in self.wcparents:
@@ -295,7 +295,7 @@ self.color_func = self.text_color_author
def text_color_default(self, rev, author):
- return int(rev) >= self.origtip and 'darkgreen' or 'black'
+ return int(rev) >= self.origtip and gtklib.NEW_REV_COLOR or gtklib.NORMAL
colors = '''black blue deeppink mediumorchid blue burlywood4 goldenrod
slateblue red2 navy dimgrey'''.split()
@@ -303,7 +303,7 @@
def text_color_author(self, rev, author):
if int(rev) >= self.origtip:
- return 'darkgreen'
+ return gtklib.NEW_REV_COLOR
for re, v in self.author_pats:
if (re.search(author)):
return v
|
@@ -53,7 +53,7 @@ elif line.startswith('+'):
hunk += gtklib.markup(line, color=gtklib.DGREEN)
elif line.startswith('@@'):
- hunk = gtklib.markup(line, color='#FF8000')
+ hunk = gtklib.markup(line, color=gtklib.DORANGE)
else:
hunk += gtklib.markup(line)
return hunk
@@ -411,7 +411,7 @@ diffcol.add_attribute(cell, 'markup', DM_DISP_TEXT)
# differentiate header chunks
- cell.set_property('cell-background', '#DDDDDD')
+ cell.set_property('cell-background', gtklib.STATUS_HEADER)
diffcol.add_attribute(cell, 'cell_background_set', DM_IS_HEADER)
self.headerfont = self.difffont.copy()
self.headerfont.set_weight(pango.WEIGHT_HEAVY)
@@ -420,8 +420,8 @@ self.rejfont = self.difffont.copy()
self.rejfont.set_weight(pango.WEIGHT_LIGHT)
diffcol.add_attribute(cell, 'font-desc', DM_FONT)
- cell.set_property('background', '#EEEEEE')
- cell.set_property('foreground', '#888888')
+ cell.set_property('background', gtklib.STATUS_REJECT_BACKGROUND)
+ cell.set_property('foreground', gtklib.STATUS_REJECT_FOREGROUND)
diffcol.add_attribute(cell, 'background-set', DM_REJECTED)
diffcol.add_attribute(cell, 'foreground-set', DM_REJECTED)
difftree.append_column(diffcol)
@@ -870,7 +870,8 @@ sel = lambda x: x >= lasthunk or not dmodel[hc+x+1][DM_REJECTED]
newtext = chunks[0].selpretty(sel)
if not selected:
- newtext = "<span foreground='#888888'>" + newtext + "</span>"
+ newtext = "<span foreground='" + gtklib.STATUS_REJECT_FOREGROUND + \
+ "'>" + newtext + "</span>"
dmodel[hc][DM_DISP_TEXT] = newtext
def updated_codes(self):
@@ -920,15 +921,15 @@ elif stat == 'R':
text_renderer.set_property('foreground', gtklib.DRED)
elif stat == 'C':
- text_renderer.set_property('foreground', 'black')
+ text_renderer.set_property('foreground', gtklib.NORMAL)
elif stat == '!':
- text_renderer.set_property('foreground', 'red')
+ text_renderer.set_property('foreground', gtklib.RED)
elif stat == '?':
- text_renderer.set_property('foreground', '#AA5000')
+ text_renderer.set_property('foreground', gtklib.DORANGE)
elif stat == 'I':
- text_renderer.set_property('foreground', 'black')
+ text_renderer.set_property('foreground', gtklib.NORMAL)
else:
- text_renderer.set_property('foreground', 'black')
+ text_renderer.set_property('foreground', gtklib.NORMAL)
def rename_file(self, wfile):
@@ -1096,7 +1097,7 @@ else:
buf.create_tag('removed', foreground=gtklib.DRED)
buf.create_tag('added', foreground=gtklib.DGREEN)
- buf.create_tag('position', foreground='#FF8000')
+ buf.create_tag('position', foreground=gtklib.DORANGE)
buf.create_tag('header', foreground=gtklib.DBLUE)
bufiter = buf.get_start_iter()
|
@@ -721,11 +721,11 @@
stat = row[MQ_STATUS]
if stat == 'A':
- cell.set_property('foreground', 'blue')
+ cell.set_property('foreground', gtklib.BLUE)
elif stat == 'U':
- cell.set_property('foreground', '#909090')
+ cell.set_property('foreground', gtklib.GREY)
else:
- cell.set_property('foreground', 'black')
+ cell.set_property('foreground', gtklib.NORMAL)
patchname = row[MQ_NAME]
if self.is_qtip(patchname):
|
|
|
@@ -0,0 +1,868 @@ + # thgpbranch.py - embeddable widget for the PatchBranch extension
+#
+# Copyright 2009 Peer Sommerlund <peer.sommerlund@gmail.com>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2, incorporated herein by reference.
+
+import os
+import tempfile
+import gtk
+import gobject
+
+from mercurial import cmdutil, extensions
+from mercurial import commands as hg
+import mercurial.ui
+
+from tortoisehg.util.i18n import _
+
+from tortoisehg.hgtk import hgcmd
+from tortoisehg.hgtk import update
+from tortoisehg.hgtk import gtklib, dialog
+from tortoisehg.hgtk.logview import graphcell
+
+# Patch Branch model enumeration
+M_NODE = 0
+M_IN_LINES = 1
+M_OUT_LINES = 2
+M_NAME = 3
+M_STATUS = 4
+M_TITLE = 5
+M_MSG = 6
+M_MSGESC = 7
+
+# Patch Branch column enumeration
+C_GRAPH = 0
+C_STATUS = 1
+C_NAME = 2
+C_TITLE = 3
+C_MSG = 4
+
+class PBranchWidget(gtk.VBox):
+
+ __gproperties__ = {
+ 'graph-column-visible': (gobject.TYPE_BOOLEAN,
+ 'Graph',
+ 'Show graph column',
+ False,
+ gobject.PARAM_READWRITE),
+ 'status-column-visible': (gobject.TYPE_BOOLEAN,
+ 'Status',
+ 'Show status column',
+ False,
+ gobject.PARAM_READWRITE),
+ 'name-column-visible': (gobject.TYPE_BOOLEAN,
+ 'Name',
+ 'Show name column',
+ False,
+ gobject.PARAM_READWRITE),
+ 'title-column-visible': (gobject.TYPE_BOOLEAN,
+ 'Title',
+ 'Show title column',
+ False,
+ gobject.PARAM_READWRITE),
+ 'message-column-visible': (gobject.TYPE_BOOLEAN,
+ 'Title',
+ 'Show title column',
+ False,
+ gobject.PARAM_READWRITE),
+ 'show-internal-branches': (gobject.TYPE_BOOLEAN,
+ 'ShowInternalBranches',
+ "Show internal branches",
+ False,
+ gobject.PARAM_READWRITE)
+ }
+
+ __gsignals__ = {
+ 'repo-invalidated': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ()),
+ 'patch-selected': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ (int, # revision number for patch head
+ str)) # patch name
+ }
+
+ def __init__(self, parentwin, repo, statusbar, accelgroup=None, tooltips=None):
+ gtk.VBox.__init__(self)
+
+ self.parent_window = parentwin
+ self.repo = repo
+ self.pbranch = extensions.find('pbranch')
+ self.statusbar = statusbar
+
+ # top toolbar
+ tbar = gtklib.SlimToolbar(tooltips)
+
+ ## buttons
+ self.btn = {}
+ pmergebtn = tbar.append_button(gtk.STOCK_CONVERT,
+ _('Merge all pending dependencies'))
+ pmergebtn.connect('clicked', self.pmerge_clicked)
+ self.btn['pmerge'] = pmergebtn
+
+ pbackoutbtn = tbar.append_button(gtk.STOCK_GO_BACK,
+ _('Backout current patch branch'))
+ pbackoutbtn.connect('clicked', self.pbackout_clicked)
+ self.btn['pbackout'] = pbackoutbtn
+
+ reapplybtn = gtk.ToolButton(gtk.STOCK_GO_FORWARD)
+ reapplybtn = tbar.append_button(gtk.STOCK_GO_FORWARD,
+ _('Backport part of a changeset to a dependency'))
+ reapplybtn.connect('clicked', self.reapply_clicked)
+ self.btn['reapply'] = reapplybtn
+
+ pnewbtn = tbar.append_button(gtk.STOCK_NEW,
+ _('Start a new patch branch'))
+ pnewbtn.connect('clicked', self.pnew_clicked)
+ self.btn['pnew'] = pnewbtn
+
+ pgraphbtn = tbar.append_button(gtk.STOCK_EDIT,
+ _('Edit patch dependency graph'))
+ pgraphbtn.connect('clicked', self.edit_pgraph_clicked)
+ self.btn['pnew'] = pnewbtn
+
+ ## separator
+ tbar.append_space()
+
+ ## drop-down menu
+ menubtn = gtk.MenuToolButton('')
+ menubtn.set_menu(self.create_view_menu())
+ tbar.append_widget(menubtn, padding=0)
+ self.btn['menu'] = menubtn
+ def after_init():
+ menubtn.child.get_children()[0].hide()
+ gtklib.idle_add_single_call(after_init)
+ self.pack_start(tbar, False, False)
+
+ # center pane
+ mainbox = gtk.VBox()
+ self.pack_start(mainbox, True, True)
+
+ ## scrolled pane
+ pane = gtk.ScrolledWindow()
+ pane.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ pane.set_shadow_type(gtk.SHADOW_IN)
+ mainbox.pack_start(pane)
+
+ ### patch list
+ #### patch list model
+ self.model = gtk.ListStore(
+ gobject.TYPE_PYOBJECT, # node info
+ gobject.TYPE_PYOBJECT, # in-lines
+ gobject.TYPE_PYOBJECT, # out-lines
+ str, # patch name
+ str, # patch status
+ str, # patch title
+ str, # patch message
+ str) # patch message escaped
+ #### patch list view
+ self.list = gtk.TreeView(self.model)
+ # To support old PyGTK (<2.12)
+ if hasattr(self.list, 'set_tooltip_column'):
+ self.list.set_tooltip_column(M_MSGESC)
+ self.list.connect('cursor-changed', self.list_sel_changed)
+ self.list.connect('button-press-event', self.list_pressed)
+ self.list.connect('row-activated', self.list_row_activated)
+ self.list.connect('size-allocate', self.list_size_allocated)
+
+ #### patch list columns
+ self.cols = {}
+ self.cells = {}
+
+ def addcol(header, col_idx, model_idx=None, right=False, resizable=False,
+ editable=False, editfunc=None, cell_renderer=None,
+ properties=[]):
+ header = (right and '%s ' or ' %s') % header
+ cell = cell_renderer or gtk.CellRendererText()
+ if editfunc:
+ cell.set_property('editable', editable)
+ cell.connect('edited', editfunc)
+ col = gtk.TreeViewColumn(header, cell)
+ if cell_renderer is None:
+ col.add_attribute(cell, 'text', model_idx)
+ col.set_resizable(resizable)
+ col.set_visible(self.get_property(self.col_to_prop(col_idx)))
+ if right:
+ col.set_alignment(1)
+ cell.set_property('xalign', 1)
+ for (property_name, model_index) in properties:
+ col.add_attribute(cell, property_name, model_index)
+ self.list.append_column(col)
+ self.cols[col_idx] = col
+ self.cells[col_idx] = cell
+
+ def cell_edited(cell, path, newname):
+ row = self.model[path]
+ patchname = row[M_NAME]
+ if newname != patchname:
+ self.qrename(newname, patch=patchname)
+
+ #### patch list columns and cell renderers
+
+ addcol(_('Graph'), C_GRAPH, resizable=True,
+ cell_renderer=graphcell.CellRendererGraph(),
+ properties=[("node", M_NODE),
+ ("in-lines",M_IN_LINES),
+ ("out-lines", M_OUT_LINES)]
+ )
+ addcol(_('St'), C_STATUS, M_STATUS)
+ addcol(_('Name'), C_NAME, M_NAME, editfunc=cell_edited)
+ addcol(_('Title'), C_TITLE, M_TITLE)
+ addcol(_('Message'), C_MSG, M_MSG)
+
+ pane.add(self.list)
+
+ ## command widget
+ self.cmd = hgcmd.CmdWidget(style=hgcmd.STYLE_COMPACT,
+ tooltips=tooltips)
+ mainbox.pack_start(self.cmd, False, False)
+
+ # accelerator
+ if accelgroup:
+ # TODO
+ pass
+
+ ### public functions ###
+
+ def refresh(self):
+ """
+ Refresh the list of patches.
+ This operation will try to keep selection state.
+ """
+ if not self.pbranch:
+ return
+
+ # store selected patch name
+ selname = None
+ model, paths = self.list.get_selection().get_selected_rows()
+ if len(paths) > 0:
+ selname = model[paths[0]][M_NAME]
+
+ # compute model data
+ self.model.clear()
+ opts = {'tips': True}
+ mgr = self.pbranch.patchmanager(self.repo.ui, self.repo, opts)
+ graph = mgr.graphforopts(opts)
+ if not self.get_property('show-internal-branches'):
+ graph = mgr.patchonlygraph(graph)
+ names = None
+ patch_list = graph.topolist(names)
+ in_lines = []
+ if patch_list:
+ dep_list = [patch_list[0]]
+ cur_branch = self.repo['.'].branch()
+ patch_status = {}
+ for name in patch_list:
+ patch_status[name] = self.pstatus(name)
+ for name in patch_list:
+ parents = graph.deps(name)
+
+ # Node properties
+ if name in dep_list:
+ node_column = dep_list.index(name)
+ else:
+ node_column = len(dep_list)
+ node_colour = patch_status[name] and '#ff0000' or 0
+ node_status = (name == cur_branch) and 4 or 0
+ node = (node_column, node_colour, node_status)
+
+ # Find next dependency list
+ my_deps = []
+ for p in parents:
+ if p not in dep_list:
+ my_deps.append(p)
+ next_dep_list = dep_list[:]
+ next_dep_list[node_column:node_column+1] = my_deps
+
+ # Dependency lines
+ shift = len(parents) - 1
+ out_lines = []
+ for p in parents:
+ dep_column = next_dep_list.index(p)
+ colour = 0 # black
+ if patch_status[p]:
+ colour = '#ff0000' # red
+ style = 0 # solid lines
+ out_lines.append((node_column, dep_column, colour, style))
+ for lines in in_lines:
+ (start_column, end_column, colour, style) = lines
+ if end_column == node_column:
+ # Deps to current patch end here
+ pass
+ else:
+ # Find line continuations
+ dep = dep_list[end_column]
+ dep_column = next_dep_list.index(dep)
+ out_lines.append((end_column, dep_column, colour, style))
+
+ stat = patch_status[name] and 'M' or 'C' # patch status
+ patchname = name
+ msg = self.pmessage(name) # summary
+ if msg:
+ msg_esc = gtklib.markup_escape_text(msg) # escaped summary (utf-8)
+ title = msg.split('\n')[0]
+ else:
+ msg_esc = None
+ title = None
+ self.model.append((node, in_lines, out_lines, patchname, stat,
+ title, msg, msg_esc))
+ # Loop
+ in_lines = out_lines
+ dep_list = next_dep_list
+
+
+ # restore patch selection
+ if selname:
+ iter = self.get_iter_by_patchname(selname)
+ if iter:
+ self.list.get_selection().select_iter(iter)
+
+ # update UI sensitives
+ self.update_sensitives()
+
+ # report status
+ status_text = ''
+ idle_text = None
+ if self.has_patch():
+ status_text = self.pending_merges() \
+ and _('pending pmerges') \
+ or _('no pending pmerges')
+ self.statusbar.set_text(status_text, 'pbranch')
+ self.statusbar.set_idle_text(idle_text)
+
+ def pgraph(self):
+ """
+ [pbranch] Execute 'pgraph' command.
+
+ :returns: A list of patches and dependencies
+ """
+ if self.pbranch is None:
+ return None
+ opts = {}
+ mgr = self.pbranch.patchmanager(self.repo.ui, self.repo, opts)
+ return mgr.graphforopts(opts)
+
+ def patch_list(self, opts={}):
+ """List all patches in pbranch dependency DAG"""
+ mgr = self.pbranch.patchmanager(self.repo.ui, self.repo, opts)
+ graph = mgr.graphforopts(opts)
+ names = None
+ return graph.topolist(names)
+
+ def pending_merges(self):
+ """Return True if there are pending pmerge operations"""
+ for patch in self.patch_list():
+ if self.pstatus(patch):
+ return True
+ return False
+
+ def pstatus(self, patch_name):
+ """
+ [pbranch] Execute 'pstatus' command.
+
+ :param patch_name: Name of patch-branch
+ :retv: list of status messages. If empty there is no pending merges
+ """
+ if self.pbranch is None:
+ return None
+ status = []
+ opts = {}
+ mgr = self.pbranch.patchmanager(self.repo.ui, self.repo, opts)
+ graph = mgr.graphforopts(opts)
+ heads = self.repo.branchheads(patch_name)
+ if len(heads) > 1:
+ status.append(_('needs merge of %i heads\n') % len(heads))
+ for dep, through in graph.pendingmerges(patch_name):
+ if through:
+ status.append(_('needs merge with %s (through %s)\n') %
+ (dep, ", ".join(through)))
+ else:
+ status.append(_('needs merge with %s\n') % dep)
+ for dep in graph.pendingrebases(patch_name):
+ status.append(_('needs update of diff base to tip of %s\n') % dep)
+ return status
+
+ def pmessage(self, patch_name):
+ """
+ Get patch message
+
+ :param patch_name: Name of patch-branch
+ :retv: Full patch message. If you extract the first line
+ you will get the patch title. If the repo does not contain
+ message or patch, the function returns None
+ """
+ opts = {}
+ mgr = self.pbranch.patchmanager(self.repo.ui, self.repo, opts)
+ try:
+ return mgr.patchdesc(patch_name)
+ except:
+ return None
+
+ def peditmessage(self, patch_name):
+ """
+ Edit patch message
+
+ :param patch_name: Name of patch-branch
+ """
+ if not patch_name in self.patch_list():
+ return
+ cmdline = ['hg', 'peditmessage', patch_name]
+ self.cmd.execute(cmdline, self.cmd_done)
+
+ def pdiff(self, patch_name):
+ """
+ [pbranch] Execute 'pdiff --tips' command.
+
+ :param patch_name: Name of patch-branch
+ :retv: list of lines of generated patch
+ """
+ opts = {}
+ mgr = self.pbranch.patchmanager(self.repo.ui, self.repo, opts)
+ graph = mgr.graphattips()
+ return graph.diff(patch_name, None, opts)
+
+ def pnew_ui(self):
+ """
+ Create new patch.
+ Propmt user for new patch name. Patch is created
+ on current branch.
+ """
+ parent = None
+ title = _('New Patch Name')
+ new_name = dialog.entry_dialog(parent, title)
+ if not new_name:
+ return False
+ self.pnew(new_name)
+ return True
+
+ def pnew(self, patch_name):
+ """
+ [pbranch] Execute 'pnew' command.
+
+ :param patch_name: Name of new patch-branch
+ """
+ if self.pbranch is None:
+ return False
+ self.pbranch.cmdnew(self.repo.ui, self.repo, patch_name)
+ self.emit('repo-invalidated')
+ return True
+
+ def pmerge(self, patch_name=None):
+ """
+ [pbranch] Execute 'pmerge' command.
+
+ :param patch_name: Merge to this patch-branch
+ """
+ if not self.has_patch():
+ return
+ cmdline = ['hg', 'pmerge']
+ if patch_name:
+ cmdline += [patch_name]
+ else:
+ cmdline += ['--all']
+ self.cmd.execute(cmdline, self.cmd_done)
+
+ def pbackout(self):
+ """
+ [pbranch] Execute 'pbackout' command.
+ """
+ assert False
+
+ def pfinish(self, patch_name):
+ """
+ [pbranch] Execute 'pfinish' command.
+
+ The workdir must be clean.
+ The patch branch dependencies must be merged.
+
+ :param patch_name: A patch branch (not an internal branch)
+ """
+ # Check preconditions for pfinish
+
+ assert self.is_patch(patch_name)
+
+ pmerge_status = self.pstatus(patch_name)
+ if pmerge_status != []:
+ dialog.error_dialog(self.parent_window,
+ _('Pending Pmerge'),
+ _('You cannot finish this patch branch unless you pmerge it first.\n'
+ 'pmerge will solve the following issues with %(patch)s:\n'
+ '* %(issuelist)s') %
+ {'patch': patch_name,
+ 'issuelist': '* '.join(pmerge_status)}
+ )
+ return
+
+ if not self.workdir_is_clean():
+ dialog.error_dialog(self.parent_window,
+ _('Uncommitted Local Changes'),
+ _('pfinish uses your working directory for temporary work.\n'
+ 'Please commit your local changes before issuing pfinish.')
+ )
+ return
+
+ if hasattr(self.repo, 'mq') and len(self.repo.mq.applied) > 0:
+ dialog.error_dialog(self.parent_window,
+ _('Applied MQ patch'),
+ _('pfinish must be able to commit, but this is not allowed\n'
+ 'as long as you have MQ patches applied.')
+ )
+ return
+
+ # Set up environment for mercurial commands
+ class CmdWidgetUi(mercurial.ui.ui):
+ def __init__(self, cmdLogWidget):
+ src = None
+ super(CmdWidgetUi, self).__init__(src)
+ self.cmdLogWidget = cmdLogWidget
+ def write(self, *args):
+ for a in args:
+ self.cmdLogWidget.append(str(a))
+ def write_err(self, *args):
+ for a in args:
+ self.cmdLogWidget.append(str(a), error=True)
+ def flush(self):
+ pass
+ def prompt(self, msg, choices=None, default="y"):
+ raise util.Abort("Internal Error: prompt not available")
+ def promptchoice(self, msg, choices, default=0):
+ raise util.Abort("Internal Error: promptchoice not available")
+ def getpass(self, prompt=None, default=None):
+ raise util.Abort("Internal Error: getpass not available")
+ repo = self.repo
+ ui = CmdWidgetUi(self.cmd.log)
+ old_ui = repo.ui
+ repo.ui = ui
+
+ # Commit patch to dependency
+ fd, patch_file_name = tempfile.mkstemp(prefix='thg-patch-')
+ patch_file = os.fdopen(fd, 'w')
+ patch_file.writelines(self.pdiff(patch_name))
+ patch_file.close()
+ upstream_branch = self.pgraph().deps(patch_name)[0]
+ hg.update(ui, repo, rev=upstream_branch)
+ hg.import_(ui, repo, patch_file_name, base='', strip=1)
+ os.unlink(patch_file_name)
+
+ # Close patch branch
+ hg.update(ui, repo, rev=patch_name)
+ hg.merge(ui, repo, upstream_branch)
+ msg = _('Patch branch finished')
+ hg.commit(ui, repo, close_branch=True, message=msg)
+
+ # Update GUI
+ repo.ui = old_ui
+ self.emit('repo-invalidated')
+
+ def has_pbranch(self):
+ """ return True if pbranch extension can be used """
+ return self.pbranch is not None
+
+ def has_patch(self):
+ """ return True if pbranch extension is in use on repo """
+ return self.has_pbranch() and self.pgraph() != []
+
+ def is_patch(self, branch_name):
+ """ return True if branch is a patch. This excludes root branches
+ and internal diff base branches (for patches with multiple
+ dependencies. """
+ return self.has_pbranch() and self.pgraph().ispatch(branch_name)
+
+ def cur_branch(self):
+ """ Return branch that workdir belongs to. """
+ return self.repo.dirstate.branch()
+
+ def workdir_is_clean(self):
+ """ return True if the working directory is clean """
+ c = self.repo[None]
+ return not (c.modified() or c.added() or c.removed())
+
+ ### internal functions ###
+
+ def get_iter_by_patchname(self, name):
+ """ return iter has specified patch name """
+ if name:
+ for row in self.model:
+ if row[M_NAME] == name:
+ return row.iter
+ return None
+
+ def get_path_by_patchname(self, name):
+ """ return path has specified patch name """
+ iter = self.get_iter_by_patchname(name)
+ if iter:
+ return self.model.get_path(iter)
+ return None
+
+ def update_sensitives(self):
+ """ Update the sensitives of entire UI """
+ def disable_pbranchcmd():
+ for name in ('pbackout', 'pmerge', 'pnew', 'reapply'):
+ self.btn[name].set_sensitive(False)
+ if self.pbranch:
+ self.list.set_sensitive(True)
+ self.btn['menu'].set_sensitive(True)
+ in_pbranch = True #TODO
+ is_merge = len(self.repo.parents()) > 1
+ self.btn['pmerge'].set_sensitive(in_pbranch)
+ self.btn['pbackout'].set_sensitive(in_pbranch)
+ self.btn['pnew'].set_sensitive(not is_merge)
+ self.btn['reapply'].set_sensitive(True)
+ else:
+ self.list.set_sensitive(False)
+ self.btn['menu'].set_sensitive(False)
+ disable_pbranchcmd()
+
+ def scroll_to_current(self):
+ """
+ Scroll to current patch in the patch list.
+ If the patch is selected, it will do nothing.
+ """
+ if self.list.get_selection().count_selected_rows() > 0:
+ return
+ curpatch = self.cur_branch()
+ if not curpatch:
+ return
+ path = self.get_path_by_patchname(curpatch)
+ if path:
+ self.list.scroll_to_cell(path)
+
+ def show_patch_cmenu(self, list, path):
+ """Context menu for selected patch"""
+ row = self.model[path]
+
+ menu = gtk.Menu()
+ def append(label, handler=None):
+ item = gtk.MenuItem(label, True)
+ item.set_border_width(1)
+ if handler:
+ item.connect('activate', handler, row)
+ menu.append(item)
+
+ has_pbranch = self.has_pbranch()
+ is_current = self.has_patch() and self.cur_branch() == row[M_NAME]
+ is_patch = self.is_patch(row[M_NAME])
+ is_internal = self.pbranch.isinternal(row[M_NAME])
+ is_merge = len(self.repo.branchheads(row[M_NAME])) > 1
+
+ if has_pbranch and not is_merge and not is_internal:
+ append(_('_new'), self.pnew_activated)
+ if not is_current:
+ append(_('_goto (update workdir)'), self.goto_activated)
+ if is_patch:
+ append(_('_edit message'), self.edit_message_activated)
+ append(_('_rename'), self.rename_activated)
+ append(_('_delete'), self.delete_activated)
+ append(_('_finish'), self.finish_activated)
+
+ if len(menu.get_children()) > 0:
+ menu.show_all()
+ menu.popup(None, None, None, 0, 0)
+
+ def create_view_menu(self):
+ """Top right menu for selection of columns and
+ view configuration."""
+ menu = gtk.Menu()
+ def append(item=None, handler=None, check=False,
+ active=False, sep=False):
+ if sep:
+ item = gtk.SeparatorMenuItem()
+ else:
+ if isinstance(item, str):
+ if check:
+ item = gtk.CheckMenuItem(item)
+ item.set_active(active)
+ else:
+ item = gtk.MenuItem(item)
+ item.set_border_width(1)
+ if handler:
+ item.connect('activate', handler)
+ menu.append(item)
+ return item
+ def colappend(label, col_idx, active=True):
+ def handler(menuitem):
+ col = self.cols[col_idx]
+ col.set_visible(menuitem.get_active())
+ propname = self.col_to_prop(col_idx)
+ item = append(label, handler, check=True, active=active)
+ self.vmenu[propname] = item
+
+ self.vmenu = {}
+
+ colappend(_('Show graph'), C_GRAPH)
+ colappend(_('Show status'), C_STATUS, active=False)
+ colappend(_('Show name'), C_NAME)
+ colappend(_('Show title'), C_TITLE, active=False)
+ colappend(_('Show message'), C_MSG, active=False)
+
+ append(sep=True)
+
+ def enable_editable(item):
+ self.cells[C_NAME].set_property('editable', item.get_active())
+ item = append(_('Enable editable cells'), enable_editable,
+ check=True, active=False)
+ self.vmenu['editable-cell'] = item
+ item = append(_("Show internal branches"), lambda item: self.refresh(),
+ check=True, active=False)
+ self.vmenu['show-internal-branches'] = item
+
+ menu.show_all()
+ return menu
+
+ def show_dialog(self, dlg):
+ """Show modal dialog and block application
+ See also show_dialog in history.py
+ """
+ dlg.set_transient_for(self.parent_window)
+ dlg.show_all()
+ dlg.run()
+ if gtk.pygtk_version < (2, 12, 0):
+ # Workaround for old PyGTK (< 2.12.0) issue.
+ # See background of this: f668034aeda3
+ dlg.set_transient_for(None)
+
+
+ def update_by_row(self, row):
+ branch = row[M_NAME]
+ rev = cmdutil.revrange(self.repo, [branch])
+ parents = [x.node() for x in self.repo.parents()]
+ dialog = update.UpdateDialog(rev[0])
+ self.show_dialog(dialog)
+ self.update_completed(parents)
+
+ def update_completed(self, oldparents):
+ self.repo.invalidate()
+ self.repo.dirstate.invalidate()
+ newparents = [x.node() for x in self.repo.parents()]
+ if not oldparents == newparents:
+ self.emit('repo-invalidated')
+
+ def cmd_done(self, returncode, useraborted, noemit=False):
+ if returncode == 0:
+ if self.cmd.get_pbar():
+ self.cmd.set_result(_('Succeed'), style='ok')
+ elif useraborted:
+ self.cmd.set_result(_('Canceled'), style='error')
+ else:
+ self.cmd.set_result(_('Failed'), style='error')
+ self.refresh()
+ if not noemit:
+ self.emit('repo-invalidated')
+
+ def do_get_property(self, property):
+ try:
+ return self.vmenu[property.name].get_active()
+ except:
+ raise AttributeError, 'unknown property %s' % property.name
+
+ def do_set_property(self, property, value):
+ try:
+ self.vmenu[property.name].set_active(value)
+ except:
+ raise AttributeError, 'unknown property %s' % property.name
+
+ def col_to_prop(self, col_idx):
+ if col_idx == C_GRAPH:
+ return 'graph-column-visible'
+ if col_idx == C_STATUS:
+ return 'status-column-visible'
+ if col_idx == C_NAME:
+ return 'name-column-visible'
+ if col_idx == C_TITLE:
+ return 'title-column-visible'
+ if col_idx == C_MSG:
+ return 'message-column-visible'
+ return ''
+
+ ### signal handlers ###
+
+ def list_pressed(self, list, event):
+ x, y = int(event.x), int(event.y)
+ pathinfo = list.get_path_at_pos(x, y)
+ if event.button == 1:
+ if not pathinfo:
+ # HACK: clear selection after this function calling,
+ # against selection by getting focus
+ def unselect():
+ selection = list.get_selection()
+ selection.unselect_all()
+ gtklib.idle_add_single_call(unselect)
+ elif event.button == 3:
+ if pathinfo:
+ self.show_patch_cmenu(self.list, pathinfo[0])
+
+ def list_sel_changed(self, list):
+ path, focus = list.get_cursor()
+ row = self.model[path]
+ patchname = row[M_NAME]
+ try:
+ ctx = self.repo[patchname]
+ revid = ctx.rev()
+ except hglib.RepoError:
+ revid = -1
+ self.emit('patch-selected', revid, patchname)
+
+ def list_row_activated(self, list, path, column):
+ self.update_by_row(self.model[path])
+
+ def list_size_allocated(self, list, req):
+ if self.has_patch():
+ self.scroll_to_current()
+
+ def pbackout_clicked(self, toolbutton):
+ pass
+
+ def pmerge_clicked(self, toolbutton):
+ self.pmerge()
+
+ def pnew_clicked(self, toolbutton):
+ self.pnew_ui()
+
+ def reapply_clicked(self, toolbutton):
+ pass
+
+ def edit_pgraph_clicked(self, toolbutton):
+ opts = {} # TODO: How to find user ID
+ mgr = self.pbranch.patchmanager(self.repo.ui, self.repo, opts)
+ oldtext = mgr.graphdesc()
+ # run editor in the repository root
+ olddir = os.getcwd()
+ os.chdir(self.repo.root)
+ newtext = self.repo.ui.edit(oldtext, opts.get('user'))
+ os.chdir(olddir)
+ mgr.updategraphdesc(newtext)
+
+ ### context menu signal handlers ###
+
+ def pnew_activated(self, menuitem, row):
+ """Insert new patch after this row"""
+ if self.cur_branch() == row[M_NAME]:
+ self.pnew_ui()
+ return
+ # pnew from patch different than current
+ assert False
+ if self.wdir_modified():
+ # Ask user if current changes should be discarded
+ # Abort if user does not agree
+ pass
+ # remember prev branch
+ # Update to row[M_NAME]
+ # pnew_ui
+ # if aborted, update back to prev branch
+ pass
+
+ def edit_message_activated(self, menuitem, row):
+ self.peditmessage(row[M_NAME])
+
+ def goto_activated(self, menuitem, row):
+ self.update_by_row(row)
+
+ def delete_activated(self, menuitem, row):
+ assert False
+
+ def rename_activated(self, menuitem, row):
+ assert False
+
+ def finish_activated(self, menuitem, row):
+ self.pfinish(row[M_NAME])
|
Loading...