/contrib/nautilus-thg.py
https://bitbucket.org/tortoisehg/hgtk/ · Python · 380 lines · 309 code · 43 blank · 28 comment · 88 complexity · 0b1b273e04cbbe35c4de2b8598973377 MD5 · raw file
- # TortoiseHg plugin for Nautilus
- #
- # Copyright 2007 Steve Borho
- #
- # This software may be used and distributed according to the terms of the
- # GNU General Public License version 2, incorporated herein by reference.
- import gtk
- import gobject
- import nautilus
- import gnomevfs
- import os
- import sys
- thg_main = 'thg'
- idstr_prefix = 'HgNautilus2'
- if gtk.gtk_version < (2, 14, 0):
- # at least on 2.12.12, gtk widgets can be confused by control
- # char markups (like ""), so use cgi.escape instead
- from cgi import escape as markup_escape_text
- else:
- from gobject import markup_escape_text
- try:
- from mercurial import demandimport
- except ImportError:
- # workaround to use user's local python libs
- userlib = os.path.expanduser('~/lib/python')
- if os.path.exists(userlib) and userlib not in sys.path:
- sys.path.append(userlib)
- from mercurial import demandimport
- demandimport.enable()
- import subprocess
- import urllib
- from mercurial import hg, ui, match, util, error
- from mercurial.node import short
- def _thg_path():
- # check if nautilus-thg.py is a symlink first
- pfile = __file__
- if pfile.endswith('.pyc'):
- pfile = pfile[:-1]
- path = os.path.dirname(os.path.dirname(os.path.realpath(pfile)))
- thgpath = os.path.normpath(path)
- testpath = os.path.join(thgpath, 'tortoisehg')
- if os.path.isdir(testpath) and thgpath not in sys.path:
- sys.path.insert(0, thgpath)
- _thg_path()
- from tortoisehg.util import paths, debugthg, cachethg
- if debugthg.debug('N'):
- debugf = debugthg.debugf
- else:
- debugf = debugthg.debugf_No
- nofilecmds = 'about serve synch repoconfig userconfig merge unmerge'.split()
- class HgExtensionDefault:
- def __init__(self):
- self.scanStack = []
- self.allvfs = {}
- self.inv_dirs = set()
- from tortoisehg.util import menuthg
- self.hgtk = paths.find_in_path(thg_main)
- self.menu = menuthg.menuThg()
- self.notify = os.path.expanduser('~/.tortoisehg/notify')
- try:
- f = open(self.notify, 'w')
- f.close()
- ds_uri = gnomevfs.get_uri_from_local_path(self.notify)
- self.gmon = gnomevfs.monitor_add(ds_uri,
- gnomevfs.MONITOR_FILE, self.notified)
- except (gnomevfs.NotSupportedError, IOError), e:
- debugf('no notification because of %s', e)
- self.notify = ''
- def icon(self, iname):
- return paths.get_tortoise_icon(iname)
- def get_path_for_vfs_file(self, vfs_file, store=True):
- if vfs_file.get_uri_scheme() != 'file':
- return None
- path = urllib.unquote(vfs_file.get_uri()[7:])
- if vfs_file.is_gone():
- self.allvfs.pop(path, '')
- return None
- if store:
- self.allvfs[path] = vfs_file
- return path
- def get_vfs(self, path):
- vfs = self.allvfs.get(path, None)
- if vfs and vfs.is_gone():
- del self.allvfs[path]
- return None
- return vfs
- def get_repo_for_path(self, path):
- '''
- Find mercurial repository for vfs_file
- Returns hg.repo
- '''
- p = paths.find_root(path)
- if not p:
- return None
- try:
- return hg.repository(ui.ui(), path=p)
- except error.RepoError:
- return None
- except StandardError, e:
- debugf(e)
- return None
- def run_dialog(self, menuitem, hgtkcmd, cwd = None):
- '''
- hgtkcmd - hgtk subcommand
- '''
- if cwd: #bg
- self.files = []
- else:
- cwd = self.cwd
- repo = self.get_repo_for_path(cwd)
- cmdopts = [sys.executable, self.hgtk, hgtkcmd]
- if hgtkcmd not in nofilecmds and self.files:
- pipe = subprocess.PIPE
- cmdopts += ['--listfile', '-']
- else:
- pipe = None
- proc = subprocess.Popen(cmdopts, cwd=cwd, stdin=pipe, shell=False)
- if pipe:
- proc.stdin.write('\n'.join(self.files))
- proc.stdin.close()
- def buildMenu(self, vfs_files, bg):
- '''Build menu'''
- self.pos = 0
- self.files = []
- files = []
- for vfs_file in vfs_files:
- f = self.get_path_for_vfs_file(vfs_file)
- if f:
- files.append(f)
- if not files:
- return
- if bg or len(files) == 1 and vfs_files[0].is_directory():
- cwd = files[0]
- files = []
- else:
- cwd = os.path.dirname(files[0])
- repo = self.get_repo_for_path(cwd)
- if repo:
- menus = self.menu.get_commands(repo, cwd, files)
- self.files = files
- else:
- menus = self.menu.get_norepo_commands(cwd, files)
- self.cwd = cwd
- return self._buildMenu(menus)
- def _buildMenu(self, menus):
- '''Build one level of a menu'''
- items = []
- if self.files:
- passcwd = None
- else: #bg
- passcwd = self.cwd
- for menu_info in menus:
- idstr = '%s::%02d%s' % (idstr_prefix ,self.pos, menu_info.hgcmd)
- self.pos += 1
- if menu_info.isSep():
- # can not insert a separator till now
- pass
- elif menu_info.isSubmenu():
- if hasattr(nautilus, 'Menu'):
- item = nautilus.MenuItem(idstr, menu_info.menutext,
- menu_info.helptext)
- submenu = nautilus.Menu()
- item.set_submenu(submenu)
- for subitem in self._buildMenu(menu_info.get_menus()):
- submenu.append_item(subitem)
- items.append(item)
- else: #submenu not suported
- for subitem in self._buildMenu(menu_info.get_menus()):
- items.append(subitem)
- else:
- if menu_info.state:
- item = nautilus.MenuItem(idstr,
- menu_info.menutext,
- menu_info.helptext,
- self.icon(menu_info.icon))
- item.connect('activate', self.run_dialog, menu_info.hgcmd,
- passcwd)
- items.append(item)
- return items
- def get_background_items(self, window, vfs_file):
- '''Build context menu for current directory'''
- if vfs_file and self.menu:
- return self.buildMenu([vfs_file], True)
- else:
- self.files = []
- def get_file_items(self, window, vfs_files):
- '''Build context menu for selected files/directories'''
- if vfs_files and self.menu:
- return self.buildMenu(vfs_files, False)
- def get_columns(self):
- return nautilus.Column(idstr_prefix + "::80hg_status",
- "hg_status",
- "Hg Status",
- "Version control status"),
- def _get_file_status(self, localpath, repo=None):
- cachestate = cachethg.get_state(localpath, repo)
- cache2state = {cachethg.UNCHANGED: ('default', 'clean'),
- cachethg.ADDED: ('new', 'added'),
- cachethg.MODIFIED: ('important', 'modified'),
- cachethg.UNKNOWN: (None, 'unrevisioned'),
- cachethg.IGNORED: ('noread', 'ignored'),
- cachethg.NOT_IN_REPO: (None, 'unrevisioned'),
- cachethg.ROOT: ('generic', 'root'),
- cachethg.UNRESOLVED: ('danger', 'unresolved')}
- emblem, status = cache2state.get(cachestate, (None, '?'))
- return emblem, status
- def notified(self, mon_uri=None, event_uri=None, event=None):
- debugf('notified from hgtk, %s', event, level='n')
- f = open(self.notify, 'a+')
- files = None
- try:
- files = [line[:-1] for line in f if line]
- if files:
- f.truncate(0)
- finally:
- f.close()
- if not files:
- return
- root = os.path.commonprefix(files)
- root = paths.find_root(root)
- if root:
- self.invalidate(files, root)
- def invalidate(self, paths, root = ''):
- started = bool(self.inv_dirs)
- if cachethg.cache_pdir == root and root not in self.inv_dirs:
- cachethg.overlay_cache.clear()
- self.inv_dirs.update([os.path.dirname(root), '/', ''])
- for path in paths:
- path = os.path.join(root, path)
- while path not in self.inv_dirs:
- self.inv_dirs.add(path)
- path = os.path.dirname(path)
- if cachethg.cache_pdir == path:
- cachethg.overlay_cache.clear()
- if started:
- return
- if len(paths) > 1:
- self._invalidate_dirs()
- else:
- #group invalidation of directories
- gobject.timeout_add(200, self._invalidate_dirs)
- def _invalidate_dirs(self):
- for path in self.inv_dirs:
- vfs = self.get_vfs(path)
- if vfs:
- vfs.invalidate_extension_info()
- self.inv_dirs.clear()
- # property page borrowed from http://www.gnome.org/~gpoo/hg/nautilus-hg/
- def __add_row(self, row, label_item, label_value):
- label = gtk.Label(label_item)
- label.set_use_markup(True)
- label.set_alignment(1, 0)
- self.table.attach(label, 0, 1, row, row + 1, gtk.FILL, gtk.FILL, 0, 0)
- label.show()
- label = gtk.Label(label_value)
- label.set_use_markup(True)
- label.set_alignment(0, 1)
- label.show()
- self.table.attach(label, 1, 2, row, row + 1, gtk.FILL, 0, 0, 0)
- def get_property_pages(self, vfs_files):
- if len(vfs_files) != 1:
- return
- file = vfs_files[0]
- path = self.get_path_for_vfs_file(file)
- if path is None or file.is_directory():
- return
- repo = self.get_repo_for_path(path)
- if repo is None:
- return
- localpath = path[len(repo.root)+1:]
- emblem, status = self._get_file_status(path, repo)
- # Get the information from Mercurial
- ctx = repo['.']
- try:
- fctx = ctx.filectx(localpath)
- rev = fctx.filelog().linkrev(fctx.filerev())
- except:
- rev = ctx.rev()
- ctx = repo.changectx(rev)
- node = short(ctx.node())
- date = util.datestr(ctx.date(), '%Y-%m-%d %H:%M:%S %1%2')
- parents = '\n'.join([short(p.node()) for p in ctx.parents()])
- description = ctx.description()
- user = ctx.user()
- user = markup_escape_text(user)
- tags = ', '.join(ctx.tags())
- branch = ctx.branch()
- self.property_label = gtk.Label('Mercurial')
- self.table = gtk.Table(7, 2, False)
- self.table.set_border_width(5)
- self.table.set_row_spacings(5)
- self.table.set_col_spacings(5)
- self.__add_row(0, '<b>Status</b>:', status)
- self.__add_row(1, '<b>Last-Commit-Revision</b>:', str(rev))
- self.__add_row(2, '<b>Last-Commit-Description</b>:', description)
- self.__add_row(3, '<b>Last-Commit-Date</b>:', date)
- self.__add_row(4, '<b>Last-Commit-User</b>:', user)
- if tags:
- self.__add_row(5, '<b>Tags</b>:', tags)
- if branch != 'default':
- self.__add_row(6, '<b>Branch</b>:', branch)
- self.table.show()
- return nautilus.PropertyPage("MercurialPropertyPage::status",
- self.property_label, self.table),
- class HgExtensionIcons(HgExtensionDefault):
- def update_file_info(self, file):
- '''Queue file for emblem and hg status update'''
- self.scanStack.append(file)
- if len(self.scanStack) == 1:
- gobject.idle_add(self.fileinfo_on_idle)
- def fileinfo_on_idle(self):
- '''Update emblem and hg status for files when there is time'''
- if not self.scanStack:
- return False
- try:
- vfs_file = self.scanStack.pop()
- path = self.get_path_for_vfs_file(vfs_file, False)
- if not path:
- return True
- oldvfs = self.get_vfs(path)
- if oldvfs and oldvfs != vfs_file:
- #file has changed on disc (not invalidated)
- self.get_path_for_vfs_file(vfs_file) #save new vfs
- self.invalidate([os.path.dirname(path)])
- emblem, status = self._get_file_status(path)
- if emblem is not None:
- vfs_file.add_emblem(emblem)
- vfs_file.add_string_attribute('hg_status', status)
- except StandardError, e:
- debugf(e)
- return True
- if ui.ui().configbool("tortoisehg", "overlayicons", default = True):
- class HgExtension(HgExtensionIcons, nautilus.MenuProvider, nautilus.ColumnProvider, nautilus.PropertyPageProvider, nautilus.InfoProvider):
- pass
- else:
- class HgExtension(HgExtensionDefault, nautilus.MenuProvider, nautilus.ColumnProvider, nautilus.PropertyPageProvider):
- pass