1. TortoiseHg
  2. TortoiseHg
  3. thg

Commits

Steve Borho  committed 03c6609 Merge

Merge with default

  • Participants
  • Parent commits b1db08e, f9c94ac
  • Branches stable

Comments (0)

Files changed (30)

File TortoiseHgOverlayServer.py

View file
  • Ignore whitespace
         _stderr = sys.stderr
         sys.stderr = errorstream
         try:
+            # Ensure that all unset dirstate entries can be updated.
+            time.sleep(2)
+            updated_any = False
             for r in sorted(roots):
                 try:
-                    shlib.update_thgstatus(_ui, r, wait=False)
-                    shlib.shell_notify([r])
+                    if shlib.update_thgstatus(_ui, r, wait=False):
+                        updated_any = True
+                    shlib.shell_notify([r], noassoc=True)
                     logger.msg('Updated ' + r)
                 except (IOError, OSError):
                     print "IOError or OSError on updating %s (check permissions)" % r
                     failedroots.add(r)
             notifypaths -= failedroots
             if notifypaths:
-                time.sleep(2)
-                shlib.shell_notify(list(notifypaths))
+                shlib.shell_notify(list(notifypaths), noassoc=not updated_any)
                 logger.msg('Shell notified')
             errmsg = errorstream.getvalue()
             if errmsg:

File icons/16x16/apps/kiln.png

  • Ignore whitespace
Added
New image

File tortoisehg/hgqt/bugreport.py

View file
  • Ignore whitespace
                 fake = FakeRepo()
                 qtlib.editfiles(fake, [fname], lineno, parent=self)
             except Exception, e:
-                QDesktopServices.openUrl(QUrl.fromLocalFile(fname))
+                qtlib.openlocalurl(fname)
         if ref.startswith('#fix:'):
             from tortoisehg.hgqt import settings
             errtext = ref[5:].split(' ')[0]

File tortoisehg/hgqt/chunks.py

View file
  • Ignore whitespace
                         for chunk in ctx._files[wfile]:
                             chunk.write(buf)
                 fp.write(buf.getvalue())
-                fp.rename()
+                fp.close()
             finally:
                 del fp
             ctx.invalidate()
                 for file in ctx._fileorder:
                     for chunk in ctx._files[file]:
                         chunk.write(fp)
-                fp.rename()
+                fp.close()
                 ctx.invalidate()
                 self.fileModified.emit()
                 return True
                         continue
                     for chunk in ctx._files[file]:
                         chunk.write(fp)
-                fp.rename()
+                fp.close()
             finally:
                 del fp
             ctx.invalidate()

File tortoisehg/hgqt/commit.py

View file
  • Ignore whitespace
 # GNU General Public License version 2, incorporated herein by reference.
 
 import os
+import re
 
 from mercurial import ui, util, error
 
 from tortoisehg.hgqt.i18n import _
 from tortoisehg.hgqt.messageentry import MessageEntry
 from tortoisehg.hgqt import qtlib, qscilib, status, cmdui, branchop, revpanel
-from tortoisehg.hgqt import hgrcutil, mq
+from tortoisehg.hgqt import hgrcutil, mq, lfprompt
 
 from PyQt4.QtCore import *
 from PyQt4.QtGui import *
             self.msgte.setFocus()
             return
 
+        linkmandatory = self.repo.ui.config('tortoisehg',
+                                            'issue.linkmandatory', False)
+        if linkmandatory:
+            issueregex = self.repo.ui.config('tortoisehg', 'issue.regex')
+            if issueregex:
+                m = re.search(issueregex, msg)
+                if not m:
+                    qtlib.WarningMsgBox(_('Nothing Commited'),
+                                        _('No issue link was found in the commit message.  '
+                                          'The commit message should contain an issue '
+                                          'link.  Configure this in the \'Issue Tracking\' '
+                                          'section of the settings.'),
+                                        parent=self)
+                    self.msgte.setFocus()
+                    return False
+
         commandlines = []
 
         brcmd = []
                     (_('&Add'), _('Cancel')), 0, 1,
                     checkedUnknowns).run()
             if res == 0:
+                haslf = 'largefiles' in repo.extensions()
+                haskbf = 'kbfiles' in repo.extensions()
+                if haslf or haskbf:
+                    result = lfprompt.promptForLfiles(self, repo.ui, repo,
+                                                      checkedUnknowns, haskbf)
+                    if not result:
+                        return
+                    checkedUnknowns, lfiles = result
+                    if lfiles:
+                        if haslf:
+                            cmd = ['add', '--repository', repo.root, '--large'] + \
+                                  [repo.wjoin(f) for f in lfiles]
+                        else:
+                            cmd = ['add', '--repository', repo.root, '--bf'] + \
+                                  [repo.wjoin(f) for f in lfiles]
+                        commandlines.append(cmd)
                 cmd = ['add', '--repository', repo.root] + \
                       [repo.wjoin(f) for f in checkedUnknowns]
                 commandlines.append(cmd)

File tortoisehg/hgqt/filedata.py

View file
  • Ignore whitespace
             return None
         try:
             data = fctx.data()
-            if '\0' in data:
+            if '\0' in data or ctx.isStandin(wfile):
                 self.error = p + _('File is binary')
                 if status != 'A':
                     return None
                 return 'C'
             return None
 
+        isbfile = False
         repo = ctx._repo
         self.flabel += u'<b>%s</b>' % hglib.tounicode(wfile)
 
                     else:
                         self.contents = olddata
                 self.flabel += _(' <i>(was deleted)</i>')
