# # # patch "mtn.py" # from [c65916ab881226ab1ca6b4d44abbc5ccfa8badbd] # to [5d4b39dca29ba0424ee0960951b19ae25641a4dd] # # patch "viewmtn.py" # from [e77b0ab87261e18f6f54cf23517f1ff9c246c7ab] # to [88e9dbedf9327ae01d3866eac3769733b9197efc] # ============================================================ --- mtn.py c65916ab881226ab1ca6b4d44abbc5ccfa8badbd +++ mtn.py 5d4b39dca29ba0424ee0960951b19ae25641a4dd @@ -7,6 +7,7 @@ from common import set_nonblocking, term import threading import popen2 from common import set_nonblocking, terminate_popen3 +from traceback import format_exc import web from web import debug @@ -95,7 +96,7 @@ class Automate(Runner): break except: # mtn has died underneath the automate; restart it - debug("exception writing to child process; attempting restart.") + debug("exception writing to child process; attempting restart: %s" % format_exc()) self.stop() import sys ============================================================ --- viewmtn.py e77b0ab87261e18f6f54cf23517f1ff9c246c7ab +++ viewmtn.py 88e9dbedf9327ae01d3866eac3769733b9197efc @@ -39,6 +39,25 @@ static_join = lambda path: urlparse.urlj dynamic_join = lambda path: urlparse.urljoin(config.dynamic_uri_path, path) static_join = lambda path: urlparse.urljoin(config.static_uri_path, path) +def quicklog(changelog): + interesting_line = None + for line in changelog: + line = line.strip() + if line: + interesting_line = line + break + if not interesting_line: + return "" + if interesting_line.startswith('*'): + interesting_line = interesting_line[1:].strip() + return interesting_line + +def normalise_changelog(changelog): + changelog = map(hq, changelog.split('\n')) + if changelog and changelog[-1] == '': + changelog = changelog[:-1] + return changelog + class Link: def __init__(self, description=None, link_type=None, **kwargs): self.relative_uri = None @@ -109,7 +128,7 @@ def certs_for_template(cert_gen): def certs_for_template(cert_gen): for cert in cert_gen: if cert[0] == 'key' and len(cert) != 10: - raise Exception("Not a correcly formatted certificate: %s" % cert) + raise Exception("Not a correctly formatted certificate: %s" % cert) if cert[3] != 'ok': raise Exception("Certificate failed check.") @@ -293,11 +312,6 @@ class BranchChanges: return certs_for_revs[from_change:to_change], new_to_parent def GET(self, branch, from_change, to_change, template_name): - def quicklog(changelog): - rv = changelog[0].strip() - if rv.startswith('*'): - rv = rv[1:].strip() - return rv def for_template(revs): rv = [] for rev, certs in revs: @@ -315,9 +329,7 @@ class BranchChanges: elif cert[5] == 'author': author = cert[7] elif cert[5] == 'changelog': - changelog = map(hq, cert[7].split('\n')) - if changelog and changelog[-1] == '': - changelog = changelog[:-1] + changelog = normalised_changelog(cert[7]) shortlog = quicklog(changelog) if rev_branch != branch.name: # yikes, fallen down a well @@ -438,7 +450,6 @@ class RevisionBrowse(RevisionPage): def GET(self, revision, path): revision = mtn.Revision(revision) branches = RevisionPage.branches_for_rev(self, revision) - debug(branches) revisions = ops.get_revision(revision) page_title = "Browsing revision %s [%s]" % (revision.abbrev(), path or '') if len(branches) > 0: @@ -447,13 +458,91 @@ class RevisionBrowse(RevisionPage): else: branch_plural = 'branches' page_title += " of %s %s" % (branch_plural, ', '.join(branches)) - manifest = map(None, ops.get_manifest_of(revision)) + + def components(path): + # NB: mtn internally uses '/' for paths, so we shouldn't use os.path.join() + # we should do things manually; otherwise we'll break on other platforms + # when we accidentally use \ or : or whatever. + # + # also, let's handle the case of spurious extra / characters + # whatever we return should make sense as '/'.join(rv) + rv = [] + while path: + path = path.lstrip('/') + pc = path.split('/', 1) + if len(pc) == 2: + rv.append(pc[0]) + path = pc[1] + else: + rv.append(pc[0]) + path = '' + return rv + + path_components = components(path) + normalised_path = '/'.join(path_components) + # TODO: detect whether or not this exists and skip the following if it doesn't. + + def cut_manifest_to_subdir(): + manifest = map(None, ops.get_manifest_of(revision)) + in_the_dir = False + for stanza in manifest: + stanza_type = stanza[0] + if stanza_type != "file" and stanza_type != "dir": + continue + this_path = stanza[1] + + if not in_the_dir: + if stanza_type == "dir" and this_path == normalised_path: + in_the_dir = True + continue + + # debug(["inthedir", stanza_type, this_path]) + this_path_components = components(this_path) + if stanza_type == "dir": + if len(this_path_components) == len(path_components) + 1 and \ + this_path_components[:len(path_components)] == path_components: + yield (stanza_type, this_path) + else: + in_the_dir = False + # and we've come out of the dir ne'er to re-enter, so.. + break + elif stanza_type == "file" and len(this_path_components) == len(path_components): + yield (stanza_type, this_path) + + def get_timecert(rev): + for cert in ops.certs(rev): + if cert[4] == 'name' and cert[5] == 'date': + revdate = common.parse_timecert(cert[7]) + + def info_for_manifest(entry_iter): + for stanza_type, this_path in entry_iter: + # determine the most recent of the content marks + content_marks = [t[1] for t in ops.get_content_changed(revision, this_path)] + content_marks.sort(lambda b, a: cmp(get_timecert(a), get_timecert(b))) + content_mark = content_marks[0] + # determine the author, and the shortlog + author, ago, shortlog = None, None, None + for cert in ops.certs(content_mark): + if cert[4] != 'name': + continue + name, value = cert[5], cert[7] + if name == "author": + author = value + elif name == "date": + revdate = common.parse_timecert(value) + ago = common.ago(revdate) + elif name == "changelog": + shortlog = quicklog(normalise_changelog(value)) + to_yield = (stanza_type, this_path, author, ago, shortlog) + yield map(lambda x: x or "", to_yield) + renderer.render('revisionbrowse.html', branches=branches, branch_links=', '.join(map(lambda b: link(mtn.Branch(b)).html(), branches)), path=path, page_title=page_title, - revision=revision) + revision=revision, + entries=info_for_manifest(cut_manifest_to_subdir())) class RevisionTar: def GET(self, revision):