Mercurial and Git clients can push and pull from this alias URL to interact with this repository. You can change to which repository an alias points by going to the Aliases link on the project page.
# status.py - working copy browser## Copyright 2010 Steve Borho <steve@borho.org>## 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
-from mercurial import ui, hg, util, patch, cmdutil, error, mdiff, context
+from mercurial import ui, hg, util, patch, cmdutil, error, mdiff, context, mergefrom tortoisehg.hgqt import qtlib, htmlui, chunkselect
from tortoisehg.util import paths, hglib
from tortoisehg.util.i18n import _
fromPyQt4.QtCoreimportQt,QVariant,SIGNAL,QAbstractTableModelfromPyQt4.QtCoreimportQObject,QEvent,QMimeData,QUrlfromPyQt4.QtGuiimportQWidget,QVBoxLayout,QSplitter,QTreeViewfromPyQt4.QtGuiimportQTextEdit,QFont,QColor,QDrag# This widget can be used as the basis of the commit tool or any other# working copy browser.
# Technical Debt
-# Show merge status column, when appropriate# Thread refreshWctx, connect to an external progress bar
# Thread rowSelected, connect to an external progress bar
# Need mechanisms to clear pats and toggle visibility options
# Need mechanism to override file size/binary check# Show subrepos better# Save splitter position to parent's QSetting# Context menu, toolbar# Sorting, filtering of working files# Chunk selection# tri-state checkboxes for commit# Investigate Qt.DecorationRole and possible use of overlay icons# Investigate folding/nesting of filesclassStatusWidget(QWidget):def__init__(self,pats,opts,parent=None):QWidget.__init__(self,parent)root=paths.find_root()assert(root)self.repo=hg.repository(ui.ui(),path=root)self.wctx=self.repo[None] self.opts = dict(unknown=True, clean=False, ignored=False)
self.opts.update(opts)
self.pats = pats
+ self.ms = {} # determine the user configured status colors
# (in the future, we could support full rich-text tags)
qtlib.configstyles(self.repo.ui)forstatincolor_labels.keys():effect=qtlib.geteffect(color_labels[stat])foreineffect.split(';'):ife.startswith('color:'):colors[stat]=QColor(e[7:])breaksplit=QSplitter(Qt.Horizontal)layout=QVBoxLayout()layout.addWidget(split)self.setLayout(layout)self.tv=WctxFileTree(root,split)self.connect(self.tv,SIGNAL('clicked(QModelIndex)'),self.rowSelected)self.te=QTextEdit(split)self.te.document().setDefaultStyleSheet(qtlib.thgstylesheet)self.te.setReadOnly(True)self.te.setLineWrapMode(QTextEdit.NoWrap)# it is not clear why I had to set this QFont to get monospacef=QFont("Monospace")f.setStyleHint(QFont.TypeWriter)f.setPointSize(9)self.te.setFont(f)ifnotparent:self.setWindowTitle(_('TortoiseHg Status'))self.resize(650,400)# 60% for diff panesplit.setStretchFactor(0,2)split.setStretchFactor(1,5)self.refreshWctx()self.updateModel()defkeyPressEvent(self,event):ifevent.key()==Qt.Key_F5:self.te.clear()self.refreshWctx()self.updateModel()else:returnsuper(StatusWidget,self).keyPressEvent(event) def refreshWctx(self):
hglib.invalidaterepo(self.repo)
+ self.ms = merge.mergestate(self.repo) extract = lambda x, y: dict(zip(x, map(y.get, x)))
stopts = extract(('unknown', 'ignored', 'clean'), self.opts)
if self.pats:
m=cmdutil.match(self.repo,self.pats)status=self.repo.status(match=m,**stopts)self.wctx=context.workingctx(self.repo,changes=status)returnwctx=self.repo[None]try:wctx.status(**stopts)exceptAttributeError:# your mercurial source is not new enough, falling back# to triggering implicit status() call.wctx.modified()except(OSError,IOError,util.Abort),e:self.status_error=str(e)self.wctx=wctxdefisMerge(self): return bool(self.wctx.p2())
def updateModel(self):
- tm = WctxModel(self.wctx, self.opts)
+ tm = WctxModel(self.wctx, self.ms, self.opts)
self.tv.setModel(tm)
self.tv.setItemsExpandable(False)
self.tv.setRootIsDecorated(False)
self.tv.setSortingEnabled(True)
self.tv.sortByColumn(COL_PATH_DISPLAY)
- self.tv.resizeColumnToContents(COL_CHECK)
- self.tv.resizeColumnToContents(COL_STATUS)
- self.tv.resizeColumnToContents(COL_PATH_DISPLAY)+ for col in xrange(COL_PATH):+ self.tv.resizeColumnToContents(col)
+ self.tv.setColumnHidden(COL_MERGE_STATE, not tm.anyMerge())
self.connect(self.tv, SIGNAL('activated(QModelIndex)'), tm.toggleRow)
self.connect(self.tv, SIGNAL('pressed(QModelIndex)'), tm.pressedRow)
defrowSelected(self,index):'Connected to treeview "clicked" signal'pfile=index.model().getPath(index)wfile=util.pconvert(pfile)warnings=chunkselect.check_max_diff(self.wctx,wfile)ifwarnings:text='<b>Diffs not displayed: %s</b>'%warnings[1]self.te.setHtml(text)returnifself.isMerge():header=_('===== Diff to first parent %d:%s =====\n')%(self.wctx.p1().rev(),str(self.wctx.p1()))header='<h3>'+header+'</h3></br>'else:header=''hu=htmlui.htmlui()m=cmdutil.matchfiles(self.repo,[wfile])try:try:fors,linpatch.difflabel(self.wctx.diff,match=m):hu.write(s,label=l)exceptAttributeError:# your mercurial source is not new enough, falling back# to manual patch.diff() callopts=mdiff.diffopts(git=True,nodates=True)n2,n1=None,self.wctx.p1().node()fors,linpatch.difflabel(patch.diff,self.repo,n1,n2,match=m,opts=opts):hu.write(s,label=l)except(IOError,error.RepoError,error.LookupError,util.Abort),e:self.status_error=str(e)returno,e=hu.getdata()diff=oor_('<em>No change</em>')ifself.isMerge():text=header+diffelse:self.te.setHtml(header+diff)returntry:fors,linpatch.difflabel(self.wctx.diff,self.wctx.p2(),match=m):hu.write(s,label=l)except(IOError,error.RepoError,error.LookupError,util.Abort),e:self.status_error=str(e)returntext+='</br><h3>'text+=_('===== Diff to second parent %d:%s =====\n')%(self.wctx.p2().rev(),str(self.wctx.p2()))text+='</h3></br>'o,e=hu.getdata()diff=oor_('<em>No change</em>')self.te.setHtml(text+diff)classWctxFileTree(QTreeView):def__init__(self,root,parent=None):QTreeView.__init__(self,parent)self.root=rootdefkeyPressEvent(self,event):ifevent.key()==32:forindexinself.selectedIndexes():self.model().toggleRow(index)else:returnsuper(WctxFileTree,self).keyPressEvent(event)defdragObject(self):rows=set()urls=[]forindexinself.selectedIndexes():ifindex.row()notinrows:rows.add(index.row())path=self.model().getPath(index)u=QUrl()u.setPath('file://'+os.path.join(self.root,path))urls.append(u)ifrows:d=QDrag(self)m=QMimeData()m.setUrls(urls)d.setMimeData(m)d.start(Qt.CopyAction)defmouseMoveEvent(self,event):self.dragObject()COL_CHECK = 0
COL_STATUS = 1
-COL_PATH_DISPLAY = 2
-COL_PATH = 3
+COL_MERGE_STATE = 2
+COL_PATH_DISPLAY = 3
+COL_PATH = 4tips = {
'M': _('%s is modified'),
'A':_('%s is added'),'R':_('%s is removed'),'?':_('%s is not tracked (unknown)'),'!':_('%s is missing!'),'I':_('%s is ignored'),'C':_('%s is not modified (clean)'),'S':_('%s is a dirty subrepo'),}color_labels={'M':'status.modified','A':'status.added','R':'status.removed','?':'status.unknown','!':'status.deleted','I':'status.ignored','C':'status.clean','S':'status.subrepo',}colors = {}
class WctxModel(QAbstractTableModel):
- def __init__(self, wctx, opts, parent=None):
+ def __init__(self, wctx, ms, opts, parent=None):
QAbstractTableModel.__init__(self, parent)
rows = []
for m in wctx.modified():
- rows.append([True, 'M', hglib.tounicode(m), m])
+ mst = m in ms and ms[m].upper() or ""+ rows.append([True, 'M', mst, hglib.tounicode(m), m])
for a in wctx.added():
- rows.append([True, 'A', hglib.tounicode(a), a])
+ mst = a in ms and ms[a].upper() or ""+ rows.append([True, 'A', mst, hglib.tounicode(a), a])
for r in wctx.removed():
- rows.append([True, 'R', hglib.tounicode(r), r])
+ mst = r in ms and ms[r].upper() or ""+ rows.append([True, 'R', mst, hglib.tounicode(r), r])
for d in wctx.deleted():
- rows.append([False, '!', hglib.tounicode(d), d])
+ mst = d in ms and ms[d].upper() or ""+ rows.append([False, '!', mst, hglib.tounicode(d), d])
if opts['unknown']:
for u in wctx.unknown():
- rows.append([False, '?', hglib.tounicode(u), u])
+ rows.append([False, '?', '', hglib.tounicode(u), u])
if opts['ignored']:
for i in wctx.ignored():
- rows.append([False, 'I', hglib.tounicode(i), i])
+ rows.append([False, 'I', '', hglib.tounicode(i), i])
if opts['clean']:
for c in wctx.clean():
- rows.append([False, 'C', hglib.tounicode(c), c])
+ rows.append([False, 'C', '', hglib.tounicode(c), c])
try:
for s in wctx.substate:
if wctx.sub(s).dirty():
- rows.append([False, 'S', hglib.tounicode(s), s])
+ rows.append([False, 'S', '', hglib.tounicode(s), s])
except (OSError, IOError, error.ConfigError), e:
self.status_error = str(e)
+ self.headers = ('*', _('Stat'), _('M'), _('Filename')) self.rows = rows
- self.headers = ('*', _('Stat'), _('Filename')) def rowCount(self, parent):
return len(self.rows)
defcolumnCount(self,parent):returnlen(self.headers)defdata(self,index,role):ifnotindex.isValid():returnQVariant()ifindex.column()==COL_CHECK:ifrole==Qt.CheckStateRole:# also Qt.PartiallyCheckedifself.rows[index.row()][COL_CHECK]:returnQt.Checkedelse:returnQt.Unchecked elif role == Qt.DisplayRole:
return QVariant(self.rows[index.row()][index.column()])
- checked, status, upath, path = self.rows[index.row()]
+ checked, status, mst, upath, path = self.rows[index.row()]
if role == Qt.TextColorRole:
return colors.get(status, QColor('black'))
elif role == Qt.ToolTipRole:
if status in tips:
- return QVariant(tips[status] % upath)
+ tip = tips[status] % upath+ if mst == 'R':+ tip += _(', resolved merge')+ elif mst == 'U':+ tip += _(', unresolved merge')+ return QVariant(tip)
return QVariant()
def headerData(self, col, orientation, role):
ifrole!=Qt.DisplayRoleororientation!=Qt.Horizontal:returnQVariant()else:returnQVariant(self.headers[col])defflags(self,index):flags=Qt.ItemIsSelectable|Qt.ItemIsEnabled|Qt.ItemIsDragEnabledifindex.column()==COL_CHECK:flags|=Qt.ItemIsUserCheckablereturnflags # Custom methods
+ def anyMerge(self):+ for r in self.rows:+ if r[COL_MERGE_STATE]:+ return True+ return False+ def getPath(self, index):
assert index.isValid()
return self.rows[index.row()][COL_PATH]
deftoggleRow(self,index):'Connected to "activated" signal, emitted by dbl-click or enter'assertindex.isValid()row=index.row()self.rows[row][COL_CHECK]=notself.rows[row][COL_CHECK]self.emit(SIGNAL("layoutChanged()"))defpressedRow(self,index):'Connected to "pressed" signal, emitted by mouse clicks'assertindex.isValid()ifindex.column()==COL_CHECK:self.toggleRow(index)defrun(ui,*pats,**opts):returnStatusWidget(pats,opts)
Attach a Trello Card
Add a tag
Your session has expired
You are no longer logged in. Please log in and try your request again.
Filter RSS Feed
This RSS feed URL allows you to see the contents of your current filter using any feed reader.
This link includes a special authentication token. If you share the URL with anyone else, they can see this RSS feed's activity. You can disable these tokens when needed.
Your current filter is unsaved; changing it won't affect this RSS feed.