test-lfs-test-server: add a testcase for `hg serve`
authorMatt Harbison <matt_harbison@yahoo.com>
Sat, 17 Mar 2018 02:37:46 -0400
changeset 37153 f51c2780db3a
parent 37152 0348c778bf70
child 37154 f8e1f48de118
test-lfs-test-server: add a testcase for `hg serve` I haven't figured out yet how to make the authentication checks work for a specific list of users, so the 'web.allow-push' list is wildcarded. (It appears that the client doesn't react to a 401 by sending authentication data, which may be caused in part by not having all of the headers in httpbasicauthhandler's http_error_auth_reqed(), compared to a run of test-http.t. But in any case, we should probably have a separate set of tests for various authentication scenarios. As it is, without the wildcard, no push access is granted.) There are several deviations from the `lfs-test-server` case: - `hg serve` emits a Server header. I think Gregory indicated that this isn't easily suppressed. - `hg serve` names the "basic" transfer handler in the Batch API response. Not having to specify it was for backwards compatability, so this seems like the right thing to do. (`lfs-test-server` doesn't name it, whether it was explicitly requested by the client or not.) - PUT status for a newly created file is 201, per RFC-2616 [1]. The Basic Transfer API [2] shows an example upload transcript with a 200 response. It doesn't make much sense to re-upload a file (unless it is corrupt) in an example, but I wouldn't be surprised if some other implementations also expect 200 because of this. But the RFC says MUST use 201 for creation. - The Content-Type for the file transfers is "application/octet-stream", like the sample transcript (though I don't see it explicitly called out in the text elsewhere). Using "text/plain" seems clearly wrong. - `lfs-test-server` isn't removing the action property and sending back an error code like the spec calls out when a file is missing or corrupt. Doing so on the `hg serve` side reveals a bug in our client code when handling the response- it indicates the remote file is missing instead of corrupt around line 452. I'll probably glob over the Content-Length differences once this settles down. Prior to the recent hgweb refactoring, the Batch API response was using chunked encodings instead. Back to the RFC, I have no idea if the python framework handles the "MUST NOT ignore any Content-* (e.g. Content-Range) headers that it does not understand or implement and MUST return a 501" for a PUT request. [1] https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.6 [2] https://github.com/git-lfs/git-lfs/blob/master/docs/api/basic-transfers.md#uploads
tests/test-lfs-test-server.t
--- a/tests/test-lfs-test-server.t	Thu Mar 29 09:23:39 2018 -0700
+++ b/tests/test-lfs-test-server.t	Sat Mar 17 02:37:46 2018 -0400
@@ -1,13 +1,26 @@
+#testcases git-server hg-server
+
+#if git-server
 #require lfs-test-server
+#else
+#require serve
+#endif
 
+#if git-server
   $ LFS_LISTEN="tcp://:$HGPORT"
   $ LFS_HOST="localhost:$HGPORT"
   $ LFS_PUBLIC=1
   $ export LFS_LISTEN LFS_HOST LFS_PUBLIC
-#if no-windows
+#else
+  $ LFS_HOST="localhost:$HGPORT/.git/info/lfs"
+#endif
+
+#if no-windows git-server
   $ lfs-test-server &> lfs-server.log &
   $ echo $! >> $DAEMON_PIDS
-#else
+#endif
+
+#if windows git-server
   $ cat >> $TESTTMP/spawn.py <<EOF
   > import os
   > import subprocess
@@ -29,10 +42,23 @@
   > [extensions]
   > lfs=
   > [lfs]
-  > url=http://foo:bar@$LFS_HOST/
+  > url=http://foo:bar@$LFS_HOST
   > track=all()
+  > [web]
+  > push_ssl = False
+  > allow-push = *
   > EOF
 
+Use a separate usercache, otherwise the server sees what the client commits, and
+never requests a transfer.
+
+#if hg-server
+  $ hg init server
+  $ hg --config "lfs.usercache=$TESTTMP/servercache" -R server serve -d \
+  >    -p $HGPORT --pid-file=hg.pid -A $TESTTMP/access.log -E $TESTTMP/errors.log
+  $ cat hg.pid >> $DAEMON_PIDS
+#endif
+
   $ hg init repo1
   $ cd repo1
   $ echo THIS-IS-LFS > a
@@ -55,9 +81,11 @@
   listing keys for "bookmarks"
   lfs: computing set of blobs to upload
   Status: 200
