diff --git a/contrib/plan9/9diff b/contrib/plan9/9diff new file mode 100644 --- /dev/null +++ b/contrib/plan9/9diff @@ -0,0 +1,42 @@ +#!/bin/rc +# 9diff - Mercurial extdiff wrapper for diff(1) + +rfork e + +fn getfiles{ + cd $1 && \ + for(f in `{du -as | awk '{print $2}'}) + test -f $f && echo `{cleanname $f} +} + +fn usage{ + echo >[1=2] usage: 9diff [diff options] parent child root + exit usage +} + +opts=() +while(~ $1 -*){ + opts=($opts $1) + shift +} +if(! ~ $#* 3) + usage + +# extdiff will set the parent and child to a single file if there is +# only one change. If there are multiple changes, directories will be +# set. diff(1) does not cope particularly with directories; instead we +# do the recursion ourselves and diff each file individually. +if(test -f $1) + diff $opts $1 $2 +if not{ + # extdiff will create a snapshot of the working copy to prevent + # conflicts during the diff. We circumvent this behavior by + # diffing against the repository root to produce plumbable + # output. This is antisocial. + for(f in `{sort -u <{getfiles $1} <{getfiles $2}}){ + file1=$1/$f; test -f $file1 || file1=/dev/null + file2=$3/$f; test -f $file2 || file2=/dev/null + diff $opts $file1 $file2 + } +} +exit '' diff --git a/contrib/plan9/README b/contrib/plan9/README new file mode 100644 --- /dev/null +++ b/contrib/plan9/README @@ -0,0 +1,39 @@ +Mercurial for Plan 9 from Bell Labs +=================================== + +This directory contains support for Mercurial on Plan 9 from Bell Labs +platforms. It is assumed that the version of Python running on these +systems supports the ANSI/POSIX Environment (APE). At the time of this +writing, the bichued/python port is the most commonly installed version +of Python on these platforms. If a native port of Python is ever made, +some minor modification will need to be made to support some of the more +esoteric requirements of the platform rather than those currently made +(cf. posix.py). + +By default, installations will have the factotum extension enabled; this +extension permits factotum(4) to act as an authentication agent for +HTTP repositories. Additionally, an extdiff command named 9diff is +enabled which generates diff(1) compatible output suitable for use with +the plumber(4). + +Commit messages are plumbed using E if no editor is defined; users must +update the plumbed file to continue, otherwise the hg process must be +interrupted. + +Some work remains with regard to documentation. Section 5 manual page +references for hgignore and hgrc need to be re-numbered to section 6 (file +formats) and a new man page writer should be written to support the +Plan 9 man macro set. Until these issues can be resolved, manual pages +are elided from the installation. + +Basic install: + + % mk install # do a system-wide install + % hg debuginstall # sanity-check setup + % hg # see help + +A proto(2) file is included in this directory as an example of how a +binary distribution could be packaged, ostensibly with contrib(1). + +See http://mercurial.selenic.com/ for detailed installation +instructions, platform-specific notes, and Mercurial user information. diff --git a/contrib/plan9/hgrc.d/9diff.rc b/contrib/plan9/hgrc.d/9diff.rc new file mode 100644 --- /dev/null +++ b/contrib/plan9/hgrc.d/9diff.rc @@ -0,0 +1,5 @@ +[extensions] +extdiff = + +[extdiff] +9diff = 9diff -cm $parent $child $root diff --git a/contrib/plan9/hgrc.d/factotum.rc b/contrib/plan9/hgrc.d/factotum.rc new file mode 100644 --- /dev/null +++ b/contrib/plan9/hgrc.d/factotum.rc @@ -0,0 +1,2 @@ +[extensions] +factotum = diff --git a/contrib/plan9/mkfile b/contrib/plan9/mkfile new file mode 100644 --- /dev/null +++ b/contrib/plan9/mkfile @@ -0,0 +1,37 @@ +APE=/sys/src/ape +<$APE/config + +PYTHON=python +PYTHONBIN=/rc/bin +SH=ape/psh + +PURE=--pure +ROOT=../.. + +# This is slightly underhanded; Plan 9 does not support GNU gettext nor +# does it support dynamically loaded extension modules. We work around +# this by calling build_py and build_scripts directly; this avoids +# additional platform hacks in setup.py. +build:VQ: + @{ + cd $ROOT + $SH -c '$PYTHON setup.py $PURE build_py build_scripts' + } + +clean:VQ: + @{ + cd $ROOT + $SH -c '$PYTHON setup.py $PURE clean --all' + } + +install:VQ: build + @{ + cd $ROOT + $SH -c '$PYTHON setup.py $PURE install \ + --install-scripts $PYTHONBIN \ + --skip-build' \ + --force + } + mkdir -p /lib/mercurial/hgrc.d + dircp hgrc.d /lib/mercurial/hgrc.d + cp 9diff /rc/bin diff --git a/contrib/plan9/proto b/contrib/plan9/proto new file mode 100644 --- /dev/null +++ b/contrib/plan9/proto @@ -0,0 +1,20 @@ +lib - sys sys + mercurial - sys sys + hgrc.d - sys sys + 9diff.rc - sys sys + factotum.rc - sys sys +rc - sys sys + bin - sys sys + 9diff - sys sys + hg - sys sys +sys - sys sys + lib - sys sys + python - sys sys + lib - sys sys + python2.5 - sys sys + site-packages - sys sys + hgext - sys sys + + - sys sys + mercurial - sys sys + + - sys sys + mercurial-2.1.1-py2.5.egg-info - sys sys diff --git a/hgext/factotum.py b/hgext/factotum.py new file mode 100644 --- /dev/null +++ b/hgext/factotum.py @@ -0,0 +1,120 @@ +# factotum.py - Plan 9 factotum integration for Mercurial +# +# Copyright (C) 2012 Steven Stallion +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +'''http authentication with factotum + +This extension allows the factotum facility on Plan 9 from Bell Labs platforms +to provide authentication information for HTTP access. Configuration entries +specified in the auth section as well as authentication information provided +in the repository URL are fully supported. If no prefix is specified, a value +of ``*`` will be assumed. + +By default, keys are specified as:: + + proto=pass service=hg prefix= user= !password= + +If the factotum extension is unable to read the required key, one will be +requested interactively. + +A configuration section is available to customize runtime behavior. By +default, these entries are:: + + [factotum] + mount = /mnt/factotum + path = /bin/auth/factotum + service = hg + +The mount entry defines the mount point for the factotum file service. The +path entry defines the full path to the factotum binary. Lastly, the service +entry controls the service name used when reading keys. + +''' + +from mercurial.i18n import _ +from mercurial.url import passwordmgr +from mercurial import httpconnection, urllib2, util +import os + +ERRMAX = 128 + +def auth_getkey(self, params): + if not self.ui.interactive(): + raise util.Abort(_('factotum not interactive')) + if 'user=' not in params: + params = '%s user?' % params + params = '%s !password?' % params + os.system("%s -g '%s'" % (_path, params)) + +def auth_getuserpasswd(self, getkey, params): + params = 'proto=pass %s' % params + while True: + fd = os.open('%s/rpc' % _mount, os.O_RDWR) + try: + try: + os.write(fd, 'start %s' % params) + l = os.read(fd, ERRMAX).split() + if l[0] == 'ok': + os.write(fd, 'read') + l = os.read(fd, ERRMAX).split() + if l[0] == 'ok': + return l[1:] + except (OSError, IOError): + raise util.Abort(_('factotum not responding')) + finally: + os.close(fd) + getkey(self, params) + +def monkeypatch_method(cls): + def decorator(func): + setattr(cls, func.__name__, func) + return func + return decorator + +@monkeypatch_method(passwordmgr) +def find_user_password(self, realm, authuri): + user, passwd = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password( + self, realm, authuri) + if user and passwd: + self._writedebug(user, passwd) + return (user, passwd) + + prefix = '' + res = httpconnection.readauthforuri(self.ui, authuri, user) + if res: + _, auth = res + prefix = auth.get('prefix') + user, passwd = auth.get('username'), auth.get('password') + if not user or not passwd: + if not prefix: + prefix = '*' + params = 'service=%s prefix=%s' % (_service, prefix) + if user: + params = '%s user=%s' % (params, user) + user, passwd = auth_getuserpasswd(self, auth_getkey, params) + + self.add_password(realm, authuri, user, passwd) + self._writedebug(user, passwd) + return (user, passwd) + +def uisetup(ui): + global _mount + _mount = ui.config('factotum', 'mount', '/mnt/factotum') + global _path + _path = ui.config('factotum', 'path', '/bin/auth/factotum') + global _service + _service = ui.config('factotum', 'service', 'hg') diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt --- a/mercurial/help/config.txt +++ b/mercurial/help/config.txt @@ -28,16 +28,17 @@ alphabetical order, later ones overridin paths are given below, settings from earlier paths override later ones. -| (Unix, Windows) ``/.hg/hgrc`` +| (All) ``/.hg/hgrc`` Per-repository configuration options that only apply in a particular repository. This file is not version-controlled, and will not get transferred during a "clone" operation. Options in this file override options in all other configuration files. On - Unix, most of this file will be ignored if it doesn't belong to a - trusted user or to a trusted group. See the documentation for the - ``[trusted]`` section below for more details. + Plan 9 and Unix, most of this file will be ignored if it doesn't + belong to a trusted user or to a trusted group. See the documentation + for the ``[trusted]`` section below for more details. +| (Plan 9) ``$home/lib/hgrc`` | (Unix) ``$HOME/.hgrc`` | (Windows) ``%USERPROFILE%\.hgrc`` | (Windows) ``%USERPROFILE%\Mercurial.ini`` @@ -50,6 +51,8 @@ ones. directory. Options in these files override per-system and per-installation options. +| (Plan 9) ``/lib/mercurial/hgrc`` +| (Plan 9) ``/lib/mercurial/hgrc.d/*.rc`` | (Unix) ``/etc/mercurial/hgrc`` | (Unix) ``/etc/mercurial/hgrc.d/*.rc`` @@ -58,6 +61,8 @@ ones. executed by any user in any directory. Options in these files override per-installation options. +| (Plan 9) ``/lib/mercurial/hgrc`` +| (Plan 9) ``/lib/mercurial/hgrc.d/*.rc`` | (Unix) ``/etc/mercurial/hgrc`` | (Unix) ``/etc/mercurial/hgrc.d/*.rc`` diff --git a/mercurial/posix.py b/mercurial/posix.py --- a/mercurial/posix.py +++ b/mercurial/posix.py @@ -333,6 +333,9 @@ def findexe(command): if os.sep in command: return findexisting(command) + if sys.platform == 'plan9': + return findexisting(os.path.join('/bin', command)) + for path in os.environ.get('PATH', '').split(os.pathsep): executable = findexisting(os.path.join(path, command)) if executable is not None: diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py --- a/mercurial/scmutil.py +++ b/mercurial/scmutil.py @@ -436,15 +436,22 @@ if os.name != 'nt': def systemrcpath(): path = [] + if sys.platform == 'plan9': + root = '/lib/mercurial' + else: + root = '/etc/mercurial' # old mod_python does not set sys.argv if len(getattr(sys, 'argv', [])) > 0: p = os.path.dirname(os.path.dirname(sys.argv[0])) - path.extend(rcfiles(os.path.join(p, 'etc/mercurial'))) - path.extend(rcfiles('/etc/mercurial')) + path.extend(rcfiles(os.path.join(p, root))) + path.extend(rcfiles(root)) return path def userrcpath(): - return [os.path.expanduser('~/.hgrc')] + if sys.platform == 'plan9': + return [os.environ['home'] + '/lib/hgrc'] + else: + return [os.path.expanduser('~/.hgrc')] else: diff --git a/mercurial/ui.py b/mercurial/ui.py --- a/mercurial/ui.py +++ b/mercurial/ui.py @@ -687,10 +687,17 @@ class ui(object): def geteditor(self): '''return editor to use''' + if sys.platform == 'plan9': + # vi is the MIPS instruction simulator on Plan 9. We + # instead default to E to plumb commit messages to + # avoid confusion. + editor = 'E' + else: + editor = 'vi' return (os.environ.get("HGEDITOR") or self.config("ui", "editor") or os.environ.get("VISUAL") or - os.environ.get("EDITOR", "vi")) + os.environ.get("EDITOR", editor)) def progress(self, topic, pos, item="", unit="", total=None): '''show a progress message diff --git a/mercurial/util.py b/mercurial/util.py --- a/mercurial/util.py +++ b/mercurial/util.py @@ -422,22 +422,29 @@ def system(cmd, environ={}, cwd=None, on return str(val) origcmd = cmd cmd = quotecommand(cmd) - env = dict(os.environ) - env.update((k, py2shell(v)) for k, v in environ.iteritems()) - env['HG'] = hgexecutable() - if out is None or out == sys.__stdout__: - rc = subprocess.call(cmd, shell=True, close_fds=closefds, - env=env, cwd=cwd) + if sys.platform == 'plan9': + # subprocess kludge to work around issues in half-baked Python + # ports, notably bichued/python: + if not cwd is None: + os.chdir(cwd) + rc = os.system(cmd) else: - proc = subprocess.Popen(cmd, shell=True, close_fds=closefds, - env=env, cwd=cwd, stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) - for line in proc.stdout: - out.write(line) - proc.wait() - rc = proc.returncode - if sys.platform == 'OpenVMS' and rc & 1: - rc = 0 + env = dict(os.environ) + env.update((k, py2shell(v)) for k, v in environ.iteritems()) + env['HG'] = hgexecutable() + if out is None or out == sys.__stdout__: + rc = subprocess.call(cmd, shell=True, close_fds=closefds, + env=env, cwd=cwd) + else: + proc = subprocess.Popen(cmd, shell=True, close_fds=closefds, + env=env, cwd=cwd, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + for line in proc.stdout: + out.write(line) + proc.wait() + rc = proc.returncode + if sys.platform == 'OpenVMS' and rc & 1: + rc = 0 if rc and onerr: errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]), explainexit(rc)[0]) diff --git a/setup.py b/setup.py --- a/setup.py +++ b/setup.py @@ -127,10 +127,16 @@ except ImportError: py2exeloaded = False def runcmd(cmd, env): - p = subprocess.Popen(cmd, stdout=subprocess.PIPE, - stderr=subprocess.PIPE, env=env) - out, err = p.communicate() - return out, err + if sys.platform == 'plan9': + # subprocess kludge to work around issues in half-baked Python + # ports, notably bichued/python: + _, out, err = os.popen3(cmd) + return str(out), str(err) + else: + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, env=env) + out, err = p.communicate() + return out, err def runhg(cmd, env): out, err = runcmd(cmd, env) diff --git a/tests/test-duplicateoptions.py b/tests/test-duplicateoptions.py --- a/tests/test-duplicateoptions.py +++ b/tests/test-duplicateoptions.py @@ -1,7 +1,7 @@ import os from mercurial import ui, commands, extensions -ignore = set(['highlight', 'inotify', 'win32text']) +ignore = set(['highlight', 'inotify', 'win32text', 'factotum']) if os.name != 'nt': ignore.add('win32mbcs')