PageRenderTime 116ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/tortoisehg/hgqt/fileview.py

https://bitbucket.org/tortoisehg/hgtk/
Python | 663 lines | 639 code | 6 blank | 18 comment | 0 complexity | c2ad430b1a844051014acc122a84e55c MD5 | raw file
Possible License(s): GPL-2.0
  1. # Copyright (c) 2009-2010 LOGILAB S.A. (Paris, FRANCE).
  2. # http://www.logilab.fr/ -- mailto:contact@logilab.fr
  3. #
  4. # This program is free software; you can redistribute it and/or modify it under
  5. # the terms of the GNU General Public License as published by the Free Software
  6. # Foundation; either version 2 of the License, or (at your option) any later
  7. # version.
  8. #
  9. # This program is distributed in the hope that it will be useful, but WITHOUT
  10. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  11. # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License along with
  14. # this program; if not, write to the Free Software Foundation, Inc.,
  15. # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  16. """
  17. Qt4 high level widgets for hg repo changelogs and filelogs
  18. """
  19. import os
  20. import difflib
  21. import re
  22. from mercurial import hg, error, match, patch, util
  23. from mercurial import ui as uimod, mdiff
  24. from PyQt4.QtCore import *
  25. from PyQt4.QtGui import *
  26. from PyQt4 import Qsci
  27. from tortoisehg.util import hglib, patchctx
  28. from tortoisehg.hgqt.i18n import _
  29. from tortoisehg.hgqt import annotate, qscilib, qtlib, blockmatcher, lexers
  30. from tortoisehg.hgqt import visdiff, wctxactions
  31. qsci = Qsci.QsciScintilla
  32. class HgFileView(QFrame):
  33. """file diff and content viewer"""
  34. linkActivated = pyqtSignal(QString)
  35. fileDisplayed = pyqtSignal(QString, QString)
  36. showMessage = pyqtSignal(QString)
  37. revisionSelected = pyqtSignal(int)
  38. searchRequested = pyqtSignal(unicode)
  39. """Emitted (pattern) when user request to search content"""
  40. grepRequested = pyqtSignal(unicode, dict)
  41. """Emitted (pattern, opts) when user request to search changelog"""
  42. def __init__(self, repo, parent):
  43. QFrame.__init__(self, parent)
  44. framelayout = QVBoxLayout(self)
  45. framelayout.setContentsMargins(0,0,0,0)
  46. framelayout.setSpacing(0)
  47. l = QHBoxLayout()
  48. l.setContentsMargins(0,0,0,0)
  49. l.setSpacing(0)
  50. self.topLayout = QVBoxLayout()
  51. self.labelhbox = hbox = QHBoxLayout()
  52. hbox.setContentsMargins(0,0,0,0)
  53. hbox.setSpacing(2)
  54. self.topLayout.addLayout(hbox)
  55. self.diffToolbar = QToolBar(_('Diff Toolbar'))
  56. self.diffToolbar.setIconSize(QSize(16,16))
  57. hbox.addWidget(self.diffToolbar)
  58. self.filenamelabel = w = QLabel()
  59. w.setWordWrap(True)
  60. f = w.textInteractionFlags()
  61. w.setTextInteractionFlags(f | Qt.TextSelectableByMouse)
  62. w.linkActivated.connect(self.linkActivated)
  63. hbox.addWidget(w, 1)
  64. self.extralabel = w = QLabel()
  65. w.setWordWrap(True)
  66. w.linkActivated.connect(self.linkActivated)
  67. self.topLayout.addWidget(w)
  68. w.hide()
  69. framelayout.addLayout(self.topLayout)
  70. framelayout.addLayout(l, 1)
  71. hbox = QHBoxLayout()
  72. hbox.setContentsMargins(0, 0, 0, 0)
  73. hbox.setSpacing(0)
  74. l.addLayout(hbox)
  75. self.blk = blockmatcher.BlockList(self)
  76. self.sci = annotate.AnnotateView(repo, self)
  77. hbox.addWidget(self.blk)
  78. hbox.addWidget(self.sci, 1)
  79. for name in ('searchRequested', 'editSelected', 'grepRequested'):
  80. getattr(self.sci, name).connect(getattr(self, name))
  81. self.sci.revisionHint.connect(self.showMessage)
  82. self.sci.sourceChanged.connect(self.sourceChanged)
  83. self.sci.setAnnotationEnabled(False)
  84. self.blk.linkScrollBar(self.sci.verticalScrollBar())
  85. self.blk.setVisible(False)
  86. self.sci.setFrameStyle(0)
  87. self.sci.setReadOnly(True)
  88. self.sci.setUtf8(True)
  89. self.sci.installEventFilter(qscilib.KeyPressInterceptor(self))
  90. self.sci.setContextMenuPolicy(Qt.CustomContextMenu)
  91. self.sci.customContextMenuRequested.connect(self.menuRequested)
  92. self.sci.setCaretLineVisible(False)
  93. # define markers for colorize zones of diff
  94. self.markerplus = self.sci.markerDefine(qsci.Background)
  95. self.markerminus = self.sci.markerDefine(qsci.Background)
  96. self.markertriangle = self.sci.markerDefine(qsci.Background)
  97. self.sci.setMarkerBackgroundColor(QColor('#B0FFA0'), self.markerplus)
  98. self.sci.setMarkerBackgroundColor(QColor('#A0A0FF'), self.markerminus)
  99. self.sci.setMarkerBackgroundColor(QColor('#FFA0A0'), self.markertriangle)
  100. # hide margin 0 (markers)
  101. self.sci.setMarginType(0, qsci.SymbolMargin)
  102. self.sci.setMarginWidth(0, 0)
  103. self.searchbar = qscilib.SearchToolBar(hidable=True)
  104. self.searchbar.hide()
  105. self.searchbar.searchRequested.connect(self.find)
  106. self.searchbar.conditionChanged.connect(self.highlightText)
  107. self.layout().addWidget(self.searchbar)
  108. self._ctx = None
  109. self._filename = None
  110. self._status = None
  111. self._mode = None
  112. self._lostMode = None
  113. self._lastSearch = u'', False
  114. self.actionDiffMode = QAction('Diff', self)
  115. self.actionDiffMode.setCheckable(True)
  116. self.actionFileMode = QAction('File', self)
  117. self.actionFileMode.setCheckable(True)
  118. self.actionAnnMode = QAction('Ann', self)
  119. self.actionAnnMode.setCheckable(True)
  120. self.modeToggleGroup = QActionGroup(self)
  121. self.modeToggleGroup.addAction(self.actionDiffMode)
  122. self.modeToggleGroup.addAction(self.actionFileMode)
  123. self.modeToggleGroup.addAction(self.actionAnnMode)
  124. self.modeToggleGroup.triggered.connect(self.setMode)
  125. # Next/Prev diff (in full file mode)
  126. self.actionNextDiff = QAction(qtlib.geticon('down'),
  127. 'Next diff (alt+down)', self)
  128. self.actionNextDiff.setShortcut('Alt+Down')
  129. self.actionNextDiff.triggered.connect(self.nextDiff)
  130. self.actionPrevDiff = QAction(qtlib.geticon('up'),
  131. 'Previous diff (alt+up)', self)
  132. self.actionPrevDiff.setShortcut('Alt+Up')
  133. self.actionPrevDiff.triggered.connect(self.prevDiff)
  134. self.forceMode('diff')
  135. self.actionFind = self.searchbar.toggleViewAction()
  136. self.actionFind.setIcon(qtlib.geticon('edit-find'))
  137. self.actionFind.setShortcut(QKeySequence.Find)
  138. tb = self.diffToolbar
  139. tb.addAction(self.actionDiffMode)
  140. tb.addAction(self.actionFileMode)
  141. tb.addAction(self.actionAnnMode)
  142. tb.addSeparator()
  143. tb.addAction(self.actionNextDiff)
  144. tb.addAction(self.actionPrevDiff)
  145. tb.addSeparator()
  146. tb.addAction(self.actionFind)
  147. self.actionNextLine = QAction('Next line', self)
  148. self.actionNextLine.setShortcut(Qt.SHIFT + Qt.Key_Down)
  149. self.actionNextLine.triggered.connect(self.nextLine)
  150. self.addAction(self.actionNextLine)
  151. self.actionPrevLine = QAction('Prev line', self)
  152. self.actionPrevLine.setShortcut(Qt.SHIFT + Qt.Key_Up)
  153. self.actionPrevLine.triggered.connect(self.prevLine)
  154. self.addAction(self.actionPrevLine)
  155. self.actionNextCol = QAction('Next column', self)
  156. self.actionNextCol.setShortcut(Qt.SHIFT + Qt.Key_Right)
  157. self.actionNextCol.triggered.connect(self.nextCol)
  158. self.addAction(self.actionNextCol)
  159. self.actionPrevCol = QAction('Prev column', self)
  160. self.actionPrevCol.setShortcut(Qt.SHIFT + Qt.Key_Left)
  161. self.actionPrevCol.triggered.connect(self.prevCol)
  162. self.addAction(self.actionPrevCol)
  163. self.timer = QTimer()
  164. self.timer.setSingleShot(False)
  165. self.timer.timeout.connect(self.idle_fill_files)
  166. def menuRequested(self, point):
  167. point = self.sci.mapToGlobal(point)
  168. return self.sci.createStandardContextMenu().exec_(point)
  169. def loadSettings(self, qs, prefix):
  170. self.sci.loadSettings(qs, prefix)
  171. def saveSettings(self, qs, prefix):
  172. self.sci.saveSettings(qs, prefix)
  173. @pyqtSlot(QAction)
  174. def setMode(self, action):
  175. 'One of the mode toolbar buttons has been toggled'
  176. mode = {'Diff':'diff', 'File':'file', 'Ann':'ann'}[str(action.text())]
  177. self.actionNextDiff.setEnabled(mode == 'file')
  178. self.actionPrevDiff.setEnabled(False)
  179. self.blk.setVisible(mode == 'file')
  180. self.sci.setAnnotationEnabled(mode == 'ann')
  181. if mode != self._mode:
  182. self._mode = mode
  183. if not self._lostMode:
  184. self.displayFile()
  185. def forceMode(self, mode):
  186. 'Force into file or diff mode, based on content constaints'
  187. assert mode in ('diff', 'file')
  188. if self._lostMode is None:
  189. self._lostMode = self._mode
  190. self._mode = mode
  191. if mode == 'diff':
  192. self.actionDiffMode.setChecked(True)
  193. else:
  194. self.actionFileMode.setChecked(True)
  195. self.actionDiffMode.setEnabled(False)
  196. self.actionFileMode.setEnabled(False)
  197. self.actionAnnMode.setEnabled(False)
  198. self.actionNextDiff.setEnabled(False)
  199. self.actionPrevDiff.setEnabled(False)
  200. self.blk.setVisible(mode == 'file')
  201. self.sci.setAnnotationEnabled(False)
  202. def setContext(self, ctx):
  203. self._ctx = ctx
  204. self._p_rev = None
  205. self.sci.setTabWidth(ctx._repo.tabwidth)
  206. self.actionAnnMode.setVisible(ctx.rev() != None)
  207. def displayDiff(self, rev):
  208. if rev != self._p_rev:
  209. self.displayFile(rev=rev)
  210. def clearDisplay(self):
  211. self.sci.clear()
  212. self.blk.clear()
  213. # Setting the label to ' ' rather than clear() keeps the label
  214. # from disappearing during refresh, and tool layouts bouncing
  215. self.filenamelabel.setText(' ')
  216. self.extralabel.hide()
  217. self._diffs = []
  218. def displayFile(self, filename=None, rev=None, status=None):
  219. if filename is None:
  220. filename, status = self._filename, self._status
  221. else:
  222. self._filename, self._status = filename, status
  223. if rev is not None:
  224. self._p_rev = rev
  225. self.clearDisplay()
  226. if filename is None:
  227. self.forceMode('file')
  228. return
  229. ctx = self._ctx
  230. repo = ctx._repo
  231. if self._p_rev is not None:
  232. ctx2 = repo[self._p_rev]
  233. else:
  234. ctx2 = None
  235. fd = FileData(ctx, ctx2, filename, status)
  236. if fd.elabel:
  237. self.extralabel.setText(fd.elabel)
  238. self.extralabel.show()
  239. else:
  240. self.extralabel.hide()
  241. self.filenamelabel.setText(fd.flabel)
  242. if not fd.isValid():
  243. self.sci.setText(fd.error)
  244. self.forceMode('file')
  245. return
  246. if fd.diff and not fd.contents:
  247. self.forceMode('diff')
  248. elif fd.contents and not fd.diff:
  249. self.forceMode('file')
  250. elif not fd.contents and not fd.diff:
  251. self.forceMode('file')
  252. else:
  253. self.actionDiffMode.setEnabled(True)
  254. self.actionFileMode.setEnabled(True)
  255. self.actionAnnMode.setEnabled(True)
  256. if self._lostMode:
  257. if self._lostMode == 'diff':
  258. self.actionDiffMode.trigger()
  259. elif self._lostMode == 'file':
  260. self.actionFileMode.trigger()
  261. elif self._lostMode == 'ann':
  262. self.actionAnnMode.trigger()
  263. self._lostMode = None
  264. if self._mode == 'diff':
  265. self.sci.setMarginWidth(1, 0)
  266. lexer = lexers.get_diff_lexer(self)
  267. self.sci.setLexer(lexer)
  268. # trim first three lines, for example:
  269. # diff -r f6bfc41af6d7 -r c1b18806486d tortoisehg/hgqt/thgrepo.py
  270. # --- a/tortoisehg/hgqt/thgrepo.py
  271. # +++ b/tortoisehg/hgqt/thgrepo.py
  272. noheader = fd.diff.split('\n', 3)[3]
  273. self.sci.setText(hglib.tounicode(noheader))
  274. elif fd.contents is None:
  275. return
  276. elif self._mode == 'ann':
  277. self.sci.setSource(filename, ctx.rev())
  278. else:
  279. lexer = lexers.get_lexer(filename, fd.contents, self)
  280. self.sci.setLexer(lexer)
  281. self.sci.setText(fd.contents)
  282. self.sci._updatemarginwidth()
  283. self.highlightText(*self._lastSearch)
  284. uf = hglib.tounicode(self._filename)
  285. self.fileDisplayed.emit(uf, fd.contents or QString())
  286. if self._mode == 'file' and fd.contents and fd.olddata:
  287. # Update diff margin
  288. if self.timer.isActive():
  289. self.timer.stop()
  290. olddata = fd.olddata.splitlines()
  291. newdata = fd.contents.splitlines()
  292. self._diff = difflib.SequenceMatcher(None, olddata, newdata)
  293. self.blk.syncPageStep()
  294. self.timer.start()
  295. def nextDiff(self):
  296. if self._mode == 'diff' or not self._diffs:
  297. self.actionNextDiff.setEnabled(False)
  298. self.actionPrevDiff.setEnabled(False)
  299. return
  300. row, column = self.sci.getCursorPosition()
  301. for i, (lo, hi) in enumerate(self._diffs):
  302. if lo > row:
  303. last = (i == (len(self._diffs)-1))
  304. self.sci.setCursorPosition(lo, 0)
  305. self.sci.verticalScrollBar().setValue(lo)
  306. break
  307. else:
  308. last = True
  309. self.actionNextDiff.setEnabled(not last)
  310. self.actionPrevDiff.setEnabled(True)
  311. def prevDiff(self):
  312. if self._mode == 'diff' or not self._diffs:
  313. self.actionNextDiff.setEnabled(False)
  314. self.actionPrevDiff.setEnabled(False)
  315. return
  316. row, column = self.sci.getCursorPosition()
  317. for i, (lo, hi) in enumerate(reversed(self._diffs)):
  318. if hi < row:
  319. first = (i == (len(self._diffs)-1))
  320. self.sci.setCursorPosition(lo, 0)
  321. self.sci.verticalScrollBar().setValue(lo)
  322. break
  323. else:
  324. first = True
  325. self.actionNextDiff.setEnabled(True)
  326. self.actionPrevDiff.setEnabled(not first)
  327. def nextLine(self):
  328. x, y = self.sci.getCursorPosition()
  329. self.sci.setCursorPosition(x+1, y)
  330. def prevLine(self):
  331. x, y = self.sci.getCursorPosition()
  332. self.sci.setCursorPosition(x-1, y)
  333. def nextCol(self):
  334. x, y = self.sci.getCursorPosition()
  335. self.sci.setCursorPosition(x, y+1)
  336. def prevCol(self):
  337. x, y = self.sci.getCursorPosition()
  338. self.sci.setCursorPosition(x, y-1)
  339. def nDiffs(self):
  340. return len(self._diffs)
  341. @pyqtSlot(unicode, object)
  342. @pyqtSlot(unicode, object, int)
  343. def sourceChanged(self, path, rev, line=None):
  344. self.revisionSelected.emit(rev)
  345. @pyqtSlot(unicode, object, int)
  346. def editSelected(self, path, rev, line):
  347. """Open editor to show the specified file"""
  348. repo = self._ctx._repo
  349. path = hglib.fromunicode(path)
  350. base = visdiff.snapshot(repo, [path], repo[rev])[0]
  351. files = [os.path.join(base, path)]
  352. pattern = hglib.fromunicode(self._lastSearch[0])
  353. wctxactions.edit(self, repo.ui, repo, files, line, pattern)
  354. @pyqtSlot(unicode, bool, bool, bool)
  355. def find(self, exp, icase=True, wrap=False, forward=True):
  356. self.sci.find(exp, icase, wrap, forward)
  357. @pyqtSlot(unicode, bool)
  358. def highlightText(self, match, icase=False):
  359. self._lastSearch = match, icase
  360. self.sci.highlightText(match, icase)
  361. def verticalScrollBar(self):
  362. return self.sci.verticalScrollBar()
  363. def idle_fill_files(self):
  364. # we make a burst of diff-lines computed at once, but we
  365. # disable GUI updates for efficiency reasons, then only
  366. # refresh GUI at the end of the burst
  367. self.sci.setUpdatesEnabled(False)
  368. self.blk.setUpdatesEnabled(False)
  369. for n in range(30): # burst pool
  370. if self._diff is None or not self._diff.get_opcodes():
  371. self.actionNextDiff.setEnabled(bool(self._diffs))
  372. self.actionPrevDiff.setEnabled(False)
  373. self._diff = None
  374. self.timer.stop()
  375. break
  376. tag, alo, ahi, blo, bhi = self._diff.get_opcodes().pop(0)
  377. if tag == 'replace':
  378. self._diffs.append([blo, bhi])
  379. self.blk.addBlock('x', blo, bhi)
  380. for i in range(blo, bhi):
  381. self.sci.markerAdd(i, self.markertriangle)
  382. elif tag == 'delete':
  383. # You cannot effectively show deleted lines in a single
  384. # pane display. They do not exist.
  385. pass
  386. # self._diffs.append([blo, bhi])
  387. # self.blk.addBlock('-', blo, bhi)
  388. # for i in range(alo, ahi):
  389. # self.sci.markerAdd(i, self.markerminus)
  390. elif tag == 'insert':
  391. self._diffs.append([blo, bhi])
  392. self.blk.addBlock('+', blo, bhi)
  393. for i in range(blo, bhi):
  394. self.sci.markerAdd(i, self.markerplus)
  395. elif tag == 'equal':
  396. pass
  397. else:
  398. raise ValueError, 'unknown tag %r' % (tag,)
  399. # ok, enable GUI refresh for code viewers and diff-block displayers
  400. self.sci.setUpdatesEnabled(True)
  401. self.blk.setUpdatesEnabled(True)
  402. class FileData(object):
  403. def __init__(self, ctx, ctx2, wfile, status=None):
  404. self.contents = None
  405. self.error = None
  406. self.olddata = None
  407. self.diff = None
  408. self.flabel = u''
  409. self.elabel = u''
  410. self.readStatus(ctx, ctx2, wfile, status)
  411. def checkMaxDiff(self, ctx, wfile):
  412. p = _('File or diffs not displayed: ')
  413. try:
  414. fctx = ctx.filectx(wfile)
  415. if ctx.rev() is None:
  416. size = fctx.size()
  417. else:
  418. # fctx.size() can read all data into memory in rename cases so
  419. # we read the size directly from the filelog, this is deeper
  420. # under the API than I prefer to go, but seems necessary
  421. size = fctx._filelog.rawsize(fctx.filerev())
  422. except (EnvironmentError, error.LookupError), e:
  423. self.error = p + hglib.tounicode(str(e))
  424. return None
  425. if size > ctx._repo.maxdiff:
  426. self.error = p + _('File is larger than the specified max size.\n')
  427. return None
  428. try:
  429. data = fctx.data()
  430. if '\0' in data:
  431. self.error = p + _('File is binary.\n')
  432. return None
  433. except EnvironmentError, e:
  434. self.error = p + hglib.tounicode(str(e))
  435. return None
  436. return fctx, data
  437. def isValid(self):
  438. return self.error is None
  439. def readStatus(self, ctx, ctx2, wfile, status):
  440. def getstatus(repo, n1, n2, wfile):
  441. m = match.exact(repo.root, repo.getcwd(), [wfile])
  442. modified, added, removed = repo.status(n1, n2, match=m)[:3]
  443. if wfile in modified:
  444. return 'M'
  445. if wfile in added:
  446. return 'A'
  447. if wfile in removed:
  448. return 'R'
  449. return None
  450. repo = ctx._repo
  451. self.flabel += u'<b>%s</b>' % hglib.tounicode(wfile)
  452. if isinstance(ctx, patchctx.patchctx):
  453. self.diff = ctx.thgmqpatchdata(wfile)
  454. flags = ctx.flags(wfile)
  455. if flags in ('x', '-'):
  456. lbl = _("exec mode has been <font color='red'>%s</font>")
  457. change = (flags == 'x') and _('set') or _('unset')
  458. self.elabel = lbl % change
  459. elif flags == 'l':
  460. self.flabel += _(' <i>(is a symlink)</i>')
  461. return
  462. absfile = repo.wjoin(wfile)
  463. if (wfile in ctx and 'l' in ctx.flags(wfile)) or \
  464. os.path.islink(absfile):
  465. if wfile in ctx:
  466. data = ctx[wfile].data()
  467. else:
  468. data = os.readlink(absfile)
  469. self.contents = hglib.tounicode(data)
  470. self.flabel += _(' <i>(is a symlink)</i>')
  471. return
  472. if status is None:
  473. status = getstatus(repo, ctx.p1().node(), ctx.node(), wfile)
  474. if ctx2 is None:
  475. ctx2 = ctx.p1()
  476. if status == 'S':
  477. try:
  478. from mercurial import subrepo, commands
  479. assert(ctx.rev() is None)
  480. out = []
  481. _ui = uimod.ui()
  482. sroot = repo.wjoin(wfile)
  483. srepo = hg.repository(_ui, path=sroot)
  484. srev = ctx.substate.get(wfile, subrepo.nullstate)[1]
  485. sactual = srepo['.'].hex()
  486. _ui.pushbuffer()
  487. commands.status(_ui, srepo)
  488. data = _ui.popbuffer()
  489. if data:
  490. out.append(_('File Status:\n'))
  491. out.append(data)
  492. out.append('\n')
  493. if srev == '':
  494. out.append(_('New subrepository\n\n'))
  495. elif srev != sactual:
  496. out.append(_('Revision has changed from:\n\n'))
  497. opts = {'date':None, 'user':None, 'rev':[srev]}
  498. _ui.pushbuffer()
  499. commands.log(_ui, srepo, **opts)
  500. out.append(hglib.tounicode(_ui.popbuffer()))
  501. out.append(_('To:\n'))
  502. opts['rev'] = [sactual]
  503. _ui.pushbuffer()
  504. commands.log(_ui, srepo, **opts)
  505. out.append(hglib.tounicode(_ui.popbuffer()))
  506. self.contents = u''.join(out)
  507. self.flabel += _(' <i>(is a dirty sub-repository)</i>')
  508. lbl = u' <a href="subrepo:%s">%s...</a>'
  509. self.flabel += lbl % (hglib.tounicode(sroot), _('open'))
  510. except (error.RepoError, util.Abort), e:
  511. self.error = _('Not a Mercurial subrepo, not previewable')
  512. return
  513. # TODO: elif check if a subdirectory (for manifest tool)
  514. if status in ('R', '!'):
  515. if wfile in ctx.p1():
  516. newdata = ctx.p1()[wfile].data()
  517. self.contents = hglib.tounicode(newdata)
  518. self.flabel += _(' <i>(was deleted)</i>')
  519. else:
  520. self.flabel += _(' <i>(was added, now missing)</i>')
  521. return
  522. if status in ('I', '?'):
  523. try:
  524. data = open(repo.wjoin(wfile), 'r').read()
  525. if '\0' in data:
  526. self.error = 'binary file'
  527. else:
  528. self.contents = hglib.tounicode(data)
  529. self.flabel += _(' <i>(is unversioned)</i>')
  530. except EnvironmentError, e:
  531. self.error = hglib.tounicode(str(e))
  532. return
  533. if status in ('M', 'A'):
  534. res = self.checkMaxDiff(ctx, wfile)
  535. if res is None:
  536. return
  537. fctx, newdata = res
  538. self.contents = hglib.tounicode(newdata)
  539. change = None
  540. for pfctx in fctx.parents():
  541. if 'x' in fctx.flags() and 'x' not in pfctx.flags():
  542. change = _('set')
  543. elif 'x' not in fctx.flags() and 'x' in pfctx.flags():
  544. change = _('unset')
  545. if change:
  546. lbl = _("exec mode has been <font color='red'>%s</font>")
  547. self.elabel = lbl % change
  548. if status == 'A':
  549. renamed = fctx.renamed()
  550. if not renamed:
  551. self.flabel += _(' <i>(was added)</i>')
  552. return
  553. oldname, node = renamed
  554. fr = hglib.tounicode(oldname)
  555. self.flabel += _(' <i>(renamed from %s)</i>') % fr
  556. olddata = repo.filectx(oldname, fileid=node).data()
  557. elif status == 'M':
  558. if wfile not in ctx2:
  559. # merge situation where file was added in other branch
  560. self.flabel += _(' <i>(was added)</i>')
  561. return
  562. oldname = wfile
  563. olddata = ctx2[wfile].data()
  564. else:
  565. return
  566. self.olddata = olddata
  567. newdate = util.datestr(ctx.date())
  568. olddate = util.datestr(ctx2.date())
  569. revs = [str(ctx), str(ctx2)]
  570. diffopts = patch.diffopts(repo.ui, {})
  571. diffopts.git = False
  572. self.diff = mdiff.unidiff(olddata, olddate, newdata, newdate,
  573. oldname, wfile, revs, diffopts)