#
#
# patch "mtn.py"
# from [8fbed63f4646db8406ddcebf20e85697ce1f7e01]
# to [219dcc3089d7a5cdaf9b4be6d27992208f3100c9]
#
# patch "viewmtn.py"
# from [b025f7cc219e5597bb29cb146a74bb91c59e691b]
# to [f175d4989b3a34435d6f5df3ce800b020a29a13d]
#
============================================================
--- mtn.py 8fbed63f4646db8406ddcebf20e85697ce1f7e01
+++ mtn.py 219dcc3089d7a5cdaf9b4be6d27992208f3100c9
@@ -101,7 +101,7 @@ class Automate(Runner):
self.process.childerr ])
def run(self, *args, **kwargs):
-# debug(("automate is running:", args, kwargs))
+ #debug(("automate is running:", args, kwargs))
lock = self.lock
stop = self.stop
@@ -407,6 +407,10 @@ class Operations:
continue
yield apply(Revision, (line,))
+ def get_corresponding_path(self, revision1, path, revision2):
+ for stanza in basic_io_from_stream(self.automate.run('get_corresponding_path', [revision1, path, revision2])):
+ yield stanza
+
def get_content_changed(self, revision, path):
for stanza in basic_io_from_stream(self.automate.run('get_content_changed', [revision, path])):
yield stanza
@@ -432,7 +436,6 @@ class Operations:
for line in self.standalone.run('diff', args):
yield line
-
###
### vi:expandtab:sw=4:ts=4
###
============================================================
--- viewmtn.py b025f7cc219e5597bb29cb146a74bb91c59e691b
+++ viewmtn.py f175d4989b3a34435d6f5df3ce800b020a29a13d
@@ -16,7 +16,6 @@ import json
import sys
import web
import json
-import heapq
import struct
import string
import rfc822
@@ -33,6 +32,7 @@ hq = cgi.escape
from fdo import sharedmimeinfo, icontheme
import release
hq = cgi.escape
+import heapq
import web
debug = web.debug
@@ -296,6 +296,22 @@ def link(obj, link_type=None, **kwargs):
kwargs['link_type'] = link_type
return link_class(obj, **kwargs)
+class ComparisonRev:
+ def __init__(self, ops, revision):
+ self.revision = revision
+ self.certs = map (None, ops.certs(self.revision))
+ self.date = None
+ for cert in self.certs:
+ if cert[4] == 'name' and cert[5] == 'date':
+ self.date = common.parse_timecert(cert[7])
+ def __repr__(self):
+ return "ComparisonRev <%s>" % (repr(self.revision))
+ def __cmp__(self, other):
+ # irritating edge-case, heapq compares us to empty string if
+ # there's only one thing in the list
+ if not other: return 1
+ return cmp(other.date, self.date)
+
class Renderer:
def __init__(self):
# any templates that can be inherited from, should be added to the list here
@@ -374,54 +390,35 @@ class Help:
def GET(self):
renderer.render('help.html', page_title="Help")
-class BranchChanges:
- def get_last_changes(self, branch, heads, from_change, to_change):
+class Changes:
+ def __get_last_changes(self, start_from, parent_func, selection_func, n):
+ """returns at least n revisions that are parents of the revisions in start_from,
+ ordered by time (descending). selection_func is called for each revision, and
+ that revision is only included in the result if the function returns True."""
# revised algorithm in colaboration with Matthias Radestock
#
# use a heapq to keep a list of interesting revisions
# pop from the heapq; get the most recent interesting revision
# insert the parents of this revision into the heap
# .. repeat until len(heap) >= to_count
- class ComparisonRev:
- def __init__(self, revision):
- self.revision = revision
- self.certs = map (None, ops.certs(self.revision))
- self.date = None
- for cert in self.certs:
- if cert[4] == 'name' and cert[5] == 'date':
- self.date = common.parse_timecert(cert[7])
- def __cmp__(self, other):
- # irritating edge-case, heapq compares us to empty string if
- # there's only one thing in the list
- if not other: return 1
- return cmp(other.date, self.date)
+ if not start_from:
+ raise Exception("get_last_changes() unable to find somewhere to start.")
- def on_our_branch(r):
- rv = False
- for cert in ops.certs(r):
- if cert[4] == 'name' and cert[5] == 'branch':
- if cert[7] == branch.name:
- rv = True
- return rv
-
- if not heads:
- raise Exception("get_last_changes() unable to find somewhere to start - probably a non-existent branch?")
-
last_result = None
in_result = set()
result = []
revq = []
- for rev in heads:
- heapq.heappush(revq, ComparisonRev(rev))
- while len(result) < to_change:
-# print >>sys.stderr, "start_revq state:", map(lambda x: (x.revision, x.date), revq)
+ for rev in start_from:
+ heapq.heappush(revq, ComparisonRev(ops, rev))
+ while len(result) < n:
+ # print >>sys.stderr, "start_revq state:", map(lambda x: (x.revision, x.date), revq)
# update based on the last result we output
if last_result != None:
- parents = filter(None, ops.parents(last_result.revision))
+ parents = filter(None, parent_func(last_result.revision))
for parent_rev in parents:
- if parent_rev == None or not on_our_branch(parent_rev):
+ if parent_rev == None or not selection_func(parent_rev):
continue
- heapq.heappush(revq, ComparisonRev(parent_rev))
+ heapq.heappush(revq, ComparisonRev(ops, parent_rev))
# try and find something we haven't already output in the heap
last_result = None
@@ -435,70 +432,82 @@ class BranchChanges:
# follow the newest edge
in_result.add(last_result.revision)
result.append(last_result)
-
- rv = map (lambda x: (x.revision, x.certs), result[from_change:to_change]), revq
+
+ rv = map (lambda x: (x.revision, x.certs), result), revq
return rv
- def GET(self, branch, from_change, to_change, template_name):
- def for_template(revs):
- rv = []
- for rev, certs in revs:
- rev_branch = ""
- revision, diffs, ago, author, changelog, shortlog, when = mtn.Revision(rev), [], "", mtn.Author(""), "", "", ""
- for cert in certs:
- if cert[4] != 'name':
- continue
- if cert[5] == "branch":
- rev_branch = cert[7]
- elif cert[5] == 'date':
- revdate = common.parse_timecert(cert[7])
- ago = common.ago(revdate)
- when = revdate.strftime('%a, %d %b %Y %H:%M:%S GMT')
- elif cert[5] == 'author':
- author = mtn.Author(cert[7])
- elif cert[5] == 'changelog':
- changelog = normalise_changelog(cert[7]) # NB: this HTML escapes!
- shortlog = quicklog(changelog) # so this is also HTML escaped.
- if rev_branch != branch.name:
- # yikes, fallen down a well
+ def on_our_branch(self, branch, revision):
+ rv = False
+ for cert in ops.certs(revision):
+ if cert[4] == 'name' and cert[5] == 'branch':
+ if cert[7] == branch.name:
+ rv = True
+ return rv
+
+ def for_template(self, revs):
+ rv = []
+ for rev, certs in revs:
+ rev_branch = ""
+ revision, diffs, ago, author, changelog, shortlog, when = mtn.Revision(rev), [], "", mtn.Author(""), "", "", ""
+ for cert in certs:
+ if cert[4] != 'name':
continue
- for stanza in ops.get_revision(rev):
- if stanza and stanza[0] == "old_revision":
- old_revision = stanza[1]
- diffs.append(Diff(mtn.Revision(old_revision), revision))
- if diffs:
- diffs = '| ' + ', '.join([link(d).html('diff') for d in diffs])
- else:
- diffs = ''
- rv.append((revision, diffs, ago, mtn.Author(author), '
\n'.join(changelog), shortlog, when))
- return rv
-
- branch = mtn.Branch(branch)
+ if cert[5] == "branch":
+ rev_branch = cert[7]
+ elif cert[5] == 'date':
+ revdate = common.parse_timecert(cert[7])
+ ago = common.ago(revdate)
+ when = revdate.strftime('%a, %d %b %Y %H:%M:%S GMT')
+ elif cert[5] == 'author':
+ author = mtn.Author(cert[7])
+ elif cert[5] == 'changelog':
+ changelog = normalise_changelog(cert[7]) # NB: this HTML escapes!
+ shortlog = quicklog(changelog) # so this is also HTML escaped.
+ for stanza in ops.get_revision(rev):
+ if stanza and stanza[0] == "old_revision":
+ old_revision = stanza[1]
+ diffs.append(Diff(mtn.Revision(old_revision), revision))
+ if diffs:
+ diffs = '| ' + ', '.join([link(d).html('diff') for d in diffs])
+ else:
+ diffs = ''
+ rv.append((revision, diffs, ago, mtn.Author(author), '
\n'.join(changelog), shortlog, when))
+ return rv
+
+ def determine_bounds(self, from_change, to_change):
+ per_page = 10
+ max_per_page = 100
+ if from_change: from_change = int(from_change)
+ else: from_change = 0
+ if to_change: to_change = int(to_change)
+ else: to_change = per_page
+ if to_change - from_change > max_per_page:
+ to_change = from_change + max_per_page
+ next_from = to_change
+ next_to = to_change + per_page
+ previous_from = from_change - per_page
+ previous_to = previous_from + per_page
+ return (from_change, to_change, next_from, next_to, previous_from, previous_to)
+
+ def branch_get_last_changes(self, branch, from_change, to_change):
heads = [t for t in ops.heads(branch.name)]
if not heads:
return web.notfound()
- per_page = 10
- if from_change:
- from_change = int(from_change)
- else:
- from_change = 0
- if to_change:
- to_change = int(to_change)
- else:
- to_change = per_page
- changed, new_starting_point = self.get_last_changes(branch, heads, from_change, to_change)
- # next and previous 'from' and 'to' indexes
- if len(changed) == to_change - from_change:
- next_from, next_to = to_change, to_change + per_page
- else:
+ changed, new_starting_point = self.__get_last_changes(heads,
+ lambda r: ops.parents(r),
+ lambda r: self.on_our_branch(branch, r),
+ to_change)
+ return changed, new_starting_point
+
+ def Branch_GET(self, branch, from_change, to_change, template_name):
+ branch = mtn.Branch(branch)
+ from_change, to_change, next_from, next_to, previous_from, previous_to = self.determine_bounds(from_change, to_change)
+ changed, new_starting_point = self.branch_get_last_changes(branch, from_change, to_change)
+ changed = changed[from_change:to_change]
+ if len(changed) != to_change - from_change:
next_from, next_to = None, None
- if from_change > 0:
- previous_from = from_change - per_page
- if previous_from < 0: previous_from = 0
- previous_to = previous_from + per_page
- else:
+ if from_change <= 0:
previous_from, previous_to = None, None
-
renderer.render(template_name,
page_title="Branch %s" % branch.name,
branch=branch,
@@ -508,15 +517,54 @@ class BranchChanges:
previous_to=previous_to,
next_from=next_from,
next_to=next_to,
- display_revs=for_template(changed))
+ display_revs=self.for_template(changed))
+
+ def file_get_last_changes(self, from_change, to_change, revision, path):
+ def content_changed_fn(in_revision):
+ uniq = set()
+ parents = map(None, ops.parents(in_revision))
+ for parent in parents:
+ stanza = map(None, ops.get_corresponding_path(revision, path, parent))
+ # file does not exist in this revision; skip!
+ if not stanza:
+ continue
+ stanza = map(None, ops.get_content_changed(parent, path))
+ uniq.add(stanza[0][1])
+ return list(uniq)
+ start_at = content_changed_fn(revision) # not just the starting revision! we might not have changed 'path' in the starting rev..
+ changed, new_starting_point = self.__get_last_changes(start_at,
+ content_changed_fn,
+ lambda r: True,
+ to_change)
+ return changed, new_starting_point
+
+ def File_GET(self, from_change, to_change, revision, path, template_name):
+ from_change, to_change, next_from, next_to, previous_from, previous_to = self.determine_bounds(from_change, to_change)
+ changed, new_starting_point = self.file_get_last_changes(from_change, to_change, revision, path)
+ changed = changed[from_change:to_change]
+ if len(changed) != to_change - from_change:
+ next_from, next_to = None, None
+ if from_change <= 0:
+ previous_from, previous_to = None, None
+ renderer.render(template_name,
+ page_title="Changes to '%s' (from %s)" % (cgi.escape(path), revision.abbrev()),
+ path=path,
+ revision=revision,
+ from_change=from_change,
+ to_change=to_change,
+ previous_from=previous_from,
+ previous_to=previous_to,
+ next_from=next_from,
+ next_to=next_to,
+ display_revs=self.for_template(changed))
-class HTMLBranchChanges(BranchChanges):
+class HTMLBranchChanges(Changes):
def GET(self, branch, from_change, to_change):
- BranchChanges.GET(self, branch, from_change, to_change, "branchchanges.html")
+ Changes.Branch_GET(self, branch, from_change, to_change, "branchchanges.html")
-class RSSBranchChanges(BranchChanges):
+class RSSBranchChanges(Changes):
def GET(self, branch, from_change, to_change):
- BranchChanges.GET(self, branch, from_change, to_change, "branchchangesrss.html")
+ Changes.Branch_GET(self, branch, from_change, to_change, "branchchangesrss.html")
class RevisionPage(object):
def get_fileid(self, revision, filename):
@@ -540,6 +588,20 @@ class RevisionPage(object):
rv.append(stanza[7])
return rv
+class RevisionFileChanges(Changes, RevisionPage):
+ def GET(self, from_change, to_change, revision, path):
+ revision = mtn.Revision(revision)
+ if not self.exists(revision):
+ return web.notfound()
+ Changes.File_GET(self, from_change, to_change, revision, path, "revisionfilechanges.html")
+
+class RevisionFileChangesRSS(Changes, RevisionPage):
+ def GET(self, from_change, to_change, revision, path):
+ revision = mtn.Revision(revision)
+ if not self.exists(revision):
+ return web.notfound()
+ Changes.File_GET(self, from_change, to_change, revision, path, "revisionfilechangesrss.html")
+
class RevisionInfo(RevisionPage):
def GET(self, revision):
revision = mtn.Revision(revision)
@@ -1007,10 +1069,9 @@ class Json(object):
'branch' : for_branch,
}
branch = mtn.Branch(for_branch)
- changes = BranchChanges().get_last_changes(branch, [t for t in ops.heads(branch.name)], 0, 1)
+ changes, new_starting_point = Changes().branch_get_last_changes(branch, 0, 1)
if len(changes) < 1:
return web.notfound()
- changes, new_starting_point = changes
if not changes:
rv['error_string'] = 'no revisions in branch'
else:
@@ -1092,6 +1153,7 @@ class RobotsTxt:
# as this is an enormous amount of information.
for access_method in ['/revision/', '/branch/head/', '/branch/anyhead/', '/branch/changes/from/', '/json/', '/mimeicon/']:
print "Disallow:", access_method + revision_page
+
urls = (
r'/', 'Index', #done
r'/about', 'About', #done
@@ -1104,6 +1166,9 @@ 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/filechanges/()()('+mtn.revision_re+')/(.*)', 'RevisionFileChanges',
+ r'/revision/filechanges/from/(\d+)/to/(\d+)/('+mtn.revision_re+')/(.*)', 'RevisionFileChanges',
+ r'/revision/filechanges/rss/('+mtn.revision_re+')/(.*)', 'RevisionFileChangesRSS',
r'/revision/downloadfile/('+mtn.revision_re+')/(.*)', 'RevisionDownloadFile',
r'/revision/info/('+mtn.revision_re+')', 'RevisionInfo',
r'/revision/tar/('+mtn.revision_re+')', 'RevisionTar',