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