+            elif hasattr(ctx.p1(), 'hasStandin') and ctx.p1().hasStandin(wfile):
+                self.error = 'binary file'
+                self.flabel += _(' <i>(was deleted)</i>')
             else:
                 self.flabel += _(' <i>(was added, now missing)</i>')
             return
                     return
                 else:
                     data = util.posixfile(absfile, 'r').read()
+            elif ctx.hasStandin(wfile):
+                data = '\0'
             else:
                 data = ctx.filectx(wfile).data()
             if '\0' in data:
             return
 
         if status in ('M', 'A'):
+            if ctx.hasStandin(wfile):
+                wfile = ctx.findStandin(wfile)
+                isbfile = True
             res = self.checkMaxDiff(ctx, wfile, maxdiff, status)
             if res is None:
                 return
         revs = [str(ctx), str(ctx2)]
         diffopts = patch.diffopts(repo.ui, {})
         diffopts.git = False
+        if isbfile:
+            olddata += '\0'
+            newdata += '\0'
         self.diff = mdiff.unidiff(olddata, olddate, newdata, newdate,
                                   oldname, wfile, revs, diffopts)

File tortoisehg/hgqt/filelistmodel.py

View file
  • Ignore whitespace
         for lst, flag in ((added, 'A'), (modified, 'M'), (removed, 'R')):
             for f in filter(func, lst):
                 wasmerged = ismerge and f in ctxfiles
+                f = self._ctx.removeStandin(f)
                 files.append({'path': f, 'status': flag, 'parent': parent,
                               'wasmerged': wasmerged})
         return files

File tortoisehg/hgqt/fileview.py

View file
  • Ignore whitespace
         self.actionNextDiff.setEnabled(False)
         self.actionPrevDiff.setEnabled(False)
 
+        self.maxWidth = 0
+        self.sci.showHScrollBar(False)
+
     def displayFile(self, filename=None, status=None):
         if isinstance(filename, (unicode, QString)):
             filename = hglib.fromunicode(filename)
         self.actionNextDiff.setEnabled(bool(self._diffs))
         self.actionPrevDiff.setEnabled(bool(self._diffs))
 
+        lexer = self.sci.lexer()
+
+        if lexer:
+            font = self.sci.lexer().font(0)
+        else:
+            font = self.sci.font()
+
+        fm = QFontMetrics(font)
+        maxWidth = fm.maxWidth()
+        lines = self.sci.text().split('\n')
+        widths = [fm.width(line) + maxWidth for line in lines]
+        self.maxWidth = max(widths)
+        self.updateScrollBar()
+
     #
     # These four functions are used by Shift+Cursor actions in revdetails
     #
                 add(name, func)
         menu.exec_(point)
 
+    def resizeEvent(self, event):
+        super(HgFileView, self).resizeEvent(event)
+        self.updateScrollBar()
+
+    def updateScrollBar(self):
+        sbWidth = self.sci.verticalScrollBar().width()
+        scrollWidth = self.maxWidth + sbWidth - self.sci.width()
+        self.sci.showHScrollBar(scrollWidth > 0)
+        self.sci.horizontalScrollBar().setRange(0, scrollWidth)
 
 class AnnotateView(qscilib.Scintilla):
     'QScintilla widget capable of displaying annotations'

File tortoisehg/hgqt/grep.py

View file
  • Ignore whitespace
 
 from mercurial import ui, hg, error, commands, match, util, subrepo
 
-from tortoisehg.hgqt import htmlui, visdiff, qtlib, htmldelegate, thgrepo, cmdui
+from tortoisehg.hgqt import htmlui, visdiff, qtlib, htmldelegate, thgrepo, cmdui, settings
 from tortoisehg.util import paths, hglib, thread2
 from tortoisehg.hgqt.i18n import _
 
                 pass
 
     def run(self):
+        haskbf = settings.hasExtension('kbfiles')
+        haslf = settings.hasExtension('largefiles')
         self.thread_id = int(QThread.currentThreadId())
 
         def emitrow(row):
                     try:
                         fname, line, rev, addremove, user, text, tail = \
                                 self.fullmsg.split('\0', 6)
+                        if haslf and thgrepo.isLfStandin(fname):
+                            raise ValueError
+                        if (haslf or haskbf) and thgrepo.isBfStandin(fname):
+                            raise ValueError
                         text = hglib.tounicode(text)
                         text = Qt.escape(text)
                         text = '<b>%s</b> <span>%s</span>' % (addremove, text)
         unit = _('files')
         total = len(ctx.manifest())
         count = 0
+        haskbf = settings.hasExtension('kbfiles')
+        haslf = settings.hasExtension('largefiles')
         for wfile in ctx:                # walk manifest
             if self.canceled:
                 break
+            if haslf and thgrepo.isLfStandin(wfile):
+                continue
+            if (haslf or haskbf) and thgrepo.isBfStandin(wfile):
+                continue
             self.progress.emit(topic, count, wfile, unit, total)
             count += 1
             if not matchfn(wfile):

File tortoisehg/hgqt/hgignore.py

View file
  • Ignore whitespace
         try:
             f = util.atomictempfile(self.ignorefile, 'wb', createmode=None)
             f.write(out)
