##// END OF EJS Templates
lfs: teach the blob server to handle --prefix
Matt Harbison -
r37635:b03f2e0f default
parent child Browse files
Show More
@@ -1,291 +1,291 b''
1 # wireprotolfsserver.py - lfs protocol server side implementation
1 # wireprotolfsserver.py - lfs protocol server side implementation
2 #
2 #
3 # Copyright 2018 Matt Harbison <matt_harbison@yahoo.com>
3 # Copyright 2018 Matt Harbison <matt_harbison@yahoo.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import datetime
10 import datetime
11 import errno
11 import errno
12 import json
12 import json
13
13
14 from mercurial.hgweb import (
14 from mercurial.hgweb import (
15 common as hgwebcommon,
15 common as hgwebcommon,
16 )
16 )
17
17
18 from mercurial import (
18 from mercurial import (
19 pycompat,
19 pycompat,
20 )
20 )
21
21
22 HTTP_OK = hgwebcommon.HTTP_OK
22 HTTP_OK = hgwebcommon.HTTP_OK
23 HTTP_CREATED = hgwebcommon.HTTP_CREATED
23 HTTP_CREATED = hgwebcommon.HTTP_CREATED
24 HTTP_BAD_REQUEST = hgwebcommon.HTTP_BAD_REQUEST
24 HTTP_BAD_REQUEST = hgwebcommon.HTTP_BAD_REQUEST
25 HTTP_NOT_FOUND = hgwebcommon.HTTP_NOT_FOUND
25 HTTP_NOT_FOUND = hgwebcommon.HTTP_NOT_FOUND
26
26
27 def handlewsgirequest(orig, rctx, req, res, checkperm):
27 def handlewsgirequest(orig, rctx, req, res, checkperm):
28 """Wrap wireprotoserver.handlewsgirequest() to possibly process an LFS
28 """Wrap wireprotoserver.handlewsgirequest() to possibly process an LFS
29 request if it is left unprocessed by the wrapped method.
29 request if it is left unprocessed by the wrapped method.
30 """
30 """
31 if orig(rctx, req, res, checkperm):
31 if orig(rctx, req, res, checkperm):
32 return True
32 return True
33
33
34 if not rctx.repo.ui.configbool('experimental', 'lfs.serve'):
34 if not rctx.repo.ui.configbool('experimental', 'lfs.serve'):
35 return False
35 return False
36
36
37 if not req.dispatchpath:
37 if not req.dispatchpath:
38 return False
38 return False
39
39
40 try:
40 try:
41 if req.dispatchpath == b'.git/info/lfs/objects/batch':
41 if req.dispatchpath == b'.git/info/lfs/objects/batch':
42 checkperm(rctx, req, 'pull')
42 checkperm(rctx, req, 'pull')
43 return _processbatchrequest(rctx.repo, req, res)
43 return _processbatchrequest(rctx.repo, req, res)
44 # TODO: reserve and use a path in the proposed http wireprotocol /api/
44 # TODO: reserve and use a path in the proposed http wireprotocol /api/
45 # namespace?
45 # namespace?
46 elif req.dispatchpath.startswith(b'.hg/lfs/objects'):
46 elif req.dispatchpath.startswith(b'.hg/lfs/objects'):
47 return _processbasictransfer(rctx.repo, req, res,
47 return _processbasictransfer(rctx.repo, req, res,
48 lambda perm:
48 lambda perm:
49 checkperm(rctx, req, perm))
49 checkperm(rctx, req, perm))
50 return False
50 return False
51 except hgwebcommon.ErrorResponse as e:
51 except hgwebcommon.ErrorResponse as e:
52 # XXX: copied from the handler surrounding wireprotoserver._callhttp()
52 # XXX: copied from the handler surrounding wireprotoserver._callhttp()
53 # in the wrapped function. Should this be moved back to hgweb to
53 # in the wrapped function. Should this be moved back to hgweb to
54 # be a common handler?
54 # be a common handler?
55 for k, v in e.headers:
55 for k, v in e.headers:
56 res.headers[k] = v
56 res.headers[k] = v
57 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
57 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
58 res.setbodybytes(b'0\n%s\n' % pycompat.bytestr(e))
58 res.setbodybytes(b'0\n%s\n' % pycompat.bytestr(e))
59 return True
59 return True
60
60
61 def _sethttperror(res, code, message=None):
61 def _sethttperror(res, code, message=None):
62 res.status = hgwebcommon.statusmessage(code, message=message)
62 res.status = hgwebcommon.statusmessage(code, message=message)
63 res.headers[b'Content-Type'] = b'text/plain; charset=utf-8'
63 res.headers[b'Content-Type'] = b'text/plain; charset=utf-8'
64 res.setbodybytes(b'')
64 res.setbodybytes(b'')
65
65
66 def _processbatchrequest(repo, req, res):
66 def _processbatchrequest(repo, req, res):
67 """Handle a request for the Batch API, which is the gateway to granting file
67 """Handle a request for the Batch API, which is the gateway to granting file
68 access.
68 access.
69
69
70 https://github.com/git-lfs/git-lfs/blob/master/docs/api/batch.md
70 https://github.com/git-lfs/git-lfs/blob/master/docs/api/batch.md
71 """
71 """
72
72
73 # Mercurial client request:
73 # Mercurial client request:
74 #
74 #
75 # HOST: localhost:$HGPORT
75 # HOST: localhost:$HGPORT
76 # ACCEPT: application/vnd.git-lfs+json
76 # ACCEPT: application/vnd.git-lfs+json
77 # ACCEPT-ENCODING: identity
77 # ACCEPT-ENCODING: identity
78 # USER-AGENT: git-lfs/2.3.4 (Mercurial 4.5.2+1114-f48b9754f04c+20180316)
78 # USER-AGENT: git-lfs/2.3.4 (Mercurial 4.5.2+1114-f48b9754f04c+20180316)
79 # Content-Length: 125
79 # Content-Length: 125
80 # Content-Type: application/vnd.git-lfs+json
80 # Content-Type: application/vnd.git-lfs+json
81 #
81 #
82 # {
82 # {
83 # "objects": [
83 # "objects": [
84 # {
84 # {
85 # "oid": "31cf...8e5b"
85 # "oid": "31cf...8e5b"
86 # "size": 12
86 # "size": 12
87 # }
87 # }
88 # ]
88 # ]
89 # "operation": "upload"
89 # "operation": "upload"
90 # }
90 # }
91
91
92 if (req.method != b'POST'
92 if (req.method != b'POST'
93 or req.headers[b'Content-Type'] != b'application/vnd.git-lfs+json'
93 or req.headers[b'Content-Type'] != b'application/vnd.git-lfs+json'
94 or req.headers[b'Accept'] != b'application/vnd.git-lfs+json'):
94 or req.headers[b'Accept'] != b'application/vnd.git-lfs+json'):
95 # TODO: figure out what the proper handling for a bad request to the
95 # TODO: figure out what the proper handling for a bad request to the
96 # Batch API is.
96 # Batch API is.
97 _sethttperror(res, HTTP_BAD_REQUEST, b'Invalid Batch API request')
97 _sethttperror(res, HTTP_BAD_REQUEST, b'Invalid Batch API request')
98 return True
98 return True
99
99
100 # XXX: specify an encoding?
100 # XXX: specify an encoding?
101 lfsreq = json.loads(req.bodyfh.read())
101 lfsreq = json.loads(req.bodyfh.read())
102
102
103 # If no transfer handlers are explicitly requested, 'basic' is assumed.
103 # If no transfer handlers are explicitly requested, 'basic' is assumed.
104 if 'basic' not in lfsreq.get('transfers', ['basic']):
104 if 'basic' not in lfsreq.get('transfers', ['basic']):
105 _sethttperror(res, HTTP_BAD_REQUEST,
105 _sethttperror(res, HTTP_BAD_REQUEST,
106 b'Only the basic LFS transfer handler is supported')
106 b'Only the basic LFS transfer handler is supported')
107 return True
107 return True
108
108
109 operation = lfsreq.get('operation')
109 operation = lfsreq.get('operation')
110 if operation not in ('upload', 'download'):
110 if operation not in ('upload', 'download'):
111 _sethttperror(res, HTTP_BAD_REQUEST,
111 _sethttperror(res, HTTP_BAD_REQUEST,
112 b'Unsupported LFS transfer operation: %s' % operation)
112 b'Unsupported LFS transfer operation: %s' % operation)
113 return True
113 return True
114
114
115 localstore = repo.svfs.lfslocalblobstore
115 localstore = repo.svfs.lfslocalblobstore
116
116
117 objects = [p for p in _batchresponseobjects(req, lfsreq.get('objects', []),
117 objects = [p for p in _batchresponseobjects(req, lfsreq.get('objects', []),
118 operation, localstore)]
118 operation, localstore)]
119
119
120 rsp = {
120 rsp = {
121 'transfer': 'basic',
121 'transfer': 'basic',
122 'objects': objects,
122 'objects': objects,
123 }
123 }
124
124
125 res.status = hgwebcommon.statusmessage(HTTP_OK)
125 res.status = hgwebcommon.statusmessage(HTTP_OK)
126 res.headers[b'Content-Type'] = b'application/vnd.git-lfs+json'
126 res.headers[b'Content-Type'] = b'application/vnd.git-lfs+json'
127 res.setbodybytes(pycompat.bytestr(json.dumps(rsp)))
127 res.setbodybytes(pycompat.bytestr(json.dumps(rsp)))
128
128
129 return True
129 return True
130
130
131 def _batchresponseobjects(req, objects, action, store):
131 def _batchresponseobjects(req, objects, action, store):
132 """Yield one dictionary of attributes for the Batch API response for each
132 """Yield one dictionary of attributes for the Batch API response for each
133 object in the list.
133 object in the list.
134
134
135 req: The parsedrequest for the Batch API request
135 req: The parsedrequest for the Batch API request
136 objects: The list of objects in the Batch API object request list
136 objects: The list of objects in the Batch API object request list
137 action: 'upload' or 'download'
137 action: 'upload' or 'download'
138 store: The local blob store for servicing requests"""
138 store: The local blob store for servicing requests"""
139
139
140 # Successful lfs-test-server response to solict an upload:
140 # Successful lfs-test-server response to solict an upload:
141 # {
141 # {
142 # u'objects': [{
142 # u'objects': [{
143 # u'size': 12,
143 # u'size': 12,
144 # u'oid': u'31cf...8e5b',
144 # u'oid': u'31cf...8e5b',
145 # u'actions': {
145 # u'actions': {
146 # u'upload': {
146 # u'upload': {
147 # u'href': u'http://localhost:$HGPORT/objects/31cf...8e5b',
147 # u'href': u'http://localhost:$HGPORT/objects/31cf...8e5b',
148 # u'expires_at': u'0001-01-01T00:00:00Z',
148 # u'expires_at': u'0001-01-01T00:00:00Z',
149 # u'header': {
149 # u'header': {
150 # u'Accept': u'application/vnd.git-lfs'
150 # u'Accept': u'application/vnd.git-lfs'
151 # }
151 # }
152 # }
152 # }
153 # }
153 # }
154 # }]
154 # }]
155 # }
155 # }
156
156
157 # TODO: Sort out the expires_at/expires_in/authenticated keys.
157 # TODO: Sort out the expires_at/expires_in/authenticated keys.
158
158
159 for obj in objects:
159 for obj in objects:
160 # Convert unicode to ASCII to create a filesystem path
160 # Convert unicode to ASCII to create a filesystem path
161 oid = obj.get('oid').encode('ascii')
161 oid = obj.get('oid').encode('ascii')
162 rsp = {
162 rsp = {
163 'oid': oid,
163 'oid': oid,
164 'size': obj.get('size'), # XXX: should this check the local size?
164 'size': obj.get('size'), # XXX: should this check the local size?
165 #'authenticated': True,
165 #'authenticated': True,
166 }
166 }
167
167
168 exists = True
168 exists = True
169 verifies = False
169 verifies = False
170
170
171 # Verify an existing file on the upload request, so that the client is
171 # Verify an existing file on the upload request, so that the client is
172 # solicited to re-upload if it corrupt locally. Download requests are
172 # solicited to re-upload if it corrupt locally. Download requests are
173 # also verified, so the error can be flagged in the Batch API response.
173 # also verified, so the error can be flagged in the Batch API response.
174 # (Maybe we can use this to short circuit the download for `hg verify`,
174 # (Maybe we can use this to short circuit the download for `hg verify`,
175 # IFF the client can assert that the remote end is an hg server.)
175 # IFF the client can assert that the remote end is an hg server.)
176 # Otherwise, it's potentially overkill on download, since it is also
176 # Otherwise, it's potentially overkill on download, since it is also
177 # verified as the file is streamed to the caller.
177 # verified as the file is streamed to the caller.
178 try:
178 try:
179 verifies = store.verify(oid)
179 verifies = store.verify(oid)
180 except IOError as inst:
180 except IOError as inst:
181 if inst.errno != errno.ENOENT:
181 if inst.errno != errno.ENOENT:
182 rsp['error'] = {
182 rsp['error'] = {
183 'code': 500,
183 'code': 500,
184 'message': inst.strerror or 'Internal Server Server'
184 'message': inst.strerror or 'Internal Server Server'
185 }
185 }
186 yield rsp
186 yield rsp
187 continue
187 continue
188
188
189 exists = False
189 exists = False
190
190
191 # Items are always listed for downloads. They are dropped for uploads
191 # Items are always listed for downloads. They are dropped for uploads
192 # IFF they already exist locally.
192 # IFF they already exist locally.
193 if action == 'download':
193 if action == 'download':
194 if not exists:
194 if not exists:
195 rsp['error'] = {
195 rsp['error'] = {
196 'code': 404,
196 'code': 404,
197 'message': "The object does not exist"
197 'message': "The object does not exist"
198 }
198 }
199 yield rsp
199 yield rsp
200 continue
200 continue
201
201
202 elif not verifies:
202 elif not verifies:
203 rsp['error'] = {
203 rsp['error'] = {
204 'code': 422, # XXX: is this the right code?
204 'code': 422, # XXX: is this the right code?
205 'message': "The object is corrupt"
205 'message': "The object is corrupt"
206 }
206 }
207 yield rsp
207 yield rsp
208 continue
208 continue
209
209
210 elif verifies:
210 elif verifies:
211 yield rsp # Skip 'actions': already uploaded
211 yield rsp # Skip 'actions': already uploaded
212 continue
212 continue
213
213
214 expiresat = datetime.datetime.now() + datetime.timedelta(minutes=10)
214 expiresat = datetime.datetime.now() + datetime.timedelta(minutes=10)
215
215
216 rsp['actions'] = {
216 rsp['actions'] = {
217 '%s' % action: {
217 '%s' % action: {
218 # TODO: Account for the --prefix, if any.
218 'href': '%s%s/.hg/lfs/objects/%s'
219 'href': '%s/.hg/lfs/objects/%s' % (req.baseurl, oid),
219 % (req.baseurl, req.apppath, oid),
220 # datetime.isoformat() doesn't include the 'Z' suffix
220 # datetime.isoformat() doesn't include the 'Z' suffix
221 "expires_at": expiresat.strftime('%Y-%m-%dT%H:%M:%SZ'),
221 "expires_at": expiresat.strftime('%Y-%m-%dT%H:%M:%SZ'),
222 'header': {
222 'header': {
223 # The spec doesn't mention the Accept header here, but avoid
223 # The spec doesn't mention the Accept header here, but avoid
224 # a gratuitous deviation from lfs-test-server in the test
224 # a gratuitous deviation from lfs-test-server in the test
225 # output.
225 # output.
226 'Accept': 'application/vnd.git-lfs'
226 'Accept': 'application/vnd.git-lfs'
227 }
227 }
228 }
228 }
229 }
229 }
230
230
231 yield rsp
231 yield rsp
232
232
233 def _processbasictransfer(repo, req, res, checkperm):
233 def _processbasictransfer(repo, req, res, checkperm):
234 """Handle a single file upload (PUT) or download (GET) action for the Basic
234 """Handle a single file upload (PUT) or download (GET) action for the Basic
235 Transfer Adapter.
235 Transfer Adapter.
236
236
237 After determining if the request is for an upload or download, the access
237 After determining if the request is for an upload or download, the access
238 must be checked by calling ``checkperm()`` with either 'pull' or 'upload'
238 must be checked by calling ``checkperm()`` with either 'pull' or 'upload'
239 before accessing the files.
239 before accessing the files.
240
240
241 https://github.com/git-lfs/git-lfs/blob/master/docs/api/basic-transfers.md
241 https://github.com/git-lfs/git-lfs/blob/master/docs/api/basic-transfers.md
242 """
242 """
243
243
244 method = req.method
244 method = req.method
245 oid = req.dispatchparts[-1]
245 oid = req.dispatchparts[-1]
246 localstore = repo.svfs.lfslocalblobstore
246 localstore = repo.svfs.lfslocalblobstore
247
247
248 if len(req.dispatchparts) != 4:
248 if len(req.dispatchparts) != 4:
249 _sethttperror(res, HTTP_NOT_FOUND)
249 _sethttperror(res, HTTP_NOT_FOUND)
250 return True
250 return True
251
251
252 if method == b'PUT':
252 if method == b'PUT':
253 checkperm('upload')
253 checkperm('upload')
254
254
255 # TODO: verify Content-Type?
255 # TODO: verify Content-Type?
256
256
257 existed = localstore.has(oid)
257 existed = localstore.has(oid)
258
258
259 # TODO: how to handle timeouts? The body proxy handles limiting to
259 # TODO: how to handle timeouts? The body proxy handles limiting to
260 # Content-Length, but what happens if a client sends less than it
260 # Content-Length, but what happens if a client sends less than it
261 # says it will?
261 # says it will?
262
262
263 # TODO: download() will abort if the checksum fails. It should raise
263 # TODO: download() will abort if the checksum fails. It should raise
264 # something checksum specific that can be caught here, and turned
264 # something checksum specific that can be caught here, and turned
265 # into an http code.
265 # into an http code.
266 localstore.download(oid, req.bodyfh)
266 localstore.download(oid, req.bodyfh)
267
267
268 statusmessage = hgwebcommon.statusmessage
268 statusmessage = hgwebcommon.statusmessage
269 res.status = statusmessage(HTTP_OK if existed else HTTP_CREATED)
269 res.status = statusmessage(HTTP_OK if existed else HTTP_CREATED)
270
270
271 # There's no payload here, but this is the header that lfs-test-server
271 # There's no payload here, but this is the header that lfs-test-server
272 # sends back. This eliminates some gratuitous test output conditionals.
272 # sends back. This eliminates some gratuitous test output conditionals.
273 res.headers[b'Content-Type'] = b'text/plain; charset=utf-8'
273 res.headers[b'Content-Type'] = b'text/plain; charset=utf-8'
274 res.setbodybytes(b'')
274 res.setbodybytes(b'')
275
275
276 return True
276 return True
277 elif method == b'GET':
277 elif method == b'GET':
278 checkperm('pull')
278 checkperm('pull')
279
279
280 res.status = hgwebcommon.statusmessage(HTTP_OK)
280 res.status = hgwebcommon.statusmessage(HTTP_OK)
281 res.headers[b'Content-Type'] = b'application/octet-stream'
281 res.headers[b'Content-Type'] = b'application/octet-stream'
282
282
283 # TODO: figure out how to send back the file in chunks, instead of
283 # TODO: figure out how to send back the file in chunks, instead of
284 # reading the whole thing.
284 # reading the whole thing.
285 res.setbodybytes(localstore.read(oid))
285 res.setbodybytes(localstore.read(oid))
286
286
287 return True
287 return True
288 else:
288 else:
289 _sethttperror(res, HTTP_BAD_REQUEST,
289 _sethttperror(res, HTTP_BAD_REQUEST,
290 message=b'Unsupported LFS transfer method: %s' % method)
290 message=b'Unsupported LFS transfer method: %s' % method)
291 return True
291 return True
@@ -1,67 +1,151 b''
1 #require serve no-reposimplestore
1 #require serve no-reposimplestore
2
2
3 $ cat >> $HGRCPATH <<EOF
3 $ cat >> $HGRCPATH <<EOF
4 > [extensions]
4 > [extensions]
5 > lfs=
5 > lfs=
6 > [lfs]
6 > [lfs]
7 > url=http://localhost:$HGPORT/.git/info/lfs
7 > url=http://localhost:$HGPORT/.git/info/lfs
8 > track=all()
8 > track=all()
9 > [web]
9 > [web]
10 > push_ssl = False
10 > push_ssl = False
11 > allow-push = *
11 > allow-push = *
12 > EOF
12 > EOF
13
13
14 Serving LFS files can experimentally be turned off. The long term solution is
14 Serving LFS files can experimentally be turned off. The long term solution is
15 to support the 'verify' action in both client and server, so that the server can
15 to support the 'verify' action in both client and server, so that the server can
16 tell the client to store files elsewhere.
16 tell the client to store files elsewhere.
17
17
18 $ hg init server
18 $ hg init server
19 $ hg --config "lfs.usercache=$TESTTMP/servercache" \
19 $ hg --config "lfs.usercache=$TESTTMP/servercache" \
20 > --config experimental.lfs.serve=False -R server serve -d \
20 > --config experimental.lfs.serve=False -R server serve -d \
21 > -p $HGPORT --pid-file=hg.pid -A $TESTTMP/access.log -E $TESTTMP/errors.log
21 > -p $HGPORT --pid-file=hg.pid -A $TESTTMP/access.log -E $TESTTMP/errors.log
22 $ cat hg.pid >> $DAEMON_PIDS
22 $ cat hg.pid >> $DAEMON_PIDS
23
23
24 Uploads fail...
24 Uploads fail...
25
25
26 $ hg init client
26 $ hg init client
27 $ echo 'this-is-an-lfs-file' > client/lfs.bin
27 $ echo 'this-is-an-lfs-file' > client/lfs.bin
28 $ hg -R client ci -Am 'initial commit'
28 $ hg -R client ci -Am 'initial commit'
29 adding lfs.bin
29 adding lfs.bin
30 $ hg -R client push http://localhost:$HGPORT
30 $ hg -R client push http://localhost:$HGPORT
31 pushing to http://localhost:$HGPORT/
31 pushing to http://localhost:$HGPORT/
32 searching for changes
32 searching for changes
33 abort: LFS HTTP error: HTTP Error 400: no such method: .git (action=upload)!
33 abort: LFS HTTP error: HTTP Error 400: no such method: .git (action=upload)!
34 [255]
34 [255]
35
35
36 ... so do a local push to make the data available. Remove the blob from the
36 ... so do a local push to make the data available. Remove the blob from the
37 default cache, so it attempts to download.
37 default cache, so it attempts to download.
38 $ hg --config "lfs.usercache=$TESTTMP/servercache" \
38 $ hg --config "lfs.usercache=$TESTTMP/servercache" \
39 > --config "lfs.url=null://" \
39 > --config "lfs.url=null://" \
40 > -R client push -q server
40 > -R client push -q server
41 $ rm -rf `hg config lfs.usercache`
41 $ mv `hg config lfs.usercache` $TESTTMP/servercache
42
42
43 Downloads fail...
43 Downloads fail...
44
44
45 $ hg clone http://localhost:$HGPORT httpclone
45 $ hg clone http://localhost:$HGPORT httpclone
46 requesting all changes
46 requesting all changes
47 adding changesets
47 adding changesets
48 adding manifests
48 adding manifests
49 adding file changes
49 adding file changes
50 added 1 changesets with 1 changes to 1 files
50 added 1 changesets with 1 changes to 1 files
51 new changesets 525251863cad
51 new changesets 525251863cad
52 updating to branch default
52 updating to branch default
53 abort: LFS HTTP error: HTTP Error 400: no such method: .git (action=download)!
53 abort: LFS HTTP error: HTTP Error 400: no such method: .git (action=download)!
54 [255]
54 [255]
55
55
56 $ $PYTHON $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
56 $ $PYTHON $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
57
57
58 $ cat $TESTTMP/access.log $TESTTMP/errors.log
58 $ cat $TESTTMP/access.log $TESTTMP/errors.log
59 $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
59 $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
60 $LOCALIP - - [$LOGDATE$] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D525251863cad618e55d483555f3d00a2ca99597e x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
60 $LOCALIP - - [$LOGDATE$] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D525251863cad618e55d483555f3d00a2ca99597e x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
61 $LOCALIP - - [$LOGDATE$] "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
61 $LOCALIP - - [$LOGDATE$] "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
62 $LOCALIP - - [$LOGDATE$] "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
62 $LOCALIP - - [$LOGDATE$] "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
63 $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 400 - (glob)
63 $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 400 - (glob)
64 $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
64 $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
65 $LOCALIP - - [$LOGDATE$] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
65 $LOCALIP - - [$LOGDATE$] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
66 $LOCALIP - - [$LOGDATE$] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%252C03%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps%250Arev-branch-cache%250Astream%253Dv2&cg=1&common=0000000000000000000000000000000000000000&heads=525251863cad618e55d483555f3d00a2ca99597e&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
66 $LOCALIP - - [$LOGDATE$] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%252C03%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps%250Arev-branch-cache%250Astream%253Dv2&cg=1&common=0000000000000000000000000000000000000000&heads=525251863cad618e55d483555f3d00a2ca99597e&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
67 $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 400 - (glob)
67 $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 400 - (glob)
68
69 Blob URIs are correct when --prefix is used
70
71 $ rm -f $TESTTMP/access.log $TESTTMP/errors.log
72 $ hg --config "lfs.usercache=$TESTTMP/servercache" -R server serve -d \
73 > -p $HGPORT --pid-file=hg.pid --prefix=subdir/mount/point \
74 > -A $TESTTMP/access.log -E $TESTTMP/errors.log
75 $ cat hg.pid >> $DAEMON_PIDS
76
77 $ hg --config lfs.url=http://localhost:$HGPORT/subdir/mount/point/.git/info/lfs \
78 > clone --debug http://localhost:$HGPORT/subdir/mount/point cloned2
79 using http://localhost:$HGPORT/subdir/mount/point
80 sending capabilities command
81 query 1; heads
82 sending batch command
83 requesting all changes
84 sending getbundle command
85 bundle2-input-bundle: with-transaction
86 bundle2-input-part: "changegroup" (params: 1 mandatory 1 advisory) supported
87 adding changesets
88 add changeset 525251863cad
89 adding manifests
90 adding file changes
91 adding lfs.bin revisions
92 added 1 changesets with 1 changes to 1 files
93 calling hook pretxnchangegroup.lfs: hgext.lfs.checkrequireslfs
94 bundle2-input-part: total payload size 648
95 bundle2-input-part: "listkeys" (params: 1 mandatory) supported
96 bundle2-input-part: "phase-heads" supported
97 bundle2-input-part: total payload size 24
98 bundle2-input-part: "cache:rev-branch-cache" supported
99 bundle2-input-part: total payload size 39
100 bundle2-input-bundle: 3 parts total
101 checking for updated bookmarks
102 updating the branch cache
103 new changesets 525251863cad
104 updating to branch default
105 resolving manifests
106 branchmerge: False, force: False, partial: False
107 ancestor: 000000000000, local: 000000000000+, remote: 525251863cad
108 Status: 200
109 Content-Length: 371
110 Content-Type: application/vnd.git-lfs+json
111 Date: $HTTP_DATE$
112 Server: testing stub value
113 {
114 "objects": [
115 {
116 "actions": {
117 "download": {
118 "expires_at": "$ISO_8601_DATE_TIME$"
119 "header": {
120 "Accept": "application/vnd.git-lfs"
121 }
122 "href": "http://localhost:$HGPORT/subdir/mount/point/.hg/lfs/objects/f03217a32529a28a42d03b1244fe09b6e0f9fd06d7b966d4d50567be2abe6c0e"
123 }
124 }
125 "oid": "f03217a32529a28a42d03b1244fe09b6e0f9fd06d7b966d4d50567be2abe6c0e"
126 "size": 20
127 }
128 ]
129 "transfer": "basic"
130 }
131 lfs: downloading f03217a32529a28a42d03b1244fe09b6e0f9fd06d7b966d4d50567be2abe6c0e (20 bytes)
132 Status: 200
133 Content-Length: 20
134 Content-Type: application/octet-stream
135 Date: $HTTP_DATE$
136 Server: testing stub value
137 lfs: adding f03217a32529a28a42d03b1244fe09b6e0f9fd06d7b966d4d50567be2abe6c0e to the usercache
138 lfs: processed: f03217a32529a28a42d03b1244fe09b6e0f9fd06d7b966d4d50567be2abe6c0e
139 lfs.bin: remote created -> g
140 getting lfs.bin
141 lfs: found f03217a32529a28a42d03b1244fe09b6e0f9fd06d7b966d4d50567be2abe6c0e in the local lfs store
142 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
143
144 $ $PYTHON $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
145
146 $ cat $TESTTMP/access.log $TESTTMP/errors.log
147 $LOCALIP - - [$LOGDATE$] "GET /subdir/mount/point?cmd=capabilities HTTP/1.1" 200 - (glob)
148 $LOCALIP - - [$LOGDATE$] "GET /subdir/mount/point?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
149 $LOCALIP - - [$LOGDATE$] "GET /subdir/mount/point?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&bundlecaps=HG20%2Cbundle2%3DHG20%250Abookmarks%250Achangegroup%253D01%252C02%252C03%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps%250Arev-branch-cache%250Astream%253Dv2&cg=1&common=0000000000000000000000000000000000000000&heads=525251863cad618e55d483555f3d00a2ca99597e&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
150 $LOCALIP - - [$LOGDATE$] "POST /subdir/mount/point/.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
151 $LOCALIP - - [$LOGDATE$] "GET /subdir/mount/point/.hg/lfs/objects/f03217a32529a28a42d03b1244fe09b6e0f9fd06d7b966d4d50567be2abe6c0e HTTP/1.1" 200 - (glob)
General Comments 0
You need to be logged in to leave comments. Login now