changeset 7305:c21d236ca897

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.
author Ry4an Brase <ry4an-hg@ry4an.org>
date Mon, 03 Nov 2008 10:20:28 +0100
parents 68374f1c8c87
children 8e46e59aaf4c
files mercurial/hgweb/webcommands.py templates/coal/map templates/gitweb/map templates/map templates/paper/map tests/test-hgweb-descend-empties tests/test-hgweb-descend-empties.out
diffstat 7 files changed, 111 insertions(+), 19 deletions(-) [+]
line wrap: on
line diff
--- 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>&nbsp;<td>&nbsp;<td>&nbsp;<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>&nbsp;<td>&nbsp;<td>&nbsp;<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>&nbsp;<td align=right><tt class="date">#date|isodate#</tt>&nbsp;<td align=right><tt>#size#</tt>&nbsp;<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>&nbsp;
+  <td>&nbsp;
+  <td>&nbsp;
+  <td><a href="/file/9087c84a0f5d/">[up]</a>
+</tr>
+<tr class="parity1"><td><tt>drwxr-xr-x</tt>&nbsp;<td>&nbsp;<td>&nbsp;<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>&nbsp;<td>&nbsp;<td>&nbsp;<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>&nbsp;<td>&nbsp;<td>&nbsp;<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