Merge lp:~mkanat/loggerhead/view-default into lp:loggerhead

Proposed by Max Kanat-Alexander
Status: Merged
Approved by: Max Kanat-Alexander
Approved revision: 435
Merged at revision: 433
Proposed branch: lp:~mkanat/loggerhead/view-default
Merge into: lp:loggerhead
Diff against target: 449 lines (+149/-91)
9 files modified
loggerhead/apps/branch.py (+2/-0)
loggerhead/controllers/annotate_ui.py (+54/-0)
loggerhead/controllers/view_ui.py (+33/-48)
loggerhead/highlight.py (+4/-2)
loggerhead/static/css/view.css (+14/-12)
loggerhead/templatefunctions.py (+2/-2)
loggerhead/templates/inventory.pt (+8/-8)
loggerhead/templates/revision.pt (+2/-2)
loggerhead/templates/view.pt (+30/-17)
To merge this branch: bzr merge lp:~mkanat/loggerhead/view-default
Reviewer Review Type Date Requested Status
Martin Pool Approve
Michael Hudson-Doyle Approve
Review via email: mp+42222@code.launchpad.net

Commit message

Create a new "view" controller, and have that be the default view of a file.

Description of the change

This adds a new controller, "view", and makes it the default for viewing a file, instead of "annotate". The "view" controller is just like "annotate", except that it doesn't show the revision for each line. This should significantly improve the performance of loggerhead, particularly on high-level installations (like launchpad) or on large branches (such as launchpad itself) because most of the time, people don't need the annotated information for a branch--they just want to see its content.

In actual tests, the "view" controller is between 3x and 10x faster than the "annotate" controller. (To see similar gains to these, try viewing a large file on the launchpad devel branch without any existing caches in loggerhead.)

The "view" and "annotate" controller both use the same templates, and I've renamed all of the templates and CSS to "view" instead of "annotate" (since we now think of "annotate" as a special case of "view"). I've moved annotate_ui.py to view_ui.py and created a brand-new annotate_ui.py over the old one.

To post a comment you must log in.
Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :

Yay for progress on this!

I think it's mostly fine. The template is ugly though. Why do you need to put a None at the end of the annotation iterator? It would be better to pad out the results until you've reached the length of the text lines, or something.

If you're going to rename the annotation css file, you should probably rename the rules therein too.

Everything else looks ok. Loading the annotation information via ajax would be nice, but can wait :-)

Cheers,
mwh

review: Approve
Revision history for this message
Max Kanat-Alexander (mkanat) wrote :

> I think it's mostly fine. The template is ugly though. Why do you need to
> put a None at the end of the annotation iterator? It would be better to pad
> out the results until you've reached the length of the text lines, or
> something.

  That's true, I could do that, but that would also then involve code for keeping track of when there were extra lines in 'contents' that were beyond how far along we were with annotating. Would simplify the template, though, which I think is a valid goal. I think the problem is that 'contents' contains an extra final line when there's a "\n" at the end of the file, and annotate_iter doesn't consider that to be a line.

> If you're going to rename the annotation css file, you should probably rename
> the rules therein too.

  Yeah, I suppose you're right. I was trying to keep the changes at least *somewhat* focused though, so that they could be reviewed sensibly. But now that it's passed review I'll just do that change.

> Everything else looks ok. Loading the annotation information via ajax would
> be nice, but can wait :-)

  Yeah, I had the same thought.

Revision history for this message
Martin Pool (mbp) wrote :

That looks plausible to me, and reasonably clean.

As we discussed elsewhere, it would be good to get this onto the Launchpad deployment branch separately from changes in loggerhead trunk that are possibly more risky, such as historydb.

I wish there was an automatic test included here, but if there is no framework in place I won't ask you to write one as part of this proposal.

review: Approve
lp:~mkanat/loggerhead/view-default updated
433. By Max Kanat-Alexander

Instead of yielding None for when there are more lines than annotate_iter
has, just fix the off-by-one bug that made us have too many lines in
"contents" (caused by annotate_iter using split_lines and us using
split("\n") in highlight()).

