Merge lp:~mwhudson/loggerhead/ajaxy-revision-page into lp:loggerhead

Proposed by Michael Hudson-Doyle
Status: Merged
Merged at revision: not available
Proposed branch: lp:~mwhudson/loggerhead/ajaxy-revision-page
Merge into: lp:loggerhead
Diff against target: None lines
To merge this branch: bzr merge lp:~mwhudson/loggerhead/ajaxy-revision-page
Reviewer Review Type Date Requested Status
Paul Hummer (community) Approve
Review via email: mp+4569@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :

Hi Reviewer,

The main change this branch makes is to make the default revision page not show any diffs by default and having them loaded by the wonders of xhr.

I've put some effort in to making sure non-js users have a decent experience -- instead of xhr-ing a diff, when scripting is disabled the user is taken to a page that just contains the the diff for the file.

The code, particularly in revision_ui.py, is now much less than clear. I'll fix that in the next branch tomorrow -- basically if you think the new behaviour is good, I can then clean up the innards without changing behaviour tomorrow (perhaps it would have been better to clean up the innards then clean up the behaviour, but oh well).

Revision history for this message
Paul Hummer (rockstar) wrote :

This is very sexy!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'loggerhead/apps/branch.py'
2--- loggerhead/apps/branch.py 2009-03-13 05:36:26 +0000
3+++ loggerhead/apps/branch.py 2009-03-17 00:02:21 +0000
4@@ -11,15 +11,16 @@
5 from paste import httpexceptions
6
7 from loggerhead.apps import static_app
8+from loggerhead.controllers.annotate_ui import AnnotateUI
9+from loggerhead.controllers.atom_ui import AtomUI
10 from loggerhead.controllers.changelog_ui import ChangeLogUI
11+from loggerhead.controllers.diff_ui import DiffUI
12+from loggerhead.controllers.download_ui import DownloadUI
13+from loggerhead.controllers.filediff_ui import FileDiffUI
14 from loggerhead.controllers.inventory_ui import InventoryUI
15-from loggerhead.controllers.annotate_ui import AnnotateUI
16 from loggerhead.controllers.revision_ui import RevisionUI
17 from loggerhead.controllers.revlog_ui import RevLogUI
18-from loggerhead.controllers.atom_ui import AtomUI
19-from loggerhead.controllers.download_ui import DownloadUI
20 from loggerhead.controllers.search_ui import SearchUI
21-from loggerhead.controllers.diff_ui import DiffUI
22 from loggerhead.history import History
23 from loggerhead import util
24
25@@ -79,15 +80,16 @@
26 return self._static_url_base + path
27
28 controllers_dict = {
29+ '+filediff': FileDiffUI,
30+ '+revlog': RevLogUI,
31 'annotate': AnnotateUI,
32+ 'atom': AtomUI,
33 'changes': ChangeLogUI,
34+ 'diff': DiffUI,
35+ 'download': DownloadUI,
36 'files': InventoryUI,
37 'revision': RevisionUI,
38- '+revlog': RevLogUI,
39- 'download': DownloadUI,
40- 'atom': AtomUI,
41 'search': SearchUI,
42- 'diff': DiffUI,
43 }
44
45 def last_updated(self):
46
47=== added file 'loggerhead/controllers/filediff_ui.py'
48--- loggerhead/controllers/filediff_ui.py 1970-01-01 00:00:00 +0000
49+++ loggerhead/controllers/filediff_ui.py 2009-03-17 06:43:34 +0000
50@@ -0,0 +1,97 @@
51+from StringIO import StringIO
52+import urllib
53+
54+from bzrlib import diff
55+from bzrlib import errors
56+
57+from loggerhead import util
58+from loggerhead.controllers import TemplatedBranchView
59+
60+def _process_diff(difftext):
61+ chunks = []
62+ chunk = None
63+ for line in difftext.splitlines():
64+ if len(line) == 0:
65+ continue
66+ if line.startswith('+++ ') or line.startswith('--- '):
67+ continue
68+ if line.startswith('@@ '):
69+ # new chunk
70+ if chunk is not None:
71+ chunks.append(chunk)
72+ chunk = util.Container()
73+ chunk.diff = []
74+ split_lines = line.split(' ')[1:3]
75+ lines = [int(x.split(',')[0][1:]) for x in split_lines]
76+ old_lineno = lines[0]
77+ new_lineno = lines[1]
78+ elif line.startswith(' '):
79+ chunk.diff.append(util.Container(old_lineno=old_lineno,
80+ new_lineno=new_lineno,
81+ type='context',
82+ line=line[1:]))
83+ old_lineno += 1
84+ new_lineno += 1
85+ elif line.startswith('+'):
86+ chunk.diff.append(util.Container(old_lineno=None,
87+ new_lineno=new_lineno,
88+ type='insert', line=line[1:]))
89+ new_lineno += 1
90+ elif line.startswith('-'):
91+ chunk.diff.append(util.Container(old_lineno=old_lineno,
92+ new_lineno=None,
93+ type='delete', line=line[1:]))
94+ old_lineno += 1
95+ else:
96+ chunk.diff.append(util.Container(old_lineno=None,
97+ new_lineno=None,
98+ type='unknown',
99+ line=repr(line)))
100+ if chunk is not None:
101+ chunks.append(chunk)
102+ return chunks
103+
104+
105+def diff_chunks_for_file(file_id, old_tree, new_tree):
106+ try:
107+ old_lines = old_tree.get_file_lines(file_id)
108+ except errors.NoSuchId:
109+ old_lines = []
110+ try:
111+ new_lines = new_tree.get_file_lines(file_id)
112+ except errors.NoSuchId:
113+ new_lines = []
114+ buffer = StringIO()
115+ if old_lines != new_lines:
116+ try:
117+ diff.internal_diff('', old_lines, '', new_lines, buffer)
118+ except errors.BinaryFile:
119+ difftext = ''
120+ else:
121+ difftext = buffer.getvalue()
122+ else:
123+ difftext = ''
124+
125+ return _process_diff(difftext)
126+
127+
128+class FileDiffUI(TemplatedBranchView):
129+
130+ template_path = 'loggerhead.templates.filediff'
131+
132+ def get_values(self, path, kwargs, headers):
133+ history = self._history
134+
135+ revid = urllib.unquote(self.args[0])
136+ compare_revid = urllib.unquote(self.args[1])
137+ file_id = urllib.unquote(self.args[2])
138+
139+ repository = self._history._branch.repository
140+ old_tree, new_tree = repository.revision_trees([compare_revid, revid])
141+
142+ chunks = diff_chunks_for_file(file_id, old_tree, new_tree)
143+
144+ return {
145+ 'util': util,
146+ 'chunks': chunks,
147+ }
148
149=== modified file 'loggerhead/controllers/revision_ui.py'
150--- loggerhead/controllers/revision_ui.py 2009-03-17 02:19:20 +0000
151+++ loggerhead/controllers/revision_ui.py 2009-03-17 06:43:34 +0000
152@@ -17,70 +17,26 @@
153 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
154 #
155
156-from StringIO import StringIO
157-
158-import bzrlib.diff
159-from bzrlib import errors
160+import simplejson
161+import urllib
162
163 from paste.httpexceptions import HTTPServerError
164
165 from loggerhead import util
166 from loggerhead.controllers import TemplatedBranchView
167+from loggerhead.controllers.filediff_ui import diff_chunks_for_file
168 from loggerhead.history import rich_filename
169
170
171 DEFAULT_LINE_COUNT_LIMIT = 3000
172
173+def dq(p):
174+ return urllib.quote(urllib.quote(p, safe=''))
175
176 class RevisionUI(TemplatedBranchView):
177
178 template_path = 'loggerhead.templates.revision'
179
180- def _process_diff(self, diff):
181- # doesn't really need to be a method; could be static.
182- chunks = []
183- chunk = None
184- for line in diff.splitlines():
185- if len(line) == 0:
186- continue
187- if line.startswith('+++ ') or line.startswith('--- '):
188- continue
189- if line.startswith('@@ '):
190- # new chunk
191- if chunk is not None:
192- chunks.append(chunk)
193- chunk = util.Container()
194- chunk.diff = []
195- split_lines = line.split(' ')[1:3]
196- lines = [int(x.split(',')[0][1:]) for x in split_lines]
197- old_lineno = lines[0]
198- new_lineno = lines[1]
199- elif line.startswith(' '):
200- chunk.diff.append(util.Container(old_lineno=old_lineno,
201- new_lineno=new_lineno,
202- type='context',
203- line=line[1:]))
204- old_lineno += 1
205- new_lineno += 1
206- elif line.startswith('+'):
207- chunk.diff.append(util.Container(old_lineno=None,
208- new_lineno=new_lineno,
209- type='insert', line=line[1:]))
210- new_lineno += 1
211- elif line.startswith('-'):
212- chunk.diff.append(util.Container(old_lineno=old_lineno,
213- new_lineno=None,
214- type='delete', line=line[1:]))
215- old_lineno += 1
216- else:
217- chunk.diff.append(util.Container(old_lineno=None,
218- new_lineno=None,
219- type='unknown',
220- line=repr(line)))
221- if chunk is not None:
222- chunks.append(chunk)
223- return chunks
224-
225 def _parse_diffs(self, old_tree, new_tree, delta, specific_path):
226 """
227 Return a list of processed diffs, in the format::
228@@ -98,57 +54,36 @@
229 ),
230 )
231 """
232+ if specific_path:
233+ fid = new_tree.path2id(specific_path)
234+ kind = new_tree.kind(fid)
235+ chunks=diff_chunks_for_file(fid, old_tree, new_tree)
236+ return [util.Container(
237+ filename=rich_filename(specific_path, kind), file_id=fid,
238+ chunks=chunks)]
239+
240 process = []
241 out = []
242
243- def include_specific_path(path):
244- return specific_path == path
245- def include_all_paths(path):
246- return True
247- if specific_path:
248- include_path = include_specific_path
249- else:
250- include_path = include_all_paths
251-
252 for old_path, new_path, fid, \
253 kind, text_modified, meta_modified in delta.renamed:
254- if text_modified and include_path(new_path):
255- process.append((old_path, new_path, fid, kind))
256+ if text_modified:
257+ process.append((new_path, fid, kind))
258 for path, fid, kind, text_modified, meta_modified in delta.modified:
259- if include_path(path):
260- process.append((path, path, fid, kind))
261+ process.append((path, fid, kind))
262 for path, fid, kind in delta.added:
263- if kind == 'file' and include_path(path):
264- process.append((path, path, fid, kind))
265+ if kind == 'file':
266+ process.append((path, fid, kind))
267 for path, fid, kind in delta.removed:
268- if kind == 'file' and include_path(path):
269- process.append((path, path, fid, kind))
270-
271- process.sort(key=lambda x:x[1])
272-
273- for old_path, new_path, fid, kind in process:
274- try:
275- old_lines = old_tree.get_file_lines(fid)
276- except errors.NoSuchId:
277- old_lines = []
278- try:
279- new_lines = new_tree.get_file_lines(fid)
280- except errors.NoSuchId:
281- new_lines = []
282- buffer = StringIO()
283- if old_lines != new_lines:
284- try:
285- bzrlib.diff.internal_diff(old_path, old_lines,
286- new_path, new_lines, buffer)
287- except bzrlib.errors.BinaryFile:
288- diff = ''
289- else:
290- diff = buffer.getvalue()
291- else:
292- diff = ''
293+ if kind == 'file':
294+ process.append((path, fid, kind))
295+
296+ process.sort()
297+
298+ for new_path, fid, kind in process:
299 out.append(util.Container(
300 filename=rich_filename(new_path, kind), file_id=fid,
301- chunks=self._process_diff(diff)))
302+ chunks=[]))
303
304 return out
305
306@@ -200,6 +135,20 @@
307 if path in ('', '/'):
308 path = None
309 change.changes, diffs = self.get_changes_with_diff(change, compare_revid, path)
310+ link_data = {}
311+ path_to_id = {}
312+ if compare_revid is None:
313+ if change.parents:
314+ cr = change.parents[0].revid
315+ else:
316+ cr = 'null:'
317+ else:
318+ cr = compare_revid
319+ for i, item in enumerate(diffs):
320+ item.index = i
321+ link_data['diff-' + str(i)] = '%s/%s/%s' % (
322+ dq(revid), dq(cr), dq(item.file_id))
323+ path_to_id[item.filename] = 'diff-' + str(i)
324 # add parent & merge-point branch-nick info, in case it's useful
325 h.get_branch_nicks([change])
326
327@@ -215,7 +164,10 @@
328 'revid': revid,
329 'change': change,
330 'diffs': diffs,
331+ 'link_data': simplejson.dumps(link_data),
332 'specific_path': path,
333+ 'json_specific_path': simplejson.dumps(path),
334+ 'path_to_id': simplejson.dumps(path_to_id),
335 'start_revid': start_revid,
336 'filter_file_id': filter_file_id,
337 'util': util,
338
339=== modified file 'loggerhead/static/javascript/custom.js'
340--- loggerhead/static/javascript/custom.js 2009-03-16 03:22:02 +0000
341+++ loggerhead/static/javascript/custom.js 2009-03-17 04:26:28 +0000
342@@ -86,6 +86,7 @@
343 this.expand_icon = config.expand_icon;
344 this.source = config.source;
345 this.loading = config.loading;
346+ this.node_process = config.node_process;
347 }
348
349 function get_height(node) {
350@@ -99,17 +100,19 @@
351 return height;
352 }
353
354-Collapsable.prototype._load_finished = function(tid, res)
355+Collapsable.prototype._load_finished = function(tid, res, args)
356 {
357 var newNode = Y.Node.create(res.responseText.split('\n').splice(1).join(''));
358+ if (this.node_process)
359+ this.node_process(newNode);
360 this.source_target.ancestor().replaceChild(newNode, this.source_target);
361 this.source_target = null;
362 this.source = null;
363 this.loading.setStyle('display', 'none');
364- this.open();
365+ this.open(args[0]);
366 };
367
368-Collapsable.prototype.open = function()
369+Collapsable.prototype.open = function(callback)
370 {
371 if (this.source) {
372 this.loading.setStyle('display', 'block');
373@@ -117,6 +120,7 @@
374 this.source,
375 {
376 on: {complete: this._load_finished},
377+ arguments: [callback],
378 context: this
379 });
380 return;
381@@ -146,7 +150,7 @@
382 duration: 0.2
383 });
384
385- anim.on('end', this.openComplete, this);
386+ anim.on('end', this.openComplete, this, callback);
387 container.setStyle('marginBottom', close_height - open_height);
388 if (this.close_node) {
389 this.close_node.setStyle('display', 'none');
390@@ -156,8 +160,9 @@
391 anim.run();
392 };
393
394-Collapsable.prototype.openComplete = function()
395+Collapsable.prototype.openComplete = function(evt, callback)
396 {
397+ if (callback) callback();
398 this.is_open = true;
399 };
400
401
402=== modified file 'loggerhead/static/javascript/diff.js'
403--- loggerhead/static/javascript/diff.js 2009-03-16 21:53:23 +0000
404+++ loggerhead/static/javascript/diff.js 2009-03-17 06:51:29 +0000
405@@ -87,13 +87,14 @@
406
407 function toggle_unified_sbs(event) {
408 event.preventDefault();
409+ var pts = Y.all(".pseudotable");
410 if (unified) {
411- Y.all(".pseudotable").each(make_sbs);
412+ pts && pts.each(make_sbs);
413 unified = false;
414 Y.get("#toggle_unified_sbs").set('innerHTML', "Show unified diffs");
415 }
416 else {
417- Y.all(".pseudotable").each(make_unified);
418+ pts && pts.each(make_unified);
419 unified = true;
420 Y.get("#toggle_unified_sbs").set('innerHTML', "Show diffs side-by-side");
421 }
422@@ -103,7 +104,7 @@
423
424 function toggle_expand_all_revisionview(action)
425 {
426- var diffs = Y.all('.diffBox');
427+ var diffs = Y.all('.diff');
428 if (diffs == null) return;
429 diffs.each(
430 function(item, i)
431@@ -142,24 +143,61 @@
432 '#collapse_all a'
433 );
434
435+function node_process(node) {
436+ if (!unified) {
437+ node.get('children').filter('.pseudotable').each(make_sbs);
438+ }
439+}
440+
441+function zoom_to_diff (path) {
442+ var collapsable = Y.get('#' + path_to_id[path]).collapsable;
443+ if (!collapsable.is_open) {
444+ collapsable.open(
445+ function () {
446+ window.location.hash = '#' + path;
447+ });
448+ }
449+}
450+
451 Y.on(
452 "domready", function () {
453 Y.all(".show_if_js").removeClass("show_if_js");
454- var diffs = Y.all('.diffBox');
455+ Y.all("#list-files a").on(
456+ 'click',
457+ function (e) {
458+ e.preventDefault();
459+ var path = decodeURIComponent(e.target.get('href').split('#')[1]);
460+ window.location.hash = '#' + path;
461+ zoom_to_diff(path);
462+ });
463+ var diffs = Y.all('.diff');
464 if (diffs == null) return;
465 diffs.each(
466 function(item, i)
467 {
468- item.query('.expand_diff').on('click', function() { collapsable.toggle(); });
469+ var source_url = null;
470+ if (!specific_path)
471+ source_url = global_path + '+filediff/' + link_data[item.get('id')];
472+ item.query('.the-link').on(
473+ 'click',
474+ function(e) {
475+ e.preventDefault();
476+ collapsable.toggle();
477+ });
478 var collapsable = new Collapsable(
479 {
480 expand_icon: item.query('.expand_diff'),
481- open_node: item.ancestor().query('.diffinfo'),
482+ open_node: item.query('.diffinfo'),
483 close_node: null,
484- source: null,
485- source_target: null,
486- is_open: true
487+ source: source_url,
488+ source_target: item.query('.source_target'),
489+ is_open: specific_path != null,
490+ loading: item.query('.loading'),
491+ node_process: node_process
492 });
493 item.collapsable=collapsable;
494 });
495+ if (window.location.hash && !specific_path) {
496+ zoom_to_diff(window.location.hash.substring(1));
497+ }
498 });
499
500=== modified file 'loggerhead/templatefunctions.py'
501--- loggerhead/templatefunctions.py 2009-03-07 07:29:26 +0000
502+++ loggerhead/templatefunctions.py 2009-03-17 06:32:25 +0000
503@@ -38,20 +38,20 @@
504
505
506 @templatefunc
507-def file_change_summary(url, entry, link_style='normal', currently_showing=None):
508- if link_style == 'fragment':
509+def file_change_summary(url, entry, style='normal', currently_showing=None):
510+ if style == 'fragment':
511 def file_link(filename):
512- if currently_showing:
513- if filename == currently_showing:
514- return '<b><a href="#%s">%s</a></b>' % (
515- cgi.escape(filename), cgi.escape(filename))
516- else:
517- return revision_link(url, entry.revno, filename)
518+ if currently_showing and filename == currently_showing:
519+ return '<b><a href="#%s">%s</a></b>' % (
520+ cgi.escape(filename), cgi.escape(filename))
521 else:
522- return '<a href="#%s">%s</a>' % (
523- cgi.escape(filename), cgi.escape(filename))
524+ return revision_link(
525+ url, entry.revno, filename, '#' + filename)
526 else:
527- file_link = lambda filename: revision_link(url, entry.revno, filename)
528+ def file_link(filename):
529+ return '<a href="%s%s" title="View changes to %s in revision %s">%s</a>'%(
530+ url(['/revision', entry.revno]), '#' + filename, cgi.escape(filename),
531+ cgi.escape(entry.revno), cgi.escape(filename))
532 return _pt('revisionfilechanges').expand(
533 url=url, entry=entry, file_link=file_link,
534 currently_showing=currently_showing, **templatefunctions)
535@@ -120,7 +120,7 @@
536 url(['/annotate', revno, path]), cgi.escape(path), cgi.escape(path))
537
538 @templatefunc
539-def revision_link(url, revno, path):
540- return '<a href="%s" title="View changes to %s in revision %s">%s</a>'%(
541- url(['/revision', revno, path]), cgi.escape(path), cgi.escape(revno),
542- cgi.escape(path))
543+def revision_link(url, revno, path, frag=''):
544+ return '<a href="%s%s" title="View changes to %s in revision %s">%s</a>'%(
545+ url(['/revision', revno, path]), frag, cgi.escape(path),
546+ cgi.escape(revno), cgi.escape(path))
547
548=== added file 'loggerhead/templates/filediff.pt'
549--- loggerhead/templates/filediff.pt 1970-01-01 00:00:00 +0000
550+++ loggerhead/templates/filediff.pt 2009-03-17 00:02:21 +0000
551@@ -0,0 +1,25 @@
552+<div class="diffinfo">
553+ <div class="pseudotable unified"
554+ tal:repeat="chunk chunks">
555+
556+ <tal:block condition="not:repeat/chunk/start">
557+ <div class="pseudorow context-row">
558+ <div class="lineNumber separate"></div>
559+ <div class="lineNumber second separate"></div>
560+ <div class="code separate"></div>
561+ <div class="clear"><!-- --></div>
562+ </div>
563+ </tal:block>
564+
565+ <div tal:repeat="line chunk/diff"
566+ tal:attributes="class string:pseudorow ${line/type}-row">
567+ <div class="lineNumber first"
568+ tal:content="structure python:util.fill_div(line.old_lineno)"></div>
569+ <div class="lineNumber second"
570+ tal:content="structure python:util.fill_div(line.new_lineno)"></div>
571+ <div tal:attributes="class string:code ${line/type}"
572+ tal:content="structure python:util.fill_div(util.html_clean(line.line))"></div>
573+ <div class="clear"><!-- --></div>
574+ </div>
575+ </div>
576+</div>
577
578=== modified file 'loggerhead/templates/revision.pt'
579--- loggerhead/templates/revision.pt 2009-03-16 06:44:50 +0000
580+++ loggerhead/templates/revision.pt 2009-03-17 03:49:33 +0000
581@@ -11,6 +11,11 @@
582 tal:attributes="href python:branch.static_url('/static/css/diff.css')"/>
583 <script type="text/javascript"
584 tal:attributes="src python:branch.static_url('/static/javascript/diff.js')"></script>
585+ <script type="text/javascript">
586+ var link_data = <tal:b content="link_data" />;
587+ var specific_path = <tal:b content="json_specific_path" />;
588+ var path_to_id = <tal:b content="path_to_id" />;
589+ </script>
590 </metal:block>
591 </head>
592
593@@ -92,14 +97,16 @@
594 </tal:we-are-comparing>
595
596 <tal:revision-info replace="structure python:revisioninfo(url, branch, change, 'fragment', specific_path)" />
597- <p class="expand" id="expand_all" style="display:none;"><a href="#">
598- <img tal:attributes="src python:branch.static_url('/static/images/treeCollapsed.png')"
599- alt="expand all" /> expand all</a>
600- </p>
601- <p class="expand show_if_js" id="collapse_all"><a href="#">
602- <img tal:attributes="src python:branch.static_url('/static/images/treeExpanded.png')"
603- alt="collapse all" /> collapse all</a>
604- </p>
605+ <tal:specific-path condition="not:specific_path">
606+ <p class="expand show_if_js" id="expand_all"><a href="#">
607+ <img tal:attributes="src python:branch.static_url('/static/images/treeCollapsed.png')"
608+ alt="expand all" /> expand all</a>
609+ </p>
610+ <p class="expand" id="collapse_all" style="display:none;"><a href="#">
611+ <img tal:attributes="src python:branch.static_url('/static/images/treeExpanded.png')"
612+ alt="collapse all" /> collapse all</a>
613+ </p>
614+ </tal:specific-path>
615 <!-- Table -->
616 <p class="expand show_if_js"><a id="toggle_unified_sbs" href="#">Show diffs side-by-side</a></p>
617 <p class="codin"><img tal:attributes="src python:branch.static_url('/static/images/newCode.gif')" alt="added" /> added</p>
618@@ -110,22 +117,34 @@
619
620 <div metal:fill-slot="content">
621 <div class="diff"
622- tal:repeat="item diffs">
623+ tal:repeat="item diffs" tal:attributes="id string:diff-${item/index}">
624
625- <div class="diffBox">
626- <img tal:attributes="src python:branch.static_url('/static/images/treeExpanded.png');
627- title python:branch.static_url('/static/images/treeCollapsed.png');
628- alt python:branch.static_url('/static/images/treeExpanded.png')"
629- class="expand_diff" />
630+ <div class="diffBox">
631 <a tal:attributes="href python:url(['/revision', change.revno, item.filename], clear=1);
632 id string:${item/filename};
633 title string:View changes to ${item/filename} only"
634- tal:content="item/filename">
635+ class="the-link">
636+ <img tal:attributes="src python:branch.static_url('/static/images/treeCollapsed.png');
637+ title python:branch.static_url('/static/images/treeCollapsed.png');
638+ alt python:branch.static_url('/static/images/treeExpanded.png')"
639+ class="expand_diff" tal:condition="not:specific_path" />
640+ <img tal:attributes="src python:branch.static_url('/static/images/treeExpanded.png');
641+ title python:branch.static_url('/static/images/treeCollapsed.png');
642+ alt python:branch.static_url('/static/images/treeExpanded.png')"
643+ class="expand_diff" tal:condition="specific_path" />
644+ <tal:b content="item/filename" />
645 </a>
646 </div>
647 <div style="overflow: hidden">
648 <div class="container">
649- <div class="diffinfo">
650+ <div class="loading" style="display:none">
651+ <img tal:attributes="src python:branch.static_url('/static/images/spinner.gif')" />
652+ </div>
653+ <div class="diffinfo" tal:condition="not:specific_path">
654+ <div class="source_target" >
655+ </div>
656+ </div>
657+ <div class="diffinfo" tal:condition="specific_path">
658 <div class="pseudotable unified"
659 tal:repeat="chunk item/chunks">
660

Subscribers

People subscribed via source and target branches