Show More
@@ -7,6 +7,7 b'' | |||||
7 |
|
7 | |||
8 | from __future__ import absolute_import |
|
8 | from __future__ import absolute_import | |
9 |
|
9 | |||
|
10 | import hashlib | |||
10 | import json |
|
11 | import json | |
11 | import os |
|
12 | import os | |
12 | import re |
|
13 | import re | |
@@ -99,8 +100,11 b' class local(object):' | |||||
99 | self.cachevfs = lfsvfs(usercache) |
|
100 | self.cachevfs = lfsvfs(usercache) | |
100 | self.ui = repo.ui |
|
101 | self.ui = repo.ui | |
101 |
|
102 | |||
102 | def write(self, oid, data): |
|
103 | def write(self, oid, data, verify=True): | |
103 | """Write blob to local blobstore.""" |
|
104 | """Write blob to local blobstore.""" | |
|
105 | if verify: | |||
|
106 | _verify(oid, data) | |||
|
107 | ||||
104 | with self.vfs(oid, 'wb', atomictemp=True) as fp: |
|
108 | with self.vfs(oid, 'wb', atomictemp=True) as fp: | |
105 | fp.write(data) |
|
109 | fp.write(data) | |
106 |
|
110 | |||
@@ -110,14 +114,23 b' class local(object):' | |||||
110 | self.ui.note(_('lfs: adding %s to the usercache\n') % oid) |
|
114 | self.ui.note(_('lfs: adding %s to the usercache\n') % oid) | |
111 | lfutil.link(self.vfs.join(oid), self.cachevfs.join(oid)) |
|
115 | lfutil.link(self.vfs.join(oid), self.cachevfs.join(oid)) | |
112 |
|
116 | |||
113 | def read(self, oid): |
|
117 | def read(self, oid, verify=True): | |
114 | """Read blob from local blobstore.""" |
|
118 | """Read blob from local blobstore.""" | |
115 | if not self.vfs.exists(oid): |
|
119 | if not self.vfs.exists(oid): | |
|
120 | blob = self._read(self.cachevfs, oid, verify) | |||
|
121 | self.ui.note(_('lfs: found %s in the usercache\n') % oid) | |||
116 | lfutil.link(self.cachevfs.join(oid), self.vfs.join(oid)) |
|
122 | lfutil.link(self.cachevfs.join(oid), self.vfs.join(oid)) | |
117 | self.ui.note(_('lfs: found %s in the usercache\n') % oid) |
|
|||
118 | else: |
|
123 | else: | |
119 | self.ui.note(_('lfs: found %s in the local lfs store\n') % oid) |
|
124 | self.ui.note(_('lfs: found %s in the local lfs store\n') % oid) | |
120 | return self.vfs.read(oid) |
|
125 | blob = self._read(self.vfs, oid, verify) | |
|
126 | return blob | |||
|
127 | ||||
|
128 | def _read(self, vfs, oid, verify): | |||
|
129 | """Read blob (after verifying) from the given store""" | |||
|
130 | blob = vfs.read(oid) | |||
|
131 | if verify: | |||
|
132 | _verify(oid, blob) | |||
|
133 | return blob | |||
121 |
|
134 | |||
122 | def has(self, oid): |
|
135 | def has(self, oid): | |
123 | """Returns True if the local blobstore contains the requested blob, |
|
136 | """Returns True if the local blobstore contains the requested blob, | |
@@ -233,6 +246,8 b' class _gitlfsremote(object):' | |||||
233 | request = util.urlreq.request(href) |
|
246 | request = util.urlreq.request(href) | |
234 | if action == 'upload': |
|
247 | if action == 'upload': | |
235 | # If uploading blobs, read data from local blobstore. |
|
248 | # If uploading blobs, read data from local blobstore. | |
|
249 | with localstore.vfs(oid) as fp: | |||
|
250 | _verifyfile(oid, fp) | |||
236 | request.data = filewithprogress(localstore.vfs(oid), None) |
|
251 | request.data = filewithprogress(localstore.vfs(oid), None) | |
237 | request.get_method = lambda: 'PUT' |
|
252 | request.get_method = lambda: 'PUT' | |
238 |
|
253 | |||
@@ -253,7 +268,7 b' class _gitlfsremote(object):' | |||||
253 |
|
268 | |||
254 | if action == 'download': |
|
269 | if action == 'download': | |
255 | # If downloading blobs, store downloaded data to local blobstore |
|
270 | # If downloading blobs, store downloaded data to local blobstore | |
256 | localstore.write(oid, response) |
|
271 | localstore.write(oid, response, verify=True) | |
257 |
|
272 | |||
258 | def _batch(self, pointers, localstore, action): |
|
273 | def _batch(self, pointers, localstore, action): | |
259 | if action not in ['upload', 'download']: |
|
274 | if action not in ['upload', 'download']: | |
@@ -324,14 +339,14 b' class _dummyremote(object):' | |||||
324 |
|
339 | |||
325 | def writebatch(self, pointers, fromstore): |
|
340 | def writebatch(self, pointers, fromstore): | |
326 | for p in pointers: |
|
341 | for p in pointers: | |
327 | content = fromstore.read(p.oid()) |
|
342 | content = fromstore.read(p.oid(), verify=True) | |
328 | with self.vfs(p.oid(), 'wb', atomictemp=True) as fp: |
|
343 | with self.vfs(p.oid(), 'wb', atomictemp=True) as fp: | |
329 | fp.write(content) |
|
344 | fp.write(content) | |
330 |
|
345 | |||
331 | def readbatch(self, pointers, tostore): |
|
346 | def readbatch(self, pointers, tostore): | |
332 | for p in pointers: |
|
347 | for p in pointers: | |
333 | content = self.vfs.read(p.oid()) |
|
348 | content = self.vfs.read(p.oid()) | |
334 | tostore.write(p.oid(), content) |
|
349 | tostore.write(p.oid(), content, verify=True) | |
335 |
|
350 | |||
336 | class _nullremote(object): |
|
351 | class _nullremote(object): | |
337 | """Null store storing blobs to /dev/null.""" |
|
352 | """Null store storing blobs to /dev/null.""" | |
@@ -368,6 +383,24 b' class _promptremote(object):' | |||||
368 | None: _promptremote, |
|
383 | None: _promptremote, | |
369 | } |
|
384 | } | |
370 |
|
385 | |||
|
386 | def _verify(oid, content): | |||
|
387 | realoid = hashlib.sha256(content).hexdigest() | |||
|
388 | if realoid != oid: | |||
|
389 | raise error.Abort(_('detected corrupt lfs object: %s') % oid, | |||
|
390 | hint=_('run hg verify')) | |||
|
391 | ||||
|
392 | def _verifyfile(oid, fp): | |||
|
393 | sha256 = hashlib.sha256() | |||
|
394 | while True: | |||
|
395 | data = fp.read(1024 * 1024) | |||
|
396 | if not data: | |||
|
397 | break | |||
|
398 | sha256.update(data) | |||
|
399 | realoid = sha256.hexdigest() | |||
|
400 | if realoid != oid: | |||
|
401 | raise error.Abort(_('detected corrupt lfs object: %s') % oid, | |||
|
402 | hint=_('run hg verify')) | |||
|
403 | ||||
371 | def remote(repo): |
|
404 | def remote(repo): | |
372 | """remotestore factory. return a store in _storemap depending on config""" |
|
405 | """remotestore factory. return a store in _storemap depending on config""" | |
373 | defaulturl = '' |
|
406 | defaulturl = '' |
@@ -54,7 +54,9 b' def readfromstore(self, text):' | |||||
54 | if not store.has(oid): |
|
54 | if not store.has(oid): | |
55 | p.filename = getattr(self, 'indexfile', None) |
|
55 | p.filename = getattr(self, 'indexfile', None) | |
56 | self.opener.lfsremoteblobstore.readbatch([p], store) |
|
56 | self.opener.lfsremoteblobstore.readbatch([p], store) | |
57 | text = store.read(oid) |
|
57 | ||
|
58 | # The caller will validate the content | |||
|
59 | text = store.read(oid, verify=False) | |||
58 |
|
60 | |||
59 | # pack hg filelog metadata |
|
61 | # pack hg filelog metadata | |
60 | hgmeta = {} |
|
62 | hgmeta = {} | |
@@ -76,7 +78,7 b' def writetostore(self, text):' | |||||
76 |
|
78 | |||
77 | # git-lfs only supports sha256 |
|
79 | # git-lfs only supports sha256 | |
78 | oid = hashlib.sha256(text).hexdigest() |
|
80 | oid = hashlib.sha256(text).hexdigest() | |
79 | self.opener.lfslocalblobstore.write(oid, text) |
|
81 | self.opener.lfslocalblobstore.write(oid, text, verify=False) | |
80 |
|
82 | |||
81 | # replace contents with metadata |
|
83 | # replace contents with metadata | |
82 | longoid = 'sha256:%s' % oid |
|
84 | longoid = 'sha256:%s' % oid |
@@ -105,16 +105,15 b' Clear the cache to force a download' | |||||
105 | lfs: found 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 in the local lfs store |
|
105 | lfs: found 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 in the local lfs store | |
106 | 3 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
106 | 3 files updated, 0 files merged, 0 files removed, 0 files unresolved | |
107 |
|
107 | |||
108 | Test a corrupt file download, but clear the cache first to force a download |
|
108 | Test a corrupt file download, but clear the cache first to force a download. | |
109 |
|
||||
110 | XXX: ideally, the validation would occur before polluting the usercache and |
|
|||
111 | local store, with a clearer error message. |
|
|||
112 |
|
109 | |||
113 | $ rm -rf `hg config lfs.usercache` |
|
110 | $ rm -rf `hg config lfs.usercache` | |
114 | $ cp $TESTTMP/lfs-content/d1/1e/1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 blob |
|
111 | $ cp $TESTTMP/lfs-content/d1/1e/1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 blob | |
115 | $ echo 'damage' > $TESTTMP/lfs-content/d1/1e/1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 |
|
112 | $ echo 'damage' > $TESTTMP/lfs-content/d1/1e/1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 | |
116 | $ rm ../repo1/.hg/store/lfs/objects/d1/1e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 |
|
113 | $ rm ../repo1/.hg/store/lfs/objects/d1/1e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 | |
117 | $ rm ../repo1/* |
|
114 | $ rm ../repo1/* | |
|
115 | ||||
|
116 | XXX: suggesting `hg verify` won't help with a corrupt file on the lfs server. | |||
118 | $ hg --repo ../repo1 update -C tip -v |
|
117 | $ hg --repo ../repo1 update -C tip -v | |
119 | resolving manifests |
|
118 | resolving manifests | |
120 | getting a |
|
119 | getting a | |
@@ -123,18 +122,16 b' local store, with a clearer error messag' | |||||
123 | lfs: found 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b in the local lfs store |
|
122 | lfs: found 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b in the local lfs store | |
124 | getting c |
|
123 | getting c | |
125 | lfs: downloading d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 (19 bytes) |
|
124 | lfs: downloading d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 (19 bytes) | |
126 |
|
|
125 | abort: detected corrupt lfs object: d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 | |
127 | lfs: processed: d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 |
|
126 | (run hg verify) | |
128 | lfs: found d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 in the local lfs store |
|
|||
129 | abort: integrity check failed on data/c.i:0! |
|
|||
130 | [255] |
|
127 | [255] | |
131 |
|
128 | |||
132 |
|
|
129 | The corrupted blob is not added to the usercache or local store | |
133 |
|
130 | |||
134 |
$ |
|
131 | $ test -f ../repo1/.hg/store/lfs/objects/d1/1e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 | |
135 | sha256=fa82ca222fc9813afad3559637960bf311170cdd80ed35287f4623eb2320a660 |
|
132 | [1] | |
136 |
$ |
|
133 | $ test -f `hg config lfs.usercache`/d1/1e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 | |
137 | sha256=fa82ca222fc9813afad3559637960bf311170cdd80ed35287f4623eb2320a660 |
|
134 | [1] | |
138 | $ cp blob $TESTTMP/lfs-content/d1/1e/1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 |
|
135 | $ cp blob $TESTTMP/lfs-content/d1/1e/1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 | |
139 |
|
136 | |||
140 | Test a corrupted file upload |
|
137 | Test a corrupted file upload | |
@@ -146,7 +143,8 b' Test a corrupted file upload' | |||||
146 | pushing to ../repo1 |
|
143 | pushing to ../repo1 | |
147 | searching for changes |
|
144 | searching for changes | |
148 | lfs: uploading e659058e26b07b39d2a9c7145b3f99b41f797b6621c8076600e9cb7ee88291f0 (17 bytes) |
|
145 | lfs: uploading e659058e26b07b39d2a9c7145b3f99b41f797b6621c8076600e9cb7ee88291f0 (17 bytes) | |
149 |
abort: |
|
146 | abort: detected corrupt lfs object: e659058e26b07b39d2a9c7145b3f99b41f797b6621c8076600e9cb7ee88291f0 | |
|
147 | (run hg verify) | |||
150 | [255] |
|
148 | [255] | |
151 |
|
149 | |||
152 | Check error message when the remote missed a blob: |
|
150 | Check error message when the remote missed a blob: |
@@ -704,18 +704,17 b' Damaging a file required by the update d' | |||||
704 | updating to branch default |
|
704 | updating to branch default | |
705 | resolving manifests |
|
705 | resolving manifests | |
706 | getting l |
|
706 | getting l | |
707 |
|
|
707 | abort: detected corrupt lfs object: 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b | |
708 | lfs: found 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b in the local lfs store |
|
708 | (run hg verify) | |
709 | abort: integrity check failed on data/l.i:3! |
|
|||
710 | [255] |
|
709 | [255] | |
711 |
|
710 | |||
712 | BUG: A corrupted lfs blob either shouldn't be created after a transfer from a |
|
711 | A corrupted lfs blob is not transferred from a file://remotestore to the | |
713 | file://remotestore, or it shouldn't be left behind. |
|
712 | usercache or local store. | |
714 |
|
713 | |||
715 |
$ |
|
714 | $ test -f emptycache/22/f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b | |
716 | sha256=40f67c7e91d554db4bc500f8f62c2e40f9f61daa5b62388e577bbae26f5396ff |
|
715 | [1] | |
717 |
$ |
|
716 | $ test -f fromcorrupt2/.hg/store/lfs/objects/22/f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b | |
718 | sha256=40f67c7e91d554db4bc500f8f62c2e40f9f61daa5b62388e577bbae26f5396ff |
|
717 | [1] | |
719 |
|
718 | |||
720 | $ hg -R fromcorrupt2 verify |
|
719 | $ hg -R fromcorrupt2 verify | |
721 | checking changesets |
|
720 | checking changesets | |
@@ -723,14 +722,13 b" file://remotestore, or it shouldn't be l" | |||||
723 | crosschecking files in changesets and manifests |
|
722 | crosschecking files in changesets and manifests | |
724 | checking files |
|
723 | checking files | |
725 | l@1: unpacking 46a2f24864bc: integrity check failed on data/l.i:0 |
|
724 | l@1: unpacking 46a2f24864bc: integrity check failed on data/l.i:0 | |
726 | l@4: unpacking 6f1ff1f39c11: integrity check failed on data/l.i:3 |
|
|||
727 | large@0: unpacking 2c531e0992ff: integrity check failed on data/large.i:0 |
|
725 | large@0: unpacking 2c531e0992ff: integrity check failed on data/large.i:0 | |
728 | 4 files, 5 changesets, 10 total revisions |
|
726 | 4 files, 5 changesets, 10 total revisions | |
729 |
|
|
727 | 2 integrity errors encountered! | |
730 | (first damaged changeset appears to be 0) |
|
728 | (first damaged changeset appears to be 0) | |
731 | [1] |
|
729 | [1] | |
732 |
|
730 | |||
733 |
|
|
731 | Corrupt local files are not sent upstream. (The alternate dummy remote | |
734 | avoids the corrupt lfs object in the original remote.) |
|
732 | avoids the corrupt lfs object in the original remote.) | |
735 |
|
733 | |||
736 | $ mkdir $TESTTMP/dummy-remote2 |
|
734 | $ mkdir $TESTTMP/dummy-remote2 | |
@@ -742,18 +740,9 b' avoids the corrupt lfs object in the ori' | |||||
742 | lfs: found 89b6070915a3d573ff3599d1cda305bc5e38549b15c4847ab034169da66e1ca8 in the local lfs store |
|
740 | lfs: found 89b6070915a3d573ff3599d1cda305bc5e38549b15c4847ab034169da66e1ca8 in the local lfs store | |
743 | lfs: found b1a6ea88da0017a0e77db139a54618986e9a2489bee24af9fe596de9daac498c in the local lfs store |
|
741 | lfs: found b1a6ea88da0017a0e77db139a54618986e9a2489bee24af9fe596de9daac498c in the local lfs store | |
744 | lfs: found 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e in the local lfs store |
|
742 | lfs: found 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e in the local lfs store | |
745 | 5 changesets found |
|
743 | abort: detected corrupt lfs object: 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e | |
746 | uncompressed size of bundle content: |
|
744 | (run hg verify) | |
747 | 997 (changelog) |
|
745 | [255] | |
748 | 1032 (manifests) |
|
|||
749 | 841 l |
|
|||
750 | 272 large |
|
|||
751 | 788 s |
|
|||
752 | 139 small |
|
|||
753 | adding changesets |
|
|||
754 | adding manifests |
|
|||
755 | adding file changes |
|
|||
756 | added 5 changesets with 10 changes to 4 files |
|
|||
757 |
|
746 | |||
758 | $ hg -R fromcorrupt2 --config lfs.url=file:///$TESTTMP/dummy-remote2 verify -v |
|
747 | $ hg -R fromcorrupt2 --config lfs.url=file:///$TESTTMP/dummy-remote2 verify -v | |
759 | repository uses revlog format 1 |
|
748 | repository uses revlog format 1 | |
@@ -764,27 +753,21 b' avoids the corrupt lfs object in the ori' | |||||
764 | lfs: found 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e in the local lfs store |
|
753 | lfs: found 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e in the local lfs store | |
765 | l@1: unpacking 46a2f24864bc: integrity check failed on data/l.i:0 |
|
754 | l@1: unpacking 46a2f24864bc: integrity check failed on data/l.i:0 | |
766 | lfs: found 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b in the local lfs store |
|
755 | lfs: found 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b in the local lfs store | |
767 | l@4: unpacking 6f1ff1f39c11: integrity check failed on data/l.i:3 |
|
|||
768 | lfs: found 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e in the local lfs store |
|
756 | lfs: found 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e in the local lfs store | |
769 | large@0: unpacking 2c531e0992ff: integrity check failed on data/large.i:0 |
|
757 | large@0: unpacking 2c531e0992ff: integrity check failed on data/large.i:0 | |
770 | lfs: found 89b6070915a3d573ff3599d1cda305bc5e38549b15c4847ab034169da66e1ca8 in the local lfs store |
|
758 | lfs: found 89b6070915a3d573ff3599d1cda305bc5e38549b15c4847ab034169da66e1ca8 in the local lfs store | |
771 | lfs: found b1a6ea88da0017a0e77db139a54618986e9a2489bee24af9fe596de9daac498c in the local lfs store |
|
759 | lfs: found b1a6ea88da0017a0e77db139a54618986e9a2489bee24af9fe596de9daac498c in the local lfs store | |
772 | 4 files, 5 changesets, 10 total revisions |
|
760 | 4 files, 5 changesets, 10 total revisions | |
773 |
|
|
761 | 2 integrity errors encountered! | |
774 | (first damaged changeset appears to be 0) |
|
762 | (first damaged changeset appears to be 0) | |
775 | [1] |
|
763 | [1] | |
776 |
|
764 | |||
777 | $ cat $TESTTMP/dummy-remote2/22/f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b | $TESTDIR/f --sha256 |
|
765 | $ cat $TESTTMP/dummy-remote2/22/f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b | $TESTDIR/f --sha256 | |
778 | sha256=40f67c7e91d554db4bc500f8f62c2e40f9f61daa5b62388e577bbae26f5396ff |
|
766 | sha256=22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b | |
779 | $ cat fromcorrupt2/.hg/store/lfs/objects/22/f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b | $TESTDIR/f --sha256 |
|
767 | $ cat fromcorrupt2/.hg/store/lfs/objects/22/f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b | $TESTDIR/f --sha256 | |
780 | sha256=40f67c7e91d554db4bc500f8f62c2e40f9f61daa5b62388e577bbae26f5396ff |
|
768 | sha256=22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b | |
781 |
|
769 | $ test -f $TESTTMP/dummy-remote2/66/100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e | ||
782 | $ cat $TESTTMP/dummy-remote2/66/100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e |
|
770 | [1] | |
783 | LONGER-THAN-TEN-BYTES-WILL-TRIGGER-LFS |
|
|||
784 | damage |
|
|||
785 | $ cat $TESTTMP/dummy-remote2/22/f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b |
|
|||
786 | RESTORE-TO-BE-LARGE |
|
|||
787 | damage |
|
|||
788 |
|
771 | |||
789 | Accessing a corrupt file will complain |
|
772 | Accessing a corrupt file will complain | |
790 |
|
773 |
General Comments 0
You need to be logged in to leave comments.
Login now