# HG changeset patch # User Ry4an Brase # Date 2008-11-03 09:20:28 # Node ID c21d236ca897105057dbf8d5609057eaa6f28088 # Parent 68374f1c8c87177dec40de56f3b046a84693e9be 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. diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py --- a/mercurial/hgweb/webcommands.py +++ b/mercurial/hgweb/webcommands.py @@ -264,6 +264,7 @@ def manifest(web, req, tmpl): node = ctx.node() files = {} + dirs = {} parity = paritygen(web.stripecount) if path and path[-1] != "/": @@ -275,20 +276,25 @@ def manifest(web, req, tmpl): 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 @@ def manifest(web, req, tmpl): "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(), diff --git a/templates/coal/map b/templates/coal/map --- a/templates/coal/map +++ b/templates/coal/map @@ -23,7 +23,7 @@ searchentry = shortlogentry.tmpl changeset = changeset.tmpl manifest = manifest.tmpl -direntry = ' {basename|escape}/drwxr-xr-x' +direntry = ' {basename|escape}/ {emptydirs|escape}drwxr-xr-x' fileentry = ' {basename|escape}{size}{permissions|permissions}' filerevision = filerevision.tmpl diff --git a/templates/gitweb/map b/templates/gitweb/map --- a/templates/gitweb/map +++ b/templates/gitweb/map @@ -19,7 +19,7 @@ changelogentry = changelogentry.tmpl searchentry = changelogentry.tmpl changeset = changeset.tmpl manifest = manifest.tmpl -direntry = 'drwxr-xr-x#basename|escape#files' +direntry = 'drwxr-xr-x#basename|escape# #emptydirs|escape#files' fileentry = '#permissions|permissions##date|isodate##size##basename|escape#file | revisions | annotate' filerevision = filerevision.tmpl fileannotate = fileannotate.tmpl diff --git a/templates/map b/templates/map --- a/templates/map +++ b/templates/map @@ -19,7 +19,7 @@ changelogentry = changelogentry.tmpl searchentry = changelogentry.tmpl changeset = changeset.tmpl manifest = manifest.tmpl -direntry = 'drwxr-xr-x   #basename|escape#/' +direntry = 'drwxr-xr-x   #basename|escape#/ #emptydirs|urlescape#' fileentry = '#permissions|permissions# #date|isodate# #size# #basename|escape#' filerevision = filerevision.tmpl fileannotate = fileannotate.tmpl diff --git a/templates/paper/map b/templates/paper/map --- a/templates/paper/map +++ b/templates/paper/map @@ -23,7 +23,7 @@ searchentry = ../coal/shortlogentry.tmpl changeset = ../coal/changeset.tmpl manifest = ../coal/manifest.tmpl -direntry = ' {basename|escape}/drwxr-xr-x' +direntry = ' {basename|escape}/ {emptydirs|escape}drwxr-xr-x' fileentry = ' {basename|escape}{size}{permissions|permissions}' filerevision = ../coal/filerevision.tmpl diff --git a/tests/test-hgweb-descend-empties b/tests/test-hgweb-descend-empties new file mode 100755 --- /dev/null +++ b/tests/test-hgweb-descend-empties @@ -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 diff --git a/tests/test-hgweb-descend-empties.out b/tests/test-hgweb-descend-empties.out new file mode 100644 --- /dev/null +++ b/tests/test-hgweb-descend-empties.out @@ -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 + + + + + + + + +test: files for changeset 9087c84a0f5d + + + +
+changelog +shortlog +graph +tags +changeset + +
+ +

files for changeset 9087c84a0f5d: /

+ + + + +
drwxr-xr-x  +   +   + [up] +
drwxr-xr-x   a1/ a2/a3/a4
drwxr-xr-x   b1/ b2
drwxr-xr-x   d1/ d2 + +
+ + + + + + +% ERRORS ENCOUNTERED