-            f.rename()
+            f.close()
             if not hasignore:
                 ret = qtlib.QuestionMsgBox(_('New file created'),
                                            _('TortoiseHg has created a new '

File tortoisehg/hgqt/lfprompt.py

View file
  • Ignore whitespace
+# bfprompt.py - prompt to add large files as bfiles
+#
+# Copyright 2011 Fog Creek Software
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+import os
+
+from mercurial import match
+from tortoisehg.hgqt import qtlib
+from tortoisehg.hgqt.i18n import _
+
+class LfilesPrompt(qtlib.CustomPrompt):
+    def __init__(self, parent, files=None):
+        qtlib.CustomPrompt.__init__(self, _('Confirm Add'),
+                                    _('Some of the files that you have selected are of a size '
+                                      'over 10 MB.  You may make more efficient use of disk space '
+                                      'by adding these files as largefiles, which will store only the '
+                                      'most recent revision of each file in your local repository, '
+                                      'with older revisions available on the server.  Do you wish '
+                                      'to add these files as largefiles?'), parent,
+                                      (_('Add as &Largefiles'), _('Add as &Normal Files'), _('Cancel')),
+                                      0, 2, files)
+
+class BfilesPrompt(qtlib.CustomPrompt):
+    def __init__(self, parent, files=None):
+        qtlib.CustomPrompt.__init__(self, _('Confirm Add'),
+                                    _('Some of the files that you have selected are of a size '
+                                      'over 10 MB.  You may make more efficient use of disk space '
+                                      'by adding these files as bfiles, which will store only the '
+                                      'most recent revision of each file in your local repository, '
+                                      'with older revisions available on the server.  Do you wish '
+                                      'to add these files as bfiles?'), parent,
+                                      (_('Add as &Bfiles'), _('Add as &Normal Files'), _('Cancel')),
+                                      0, 2, files)
+                                      
+def promptForLfiles(parent, ui, repo, files, haskbf=False):
+    lfiles = []
+    usekbf = os.path.exists('.kbf')
+    uself = os.path.exists('.hglf')
+    useneither = not usekbf and not uself
+    if haskbf:
+        section = 'kilnbfiles'
+    else:
+        section = 'largefiles'
+    minsize = int(ui.config(section, 'size', default='10'))
+    patterns = ui.config(section, 'patterns', default=())
+    if patterns:
+        patterns = patterns.split(' ')
+        matcher = match.match(repo.root, '', list(patterns))
+    else:
+        matcher = None
+    for wfile in files:
+        if not matcher or not matcher(wfile) or useneither:
+            filesize = os.path.getsize(repo.wjoin(wfile))
+            if filesize >= 10*1024*1024 and (filesize < minsize*1024*1024 or useneither):
+                lfiles.append(wfile)
+    if lfiles:
+        if haskbf:
+            ret = BfilesPrompt(parent, files).run()
+        else:
+            ret = LfilesPrompt(parent, files).run()
+        if ret == 0:
+            # add as largefiles/bfiles
+            for lfile in lfiles:
+                files.remove(lfile)
+        elif ret == 1:
+            # add as normal files
+            lfiles = []
+        elif ret == 2:
+            return None
+    return files, lfiles

File tortoisehg/hgqt/manifestdialog.py

View file
  • Ignore whitespace
     def explore(self):
         root = self._repo.wjoin(hglib.fromunicode(self.path))
         if os.path.isdir(root):
-            QDesktopServices.openUrl(QUrl.fromLocalFile(hglib.tounicode(root)))
+            qtlib.openlocalurl(root)
 
     def terminal(self):
         root = self._repo.wjoin(hglib.fromunicode(self.path))

File tortoisehg/hgqt/manifestmodel.py

View file
  • Ignore whitespace
                 if not pathinstatus(path, status, uncleanpaths):
                     continue
 
+                origpath = path
+                path = self._repo.removeStandin(path)
+                
                 e = treeroot
                 for p in hglib.tounicode(path).split('/'):
                     if not p in e:
                     e = e[p]
 
                 for st, filesofst in status.iteritems():
-                    if path in filesofst:
+                    if origpath in filesofst:
                         e.setstatus(st)
                         break
                 else:

File tortoisehg/hgqt/purge.py

View file
  • Ignore whitespace
 
             def run(self):
                 try:
-                    wctx = repo[None]
-                    wctx.status(ignored=True, unknown=True)
+                    repo.bfstatus = True
+                    repo.lfstatus = True
+                    stat = repo.status(ignored=True, unknown=True)
+                    repo.bfstatus = False
+                    repo.lfstatus = False
                     trashcan = repo.join('Trashcan')
                     if os.path.isdir(trashcan):
                         trash = os.listdir(trashcan)
                     else:
                         trash = []
-                    self.files = wctx.unknown(), wctx.ignored(), trash
+                    self.files = stat[4], stat[5], trash
                 except Exception, e:
                     self.error = str(e)
 
         self.showMessage.emit('')
         match = hglib.matchall(repo)
         match.dir = directories.append
+        repo.bfstatus = True
+        repo.lfstatus = True
         status = repo.status(match=match, ignored=opts['ignored'],
                              unknown=opts['unknown'], clean=False)
+        repo.bfstatus = False
+        repo.lfstatus = False
         files = status[4] + status[5]
 
         def remove(remove_func, name):

File tortoisehg/hgqt/qtlib.py

View file
  • Ignore whitespace
             chm = os.path.join(paths.bin_path, 'doc', 'TortoiseHg.chm')
             if os.path.exists(chm):
                 fullurl = (r'mk:@MSITStore:%s::/' % chm) + url
-                QDesktopServices.openUrl(QUrl.fromLocalFile(fullurl))
+                openlocalurl(fullurl)
                 return
         QDesktopServices.openUrl(QUrl(fullurl))
 
+def openlocalurl(path):
+    '''open the given path with the default application
+
+    takes str, unicode or QString as argument
+    returns True if open was successfull
+    '''
+
+    if isinstance(path, str):
+        path = QString(hglib.tounicode(path))
+    elif isinstance(path, unicode):
+        path = QString(path)
+    if os.name == 'nt' and path.startsWith('\\\\'):
+        # network share, special handling because of qt bug 13359
+        # see http://bugreports.qt.nokia.com/browse/QTBUG-13359
+        qurl = QUrl()
+        qurl.setUrl(QDir.toNativeSeparators(path))
+    else:
+        qurl = QUrl.fromLocalFile(path)
+    return QDesktopServices.openUrl(qurl)
+
 def openfiles(repo, files, parent=None):
     if os.name == 'nt':
         editor = 'start'

File tortoisehg/hgqt/quickop.py

View file
  • Ignore whitespace
 
 from tortoisehg.util import hglib, shlib
 from tortoisehg.hgqt.i18n import _
-from tortoisehg.hgqt import qtlib, status, cmdui
+from tortoisehg.hgqt import qtlib, status, cmdui, lfprompt
 
 from PyQt4.QtCore import *
 from PyQt4.QtGui import *
         hbox.addWidget(bb)
         toplayout.addLayout(hbox)
         self.bb = bb
+        
+        if self.command == 'add':
+            if 'largefiles' in self.repo.extensions():
+                self.addLfilesButton = QPushButton(_('Add &Largefiles'))
+            elif 'kbfiles' in self.repo.extensions():
+                self.addLfilesButton = QPushButton(_("Add &Bfiles"))
+            else:
+                self.addLfilesButton = None
+            if self.addLfilesButton:
+                self.addLfilesButton.clicked.connect(self.addLfiles)
+                bb.addButton(self.addLfilesButton, BB.ActionRole)
 
         layout.addWidget(self.statusbar)
 
                                 _('No operation to perform'),
                                 parent=self)
             return
+        self.repo.bfstatus = True
+        self.repo.lfstatus = True
+        repostate = self.repo.status()
+        self.repo.bfstatus = False
+        self.repo.lfstatus = False
         if self.command == 'remove':
             if not self.chk.isChecked():
-                modified = self.repo.status()[0]
+                modified = repostate[0]
                 selmodified = []
                 for wfile in files:
                     if wfile in modified:
                         cmdline.append('--force')
                     elif ret == 2:
                         return
-            wctx = self.repo[None]
+            unknown, ignored = repostate[4:6]
             for wfile in files:
-                if wfile not in wctx:
+                if wfile in unknown or wfile in ignored:
                     try:
                         util.unlink(wfile)
                     except EnvironmentError:
                         pass
                     files.remove(wfile)
+        elif self.command == 'add':
+            if 'largefiles' in self.repo.extensions() or 'kbfiles' in self.repo.extensions():
+                self.addWithPrompt(files)
+                return
         if files:
             cmdline.extend(files)
             self.files = files
                     s.setValue('quickop/forceremove', self.chk.isChecked())
             QDialog.reject(self)
 
+    def addLfiles(self):
+        if 'kbfiles' in self.repo.extensions():
+            cmdline = ['add', '--bf']
+        else:
+            cmdline = ['add', '--large']
+        files = self.stwidget.getChecked()
+        if not files:
+            qtlib.WarningMsgBox(_('No files selected'),
+                                _('No operation to perform'),
+                                parent=self)
+            return
+        cmdline.extend(files)
+        self.files = files
+        self.cmd.run(cmdline)
+
+    def addWithPrompt(self, files):
+        result = lfprompt.promptForLfiles(self, self.repo.ui, self.repo, files,
+                                          'kbfiles' in self.repo.extensions())
+        if not result:
+            return
+        files, bfiles = result
+        if files:
+            cmdline = ['add']
+            cmdline.extend(files)
+            self.files = files
+            self.cmd.run(cmdline)
+        if bfiles:
+            if 'kbfiles' in self.repo.extensions():
+                cmdline = ['add', '--bf']
+            else:
+                cmdline = ['add', '--large']
+            cmdline.extend(bfiles)
+            self.files = bfiles
+            self.cmd.run(cmdline)
 
 instance = None
 class HeadlessQuickop(QWidget):

File tortoisehg/hgqt/reporegistry.py

View file
  • Ignore whitespace
         index = self.indexAt(event.pos())
 
         # Determine where the item was dropped.
-        # Depth in tree: 1 = group, 2 = repo, and (eventually) 3+ = subrepo
-        depth = self.model().depth(index)
-        if depth == 1:
+        target = index.internalPointer()
+        if not target.isRepo():
             group = index
             row = -1
-        elif depth == 2:
+        else:
             indicator = self.dropIndicatorPosition()
             group = index.parent()
             row = index.row()
             if indicator == QAbstractItemView.BelowItem:
                 row = index.row() + 1
-        else:
-            index = group = row = None
 
         return index, group, row
 
 
     showMessage = pyqtSignal(QString)
     openRepo = pyqtSignal(QString, bool)
+    removeRepo = pyqtSignal(QString)
 
     def __init__(self, parent, showSubrepos=False, showNetworkSubrepos=False,
             showShortPaths=False):
 
     def explore(self):
         root = self.selitem.internalPointer().rootpath()
-        QDesktopServices.openUrl(QUrl.fromLocalFile(root))
+        qtlib.openlocalurl(root)
 
     def terminal(self):
         repoitem = self.selitem.internalPointer()
         self.tview.model().addGroup(_('New Group'))
 
     def removeSelected(self):
+        ip = self.selitem.internalPointer()
+        if ip.isRepo():
+            root = ip.rootpath()
+        else:
+            root = None
+
         self.tview.removeSelected()
 
+        if root is not None:
+            self.removeRepo.emit(hglib.tounicode(root))
+
     @pyqtSlot(QString, QString)
     def shortNameChanged(self, uroot, uname):
         it = self.tview.model().getRepoItem(hglib.fromunicode(uroot))

File tortoisehg/hgqt/repotreemodel.py

View file
  • Ignore whitespace
         group = parent.internalPointer()
         d = str(data.data(repoRegMimeType))
         if not data.hasUrls():
-            # don't allow nesting of groups
-            row = parent.row()
-            group = self.rootItem
-            parent = QModelIndex()
+            # The source is a group
+            if row < 0:
+                # The group has been dropped on a group
+                # In that case, place the group at the same level as the target
+                # group
+                row = parent.row()
+                parent = parent.parent()
+                group = parent.internalPointer()
+                if row < 0 or not isinstance(group, RepoGroupItem):
+                    # The group was dropped at the top level
+                    group = self.rootItem
+                    parent = QModelIndex()
         itemread = readXml(d, extractXmlElementName)
         if itemread is None:
             return False

File tortoisehg/hgqt/revdetails.py

View file
  • Ignore whitespace
     def explore(self):
         root = self.repo.wjoin(self.filelist.currentFile())
         if os.path.isdir(root):
-            QDesktopServices.openUrl(QUrl.fromLocalFile(root))
+            qtlib.openlocalurl(root)
 
     def terminal(self):
         root = self.repo.wjoin(self.filelist.currentFile())

File tortoisehg/hgqt/settings.py

View file
  • Ignore whitespace
 
 import os
 
-from mercurial import ui, util, error
+from mercurial import ui, util, error, extensions
 
 from tortoisehg.util import hglib, settings, paths, wconfig, i18n, bugtraq
 from tortoisehg.hgqt.i18n import _
 _unspecstr = _('<unspecified>')
 ENTRY_WIDTH = 300
 
+def hasExtension(extname):
+    for name, module in extensions.extensions():
+        if name == extname:
+            return True
+    return False
+
 class SettingsCombo(QComboBox):
     def __init__(self, parent=None, **opts):
         QComboBox.__init__(self, parent, toolTip=opts['tooltip'])
         return self.value() != self.curvalue
 
 
+class PathBrowser(QWidget):
+    def __init__(self, parent=None, **opts):
+        QWidget.__init__(self, parent, toolTip=opts['tooltip'])
+        self.opts = opts
+        
+        self.lineEdit = QLineEdit()
+        completer = QCompleter(self)
+        completer.setModel(QDirModel(completer))
+        self.lineEdit.setCompleter(completer)
+        
+        self.browseButton = QPushButton(_('&Browse...'))
+        self.browseButton.clicked.connect(self.browse)
+        
+        layout = QHBoxLayout()
+        layout.setContentsMargins(0, 0, 0, 0)
+        layout.addWidget(self.lineEdit)
+        layout.addWidget(self.browseButton)
+        self.setLayout(layout)
+        
+    def browse(self):
+        dir = QFileDialog.getExistingDirectory(self, directory=self.lineEdit.text(),
+                                               options=QFileDialog.ShowDirsOnly)
+        if dir:
+            self.lineEdit.setText(dir)
+    
+    ## common APIs for all edit widgets
+    def setValue(self, curvalue):
+        self.curvalue = curvalue
+        if curvalue:
+            self.lineEdit.setText(hglib.tounicode(curvalue))
+        else:
+            self.lineEdit.setText('')
+            
+    def value(self):
+        utext = self.lineEdit.text()
+        return utext and hglib.fromunicode(utext) or None
+    
+    def isDirty(self):
+        return self.value() != self.curvalue
+        
 def genEditCombo(opts, defaults=[]):
     opts['canedit'] = True
     opts['defaults'] = defaults
 def genBugTraqEdit(opts):
     return BugTraqConfigureEntry(**opts)
 
+def genPathBrowser(opts):
+    return PathBrowser(**opts)
+
 def findIssueTrackerPlugins():
     plugins = bugtraq.get_issue_plugins_with_names()
     names = [("%s %s" % (key[0], key[1])) for key in plugins]
           'while {1} refers to the first group and so on. If no {n} tokens'
           'are found in issue.link, the entire matched string is appended '
           'instead.')),