-  Content-Length: 309
+  Content-Length: 309 (git-server !)
+  Content-Length: 350 (hg-server !)
   Content-Type: application/vnd.git-lfs+json
   Date: $HTTP_DATE$
+  Server: testing stub value (hg-server !)
   {
     "objects": [
       {
@@ -67,19 +95,23 @@
             "header": {
               "Accept": "application/vnd.git-lfs"
             }
-            "href": "http://localhost:$HGPORT/objects/31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b"
+            "href": "http://localhost:$HGPORT/objects/31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b" (git-server !)
+            "href": "http://localhost:$HGPORT/.hg/lfs/objects/31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b" (hg-server !)
           }
         }
         "oid": "31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b"
         "size": 12
       }
     ]
+    "transfer": "basic" (hg-server !)
   }
   lfs: uploading 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b (12 bytes)
-  Status: 200
+  Status: 200 (git-server !)
+  Status: 201 (hg-server !)
   Content-Length: 0
   Content-Type: text/plain; charset=utf-8
   Date: $HTTP_DATE$
+  Server: testing stub value (hg-server !)
   lfs: processed: 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b
   lfs: uploaded 1 files (12 bytes)
   1 changesets found
@@ -125,9 +157,11 @@
    branchmerge: False, force: False, partial: False
    ancestor: 000000000000, local: 000000000000+, remote: 99a7098854a3
   Status: 200
-  Content-Length: 311
+  Content-Length: 311 (git-server !)
+  Content-Length: 352 (hg-server !)
   Content-Type: application/vnd.git-lfs+json
   Date: $HTTP_DATE$
+  Server: testing stub value (hg-server !)
   {
     "objects": [
       {
@@ -137,19 +171,22 @@
             "header": {
               "Accept": "application/vnd.git-lfs"
             }
-            "href": "http://localhost:$HGPORT/objects/31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b"
+            "href": "http://localhost:$HGPORT/*/31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b" (glob)
           }
         }
         "oid": "31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b"
         "size": 12
       }
     ]
+    "transfer": "basic" (hg-server !)
   }
   lfs: downloading 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b (12 bytes)
   Status: 200
   Content-Length: 12
-  Content-Type: text/plain; charset=utf-8
+  Content-Type: text/plain; charset=utf-8 (git-server !)
+  Content-Type: application/octet-stream (hg-server !)
   Date: $HTTP_DATE$
+  Server: testing stub value (hg-server !)
   lfs: adding 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b to the usercache
   lfs: processed: 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b
    a: remote created -> g
@@ -157,7 +194,10 @@
   lfs: found 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b in the local lfs store
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
-When the server has some blobs already
+When the server has some blobs already.  `hg serve` doesn't offer to upload
+blobs that it already knows about.  Note that lfs-test-server is simply
+toggling the action to 'download'.  The Batch API spec says it should omit the
+actions property completely.
 
   $ hg mv a b
   $ echo ANOTHER-LARGE-FILE > c
@@ -176,21 +216,23 @@
   listing keys for "bookmarks"
   lfs: computing set of blobs to upload
   Status: 200
-  Content-Length: 901
+  Content-Length: 901 (git-server !)
+  Content-Length: 755 (hg-server !)
   Content-Type: application/vnd.git-lfs+json
   Date: $HTTP_DATE$
+  Server: testing stub value (hg-server !)
   {
     "objects": [
       {
-        "actions": {
-          "download": {
-            "expires_at": "$ISO_8601_DATE_TIME$"
-            "header": {
-              "Accept": "application/vnd.git-lfs"
-            }
-            "href": "http://localhost:$HGPORT/objects/31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b"
-          }
-        }
+        "actions": { (git-server !)
+          "download": { (git-server !)
+            "expires_at": "$ISO_8601_DATE_TIME$" (git-server !)
+            "header": { (git-server !)
+              "Accept": "application/vnd.git-lfs" (git-server !)
+            } (git-server !)
+            "href": "http://localhost:$HGPORT/objects/31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b" (git-server !)
+          } (git-server !)
+        } (git-server !)
         "oid": "31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b"
         "size": 12
       }
@@ -201,7 +243,7 @@
             "header": {
               "Accept": "application/vnd.git-lfs"
             }
-            "href": "http://localhost:$HGPORT/objects/37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19"
+            "href": "http://localhost:$HGPORT/*/37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19" (glob)
           }
         }
         "oid": "37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19"
