#
#
# add_file "www/viewmtn/templates/help.html"
# content [261b26dddeecdfc25505dc8bf5ce633c947b54d6]
#
# add_file "www/viewmtn/templates/revision.html"
# content [e9eeb6212f211ce522b1db16156e7129e00993d1]
#
# add_file "www/viewmtn/templates/revisioninfo.html"
# content [f78b63b0bea3ba8971396d33e0ce9e7b0477847d]
#
# add_file "www/viewmtn/templates/tags.html"
# content [51d40e8786b2cdfb99d956a28583be36c49cc078]
#
# patch "www/viewmtn/mtn.py"
# from [9aacc29cd3af6277ef411f7e582a450f58c727ee]
# to [1f4c058e4f55b848c1c8195a38e6edaeb86310b6]
#
# patch "www/viewmtn/viewmtn.py"
# from [574970773ca270f6282d13161905dae81274170b]
# to [f64c08da43d7339e847ed1cddd44939c349bf9e7]
#
============================================================
--- www/viewmtn/templates/help.html 261b26dddeecdfc25505dc8bf5ce633c947b54d6
+++ www/viewmtn/templates/help.html 261b26dddeecdfc25505dc8bf5ce633c947b54d6
@@ -0,0 +1,22 @@
+#extends base
+
+#def body
+
+ViewMTN is a web interface to the Monotone revision control
+system. These web pages provide various methods to access the data
+controlled within a particular Monotone database.
+
+
+
+To make full use of this web interface, it is recommended that you read
+the Monotone
+manual.
+
+
+
+Feature suggestions, bug reports and patches are welcome. Please go
+to the ViewMTN
+software page and follow the contact instructions there.
+
+#end def
============================================================
--- www/viewmtn/templates/revision.html e9eeb6212f211ce522b1db16156e7129e00993d1
+++ www/viewmtn/templates/revision.html e9eeb6212f211ce522b1db16156e7129e00993d1
@@ -0,0 +1,8 @@
+#extends base
+
+#def extramenu
+Revision $revision.abbrev():
+Info |
+Browse Files |
+Download (tar)
+#end def
============================================================
--- www/viewmtn/templates/revisioninfo.html f78b63b0bea3ba8971396d33e0ce9e7b0477847d
+++ www/viewmtn/templates/revisioninfo.html f78b63b0bea3ba8971396d33e0ce9e7b0477847d
@@ -0,0 +1,43 @@
+#extends revision
+
+#def body
+
+Certificates
+
+
+#for cert in $certs
+
+
+ #filter Filter
+ $cert['name']
+ #filter WebSafe
+ |
+
+ #filter Filter
+ $cert['value']
+ #filter WebSafe
+ |
+
+#end for
+
+
+Revision Details
+
+
+#for stanza_type, descr, value in $revisions
+
+
+ #filter Filter
+ $descr
+ #filter WebSafe
+ |
+
+ #filter Filter
+ $value
+ #filter WebSafe
+
+ |
+#end for
+
+
+#end def
============================================================
--- www/viewmtn/templates/tags.html 51d40e8786b2cdfb99d956a28583be36c49cc078
+++ www/viewmtn/templates/tags.html 51d40e8786b2cdfb99d956a28583be36c49cc078
@@ -0,0 +1,29 @@
+#extends base
+
+#def body
+
+A tag marks a particular revision that is in some way significant.
+A common use of tags is to mark public release of a piece of software.
+To view a particular tag, select it from the list below.
+
+
+
+Tag | Signed by | When |
+#for tag in $tags
+
+
+ #filter Filter
+ $link($tag).html()
+ #filter WebSafe
+ |
+
+ $tag.author
+ |
+
+
+ |
+
+#end for
+
+
+#end def
============================================================
--- www/viewmtn/mtn.py 9aacc29cd3af6277ef411f7e582a450f58c727ee
+++ www/viewmtn/mtn.py 1f4c058e4f55b848c1c8195a38e6edaeb86310b6
@@ -1,10 +1,19 @@
import os
import re
+import fcntl
import pipes
import select
import threading
+import popen2
+debugging = True
+
+debug = None
+
+import web
+debug = web.debug
+
# regular expressions that are of general use when
# validating monotone output
def group_compile(r):
@@ -26,11 +35,29 @@
def abbrev(self):
return '[' + self[:8] + '..]'
+def set_nonblocking(fd):
+ fl = fcntl.fcntl(fd, fcntl.F_GETFL)
+ fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NDELAY)
+
+def terminate_popen3(process):
+ debug("%s stopping %s" % (os.getpid(), process.pid))
+ try:
+ process.tochild.close()
+ process.fromchild.close()
+ process.childerr.close()
+ if process.poll() == -1:
+ # the process is still running, so kill it.
+ os.kill(process.pid, signal.SIGKILL)
+ process.wait()
+ except:
+ debug("%s failed_to_stop %s" % (os.getpid(), self.process.pid))
+
class Runner:
def __init__(self, monotone, database):
self.base_command = [monotone, "--db=%s" % pipes.quote(database)]
packet_header_re = re.compile(r'^(\d+):(\d+):([lm]):(\d+):')
+import web
class Automate(Runner):
"""Runs commands via a particular monotone process. This
@@ -45,36 +72,43 @@
self.lock = threading.Lock()
self.process = None
+ def stop(self):
+ if not self.process:
+ return
+ terminate_popen3(self.process)
+
def __process_required(self):
if self.process != None:
return
- from utility import set_nonblocking
to_run = self.base_command + ['automate', 'stdio']
- self.child_stdin, self.child_stdout, self.child_stderr = os.popen3(to_run)
- map (set_nonblocking, [ self.child_stdin,
- self.child_stdout,
- self.child_stderr ])
+ self.process = popen2.Popen3(to_run, capturestderr=True)
+ map (set_nonblocking, [ self.process.fromchild,
+ self.process.tochild,
+ self.process.childerr ])
def run(self, command, args):
if not self.lock.acquire(False):
raise MonotoneException("Automate process can't be called: it is already locked.")
self.__process_required()
+ debug(command, args)
enc = "l%d:%s" % (len(command), command)
enc += ''.join(map(lambda x: "%d:%s" % (len(x), x), args)) + 'e'
- self.child_stdin.write(enc)
- self.child_stdin.flush()
+ debug("wrote", enc)
+ self.process.tochild.write(enc)
+ self.process.tochild.flush()
import sys
def read_result_packets():
buffer = ""
while True:
- r_stdin, r_stdout, r_stderr = select.select([self.child_stdout], [], [], None)
+ r_stdin, r_stdout, r_stderr = select.select([self.process.fromchild], [], [], None)
if not r_stdin and not r_stdout and not r_stderr:
break
- if self.child_stdout in r_stdin:
- data = self.child_stdout.read()
+ if self.process.fromchild in r_stdin:
+ data = self.process.fromchild.read()
+ debug("data", data)
if data == "":
break
buffer += data
@@ -88,7 +122,7 @@
break
in_packet = True
cmdnum, errnum, pstate, length = m.groups()
- errnum = int(cmdnum)
+ errnum = int(errnum)
length = int(length)
header_length = m.end(m.lastindex) + 1 # the '1' is the colon
@@ -113,7 +147,7 @@
for line in data.split('\n'):
yield line + '\n'
if code_max > 0:
- raise MonotoneException("error code %d in automate packet." % code_max)
+ raise MonotoneException("error code %d in automate packet." % (code_max))
self.lock.release()
class Standalone(Runner):
@@ -125,13 +159,15 @@
# arguments - and does not pass them through the shell according
# to help(os.popen3)
to_run = self.base_command + [command] + args
- child_stdin, child_stdout, child_stderr = os.popen3(to_run)
- for line in child_stdout:
+ process = popen2.Popen3(to_run, capturestderr=True)
+ for line in process.fromchild:
yield line
- stderr_data = child_stderr.read()
+ stderr_data = process.childerr.read()
if len(stderr_data) > 0:
raise MonotoneException("data on stderr for command '%s': %s" % (command,
stderr_data))
+ terminate_popen3(process)
+
class MtnObject:
def __init__(self, obj_type):
self.obj_type = obj_type
@@ -223,8 +259,10 @@
consumer = choose_consume
current_stanza = []
for line in gen:
-# print "read line:", [line]
- if (line == '' or line == '\n') and current_stanza:
+ # if we're not in an actual consumer (which we shouldn't be, unless
+ # we're parsing some sort of multi-line token) and we have a blank
+ # line, it indicates the end of any current stanza
+ if (consumer == choose_consume) and (line == '' or line == '\n') and current_stanza:
yield current_stanza
current_stanza = []
continue
@@ -239,6 +277,9 @@
self.standalone = apply(Standalone, runner_args)
self.automate = apply(Automate, runner_args)
+ def __del__(self):
+ debug("deleting Operations instance.")
+
def tags(self):
for line in (t.strip() for t in self.standalone.run('ls', ['tags'])):
if not line:
============================================================
--- www/viewmtn/viewmtn.py 574970773ca270f6282d13161905dae81274170b
+++ www/viewmtn/viewmtn.py f64c08da43d7339e847ed1cddd44939c349bf9e7
@@ -1,9 +1,10 @@
#!/usr/bin/env python
import cgi
import mtn
import web
import config
+import urllib
import urlparse
hq = cgi.escape
@@ -52,12 +53,73 @@
class BranchLink(Link):
def __init__(self, branch, **kwargs):
Link.__init__(*(self, ), **kwargs)
- self.relative_uri = 'branch/changes/' + hq(branch.name)
+ self.relative_uri = 'branch/changes/' + urllib.quote(branch.name)
self.description = hq(branch.name)
+class DiffLink(Link):
+ def __init__(self, diff, **kwargs):
+ Link.__init__(*(self, ), **kwargs)
+ self.relative_uri = 'revision/diff/' + diff.from_rev + '/with/' + diff.to_rev
+ self.description = "diff"
+
+class Diff:
+ def __init__(self, from_rev, to_rev, file):
+ self.obj_type = 'diff'
+ self.file = file
+ self.from_rev = from_rev
+ self.to_rev = to_rev
+
+def prettify(s):
+ return ' '.join(
+ map(lambda x: hq(x[0].upper() + x[1:]),
+ s.replace("_", " ").split(" ")))
+
+def certs_for_template(cert_gen):
+ for cert in cert_gen:
+ if cert[0] == 'key' and len(cert) != 10:
+ raise Exception("Not a correcly formatted certificate: %s" % cert)
+ if cert[3] != 'ok':
+ raise Exception("Certificate failed check.")
+
+ key = cert[1]
+ name = cert[5]
+ value = cert[7]
+ if name == "branch":
+ value = link(mtn.Branch(value)).html()
+ else:
+ value = '
'.join(map(hq, value.split('\n')))
+
+ yield { 'key' : key,
+ 'name' : prettify(name),
+ 'value' : value }
+
+def revisions_for_template(rev_gen):
+ for stanza in rev_gen:
+ stanza_type = stanza[0]
+ description, value = prettify(stanza_type), None
+
+ if stanza_type == "format_version" or \
+ stanza_type == "new_manifest":
+ continue
+ elif stanza_type == "patch":
+ fname, from_id, to_id = stanza[1], stanza[3], stanza[5]
+ # if from_id is null, this is a new file
+ # since we're showing that information under "Add", so
+ # skip it here
+ if not from_id:
+ continue
+ value = "Patch file %s (%s)" % ("not yet",
+ link(Diff(from_id, to_id, fname)).html())
+ else:
+ value = "(this stanza type is not explicitly rendered; please report this.)\n%s" % hq(str(stanza))
+
+ if description != None:
+ yield stanza_type, description, value
+
type_to_link_class = {
'tag' : TagLink,
- 'branch' : BranchLink
+ 'branch' : BranchLink,
+ 'diff' : DiffLink,
}
def link(obj):
@@ -117,9 +179,13 @@
class RevisionInfo:
def GET(self, revision):
revision = mtn.Revision(revision)
+ certs = ops.certs(revision)
+ revisions = ops.get_revision(revision)
renderer.render('revisioninfo.html',
page_title="Revision %s" % revision.abbrev(),
- revision=revision)
+ revision=revision,
+ certs=certs_for_template(certs),
+ revisions=revisions_for_template(revisions))
class RevisionDiff:
def GET(self, revision_from, revision_to):