+    _fi(_('Mandatory Issue Reference'), 'tortoisehg.issue.linkmandatory', genBoolRBGroup,
+        _('When committing, require that a reference to an issue be specified.  '
+          'If enabled, the regex configured in \'Issue Regex\' must find a match '
+          'in the commit message.')),
     _fi(_('Issue Tracker Plugin'), 'tortoisehg.issue.bugtraqplugin',
         (genDeferredCombo, findIssueTrackerPlugins),
         _('Configures a COM IBugTraqProvider or IBugTrackProvider2 issue '
     _fi(_('Target People'), 'reviewboard.target_people', genEditCombo,
         _('A comma separated list of target people')),
     )),
+    
+({'name': 'kbfiles', 'label': _('Kiln Bfiles'), 'icon': 'kiln', 'extension': 'kbfiles'}, (
+    _fi(_('Patterns'), 'kilnbfiles.patterns', genEditCombo,
+        _('Files with names meeting the specified patterns will be automatically '
+          'added as bfiles')),
+    _fi(_('Size'), 'kilnbfiles.size', genEditCombo,
+        _('Files of at least the specified size (in megabytes) will be added as bfiles')),
+    _fi(_('System Cache'), 'kilnbfiles.systemcache', genPathBrowser,
+        _('Path to the directory where a system-wide cache of bfiles will be stored')),
+    )),
+
+({'name': 'largefiles', 'label': _('Largefiles'), 'icon': 'kiln', 'extension': 'largefiles'}, (
+    _fi(_('Patterns'), 'largefiles.patterns', genEditCombo,
+        _('Files with names meeting the specified patterns will be automatically '
+          'added as largefiles')),
+    _fi(_('Size'), 'largefiles.size', genEditCombo,
+        _('Files of at least the specified size (in megabytes) will be added as largefiles')),
+    _fi(_('System Cache'), 'largefiles.systemcache', genPathBrowser,
+        _('Path to the directory where a system-wide cache of largefiles will be stored')),
+    )),
 
 )
 
 
         # add page items to treeview
         for meta, info in INFO:
