hgweb: descend empty directories in web view
When a manifest has a series of directories with nothing in them but a single
directory, displaying the entire chain of empty directories allows for
navigation down to the first non-empty directory with a single click.
Because Java links package hierarchy to directory hierarchy, and because Java
conventions include at least three empty directories at the top of this
hierarchy, descending down empty directories is very common in Java web tools.
--- a/mercurial/hgweb/webcommands.py Sun Nov 02 22:44:42 2008 +0100
+++ b/mercurial/hgweb/webcommands.py Mon Nov 03 10:20:28 2008 +0100
@@ -264,6 +264,7 @@
node = ctx.node()
files = {}
+ dirs = {}
parity = paritygen(web.stripecount)
if path and path[-1] != "/":
@@ -275,20 +276,25 @@
if f[:l] != path:
continue
remain = f[l:]
- idx = remain.find('/')
- if idx != -1:
- remain = remain[:idx+1]
- n = None
- files[remain] = (f, n)
+ elements = remain.split('/')
+ if len(elements) == 1:
+ files[remain] = f
+ else:
+ h = dirs # need to retain ref to dirs (root)
+ for elem in elements[0:-1]:
+ if elem not in h:
+ h[elem] = {}
+ h = h[elem]
+ if len(h) > 1:
+ break
+ h[None] = None # denotes files present
- if not files:
+ if not files and not dirs:
raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
def filelist(**map):
for f in util.sort(files):
- full, fnode = files[f]
- if not fnode:
- continue
+ full = files[f]
fctx = ctx.filectx(full)
yield {"file": full,
@@ -299,14 +305,21 @@
"permissions": mf.flags(full)}
def dirlist(**map):
- for f in util.sort(files):
- full, fnode = files[f]
- if fnode:
- continue
+ for d in util.sort(dirs):
+ emptydirs = []
+ h = dirs[d]
+ while isinstance(h, dict) and len(h) == 1:
+ k,v = h.items()[0]
+ if v:
+ emptydirs.append(k)
+ h = v
+
+ path = "%s%s" % (abspath, d)
yield {"parity": parity.next(),
- "path": "%s%s" % (abspath, f),
- "basename": f[:-1]}
+ "path": path,
+ "emptydirs": "/".join(emptydirs),
+ "basename": d}
return tmpl("manifest",
rev=ctx.rev(),
--- a/templates/coal/map Sun Nov 02 22:44:42 2008 +0100
+++ b/templates/coal/map Mon Nov 03 10:20:28 2008 +0100
@@ -23,7 +23,7 @@
changeset = changeset.tmpl
manifest = manifest.tmpl
-direntry = '<tr class="fileline parity{parity}"><td class="name"><a href="{url}file/{node|short}{path|urlescape}{sessionvars%urlparameter}"><img src="{staticurl}coal-folder.png"> {basename|escape}/</a><td class="size"></td><td class="permissions">drwxr-xr-x</td></tr>'
+direntry = '<tr class="fileline parity{parity}"><td class="name"><a href="{url}file/{node|short}{path|urlescape}{sessionvars%urlparameter}"><img src="{staticurl}coal-folder.png"> {basename|escape}/</a> <a href="{url}file/{node|short}{path|urlescape}/{emptydirs|urlescape}{sessionvars%urlparameter}">{emptydirs|escape}</a><td class="size"></td><td class="permissions">drwxr-xr-x</td></tr>'
fileentry = '<tr class="fileline parity{parity}"><td class="filename"><a href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}#l1"><img src="{staticurl}coal-file.png"> {basename|escape}</a></td><td class="size">{size}</td><td class="permissions">{permissions|permissions}</td></tr>'
filerevision = filerevision.tmpl
--- a/templates/gitweb/map Sun Nov 02 22:44:42 2008 +0100
+++ b/templates/gitweb/map Mon Nov 03 10:20:28 2008 +0100
@@ -19,7 +19,7 @@
searchentry = changelogentry.tmpl
changeset = changeset.tmpl
manifest = manifest.tmpl
-direntry = '<tr class="parity#parity#"><td style="font-family:monospace">drwxr-xr-x</td><td style="font-family:monospace"></td><td style="font-family:monospace"></td><td><a href="#url#file/#node|short##path|urlescape#{sessionvars%urlparameter}">#basename|escape#</a></td><td class="link"><a href="#url#file/#node|short##path|urlescape#{sessionvars%urlparameter}">files</a></td></tr>'
+direntry = '<tr class="parity#parity#"><td style="font-family:monospace">drwxr-xr-x</td><td style="font-family:monospace"></td><td style="font-family:monospace"></td><td><a href="#url#file/#node|short##path|urlescape#{sessionvars%urlparameter}">#basename|escape#</a> <a href="#url#file/#node|short##path|urlescape#/#emptydirs|urlescape#{sessionvars%urlparameter}">#emptydirs|escape#</a></td><td class="link"><a href="#url#file/#node|short##path|urlescape#{sessionvars%urlparameter}">files</a></td></tr>'
fileentry = '<tr class="parity#parity#"><td style="font-family:monospace">#permissions|permissions#</td><td style="font-family:monospace" align=right>#date|isodate#</td><td style="font-family:monospace" align=right>#size#</td><td class="list"><a class="list" href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#basename|escape#</a></td><td class="link"><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">file</a> | <a href="#url#log/#node|short#/#file|urlescape#{sessionvars%urlparameter}">revisions</a> | <a href="#url#annotate/#node|short#/#file|urlescape#{sessionvars%urlparameter}">annotate</a></td></tr>'
filerevision = filerevision.tmpl
fileannotate = fileannotate.tmpl
--- a/templates/map Sun Nov 02 22:44:42 2008 +0100
+++ b/templates/map Mon Nov 03 10:20:28 2008 +0100
@@ -19,7 +19,7 @@
searchentry = changelogentry.tmpl
changeset = changeset.tmpl
manifest = manifest.tmpl
-direntry = '<tr class="parity#parity#"><td><tt>drwxr-xr-x</tt> <td> <td> <td><a href="#url#file/#node|short##path|urlescape#{sessionvars%urlparameter}">#basename|escape#/</a>'
+direntry = '<tr class="parity#parity#"><td><tt>drwxr-xr-x</tt> <td> <td> <td><a href="#url#file/#node|short##path|urlescape#{sessionvars%urlparameter}">#basename|escape#/</a> <a href="#url#file/#node|short##path|urlescape#/#emptydirs|urlescape#{sessionvars%urlparameter}">#emptydirs|urlescape#</a>'
fileentry = '<tr class="parity#parity#"><td><tt>#permissions|permissions#</tt> <td align=right><tt class="date">#date|isodate#</tt> <td align=right><tt>#size#</tt> <td><a href="#url#file/#node|short#/#file|urlescape#{sessionvars%urlparameter}">#basename|escape#</a>'
filerevision = filerevision.tmpl
fileannotate = fileannotate.tmpl
--- a/templates/paper/map Sun Nov 02 22:44:42 2008 +0100
+++ b/templates/paper/map Mon Nov 03 10:20:28 2008 +0100
@@ -23,7 +23,7 @@
changeset = ../coal/changeset.tmpl
manifest = ../coal/manifest.tmpl
-direntry = '<tr class="fileline parity{parity}"><td class="name"><a href="{url}file/{node|short}{path|urlescape}{sessionvars%urlparameter}"><img src="{staticurl}coal-folder.png"> {basename|escape}/</a><td class="size"></td><td class="permissions">drwxr-xr-x</td></tr>'
+direntry = '<tr class="fileline parity{parity}"><td class="name"><a href="{url}file/{node|short}{path|urlescape}{sessionvars%urlparameter}"><img src="{staticurl}coal-folder.png"> {basename|escape}/</a> <a href="{url}file/{node|short}{path|urlescape}/{emptydirs|urlescape}{sessionvars%urlparameter}">{emptydirs|escape}</a><td class="size"></td><td class="permissions">drwxr-xr-x</td></tr>'
fileentry = '<tr class="fileline parity{parity}"><td class="filename"><a href="{url}file/{node|short}/{file|urlescape}#l1{sessionvars%urlparameter}"><img src="{staticurl}coal-file.png"> {basename|escape}</a></td><td class="size">{size}</td><td class="permissions">{permissions|permissions}</td></tr>'
filerevision = ../coal/filerevision.tmpl
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-hgweb-descend-empties Mon Nov 03 10:20:28 2008 +0100
@@ -0,0 +1,28 @@
+#!/bin/sh
+# Test chains of near empty directories, terminating 3 different ways:
+# - a1: file at level 4 (deepest)
+# - b1: two dirs at level 3
+# - e1: file at level 2
+
+echo % Set up the repo
+hg init test
+cd test
+mkdir -p a1/a2/a3/a4
+mkdir -p b1/b2/b3/b4
+mkdir -p b1/b2/c3/c4
+mkdir -p d1/d2/d3/d4
+echo foo > a1/a2/a3/a4/foo
+echo foo > b1/b2/b3/b4/foo
+echo foo > b1/b2/c3/c4/foo
+echo foo > d1/d2/d3/d4/foo
+echo foo > d1/d2/foo
+hg ci -Ama
+
+hg serve -n test -p $HGPORT -d --pid-file=hg.pid -E errors.log
+cat hg.pid >> $DAEMON_PIDS
+
+echo % manifest with descending
+"$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/file'
+
+echo % ERRORS ENCOUNTERED
+cat errors.log
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-hgweb-descend-empties.out Mon Nov 03 10:20:28 2008 +0100
@@ -0,0 +1,51 @@
+% Set up the repo
+adding a1/a2/a3/a4/foo
+adding b1/b2/b3/b4/foo
+adding b1/b2/c3/c4/foo
+adding d1/d2/d3/d4/foo
+adding d1/d2/foo
+% manifest with descending
+200 Script output follows
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+<link rel="icon" href="/static/hgicon.png" type="image/png">
+<meta name="robots" content="index, nofollow" />
+<link rel="stylesheet" href="/static/style.css" type="text/css" />
+
+<title>test: files for changeset 9087c84a0f5d</title>
+</head>
+<body>
+
+<div class="buttons">
+<a href="/log/0">changelog</a>
+<a href="/shortlog/0">shortlog</a>
+<a href="/graph">graph</a>
+<a href="/tags">tags</a>
+<a href="/rev/9087c84a0f5d">changeset</a>
+
+</div>
+
+<h2>files for changeset 9087c84a0f5d: /</h2>
+
+<table cellpadding="0" cellspacing="0">
+<tr class="parity0">
+ <td><tt>drwxr-xr-x</tt>
+ <td>
+ <td>
+ <td><a href="/file/9087c84a0f5d/">[up]</a>
+</tr>
+<tr class="parity1"><td><tt>drwxr-xr-x</tt> <td> <td> <td><a href="/file/9087c84a0f5d/a1">a1/</a> <a href="/file/9087c84a0f5d/a1/a2/a3/a4">a2/a3/a4</a><tr class="parity0"><td><tt>drwxr-xr-x</tt> <td> <td> <td><a href="/file/9087c84a0f5d/b1">b1/</a> <a href="/file/9087c84a0f5d/b1/b2">b2</a><tr class="parity1"><td><tt>drwxr-xr-x</tt> <td> <td> <td><a href="/file/9087c84a0f5d/d1">d1/</a> <a href="/file/9087c84a0f5d/d1/d2">d2</a>
+
+</table>
+
+<div class="logo">
+<a href="http://www.selenic.com/mercurial/">
+<img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
+</div>
+
+</body>
+</html>
+
+% ERRORS ENCOUNTERED