434. By Max Kanat-Alexander

The colors of rows in the annotated view wasn't getting set properly. There
would be a revision number, and then the *next* line would change color.

435. By Max Kanat-Alexander

Rename the anno* CSS classes to view*.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'loggerhead/apps/branch.py'
--- loggerhead/apps/branch.py 2010-05-21 14:07:06 +0000
+++ loggerhead/apps/branch.py 2010-12-01 08:13:14 +0000
@@ -29,6 +29,7 @@
2929
30from loggerhead.apps import static_app30from loggerhead.apps import static_app
31from loggerhead.controllers.annotate_ui import AnnotateUI31from loggerhead.controllers.annotate_ui import AnnotateUI
32from loggerhead.controllers.view_ui import ViewUI
32from loggerhead.controllers.atom_ui import AtomUI33from loggerhead.controllers.atom_ui import AtomUI
33from loggerhead.controllers.changelog_ui import ChangeLogUI34from loggerhead.controllers.changelog_ui import ChangeLogUI
34from loggerhead.controllers.diff_ui import DiffUI35from loggerhead.controllers.diff_ui import DiffUI
@@ -114,6 +115,7 @@
114 'files': InventoryUI,115 'files': InventoryUI,
115 'revision': RevisionUI,116 'revision': RevisionUI,
116 'search': SearchUI,117 'search': SearchUI,
118 'view': ViewUI,
117 }119 }
118120
119 def last_updated(self):121 def last_updated(self):
120122
=== added file 'loggerhead/controllers/annotate_ui.py'
--- loggerhead/controllers/annotate_ui.py 1970-01-01 00:00:00 +0000
+++ loggerhead/controllers/annotate_ui.py 2010-12-01 08:13:14 +0000
@@ -0,0 +1,54 @@
1#
2# Copyright (C) 2010 Canonical Ltd.
3#
4# This program is free software; you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation; either version 2 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program; if not, write to the Free Software
16# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17#
18
19from loggerhead.controllers.view_ui import ViewUI
20from loggerhead import util
21
22class AnnotateUI(ViewUI):
23
24 def annotate_file(self, info):
25 file_id = info['file_id']
26 revid = info['change'].revid
27
28 tree = self.tree_for(file_id, revid)
29
30 change_cache = {}
31 last_line_revid = None
32 parity = 1
33 for line_revid, text in tree.annotate_iter(file_id):
34 if line_revid == last_line_revid:
35 # remember which lines have a new revno and which don't
36 new_rev = False
37 else:
38 new_rev = True
39 parity ^= 1
40 last_line_revid = line_revid
41 if line_revid in change_cache:
42 change = change_cache[line_revid]
43 else:
44 change = self._history.get_changes([line_revid])[0]
45 change_cache[line_revid] = change
46
47 yield util.Container(
48 parity=parity, new_rev=new_rev, change=change)
49
50 def get_values(self, path, kwargs, headers):
51 values = super(AnnotateUI, self).get_values(path, kwargs, headers)
52 values['annotated'] = self.annotate_file(values)
53
54 return values
055
=== renamed file 'loggerhead/controllers/annotate_ui.py' => 'loggerhead/controllers/view_ui.py'
--- loggerhead/controllers/annotate_ui.py 2009-10-17 06:35:33 +0000
+++ loggerhead/controllers/view_ui.py 2010-12-01 08:13:14 +0000
@@ -35,20 +35,18 @@
35from loggerhead import util35from loggerhead import util
3636
3737
38class AnnotateUI(TemplatedBranchView):38class ViewUI(TemplatedBranchView):
3939
40 template_path = 'loggerhead.templates.annotate'40 template_path = 'loggerhead.templates.view'
4141
42 def annotate_file(self, file_id, revid):42 def tree_for(self, file_id, revid):
43 z = time.time()
44 lineno = 1
45 parity = 0
46
47 file_revid = self._history.get_inventory(revid)[file_id].revision43 file_revid = self._history.get_inventory(revid)[file_id].revision
48 tree = self._history._branch.repository.revision_tree(file_revid)44 return self._history._branch.repository.revision_tree(file_revid)
4945
46 def text_lines(self, file_id, revid):
50 file_name = os.path.basename(self._history.get_path(revid, file_id))47 file_name = os.path.basename(self._history.get_path(revid, file_id))
5148
49 tree = self.tree_for(file_id, revid)
52 file_text = tree.get_file_text(file_id)50 file_text = tree.get_file_text(file_id)
53 encoding = 'utf-8'51 encoding = 'utf-8'
54 try:52 try:
@@ -58,44 +56,27 @@
58 file_text = file_text.decode(encoding)56 file_text = file_text.decode(encoding)
5957
60 file_lines = bzrlib.osutils.split_lines(file_text)58 file_lines = bzrlib.osutils.split_lines(file_text)
59 # This can throw bzrlib.errors.BinaryFile (which our caller catches).
60 bzrlib.textfile.check_text_lines(file_lines)
61
62 if highlight is not None:
63 hl_lines = highlight(file_name, file_text, encoding)
64 # highlight strips off extra newlines at the end of the file.
65 extra_lines = len(file_lines) - len(hl_lines)
66 hl_lines.extend([u''] * extra_lines)
67 else:
68 hl_lines = map(cgi.escape, file_lines)
69
70 return hl_lines;
6171
72 def file_contents(self, file_id, revid):
62 try:73 try:
63 bzrlib.textfile.check_text_lines(file_lines)74 file_lines = self.text_lines(file_id, revid)
64 except bzrlib.errors.BinaryFile:75 except bzrlib.errors.BinaryFile:
65 # bail out; this isn't displayable text76 # bail out; this isn't displayable text
66 yield util.Container(parity=0, lineno=1, status='same',77 return ['(This is a binary file.)']
67 text='(This is a binary file.)',78
68 change=util.Container())79 return file_lines
69 else:
70 if highlight is not None:
71 hl_lines = highlight(file_name, file_text, encoding)
72 hl_lines.extend([u''] * (len(file_lines) - len(hl_lines)))
73 else:
74 hl_lines = map(cgi.escape, file_lines)
75
76 change_cache = {}
77
78 last_line_revid = None
79 for line_revid, text in tree.annotate_iter(file_id):
80 if line_revid == last_line_revid:
81 # remember which lines have a new revno and which don't
82 status = 'same'
83 else:
84 status = 'changed'
85 parity ^= 1
86 last_line_revid = line_revid
87 if line_revid in change_cache:
88 change = change_cache[line_revid]
89 else:
90 change = self._history.get_changes([line_revid])[0]
91 change_cache[line_revid] = change
92
93 yield util.Container(
94 parity=parity, lineno=lineno, status=status,
95 change=change, text=hl_lines[lineno - 1])
96 lineno += 1
97
98 self.log.debug('annotate: %r secs' % (time.time() - z,))
9980
100 def get_values(self, path, kwargs, headers):81 def get_values(self, path, kwargs, headers):
101 history = self._history82 history = self._history
@@ -105,7 +86,7 @@
105 file_id = kwargs.get('file_id', None)86 file_id = kwargs.get('file_id', None)
106 if (file_id is None) and (path is None):87 if (file_id is None) and (path is None):
107 raise HTTPBadRequest('No file_id or filename '88 raise HTTPBadRequest('No file_id or filename '
108 'provided to annotate')89 'provided to view')
10990
110 if file_id is None:91 if file_id is None:
111 file_id = history.get_file_id(revid, path)92 file_id = history.get_file_id(revid, path)
@@ -140,13 +121,17 @@
140 branch_breadcrumbs = util.branch_breadcrumbs(path, inv, 'files')121 branch_breadcrumbs = util.branch_breadcrumbs(path, inv, 'files')
141122
142 return {123 return {
124 # In AnnotateUI, "annotated" is a generator giving revision
125 # numbers per lines, but the template checks if "annotated" is
126 # true or not before using it, so we have to define it here also.
127 'annotated': False,
143 'revno_url': revno_url,128 'revno_url': revno_url,
144 'file_id': file_id,129 'file_id': file_id,
145 'path': path,130 'file_path': path,
146 'filename': filename,131 'filename': filename,
147 'navigation': navigation,132 'navigation': navigation,
148 'change': change,133 'change': change,
149 'contents': list(self.annotate_file(file_id, revid)),134 'contents': self.file_contents(file_id, revid),
150 'fileview_active': True,135 'fileview_active': True,
151 'directory_breadcrumbs': directory_breadcrumbs,136 'directory_breadcrumbs': directory_breadcrumbs,
152 'branch_breadcrumbs': branch_breadcrumbs,137 'branch_breadcrumbs': branch_breadcrumbs,
153138
=== modified file 'loggerhead/highlight.py'
--- loggerhead/highlight.py 2010-04-22 03:23:55 +0000
+++ loggerhead/highlight.py 2010-12-01 08:13:14 +0000
@@ -16,6 +16,7 @@
16# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA16# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17#17#
1818
19import bzrlib.osutils
19import cgi20import cgi
2021
21from pygments import highlight as _highlight_func22from pygments import highlight as _highlight_func
@@ -36,7 +37,7 @@
36 """37 """
3738
38 if len(text) > MAX_HIGHLIGHT_SIZE:39 if len(text) > MAX_HIGHLIGHT_SIZE:
39 return map(cgi.escape, text.split('\n'))40 return map(cgi.escape, bzrlib.osutils.split_lines(text))
4041
41 formatter = HtmlFormatter(style=style, nowrap=True, classprefix='pyg-')42 formatter = HtmlFormatter(style=style, nowrap=True, classprefix='pyg-')
4243
@@ -48,6 +49,7 @@
48 except (ClassNotFound, ValueError):49 except (ClassNotFound, ValueError):
49 lexer = TextLexer(encoding=encoding)50 lexer = TextLexer(encoding=encoding)
5051
51 hl_lines = _highlight_func(text, lexer, formatter).split('\n')52 hl_lines = _highlight_func(text, lexer, formatter)
53 hl_lines = bzrlib.osutils.split_lines(hl_lines)
5254
53 return hl_lines55 return hl_lines
5456
=== renamed file 'loggerhead/static/css/annotate.css' => 'loggerhead/static/css/view.css'
--- loggerhead/static/css/annotate.css 2009-04-30 10:39:05 +0000
+++ loggerhead/static/css/view.css 2010-12-01 08:13:14 +0000
@@ -1,29 +1,31 @@
1/*table*/1/*table*/
2.annoLineTit, .annoLine, .annoRevTit, .annoRev, .annoComm, .annoCommTit, .annoContTit, .annoCont {2.viewLineTit, .viewLine, .viewRevTit, .viewRev,
3 width:45px;3.viewContTit, .viewCont
4{
4 border:1px solid #d2d2d2;5 border:1px solid #d2d2d2;
5}6}
6.annoLine, .annoRev, .annoComm, .annoCont {7.viewLine, .viewRev, .viewComm, .viewCont {
7 border:none;8 border:none;
8}9}
9.annoLine {10.viewLine, .viewLineTit {
11 padding-right: .2em;
12}
13.viewLine {
10 width:37px;14 width:37px;
11}15}
12.annoContTit, .annoCont {16.viewContTit, .viewCont {
13 width:auto;17 width:auto;
18 padding-left: .3em;
14}19}
15.annoRevTit, .annoRev {20.viewRevTit, .viewRev {
16 width:70px;21 width:70px;
17 text-align:center;22 text-align:center;
18}23}
19.annoComm, .annoCommTit {24.viewLine, .viewCont {
20 width:200px;
21}
22.annoLine, .annoCont {
23 font:normal 12px/normal monospace;25 font:normal 12px/normal monospace;
24 whitespace: pre;26 whitespace: pre;
25}27}
26.annoCont pre { margin: 0; }28.viewCont pre { margin: 0; }
27.annoLine {29.viewLine {
28 text-align:right;30 text-align:right;
29}31}
3032
=== modified file 'loggerhead/templatefunctions.py'
--- loggerhead/templatefunctions.py 2009-10-17 08:47:38 +0000
+++ loggerhead/templatefunctions.py 2010-12-01 08:13:14 +0000
@@ -121,9 +121,9 @@
121121
122122
123@templatefunc123@templatefunc
124def annotate_link(url, revno, path):124def view_link(url, revno, path):
125 return '<a href="%s" title="Annotate %s">%s</a>' % (125 return '<a href="%s" title="Annotate %s">%s</a>' % (
126 url(['/annotate', revno, path]), cgi.escape(path), cgi.escape(path))126 url(['/view', revno, path]), cgi.escape(path), cgi.escape(path))
127127
128@templatefunc128@templatefunc
129def revision_link(url, revno, path, frag=''):129def revision_link(url, revno, path, frag=''):
130130
=== modified file 'loggerhead/templates/inventory.pt'
--- loggerhead/templates/inventory.pt 2009-10-17 06:55:25 +0000
+++ loggerhead/templates/inventory.pt 2010-12-01 08:13:14 +0000
@@ -89,11 +89,11 @@
8989
90 <!-- Show this if it's a symlink -->90 <!-- Show this if it's a symlink -->
91 <tr tal:attributes="class string:blueRow${repeat/file/even}" tal:condition="python:file.kind=='symlink'">91 <tr tal:attributes="class string:blueRow${repeat/file/even}" tal:condition="python:file.kind=='symlink'">
92 <td class="autcell"><a tal:attributes="href python:url(['/annotate', change.revno, file.absolutepath])">92 <td class="autcell"><a tal:attributes="href python:url(['/view', change.revno, file.absolutepath])">
93 <img tal:attributes="src python:branch.static_url('/static/images/ico_file_flecha.gif')" alt="Symlink" />93 <img tal:attributes="src python:branch.static_url('/static/images/ico_file_flecha.gif')" alt="Symlink" />
94 </a>94 </a>
9595
96 <a tal:attributes="href python:url(['/annotate', revno_url, file.absolutepath])"96 <a tal:attributes="href python:url(['/view', revno_url, file.absolutepath])"
97 tal:content="file/filename" class="link"></a>97 tal:content="file/filename" class="link"></a>
98 </td>98 </td>
99 <td class="date"><a tal:attributes="href python:url(['/revision', file.change.revno]);99 <td class="date"><a tal:attributes="href python:url(['/revision', file.change.revno]);
@@ -113,17 +113,17 @@
113113
114 <!-- Show this if it's a regular file -->114 <!-- Show this if it's a regular file -->
115 <tr tal:attributes="class string:blueRow${repeat/file/even}" tal:condition="python:file.kind=='file'">115 <tr tal:attributes="class string:blueRow${repeat/file/even}" tal:condition="python:file.kind=='file'">
116 <td class="autcell"><a tal:attributes="href python:url(['/annotate', revno_url, file.absolutepath])">116 <td class="autcell"><a tal:attributes="href python:url(['/view', revno_url, file.absolutepath])">
117 <img tal:attributes="src python:branch.static_url('/static/images/ico_file.gif');117 <img tal:attributes="src python:branch.static_url('/static/images/ico_file.gif');
118 title string:Annotate ${file/filename}"118 title string:View ${file/filename}"
119 tal:condition="python:file.executable is False" />119 tal:condition="python:file.executable is False" />
120 <!-- Show a different icon id the file executable -->120 <!-- Show a different icon id the file executable -->
121 <img tal:attributes="src python:branch.static_url('/static/images/ico_file_modify.gif');121 <img tal:attributes="src python:branch.static_url('/static/images/ico_file_modify.gif');
122 title string:Annotate ${file/filename}"122 title string:View ${file/filename}"
123 tal:condition="python:file.executable is True" alt="File" />123 tal:condition="python:file.executable is True" alt="File" />
124 </a>124 </a>
125125
126 <a tal:attributes="href python:url(['/annotate', revno_url, file.absolutepath])"126 <a tal:attributes="href python:url(['/view', revno_url, file.absolutepath])"
127 tal:content="file/filename" class="link"></a></td>127 tal:content="file/filename" class="link"></a></td>
128 <td class="date"><a tal:attributes="href python:url(['/revision', file.change.revno]);128 <td class="date"><a tal:attributes="href python:url(['/revision', file.change.revno]);
129 title string:Show revision ${file/change/revno}"129 title string:Show revision ${file/change/revno}"
@@ -131,8 +131,8 @@
131 </td>131 </td>
132 <td class="date" tal:content="python:util.date_time(file.change.date)"></td>132 <td class="date" tal:content="python:util.date_time(file.change.date)"></td>
133 <td class="timedate2" tal:content="python:util.human_size(file.size)"></td>133 <td class="timedate2" tal:content="python:util.human_size(file.size)"></td>
134 <td class="expcell"><a tal:attributes="href python:url(['/annotate', revno_url, file.absolutepath]);134 <td class="expcell"><a tal:attributes="href python:url(['/view', revno_url, file.absolutepath]);
135 title string:Annotate ${file/filename}">135 title string:View ${file/filename}">
136 <img tal:attributes="src python:branch.static_url('/static/images/ico_planilla.gif')" alt="Diff" />136 <img tal:attributes="src python:branch.static_url('/static/images/ico_planilla.gif')" alt="Diff" />
137 </a>137 </a>
138 </td>138 </td>
139139
=== modified file 'loggerhead/templates/revision.pt'
--- loggerhead/templates/revision.pt 2009-04-28 03:01:54 +0000
+++ loggerhead/templates/revision.pt 2010-12-01 08:13:14 +0000
@@ -42,7 +42,7 @@
42 </tal:compare-to>42 </tal:compare-to>
43 </span>43 </span>
44 <span class="breadcrumb" tal:condition="specific_path">44 <span class="breadcrumb" tal:condition="specific_path">
45 : <tal:annotate content="structure python:annotate_link(url, change.revno, specific_path)" />45 : <tal:annotate content="structure python:view_link(url, change.revno, specific_path)" />
46 </span>46 </span>
47 </h1>47 </h1>
48 <tal:branch-info replace="structure python:branchinfo(branch)" />48 <tal:branch-info replace="structure python:branchinfo(branch)" />
@@ -55,7 +55,7 @@
55 </a>55 </a>
56 </p>56 </p>
57 <p tal:condition="specific_path">57 <p tal:condition="specific_path">
58 Viewing changes to <tal:annotate content="structure python:annotate_link(url, change.revno, specific_path)" />58 Viewing changes to <tal:annotate content="structure python:view_link(url, change.revno, specific_path)" />
59 </p>59 </p>
60 <ul id="submenuTabs">60 <ul id="submenuTabs">
61 <li id="first"><a tal:attributes="href python:url(['/files', change.revno]);61 <li id="first"><a tal:attributes="href python:url(['/files', change.revno]);
6262
=== renamed file 'loggerhead/templates/annotate.pt' => 'loggerhead/templates/view.pt'
--- loggerhead/templates/annotate.pt 2009-04-30 10:39:05 +0000
+++ loggerhead/templates/view.pt 2010-12-01 08:13:14 +0000
@@ -3,12 +3,12 @@
3<html xmlns="http://www.w3.org/1999/xhtml" metal:use-macro="macros/main">3<html xmlns="http://www.w3.org/1999/xhtml" metal:use-macro="macros/main">
4 <head>4 <head>
5 <title metal:fill-slot="title"5 <title metal:fill-slot="title"
6 tal:content="string:${branch/friendly_name} : contents of ${path}6 tal:content="string:${branch/friendly_name} : contents of ${file_path}
7 at revision ${change/revno}">7 at revision ${change/revno}">
8 </title>8 </title>
9 <metal:block fill-slot="header_extras">9 <metal:block fill-slot="header_extras">
10 <link rel="stylesheet" type="text/css" media="all"10 <link rel="stylesheet" type="text/css" media="all"
11 tal:attributes="href python:branch.static_url('/static/css/annotate.css')"/>11 tal:attributes="href python:branch.static_url('/static/css/view.css')"/>
12 <link rel="stylesheet" type="text/css" media="all"12 <link rel="stylesheet" type="text/css" media="all"
13 tal:attributes="href python:branch.static_url('/static/css/highlight.css')"/>13 tal:attributes="href python:branch.static_url('/static/css/highlight.css')"/>
14 </metal:block>14 </metal:block>
@@ -36,6 +36,14 @@
36 <li id="first">36 <li id="first">
37 <a tal:attributes="href python:url(['/files', change.revno], clear=1)">browse files</a>37 <a tal:attributes="href python:url(['/files', change.revno], clear=1)">browse files</a>
38 </li>38 </li>
39 <li tal:condition="not:annotated">
40 <a tal:attributes="href python:url(['/annotate', revno_url, file_path], clear=1)"
41 >view revision numbers per line</a>
42 </li>
43 <li tal:condition="annotated">
44 <a tal:attributes="href python:url(['/view', revno_url, file_path], clear=1)"
45 >view without revision numbers</a>
46 </li>
39 <li>47 <li>
40 <a tal:attributes="href python:url(['/revision', change.revno], clear=1)">view revision</a>48 <a tal:attributes="href python:url(['/revision', change.revno], clear=1)">view revision</a>
41 </li>49 </li>
@@ -50,25 +58,30 @@
50 </li>58 </li>
51 </ul>59 </ul>
5260
53 <div class="annotate">61 <div class="view">
54 <table id="logentries">62 <table id="logentries">
55 <tr class="logheader">63 <tr class="logheader">
56 <td class="annoLineTit">Line</td>64 <td class="viewLineTit">Line</td>
57 <td class="annoRevTit">Revision</td>65 <td class="viewRevTit" tal:condition="annotated">Revision</td>
58 <td class="annoContTit">Contents</td>66 <td class="viewContTit">Contents</td>
59 </tr>67 </tr>
6068
61 <tr tal:repeat="line contents"69 <tal:rep tal:repeat="line contents">
62 tal:attributes="class string:blueRow${line/parity}">70 <tr tal:define="anno python:annotated and annotated.next()"
63 <td class="annoLine"><a tal:attributes="id string:L${line/lineno}; href string:#L${line/lineno}" tal:content="line/lineno">1</a></td>71 tal:attributes="class python:anno and 'blueRow' + str(anno.parity) or None">
64 <td class="annoRev">72 <td class="viewLine">
65 <a tal:condition="python:line.status=='changed'"73 <a tal:attributes="id string:L${repeat/line/number}; href string:#L${repeat/line/number}"
66 tal:content="python:util.trunc(line.change.revno)"74 tal:content="repeat/line/number">1</a>
67 tal:attributes="href python:url(['/revision', line.change.revno], clear=1);75 </td>
68 title python:'%s by %s, on %s (%s)'%(line.change.revno, ', '.join(util.hide_emails(line.change.authors)), line.change.date.strftime('%d %b %Y %H:%M'), util.date_time(line.change.date))"></a>76 <td class="viewRev" tal:condition="annotated">
69 </td>77 <a tal:condition="python:anno.new_rev"
70 <td class="annoCont"><pre tal:content="structure line/text"></pre></td>78 tal:content="python:util.trunc(anno.change.revno)"
71 </tr>79 tal:attributes="href python:url(['/revision', anno.change.revno], clear=1);
80 title python:'%s by %s, on %s (%s)' % (anno.change.revno, ', '.join(util.hide_emails(anno.change.authors)), anno.change.date.strftime('%d %b %Y %H:%M'), util.date_time(anno.change.date))"></a>
81 </td>
82 <td class="viewCont"><pre tal:content="structure line"></pre></td>
83 </tr>
84 </tal:rep>
72 </table>85 </table>
73 </div>86 </div>
7487

Subscribers

People subscribed via source and target branches