# # # patch "branch.psp" # from [f7ee6449c43df5afb9843bfd019f6216af7b04b4] # to [016f5daf53d8047dc6cd8e924c679f4105bfa410] # # patch "common.py" # from [87a1283680b253e297af32da14119a2546730fda] # to [326b759a8cc797fb5f57b12c7c5531fe90c2a010] # # patch "error.psp" # from [7152c3ff110418aca5d23c374ea9fb92a0e98379] # to [e1defd5a7a1469119dcbe07f0624e3412c394a03] # # patch "file.psp" # from [fa6a72321817631e86ee73ad28dacf97bf8d7218] # to [d36cc97dee84c41f56e0d81272f0050229d39ea0] # # patch "fileinbranch.psp" # from [1d3a5d5742dacc82879b558f8e785915653839c0] # to [4a02ad29a9eb9486ddde1d67bf19851cc421009c] # # patch "headofbranch.psp" # from [941f8cb85d9e1c05d5e1e9a7fe419e6c45bdaec3] # to [a3561a6b2bbf54822d3d82621421f05e07fe49b0] # # patch "html.py" # from [1a39d42e352389a7c9cd52a5037861895ed6d4b8] # to [65567f42bebe8cba7faf23471d7c8e6031bc50b5] # # patch "index.psp" # from [57d99c98f15d8761c2f8c1ca85b0aa5d959cb88d] # to [6d0099247d31734a99caa03b8d2ac24acc034582] # # patch "manifest.psp" # from [d6cec402657f3518eb8017a54a22428644dcc233] # to [9bfe0452e706fb8fd3f84c91b137061795e2686b] # # patch "monotone.py" # from [09a54ce28dfd9610ba5c53188ee17f27c4716ef7] # to [27ba5f94c6849264fb9df4f695580591e1ee3146] # # patch "revision.psp" # from [54379dc8fb3e0fd02a084fcd92bdb0f41a85de61] # to [82d35235b0c254d2ec2552d53b72f17242f462b1] # # patch "tags.psp" # from [9b74bcccf9f006be39cf615e707cbb0d4c85ede4] # to [6fdb677d68ef8f35cf439a2d7c34e37ff29cd9af] # # patch "tarofbranch.psp" # from [99f001ed6a4855e6bbe97dbaca580fd6673bfae1] # to [1af97621a341b580ee0f92f701cf18ae95087c83] # # patch "utility.py" # from [c1f6a1ef480d6270c208d9b74a6dcbe1b8be7a06] # to [78cd2c53af05e63bb76c097dc832b6f75394e40d] # # patch "wrapper.py" # from [cecd28873835961f94fcc047350c89e6bba4151f] # to [904f434b11952f8634568a5de0f80298efb872df] # ============================================================ --- branch.psp f7ee6449c43df5afb9843bfd019f6216af7b04b4 +++ branch.psp 016f5daf53d8047dc6cd8e924c679f4105bfa410 @@ -14,20 +14,20 @@ psp.set_error_page("error.psp") def prettify(s): - return ' '.join(map(lambda x: hq(x[0].upper() + x[1:]), s.replace("_", " ").split(" "))) + return ' '.join(map(lambda x: hq(x[0].upper() + x[1:]), s.replace("_", " ").split(" "))) if not form.has_key('branch'): - raise Exception("No branch specified.") + raise Exception("No branch specified.") page = 1 if form.has_key('page'): - try: page = int(form['page']) - except: pass + try: page = int(form['page']) + except: pass if form.has_key('rss'): - output = 'rss' + output = 'rss' else: - output = 'html' + output = 'html' if page < 1: page = 1 @@ -38,13 +38,13 @@ page_title = "Changes to branch %s" % (hq(branch)) if output == 'html': - info = { - 'title' : page_title, - 'branch_rss' : [branch] - } - req.write(template.header(info)) + info = { + 'title' : page_title, + 'branch_rss' : [branch] + } + req.write(template.header(info)) elif output == 'rss': - req.write('''\ + req.write('''\ en-us @@ -58,12 +58,12 @@ recent = mt.toposort(mt.ancestors(heads) + heads) if display > len(recent): - display = len(recent) + display = len(recent) if offset > len(recent): - offset = len(recent) - no_next = True + offset = len(recent) + no_next = True else: - no_next = False + no_next = False start = -1*offset stop = start + display @@ -75,84 +75,84 @@ <% if output == 'html': - req.write('''\ + req.write('''\

This page lists changes %s through %s to %s. It may also be useful to view the %s of this branch.

''' % (-1*(stop or 0), (-1 * start), link('branch', branch), link('headofbranch', branch, 'current head revision'))) - req.write('') + req.write('
') %> <% for idx, id in enumerate(recent): - certs = mt.certs(id) + certs = mt.certs(id) - if output == 'rss': - req.write('\n') - req.write('%s\n' % (urllib.basejoin(config.base_url, 'revision.psp?id=%s' % (urllib.quote(id))))) + if output == 'rss': + req.write('\n') + req.write('%s\n' % (urllib.basejoin(config.base_url, 'revision.psp?id=%s' % (urllib.quote(id))))) - changelog, certdate, quicklog, certinfo = None, None, "", "" - for cert in certs: - name, value = None, None - for k, v in cert: - if k == "name": name = v - elif k == "value": value = v - if name == None or value == None: continue - if name == "date": - certdate = apply(datetime.datetime, time.strptime(value, "%Y-%m-%dT%H:%M:%S")[:6]) - if output == 'rss': req.write("\t%s\n" % hq(certdate.strftime("%a, %d %b %Y %H:%M:%S +0000"))) - elif name == "branch": - value = link("branch", value) - else: - if name == "changelog": - quicklog = hq(value.strip().split('\n')[0]) - if quicklog.startswith('*'): quicklog = quicklog[1:].strip() - if output == 'rss': req.write("\t%s\n%s\n" % (quicklog, hq(value))) - elif name == "author": - if output == 'rss': req.write("\t%s\n" % (hq(value))) - value = '
'.join(map(hq, value.split('\n'))) - certinfo += '
\n' % (prettify(name), value) + changelog, certdate, quicklog, certinfo = None, None, "", "" + for cert in certs: + name, value = None, None + for k, v in cert: + if k == "name": name = v + elif k == "value": value = v + if name == None or value == None: continue + if name == "date": + certdate = apply(datetime.datetime, time.strptime(value, "%Y-%m-%dT%H:%M:%S")[:6]) + if output == 'rss': req.write("\t%s\n" % hq(certdate.strftime("%a, %d %b %Y %H:%M:%S +0000"))) + elif name == "branch": + value = link("branch", value) + else: + if name == "changelog": + quicklog = hq(value.strip().split('\n')[0]) + if quicklog.startswith('*'): quicklog = quicklog[1:].strip() + if output == 'rss': req.write("\t%s\n%s\n" % (quicklog, hq(value))) + elif name == "author": + if output == 'rss': req.write("\t%s\n" % (hq(value))) + value = '
'.join(map(hq, value.split('\n'))) + certinfo += '\n' % (prettify(name), value) - now = datetime.datetime.utcnow() - ago = now - certdate - if ago.days > 0: - ago = "%d days, %d hours" % (ago.days, ago.seconds / 3600) - elif ago.seconds > 3600: - hours = ago.seconds / 3600 - minutes = (ago.seconds - (hours * 3600)) / 60 - ago = "%d hours, %d minutes" % (hours, minutes) - else: - minutes = ago.seconds / 60 - seconds = (ago.seconds - (minutes * 60)) - ago = "%d minutes, %d seconds" % (minutes, seconds) + now = datetime.datetime.utcnow() + ago = now - certdate + if ago.days > 0: + ago = "%d days, %d hours" % (ago.days, ago.seconds / 3600) + elif ago.seconds > 3600: + hours = ago.seconds / 3600 + minutes = (ago.seconds - (hours * 3600)) / 60 + ago = "%d hours, %d minutes" % (hours, minutes) + else: + minutes = ago.seconds / 60 + seconds = (ago.seconds - (minutes * 60)) + ago = "%d minutes, %d seconds" % (minutes, seconds) - if output == 'html': - style = "font-size: 130%%; border-bottom-style: solid; border-bottom-width: 1px; border-bottom-color: black;" - if idx != 0: - style += "border-top-style: solid; border-top-width: 1px; border-top-color: black;" - req.write('\n' % (style, ago, quicklog, link("revision", id, "revision info"), link("manifest", id, "browse files"))) - req.write(certinfo) - elif output == 'rss': - req.write('\n') + if output == 'html': + style = "font-size: 130%%; border-bottom-style: solid; border-bottom-width: 1px; border-bottom-color: black;" + if idx != 0: + style += "border-top-style: solid; border-top-width: 1px; border-top-color: black;" + req.write('\n' % (style, ago, quicklog, link("revision", id, "revision info"), link("manifest", id, "browse files"))) + req.write(certinfo) + elif output == 'rss': + req.write('\n') if output == 'html': - req.write('
%s:%s
%s:%s
%s ago: %s (%s, %s)
%s ago: %s (%s, %s)
') + req.write('') - c = [] - if page != 1: - c.append('Newer changes' % (urllib.quote(branch), page-1)) - if not no_next: - c.append('Older changes' % (urllib.quote(branch), page+1)) - req.write('

' + ' | '.join(c) + '

') + c = [] + if page != 1: + c.append('Newer changes' % (urllib.quote(branch), page-1)) + if not no_next: + c.append('Older changes' % (urllib.quote(branch), page+1)) + req.write('

' + ' | '.join(c) + '

') %> <% if output == 'html': - req.write(template.footer(info)) + req.write(template.footer(info)) elif output == 'rss': - req.write('''\ + req.write('''\
''') ============================================================ --- common.py 87a1283680b253e297af32da14119a2546730fda +++ common.py 326b759a8cc797fb5f57b12c7c5531fe90c2a010 @@ -5,12 +5,12 @@ escape_function = pydoc.HTMLRepr().escape def type_wrapper(e, x): - if x == None: - return "" - elif type(x) == type([]): - return '
'.join(map(e, x)) - else: - return e(x) + if x == None: + return "" + elif type(x) == type([]): + return '
'.join(map(e, x)) + else: + return e(x) # # FIXME @@ -20,71 +20,71 @@ # def link(mt, link_type, link_to, description = None, no_quote = False): - hq = html_escape() - if not no_quote and description != None: description = hq(description) - if link_type == "revision": - rv = '' % (urllib.quote(link_to)) - if description != None: rv += description - else: rv += hq(link_to[:8]) + ".." - rv += '' - if description == None: rv = '[' + rv + ']' - return rv - elif link_type == "diff": - link_to = map(urllib.quote, link_to) - uri = 'getdiff.py?id1=%s&id2=%s' % (link_to[0], link_to[1]) - if len(link_to) == 3: - uri += '&fname=%s' % (link_to[2]) - rv = '' - if description != None: rv += description - else: rv += "diff" - rv += '' - return rv - elif link_type == "download": - if type(link_to) == type([]): - rv = '' % (urllib.quote(link_to[0]), urllib.quote(link_to[1])) - link_id = link_to[0] - else: - rv = '' % (urllib.quote(link_to)) - link_id = link_to - if description != None: rv += description + "" - else: rv = "[" + rv + hq(link_id[:8]) + ".." + "]" - return rv - elif link_type == "file": - revision_id, path = link_to - rv = '' % (urllib.quote(revision_id), urllib.quote(path)) - if description != None: rv += description + "" - else: rv = "[" + rv + hq(path + '@' + revision_id[:8]) + ".." + "]" - return rv - elif link_type == "branch": - rv = '' % (urllib.quote(link_to)) - if description != None: rv += description - else: rv += hq(link_to) - rv += '' - return rv - elif link_type == "tar": - rv = '' % (urllib.quote(link_to)) - if description != None: rv += description - else: rv = "tar of [" + rv + hq(link_to[:8]) + "..]" + "]" - rv += '' - return rv - elif link_type == "headofbranch": - rv = '' % (urllib.quote(link_to)) - if description != None: rv += description - else: rv += "head of " + hq(link_to) - rv += '' - return rv - elif link_type == "manifest": - rv = '' % (urllib.quote(link_to)) - if description != None: rv += description - else: rv += hq(link_to[:8]) + ".." - rv += '' - if description == None: rv = '[' + rv + ']' - return rv - else: - rv = 'Unknown link type: %s' % (hq(link_type)) - return rv + hq = html_escape() + if not no_quote and description != None: description = hq(description) + if link_type == "revision": + rv = '' % (urllib.quote(link_to)) + if description != None: rv += description + else: rv += hq(link_to[:8]) + ".." + rv += '' + if description == None: rv = '[' + rv + ']' + return rv + elif link_type == "diff": + link_to = map(urllib.quote, link_to) + uri = 'getdiff.py?id1=%s&id2=%s' % (link_to[0], link_to[1]) + if len(link_to) == 3: + uri += '&fname=%s' % (link_to[2]) + rv = '' + if description != None: rv += description + else: rv += "diff" + rv += '' + return rv + elif link_type == "download": + if type(link_to) == type([]): + rv = '' % (urllib.quote(link_to[0]), urllib.quote(link_to[1])) + link_id = link_to[0] + else: + rv = '' % (urllib.quote(link_to)) + link_id = link_to + if description != None: rv += description + "" + else: rv = "[" + rv + hq(link_id[:8]) + ".." + "]" + return rv + elif link_type == "file": + revision_id, path = link_to + rv = '' % (urllib.quote(revision_id), urllib.quote(path)) + if description != None: rv += description + "" + else: rv = "[" + rv + hq(path + '@' + revision_id[:8]) + ".." + "]" + return rv + elif link_type == "branch": + rv = '' % (urllib.quote(link_to)) + if description != None: rv += description + else: rv += hq(link_to) + rv += '' + return rv + elif link_type == "tar": + rv = '' % (urllib.quote(link_to)) + if description != None: rv += description + else: rv = "tar of [" + rv + hq(link_to[:8]) + "..]" + "]" + rv += '' + return rv + elif link_type == "headofbranch": + rv = '' % (urllib.quote(link_to)) + if description != None: rv += description + else: rv += "head of " + hq(link_to) + rv += '' + return rv + elif link_type == "manifest": + rv = '' % (urllib.quote(link_to)) + if description != None: rv += description + else: rv += hq(link_to[:8]) + ".." + rv += '' + if description == None: rv = '[' + rv + ']' + return rv + else: + rv = 'Unknown link type: %s' % (hq(link_type)) + return rv def html_escape(): - "returns a function stolen from pydoc that can be used to escape HTML" - return lambda x: type_wrapper(escape_function, x) + "returns a function stolen from pydoc that can be used to escape HTML" + return lambda x: type_wrapper(escape_function, x) ============================================================ --- error.psp 7152c3ff110418aca5d23c374ea9fb92a0e98379 +++ error.psp e1defd5a7a1469119dcbe07f0624e3412c394a03 @@ -13,7 +13,9 @@ # message without a traceback: this is an intentionally raised Exception rather than # a program error e_type, e_value, e_traceback = sys.exc_info() + if (str(e_type) == "exceptions.Exception"): + # this is a deliberate exception, pretty print %>