+            if 'extension' in meta and not hasExtension(meta['extension']):
+                continue
             if isinstance(meta['icon'], str):
                 icon = qtlib.geticon(meta['icon'])
             else:

File tortoisehg/hgqt/status.py

View file
  • Ignore whitespace
 
 from tortoisehg.util import paths, hglib
 from tortoisehg.hgqt.i18n import _
-from tortoisehg.hgqt import qtlib, wctxactions, visdiff, cmdui, fileview
+from tortoisehg.hgqt import qtlib, wctxactions, visdiff, cmdui, fileview, thgrepo
 
 from PyQt4.QtCore import *
 from PyQt4.QtGui import *
 
     def __init__(self, repo, pctx, pats, opts, parent=None):
         super(StatusThread, self).__init__()
-        self.repo = hg.repository(repo.ui, repo.root)
+        self.repo = thgrepo.repository(repo.ui, repo.root)
         self.pctx = pctx
         self.pats = pats
         self.opts = opts
                     # status and commit only pre-check MAR files
                     precheckfn = lambda x: x < 4
                 m = hglib.match(self.repo[None], self.pats)
+                self.repo.bfstatus = True
+                self.repo.lfstatus = True
                 status = self.repo.status(match=m, **stopts)
+                self.repo.bfstatus = False
+                self.repo.lfstatus = False
                 # Record all matched files as initially checked
                 for i, stat in enumerate(StatusType.preferredOrder):
                     if stat == 'S':
                 wctx = context.workingctx(self.repo, changes=status)
                 self.patchecked = patchecked
             elif self.pctx:
