# # # 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: #filter Filter -
$changelog
+$changelog #filter WebSafe ============================================================ --- templates/revisionfile.html 8a090c4dd2419ae56b79a4b3567db21ac7920e01 +++ templates/revisionfile.html 1d6c59527444f4efd35f210837e0c64a6375f480 @@ -1,9 +1,12 @@ #extends revision #def body

-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',