@@ -31,15 +33,16 @@ <% else: + # show the raw exception %>

Exception:

<% - import traceback - mesg = traceback.format_exception(e_type, e_value, e_traceback) - mesg = map(hq, mesg) - req.write('

' + '
'.join(mesg) + '
') + import traceback + mesg = traceback.format_exception(e_type, e_value, e_traceback) + mesg = map(hq, mesg) + req.write('
' + '
'.join(mesg) + '
') %>

============================================================ --- file.psp fa6a72321817631e86ee73ad28dacf97bf8d7218 +++ file.psp d36cc97dee84c41f56e0d81272f0050229d39ea0 @@ -20,33 +20,33 @@ psp.set_error_page("error.psp") if not form.has_key('id'): - raise Exception("No revision ID specified.") + raise Exception("No revision ID specified.") id = form['id'] if not monotone.is_valid_id(id): - raise Exception("Specified revision ID is not valid.") + raise Exception("Specified revision ID is not valid.") if not form.has_key('path'): - raise Exception("No path specified.") + raise Exception("No path specified.") path = form['path'] revision = mt.revision(id) if not revision.has_key('new_manifest'): - raise Exception("There is no manifest in this revision ID.") + raise Exception("There is no manifest in this revision ID.") manifest_id = revision['new_manifest'][0][0][1] manifest = mt.manifest(manifest_id) matching_file_id = None for file_id, filename in manifest: - if filename == path: - matching_file_id = file_id - break + if filename == path: + matching_file_id = file_id + break if matching_file_id == None: - raise Exception("File not found in this revision.") + raise Exception("File not found in this revision.") info = { - 'title' : "File '/%s' in revision %s" % (hq(path), hq(id)), - } + 'title' : "File '/%s' in revision %s" % (hq(path), hq(id)), + } req.write(template.header(info)) contents = mt.file(matching_file_id) @@ -57,41 +57,41 @@ # is it binary? def is_binary(): - nontext_chars = "\x01\x02\x03\x04\x05\x06\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1c\x1d\x1e\x1f" - check = {} - for char in nontext_chars: - check[char] = True - for i in contents: - if check.has_key(i): return True - return False + nontext_chars = "\x01\x02\x03\x04\x05\x06\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1c\x1d\x1e\x1f" + check = {} + for char in nontext_chars: + check[char] = True + for i in contents: + if check.has_key(i): return True + return False mime_type = mimetypes.guess_type(path)[0] if mime_type == None: mime_type = 'text/plain' if mime_type == 'image/png' or mime_type == 'image/jpeg' or mime_type == 'image/gif': - display_as_image = True + display_as_image = True else: display_as_image = False # okay; can we guess a valid enscript filter to run this through? tsp = mime_type.split('/', 1) filter = None if tsp[0] == 'text': - candidate = tsp[1] - if candidate.startswith('x-'): candidate = candidate[2:] - if candidate.endswith('src'): candidate = candidate[:-3] - if candidate.endswith('hdr'): candidate = candidate[:-3] - if candidate == 'c++': candidate = 'cpp' # ugly - if candidate in enscript_langs: filter = candidate + candidate = tsp[1] + if candidate.startswith('x-'): candidate = candidate[2:] + if candidate.endswith('src'): candidate = candidate[:-3] + if candidate.endswith('hdr'): candidate = candidate[:-3] + if candidate == 'c++': candidate = 'cpp' # ugly + if candidate in enscript_langs: filter = candidate if filter == None: - # heh, will at least work for lua files - last_dot = path.rfind('.') - if last_dot == -1: last_dot = 0 - candidate = path[last_dot:] - if candidate in enscript_langs: filter = candidate + # heh, will at least work for lua files + last_dot = path.rfind('.') + if last_dot == -1: last_dot = 0 + candidate = path[last_dot:] + if candidate in enscript_langs: filter = candidate # if no filter then let's check if it's binary or not; if not binary # we'll just treat it as text; otherwise display a warning and a download # link if filter == None and not is_binary(): - filter = 'text' + filter = 'text' req.write('

For more information about the revision containing this file, see its %s page. For other files contained in this revision, see the %s.

