PageRenderTime 79ms CodeModel.GetById 47ms app.highlight 28ms RepoModel.GetById 1ms app.codeStats 0ms

/tortoisehg/hgqt/wctxactions.py

https://bitbucket.org/tortoisehg/hgtk/
Python | 330 lines | 287 code | 30 blank | 13 comment | 63 complexity | 859bdff4831fa191e0d12dc73d97e33d MD5 | raw file
  1# wctxactions.py - menu and responses for working copy files
  2#
  3# Copyright 2010 Steve Borho <steve@borho.org>
  4#
  5# This software may be used and distributed according to the terms of the
  6# GNU General Public License version 2, incorporated herein by reference.
  7
  8import os
  9import re
 10import subprocess
 11
 12from mercurial import util, error, merge, commands
 13from tortoisehg.hgqt import qtlib, htmlui, visdiff
 14from tortoisehg.util import hglib, shlib
 15from tortoisehg.hgqt.i18n import _
 16
 17from PyQt4.QtCore import Qt
 18from PyQt4.QtGui import QAction, QMenu, QMessageBox, QFileDialog, QDialog
 19
 20def wctxactions(parent, point, repo, selrows):
 21    if not selrows:
 22        return
 23    alltypes = set()
 24    for t, path in selrows:
 25        alltypes |= t
 26
 27    def make(text, func, types, icon=None):
 28        files = [f for t, f in selrows if t & types]
 29        if not files:
 30            return None
 31        action = menu.addAction(text)
 32        if icon:
 33            action.setIcon(icon)
 34        action.args = (func, parent, files, repo)
 35        action.run = lambda: run(*action.args)
 36        action.triggered.connect(action.run)
 37        return action
 38
 39    if hasattr(parent, 'contextmenu'):
 40        menu = parent.contextmenu
 41        menu.clear()
 42    else:
 43        menu = QMenu(parent)
 44        parent.contextmenu = menu
 45    make(_('&Visual Diff'), vdiff, frozenset('MAR!'))
 46    make(_('Edit'), edit, frozenset('MACI?'))
 47    make(_('View missing'), viewmissing, frozenset('R!'))
 48    if len(repo.parents()) > 1:
 49        make(_('View other'), viewother, frozenset('MA'))
 50    menu.addSeparator()
 51    make(_('&Revert'), revert, frozenset('MAR!'))
 52    make(_('&Add'), add, frozenset('R'))
 53    menu.addSeparator()
 54    make(_('File History'), log, frozenset('MARC!'))
 55    make(_('&Annotate'), annotate, frozenset('MARC!'))
 56    menu.addSeparator()
 57    make(_('&Forget'), forget, frozenset('MAC!'))
 58    make(_('&Add'), add, frozenset('I?'))
 59    make(_('&Detect Renames...'), guessRename, frozenset('A?!'))
 60    make(_('&Ignore'), ignore, frozenset('?'))
 61    make(_('Remove versioned'), remove, frozenset('C'))
 62    make(_('&Delete unversioned'), delete, frozenset('?I'))
 63    if len(selrows) == 1:
 64        menu.addSeparator()
 65        t, path = selrows[0]
 66        wctx = repo[None]
 67        if t & frozenset('?') and wctx.deleted():
 68            rmenu = QMenu(_('Was renamed from'))
 69            for d in wctx.deleted()[:15]:
 70                def mkaction(deleted):
 71                    a = rmenu.addAction(hglib.tounicode(deleted))
 72                    a.triggered.connect(lambda: renamefromto(repo, deleted, path))
 73                mkaction(d)
 74            menu.addMenu(rmenu)
 75        else:
 76            make(_('&Copy...'), copy, frozenset('MC'))
 77            make(_('Rename...'), rename, frozenset('MC'))
 78    menu.addSeparator()
 79    make(_('Mark unresolved'), unmark, frozenset('r'))
 80    make(_('Mark resolved'), mark, frozenset('u'))
 81    f = make(_('Restart Merge...'), resolve, frozenset('u'))
 82    if f:
 83        files = [f for t, f in selrows if 'u' in t]
 84        rmenu = QMenu(_('Restart merge with'))
 85        for tool in hglib.mergetools(repo.ui):
 86            def mkaction(rtool):
 87                a = rmenu.addAction(hglib.tounicode(rtool))
 88                a.triggered.connect(lambda: resolve_with(rtool, repo, files))
 89            mkaction(tool)
 90        menu.addMenu(rmenu)
 91    return menu.exec_(point)
 92
 93def run(func, parent, files, repo):
 94    'run wrapper for all action methods'
 95    hu = htmlui.htmlui()
 96    name = func.__name__.title()
 97    notify = False
 98    cwd = os.getcwd()
 99    try:
