# # # patch "mtn.py" # from [e65ea7979a3cbfe80bd49a6cbd83ca69f28e270f] # to [5974c2d53154febf368c5d2435dd84c6e23d819a] # # patch "templates/branchchanges.html" # from [802a6ef3b009623630d03aceb3f7dde6b406a056] # to [e7d059ec23b3f239881f19b59493eec7bc385a7f] # # patch "templates/revisionfile.html" # from [8a090c4dd2419ae56b79a4b3567db21ac7920e01] # to [1d6c59527444f4efd35f210837e0c64a6375f480] # # patch "viewmtn.py" # from [2bad66fa28530d9215ebc88db099b5d3886917da] # to [7a151db43bc8aef575ac63d8510c0b93867c89c2] # ============================================================ --- mtn.py e65ea7979a3cbfe80bd49a6cbd83ca69f28e270f +++ mtn.py 5974c2d53154febf368c5d2435dd84c6e23d819a @@ -331,6 +331,13 @@ class Operations: continue yield apply(Revision, (line,)) + def children(self, revision): + if revision != "": + for line in (t.strip() for t in self.automate.run('children', [revision])): + if not line: + continue + yield apply(Revision, (line,)) + def toposort(self, revisions): for line in (t.strip() for t in self.automate.run('toposort', revisions)): if not line: ============================================================ --- templates/branchchanges.html 802a6ef3b009623630d03aceb3f7dde6b406a056 +++ templates/branchchanges.html e7d059ec23b3f239881f19b59493eec7bc385a7f @@ -26,7 +26,7 @@ Changes $from_change to $to_change on th
$changelog+
$changelog
#filter WebSafe
-Below is the file '$file' from this revision. +Below is the file '$filename.name' from this revision. You can also +#filter Filter +$link($filename, for_download=True).html(override_description="download the file"). +#filter WebSafe
============================================================
--- viewmtn.py 2bad66fa28530d9215ebc88db099b5d3886917da
+++ viewmtn.py 7a151db43bc8aef575ac63d8510c0b93867c89c2
@@ -1,7 +1,8 @@ import mtn
#!/usr/bin/env python2.4
import cgi
import mtn
+import sys
import web
import rfc822
import config
@@ -9,6 +10,7 @@ import syntax
import urllib
import urlparse
import syntax
+import tempfile
import datetime
hq = cgi.escape
@@ -150,7 +152,12 @@ class FileLink(Link):
class FileLink(Link):
def __init__(self, file, **kwargs):
Link.__init__(*(self, ), **kwargs)
- self.relative_uri = 'revision/file/' + file.in_revision + '/' + urllib.quote(file.name)
+ debug(kwargs)
+ if kwargs.has_key('for_download'):
+ access_method = 'downloadfile'
+ else:
+ access_method = 'file'
+ self.relative_uri = 'revision/' + access_method + '/' + file.in_revision + '/' + urllib.quote(file.name)
self.description = hq(file.name)
class Diff:
@@ -430,6 +437,14 @@ class RevisionPage:
BranchChanges.GET(self, branch, from_change, to_change, "branchchangesrss.html")
class RevisionPage:
+ def get_fileid(self, revision, filename):
+ rv = None
+ for stanza in ops.get_manifest_of(revision):
+ if stanza[0] != 'file':
+ continue
+ if stanza[1] == filename:
+ rv = stanza[3]
+ return rv
def branches_for_rev(self, revisions_val):
rv = []
for stanza in ops.certs(revisions_val):
@@ -466,28 +481,30 @@ class RevisionFile(RevisionPage):
files=files)
class RevisionFile(RevisionPage):
- def GET(self, revision, file):
- def get_fileid():
- rv = None
- for stanza in ops.get_manifest_of(revision):
- if stanza[0] != 'file':
- continue
- if stanza[1] == file:
- rv = stanza[3]
- return rv
-
+ def GET(self, revision, filename):
revision = mtn.Revision(revision)
- language = file.rsplit('.', 1)[-1]
- fileid = get_fileid()
+ language = filename.rsplit('.', 1)[-1]
+ fileid = RevisionPage.get_fileid(self, revision, filename)
if not fileid:
return web.notfound()
contents = ops.get_file(fileid)
renderer.render('revisionfile.html',
- file=file,
- page_title="File %s in revision %s" % (file, revision.abbrev()),
+ filename=mtn.File(filename, revision),
+ page_title="File %s in revision %s" % (filename, revision.abbrev()),
revision=revision,
contents=syntax.highlight(contents, language))
+class RevisionDownloadFile(RevisionPage):
+ def GET(self, revision, filename):
+ web.header('Content-Disposition', 'attachment; filename=%s' % filename)
+ revision = mtn.Revision(revision)
+ fileid = RevisionPage.get_fileid(self, revision, filename)
+ if not fileid:
+ return web.notfound()
+ for data in ops.get_file(fileid):
+ sys.stdout.write(data)
+ sys.stdout.flush()
+
class RevisionBrowse(RevisionPage):
def GET(self, revision, path):
revision = mtn.Revision(revision)
@@ -641,6 +658,60 @@ class RevisionTar:
revision = mtn.Revision(revision)
print "not implemented"
+class RevisionGraph:
+ def GET(self, revision, revision_count=10):
+
+ def dot_escape(s):
+ # kinda paranoid, should probably revise later
+ permitted=string.digits + string.letters + ' -<>-:,address@hidden&.+_~?/'
+ return ''.join([t for t in s if t in permitted])
+
+ print "graph code for revision is: ", revision
+
+ # strategy: we want to show information about this revision's place
+ # in the overall graph, both forward and back, for revision_count
+ # revisions in both directions (if possible)
+ #
+ # we will show propogates as dashed arcs
+ # otherwise, a full arc
+ #
+ # we'll show the arcs leading away from the revisions at either end,
+ # to make it clear that this is one part of a larger picture
+ #
+ # it'd be neat if someone wrote a google-maps style browser; I have
+ # some ideas as to how to approach this problem.
+
+ # revision graph is prone to change; someone could commit anywhere
+ # any time. so we'll have to generate this dotty file each time;
+ # let's write it into a temporary file (save some memory, no point
+ # keeping it about on disk) and sha1 hash the contents.
+ # we'll then see if ..png exists; if not, we'll
+ # generate it from the dot file
+
+ # let's be general, it's fairly symmetrical in either direction anyway
+ # I think we want to show a consistent view over a depth vertically; at the
+ # very least we should always show the dangling arcs
+ arcs = set()
+ nodes = set()
+ def directed_graph_nodes(from_revision, direction, mode_node_fn):
+ nodes = [t for t in mode_node_fn()]
+ return nodes
+
+ graph = '''
+digraph ancestry {
+ ratio=compress
+ nodesep=0.1
+ ranksep=0.2
+ edge [dir=back];
+'''
+ graph += '''
+}
+'''
+
+ parent_nodes = directed_graph_nodes(ops.parents)
+ child_nodes = directed_graph_nodes(ops.children)
+
+
class Json:
def GET(self, method, data):
print "Bah."
@@ -658,8 +729,10 @@ urls = (
r'/revision/diff/('+mtn.revision_re+')/with/('+mtn.revision_re+')', 'RevisionDiff',
r'/revision/diff/('+mtn.revision_re+')/with/('+mtn.revision_re+')'+'/(.*)', 'RevisionDiff',
r'/revision/file/('+mtn.revision_re+')/(.*)', 'RevisionFile',
+ r'/revision/downloadfile/('+mtn.revision_re+')/(.*)', 'RevisionDownloadFile',
r'/revision/info/('+mtn.revision_re+')', 'RevisionInfo',
r'/revision/tar/('+mtn.revision_re+')', 'RevisionTar',
+ r'/revision/graph/('+mtn.revision_re+')', 'RevisionGraph',
r'/branch/changes/(.*)/from/(\d+)/to/(\d+)', 'HTMLBranchChanges',
r'/branch/changes/([^/]+)()()', 'HTMLBranchChanges',