plan9: initial support for plan 9 from bell labs
authorSteven Stallion <sstallion@gmail.com>
Sun, 08 Apr 2012 12:43:41 -0700
changeset 16383 f5dd179bfa4a
parent 16382 f542d291c4f2
child 16384 8d821a173e4e
plan9: initial support for plan 9 from bell labs This patch contains support for Plan 9 from Bell Labs. A README is provided in contrib/plan9 which describes the port in greater detail. A new extension is also provided named factotum which permits the factotum(4) authentication agent to provide credentials for HTTP repositories. This extension is also applicable to other POSIX platforms which make use of Plan 9 from User Space (aka plan9ports).
contrib/plan9/9diff
contrib/plan9/README
contrib/plan9/hgrc.d/9diff.rc
contrib/plan9/hgrc.d/factotum.rc
contrib/plan9/mkfile
contrib/plan9/proto
hgext/factotum.py
mercurial/help/config.txt
mercurial/posix.py
mercurial/scmutil.py
mercurial/ui.py
mercurial/util.py
setup.py
tests/test-duplicateoptions.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/plan9/9diff	Sun Apr 08 12:43:41 2012 -0700
@@ -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 ''
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/plan9/README	Sun Apr 08 12:43:41 2012 -0700
@@ -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.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/plan9/hgrc.d/9diff.rc	Sun Apr 08 12:43:41 2012 -0700
@@ -0,0 +1,5 @@
+[extensions]
+extdiff =
+
+[extdiff]
+9diff = 9diff -cm $parent $child $root
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/plan9/hgrc.d/factotum.rc	Sun Apr 08 12:43:41 2012 -0700
@@ -0,0 +1,2 @@
+[extensions]
+factotum =
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/plan9/mkfile	Sun Apr 08 12:43:41 2012 -0700
@@ -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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/plan9/proto	Sun Apr 08 12:43:41 2012 -0700
@@ -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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/factotum.py	Sun Apr 08 12:43:41 2012 -0700
@@ -0,0 +1,120 @@
+# factotum.py - Plan 9 factotum integration for Mercurial
+#
+# Copyright (C) 2012 Steven Stallion <sstallion@gmail.com>
+#
+# 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=<prefix> user=<username> !password=<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')
--- a/mercurial/help/config.txt	Sun Apr 08 22:17:51 2012 -0500
+++ b/mercurial/help/config.txt	Sun Apr 08 12:43:41 2012 -0700
@@ -28,16 +28,17 @@
 paths are given below, settings from earlier paths override later
 ones.
 
-| (Unix, Windows) ``<repo>/.hg/hgrc``
+| (All) ``<repo>/.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 @@
     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 @@
     executed by any user in any directory. Options in these files
     override per-installation options.
 
+| (Plan 9) ``<install-root>/lib/mercurial/hgrc``
+| (Plan 9) ``<install-root>/lib/mercurial/hgrc.d/*.rc``
 | (Unix) ``<install-root>/etc/mercurial/hgrc``
 | (Unix) ``<install-root>/etc/mercurial/hgrc.d/*.rc``
 
--- a/mercurial/posix.py	Sun Apr 08 22:17:51 2012 -0500
+++ b/mercurial/posix.py	Sun Apr 08 12:43:41 2012 -0700
@@ -333,6 +333,9 @@
     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:
--- a/mercurial/scmutil.py	Sun Apr 08 22:17:51 2012 -0500
+++ b/mercurial/scmutil.py	Sun Apr 08 12:43:41 2012 -0700
@@ -436,15 +436,22 @@
 
     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:
 
--- a/mercurial/ui.py	Sun Apr 08 22:17:51 2012 -0500
+++ b/mercurial/ui.py	Sun Apr 08 12:43:41 2012 -0700
@@ -687,10 +687,17 @@
 
     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
--- a/mercurial/util.py	Sun Apr 08 22:17:51 2012 -0500
+++ b/mercurial/util.py	Sun Apr 08 12:43:41 2012 -0700
@@ -422,22 +422,29 @@
         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])
--- a/setup.py	Sun Apr 08 22:17:51 2012 -0500
+++ b/setup.py	Sun Apr 08 12:43:41 2012 -0700
@@ -127,10 +127,16 @@
     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)
--- a/tests/test-duplicateoptions.py	Sun Apr 08 22:17:51 2012 -0500
+++ b/tests/test-duplicateoptions.py	Sun Apr 08 12:43:41 2012 -0700
@@ -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')