100        os.chdir(repo.root)
101        try:
102            # All operations should quietly succeed.  Any error should
103            # result in a message box
104            notify = func(parent, hu, repo, files)
105            o, e = hu.getdata()
106            if e:
107                QMessageBox.warning(parent, name + _(' errors'), str(e))
108            elif o:
109                QMessageBox.information(parent, name + _(' output'), str(o))
110            elif notify:
111                wfiles = [repo.wjoin(x) for x in files]
112                shlib.shell_notify(wfiles)
113        except (IOError, OSError), e:
114            err = hglib.tounicode(str(e))
115            QMessageBox.critical(parent, name + _(' Aborted'), err)
116        except util.Abort, e:
117            if e.hint:
118                err = _('%s (hint: %s)') % (hglib.tounicode(str(e)),
119                                            hglib.tounicode(e.hint))
120            else:
121                err = hglib.tounicode(str(e))
122            QMessageBox.critical(parent, name + _(' Aborted'), err)
123        except (error.LookupError), e:
124            err = hglib.tounicode(str(e))
125            QMessageBox.critical(parent, name + _(' Aborted'), err)
126        except NotImplementedError:
127            QMessageBox.critical(parent, name + _(' not implemented'), 
128                    'Please add it :)')
129    finally:
130        os.chdir(cwd)
131    return notify
132
133def renamefromto(repo, deleted, unknown):
134    repo[None].copy(deleted, unknown)
135    repo[None].remove([deleted], unlink=False) # !->R
136
137def vdiff(parent, ui, repo, files):
138    dlg = visdiff.visualdiff(ui, repo, files, {})
139    if dlg:
140        dlg.exec_()
141
142def edit(parent, ui, repo, files, lineno=None, search=None):
143    files = [util.shellquote(util.localpath(f)) for f in files]
144    editor = ui.config('tortoisehg', 'editor')
145    assert len(files) == 1 or lineno == None
146    if editor:
147        try:
148            regexp = re.compile('\[([^\]]*)\]')
149            expanded = []
150            pos = 0
151            for m in regexp.finditer(editor):
152                expanded.append(editor[pos:m.start()-1])
153                phrase = editor[m.start()+1:m.end()-1]
154                pos=m.end()+1
155                if '$LINENUM' in phrase:
156                    if lineno is None:
157                        # throw away phrase
158                        continue
159                    phrase = phrase.replace('$LINENUM', str(lineno))
160                elif '$SEARCH' in phrase:
161                    if search is None:
162                        # throw away phrase
163                        continue
164                    phrase = phrase.replace('$SEARCH', search)
165                if '$FILE' in phrase:
166                    phrase = phrase.replace('$FILE', files[0])
167                    files = []
168                expanded.append(phrase)
169            expanded.append(editor[pos:])
170            cmdline = ' '.join(expanded + files)
171        except ValueError, e:
172            # '[' or ']' not found
173            cmdline = ' '.join([editor] + files)
174        except TypeError, e:
175            # variable expansion failed
176            cmdline = ' '.join([editor] + files)
177    else:
178        editor = os.environ.get('HGEDITOR') or ui.config('ui', 'editor') or \
179                 os.environ.get('EDITOR', 'vi')
180        cmdline = ' '.join([editor] + files)
181    if os.path.basename(editor) in ('vi', 'vim', 'hgeditor'):
182        res = QMessageBox.critical(parent,
183                       _('No visual editor configured'),
184                       _('Please configure a visual editor.'))
185        from tortoisehg.hgqt.settings import SettingsDialog
186        dlg = SettingsDialog(False, focus='tortoisehg.editor')
187        dlg.exec_()
188        return
189
190    cmdline = util.quotecommand(cmdline)
191    try:
192        subprocess.Popen(cmdline, shell=True, creationflags=visdiff.openflags,
193                         stderr=None, stdout=None, stdin=None)
194    except (OSError, EnvironmentError), e:
195        QMessageBox.warning(parent,
196                 _('Editor launch failure'),
197                 _('%s : %s') % (cmd, str(e)))
198    return False
199
200
201def viewmissing(parent, ui, repo, files):
202    base, _ = visdiff.snapshot(repo, files, repo['.'])
203    edit(parent, ui, repo, [os.path.join(base, f) for f in files])
204
205def viewother(parent, ui, repo, files):
206    wctx = repo[None]
207    assert bool(wctx.p2())
208    base, _ = visdiff.snapshot(repo, files, wctx.p2())
209    edit(parent, ui, repo, [os.path.join(base, f) for f in files])
210
211def revert(parent, ui, repo, files):
212    revertopts = {'date': None, 'rev': '.'}
213
214    if len(repo.parents()) > 1:
215        res = qtlib.CustomPrompt(
216                _('Uncommited merge - please select a parent revision'),
217                _('Revert files to local or other parent?'), parent,
218                (_('&Local'), _('&Other'), _('Cancel')), 0, 2, files).run()
219        if res == 0:
220            revertopts['rev'] = repo[None].p1().rev()
221        elif res == 1:
222            revertopts['rev'] = repo[None].p2().rev()
223        else:
224            return
225        commands.revert(ui, repo, *files, **revertopts)
226    else:
227        res = qtlib.CustomPrompt(
228                 _('Confirm Revert'),
229                 _('Revert changes to files?'), parent,
230                 (_('&Yes (backup changes)'), _('Yes (&discard changes)'),
231                  _('Cancel')), 2, 2, files).run()
232        if res == 2:
233            return False
234        if res == 1:
235            revertopts['no_backup'] = True
236        commands.revert(ui, repo, *files, **revertopts)
237        return True
238
239def log(parent, ui, repo, files):
240    from tortoisehg.hgqt.workbench import run
241    from tortoisehg.hgqt.run import qtrun
242    opts = {'root': repo.root}
243    qtrun(run, repo.ui, *files, **opts)
244    return False
245
246def annotate(parent, ui, repo, files):
247    from tortoisehg.hgqt.annotate import run
248    from tortoisehg.hgqt.run import qtrun
249    opts = {'root': repo.root}
250    qtrun(run, repo.ui, *files, **opts)
251    return False
252
253def forget(parent, ui, repo, files):
254    commands.forget(ui, repo, *files)
255    return True
256
257def add(parent, ui, repo, files):
258    commands.add(ui, repo, *files)
259    return True
260
261def guessRename(parent, ui, repo, files):
262    from tortoisehg.hgqt.guess import DetectRenameDialog
263    dlg = DetectRenameDialog(repo, parent, *files)
264    def matched():
265        ret = True
266    ret = False
267    dlg.matchAccepted.connect(matched)
268    dlg.finished.connect(dlg.deleteLater)
269    dlg.exec_()
270    return ret
271
272def ignore(parent, ui, repo, files):
273    from tortoisehg.hgqt.hgignore import HgignoreDialog
274    dlg = HgignoreDialog(repo, parent, *files)
275    dlg.finished.connect(dlg.deleteLater)
276    return dlg.exec_() == QDialog.Accepted
277
278def remove(parent, ui, repo, files):
279    commands.remove(ui, repo, *files)
280    return True
281
282def delete(parent, ui, repo, files):
283    res = qtlib.CustomPrompt(
284            _('Confirm Delete Unrevisioned'),
285            _('Delete the following unrevisioned files?'),
286            parent, (_('&Delete'), _('Cancel')), 1, 1, files).run()
287    if res == 1:
288        return
289    for wfile in files:
290        os.unlink(wfile)
291    return True
292
293def copy(parent, ui, repo, files):
294    assert len(files) == 1
295    wfile = repo.wjoin(files[0])
296    fd = QFileDialog(parent)
297    fname = fd.getSaveFileName(parent, _('Copy file to'), wfile)
298    if not fname:
299        return
300    fname = hglib.fromunicode(fname)
301    wfiles = [wfile, fname]
302    commands.copy(ui, repo, *wfiles)
303    return True
304
305def rename(parent, ui, repo, files):
306    # needs rename dialog
307    raise NotImplementedError()
308
309def unmark(parent, ui, repo, files):
310    ms = merge.mergestate(repo)
311    for wfile in files:
312        ms.mark(wfile, 'u')
313    ms.commit()
314    return True
315
316def mark(parent, ui, repo, files):
317    ms = merge.mergestate(repo)
318    for wfile in files:
319        ms.mark(wfile, 'r')
320    ms.commit()
321    return True
322
323def resolve(parent, ui, repo, files):
324    commands.resolve(ui, repo, *files)
325    return True
326
327def resolve_with(tool, repo, files):
328    opts = {'tool': tool}
329    commands.resolve(repo.ui, repo, *files, **opts)
330    return True