@@ -214,26 +256,31 @@
             "header": {
               "Accept": "application/vnd.git-lfs"
             }
-            "href": "http://localhost:$HGPORT/objects/d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998"
+            "href": "http://localhost:$HGPORT/*/d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998" (glob)
           }
         }
         "oid": "d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998"
         "size": 19
       }
     ]
+    "transfer": "basic" (hg-server !)
   }
   lfs: need to transfer 2 objects (39 bytes)
   lfs: uploading 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 (20 bytes)
-  Status: 200
+  Status: 200 (git-server !)
+  Status: 201 (hg-server !)
   Content-Length: 0
   Content-Type: text/plain; charset=utf-8
   Date: $HTTP_DATE$
+  Server: testing stub value (hg-server !)
   lfs: processed: 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19
   lfs: uploading d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 (19 bytes)
-  Status: 200
+  Status: 200 (git-server !)
+  Status: 201 (hg-server !)
   Content-Length: 0
   Content-Type: text/plain; charset=utf-8
   Date: $HTTP_DATE$
+  Server: testing stub value (hg-server !)
   lfs: processed: d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998
   lfs: uploaded 2 files (39 bytes)
   1 changesets found
@@ -281,9 +328,11 @@
    branchmerge: False, force: False, partial: False
    ancestor: 99a7098854a3, local: 99a7098854a3+, remote: dfca2c9e2ef2
   Status: 200
-  Content-Length: 608
+  Content-Length: 608 (git-server !)
+  Content-Length: 670 (hg-server !)
   Content-Type: application/vnd.git-lfs+json
   Date: $HTTP_DATE$
+  Server: testing stub value (hg-server !)
   {
     "objects": [
       {
@@ -293,7 +342,7 @@
             "header": {
               "Accept": "application/vnd.git-lfs"
             }
-            "href": "http://localhost:$HGPORT/objects/37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19"
+            "href": "http://localhost:$HGPORT/*/37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19" (glob)
           }
         }
         "oid": "37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19"
@@ -306,27 +355,32 @@
             "header": {
               "Accept": "application/vnd.git-lfs"
             }
-            "href": "http://localhost:$HGPORT/objects/d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998"
+            "href": "http://localhost:$HGPORT/*/d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998" (glob)
           }
         }
         "oid": "d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998"
         "size": 19
       }
     ]
+    "transfer": "basic" (hg-server !)
   }
   lfs: need to transfer 2 objects (39 bytes)
   lfs: downloading 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 (20 bytes)
   Status: 200
   Content-Length: 20
-  Content-Type: text/plain; charset=utf-8
+  Content-Type: text/plain; charset=utf-8 (git-server !)
+  Content-Type: application/octet-stream (hg-server !)
   Date: $HTTP_DATE$
+  Server: testing stub value (hg-server !)
   lfs: adding 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 to the usercache
   lfs: processed: 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19
   lfs: downloading d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 (19 bytes)
   Status: 200
   Content-Length: 19
-  Content-Type: text/plain; charset=utf-8
+  Content-Type: text/plain; charset=utf-8 (git-server !)
+  Content-Type: application/octet-stream (hg-server !)
   Date: $HTTP_DATE$
+  Server: testing stub value (hg-server !)
   lfs: adding d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 to the usercache
   lfs: processed: d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998
    b: remote created -> g
@@ -341,45 +395,61 @@
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 Test a corrupt file download, but clear the cache first to force a download.
+`hg serve` indicates a corrupt file without transferring it, unlike
+lfs-test-server.
 
   $ rm -rf `hg config lfs.usercache`
+#if git-server
   $ cp $TESTTMP/lfs-content/d1/1e/1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 blob
   $ echo 'damage' > $TESTTMP/lfs-content/d1/1e/1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998
