##// END OF EJS Templates
lfs: improve the client message when the server signals an object error...
Matt Harbison -
r37259:67db8484 default
parent child Browse files
Show More
@@ -1,504 +1,515 b''
1 1 # blobstore.py - local and remote (speaking Git-LFS protocol) blob storages
2 2 #
3 3 # Copyright 2017 Facebook, Inc.
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import hashlib
11 11 import json
12 12 import os
13 13 import re
14 14 import socket
15 15
16 16 from mercurial.i18n import _
17 17
18 18 from mercurial import (
19 19 error,
20 20 pathutil,
21 21 pycompat,
22 22 url as urlmod,
23 23 util,
24 24 vfs as vfsmod,
25 25 worker,
26 26 )
27 27
28 28 from ..largefiles import lfutil
29 29
30 30 # 64 bytes for SHA256
31 31 _lfsre = re.compile(br'\A[a-f0-9]{64}\Z')
32 32
33 33 class lfsvfs(vfsmod.vfs):
34 34 def join(self, path):
35 35 """split the path at first two characters, like: XX/XXXXX..."""
36 36 if not _lfsre.match(path):
37 37 raise error.ProgrammingError('unexpected lfs path: %s' % path)
38 38 return super(lfsvfs, self).join(path[0:2], path[2:])
39 39
40 40 def walk(self, path=None, onerror=None):
41 41 """Yield (dirpath, [], oids) tuple for blobs under path
42 42
43 43 Oids only exist in the root of this vfs, so dirpath is always ''.
44 44 """
45 45 root = os.path.normpath(self.base)
46 46 # when dirpath == root, dirpath[prefixlen:] becomes empty
47 47 # because len(dirpath) < prefixlen.
48 48 prefixlen = len(pathutil.normasprefix(root))
49 49 oids = []
50 50
51 51 for dirpath, dirs, files in os.walk(self.reljoin(self.base, path or ''),
52 52 onerror=onerror):
53 53 dirpath = dirpath[prefixlen:]
54 54
55 55 # Silently skip unexpected files and directories
56 56 if len(dirpath) == 2:
57 57 oids.extend([dirpath + f for f in files
58 58 if _lfsre.match(dirpath + f)])
59 59
60 60 yield ('', [], oids)
61 61
62 62 class filewithprogress(object):
63 63 """a file-like object that supports __len__ and read.
64 64
65 65 Useful to provide progress information for how many bytes are read.
66 66 """
67 67
68 68 def __init__(self, fp, callback):
69 69 self._fp = fp
70 70 self._callback = callback # func(readsize)
71 71 fp.seek(0, os.SEEK_END)
72 72 self._len = fp.tell()
73 73 fp.seek(0)
74 74
75 75 def __len__(self):
76 76 return self._len
77 77
78 78 def read(self, size):
79 79 if self._fp is None:
80 80 return b''
81 81 data = self._fp.read(size)
82 82 if data:
83 83 if self._callback:
84 84 self._callback(len(data))
85 85 else:
86 86 self._fp.close()
87 87 self._fp = None
88 88 return data
89 89
90 90 class local(object):
91 91 """Local blobstore for large file contents.
92 92
93 93 This blobstore is used both as a cache and as a staging area for large blobs
94 94 to be uploaded to the remote blobstore.
95 95 """
96 96
97 97 def __init__(self, repo):
98 98 fullpath = repo.svfs.join('lfs/objects')
99 99 self.vfs = lfsvfs(fullpath)
100 100 usercache = lfutil._usercachedir(repo.ui, 'lfs')
101 101 self.cachevfs = lfsvfs(usercache)
102 102 self.ui = repo.ui
103 103
104 104 def open(self, oid):
105 105 """Open a read-only file descriptor to the named blob, in either the
106 106 usercache or the local store."""
107 107 # The usercache is the most likely place to hold the file. Commit will
108 108 # write to both it and the local store, as will anything that downloads
109 109 # the blobs. However, things like clone without an update won't
110 110 # populate the local store. For an init + push of a local clone,
111 111 # the usercache is the only place it _could_ be. If not present, the
112 112 # missing file msg here will indicate the local repo, not the usercache.
113 113 if self.cachevfs.exists(oid):
114 114 return self.cachevfs(oid, 'rb')
115 115
116 116 return self.vfs(oid, 'rb')
117 117
118 118 def download(self, oid, src):
119 119 """Read the blob from the remote source in chunks, verify the content,
120 120 and write to this local blobstore."""
121 121 sha256 = hashlib.sha256()
122 122
123 123 with self.vfs(oid, 'wb', atomictemp=True) as fp:
124 124 for chunk in util.filechunkiter(src, size=1048576):
125 125 fp.write(chunk)
126 126 sha256.update(chunk)
127 127
128 128 realoid = sha256.hexdigest()
129 129 if realoid != oid:
130 130 raise error.Abort(_('corrupt remote lfs object: %s') % oid)
131 131
132 132 # XXX: should we verify the content of the cache, and hardlink back to
133 133 # the local store on success, but truncate, write and link on failure?
134 134 if not self.cachevfs.exists(oid):
135 135 self.ui.note(_('lfs: adding %s to the usercache\n') % oid)
136 136 lfutil.link(self.vfs.join(oid), self.cachevfs.join(oid))
137 137
138 138 def write(self, oid, data):
139 139 """Write blob to local blobstore.
140 140
141 141 This should only be called from the filelog during a commit or similar.
142 142 As such, there is no need to verify the data. Imports from a remote
143 143 store must use ``download()`` instead."""
144 144 with self.vfs(oid, 'wb', atomictemp=True) as fp:
145 145 fp.write(data)
146 146
147 147 # XXX: should we verify the content of the cache, and hardlink back to
148 148 # the local store on success, but truncate, write and link on failure?
149 149 if not self.cachevfs.exists(oid):
150 150 self.ui.note(_('lfs: adding %s to the usercache\n') % oid)
151 151 lfutil.link(self.vfs.join(oid), self.cachevfs.join(oid))
152 152
153 153 def read(self, oid, verify=True):
154 154 """Read blob from local blobstore."""
155 155 if not self.vfs.exists(oid):
156 156 blob = self._read(self.cachevfs, oid, verify)
157 157
158 158 # Even if revlog will verify the content, it needs to be verified
159 159 # now before making the hardlink to avoid propagating corrupt blobs.
160 160 # Don't abort if corruption is detected, because `hg verify` will
161 161 # give more useful info about the corruption- simply don't add the
162 162 # hardlink.
163 163 if verify or hashlib.sha256(blob).hexdigest() == oid:
164 164 self.ui.note(_('lfs: found %s in the usercache\n') % oid)
165 165 lfutil.link(self.cachevfs.join(oid), self.vfs.join(oid))
166 166 else:
167 167 self.ui.note(_('lfs: found %s in the local lfs store\n') % oid)
168 168 blob = self._read(self.vfs, oid, verify)
169 169 return blob
170 170
171 171 def _read(self, vfs, oid, verify):
172 172 """Read blob (after verifying) from the given store"""
173 173 blob = vfs.read(oid)
174 174 if verify:
175 175 _verify(oid, blob)
176 176 return blob
177 177
178 178 def verify(self, oid):
179 179 """Indicate whether or not the hash of the underlying file matches its
180 180 name."""
181 181 sha256 = hashlib.sha256()
182 182
183 183 with self.open(oid) as fp:
184 184 for chunk in util.filechunkiter(fp, size=1048576):
185 185 sha256.update(chunk)
186 186
187 187 return oid == sha256.hexdigest()
188 188
189 189 def has(self, oid):
190 190 """Returns True if the local blobstore contains the requested blob,
191 191 False otherwise."""
192 192 return self.cachevfs.exists(oid) or self.vfs.exists(oid)
193 193
194 194 class _gitlfsremote(object):
195 195
196 196 def __init__(self, repo, url):
197 197 ui = repo.ui
198 198 self.ui = ui
199 199 baseurl, authinfo = url.authinfo()
200 200 self.baseurl = baseurl.rstrip('/')
201 201 useragent = repo.ui.config('experimental', 'lfs.user-agent')
202 202 if not useragent:
203 203 useragent = 'git-lfs/2.3.4 (Mercurial %s)' % util.version()
204 204 self.urlopener = urlmod.opener(ui, authinfo, useragent)
205 205 self.retry = ui.configint('lfs', 'retry')
206 206
207 207 def writebatch(self, pointers, fromstore):
208 208 """Batch upload from local to remote blobstore."""
209 209 self._batch(_deduplicate(pointers), fromstore, 'upload')
210 210
211 211 def readbatch(self, pointers, tostore):
212 212 """Batch download from remote to local blostore."""
213 213 self._batch(_deduplicate(pointers), tostore, 'download')
214 214
215 215 def _batchrequest(self, pointers, action):
216 216 """Get metadata about objects pointed by pointers for given action
217 217
218 218 Return decoded JSON object like {'objects': [{'oid': '', 'size': 1}]}
219 219 See https://github.com/git-lfs/git-lfs/blob/master/docs/api/batch.md
220 220 """
221 221 objects = [{'oid': p.oid(), 'size': p.size()} for p in pointers]
222 222 requestdata = json.dumps({
223 223 'objects': objects,
224 224 'operation': action,
225 225 })
226 226 batchreq = util.urlreq.request('%s/objects/batch' % self.baseurl,
227 227 data=requestdata)
228 228 batchreq.add_header('Accept', 'application/vnd.git-lfs+json')
229 229 batchreq.add_header('Content-Type', 'application/vnd.git-lfs+json')
230 230 try:
231 231 rsp = self.urlopener.open(batchreq)
232 232 rawjson = rsp.read()
233 233 except util.urlerr.httperror as ex:
234 234 raise LfsRemoteError(_('LFS HTTP error: %s (action=%s)')
235 235 % (ex, action))
236 236 try:
237 237 response = json.loads(rawjson)
238 238 except ValueError:
239 239 raise LfsRemoteError(_('LFS server returns invalid JSON: %s')
240 240 % rawjson)
241 241
242 242 if self.ui.debugflag:
243 243 self.ui.debug('Status: %d\n' % rsp.status)
244 244 # lfs-test-server and hg serve return headers in different order
245 245 self.ui.debug('%s\n'
246 246 % '\n'.join(sorted(str(rsp.info()).splitlines())))
247 247
248 248 if 'objects' in response:
249 249 response['objects'] = sorted(response['objects'],
250 250 key=lambda p: p['oid'])
251 251 self.ui.debug('%s\n'
252 252 % json.dumps(response, indent=2,
253 253 separators=('', ': '), sort_keys=True))
254 254
255 255 return response
256 256
257 257 def _checkforservererror(self, pointers, responses, action):
258 258 """Scans errors from objects
259 259
260 260 Raises LfsRemoteError if any objects have an error"""
261 261 for response in responses:
262 262 # The server should return 404 when objects cannot be found. Some
263 263 # server implementation (ex. lfs-test-server) does not set "error"
264 264 # but just removes "download" from "actions". Treat that case
265 265 # as the same as 404 error.
266 notfound = (response.get('error', {}).get('code') == 404
267 or (action == 'download'
268 and action not in response.get('actions', [])))
269 if notfound:
270 ptrmap = {p.oid(): p for p in pointers}
271 p = ptrmap.get(response['oid'], None)
272 if p:
273 filename = getattr(p, 'filename', 'unknown')
274 raise LfsRemoteError(
275 _(('LFS server error. Remote object '
276 'for "%s" not found: %r')) % (filename, response))
266 if 'error' not in response:
267 if (action == 'download'
268 and action not in response.get('actions', [])):
269 code = 404
277 270 else:
278 raise LfsRemoteError(
279 _('LFS server error. Unsolicited response for oid %s')
280 % response['oid'])
281 if 'error' in response:
282 raise LfsRemoteError(_('LFS server error: %r') % response)
271 continue
272 else:
273 # An error dict without a code doesn't make much sense, so
274 # treat as a server error.
275 code = response.get('error').get('code', 500)
276
277 ptrmap = {p.oid(): p for p in pointers}
278 p = ptrmap.get(response['oid'], None)
279 if p:
280 filename = getattr(p, 'filename', 'unknown')
281 errors = {
282 404: 'The object does not exist',
283 410: 'The object was removed by the owner',
284 422: 'Validation error',
285 500: 'Internal server error',
286 }
287 msg = errors.get(code, 'status code %d' % code)
288 raise LfsRemoteError(_('LFS server error for "%s": %s')
289 % (filename, msg))
290 else:
291 raise LfsRemoteError(
292 _('LFS server error. Unsolicited response for oid %s')
293 % response['oid'])
283 294
284 295 def _extractobjects(self, response, pointers, action):
285 296 """extract objects from response of the batch API
286 297
287 298 response: parsed JSON object returned by batch API
288 299 return response['objects'] filtered by action
289 300 raise if any object has an error
290 301 """
291 302 # Scan errors from objects - fail early
292 303 objects = response.get('objects', [])
293 304 self._checkforservererror(pointers, objects, action)
294 305
295 306 # Filter objects with given action. Practically, this skips uploading
296 307 # objects which exist in the server.
297 308 filteredobjects = [o for o in objects if action in o.get('actions', [])]
298 309
299 310 return filteredobjects
300 311
301 312 def _basictransfer(self, obj, action, localstore):
302 313 """Download or upload a single object using basic transfer protocol
303 314
304 315 obj: dict, an object description returned by batch API
305 316 action: string, one of ['upload', 'download']
306 317 localstore: blobstore.local
307 318
308 319 See https://github.com/git-lfs/git-lfs/blob/master/docs/api/\
309 320 basic-transfers.md
310 321 """
311 322 oid = pycompat.bytestr(obj['oid'])
312 323
313 324 href = pycompat.bytestr(obj['actions'][action].get('href'))
314 325 headers = obj['actions'][action].get('header', {}).items()
315 326
316 327 request = util.urlreq.request(href)
317 328 if action == 'upload':
318 329 # If uploading blobs, read data from local blobstore.
319 330 if not localstore.verify(oid):
320 331 raise error.Abort(_('detected corrupt lfs object: %s') % oid,
321 332 hint=_('run hg verify'))
322 333 request.data = filewithprogress(localstore.open(oid), None)
323 334 request.get_method = lambda: 'PUT'
324 335
325 336 for k, v in headers:
326 337 request.add_header(k, v)
327 338
328 339 response = b''
329 340 try:
330 341 req = self.urlopener.open(request)
331 342
332 343 if self.ui.debugflag:
333 344 self.ui.debug('Status: %d\n' % req.status)
334 345 # lfs-test-server and hg serve return headers in different order
335 346 self.ui.debug('%s\n'
336 347 % '\n'.join(sorted(str(req.info()).splitlines())))
337 348
338 349 if action == 'download':
339 350 # If downloading blobs, store downloaded data to local blobstore
340 351 localstore.download(oid, req)
341 352 else:
342 353 while True:
343 354 data = req.read(1048576)
344 355 if not data:
345 356 break
346 357 response += data
347 358 if response:
348 359 self.ui.debug('lfs %s response: %s' % (action, response))
349 360 except util.urlerr.httperror as ex:
350 361 if self.ui.debugflag:
351 362 self.ui.debug('%s: %s\n' % (oid, ex.read()))
352 363 raise LfsRemoteError(_('HTTP error: %s (oid=%s, action=%s)')
353 364 % (ex, oid, action))
354 365
355 366 def _batch(self, pointers, localstore, action):
356 367 if action not in ['upload', 'download']:
357 368 raise error.ProgrammingError('invalid Git-LFS action: %s' % action)
358 369
359 370 response = self._batchrequest(pointers, action)
360 371 objects = self._extractobjects(response, pointers, action)
361 372 total = sum(x.get('size', 0) for x in objects)
362 373 sizes = {}
363 374 for obj in objects:
364 375 sizes[obj.get('oid')] = obj.get('size', 0)
365 376 topic = {'upload': _('lfs uploading'),
366 377 'download': _('lfs downloading')}[action]
367 378 if len(objects) > 1:
368 379 self.ui.note(_('lfs: need to transfer %d objects (%s)\n')
369 380 % (len(objects), util.bytecount(total)))
370 381 self.ui.progress(topic, 0, total=total)
371 382 def transfer(chunk):
372 383 for obj in chunk:
373 384 objsize = obj.get('size', 0)
374 385 if self.ui.verbose:
375 386 if action == 'download':
376 387 msg = _('lfs: downloading %s (%s)\n')
377 388 elif action == 'upload':
378 389 msg = _('lfs: uploading %s (%s)\n')
379 390 self.ui.note(msg % (obj.get('oid'),
380 391 util.bytecount(objsize)))
381 392 retry = self.retry
382 393 while True:
383 394 try:
384 395 self._basictransfer(obj, action, localstore)
385 396 yield 1, obj.get('oid')
386 397 break
387 398 except socket.error as ex:
388 399 if retry > 0:
389 400 self.ui.note(
390 401 _('lfs: failed: %r (remaining retry %d)\n')
391 402 % (ex, retry))
392 403 retry -= 1
393 404 continue
394 405 raise
395 406
396 407 # Until https multiplexing gets sorted out
397 408 if self.ui.configbool('experimental', 'lfs.worker-enable'):
398 409 oids = worker.worker(self.ui, 0.1, transfer, (),
399 410 sorted(objects, key=lambda o: o.get('oid')))
400 411 else:
401 412 oids = transfer(sorted(objects, key=lambda o: o.get('oid')))
402 413
403 414 processed = 0
404 415 blobs = 0
405 416 for _one, oid in oids:
406 417 processed += sizes[oid]
407 418 blobs += 1
408 419 self.ui.progress(topic, processed, total=total)
409 420 self.ui.note(_('lfs: processed: %s\n') % oid)
410 421 self.ui.progress(topic, pos=None, total=total)
411 422
412 423 if blobs > 0:
413 424 if action == 'upload':
414 425 self.ui.status(_('lfs: uploaded %d files (%s)\n')
415 426 % (blobs, util.bytecount(processed)))
416 427 # TODO: coalesce the download requests, and comment this in
417 428 #elif action == 'download':
418 429 # self.ui.status(_('lfs: downloaded %d files (%s)\n')
419 430 # % (blobs, util.bytecount(processed)))
420 431
421 432 def __del__(self):
422 433 # copied from mercurial/httppeer.py
423 434 urlopener = getattr(self, 'urlopener', None)
424 435 if urlopener:
425 436 for h in urlopener.handlers:
426 437 h.close()
427 438 getattr(h, "close_all", lambda : None)()
428 439
429 440 class _dummyremote(object):
430 441 """Dummy store storing blobs to temp directory."""
431 442
432 443 def __init__(self, repo, url):
433 444 fullpath = repo.vfs.join('lfs', url.path)
434 445 self.vfs = lfsvfs(fullpath)
435 446
436 447 def writebatch(self, pointers, fromstore):
437 448 for p in _deduplicate(pointers):
438 449 content = fromstore.read(p.oid(), verify=True)
439 450 with self.vfs(p.oid(), 'wb', atomictemp=True) as fp:
440 451 fp.write(content)
441 452
442 453 def readbatch(self, pointers, tostore):
443 454 for p in _deduplicate(pointers):
444 455 with self.vfs(p.oid(), 'rb') as fp:
445 456 tostore.download(p.oid(), fp)
446 457
447 458 class _nullremote(object):
448 459 """Null store storing blobs to /dev/null."""
449 460
450 461 def __init__(self, repo, url):
451 462 pass
452 463
453 464 def writebatch(self, pointers, fromstore):
454 465 pass
455 466
456 467 def readbatch(self, pointers, tostore):
457 468 pass
458 469
459 470 class _promptremote(object):
460 471 """Prompt user to set lfs.url when accessed."""
461 472
462 473 def __init__(self, repo, url):
463 474 pass
464 475
465 476 def writebatch(self, pointers, fromstore, ui=None):
466 477 self._prompt()
467 478
468 479 def readbatch(self, pointers, tostore, ui=None):
469 480 self._prompt()
470 481
471 482 def _prompt(self):
472 483 raise error.Abort(_('lfs.url needs to be configured'))
473 484
474 485 _storemap = {
475 486 'https': _gitlfsremote,
476 487 'http': _gitlfsremote,
477 488 'file': _dummyremote,
478 489 'null': _nullremote,
479 490 None: _promptremote,
480 491 }
481 492
482 493 def _deduplicate(pointers):
483 494 """Remove any duplicate oids that exist in the list"""
484 495 reduced = util.sortdict()
485 496 for p in pointers:
486 497 reduced[p.oid()] = p
487 498 return reduced.values()
488 499
489 500 def _verify(oid, content):
490 501 realoid = hashlib.sha256(content).hexdigest()
491 502 if realoid != oid:
492 503 raise error.Abort(_('detected corrupt lfs object: %s') % oid,
493 504 hint=_('run hg verify'))
494 505
495 506 def remote(repo):
496 507 """remotestore factory. return a store in _storemap depending on config"""
497 508 url = util.url(repo.ui.config('lfs', 'url') or '')
498 509 scheme = url.scheme
499 510 if scheme not in _storemap:
500 511 raise error.Abort(_('lfs: unknown url scheme: %s') % scheme)
501 512 return _storemap[scheme](repo, url)
502 513
503 514 class LfsRemoteError(error.RevlogError):
504 515 pass
@@ -1,924 +1,924 b''
1 1 #testcases git-server hg-server
2 2
3 3 #if git-server
4 4 #require lfs-test-server
5 5 #else
6 6 #require serve
7 7 #endif
8 8
9 9 #if git-server
10 10 $ LFS_LISTEN="tcp://:$HGPORT"
11 11 $ LFS_HOST="localhost:$HGPORT"
12 12 $ LFS_PUBLIC=1
13 13 $ export LFS_LISTEN LFS_HOST LFS_PUBLIC
14 14 #else
15 15 $ LFS_HOST="localhost:$HGPORT/.git/info/lfs"
16 16 #endif
17 17
18 18 #if no-windows git-server
19 19 $ lfs-test-server &> lfs-server.log &
20 20 $ echo $! >> $DAEMON_PIDS
21 21 #endif
22 22
23 23 #if windows git-server
24 24 $ cat >> $TESTTMP/spawn.py <<EOF
25 25 > import os
26 26 > import subprocess
27 27 > import sys
28 28 >
29 29 > for path in os.environ["PATH"].split(os.pathsep):
30 30 > exe = os.path.join(path, 'lfs-test-server.exe')
31 31 > if os.path.exists(exe):
32 32 > with open('lfs-server.log', 'wb') as out:
33 33 > p = subprocess.Popen(exe, stdout=out, stderr=out)
34 34 > sys.stdout.write('%s\n' % p.pid)
35 35 > sys.exit(0)
36 36 > sys.exit(1)
37 37 > EOF
38 38 $ $PYTHON $TESTTMP/spawn.py >> $DAEMON_PIDS
39 39 #endif
40 40
41 41 $ cat >> $HGRCPATH <<EOF
42 42 > [extensions]
43 43 > lfs=
44 44 > [lfs]
45 45 > url=http://foo:bar@$LFS_HOST
46 46 > track=all()
47 47 > [web]
48 48 > push_ssl = False
49 49 > allow-push = *
50 50 > EOF
51 51
52 52 Use a separate usercache, otherwise the server sees what the client commits, and
53 53 never requests a transfer.
54 54
55 55 #if hg-server
56 56 $ hg init server
57 57 $ hg --config "lfs.usercache=$TESTTMP/servercache" -R server serve -d \
58 58 > -p $HGPORT --pid-file=hg.pid -A $TESTTMP/access.log -E $TESTTMP/errors.log
59 59 $ cat hg.pid >> $DAEMON_PIDS
60 60 #endif
61 61
62 62 $ hg init repo1
63 63 $ cd repo1
64 64 $ echo THIS-IS-LFS > a
65 65 $ hg commit -m a -A a
66 66
67 67 A push can be serviced directly from the usercache if it isn't in the local
68 68 store.
69 69
70 70 $ hg init ../repo2
71 71 $ mv .hg/store/lfs .hg/store/lfs_
72 72 $ hg push ../repo2 --debug
73 73 http auth: user foo, password ***
74 74 pushing to ../repo2
75 75 http auth: user foo, password ***
76 76 query 1; heads
77 77 searching for changes
78 78 1 total queries in *s (glob)
79 79 listing keys for "phases"
80 80 checking for updated bookmarks
81 81 listing keys for "bookmarks"
82 82 lfs: computing set of blobs to upload
83 83 Status: 200
84 84 Content-Length: 309 (git-server !)
85 85 Content-Length: 350 (hg-server !)
86 86 Content-Type: application/vnd.git-lfs+json
87 87 Date: $HTTP_DATE$
88 88 Server: testing stub value (hg-server !)
89 89 {
90 90 "objects": [
91 91 {
92 92 "actions": {
93 93 "upload": {
94 94 "expires_at": "$ISO_8601_DATE_TIME$"
95 95 "header": {
96 96 "Accept": "application/vnd.git-lfs"
97 97 }
98 98 "href": "http://localhost:$HGPORT/objects/31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b" (git-server !)
99 99 "href": "http://localhost:$HGPORT/.hg/lfs/objects/31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b" (hg-server !)
100 100 }
101 101 }
102 102 "oid": "31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b"
103 103 "size": 12
104 104 }
105 105 ]
106 106 "transfer": "basic" (hg-server !)
107 107 }
108 108 lfs: uploading 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b (12 bytes)
109 109 Status: 200 (git-server !)
110 110 Status: 201 (hg-server !)
111 111 Content-Length: 0
112 112 Content-Type: text/plain; charset=utf-8
113 113 Date: $HTTP_DATE$
114 114 Server: testing stub value (hg-server !)
115 115 lfs: processed: 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b
116 116 lfs: uploaded 1 files (12 bytes)
117 117 1 changesets found
118 118 list of changesets:
119 119 99a7098854a3984a5c9eab0fc7a2906697b7cb5c
120 120 bundle2-output-bundle: "HG20", 4 parts total
121 121 bundle2-output-part: "replycaps" * bytes payload (glob)
122 122 bundle2-output-part: "check:heads" streamed payload
123 123 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
124 124 bundle2-output-part: "phase-heads" 24 bytes payload
125 125 bundle2-input-bundle: with-transaction
126 126 bundle2-input-part: "replycaps" supported
127 127 bundle2-input-part: total payload size * (glob)
128 128 bundle2-input-part: "check:heads" supported
129 129 bundle2-input-part: total payload size 20
130 130 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
131 131 adding changesets
132 132 add changeset 99a7098854a3
133 133 adding manifests
134 134 adding file changes
135 135 adding a revisions
136 136 added 1 changesets with 1 changes to 1 files
137 137 calling hook pretxnchangegroup.lfs: hgext.lfs.checkrequireslfs
138 138 bundle2-input-part: total payload size 617
139 139 bundle2-input-part: "phase-heads" supported
140 140 bundle2-input-part: total payload size 24
141 141 bundle2-input-bundle: 3 parts total
142 142 updating the branch cache
143 143 bundle2-output-bundle: "HG20", 1 parts total
144 144 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
145 145 bundle2-input-bundle: no-transaction
146 146 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
147 147 bundle2-input-bundle: 0 parts total
148 148 listing keys for "phases"
149 149 $ mv .hg/store/lfs_ .hg/store/lfs
150 150
151 151 Clear the cache to force a download
152 152 $ rm -rf `hg config lfs.usercache`
153 153 $ cd ../repo2
154 154 $ hg update tip --debug
155 155 http auth: user foo, password ***
156 156 resolving manifests
157 157 branchmerge: False, force: False, partial: False
158 158 ancestor: 000000000000, local: 000000000000+, remote: 99a7098854a3
159 159 Status: 200
160 160 Content-Length: 311 (git-server !)
161 161 Content-Length: 352 (hg-server !)
162 162 Content-Type: application/vnd.git-lfs+json
163 163 Date: $HTTP_DATE$
164 164 Server: testing stub value (hg-server !)
165 165 {
166 166 "objects": [
167 167 {
168 168 "actions": {
169 169 "download": {
170 170 "expires_at": "$ISO_8601_DATE_TIME$"
171 171 "header": {
172 172 "Accept": "application/vnd.git-lfs"
173 173 }
174 174 "href": "http://localhost:$HGPORT/*/31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b" (glob)
175 175 }
176 176 }
177 177 "oid": "31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b"
178 178 "size": 12
179 179 }
180 180 ]
181 181 "transfer": "basic" (hg-server !)
182 182 }
183 183 lfs: downloading 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b (12 bytes)
184 184 Status: 200
185 185 Content-Length: 12
186 186 Content-Type: text/plain; charset=utf-8 (git-server !)
187 187 Content-Type: application/octet-stream (hg-server !)
188 188 Date: $HTTP_DATE$
189 189 Server: testing stub value (hg-server !)
190 190 lfs: adding 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b to the usercache
191 191 lfs: processed: 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b
192 192 a: remote created -> g
193 193 getting a
194 194 lfs: found 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b in the local lfs store
195 195 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
196 196
197 197 When the server has some blobs already. `hg serve` doesn't offer to upload
198 198 blobs that it already knows about. Note that lfs-test-server is simply
199 199 toggling the action to 'download'. The Batch API spec says it should omit the
200 200 actions property completely.
201 201
202 202 $ hg mv a b
203 203 $ echo ANOTHER-LARGE-FILE > c
204 204 $ echo ANOTHER-LARGE-FILE2 > d
205 205 $ hg commit -m b-and-c -A b c d
206 206 $ hg push ../repo1 --debug
207 207 http auth: user foo, password ***
208 208 pushing to ../repo1
209 209 http auth: user foo, password ***
210 210 query 1; heads
211 211 searching for changes
212 212 all remote heads known locally
213 213 listing keys for "phases"
214 214 checking for updated bookmarks
215 215 listing keys for "bookmarks"
216 216 listing keys for "bookmarks"
217 217 lfs: computing set of blobs to upload
218 218 Status: 200
219 219 Content-Length: 901 (git-server !)
220 220 Content-Length: 755 (hg-server !)
221 221 Content-Type: application/vnd.git-lfs+json
222 222 Date: $HTTP_DATE$
223 223 Server: testing stub value (hg-server !)
224 224 {
225 225 "objects": [
226 226 {
227 227 "actions": { (git-server !)
228 228 "download": { (git-server !)
229 229 "expires_at": "$ISO_8601_DATE_TIME$" (git-server !)
230 230 "header": { (git-server !)
231 231 "Accept": "application/vnd.git-lfs" (git-server !)
232 232 } (git-server !)
233 233 "href": "http://localhost:$HGPORT/objects/31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b" (git-server !)
234 234 } (git-server !)
235 235 } (git-server !)
236 236 "oid": "31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b"
237 237 "size": 12
238 238 }
239 239 {
240 240 "actions": {
241 241 "upload": {
242 242 "expires_at": "$ISO_8601_DATE_TIME$"
243 243 "header": {
244 244 "Accept": "application/vnd.git-lfs"
245 245 }
246 246 "href": "http://localhost:$HGPORT/*/37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19" (glob)
247 247 }
248 248 }
249 249 "oid": "37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19"
250 250 "size": 20
251 251 }
252 252 {
253 253 "actions": {
254 254 "upload": {
255 255 "expires_at": "$ISO_8601_DATE_TIME$"
256 256 "header": {
257 257 "Accept": "application/vnd.git-lfs"
258 258 }
259 259 "href": "http://localhost:$HGPORT/*/d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998" (glob)
260 260 }
261 261 }
262 262 "oid": "d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998"
263 263 "size": 19
264 264 }
265 265 ]
266 266 "transfer": "basic" (hg-server !)
267 267 }
268 268 lfs: need to transfer 2 objects (39 bytes)
269 269 lfs: uploading 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 (20 bytes)
270 270 Status: 200 (git-server !)
271 271 Status: 201 (hg-server !)
272 272 Content-Length: 0
273 273 Content-Type: text/plain; charset=utf-8
274 274 Date: $HTTP_DATE$
275 275 Server: testing stub value (hg-server !)
276 276 lfs: processed: 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19
277 277 lfs: uploading d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 (19 bytes)
278 278 Status: 200 (git-server !)
279 279 Status: 201 (hg-server !)
280 280 Content-Length: 0
281 281 Content-Type: text/plain; charset=utf-8
282 282 Date: $HTTP_DATE$
283 283 Server: testing stub value (hg-server !)
284 284 lfs: processed: d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998
285 285 lfs: uploaded 2 files (39 bytes)
286 286 1 changesets found
287 287 list of changesets:
288 288 dfca2c9e2ef24996aa61ba2abd99277d884b3d63
289 289 bundle2-output-bundle: "HG20", 5 parts total
290 290 bundle2-output-part: "replycaps" * bytes payload (glob)
291 291 bundle2-output-part: "check:phases" 24 bytes payload
292 292 bundle2-output-part: "check:heads" streamed payload
293 293 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
294 294 bundle2-output-part: "phase-heads" 24 bytes payload
295 295 bundle2-input-bundle: with-transaction
296 296 bundle2-input-part: "replycaps" supported
297 297 bundle2-input-part: total payload size * (glob)
298 298 bundle2-input-part: "check:phases" supported
299 299 bundle2-input-part: total payload size 24
300 300 bundle2-input-part: "check:heads" supported
301 301 bundle2-input-part: total payload size 20
302 302 bundle2-input-part: "changegroup" (params: 1 mandatory) supported
303 303 adding changesets
304 304 add changeset dfca2c9e2ef2
305 305 adding manifests
306 306 adding file changes
307 307 adding b revisions
308 308 adding c revisions
309 309 adding d revisions
310 310 added 1 changesets with 3 changes to 3 files
311 311 bundle2-input-part: total payload size 1315
312 312 bundle2-input-part: "phase-heads" supported
313 313 bundle2-input-part: total payload size 24
314 314 bundle2-input-bundle: 4 parts total
315 315 updating the branch cache
316 316 bundle2-output-bundle: "HG20", 1 parts total
317 317 bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
318 318 bundle2-input-bundle: no-transaction
319 319 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
320 320 bundle2-input-bundle: 0 parts total
321 321 listing keys for "phases"
322 322
323 323 Clear the cache to force a download
324 324 $ rm -rf `hg config lfs.usercache`
325 325 $ hg --repo ../repo1 update tip --debug
326 326 http auth: user foo, password ***
327 327 resolving manifests
328 328 branchmerge: False, force: False, partial: False
329 329 ancestor: 99a7098854a3, local: 99a7098854a3+, remote: dfca2c9e2ef2
330 330 Status: 200
331 331 Content-Length: 608 (git-server !)
332 332 Content-Length: 670 (hg-server !)
333 333 Content-Type: application/vnd.git-lfs+json
334 334 Date: $HTTP_DATE$
335 335 Server: testing stub value (hg-server !)
336 336 {
337 337 "objects": [
338 338 {
339 339 "actions": {
340 340 "download": {
341 341 "expires_at": "$ISO_8601_DATE_TIME$"
342 342 "header": {
343 343 "Accept": "application/vnd.git-lfs"
344 344 }
345 345 "href": "http://localhost:$HGPORT/*/37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19" (glob)
346 346 }
347 347 }
348 348 "oid": "37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19"
349 349 "size": 20
350 350 }
351 351 {
352 352 "actions": {
353 353 "download": {
354 354 "expires_at": "$ISO_8601_DATE_TIME$"
355 355 "header": {
356 356 "Accept": "application/vnd.git-lfs"
357 357 }
358 358 "href": "http://localhost:$HGPORT/*/d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998" (glob)
359 359 }
360 360 }
361 361 "oid": "d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998"
362 362 "size": 19
363 363 }
364 364 ]
365 365 "transfer": "basic" (hg-server !)
366 366 }
367 367 lfs: need to transfer 2 objects (39 bytes)
368 368 lfs: downloading 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 (20 bytes)
369 369 Status: 200
370 370 Content-Length: 20
371 371 Content-Type: text/plain; charset=utf-8 (git-server !)
372 372 Content-Type: application/octet-stream (hg-server !)
373 373 Date: $HTTP_DATE$
374 374 Server: testing stub value (hg-server !)
375 375 lfs: adding 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 to the usercache
376 376 lfs: processed: 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19
377 377 lfs: downloading d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 (19 bytes)
378 378 Status: 200
379 379 Content-Length: 19
380 380 Content-Type: text/plain; charset=utf-8 (git-server !)
381 381 Content-Type: application/octet-stream (hg-server !)
382 382 Date: $HTTP_DATE$
383 383 Server: testing stub value (hg-server !)
384 384 lfs: adding d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 to the usercache
385 385 lfs: processed: d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998
386 386 b: remote created -> g
387 387 getting b
388 388 lfs: found 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b in the local lfs store
389 389 c: remote created -> g
390 390 getting c
391 391 lfs: found d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 in the local lfs store
392 392 d: remote created -> g
393 393 getting d
394 394 lfs: found 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 in the local lfs store
395 395 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
396 396
397 397 Test a corrupt file download, but clear the cache first to force a download.
398 398 `hg serve` indicates a corrupt file without transferring it, unlike
399 399 lfs-test-server.
400 400
401 401 $ rm -rf `hg config lfs.usercache`
402 402 #if git-server
403 403 $ cp $TESTTMP/lfs-content/d1/1e/1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 blob
404 404 $ echo 'damage' > $TESTTMP/lfs-content/d1/1e/1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998
405 405 #else
406 406 $ cp $TESTTMP/server/.hg/store/lfs/objects/d1/1e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 blob
407 407 $ echo 'damage' > $TESTTMP/server/.hg/store/lfs/objects/d1/1e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998
408 408 #endif
409 409 $ rm ../repo1/.hg/store/lfs/objects/d1/1e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998
410 410 $ rm ../repo1/*
411 411
412 412 TODO: give the proper error indication from `hg serve`
413 413
414 414 $ hg --repo ../repo1 update -C tip --debug
415 415 http auth: user foo, password ***
416 416 resolving manifests
417 417 branchmerge: False, force: True, partial: False
418 418 ancestor: dfca2c9e2ef2+, local: dfca2c9e2ef2+, remote: dfca2c9e2ef2
419 419 Status: 200
420 420 Content-Length: 311 (git-server !)
421 421 Content-Length: 183 (hg-server !)
422 422 Content-Type: application/vnd.git-lfs+json
423 423 Date: $HTTP_DATE$
424 424 Server: testing stub value (hg-server !)
425 425 {
426 426 "objects": [
427 427 {
428 428 "actions": { (git-server !)
429 429 "download": { (git-server !)
430 430 "expires_at": "$ISO_8601_DATE_TIME$" (git-server !)
431 431 "header": { (git-server !)
432 432 "Accept": "application/vnd.git-lfs" (git-server !)
433 433 } (git-server !)
434 434 "href": "http://localhost:$HGPORT/objects/d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998" (git-server !)
435 435 } (git-server !)
436 436 "error": { (hg-server !)
437 437 "code": 422 (hg-server !)
438 438 "message": "The object is corrupt" (hg-server !)
439 439 }
440 440 "oid": "d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998"
441 441 "size": 19
442 442 }
443 443 ]
444 444 "transfer": "basic" (hg-server !)
445 445 }
446 446 lfs: downloading d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 (19 bytes) (git-server !)
447 447 Status: 200 (git-server !)
448 448 Content-Length: 7 (git-server !)
449 449 Content-Type: text/plain; charset=utf-8 (git-server !)
450 450 Date: $HTTP_DATE$ (git-server !)
451 451 abort: corrupt remote lfs object: d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 (git-server !)
452 abort: LFS server error. Remote object for "c" not found: *! (glob) (hg-server !)
452 abort: LFS server error for "c": Validation error! (hg-server !)
453 453 [255]
454 454
455 455 The corrupted blob is not added to the usercache or local store
456 456
457 457 $ test -f ../repo1/.hg/store/lfs/objects/d1/1e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998
458 458 [1]
459 459 $ test -f `hg config lfs.usercache`/d1/1e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998
460 460 [1]
461 461 #if git-server
462 462 $ cp blob $TESTTMP/lfs-content/d1/1e/1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998
463 463 #else
464 464 $ cp blob $TESTTMP/server/.hg/store/lfs/objects/d1/1e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998
465 465 #endif
466 466
467 467 Test a corrupted file upload
468 468
469 469 $ echo 'another lfs blob' > b
470 470 $ hg ci -m 'another blob'
471 471 $ echo 'damage' > .hg/store/lfs/objects/e6/59058e26b07b39d2a9c7145b3f99b41f797b6621c8076600e9cb7ee88291f0
472 472 $ hg push --debug ../repo1
473 473 http auth: user foo, password ***
474 474 pushing to ../repo1
475 475 http auth: user foo, password ***
476 476 query 1; heads
477 477 searching for changes
478 478 all remote heads known locally
479 479 listing keys for "phases"
480 480 checking for updated bookmarks
481 481 listing keys for "bookmarks"
482 482 listing keys for "bookmarks"
483 483 lfs: computing set of blobs to upload
484 484 Status: 200
485 485 Content-Length: 309 (git-server !)
486 486 Content-Length: 350 (hg-server !)
487 487 Content-Type: application/vnd.git-lfs+json
488 488 Date: $HTTP_DATE$
489 489 Server: testing stub value (hg-server !)
490 490 {
491 491 "objects": [
492 492 {
493 493 "actions": {
494 494 "upload": {
495 495 "expires_at": "$ISO_8601_DATE_TIME$"
496 496 "header": {
497 497 "Accept": "application/vnd.git-lfs"
498 498 }
499 499 "href": "http://localhost:$HGPORT/*/e659058e26b07b39d2a9c7145b3f99b41f797b6621c8076600e9cb7ee88291f0" (glob)
500 500 }
501 501 }
502 502 "oid": "e659058e26b07b39d2a9c7145b3f99b41f797b6621c8076600e9cb7ee88291f0"
503 503 "size": 17
504 504 }
505 505 ]
506 506 "transfer": "basic" (hg-server !)
507 507 }
508 508 lfs: uploading e659058e26b07b39d2a9c7145b3f99b41f797b6621c8076600e9cb7ee88291f0 (17 bytes)
509 509 abort: detected corrupt lfs object: e659058e26b07b39d2a9c7145b3f99b41f797b6621c8076600e9cb7ee88291f0
510 510 (run hg verify)
511 511 [255]
512 512
513 513 Archive will prefetch blobs in a group
514 514
515 515 $ rm -rf .hg/store/lfs `hg config lfs.usercache`
516 516 $ hg archive --debug -r 1 ../archive
517 517 http auth: user foo, password ***
518 518 Status: 200
519 519 Content-Length: 905 (git-server !)
520 520 Content-Length: 988 (hg-server !)
521 521 Content-Type: application/vnd.git-lfs+json
522 522 Date: $HTTP_DATE$
523 523 Server: testing stub value (hg-server !)
524 524 {
525 525 "objects": [
526 526 {
527 527 "actions": {
528 528 "download": {
529 529 "expires_at": "$ISO_8601_DATE_TIME$"
530 530 "header": {
531 531 "Accept": "application/vnd.git-lfs"
532 532 }
533 533 "href": "http://localhost:$HGPORT/*/31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b" (glob)
534 534 }
535 535 }
536 536 "oid": "31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b"
537 537 "size": 12
538 538 }
539 539 {
540 540 "actions": {
541 541 "download": {
542 542 "expires_at": "$ISO_8601_DATE_TIME$"
543 543 "header": {
544 544 "Accept": "application/vnd.git-lfs"
545 545 }
546 546 "href": "http://localhost:$HGPORT/*/37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19" (glob)
547 547 }
548 548 }
549 549 "oid": "37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19"
550 550 "size": 20
551 551 }
552 552 {
553 553 "actions": {
554 554 "download": {
555 555 "expires_at": "$ISO_8601_DATE_TIME$"
556 556 "header": {
557 557 "Accept": "application/vnd.git-lfs"
558 558 }
559 559 "href": "http://localhost:$HGPORT/*/d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998" (glob)
560 560 }
561 561 }
562 562 "oid": "d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998"
563 563 "size": 19
564 564 }
565 565 ]
566 566 "transfer": "basic" (hg-server !)
567 567 }
568 568 lfs: need to transfer 3 objects (51 bytes)
569 569 lfs: downloading 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b (12 bytes)
570 570 Status: 200
571 571 Content-Length: 12
572 572 Content-Type: text/plain; charset=utf-8 (git-server !)
573 573 Content-Type: application/octet-stream (hg-server !)
574 574 Date: $HTTP_DATE$
575 575 Server: testing stub value (hg-server !)
576 576 lfs: adding 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b to the usercache
577 577 lfs: processed: 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b
578 578 lfs: downloading 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 (20 bytes)
579 579 Status: 200
580 580 Content-Length: 20
581 581 Content-Type: text/plain; charset=utf-8 (git-server !)
582 582 Content-Type: application/octet-stream (hg-server !)
583 583 Date: $HTTP_DATE$
584 584 Server: testing stub value (hg-server !)
585 585 lfs: adding 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 to the usercache
586 586 lfs: processed: 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19
587 587 lfs: downloading d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 (19 bytes)
588 588 Status: 200
589 589 Content-Length: 19
590 590 Content-Type: text/plain; charset=utf-8 (git-server !)
591 591 Content-Type: application/octet-stream (hg-server !)
592 592 Date: $HTTP_DATE$
593 593 Server: testing stub value (hg-server !)
594 594 lfs: adding d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 to the usercache
595 595 lfs: processed: d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998
596 596 lfs: found 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b in the local lfs store
597 597 lfs: found 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b in the local lfs store
598 598 lfs: found d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 in the local lfs store
599 599 lfs: found 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 in the local lfs store
600 600 $ find ../archive | sort
601 601 ../archive
602 602 ../archive/.hg_archival.txt
603 603 ../archive/a
604 604 ../archive/b
605 605 ../archive/c
606 606 ../archive/d
607 607
608 608 Cat will prefetch blobs in a group
609 609
610 610 $ rm -rf .hg/store/lfs `hg config lfs.usercache`
611 611 $ hg cat --debug -r 1 a b c
612 612 http auth: user foo, password ***
613 613 Status: 200
614 614 Content-Length: 608 (git-server !)
615 615 Content-Length: 670 (hg-server !)
616 616 Content-Type: application/vnd.git-lfs+json
617 617 Date: $HTTP_DATE$
618 618 Server: testing stub value (hg-server !)
619 619 {
620 620 "objects": [
621 621 {
622 622 "actions": {
623 623 "download": {
624 624 "expires_at": "$ISO_8601_DATE_TIME$"
625 625 "header": {
626 626 "Accept": "application/vnd.git-lfs"
627 627 }
628 628 "href": "http://localhost:$HGPORT/*/31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b" (glob)
629 629 }
630 630 }
631 631 "oid": "31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b"
632 632 "size": 12
633 633 }
634 634 {
635 635 "actions": {
636 636 "download": {
637 637 "expires_at": "$ISO_8601_DATE_TIME$"
638 638 "header": {
639 639 "Accept": "application/vnd.git-lfs"
640 640 }
641 641 "href": "http://localhost:$HGPORT/*/d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998" (glob)
642 642 }
643 643 }
644 644 "oid": "d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998"
645 645 "size": 19
646 646 }
647 647 ]
648 648 "transfer": "basic" (hg-server !)
649 649 }
650 650 lfs: need to transfer 2 objects (31 bytes)
651 651 lfs: downloading 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b (12 bytes)
652 652 Status: 200
653 653 Content-Length: 12
654 654 Content-Type: text/plain; charset=utf-8 (git-server !)
655 655 Content-Type: application/octet-stream (hg-server !)
656 656 Date: $HTTP_DATE$
657 657 Server: testing stub value (hg-server !)
658 658 lfs: adding 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b to the usercache
659 659 lfs: processed: 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b
660 660 lfs: downloading d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 (19 bytes)
661 661 Status: 200
662 662 Content-Length: 19
663 663 Content-Type: text/plain; charset=utf-8 (git-server !)
664 664 Content-Type: application/octet-stream (hg-server !)
665 665 Date: $HTTP_DATE$
666 666 Server: testing stub value (hg-server !)
667 667 lfs: adding d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 to the usercache
668 668 lfs: processed: d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998
669 669 lfs: found 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b in the local lfs store
670 670 THIS-IS-LFS
671 671 lfs: found 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b in the local lfs store
672 672 THIS-IS-LFS
673 673 lfs: found d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 in the local lfs store
674 674 ANOTHER-LARGE-FILE
675 675
676 676 Revert will prefetch blobs in a group
677 677
678 678 $ rm -rf .hg/store/lfs
679 679 $ rm -rf `hg config lfs.usercache`
680 680 $ rm *
681 681 $ hg revert --all -r 1 --debug
682 682 http auth: user foo, password ***
683 683 adding a
684 684 reverting b
685 685 reverting c
686 686 reverting d
687 687 Status: 200
688 688 Content-Length: 905 (git-server !)
689 689 Content-Length: 988 (hg-server !)
690 690 Content-Type: application/vnd.git-lfs+json
691 691 Date: $HTTP_DATE$
692 692 Server: testing stub value (hg-server !)
693 693 {
694 694 "objects": [
695 695 {
696 696 "actions": {
697 697 "download": {
698 698 "expires_at": "$ISO_8601_DATE_TIME$"
699 699 "header": {
700 700 "Accept": "application/vnd.git-lfs"
701 701 }
702 702 "href": "http://localhost:$HGPORT/*/31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b" (glob)
703 703 }
704 704 }
705 705 "oid": "31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b"
706 706 "size": 12
707 707 }
708 708 {
709 709 "actions": {
710 710 "download": {
711 711 "expires_at": "$ISO_8601_DATE_TIME$"
712 712 "header": {
713 713 "Accept": "application/vnd.git-lfs"
714 714 }
715 715 "href": "http://localhost:$HGPORT/*/37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19" (glob)
716 716 }
717 717 }
718 718 "oid": "37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19"
719 719 "size": 20
720 720 }
721 721 {
722 722 "actions": {
723 723 "download": {
724 724 "expires_at": "$ISO_8601_DATE_TIME$"
725 725 "header": {
726 726 "Accept": "application/vnd.git-lfs"
727 727 }
728 728 "href": "http://localhost:$HGPORT/*/d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998" (glob)
729 729 }
730 730 }
731 731 "oid": "d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998"
732 732 "size": 19
733 733 }
734 734 ]
735 735 "transfer": "basic" (hg-server !)
736 736 }
737 737 lfs: need to transfer 3 objects (51 bytes)
738 738 lfs: downloading 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b (12 bytes)
739 739 Status: 200
740 740 Content-Length: 12
741 741 Content-Type: text/plain; charset=utf-8 (git-server !)
742 742 Content-Type: application/octet-stream (hg-server !)
743 743 Date: $HTTP_DATE$
744 744 Server: testing stub value (hg-server !)
745 745 lfs: adding 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b to the usercache
746 746 lfs: processed: 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b
747 747 lfs: downloading 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 (20 bytes)
748 748 Status: 200
749 749 Content-Length: 20
750 750 Content-Type: text/plain; charset=utf-8 (git-server !)
751 751 Content-Type: application/octet-stream (hg-server !)
752 752 Date: $HTTP_DATE$
753 753 Server: testing stub value (hg-server !)
754 754 lfs: adding 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 to the usercache
755 755 lfs: processed: 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19
756 756 lfs: downloading d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 (19 bytes)
757 757 Status: 200
758 758 Content-Length: 19
759 759 Content-Type: text/plain; charset=utf-8 (git-server !)
760 760 Content-Type: application/octet-stream (hg-server !)
761 761 Date: $HTTP_DATE$
762 762 Server: testing stub value (hg-server !)
763 763 lfs: adding d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 to the usercache
764 764 lfs: processed: d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998
765 765 lfs: found 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b in the local lfs store
766 766 lfs: found d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 in the local lfs store
767 767 lfs: found 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 in the local lfs store
768 768 lfs: found 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b in the local lfs store
769 769
770 770 Check error message when the remote missed a blob:
771 771
772 772 $ echo FFFFF > b
773 773 $ hg commit -m b -A b
774 774 $ echo FFFFF >> b
775 775 $ hg commit -m b b
776 776 $ rm -rf .hg/store/lfs
777 777 $ rm -rf `hg config lfs.usercache`
778 778 $ hg update -C '.^' --debug
779 779 http auth: user foo, password ***
780 780 resolving manifests
781 781 branchmerge: False, force: True, partial: False
782 782 ancestor: 62fdbaf221c6+, local: 62fdbaf221c6+, remote: ef0564edf47e
783 783 Status: 200
784 784 Content-Length: 308 (git-server !)
785 785 Content-Length: 186 (hg-server !)
786 786 Content-Type: application/vnd.git-lfs+json
787 787 Date: $HTTP_DATE$
788 788 Server: testing stub value (hg-server !)
789 789 {
790 790 "objects": [
791 791 {
792 792 "actions": { (git-server !)
793 793 "upload": { (git-server !)
794 794 "expires_at": "$ISO_8601_DATE_TIME$" (git-server !)
795 795 "header": { (git-server !)
796 796 "Accept": "application/vnd.git-lfs" (git-server !)
797 797 } (git-server !)
798 798 "href": "http://localhost:$HGPORT/objects/8e6ea5f6c066b44a0efa43bcce86aea73f17e6e23f0663df0251e7524e140a13" (git-server !)
799 799 } (git-server !)
800 800 "error": { (hg-server !)
801 801 "code": 404 (hg-server !)
802 802 "message": "The object does not exist" (hg-server !)
803 803 }
804 804 "oid": "8e6ea5f6c066b44a0efa43bcce86aea73f17e6e23f0663df0251e7524e140a13"
805 805 "size": 6
806 806 }
807 807 ]
808 808 "transfer": "basic" (hg-server !)
809 809 }
810 abort: LFS server error. Remote object for "b" not found:(.*)! (re)
810 abort: LFS server error for "b": The object does not exist!
811 811 [255]
812 812
813 813 Check error message when object does not exist:
814 814
815 815 $ cd $TESTTMP
816 816 $ hg init test && cd test
817 817 $ echo "[extensions]" >> .hg/hgrc
818 818 $ echo "lfs=" >> .hg/hgrc
819 819 $ echo "[lfs]" >> .hg/hgrc
820 820 $ echo "threshold=1" >> .hg/hgrc
821 821 $ echo a > a
822 822 $ hg add a
823 823 $ hg commit -m 'test'
824 824 $ echo aaaaa > a
825 825 $ hg commit -m 'largefile'
826 826 $ hg debugdata .hg/store/data/a.i 1 # verify this is no the file content but includes "oid", the LFS "pointer".
827 827 version https://git-lfs.github.com/spec/v1
828 828 oid sha256:bdc26931acfb734b142a8d675f205becf27560dc461f501822de13274fe6fc8a
829 829 size 6
830 830 x-is-binary 0
831 831 $ cd ..
832 832 $ rm -rf `hg config lfs.usercache`
833 833
834 834 (Restart the server in a different location so it no longer has the content)
835 835
836 836 $ $PYTHON $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
837 837
838 838 #if hg-server
839 839 $ cat $TESTTMP/access.log $TESTTMP/errors.log
840 840 $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
841 841 $LOCALIP - - [$LOGDATE$] "PUT /.hg/lfs/objects/31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b HTTP/1.1" 201 - (glob)
842 842 $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
843 843 $LOCALIP - - [$LOGDATE$] "GET /.hg/lfs/objects/31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b HTTP/1.1" 200 - (glob)
844 844 $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
845 845 $LOCALIP - - [$LOGDATE$] "PUT /.hg/lfs/objects/37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 HTTP/1.1" 201 - (glob)
846 846 $LOCALIP - - [$LOGDATE$] "PUT /.hg/lfs/objects/d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 HTTP/1.1" 201 - (glob)
847 847 $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
848 848 $LOCALIP - - [$LOGDATE$] "GET /.hg/lfs/objects/37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 HTTP/1.1" 200 - (glob)
849 849 $LOCALIP - - [$LOGDATE$] "GET /.hg/lfs/objects/d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 HTTP/1.1" 200 - (glob)
850 850 $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
851 851 $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
852 852 $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
853 853 $LOCALIP - - [$LOGDATE$] "GET /.hg/lfs/objects/31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b HTTP/1.1" 200 - (glob)
854 854 $LOCALIP - - [$LOGDATE$] "GET /.hg/lfs/objects/37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 HTTP/1.1" 200 - (glob)
855 855 $LOCALIP - - [$LOGDATE$] "GET /.hg/lfs/objects/d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 HTTP/1.1" 200 - (glob)
856 856 $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
857 857 $LOCALIP - - [$LOGDATE$] "GET /.hg/lfs/objects/31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b HTTP/1.1" 200 - (glob)
858 858 $LOCALIP - - [$LOGDATE$] "GET /.hg/lfs/objects/d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 HTTP/1.1" 200 - (glob)
859 859 $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
860 860 $LOCALIP - - [$LOGDATE$] "GET /.hg/lfs/objects/31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b HTTP/1.1" 200 - (glob)
861 861 $LOCALIP - - [$LOGDATE$] "GET /.hg/lfs/objects/37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 HTTP/1.1" 200 - (glob)
862 862 $LOCALIP - - [$LOGDATE$] "GET /.hg/lfs/objects/d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 HTTP/1.1" 200 - (glob)
863 863 $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
864 864 #endif
865 865
866 866 $ rm $DAEMON_PIDS
867 867 $ mkdir $TESTTMP/lfs-server2
868 868 $ cd $TESTTMP/lfs-server2
869 869 #if no-windows git-server
870 870 $ lfs-test-server &> lfs-server.log &
871 871 $ echo $! >> $DAEMON_PIDS
872 872 #endif
873 873
874 874 #if windows git-server
875 875 $ $PYTHON $TESTTMP/spawn.py >> $DAEMON_PIDS
876 876 #endif
877 877
878 878 #if hg-server
879 879 $ hg init server2
880 880 $ hg --config "lfs.usercache=$TESTTMP/servercache2" -R server2 serve -d \
881 881 > -p $HGPORT --pid-file=hg.pid -A $TESTTMP/access.log -E $TESTTMP/errors.log
882 882 $ cat hg.pid >> $DAEMON_PIDS
883 883 #endif
884 884
885 885 $ cd $TESTTMP
886 886 $ hg --debug clone test test2
887 887 http auth: user foo, password ***
888 888 linked 6 files
889 889 http auth: user foo, password ***
890 890 updating to branch default
891 891 resolving manifests
892 892 branchmerge: False, force: False, partial: False
893 893 ancestor: 000000000000, local: 000000000000+, remote: d2a338f184a8
894 894 Status: 200
895 895 Content-Length: 308 (git-server !)
896 896 Content-Length: 186 (hg-server !)
897 897 Content-Type: application/vnd.git-lfs+json
898 898 Date: $HTTP_DATE$
899 899 Server: testing stub value (hg-server !)
900 900 {
901 901 "objects": [
902 902 {
903 903 "actions": { (git-server !)
904 904 "upload": { (git-server !)
905 905 "expires_at": "$ISO_8601_DATE_TIME$" (git-server !)
906 906 "header": { (git-server !)
907 907 "Accept": "application/vnd.git-lfs" (git-server !)
908 908 } (git-server !)
909 909 "href": "http://localhost:$HGPORT/objects/bdc26931acfb734b142a8d675f205becf27560dc461f501822de13274fe6fc8a" (git-server !)
910 910 } (git-server !)
911 911 "error": { (hg-server !)
912 912 "code": 404 (hg-server !)
913 913 "message": "The object does not exist" (hg-server !)
914 914 }
915 915 "oid": "bdc26931acfb734b142a8d675f205becf27560dc461f501822de13274fe6fc8a"
916 916 "size": 6
917 917 }
918 918 ]
919 919 "transfer": "basic" (hg-server !)
920 920 }
921 abort: LFS server error. Remote object for "a" not found:(.*)! (re)
921 abort: LFS server error for "a": The object does not exist!
922 922 [255]
923 923
924 924 $ $PYTHON $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
General Comments 0
You need to be logged in to leave comments. Login now