[PATCH 3 of 3 RFC] hgweb: handle obsolete changesets gracefully

Dan Villiom Podlaski Christiansen danchr at gmail.com
Thu Aug 8 02:48:46 CDT 2013


# HG changeset patch
# User Dan Villiom Podlaski Christiansen  <danchr at gmail.com>
# Date 1375624663 -7200
#      Sun Aug 04 15:57:43 2013 +0200
# Node ID 5507e2af80f5e8026a054ce440c0c8958134eefe
# Parent  b1f7ae28c3e371a70ee363a4fbe5d50063a224fc
hgweb: handle obsolete changesets gracefully

If the changeset has any successors, issue a 403 Moved Permanently;
otherwise we issue a 410 Gone. Please note that this is slightly
misleading for 'secret' changesets, as they may appear later on.

diff --git a/mercurial/hgweb/common.py b/mercurial/hgweb/common.py
--- a/mercurial/hgweb/common.py
+++ b/mercurial/hgweb/common.py
@@ -9,12 +9,14 @@
 import errno, mimetypes, os
 
 HTTP_OK = 200
+HTTP_MOVED_PERMANENTLY = 301
 HTTP_NOT_MODIFIED = 304
 HTTP_BAD_REQUEST = 400
 HTTP_UNAUTHORIZED = 401
 HTTP_FORBIDDEN = 403
 HTTP_NOT_FOUND = 404
 HTTP_METHOD_NOT_ALLOWED = 405
+HTTP_GONE = 410
 HTTP_SERVER_ERROR = 500
 
 
diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py
--- a/mercurial/hgweb/hgweb_mod.py
+++ b/mercurial/hgweb/hgweb_mod.py
@@ -8,11 +8,13 @@
 
 import os
 from mercurial import ui, hg, hook, error, encoding, templater, util, repoview
+from mercurial import obsolete
+from mercurial.node import short
 from mercurial.templatefilters import websub
 from mercurial.i18n import _
 from common import get_stat, ErrorResponse, permhooks, caching
-from common import HTTP_OK, HTTP_NOT_MODIFIED, HTTP_BAD_REQUEST
-from common import HTTP_NOT_FOUND, HTTP_SERVER_ERROR
+from common import HTTP_OK, HTTP_NOT_MODIFIED, HTTP_BAD_REQUEST, HTTP_GONE
+from common import HTTP_NOT_FOUND, HTTP_SERVER_ERROR, HTTP_MOVED_PERMANENTLY
 from request import wsgirequest
 import webcommands, protocol, webutil, re
 
@@ -249,6 +251,33 @@ class hgweb(object):
 
             return content
 
+        except error.FilteredLookupError, err:
+            succsets = obsolete.successorssets(self.repo, err.rev)
+
+            if not succsets:
+                req.respond(HTTP_GONE, ctype)
+
+                return tmpl('error', error=err.message)
+
+            elif len(succsets) != 1:
+                # TODO: changeset has divergent successors
+                req.respond(HTTP_SERVER_ERROR, ctype)
+
+                return tmpl('error', error=err.message)
+
+            location = [req.url.rstrip('/')]
+            location += req.form['cmd']
+
+            location.append(short(succsets[0][-1]))
+
+            if 'file' in req.form:
+                location += req.form['file']
+
+            req.headers.extend([('Location', '/'.join(location))])
+            req.respond(HTTP_MOVED_PERMANENTLY, ctype)
+
+            return tmpl('error', error=err.message)
+
         except (error.LookupError, error.RepoLookupError), err:
             req.respond(HTTP_NOT_FOUND, ctype)
             msg = str(err)
diff --git a/mercurial/hgweb/webutil.py b/mercurial/hgweb/webutil.py
--- a/mercurial/hgweb/webutil.py
+++ b/mercurial/hgweb/webutil.py
@@ -201,6 +201,9 @@ def cleanpath(repo, path):
 def changeidctx (repo, changeid):
     try:
         ctx = repo[changeid]
+    except error.FilteredLookupError:
+        raise
+
     except error.RepoError:
         man = repo.manifest
         ctx = repo[man.linkrev(man.rev(man.lookup(changeid)))]
diff --git a/tests/test-hgweb-commands.t b/tests/test-hgweb-commands.t
--- a/tests/test-hgweb-commands.t
+++ b/tests/test-hgweb-commands.t
@@ -1385,7 +1385,7 @@ proper status for filtered revision
   $ PATH_INFO=/rev/5; export PATH_INFO
   $ QUERY_STRING='style=raw'
   $ python hgweb.cgi #> search
-  Status: 404 Not Found\r (esc)
+  Status: 410 Gone\r (esc)
   ETag: *\r (glob) (esc)
   Content-Type: text/plain; charset=ascii\r (esc)
   \r (esc)
@@ -1399,7 +1399,7 @@ proper status for filtered revision
   $ PATH_INFO=/rev/4; export PATH_INFO
   $ QUERY_STRING='style=raw'
   $ python hgweb.cgi #> search
-  Status: 404 Not Found\r (esc)
+  Status: 410 Gone\r (esc)
   ETag: *\r (glob) (esc)
   Content-Type: text/plain; charset=ascii\r (esc)
   \r (esc)
@@ -1498,6 +1498,47 @@ filtered '0' changeset
   
   
 
+Test obsolete redirection
+
+  $ cat > ../obs.py << EOF
+  > import mercurial.obsolete
+  > mercurial.obsolete._enabled = True
+  > EOF
+  $ echo '[extensions]' >> $HGRCPATH
+  $ echo "rebase=" >> $HGRCPATH
+  $ echo "obs=${TESTTMP}/obs.py" >> $HGRCPATH
+  $ hg up 12
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo B > b; hg add b
+  $ hg ci -m 6
+
+Use a rebase to obsolete r13, and test redirection
+
+  $ hg rebase -r 13 -d 11
+  $ PATH_INFO=/rev/13; export PATH_INFO
+  $ QUERY_STRING='style=raw'
+  $ python hgweb.cgi #> search
+  Status: 301 Moved Permanently\r (esc)
+  ETag: *\r (glob) (esc)
+  Location: /test/rev/0d601cf5c587\r (esc)
+  Content-Type: text/plain; charset=ascii\r (esc)
+  \r (esc)
+  
+  error: revision 13 is hidden
+
+Rebase r13 once more, testing divergent changesets
+
+  $ hg --hidden rebase -r 13 -d 9
+  $ PATH_INFO=/rev/13; export PATH_INFO
+  $ QUERY_STRING='style=raw'
+  $ python hgweb.cgi #> search
+  Status: 500 Internal Server Error\r (esc)
+  ETag: *\r (glob) (esc)
+  Content-Type: text/plain; charset=ascii\r (esc)
+  \r (esc)
+  
+  error: revision 13 is hidden
+
 
 
   $ cd ..
diff --git a/tests/test-obsolete.t b/tests/test-obsolete.t
--- a/tests/test-obsolete.t
+++ b/tests/test-obsolete.t
@@ -747,7 +747,7 @@ check filelog view
   $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'rev/68'
   200 Script output follows
   $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'rev/67'
-  404 Not Found
+  410 Gone
   [1]
 
 check that web.view config option:


More information about the Mercurial-devel mailing list