+#else
+  $ cp $TESTTMP/server/.hg/store/lfs/objects/d1/1e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 blob
+  $ echo 'damage' > $TESTTMP/server/.hg/store/lfs/objects/d1/1e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998
+#endif
   $ rm ../repo1/.hg/store/lfs/objects/d1/1e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998
   $ rm ../repo1/*
 
+TODO: give the proper error indication from `hg serve`
+
   $ hg --repo ../repo1 update -C tip --debug
   http auth: user foo, password ***
   resolving manifests
    branchmerge: False, force: True, partial: False
    ancestor: dfca2c9e2ef2+, local: dfca2c9e2ef2+, remote: dfca2c9e2ef2
   Status: 200
-  Content-Length: 311
+  Content-Length: 311 (git-server !)
+  Content-Length: 183 (hg-server !)
   Content-Type: application/vnd.git-lfs+json
   Date: $HTTP_DATE$
+  Server: testing stub value (hg-server !)
   {
     "objects": [
       {
-        "actions": {
-          "download": {
-            "expires_at": "$ISO_8601_DATE_TIME$"
-            "header": {
-              "Accept": "application/vnd.git-lfs"
-            }
-            "href": "http://localhost:$HGPORT/objects/d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998"
-          }
+        "actions": { (git-server !)
+          "download": { (git-server !)
+            "expires_at": "$ISO_8601_DATE_TIME$" (git-server !)
+            "header": { (git-server !)
+              "Accept": "application/vnd.git-lfs" (git-server !)
+            } (git-server !)
+            "href": "http://localhost:$HGPORT/objects/d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998" (git-server !)
+          } (git-server !)
+        "error": { (hg-server !)
+          "code": 422 (hg-server !)
+          "message": "The object is corrupt" (hg-server !)
         }
         "oid": "d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998"
         "size": 19
       }
     ]
+    "transfer": "basic" (hg-server !)
   }
-  lfs: downloading d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 (19 bytes)
-  Status: 200
-  Content-Length: 7
-  Content-Type: text/plain; charset=utf-8
-  Date: $HTTP_DATE$
-  abort: corrupt remote lfs object: d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998
+  lfs: downloading d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 (19 bytes) (git-server !)
+  Status: 200 (git-server !)
+  Content-Length: 7 (git-server !)
+  Content-Type: text/plain; charset=utf-8 (git-server !)
+  Date: $HTTP_DATE$ (git-server !)
+  abort: corrupt remote lfs object: d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 (git-server !)
+  abort: LFS server error. Remote object for "c" not found: *! (glob) (hg-server !)
   [255]
 
 The corrupted blob is not added to the usercache or local store
@@ -388,7 +458,11 @@
   [1]
   $ test -f `hg config lfs.usercache`/d1/1e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998
   [1]
+#if git-server
   $ cp blob $TESTTMP/lfs-content/d1/1e/1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998
+#else
+  $ cp blob $TESTTMP/server/.hg/store/lfs/objects/d1/1e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998
+#endif
 
 Test a corrupted file upload
 
@@ -408,9 +482,11 @@
   listing keys for "bookmarks"
   lfs: computing set of blobs to upload
   Status: 200
-  Content-Length: 309
+  Content-Length: 309 (git-server !)
+  Content-Length: 350 (hg-server !)
   Content-Type: application/vnd.git-lfs+json
   Date: $HTTP_DATE$
+  Server: testing stub value (hg-server !)
   {
     "objects": [
       {
@@ -420,13 +496,14 @@
             "header": {
               "Accept": "application/vnd.git-lfs"
             }
-            "href": "http://localhost:$HGPORT/objects/e659058e26b07b39d2a9c7145b3f99b41f797b6621c8076600e9cb7ee88291f0"
+            "href": "http://localhost:$HGPORT/*/e659058e26b07b39d2a9c7145b3f99b41f797b6621c8076600e9cb7ee88291f0" (glob)
           }
         }
         "oid": "e659058e26b07b39d2a9c7145b3f99b41f797b6621c8076600e9cb7ee88291f0"
         "size": 17
       }
     ]
+    "transfer": "basic" (hg-server !)
   }
   lfs: uploading e659058e26b07b39d2a9c7145b3f99b41f797b6621c8076600e9cb7ee88291f0 (17 bytes)
   abort: detected corrupt lfs object: e659058e26b07b39d2a9c7145b3f99b41f797b6621c8076600e9cb7ee88291f0
@@ -439,9 +516,11 @@
   $ hg archive --debug -r 1 ../archive
   http auth: user foo, password ***
   Status: 200
-  Content-Length: 905
+  Content-Length: 905 (git-server !)
+  Content-Length: 988 (hg-server !)
   Content-Type: application/vnd.git-lfs+json
   Date: $HTTP_DATE$