+                self.repo.bfstatus = True
+                self.repo.lfstatus = True
                 status = self.repo.status(node1=self.pctx.p1().node(), **stopts)
+                self.repo.bfstatus = False
+                self.repo.lfstatus = False
                 wctx = context.workingctx(self.repo, changes=status)
             else:
                 wctx = self.repo[None]
+                self.repo.bfstatus = True
+                self.repo.lfstatus = True
                 wctx.status(**stopts)
+                self.repo.bfstatus = False
+                self.repo.lfstatus = False
             self.wctx = wctx
 
             wctx.dirtySubrepos = []

File tortoisehg/hgqt/sync.py

View file
  • Ignore whitespace
         self.embedded = bool(parent)
         self.targetargs = []
 
+        s = QSettings()
+        for opt in ('subrepos', 'force', 'new-branch', 'noproxy', 'debug'):
+            val = s.value('sync/' + opt, None).toBool()
+            if val:
+                self.opts[opt] = val
+        for opt in ('remotecmd', 'branch'):
+            val = str(s.value('sync/' + opt, None).toString())
+            if val:
+                self.opts[opt] = val
+
         self.repo.configChanged.connect(self.configChanged)
 
         if self.embedded:
             self.opts.update(dlg.outopts)
             self.refreshUrl()
 
+            s = QSettings()
+            for opt, val in self.opts.iteritems():
+                s.setValue('sync/' + opt, val)
+
     def reload(self):
         # Refresh configured paths
         self.paths = {}
         url = hglib.fromunicode(self.menuurl)
         u, h, p, folder, pw, scheme = parseurl(url)
         if scheme == 'local':
-            QDesktopServices.openUrl(QUrl.fromLocalFile(folder))
+            qtlib.openlocalurl(folder)
         else:
             QDesktopServices.openUrl(QUrl(url))
 
         self.setWindowTitle(_('%s - sync options') % parent.repo.displayname)
         self.repo = parent.repo
 
-        layout = QFormLayout()
+        layout = QVBoxLayout()
         self.setLayout(layout)
 
         self.newbranchcb = QCheckBox(
             _('Allow push of a new branch (--new-branch)'))
         self.newbranchcb.setChecked(opts.get('new-branch', False))
-        layout.addRow(self.newbranchcb, None)
+        layout.addWidget(self.newbranchcb)
 
         self.forcecb = QCheckBox(
             _('Force push or pull (override safety checks, --force)'))
         self.forcecb.setChecked(opts.get('force', False))
-        layout.addRow(self.forcecb, None)
+        layout.addWidget(self.forcecb)
 
         self.subrepocb = QCheckBox(
             _('Recurse into subrepositories') + u' (--subrepos)')
         self.subrepocb.setChecked(opts.get('subrepos', False))
-        layout.addRow(self.subrepocb, None)
+        layout.addWidget(self.subrepocb)
 
         self.noproxycb = QCheckBox(
             _('Temporarily disable configured HTTP proxy'))
         self.noproxycb.setChecked(opts.get('noproxy', False))
-        layout.addRow(self.noproxycb, None)
+        layout.addWidget(self.noproxycb)
         proxy = self.repo.ui.config('http_proxy', 'host')
         self.noproxycb.setEnabled(bool(proxy))
 
         self.debugcb = QCheckBox(
             _('Emit debugging output (--debug)'))
         self.debugcb.setChecked(opts.get('debug', False))