' % (link("revision", id, "revision"), link("manifest", id, "manifest"))) @@ -99,36 +99,36 @@ req.write('''
''') if display_as_image: - req.write('''') - def stop_code(): - req.write('') - def text(): - start_code() - req.write(hq(contents)) - stop_code() - def enscript(): - command = config.enscript_path + ' -o - --color -w html' - command += ' --highlight=%s' % (pipes.quote(filter)) - result = run_command(command, to_child=contents) - if result['exitcode'] != 0: - raise Exception('Error running enscript.') - in_contents = False - for line in result['fromchild'].split('\n'): - if line.startswith('
'):
-				in_contents = True
-				start_code()
-			elif line.startswith('
'): - in_contents = False - stop_code() - elif in_contents: - req.write(line + '\r\n') - if filter == "text": text() - else: enscript() + def start_code(): + req.write('
')
+    def stop_code():
+        req.write('
') + def text(): + start_code() + req.write(hq(contents)) + stop_code() + def enscript(): + command = config.enscript_path + ' -o - --color -w html' + command += ' --highlight=%s' % (pipes.quote(filter)) + result = run_command(command, to_child=contents) + if result['exitcode'] != 0: + raise Exception('Error running enscript.') + in_contents = False + for line in result['fromchild'].split('\n'): + if line.startswith('
'):
+                in_contents = True
+                start_code()
+            elif line.startswith('
'): + in_contents = False + stop_code() + elif in_contents: + req.write(line + '\r\n') + if filter == "text": text() + else: enscript() else: - req.write('''

This file seems to binary and not suitable for display in the browser. You must %s the file and use a suitable viewer.

''' % (link("download", [matching_file_id, path], "download"))) + req.write('''

This file seems to binary and not suitable for display in the browser. You must %s the file and use a suitable viewer.

''' % (link("download", [matching_file_id, path], "download"))) req.write('''
''') req.write(template.footer(info)) ============================================================ --- fileinbranch.psp 1d3a5d5742dacc82879b558f8e785915653839c0 +++ fileinbranch.psp 4a02ad29a9eb9486ddde1d67bf19851cc421009c @@ -6,9 +6,9 @@ psp.set_error_page("error.psp") if not form.has_key('branch'): - raise Exception("No branch specified.") + raise Exception("No branch specified.") if not form.has_key('path'): - raise Exception("No path specified.") + raise Exception("No path specified.") branch = form['branch'] path = form['path'] @@ -20,35 +20,35 @@ heads = mt.heads(branch) if len(heads) == 0: - raise Exception("No head ID can be determined for this branch.") + raise Exception("No head ID can be determined for this branch.") # build a hash with a list of all files in each head which # match PATH and their file ID. file_version = {} for id in heads: - revision = mt.revision(id) - manifest_id = revision['new_manifest'][0][0][1] - file_revisions = filter(lambda x: x[1] == path, mt.manifest(manifest_id)) - if len(file_revisions) > 1: - raise Exception("More than one file matches path?") - elif len(file_revisions) == 1: - file_version[id] = file_revisions[0][0] + revision = mt.revision(id) + manifest_id = revision['new_manifest'][0][0][1] + file_revisions = filter(lambda x: x[1] == path, mt.manifest(manifest_id)) + if len(file_revisions) > 1: + raise Exception("More than one file matches path?") + elif len(file_revisions) == 1: + file_version[id] = file_revisions[0][0] if len(file_version.values()) == 0: - raise Exception("No file such file found in this branch.") + raise Exception("No file such file found in this branch.") unique = True last = None values = file_version.values() for file_revision in values: - if last != None and file_revision != last: - unique = False - break - last = file_revision + if last != None and file_revision != last: + unique = False + break + last = file_revision if unique: - psp.redirect("getfile.py?id=%s&path=%s" % (urllib.quote(values[0]), urllib.quote(path))) + psp.redirect("getfile.py?id=%s&path=%s" % (urllib.quote(values[0]), urllib.quote(path))) else: - info = {'title' : "Latest version of %s in branch %s" % (hq(path), hq(branch))} - req.write(template.header(info)) + info = {'title' : "Latest version of %s in branch %s" % (hq(path), hq(branch))} + req.write(template.header(info)) %>

The branch you have selected has multiple head revisions, and the file you are attempting @@ -59,8 +59,8 @@ <% - for id in file_version.keys(): - req.write('' % (link("revision", id), link("download", [file_version[id], path], "view file"))) + for id in file_version.keys(): + req.write('' % (link("revision", id), link("download", [file_version[id], path], "view file"))) %>
Head revisionView file
%s%s
%s%s
============================================================ --- headofbranch.psp 941f8cb85d9e1c05d5e1e9a7fe419e6c45bdaec3 +++ headofbranch.psp a3561a6b2bbf54822d3d82621421f05e07fe49b0 @@ -5,7 +5,7 @@ psp.set_error_page("error.psp") if not form.has_key('branch'): - raise Exception("No branch specified.") + raise Exception("No branch specified.") branch = form['branch'] # headofbranch.psp @@ -20,14 +20,14 @@ heads = mt.heads(branch) if len(heads) == 0: - raise Exception("No head ID can be determined for this branch.") + raise Exception("No head ID can be determined for this branch.") elif len(heads) == 1: - # a single head ID; redirect to it - id = heads[0] - psp.redirect("revision.psp?id=%s" % (urllib.quote(id))) + # a single head ID; redirect to it + id = heads[0] + psp.redirect("revision.psp?id=%s" % (urllib.quote(id))) else: - info = {'title' : "Branch details for %s" % (hq(branch))} - req.write(template.header(info)) + info = {'title' : "Branch details for %s" % (hq(branch))} + req.write(template.header(info)) %>

The following head IDs are available. Please select one to view. @@ -36,8 +36,8 @@ <% - for id in heads: - req.write('' % (link("revision", id))) + for id in heads: + req.write('' % (link("revision", id))) %>
Head revision
%s
%s
============================================================ --- html.py 1a39d42e352389a7c9cd52a5037861895ed6d4b8 +++ html.py 65567f42bebe8cba7faf23471d7c8e6031bc50b5 @@ -10,71 +10,71 @@ hq = common.html_escape() def get_icon(filename, mime_type=None, is_directory=False): - if config.gnome_mimetype_icon_path == None: - return None - if is_directory: - filename = 'gnome-fs-directory.png' - else: - if mime_type == None: - mime_type = mimetypes.guess_type(filename)[0] - if not mime_type: - mime_type = "text" - if mime_type.endswith('hdr'): - mime_type = mime_type[:-3] + 'src' - # some specific fixups - filename = 'gnome-mime-' + mime_type.replace('/', '-') + '.png' - if os.access(os.path.join(config.gnome_mimetype_icon_path, filename), os.R_OK): - return '/'.join([config.gnome_mimetype_uri, filename]) - else: - return None + if config.gnome_mimetype_icon_path == None: + return None + if is_directory: + filename = 'gnome-fs-directory.png' + else: + if mime_type == None: + mime_type = mimetypes.guess_type(filename)[0] + if not mime_type: + mime_type = "text" + if mime_type.endswith('hdr'): + mime_type = mime_type[:-3] + 'src' + # some specific fixups + filename = 'gnome-mime-' + mime_type.replace('/', '-') + '.png' + if os.access(os.path.join(config.gnome_mimetype_icon_path, filename), os.R_OK): + return '/'.join([config.gnome_mimetype_uri, filename]) + else: + return None class TableWriter: - def __init__(self, req): - self.req = req - self.odd = False - def start(self): - self.req.write('') - def write(self, row, is_header=False): - if self.odd: c = 'odd' - else: c = 'even' - if is_header: self.odd = False - else: self.odd = not self.odd - self.req.write('' % (c) + row + '\n') - def stop(self): - self.req.write('
') + def __init__(self, req): + self.req = req + self.odd = False + def start(self): + self.req.write('') + def write(self, row, is_header=False): + if self.odd: c = 'odd' + else: c = 'even' + if is_header: self.odd = False + else: self.odd = not self.odd + self.req.write('' % (c) + row + '\n') + def stop(self): + self.req.write('
') class Template: - def header(self, info): - if not info.has_key("title"): info['title'] = "untitled" - if not info.has_key("extra_header"): info['extra_header'] = '' + def header(self, info): + if not info.has_key("title"): info['title'] = "untitled" + if not info.has_key("extra_header"): info['extra_header'] = '' - if info.has_key('branch_rss'): - for branch in info['branch_rss']: - info['extra_header'] += '\n' % (urllib.quote(branch), hq(branch)) - return """\ - - - - - ViewMTN: %(title)s - - - %(extra_header)s - - -

+ if info.has_key('branch_rss'): + for branch in info['branch_rss']: + info['extra_header'] += '\n' % (urllib.quote(branch), hq(branch)) + return """\ + + + + + ViewMTN: %(title)s + + + %(extra_header)s + + + +

%(title)s

+ """ % (info) + def footer(self, info): + rv = """ + ''' % (time.strftime("%a, %d %b %Y %H:%M:%S %Z", time.localtime())) + return rv -

%(title)s

- """ % (info) - def footer(self, info): - rv = """ - ''' % (time.strftime("%a, %d %b %Y %H:%M:%S %Z", time.localtime())) - return rv ============================================================ --- index.psp 57d99c98f15d8761c2f8c1ca85b0aa5d959cb88d +++ index.psp 6d0099247d31734a99caa03b8d2ac24acc034582 @@ -12,13 +12,17 @@ <% if len(branches) == 0: + # no branches %> +

There are no branches in the provided monotone database. There is therefore nothing for ViewMTN to display!

+ <% else: + # okay, so display the branches %>

There are <%=len(branches)%> branches in this Monotone database. Each branch @@ -36,9 +40,9 @@ <% - branches.sort() - for branch in branches: - req.write('' % (link("branch", branch, branch))) + branches.sort() + for branch in branches: + req.write('' % (link("branch", branch, branch))) %>
Branch
%s
%s
============================================================ --- manifest.psp d6cec402657f3518eb8017a54a22428644dcc233 +++ manifest.psp 9bfe0452e706fb8fd3f84c91b137061795e2686b @@ -16,28 +16,28 @@ psp.set_error_page("error.psp") if not form.has_key('id'): - raise Exception("No revision ID specified.") + raise Exception("No revision ID specified.") id = form['id'] if not monotone.is_valid_id(id): - raise Exception("Specified revision ID is not valid.") + raise Exception("Specified revision ID is not valid.") if not form.has_key('path'): - path = '' + path = '' else: - path = form['path'] + path = form['path'] if path.startswith('/'): path = path[1:] if path.endswith('/'): path = path[-1:] revision = mt.revision(id) if not revision.has_key('new_manifest'): - raise Exception("There is no manifest in this revision ID.") + raise Exception("There is no manifest in this revision ID.") manifest_id = revision['new_manifest'][0][0][1] manifest = mt.manifest(manifest_id) info = { - 'title' : "Dir '/%s' in revision %s" % (hq(path), hq(id)), - } + 'title' : "Dir '/%s' in revision %s" % (hq(path), hq(id)), + } req.write(template.header(info)) %> @@ -62,22 +62,22 @@ subdirs, files, last_dir = [], [], None # root dir is depth 0, subdir of that 1, .. if path == '': - sp = [] + sp = [] else: - sp = path.split('/') + sp = path.split('/') depth = len(sp) for file_id, filename in manifest: - parts = filename.split('/') + parts = filename.split('/') - if len(parts) < depth or parts[:depth] != sp[:depth]: - continue - - if len(parts) > depth + 1 and parts[depth] != last_dir: - subdirs.append((parts[depth], '/'.join(parts[:depth+1]))) - last_dir = parts[depth] - elif len(parts) == depth + 1: - files.append((parts[-1], file_id, filename)) + if len(parts) < depth or parts[:depth] != sp[:depth]: + continue + + if len(parts) > depth + 1 and parts[depth] != last_dir: + subdirs.append((parts[depth], '/'.join(parts[:depth+1]))) + last_dir = parts[depth] + elif len(parts) == depth + 1: + files.append((parts[-1], file_id, filename)) # hopefully this will eventually be less ugly # @@ -92,79 +92,79 @@ file_to_rev = {} for name, file_id, file in files: - file_to_rev[file] = None + file_to_rev[file] = None found = 0 for ancestor in ancestors: - rev = mt.revision(ancestor) - for key in rev.keys(): - for stanza in rev[key]: - affected = None - type = stanza[0][0] - if type == "patch" or type == "add_file" or type == "delete_file" or type == "delete_dir": affected = stanza[0][1] - elif type == "rename_file" or type == "rename_dir": affected = stanza[1][1] - if affected != None and file_to_rev.has_key(affected) and file_to_rev[affected] == None: - file_to_rev[affected] = ancestor - found += 1 - if found >= len(files): break + rev = mt.revision(ancestor) + for key in rev.keys(): + for stanza in rev[key]: + affected = None + type = stanza[0][0] + if type == "patch" or type == "add_file" or type == "delete_file" or type == "delete_dir": affected = stanza[0][1] + elif type == "rename_file" or type == "rename_dir": affected = stanza[1][1] + if affected != None and file_to_rev.has_key(affected) and file_to_rev[affected] == None: + file_to_rev[affected] = ancestor + found += 1 + if found >= len(files): break revinfo = {} now = datetime.datetime.utcnow() for filename in file_to_rev.keys(): - rev = file_to_rev[filename] - if revinfo.has_key(rev): continue - certs = mt.certs(rev) - ago, author, certdate, quicklog = None, None, None, None - for cert in certs: - name, value = None, None - for k, v in cert: - if k == "name": name = v - elif k == "value": value = v - if name == None or value == None: continue - if name == "date": - certdate = apply(datetime.datetime, time.strptime(value, "%Y-%m-%dT%H:%M:%S")[:6]) - elif name == "changelog": - quicklog = value.strip().split('\n')[0] - if quicklog.startswith('*'): quicklog = quicklog[1:].strip() - elif name == "author": - author = value - if certdate: - ago = now - certdate - if ago.days > 0: - ago = "%d days" % (ago.days) - elif ago.seconds > 3600: - hours = ago.seconds / 3600 - minutes = (ago.seconds - (hours * 3600)) / 60 - ago = "%d hours" % (hours) - else: - minutes = ago.seconds / 60 - seconds = (ago.seconds - (minutes * 60)) - ago = "%d minutes" % (minutes) - revinfo[rev] = (ago, author, quicklog) + rev = file_to_rev[filename] + if revinfo.has_key(rev): continue + certs = mt.certs(rev) + ago, author, certdate, quicklog = None, None, None, None + for cert in certs: + name, value = None, None + for k, v in cert: + if k == "name": name = v + elif k == "value": value = v + if name == None or value == None: continue + if name == "date": + certdate = apply(datetime.datetime, time.strptime(value, "%Y-%m-%dT%H:%M:%S")[:6]) + elif name == "changelog": + quicklog = value.strip().split('\n')[0] + if quicklog.startswith('*'): quicklog = quicklog[1:].strip() + elif name == "author": + author = value + if certdate: + ago = now - certdate + if ago.days > 0: + ago = "%d days" % (ago.days) + elif ago.seconds > 3600: + hours = ago.seconds / 3600 + minutes = (ago.seconds - (hours * 3600)) / 60 + ago = "%d hours" % (hours) + else: + minutes = ago.seconds / 60 + seconds = (ago.seconds - (minutes * 60)) + ago = "%d minutes" % (minutes) + revinfo[rev] = (ago, author, quicklog) if depth != 0: - subdirs = [('..', '/'.join(sp[:-1]))] + subdirs + subdirs = [('..', '/'.join(sp[:-1]))] + subdirs tr = TableWriter(req) tr.start() tr.write('NameAgeAuthorLast log entry', is_header=True) icon_uri = get_icon('', is_directory=True) for name, subdir in subdirs: - tr.write('%s' % (icon_uri, urllib.quote(id), urllib.quote(subdir), hq(name))) + tr.write('%s' % (icon_uri, urllib.quote(id), urllib.quote(subdir), hq(name))) for name, file_id, file in files: - icon_uri = get_icon(file) - rev = file_to_rev.get(file, None) - if not rev: - tr.write('%s' % (icon_uri, link("file", [revision_id, file], name))) - else: - age, author, quicklog = revinfo[rev] - author = hq(author or "").replace(' ', ' ') - quicklog = hq(quicklog or "") - tr.write(' %s %s %s%s' % (icon_uri, \ - link("file", [id, file], name), \ - link("revision", rev, (age or "").replace(' ', ' '), no_quote=True), - author, \ - quicklog)) + icon_uri = get_icon(file) + rev = file_to_rev.get(file, None) + if not rev: + tr.write('%s' % (icon_uri, link("file", [revision_id, file], name))) + else: + age, author, quicklog = revinfo[rev] + author = hq(author or "").replace(' ', ' ') + quicklog = hq(quicklog or "") + tr.write(' %s %s %s%s' % (icon_uri, \ + link("file", [id, file], name), \ + link("revision", rev, (age or "").replace(' ', ' '), no_quote=True), + author, \ + quicklog)) tr.stop() %> ============================================================ --- monotone.py 09a54ce28dfd9610ba5c53188ee17f27c4716ef7 +++ monotone.py 27ba5f94c6849264fb9df4f695580591e1ee3146 @@ -26,334 +26,334 @@ log_entry_re = re.compile(r'^(\S+): (.*)$') def colour_from_string(str): - def f(off): - return ord(hashval[off]) / 256.0 - hashval = sha.new(str).digest() - hue = f(5) - li = f(1) * 0.15 + 0.55 - sat = f(2) * 0.5 + .5 - return ''.join(map(lambda x: "%.2x" % int(x * 256), hls_to_rgb(hue, li, sat))) + def f(off): + return ord(hashval[off]) / 256.0 + hashval = sha.new(str).digest() + hue = f(5) + li = f(1) * 0.15 + 0.55 + sat = f(2) * 0.5 + .5 + return ''.join(map(lambda x: "%.2x" % int(x * 256), hls_to_rgb(hue, li, sat))) class Automation: - def __init__(self, base_command): - self.command = "%s automate stdio" % (base_command) - self.process = None - def __del__(self): - self.stop() - self.process = None - def start(self): - self.process = popen2.Popen3(self.command) - set_nonblocking(self.process.fromchild) - def stop(self): - if not self.process: return - try: - self.process.tochild.close() - self.process.fromchild.close() - if self.process.poll() == -1: - # the process is still running, so kill it. - os.kill(self.process.pid, signal.SIGKILL) - self.process.wait() - except: - pass - def run(self, command, args): - if self.process == None: self.start() - enc = "l%d:%s" % (len(command), command) - enc += ''.join(map(lambda x: "%d:%s" % (len(x), x), args)) + 'e' - self.process.tochild.write(enc) - self.process.tochild.flush() - r = RecvPacket() - complete = False - result_string = "" - result_code = None - while not complete: - ro, rw, re = select.select([self.process.fromchild], [], [], None) - if not ro and not rw and not re: - break - if self.process.fromchild in ro: - recv = self.process.fromchild.read() - if recv == "": break - tv = r.process_data(recv) - if tv != None: - cmdnum, error, length, is_last, result = tv - if result_code == None: result_code = int(error) - result_string += result - if is_last: - complete = True - else: - # in case there is anything left over we - # didn't parse - r = RecvPacket(r.buffer) - return result_code, result_string + def __init__(self, base_command): + self.command = "%s automate stdio" % (base_command) + self.process = None + def __del__(self): + self.stop() + self.process = None + def start(self): + self.process = popen2.Popen3(self.command) + set_nonblocking(self.process.fromchild) + def stop(self): + if not self.process: return + try: + self.process.tochild.close() + self.process.fromchild.close() + if self.process.poll() == -1: + # the process is still running, so kill it. + os.kill(self.process.pid, signal.SIGKILL) + self.process.wait() + except: + pass + def run(self, command, args): + if self.process == None: self.start() + enc = "l%d:%s" % (len(command), command) + enc += ''.join(map(lambda x: "%d:%s" % (len(x), x), args)) + 'e' + self.process.tochild.write(enc) + self.process.tochild.flush() + r = RecvPacket() + complete = False + result_string = "" + result_code = None + while not complete: + ro, rw, re = select.select([self.process.fromchild], [], [], None) + if not ro and not rw and not re: + break + if self.process.fromchild in ro: + recv = self.process.fromchild.read() + if recv == "": break + tv = r.process_data(recv) + if tv != None: + cmdnum, error, length, is_last, result = tv + if result_code == None: result_code = int(error) + result_string += result + if is_last: + complete = True + else: + # in case there is anything left over we + # didn't parse + r = RecvPacket(r.buffer) + return result_code, result_string class Monotone: - def __init__(self, mt, dbfile): - self.mt = mt - self.dbfile = dbfile - self.base_command = "%s --db=%s" % (self.mt, pipes.quote(self.dbfile)) - self.automate = Automation(self.base_command) - def branches(self): - result = utility.run_command(self.base_command + " ls branches") - if result['exitcode'] != 0: - raise Exception("Unable to list branches: %s" % (result['childerr'])) - else: - return filter(None, result['fromchild'].split('\n')) - def tags(self): - result = utility.run_command(self.base_command + " ls tags") - if result['exitcode'] != 0: - raise Exception("Unable to list tags: %s" % (result['childerr'])) - else: - return map(lambda x: x.split(' ', 2), filter(None, result['fromchild'].split('\n'))) - def heads(self, branch): - error, result = self.automate.run('heads', [branch]) - if error != 0: - raise Exception("Unable to get list of heads") - else: - return filter(None, result.split('\n')) - def basic_io_parser(self, data): - """returns a list of lists of (key, value) tuples. hashes are returned with []s around - them; strings are returned raw.""" - def unescape_string_value(str): - rv = "" - is_terminated = False - in_escape = False - if str[0] != '"': - raise Exception("basic_io parse error; not a string.") - for c in str[1:]: - if in_escape: - if c != '\\' and c != '\"': - raise Exception(r'basic_io parse error; expected \" or \\') - rv += c - in_escape = False - else: - if c == '\\': - in_escape = True - elif c == '"': - if is_terminated: - raise Exception("basic_io parse error; string ends twice!") - is_terminated = True - else: - rv += c - return is_terminated, rv - rv = [] - stanza = [] - ongoing_string = None - for line in data.split('\n'): - if not ongoing_string: - if line == '' and len(stanza) != 0: - rv.append(stanza) - stanza = [] - m = basic_io_hex_re.match(line) - if m: - key, value = m.groups() - stanza.append((key, value)) - continue - m = basic_io_string_re.match(line) - if m: - key, value = m.groups() - is_terminated, e_value = unescape_string_value(value) - if not is_terminated: ongoing_string = value - else: stanza.append((key, e_value)) - continue - else: - ongoing_string += '\n' + line - is_terminated, e_value = unescape_string_value(ongoing_string) - if is_terminated: - stanza.append((key, e_value)) - ongoing_string = None - return rv - def certs(self, id): - error, data = self.automate.run('certs', [id]) - if error != 0: raise Exception("Error obtaining cert for %s: %s" % (id, data)) - return self.basic_io_parser(data) - def ancestors(self, ids): - error, result = self.automate.run('ancestors', ids) - if error != 0: - raise Exception("Unable to get ancestors") - else: - return filter(None, result.split('\n')) - def toposort(self, ids): - error, result = self.automate.run('toposort', ids) - if error != 0: - raise Exception("Unable to toposort") - else: - return filter(None, result.split('\n')) - def revision(self, id): - error, result = self.automate.run('get_revision', [id]) - if error != 0: - raise Exception("Unable to get revision: %s" % id) - rv = {} - cv = [] - for line in result.split('\n'): - if not line: - if len(cv) != 0: - stanza_type = cv[0][0] - if not rv.has_key(stanza_type): rv[stanza_type] = [] - rv[stanza_type].append(cv) - cv = [] - continue - m = basic_io_re.match(line) - if not m: continue - cv.append(m.groups()) - return rv - def manifest(self, id): - error, result = self.automate.run('get_manifest', [id]) - if error != 0: - raise Exception("Unable to get manifest: %s" % id) - rv = [] - for line in result.split('\n'): - m = manifest_entry_re.match(line) - if not m: continue - rv.append(m.groups()) - return rv - def file(self, id): - error, result = self.automate.run('get_file', [id]) - if error != 0: - raise Exception("Unable to get file: %s" % id) - else: - return result + def __init__(self, mt, dbfile): + self.mt = mt + self.dbfile = dbfile + self.base_command = "%s --db=%s" % (self.mt, pipes.quote(self.dbfile)) + self.automate = Automation(self.base_command) + def branches(self): + result = utility.run_command(self.base_command + " ls branches") + if result['exitcode'] != 0: + raise Exception("Unable to list branches: %s" % (result['childerr'])) + else: + return filter(None, result['fromchild'].split('\n')) + def tags(self): + result = utility.run_command(self.base_command + " ls tags") + if result['exitcode'] != 0: + raise Exception("Unable to list tags: %s" % (result['childerr'])) + else: + return map(lambda x: x.split(' ', 2), filter(None, result['fromchild'].split('\n'))) + def heads(self, branch): + error, result = self.automate.run('heads', [branch]) + if error != 0: + raise Exception("Unable to get list of heads") + else: + return filter(None, result.split('\n')) + def basic_io_parser(self, data): + """returns a list of lists of (key, value) tuples. hashes are returned with []s around + them; strings are returned raw.""" + def unescape_string_value(str): + rv = "" + is_terminated = False + in_escape = False + if str[0] != '"': + raise Exception("basic_io parse error; not a string.") + for c in str[1:]: + if in_escape: + if c != '\\' and c != '\"': + raise Exception(r'basic_io parse error; expected \" or \\') + rv += c + in_escape = False + else: + if c == '\\': + in_escape = True + elif c == '"': + if is_terminated: + raise Exception("basic_io parse error; string ends twice!") + is_terminated = True + else: + rv += c + return is_terminated, rv + rv = [] + stanza = [] + ongoing_string = None + for line in data.split('\n'): + if not ongoing_string: + if line == '' and len(stanza) != 0: + rv.append(stanza) + stanza = [] + m = basic_io_hex_re.match(line) + if m: + key, value = m.groups() + stanza.append((key, value)) + continue + m = basic_io_string_re.match(line) + if m: + key, value = m.groups() + is_terminated, e_value = unescape_string_value(value) + if not is_terminated: ongoing_string = value + else: stanza.append((key, e_value)) + continue + else: + ongoing_string += '\n' + line + is_terminated, e_value = unescape_string_value(ongoing_string) + if is_terminated: + stanza.append((key, e_value)) + ongoing_string = None + return rv + def certs(self, id): + error, data = self.automate.run('certs', [id]) + if error != 0: raise Exception("Error obtaining cert for %s: %s" % (id, data)) + return self.basic_io_parser(data) + def ancestors(self, ids): + error, result = self.automate.run('ancestors', ids) + if error != 0: + raise Exception("Unable to get ancestors") + else: + return filter(None, result.split('\n')) + def toposort(self, ids): + error, result = self.automate.run('toposort', ids) + if error != 0: + raise Exception("Unable to toposort") + else: + return filter(None, result.split('\n')) + def revision(self, id): + error, result = self.automate.run('get_revision', [id]) + if error != 0: + raise Exception("Unable to get revision: %s" % id) + rv = {} + cv = [] + for line in result.split('\n'): + if not line: + if len(cv) != 0: + stanza_type = cv[0][0] + if not rv.has_key(stanza_type): rv[stanza_type] = [] + rv[stanza_type].append(cv) + cv = [] + continue + m = basic_io_re.match(line) + if not m: continue + cv.append(m.groups()) + return rv + def manifest(self, id): + error, result = self.automate.run('get_manifest', [id]) + if error != 0: + raise Exception("Unable to get manifest: %s" % id) + rv = [] + for line in result.split('\n'): + m = manifest_entry_re.match(line) + if not m: continue + rv.append(m.groups()) + return rv + def file(self, id): + error, result = self.automate.run('get_file', [id]) + if error != 0: + raise Exception("Unable to get file: %s" % id) + else: + return result - def annotate(self, id, file): - result = utility.run_command(self.base_command + " annotate --revision=%s %s" % (pipes.quote(id), pipes.quote(file))) - if result['exitcode'] != 0: - raise Exception("Unable to annotate file: %s using command '%s'" % (result['childerr'], result['run_command'])) - else: - return result['fromchild'] - - def diff(self, rev_from, rev_to, files=None): - command = self.base_command + " diff -r %s -r %s" % (pipes.quote(rev_from), pipes.quote(rev_to)) - if files != None: command += ' ' + ' '.join(map(pipes.quote, files)) - import syslog - syslog.syslog(command) - result = utility.run_command(command) - if result['exitcode'] != 0: - raise Exception("Unable to calculate diff: %s" % (result['childerr'])) - else: - return result['fromchild'] - def log(self, ids, limit=0): - rv = [] - entry = None - command = self.base_command + " log " + ' '.join(map(lambda x: '-r ' + pipes.quote(x), ids)) - if limit > 0: command += " --last=%d" % (limit) - iterator = utility.iter_command(command) - for line in iterator: - if dash_re.match(line): - entry = {} - if entry == None: continue - if not line: - rv.append(entry) - entry = None - continue - m = log_entry_re.match(line) - if m: - attr, value = m.groups() - if not entry.has_key(attr): entry[attr] = [] - entry[attr].append(value) - # clean up; otherwise we'll leak filehandlers - map(None, iterator) - if entry: rv.append(entry) - return rv - def ancestry_graph(self, graphopts, id, limit=0): - def dot_escape(s): - # kinda paranoid, should probably revise later - permitted=string.digits + string.letters + ' -<>-:,address@hidden&.+_~?/' - return ''.join(filter(lambda x: x in permitted, s)) - graphdir = graphopts['directory'] - graphuri = graphopts['uri'] - graph_id = "%s.%d" % (id, limit) - rv = { - 'dot_file' : os.path.join(graphdir, graph_id + ".dot"), - 'image_file' : os.path.join(graphdir, graph_id + ".png"), - 'imagemap_file' : os.path.join(graphdir, graph_id + ".html"), - 'dot_uri' : "%s/%s.dot" % (graphuri, urllib.quote(graph_id)), - 'image_uri' : "%s/%s.png" % (graphuri, urllib.quote(graph_id)), - 'imagemap_uri' : "%s/%s.html" % (graphuri, urllib.quote(graph_id)), - } - need_access = ['dot_file', 'image_file', 'imagemap_file'] - missing = filter(lambda x: x != True, map(lambda x: os.access(rv[x], os.R_OK), need_access)) - if len(missing) == 0: - rv['cached'] = True - return rv - contents = 'digraph ancestry {\nratio=compress\nnodesep=0.1\nranksep=0.2\nedge [dir=back];\n' - revisions = {} - for attrs in self.log([id], limit): - if not attrs.has_key("Revision") or not attrs.has_key("Ancestor"): - continue - revision = attrs['Revision'][0] - revisions[revision] = attrs - for ancestor in attrs['Ancestor']: - if len(ancestor) == 0: continue - if not revisions.has_key(ancestor): revisions[ancestor] = None - contents += '"%s"->"%s"[href="getdiff.py?id1=%s&id2=%s"]\n' % (urllib.quote(revision), urllib.quote(ancestor), urllib.quote(ancestor), urllib.quote(revision)) - for revision in revisions.keys(): - label = "%s..." % (revision[0:8]) - attrs = revisions[revision] - if attrs == None: - # fill in the gaps; would be nice to clean this up. - # shouldn't take long, anyway. - attrs = self.log([revision], 1)[0] - if attrs.has_key('Date'): - d = dot_escape(attrs['Date'][0]) - d = d[0:d.find("T")] - label += " on %s" % d - if attrs.has_key('Author'): - label += "\\n%s" % (dot_escape(attrs['Author'][0])) - override_fillcolor = colour_from_string(attrs['Author'][0]) - else: override_fillcolor = None - #opts = 'fontname=Windsor,fontsize=8,shape=box,href="revision.psp?id=%s",label="%s"' % (urllib.quote(revision), label) - opts = 'label="%s"' % label #revision[0:8] - for opt in graphopts['nodeopts']: - if opt == 'fillcolor' and override_fillcolor != None: continue - opts += ',%s="%s"' % (opt, graphopts['nodeopts'][opt]) - if revision == id: opts += ",color=blue" - opts += ',href="revision.psp?id=%s"' % urllib.quote(revision) - if override_fillcolor != None: opts += ',fillcolor="#%s"' % (override_fillcolor) - #opts += ',tooltip="by %s at %s on %s"' % (dot_escape(attrs['ChangeLog'][0]), - # dot_escape(attrs['Date'][0]), - # dot_escape(attrs['Branch'][0])) - contents += '"%s" [%s]\n' % (revision, opts) - contents += "}\n" - open(rv['dot_file'], 'w').write(contents) - os.system("%s -Tcmapx -o %s -Tpng -o %s %s" % (graphopts['dot'], pipes.quote(rv['imagemap_file']), rv['image_file'], rv['dot_file'])) - rv['cached'] = False - return rv + def annotate(self, id, file): + result = utility.run_command(self.base_command + " annotate --revision=%s %s" % (pipes.quote(id), pipes.quote(file))) + if result['exitcode'] != 0: + raise Exception("Unable to annotate file: %s using command '%s'" % (result['childerr'], result['run_command'])) + else: + return result['fromchild'] + + def diff(self, rev_from, rev_to, files=None): + command = self.base_command + " diff -r %s -r %s" % (pipes.quote(rev_from), pipes.quote(rev_to)) + if files != None: command += ' ' + ' '.join(map(pipes.quote, files)) + import syslog + syslog.syslog(command) + result = utility.run_command(command) + if result['exitcode'] != 0: + raise Exception("Unable to calculate diff: %s" % (result['childerr'])) + else: + return result['fromchild'] + def log(self, ids, limit=0): + rv = [] + entry = None + command = self.base_command + " log " + ' '.join(map(lambda x: '-r ' + pipes.quote(x), ids)) + if limit > 0: command += " --last=%d" % (limit) + iterator = utility.iter_command(command) + for line in iterator: + if dash_re.match(line): + entry = {} + if entry == None: continue + if not line: + rv.append(entry) + entry = None + continue + m = log_entry_re.match(line) + if m: + attr, value = m.groups() + if not entry.has_key(attr): entry[attr] = [] + entry[attr].append(value) + # clean up; otherwise we'll leak filehandlers + map(None, iterator) + if entry: rv.append(entry) + return rv + def ancestry_graph(self, graphopts, id, limit=0): + def dot_escape(s): + # kinda paranoid, should probably revise later + permitted=string.digits + string.letters + ' -<>-:,address@hidden&.+_~?/' + return ''.join(filter(lambda x: x in permitted, s)) + graphdir = graphopts['directory'] + graphuri = graphopts['uri'] + graph_id = "%s.%d" % (id, limit) + rv = { + 'dot_file' : os.path.join(graphdir, graph_id + ".dot"), + 'image_file' : os.path.join(graphdir, graph_id + ".png"), + 'imagemap_file' : os.path.join(graphdir, graph_id + ".html"), + 'dot_uri' : "%s/%s.dot" % (graphuri, urllib.quote(graph_id)), + 'image_uri' : "%s/%s.png" % (graphuri, urllib.quote(graph_id)), + 'imagemap_uri' : "%s/%s.html" % (graphuri, urllib.quote(graph_id)), + } + need_access = ['dot_file', 'image_file', 'imagemap_file'] + missing = filter(lambda x: x != True, map(lambda x: os.access(rv[x], os.R_OK), need_access)) + if len(missing) == 0: + rv['cached'] = True + return rv + contents = 'digraph ancestry {\nratio=compress\nnodesep=0.1\nranksep=0.2\nedge [dir=back];\n' + revisions = {} + for attrs in self.log([id], limit): + if not attrs.has_key("Revision") or not attrs.has_key("Ancestor"): + continue + revision = attrs['Revision'][0] + revisions[revision] = attrs + for ancestor in attrs['Ancestor']: + if len(ancestor) == 0: continue + if not revisions.has_key(ancestor): revisions[ancestor] = None + contents += '"%s"->"%s"[href="getdiff.py?id1=%s&id2=%s"]\n' % (urllib.quote(revision), urllib.quote(ancestor), urllib.quote(ancestor), urllib.quote(revision)) + for revision in revisions.keys(): + label = "%s..." % (revision[0:8]) + attrs = revisions[revision] + if attrs == None: + # fill in the gaps; would be nice to clean this up. + # shouldn't take long, anyway. + attrs = self.log([revision], 1)[0] + if attrs.has_key('Date'): + d = dot_escape(attrs['Date'][0]) + d = d[0:d.find("T")] + label += " on %s" % d + if attrs.has_key('Author'): + label += "\\n%s" % (dot_escape(attrs['Author'][0])) + override_fillcolor = colour_from_string(attrs['Author'][0]) + else: override_fillcolor = None + #opts = 'fontname=Windsor,fontsize=8,shape=box,href="revision.psp?id=%s",label="%s"' % (urllib.quote(revision), label) + opts = 'label="%s"' % label #revision[0:8] + for opt in graphopts['nodeopts']: + if opt == 'fillcolor' and override_fillcolor != None: continue + opts += ',%s="%s"' % (opt, graphopts['nodeopts'][opt]) + if revision == id: opts += ",color=blue" + opts += ',href="revision.psp?id=%s"' % urllib.quote(revision) + if override_fillcolor != None: opts += ',fillcolor="#%s"' % (override_fillcolor) + #opts += ',tooltip="by %s at %s on %s"' % (dot_escape(attrs['ChangeLog'][0]), + # dot_escape(attrs['Date'][0]), + # dot_escape(attrs['Branch'][0])) + contents += '"%s" [%s]\n' % (revision, opts) + contents += "}\n" + open(rv['dot_file'], 'w').write(contents) + os.system("%s -Tcmapx -o %s -Tpng -o %s %s" % (graphopts['dot'], pipes.quote(rv['imagemap_file']), rv['image_file'], rv['dot_file'])) + rv['cached'] = False + return rv def is_valid_id(s): - return len(s) == 40 and id_re.match(s) != None + return len(s) == 40 and id_re.match(s) != None import popen2 import select from utility import set_nonblocking - + packet_header_re = re.compile(r'(\d+):(\d+):([lm]):(\d+):') class RecvPacket: - "A packet received from monotone automate stdio" - def __init__(self, init_buffer=""): - self.buffer = init_buffer - self.cmdnum = None - self.error = None - self.length = None - self.is_last = None - self.result = "" - def process_data(self, data): - self.buffer += data - if self.length == None: - m = packet_header_re.match(data) - if m: - self.cmdnum, self.error, pstate, self.length = m.groups() - self.length = int(self.length) - self.is_last = pstate == "l" - self.buffer = self.buffer[m.end(m.lastindex)+1:] - if self.length != None and len(self.result) < self.length: - needed = self.length - len(self.result) - if len(self.buffer) >= needed: - available = needed - else: - available = len(self.buffer) - self.result += self.buffer[:available] - self.buffer = self.buffer[available:] - if len(self.result) == self.length: - return (self.cmdnum, self.error, self.length, self.is_last, self.result) - else: return None + "A packet received from monotone automate stdio" + def __init__(self, init_buffer=""): + self.buffer = init_buffer + self.cmdnum = None + self.error = None + self.length = None + self.is_last = None + self.result = "" + def process_data(self, data): + self.buffer += data + if self.length == None: + m = packet_header_re.match(data) + if m: + self.cmdnum, self.error, pstate, self.length = m.groups() + self.length = int(self.length) + self.is_last = pstate == "l" + self.buffer = self.buffer[m.end(m.lastindex)+1:] + if self.length != None and len(self.result) < self.length: + needed = self.length - len(self.result) + if len(self.buffer) >= needed: + available = needed + else: + available = len(self.buffer) + self.result += self.buffer[:available] + self.buffer = self.buffer[available:] + if len(self.result) == self.length: + return (self.cmdnum, self.error, self.length, self.is_last, self.result) + else: return None ============================================================ --- revision.psp 54379dc8fb3e0fd02a084fcd92bdb0f41a85de61 +++ revision.psp 82d35235b0c254d2ec2552d53b72f17242f462b1 @@ -11,47 +11,47 @@ # def prettify(s): - return ' '.join(map(lambda x: hq(x[0].upper() + x[1:]), s.replace("_", " ").split(" "))) + return ' '.join(map(lambda x: hq(x[0].upper() + x[1:]), s.replace("_", " ").split(" "))) psp.set_error_page("error.psp") if not form.has_key('id'): - raise Exception("No revision ID specified.") + raise Exception("No revision ID specified.") id = form['id'] if not monotone.is_valid_id(id): - raise Exception("Specified revision ID is not valid.") + raise Exception("Specified revision ID is not valid.") # read certificates certs = mt.certs(id) branches, cert_table = [], "" for cert in certs: - name, value = None, None - for k, v in cert: - if k == "name": name = v - elif k == "value": value = v - if name == None or value == None: continue - if name == "branch": - branches.append(value) - value = link("branch", value) - else: - value = '
'.join(map(hq, value.split('\n'))) - cert_table += '%s%s' % (prettify(name), value) + name, value = None, None + for k, v in cert: + if k == "name": name = v + elif k == "value": value = v + if name == None or value == None: continue + if name == "branch": + branches.append(value) + value = link("branch", value) + else: + value = '
'.join(map(hq, value.split('\n'))) + cert_table += '%s%s' % (prettify(name), value) info = { - 'title' : "Revision %s" % (hq(id)), - 'branch_rss' : branches - } + 'title' : "Revision %s" % (hq(id)), + 'branch_rss' : branches + } req.write(template.header(info)) # generate the revision graph ancestry_limit = 10 ancestry_maximum = 100 try: - if form.has_key('ancestry_limit'): - ancestry_limit = int(form['ancestry_limit']) + if form.has_key('ancestry_limit'): + ancestry_limit = int(form['ancestry_limit']) except: pass if ancestry_limit == 0 or ancestry_limit > ancestry_maximum: - ancestry_limit = ancestry_maximum + ancestry_limit = ancestry_maximum ancestry_graph = mt.ancestry_graph(config.graphopts, id, ancestry_limit) req.write(open(ancestry_graph['imagemap_file']).read()) @@ -68,8 +68,8 @@

Note: no manifest is associated with this revision and hence it contains no files.

<% else: - # ugh, need to wrap things nicer - req.write('''

Files contained in this revision are listed in its %s.''' % (link("manifest", id, "manifest"))) + # ugh, need to wrap things nicer + req.write('''

Files contained in this revision are listed in its %s.''' % (link("manifest", id, "manifest"))) %> @@ -85,39 +85,39 @@ <% old_revision = None for key in revision.keys(): - value = "" - for stanza in revision[key]: - type = stanza[0][0] - if type == "patch": - fname, from_id, to_id = stanza[0][1], stanza[1][1], stanza[2][1] - if not from_id: - value += 'Add file %s with revision %s
' % (hq(fname), link("file", [id, fname])) - else: - # possible bug here; we might need N diff links where N is the number of ancestors - value += 'Patch file %s from %s to %s (%s)
' % (hq(fname), link("download", [from_id, fname]), link("download", [to_id, fname]), link("diff", [old_revision, id, fname])) - elif type == "old_revision": - old_revision, old_manifest = stanza[0][1], stanza[1][1] - value += 'Old revision is: %s (%s)
Old manifest: %s
' % (link("revision", old_revision), link("diff", [old_revision, id]), link("manifest", old_revision)) - elif type == "new_manifest": - # swallow this, it's not useful - new_manifest = stanza[0][1] - value += 'New manifest is: %s
' % (link("manifest", id)) - elif type == "add_file": - new_file = stanza[0][1] - value += "Add file: %s
" % (hq(new_file)) - elif type == "delete_file": - delete_file = stanza[0][1] - value += "Delete file: %s
" % (hq(delete_file)) - elif type == "delete_dir": - delete_directory = stanza[0][1] - value += "Delete directory: %s
" % (hq(delete_directory)) - elif type == "rename_file": - old_name, new_name = stanza[0][1], stanza[1][1] - value += "Rename file %s as %s
" % (hq(old_name), hq(new_name)) - elif type == "rename_dir": - old_name, new_name = stanza[0][1], stanza[1][1] - value += "Rename directory %s as %s
" % (hq(old_name), hq(new_name)) - req.write('%s%s' % (prettify(key), value)) + value = "" + for stanza in revision[key]: + type = stanza[0][0] + if type == "patch": + fname, from_id, to_id = stanza[0][1], stanza[1][1], stanza[2][1] + if not from_id: + value += 'Add file %s with revision %s
' % (hq(fname), link("file", [id, fname])) + else: + # possible bug here; we might need N diff links where N is the number of ancestors + value += 'Patch file %s from %s to %s (%s)
' % (hq(fname), link("download", [from_id, fname]), link("download", [to_id, fname]), link("diff", [old_revision, id, fname])) + elif type == "old_revision": + old_revision, old_manifest = stanza[0][1], stanza[1][1] + value += 'Old revision is: %s (%s)
Old manifest: %s
' % (link("revision", old_revision), link("diff", [old_revision, id]), link("manifest", old_revision)) + elif type == "new_manifest": + # swallow this, it's not useful + new_manifest = stanza[0][1] + value += 'New manifest is: %s
' % (link("manifest", id)) + elif type == "add_file": + new_file = stanza[0][1] + value += "Add file: %s
" % (hq(new_file)) + elif type == "delete_file": + delete_file = stanza[0][1] + value += "Delete file: %s
" % (hq(delete_file)) + elif type == "delete_dir": + delete_directory = stanza[0][1] + value += "Delete directory: %s
" % (hq(delete_directory)) + elif type == "rename_file": + old_name, new_name = stanza[0][1], stanza[1][1] + value += "Rename file %s as %s
" % (hq(old_name), hq(new_name)) + elif type == "rename_dir": + old_name, new_name = stanza[0][1], stanza[1][1] + value += "Rename directory %s as %s
" % (hq(old_name), hq(new_name)) + req.write('%s%s' % (prettify(key), value)) %> ============================================================ --- tags.psp 9b74bcccf9f006be39cf615e707cbb0d4c85ede4 +++ tags.psp 6fdb677d68ef8f35cf439a2d7c34e37ff29cd9af @@ -26,9 +26,9 @@ <% - tags.sort(lambda x,y: cmp(x[0], y[0])) - for tag in tags: - req.write('' % (link("revision", tag[1], tag[0]), hq(tag[2]))) + tags.sort(lambda x,y: cmp(x[0], y[0])) + for tag in tags: + req.write('' % (link("revision", tag[1], tag[0]), hq(tag[2]))) %>
TagSigned by
%s%s
%s%s
============================================================ --- tarofbranch.psp 99f001ed6a4855e6bbe97dbaca580fd6673bfae1 +++ tarofbranch.psp 1af97621a341b580ee0f92f701cf18ae95087c83 @@ -5,7 +5,7 @@ psp.set_error_page("error.psp") if not form.has_key('branch'): - raise Exception("No branch specified.") + raise Exception("No branch specified.") branch = form['branch'] # tarofbranch.psp @@ -16,14 +16,14 @@ heads = mt.heads(branch) if len(heads) == 0: - raise Exception("No head ID can be determined for this branch.") + raise Exception("No head ID can be determined for this branch.") elif len(heads) == 1: - revision = mt.revision(heads[0]) - manifest_id = revision['new_manifest'][0][0][1] - psp.redirect("gettar.py?id=%s" % (urllib.quote(manifest_id))) + revision = mt.revision(heads[0]) + manifest_id = revision['new_manifest'][0][0][1] + psp.redirect("gettar.py?id=%s" % (urllib.quote(manifest_id))) else: - info = {'title' : "Latest tar file of branch %s" % (hq(branch))} - req.write(template.header(info)) + info = {'title' : "Latest tar file of branch %s" % (hq(branch))} + req.write(template.header(info)) %>

The branch you have selected has multiple head revisions The head @@ -34,10 +34,10 @@ <% - for id in heads: - revision = mt.revision(id) - manifest_id = revision['new_manifest'][0][0][1] - req.write('' % (link("revision", id), link("tar", manifest_id, "download tar archive"))) + for id in heads: + revision = mt.revision(id) + manifest_id = revision['new_manifest'][0][0][1] + req.write('' % (link("revision", id), link("tar", manifest_id, "download tar archive"))) %>
Head revisionTar file
%s%s
%s%s
============================================================ --- utility.py c1f6a1ef480d6270c208d9b74a6dcbe1b8be7a06 +++ utility.py 78cd2c53af05e63bb76c097dc832b6f75394e40d @@ -5,96 +5,96 @@ import os def set_nonblocking(fd): - fl = fcntl.fcntl(fd, fcntl.F_GETFL) - fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NDELAY) + fl = fcntl.fcntl(fd, fcntl.F_GETFL) + fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NDELAY) def run_command(command, timeout=None, to_child=None): - "returns a tuple of (was_timeout, exit_code, data_read)" - p = popen2.Popen3(command, capturestderr=True) - set_nonblocking(p.fromchild) - set_nonblocking(p.childerr) - fromchild_read = "" - childerr_read = "" - was_timeout = False - if to_child != None: - p.tochild.write(to_child) - p.tochild.close() - while 1: - ro, rw, re = select.select([p.fromchild], [], [p.childerr], timeout) - if not ro and not rw and not re: - was_timeout = True - break - if p.fromchild in ro: - recv = p.fromchild.read() - if recv == "": break - fromchild_read += recv - if p.childerr in re: - recv = p.childerr.read() - if recv == "": break - childerr_read += recv - if not was_timeout: - # check for any data we might have missed (due to a premature break) - # (if there isn't anything we just get a IOError, which we don't mind - try: fromchild_read += p.fromchild.read() - except IOError: pass - try: childerr_read += p.childerr.read() - except IOError: pass - p.fromchild.close() - # if there wasn't a timeout, the program should have exited; in which case we should wait() for it - # otherwise, it might be hung, so the parent should wait for it. - # (wrap in a try: except: just in case some other thread happens to wait() and grab ours; god wrapping - # python around UNIX is horrible sometimes) - exitcode = None - try: - if not was_timeout: exitcode = p.wait() >> 8 - except: pass - return { 'run_command' : command, - 'timeout' : was_timeout, - 'exitcode' : exitcode, - 'fromchild' : fromchild_read, - 'childerr' : childerr_read } + "returns a tuple of (was_timeout, exit_code, data_read)" + p = popen2.Popen3(command, capturestderr=True) + set_nonblocking(p.fromchild) + set_nonblocking(p.childerr) + fromchild_read = "" + childerr_read = "" + was_timeout = False + if to_child != None: + p.tochild.write(to_child) + p.tochild.close() + while 1: + ro, rw, re = select.select([p.fromchild], [], [p.childerr], timeout) + if not ro and not rw and not re: + was_timeout = True + break + if p.fromchild in ro: + recv = p.fromchild.read() + if recv == "": break + fromchild_read += recv + if p.childerr in re: + recv = p.childerr.read() + if recv == "": break + childerr_read += recv + if not was_timeout: + # check for any data we might have missed (due to a premature break) + # (if there isn't anything we just get a IOError, which we don't mind + try: fromchild_read += p.fromchild.read() + except IOError: pass + try: childerr_read += p.childerr.read() + except IOError: pass + p.fromchild.close() + # if there wasn't a timeout, the program should have exited; in which case we should wait() for it + # otherwise, it might be hung, so the parent should wait for it. + # (wrap in a try: except: just in case some other thread happens to wait() and grab ours; god wrapping + # python around UNIX is horrible sometimes) + exitcode = None + try: + if not was_timeout: exitcode = p.wait() >> 8 + except: pass + return { 'run_command' : command, + 'timeout' : was_timeout, + 'exitcode' : exitcode, + 'fromchild' : fromchild_read, + 'childerr' : childerr_read } def iter_command(command, timeout=None): - p = popen2.Popen3(command, capturestderr=True) - set_nonblocking(p.fromchild) - set_nonblocking(p.childerr) - fromchild_read = "" - childerr_read = "" - was_timeout = False - while 1: - ro, rw, re = select.select([p.fromchild], [], [p.childerr], timeout) - if not ro and not rw and not re: - was_timeout = True - break - if p.fromchild in ro: - recv = p.fromchild.read() - if recv == "": break - fromchild_read += recv - while 1: - nl = fromchild_read.find('\n') - if nl == -1: break - yield fromchild_read[:nl] - fromchild_read = fromchild_read[nl+1:] - if p.childerr in re: - recv = p.childerr.read() - if recv == "": break - childerr_read += recv - if not was_timeout: - # check for any data we might have missed (due to a premature break) - # (if there isn't anything we just get a IOError, which we don't mind - try: fromchild_read += p.fromchild.read() - except IOError: pass - try: childerr_read += p.childerr.read() - except IOError: pass - p.fromchild.close() - p.tochild.close() - # yield anything left over - to_yield = fromchild_read.split('\n') - while len(to_yield): yield to_yield.pop() - # call wait() - try: - if not was_timeout: p.wait() - except: pass - if len(childerr_read): raise Exception("data on stderr (command is %s)" % command, childerr_read) - if was_timeout: raise Exception("command timeout") + p = popen2.Popen3(command, capturestderr=True) + set_nonblocking(p.fromchild) + set_nonblocking(p.childerr) + fromchild_read = "" + childerr_read = "" + was_timeout = False + while 1: + ro, rw, re = select.select([p.fromchild], [], [p.childerr], timeout) + if not ro and not rw and not re: + was_timeout = True + break + if p.fromchild in ro: + recv = p.fromchild.read() + if recv == "": break + fromchild_read += recv + while 1: + nl = fromchild_read.find('\n') + if nl == -1: break + yield fromchild_read[:nl] + fromchild_read = fromchild_read[nl+1:] + if p.childerr in re: + recv = p.childerr.read() + if recv == "": break + childerr_read += recv + if not was_timeout: + # check for any data we might have missed (due to a premature break) + # (if there isn't anything we just get a IOError, which we don't mind + try: fromchild_read += p.fromchild.read() + except IOError: pass + try: childerr_read += p.childerr.read() + except IOError: pass + p.fromchild.close() + p.tochild.close() + # yield anything left over + to_yield = fromchild_read.split('\n') + while len(to_yield): yield to_yield.pop() + # call wait() + try: + if not was_timeout: p.wait() + except: pass + if len(childerr_read): raise Exception("data on stderr (command is %s)" % command, childerr_read) + if was_timeout: raise Exception("command timeout") ============================================================ --- wrapper.py cecd28873835961f94fcc047350c89e6bba4151f +++ wrapper.py 904f434b11952f8634568a5de0f80298efb872df @@ -17,126 +17,126 @@ sane_uri_re = re.compile('^\w+$') def get_file(req): - mt = Monotone(config.monotone, config.dbfile) - form = util.FieldStorage(req) - if not form.has_key('id'): - return apache.HTTP_BAD_REQUEST - id = form['id'] - if not monotone.is_valid_id(id): - return apache.HTTP_BAD_REQUEST - if form.has_key('plain'): - mime_type = "text/plain" - else: - mime_type = None - if form.has_key('path'): - if mime_type == None: mime_type = mimetypes.guess_type(form['path'])[0] - req.headers_out["Content-Disposition"] = "attachment; filename=%s" % urllib.quote(os.path.split(form['path'])[-1]) - if mime_type == None: mime_type = "text/plain" - req.content_type = mime_type - req.write(mt.file(id)) - return apache.OK + mt = Monotone(config.monotone, config.dbfile) + form = util.FieldStorage(req) + if not form.has_key('id'): + return apache.HTTP_BAD_REQUEST + id = form['id'] + if not monotone.is_valid_id(id): + return apache.HTTP_BAD_REQUEST + if form.has_key('plain'): + mime_type = "text/plain" + else: + mime_type = None + if form.has_key('path'): + if mime_type == None: mime_type = mimetypes.guess_type(form['path'])[0] + req.headers_out["Content-Disposition"] = "attachment; filename=%s" % urllib.quote(os.path.split(form['path'])[-1]) + if mime_type == None: mime_type = "text/plain" + req.content_type = mime_type + req.write(mt.file(id)) + return apache.OK def get_diff(req): - mt = Monotone(config.monotone, config.dbfile) - form = util.FieldStorage(req) - if not form.has_key('id1') or not form.has_key('id2'): - return apache.HTTP_BAD_REQUEST - if form.has_key('fname'): files = [form['fname']] - else: files = None - id1, id2 = form['id1'], form['id2'] - if not monotone.is_valid_id(id1) or not monotone.is_valid_id(id2): - return apache.HTTP_BAD_REQUEST - req.content_type = "text/plain" - req.write(mt.diff(id1, id2, files)) - return apache.OK + mt = Monotone(config.monotone, config.dbfile) + form = util.FieldStorage(req) + if not form.has_key('id1') or not form.has_key('id2'): + return apache.HTTP_BAD_REQUEST + if form.has_key('fname'): files = [form['fname']] + else: files = None + id1, id2 = form['id1'], form['id2'] + if not monotone.is_valid_id(id1) or not monotone.is_valid_id(id2): + return apache.HTTP_BAD_REQUEST + req.content_type = "text/plain" + req.write(mt.diff(id1, id2, files)) + return apache.OK def get_tar(req): - "make a tar file out of a given manifest ID" - class DummyFile: - def __init__(self, buf): - self.buf = buf - def seek(offset, whence=None): - # blah - return - def read(self, size): - rv, nb = self.buf[:size], self.buf[size:] - self.buf = nb - return rv - def write(self, s): - self.buf += s - mt = Monotone(config.monotone, config.dbfile) - form = util.FieldStorage(req) - if not form.has_key('id'): - return apache.HTTP_BAD_REQUEST - id = form['id'] - tar_file = DummyFile("") - tar_file_name = "%s.tar" % (id) - req.content_type = 'application/x-tar' - req.headers_out["Content-Disposition"] = "attachment; filename=%s" % tar_file_name - tf = tarfile.open(mode="w", fileobj=tar_file) - for fileid, filename in mt.manifest(id): - data = mt.file(fileid) - ti = tarfile.TarInfo() - ti.mode = 00700 - ti.mtime = 0 - ti.type = tarfile.REGTYPE - ti.uid = 0 - ti.gid = 0 - ti.name = os.path.join(id, filename) - ti.size = len(data) - tf.addfile(ti, DummyFile(data)) - tf.close() - req.write(tar_file.buf) - return apache.OK + "make a tar file out of a given manifest ID" + class DummyFile: + def __init__(self, buf): + self.buf = buf + def seek(offset, whence=None): + # blah + return + def read(self, size): + rv, nb = self.buf[:size], self.buf[size:] + self.buf = nb + return rv + def write(self, s): + self.buf += s + mt = Monotone(config.monotone, config.dbfile) + form = util.FieldStorage(req) + if not form.has_key('id'): + return apache.HTTP_BAD_REQUEST + id = form['id'] + tar_file = DummyFile("") + tar_file_name = "%s.tar" % (id) + req.content_type = 'application/x-tar' + req.headers_out["Content-Disposition"] = "attachment; filename=%s" % tar_file_name + tf = tarfile.open(mode="w", fileobj=tar_file) + for fileid, filename in mt.manifest(id): + data = mt.file(fileid) + ti = tarfile.TarInfo() + ti.mode = 00700 + ti.mtime = 0 + ti.type = tarfile.REGTYPE + ti.uid = 0 + ti.gid = 0 + ti.name = os.path.join(id, filename) + ti.size = len(data) + tf.addfile(ti, DummyFile(data)) + tf.close() + req.write(tar_file.buf) + return apache.OK handlers = { - 'getfile.py' : get_file, - 'getdiff.py' : get_diff, - 'gettar.py' : get_tar + 'getfile.py' : get_file, + 'getdiff.py' : get_diff, + 'gettar.py' : get_tar } def cleanup(req, vars): - mt = vars['mt'] - mt.automate.stop() - del mt + mt = vars['mt'] + mt.automate.stop() + del mt def handler(req): - req.content_type = "text/plain" - uri = req.uri - slash = uri.rfind('/') - if slash <> -1: uri = uri[slash+1:] + req.content_type = "text/plain" + uri = req.uri + slash = uri.rfind('/') + if slash <> -1: uri = uri[slash+1:] - # - # these handlers don't use PSP, for example if they need - # to return binary or otherwise pristine data to the user - # agent - # - if handlers.has_key(uri): - return handlers[uri](req) + # + # these handlers don't use PSP, for example if they need + # to return binary or otherwise pristine data to the user + # agent + # + if handlers.has_key(uri): + return handlers[uri](req) - # - # PSP or 404 - # - try: - if uri.endswith('.psp') and sane_uri_re.match(uri[:-4]): - req.content_type = "text/html" - mt = Monotone(config.monotone, config.dbfile) - def our_link (link_type, link_to, description=None, no_quote=False): - return common.link(mt, link_type, link_to, description, no_quote) - vars = { - 'mt' : mt, - 'link' : our_link, - 'hq' : common.html_escape(), - 'template' : Template() - } - req.register_cleanup(cleanup, (req, vars)) - # most monotone output is utf8 - req.content_type = "text/html; charset=utf-8" - instance = psp.PSP(req, filename=uri, vars=vars) - instance.run() - return apache.OK - except ValueError: - return apache.HTTP_NOT_FOUND + # + # PSP or 404 + # + try: + if uri.endswith('.psp') and sane_uri_re.match(uri[:-4]): + req.content_type = "text/html" + mt = Monotone(config.monotone, config.dbfile) + def our_link (link_type, link_to, description=None, no_quote=False): + return common.link(mt, link_type, link_to, description, no_quote) + vars = { + 'mt' : mt, + 'link' : our_link, + 'hq' : common.html_escape(), + 'template' : Template() + } + req.register_cleanup(cleanup, (req, vars)) + # most monotone output is utf8 + req.content_type = "text/html; charset=utf-8" + instance = psp.PSP(req, filename=uri, vars=vars) + instance.run() + return apache.OK + except ValueError: + return apache.HTTP_NOT_FOUND - return apache.HTTP_NOT_FOUND + return apache.HTTP_NOT_FOUND