+  Server: testing stub value (hg-server !)
   {
     "objects": [
       {
@@ -451,7 +530,7 @@
             "header": {
               "Accept": "application/vnd.git-lfs"
             }
-            "href": "http://localhost:$HGPORT/objects/31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b"
+            "href": "http://localhost:$HGPORT/*/31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b" (glob)
           }
         }
         "oid": "31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b"
@@ -464,7 +543,7 @@
             "header": {
               "Accept": "application/vnd.git-lfs"
             }
-            "href": "http://localhost:$HGPORT/objects/37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19"
+            "href": "http://localhost:$HGPORT/*/37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19" (glob)
           }
         }
         "oid": "37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19"
@@ -477,34 +556,41 @@
             "header": {
               "Accept": "application/vnd.git-lfs"
             }
-            "href": "http://localhost:$HGPORT/objects/d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998"
+            "href": "http://localhost:$HGPORT/*/d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998" (glob)
           }
         }
         "oid": "d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998"
         "size": 19
       }
     ]
+    "transfer": "basic" (hg-server !)
   }
   lfs: need to transfer 3 objects (51 bytes)
   lfs: downloading 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b (12 bytes)
   Status: 200
   Content-Length: 12
-  Content-Type: text/plain; charset=utf-8
+  Content-Type: text/plain; charset=utf-8 (git-server !)
+  Content-Type: application/octet-stream (hg-server !)
   Date: $HTTP_DATE$
+  Server: testing stub value (hg-server !)
   lfs: adding 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b to the usercache
   lfs: processed: 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b
   lfs: downloading 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 (20 bytes)
   Status: 200
   Content-Length: 20
-  Content-Type: text/plain; charset=utf-8
+  Content-Type: text/plain; charset=utf-8 (git-server !)
+  Content-Type: application/octet-stream (hg-server !)
   Date: $HTTP_DATE$
+  Server: testing stub value (hg-server !)
   lfs: adding 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 to the usercache
   lfs: processed: 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19
   lfs: downloading d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 (19 bytes)
   Status: 200
   Content-Length: 19
-  Content-Type: text/plain; charset=utf-8
+  Content-Type: text/plain; charset=utf-8 (git-server !)
+  Content-Type: application/octet-stream (hg-server !)
   Date: $HTTP_DATE$
+  Server: testing stub value (hg-server !)
   lfs: adding d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 to the usercache
   lfs: processed: d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998
   lfs: found 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b in the local lfs store
@@ -525,9 +611,11 @@
   $ hg cat --debug -r 1 a b c
   http auth: user foo, password ***
   Status: 200
-  Content-Length: 608
+  Content-Length: 608 (git-server !)
+  Content-Length: 670 (hg-server !)
   Content-Type: application/vnd.git-lfs+json
   Date: $HTTP_DATE$
+  Server: testing stub value (hg-server !)
   {
     "objects": [
       {
@@ -537,7 +625,7 @@
             "header": {
               "Accept": "application/vnd.git-lfs"
             }
-            "href": "http://localhost:$HGPORT/objects/31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b"
+            "href": "http://localhost:$HGPORT/*/31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b" (glob)
           }
         }
         "oid": "31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b"
@@ -550,27 +638,32 @@
             "header": {
               "Accept": "application/vnd.git-lfs"
             }
-            "href": "http://localhost:$HGPORT/objects/d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998"
+            "href": "http://localhost:$HGPORT/*/d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998" (glob)
           }
         }
         "oid": "d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998"
         "size": 19
       }
     ]
+    "transfer": "basic" (hg-server !)
   }
   lfs: need to transfer 2 objects (31 bytes)
   lfs: downloading 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b (12 bytes)
   Status: 200
   Content-Length: 12
-  Content-Type: text/plain; charset=utf-8
+  Content-Type: text/plain; charset=utf-8 (git-server !)
+  Content-Type: application/octet-stream (hg-server !)
   Date: $HTTP_DATE$
+  Server: testing stub value (hg-server !)
   lfs: adding 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b to the usercache
   lfs: processed: 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b
   lfs: downloading d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 (19 bytes)
   Status: 200
   Content-Length: 19
-  Content-Type: text/plain; charset=utf-8
+  Content-Type: text/plain; charset=utf-8 (git-server !)
+  Content-Type: application/octet-stream (hg-server !)
   Date: $HTTP_DATE$
+  Server: testing stub value (hg-server !)
   lfs: adding d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 to the usercache
   lfs: processed: d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998
   lfs: found 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b in the local lfs store
@@ -592,9 +685,11 @@
   reverting c
   reverting d
   Status: 200