-        layout.addRow(self.debugcb, None)
+        layout.addWidget(self.debugcb)
+
+        form = QFormLayout()
+        layout.addLayout(form)
 
         lbl = QLabel(_('Remote command:'))
         self.remotele = QLineEdit()
         if opts.get('remotecmd'):
             self.remotele.setText(hglib.tounicode(opts['remotecmd']))
-        layout.addRow(lbl, self.remotele)
+        form.addRow(lbl, self.remotele)
+
+        lbl = QLabel(_('Branch:'))
+        self.branchle = QLineEdit()
+        if opts.get('branch'):
+            self.branchle.setText(hglib.tounicode(opts['branch']))
+        form.addRow(lbl, self.branchle)
 
         BB = QDialogButtonBox
         bb = QDialogButtonBox(BB.Ok|BB.Cancel)
 
     def accept(self):
         outopts = {}
-        for name, le in (('remotecmd', self.remotele),):
+        for name, le in (('remotecmd', self.remotele),
+                         ('branch', self.branchle)):
             outopts[name] = hglib.fromunicode(le.text()).strip()
 
         outopts['subrepos'] = self.subrepocb.isChecked()

File tortoisehg/hgqt/thgrepo.py

View file
  • Ignore whitespace
 import sys
 import shutil
 import tempfile
+import re
 
 from PyQt4.QtCore import *
 
 from tortoisehg.util.patchctx import patchctx
 
 _repocache = {}
+_kbfregex = re.compile(r'^\.kbf/')
+_lfregex = re.compile(r'^\.hglf/')
 
 if 'THGDEBUG' in os.environ:
     def dbgoutput(*args):
 def _extendrepo(repo):
     class thgrepository(repo.__class__):
 
-        def changectx(self, changeid):
-            '''Extends Mercurial's standard changectx() method to
+        def __getitem__(self, changeid):
+            '''Extends Mercurial's standard __getitem__() method to
             a) return a thgchangectx with additional methods
             b) return a patchctx if changeid is the name of an MQ
             unapplied patch
                     os.path.isabs(changeid) and os.path.isfile(changeid):
                 return genPatchContext(repo, changeid)
 
-            changectx = super(thgrepository, self).changectx(changeid)
+            changectx = super(thgrepository, self).__getitem__(changeid)
             changectx.__class__ = _extendchangectx(changectx)
             return changectx
 
             dest = tempfile.mktemp(ext+'.bak', root+'_', trashcan)
             shutil.copyfile(path, dest)
 
+        def isStandin(self, path):
+            if 'largefiles' in self.extensions():
+                if _lfregex.match(path):
+                    return True
+            if 'largefiles' in self.extensions() or 'kbfiles' in self.extensions():
+                if _kbfregex.match(path):
+                    return True
+            return False
+
+        def removeStandin(self, path):
+            if 'largefiles' in self.extensions():
+                path = _lfregex.sub('', path)
+            if 'largefiles' in self.extensions() or 'kbfiles' in self.extensions():
+                path = _kbfregex.sub('', path)
+            return path
+        
+        def bfStandin(self, path):
+            return '.kbf/' + path
+
+        def lfStandin(self, path):
+            return '.hglf/' + path
+        
     return thgrepository
 
 
                     summary += u' \u2026' # ellipsis ...
 
             return summary
+        
+        def hasStandin(self, file):
+            if 'largefiles' in self._repo.extensions():
+                if self._repo.lfStandin(file) in self.manifest():
+                    return True
+            elif 'largefiles' in self._repo.extensions() or 'kbfiles' in self._repo.extensions():
+                if self._repo.bfStandin(file) in self.manifest():
+                    return True
+            return False
 
+        def isStandin(self, path):
+            return self._repo.isStandin(path)
+        
+        def removeStandin(self, path):
+            return self._repo.removeStandin(path)
+        
+        def findStandin(self, file):
+            if 'largefiles' in self._repo.extensions():
+                if self._repo.lfStandin(file) in self.manifest():
+                    return self._repo.lfStandin(file)
+            return self._repo.bfStandin(file)
+                    
     return thgchangectx
 
-
-
 _pctxcache = {}
 def genPatchContext(repo, patchpath, rev=None):
     global _pctxcache
         raise
     else:
         f.close()
+
+def isBfStandin(path):
+    return _kbfregex.match(path)
+
+def isLfStandin(path):
+    return _lfregex.match(path)

File tortoisehg/hgqt/wctxactions.py

View file
  • Ignore whitespace
 # GNU General Public License version 2, incorporated herein by reference.
 
 import os
-import re
 
-from mercurial import util, error, merge, commands
-from tortoisehg.hgqt import qtlib, htmlui, visdiff
+from mercurial import util, error, merge, commands, extensions
+from tortoisehg.hgqt import qtlib, htmlui, visdiff, lfprompt
 from tortoisehg.util import hglib, shlib
 from tortoisehg.hgqt.i18n import _
 
         allactions.append(None)
         make(_('&Forget'), forget, frozenset('MAC!'), 'filedelete')
         make(_('&Add'), add, frozenset('I?'), 'fileadd')
+        if 'largefiles' in self.repo.extensions():
+            make(_('Add &Largefiles...'), addlf, frozenset('I?'))
+        elif 'kbfiles' in self.repo.extensions():
+            make(_('Add &Bfiles'), addlf, frozenset('I?'))
         make(_('&Detect Renames...'), guessRename, frozenset('A?!'),
              'detect_rename')
         make(_('&Ignore...'), ignore, frozenset('?'), 'ignore')
     return True
 
 def add(parent, ui, repo, files):
+    haslf = 'largefiles' in repo.extensions()
+    haskbf = 'kbfiles' in repo.extensions()
+    if haslf or haskbf:
+        result = lfprompt.promptForLfiles(parent, ui, repo, files, haskbf)
+        if not result:
+            return False
+        files, lfiles = result
+        for name, module in extensions.extensions():
+            if name == 'largefiles':
+                override_add = module.overrides.override_add
+                if files:
+                    override_add(commands.add, ui, repo, *files)
+                if lfiles:
+                    override_add(commands.add, ui, repo, large=True, *lfiles)
+                return True
+            if name == 'kbfiles':
+                override_add = module.bfsetup.override_add
+                if files:
+                    override_add(commands.add, ui, repo, *files)
+                if lfiles:
+                    override_add(commands.add, ui, repo, bf=True, *lfiles)
+                return True
     commands.add(ui, repo, *files)
     return True
 
+def addlf(parent, ui, repo, files):
+    for name, module in extensions.extensions():
+        if name == 'largefiles':
+            override_add = module.overrides.override_add
+            override_add(commands.add, ui, repo, large=True, *files)
+            return True
+        if name == 'kbfiles':
+            override_add = module.bfsetup.override_add
+            override_add(commands.add, ui, repo, bf=True, *files)
+            return True
+    return False
+
 def guessRename(parent, ui, repo, files):
     from tortoisehg.hgqt.guess import DetectRenameDialog
     dlg = DetectRenameDialog(repo, parent, *files)

File tortoisehg/hgqt/workbench.py

View file
  • Ignore whitespace
         rr.setObjectName('RepoRegistryView')
         rr.showMessage.connect(self.showMessage)
         rr.openRepo.connect(self.openRepo)
+        rr.removeRepo.connect(self.removeRepo)
         rr.hide()
         self.addDockWidget(Qt.LeftDockWidgetArea, rr)
         self.activeRepoChanged.connect(rr.setActiveTabRepo)
         root = hglib.fromunicode(root)
         self._openRepo(root, reuse)
 
+    def removeRepo(self, root):
+        """ Close tab if the repo is removed from reporegistry [unicode] """
+        root = hglib.fromunicode(root)
+        for i in xrange(self.repoTabsWidget.count()):
+            w = self.repoTabsWidget.widget(i)
+            if hglib.tounicode(w.repo.root) == os.path.normpath(root):
+                self.repoTabCloseRequested(i)
+                return
+
     @pyqtSlot(QString)
     def openLinkedRepo(self, path):
         self.showRepo(path)
     def explore(self):
         w = self.repoTabsWidget.currentWidget()
         if w:
-            QDesktopServices.openUrl(QUrl.fromLocalFile(w.repo.root))
+            qtlib.openlocalurl(w.repo.root)
 
     def terminal(self):
         w = self.repoTabsWidget.currentWidget()

File tortoisehg/util/hgversion.py

View file
  • Ignore whitespace
     vers = re.split(r'\.|-', v)[:2]
     if vers == reqver or len(vers) < 2:
         return
-    nextver = list(reqver)
-    nextver[1] = str(int(reqver[1])+1)
+    nextver = map(str, divmod(int(reqver[0]) * 10 + int(reqver[1]) + 1, 10))
     if vers == nextver:
         return
     return (('This version of TortoiseHg requires Mercurial '

File tortoisehg/util/patchctx.py

View file
  • Ignore whitespace
     def thgmqunappliedpatch(self):  return True
     def thgid(self):                return self._identity
 
+    # largefiles/kbfiles methods
+    def hasStandin(self, file):     return False
+    def isStandin(self, path):      return False
+    def removeStandin(self, path):  return path
+
     def longsummary(self):
         summary = hglib.tounicode(self.description())
         if self._repo.ui.configbool('tortoisehg', 'longsummary'):

File tortoisehg/util/settings.py

View file
  • Ignore whitespace
         f = util.atomictempfile(appname, 'wb', None)
         f.write(s)
         try:
-            f.rename()
+            f.close()
         except OSError:
             pass # silently ignore these errors
 

File tortoisehg/util/shlib.py

View file
  • Ignore whitespace
                 pass
         threading.Thread(target=start_browser).start()
 
-    def shell_notify(paths):
+    def shell_notify(paths, noassoc=False):
         try:
             from win32com.shell import shell, shellcon
             import pywintypes
             shell.SHChangeNotify(shellcon.SHCNE_UPDATEITEM,
                                  shellcon.SHCNF_IDLIST | shellcon.SHCNF_FLUSH,
                                  pidl, None)
+        if not noassoc:
+            shell.SHChangeNotify(shellcon.SHCNE_ASSOCCHANGED,
+                                 shellcon.SHCNF_FLUSH,
+                                 None, None)
 
     def update_thgstatus(ui, root, wait=False):
         '''Rewrite the file .hg/thgstatus
                 time.sleep(tdelta)
 
         repo = hg.repository(ui, root) # a fresh repo object is needed
+        repo.bfstatus = True
+        repo.lfstatus = True
         repostate = repo.status() # will update .hg/dirstate as a side effect
+        repo.bfstatus = False
+        repo.lfstatus = False
         modified, added, removed, deleted = repostate[:4]
 
         dirstatus = {}
                 f.write(s + dn + '\n')
                 ui.note("%s %s\n" % (s, dn))
             f.rename()
+        return update
 
 else:
-    def shell_notify(paths):
+    def shell_notify(paths, noassoc=False):
         if not paths:
             return
         notify = os.environ.get('THG_NOTIFY', '.tortoisehg/notify')

File tortoisehg/util/wconfig.py

View file
  • Ignore whitespace
         # normalize line endings
         for line in buf.getvalue().splitlines():
             f.write(line + '\n')
-        f.rename()
+        f.close()
     finally:
         del f  # unlink temp file