-  Content-Length: 905
+  Content-Length: 905 (git-server !)
+  Content-Length: 988 (hg-server !)
   Content-Type: application/vnd.git-lfs+json
   Date: $HTTP_DATE$
+  Server: testing stub value (hg-server !)
   {
     "objects": [
       {
@@ -604,7 +699,7 @@
             "header": {
               "Accept": "application/vnd.git-lfs"
             }
-            "href": "http://localhost:$HGPORT/objects/31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b"
+            "href": "http://localhost:$HGPORT/*/31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b" (glob)
           }
         }
         "oid": "31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b"
@@ -617,7 +712,7 @@
             "header": {
               "Accept": "application/vnd.git-lfs"
             }
-            "href": "http://localhost:$HGPORT/objects/37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19"
+            "href": "http://localhost:$HGPORT/*/37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19" (glob)
           }
         }
         "oid": "37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19"
@@ -630,34 +725,41 @@
             "header": {
               "Accept": "application/vnd.git-lfs"
             }
-            "href": "http://localhost:$HGPORT/objects/d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998"
+            "href": "http://localhost:$HGPORT/*/d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998" (glob)
           }
         }
         "oid": "d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998"
         "size": 19
       }
     ]
+    "transfer": "basic" (hg-server !)
   }
   lfs: need to transfer 3 objects (51 bytes)
   lfs: downloading 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b (12 bytes)
   Status: 200
   Content-Length: 12
-  Content-Type: text/plain; charset=utf-8
+  Content-Type: text/plain; charset=utf-8 (git-server !)
+  Content-Type: application/octet-stream (hg-server !)
   Date: $HTTP_DATE$
+  Server: testing stub value (hg-server !)
   lfs: adding 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b to the usercache
   lfs: processed: 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b
   lfs: downloading 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 (20 bytes)
   Status: 200
   Content-Length: 20
-  Content-Type: text/plain; charset=utf-8
+  Content-Type: text/plain; charset=utf-8 (git-server !)
+  Content-Type: application/octet-stream (hg-server !)
   Date: $HTTP_DATE$
+  Server: testing stub value (hg-server !)
   lfs: adding 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 to the usercache
   lfs: processed: 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19
   lfs: downloading d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 (19 bytes)
   Status: 200
   Content-Length: 19
-  Content-Type: text/plain; charset=utf-8
+  Content-Type: text/plain; charset=utf-8 (git-server !)
+  Content-Type: application/octet-stream (hg-server !)
   Date: $HTTP_DATE$
+  Server: testing stub value (hg-server !)
   lfs: adding d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 to the usercache
   lfs: processed: d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998
   lfs: found 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b in the local lfs store
@@ -679,25 +781,31 @@
    branchmerge: False, force: True, partial: False
    ancestor: 62fdbaf221c6+, local: 62fdbaf221c6+, remote: ef0564edf47e
   Status: 200
-  Content-Length: 308
+  Content-Length: 308 (git-server !)
+  Content-Length: 186 (hg-server !)
   Content-Type: application/vnd.git-lfs+json
   Date: $HTTP_DATE$
+  Server: testing stub value (hg-server !)
   {
     "objects": [
       {
-        "actions": {
-          "upload": {
-            "expires_at": "$ISO_8601_DATE_TIME$"
-            "header": {
-              "Accept": "application/vnd.git-lfs"
-            }
-            "href": "http://localhost:$HGPORT/objects/8e6ea5f6c066b44a0efa43bcce86aea73f17e6e23f0663df0251e7524e140a13"
-          }
+        "actions": { (git-server !)
+          "upload": { (git-server !)
+            "expires_at": "$ISO_8601_DATE_TIME$" (git-server !)
+            "header": { (git-server !)
+              "Accept": "application/vnd.git-lfs" (git-server !)
+            } (git-server !)
+            "href": "http://localhost:$HGPORT/objects/8e6ea5f6c066b44a0efa43bcce86aea73f17e6e23f0663df0251e7524e140a13" (git-server !)
+          } (git-server !)
+        "error": { (hg-server !)
+          "code": 404 (hg-server !)
+          "message": "The object does not exist" (hg-server !)
         }
         "oid": "8e6ea5f6c066b44a0efa43bcce86aea73f17e6e23f0663df0251e7524e140a13"
         "size": 6
       }
     ]
+    "transfer": "basic" (hg-server !)
   }
   abort: LFS server error. Remote object for "b" not found:(.*)! (re)
   [255]
@@ -726,16 +834,54 @@
 (Restart the server in a different location so it no longer has the content)
 
   $ $PYTHON $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
+
+#if hg-server
+  $ cat $TESTTMP/access.log $TESTTMP/errors.log
+  $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
+  $LOCALIP - - [$LOGDATE$] "PUT /.hg/lfs/objects/31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b HTTP/1.1" 201 - (glob)
+  $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /.hg/lfs/objects/31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b HTTP/1.1" 200 - (glob)
+  $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
+  $LOCALIP - - [$LOGDATE$] "PUT /.hg/lfs/objects/37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 HTTP/1.1" 201 - (glob)
+  $LOCALIP - - [$LOGDATE$] "PUT /.hg/lfs/objects/d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 HTTP/1.1" 201 - (glob)
+  $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /.hg/lfs/objects/37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 HTTP/1.1" 200 - (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /.hg/lfs/objects/d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 HTTP/1.1" 200 - (glob)
+  $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
+  $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
+  $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /.hg/lfs/objects/31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b HTTP/1.1" 200 - (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /.hg/lfs/objects/37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 HTTP/1.1" 200 - (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /.hg/lfs/objects/d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 HTTP/1.1" 200 - (glob)
+  $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /.hg/lfs/objects/31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b HTTP/1.1" 200 - (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /.hg/lfs/objects/d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 HTTP/1.1" 200 - (glob)
+  $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /.hg/lfs/objects/31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b HTTP/1.1" 200 - (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /.hg/lfs/objects/37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 HTTP/1.1" 200 - (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /.hg/lfs/objects/d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 HTTP/1.1" 200 - (glob)
+  $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
+#endif
+
   $ rm $DAEMON_PIDS
   $ mkdir $TESTTMP/lfs-server2
   $ cd $TESTTMP/lfs-server2
-#if no-windows
+#if no-windows git-server
   $ lfs-test-server &> lfs-server.log &
   $ echo $! >> $DAEMON_PIDS
-#else
+#endif
+
+#if windows git-server
   $ $PYTHON $TESTTMP/spawn.py >> $DAEMON_PIDS
 #endif
 
+#if hg-server
+  $ hg init server2
+  $ hg --config "lfs.usercache=$TESTTMP/servercache2" -R server2 serve -d \
+  >    -p $HGPORT --pid-file=hg.pid -A $TESTTMP/access.log -E $TESTTMP/errors.log
+  $ cat hg.pid >> $DAEMON_PIDS
+#endif
+
   $ cd $TESTTMP
   $ hg --debug clone test test2
   http auth: user foo, password ***
@@ -746,25 +892,31 @@
    branchmerge: False, force: False, partial: False
    ancestor: 000000000000, local: 000000000000+, remote: d2a338f184a8
   Status: 200
-  Content-Length: 308
+  Content-Length: 308 (git-server !)
+  Content-Length: 186 (hg-server !)
   Content-Type: application/vnd.git-lfs+json
   Date: $HTTP_DATE$
+  Server: testing stub value (hg-server !)
   {
     "objects": [
       {
-        "actions": {
-          "upload": {
-            "expires_at": "$ISO_8601_DATE_TIME$"
-            "header": {
-              "Accept": "application/vnd.git-lfs"
-            }
-            "href": "http://localhost:$HGPORT/objects/bdc26931acfb734b142a8d675f205becf27560dc461f501822de13274fe6fc8a"
-          }
+        "actions": { (git-server !)
+          "upload": { (git-server !)
+            "expires_at": "$ISO_8601_DATE_TIME$" (git-server !)
+            "header": { (git-server !)
+              "Accept": "application/vnd.git-lfs" (git-server !)
+            } (git-server !)
+            "href": "http://localhost:$HGPORT/objects/bdc26931acfb734b142a8d675f205becf27560dc461f501822de13274fe6fc8a" (git-server !)
+          } (git-server !)
+        "error": { (hg-server !)
+          "code": 404 (hg-server !)
+          "message": "The object does not exist" (hg-server !)
         }
         "oid": "bdc26931acfb734b142a8d675f205becf27560dc461f501822de13274fe6fc8a"
         "size": 6
       }
     ]
+    "transfer": "basic" (hg-server !)
   }
   abort: LFS server error. Remote object for "a" not found:(.*)! (re)
   [255]