##// END OF EJS Templates
lfs: verify lfs object content when transferring to and from the remote store...
Matt Harbison -
r35492:417e8e04 default
parent child Browse files
Show More
@@ -1,395 +1,428
1 # blobstore.py - local and remote (speaking Git-LFS protocol) blob storages
1 # blobstore.py - local and remote (speaking Git-LFS protocol) blob storages
2 #
2 #
3 # Copyright 2017 Facebook, Inc.
3 # Copyright 2017 Facebook, Inc.
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 hashlib
10 import json
11 import json
11 import os
12 import os
12 import re
13 import re
13 import socket
14 import socket
14
15
15 from mercurial.i18n import _
16 from mercurial.i18n import _
16
17
17 from mercurial import (
18 from mercurial import (
18 error,
19 error,
19 pathutil,
20 pathutil,
20 url as urlmod,
21 url as urlmod,
21 util,
22 util,
22 vfs as vfsmod,
23 vfs as vfsmod,
23 worker,
24 worker,
24 )
25 )
25
26
26 from ..largefiles import lfutil
27 from ..largefiles import lfutil
27
28
28 # 64 bytes for SHA256
29 # 64 bytes for SHA256
29 _lfsre = re.compile(r'\A[a-f0-9]{64}\Z')
30 _lfsre = re.compile(r'\A[a-f0-9]{64}\Z')
30
31
31 class lfsvfs(vfsmod.vfs):
32 class lfsvfs(vfsmod.vfs):
32 def join(self, path):
33 def join(self, path):
33 """split the path at first two characters, like: XX/XXXXX..."""
34 """split the path at first two characters, like: XX/XXXXX..."""
34 if not _lfsre.match(path):
35 if not _lfsre.match(path):
35 raise error.ProgrammingError('unexpected lfs path: %s' % path)
36 raise error.ProgrammingError('unexpected lfs path: %s' % path)
36 return super(lfsvfs, self).join(path[0:2], path[2:])
37 return super(lfsvfs, self).join(path[0:2], path[2:])
37
38
38 def walk(self, path=None, onerror=None):
39 def walk(self, path=None, onerror=None):
39 """Yield (dirpath, [], oids) tuple for blobs under path
40 """Yield (dirpath, [], oids) tuple for blobs under path
40
41
41 Oids only exist in the root of this vfs, so dirpath is always ''.
42 Oids only exist in the root of this vfs, so dirpath is always ''.
42 """
43 """
43 root = os.path.normpath(self.base)
44 root = os.path.normpath(self.base)
44 # when dirpath == root, dirpath[prefixlen:] becomes empty
45 # when dirpath == root, dirpath[prefixlen:] becomes empty
45 # because len(dirpath) < prefixlen.
46 # because len(dirpath) < prefixlen.
46 prefixlen = len(pathutil.normasprefix(root))
47 prefixlen = len(pathutil.normasprefix(root))
47 oids = []
48 oids = []
48
49
49 for dirpath, dirs, files in os.walk(self.reljoin(self.base, path or ''),
50 for dirpath, dirs, files in os.walk(self.reljoin(self.base, path or ''),
50 onerror=onerror):
51 onerror=onerror):
51 dirpath = dirpath[prefixlen:]
52 dirpath = dirpath[prefixlen:]
52
53
53 # Silently skip unexpected files and directories
54 # Silently skip unexpected files and directories
54 if len(dirpath) == 2:
55 if len(dirpath) == 2:
55 oids.extend([dirpath + f for f in files
56 oids.extend([dirpath + f for f in files
56 if _lfsre.match(dirpath + f)])
57 if _lfsre.match(dirpath + f)])
57
58
58 yield ('', [], oids)
59 yield ('', [], oids)
59
60
60 class filewithprogress(object):
61 class filewithprogress(object):
61 """a file-like object that supports __len__ and read.
62 """a file-like object that supports __len__ and read.
62
63
63 Useful to provide progress information for how many bytes are read.
64 Useful to provide progress information for how many bytes are read.
64 """
65 """
65
66
66 def __init__(self, fp, callback):
67 def __init__(self, fp, callback):
67 self._fp = fp
68 self._fp = fp
68 self._callback = callback # func(readsize)
69 self._callback = callback # func(readsize)
69 fp.seek(0, os.SEEK_END)
70 fp.seek(0, os.SEEK_END)
70 self._len = fp.tell()
71 self._len = fp.tell()
71 fp.seek(0)
72 fp.seek(0)
72
73
73 def __len__(self):
74 def __len__(self):
74 return self._len
75 return self._len
75
76
76 def read(self, size):
77 def read(self, size):
77 if self._fp is None:
78 if self._fp is None:
78 return b''
79 return b''
79 data = self._fp.read(size)
80 data = self._fp.read(size)
80 if data:
81 if data:
81 if self._callback:
82 if self._callback:
82 self._callback(len(data))
83 self._callback(len(data))
83 else:
84 else:
84 self._fp.close()
85 self._fp.close()
85 self._fp = None
86 self._fp = None
86 return data
87 return data
87
88
88 class local(object):
89 class local(object):
89 """Local blobstore for large file contents.
90 """Local blobstore for large file contents.
90
91
91 This blobstore is used both as a cache and as a staging area for large blobs
92 This blobstore is used both as a cache and as a staging area for large blobs
92 to be uploaded to the remote blobstore.
93 to be uploaded to the remote blobstore.
93 """
94 """
94
95
95 def __init__(self, repo):
96 def __init__(self, repo):
96 fullpath = repo.svfs.join('lfs/objects')
97 fullpath = repo.svfs.join('lfs/objects')
97 self.vfs = lfsvfs(fullpath)
98 self.vfs = lfsvfs(fullpath)
98 usercache = lfutil._usercachedir(repo.ui, 'lfs')
99 usercache = lfutil._usercachedir(repo.ui, 'lfs')
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
107 # XXX: should we verify the content of the cache, and hardlink back to
111 # XXX: should we verify the content of the cache, and hardlink back to
108 # the local store on success, but truncate, write and link on failure?
112 # the local store on success, but truncate, write and link on failure?
109 if not self.cachevfs.exists(oid):
113 if not self.cachevfs.exists(oid):
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,
124 False otherwise."""
137 False otherwise."""
125 return self.cachevfs.exists(oid) or self.vfs.exists(oid)
138 return self.cachevfs.exists(oid) or self.vfs.exists(oid)
126
139
127 class _gitlfsremote(object):
140 class _gitlfsremote(object):
128
141
129 def __init__(self, repo, url):
142 def __init__(self, repo, url):
130 ui = repo.ui
143 ui = repo.ui
131 self.ui = ui
144 self.ui = ui
132 baseurl, authinfo = url.authinfo()
145 baseurl, authinfo = url.authinfo()
133 self.baseurl = baseurl.rstrip('/')
146 self.baseurl = baseurl.rstrip('/')
134 useragent = repo.ui.config('experimental', 'lfs.user-agent')
147 useragent = repo.ui.config('experimental', 'lfs.user-agent')
135 if not useragent:
148 if not useragent:
136 useragent = 'mercurial/%s git/2.15.1' % util.version()
149 useragent = 'mercurial/%s git/2.15.1' % util.version()
137 self.urlopener = urlmod.opener(ui, authinfo, useragent)
150 self.urlopener = urlmod.opener(ui, authinfo, useragent)
138 self.retry = ui.configint('lfs', 'retry')
151 self.retry = ui.configint('lfs', 'retry')
139
152
140 def writebatch(self, pointers, fromstore):
153 def writebatch(self, pointers, fromstore):
141 """Batch upload from local to remote blobstore."""
154 """Batch upload from local to remote blobstore."""
142 self._batch(pointers, fromstore, 'upload')
155 self._batch(pointers, fromstore, 'upload')
143
156
144 def readbatch(self, pointers, tostore):
157 def readbatch(self, pointers, tostore):
145 """Batch download from remote to local blostore."""
158 """Batch download from remote to local blostore."""
146 self._batch(pointers, tostore, 'download')
159 self._batch(pointers, tostore, 'download')
147
160
148 def _batchrequest(self, pointers, action):
161 def _batchrequest(self, pointers, action):
149 """Get metadata about objects pointed by pointers for given action
162 """Get metadata about objects pointed by pointers for given action
150
163
151 Return decoded JSON object like {'objects': [{'oid': '', 'size': 1}]}
164 Return decoded JSON object like {'objects': [{'oid': '', 'size': 1}]}
152 See https://github.com/git-lfs/git-lfs/blob/master/docs/api/batch.md
165 See https://github.com/git-lfs/git-lfs/blob/master/docs/api/batch.md
153 """
166 """
154 objects = [{'oid': p.oid(), 'size': p.size()} for p in pointers]
167 objects = [{'oid': p.oid(), 'size': p.size()} for p in pointers]
155 requestdata = json.dumps({
168 requestdata = json.dumps({
156 'objects': objects,
169 'objects': objects,
157 'operation': action,
170 'operation': action,
158 })
171 })
159 batchreq = util.urlreq.request('%s/objects/batch' % self.baseurl,
172 batchreq = util.urlreq.request('%s/objects/batch' % self.baseurl,
160 data=requestdata)
173 data=requestdata)
161 batchreq.add_header('Accept', 'application/vnd.git-lfs+json')
174 batchreq.add_header('Accept', 'application/vnd.git-lfs+json')
162 batchreq.add_header('Content-Type', 'application/vnd.git-lfs+json')
175 batchreq.add_header('Content-Type', 'application/vnd.git-lfs+json')
163 try:
176 try:
164 rawjson = self.urlopener.open(batchreq).read()
177 rawjson = self.urlopener.open(batchreq).read()
165 except util.urlerr.httperror as ex:
178 except util.urlerr.httperror as ex:
166 raise LfsRemoteError(_('LFS HTTP error: %s (action=%s)')
179 raise LfsRemoteError(_('LFS HTTP error: %s (action=%s)')
167 % (ex, action))
180 % (ex, action))
168 try:
181 try:
169 response = json.loads(rawjson)
182 response = json.loads(rawjson)
170 except ValueError:
183 except ValueError:
171 raise LfsRemoteError(_('LFS server returns invalid JSON: %s')
184 raise LfsRemoteError(_('LFS server returns invalid JSON: %s')
172 % rawjson)
185 % rawjson)
173 return response
186 return response
174
187
175 def _checkforservererror(self, pointers, responses):
188 def _checkforservererror(self, pointers, responses):
176 """Scans errors from objects
189 """Scans errors from objects
177
190
178 Returns LfsRemoteError if any objects has an error"""
191 Returns LfsRemoteError if any objects has an error"""
179 for response in responses:
192 for response in responses:
180 error = response.get('error')
193 error = response.get('error')
181 if error:
194 if error:
182 ptrmap = {p.oid(): p for p in pointers}
195 ptrmap = {p.oid(): p for p in pointers}
183 p = ptrmap.get(response['oid'], None)
196 p = ptrmap.get(response['oid'], None)
184 if error['code'] == 404 and p:
197 if error['code'] == 404 and p:
185 filename = getattr(p, 'filename', 'unknown')
198 filename = getattr(p, 'filename', 'unknown')
186 raise LfsRemoteError(
199 raise LfsRemoteError(
187 _(('LFS server error. Remote object '
200 _(('LFS server error. Remote object '
188 'for file %s not found: %r')) % (filename, response))
201 'for file %s not found: %r')) % (filename, response))
189 raise LfsRemoteError(_('LFS server error: %r') % response)
202 raise LfsRemoteError(_('LFS server error: %r') % response)
190
203
191 def _extractobjects(self, response, pointers, action):
204 def _extractobjects(self, response, pointers, action):
192 """extract objects from response of the batch API
205 """extract objects from response of the batch API
193
206
194 response: parsed JSON object returned by batch API
207 response: parsed JSON object returned by batch API
195 return response['objects'] filtered by action
208 return response['objects'] filtered by action
196 raise if any object has an error
209 raise if any object has an error
197 """
210 """
198 # Scan errors from objects - fail early
211 # Scan errors from objects - fail early
199 objects = response.get('objects', [])
212 objects = response.get('objects', [])
200 self._checkforservererror(pointers, objects)
213 self._checkforservererror(pointers, objects)
201
214
202 # Filter objects with given action. Practically, this skips uploading
215 # Filter objects with given action. Practically, this skips uploading
203 # objects which exist in the server.
216 # objects which exist in the server.
204 filteredobjects = [o for o in objects if action in o.get('actions', [])]
217 filteredobjects = [o for o in objects if action in o.get('actions', [])]
205 # But for downloading, we want all objects. Therefore missing objects
218 # But for downloading, we want all objects. Therefore missing objects
206 # should be considered an error.
219 # should be considered an error.
207 if action == 'download':
220 if action == 'download':
208 if len(filteredobjects) < len(objects):
221 if len(filteredobjects) < len(objects):
209 missing = [o.get('oid', '?')
222 missing = [o.get('oid', '?')
210 for o in objects
223 for o in objects
211 if action not in o.get('actions', [])]
224 if action not in o.get('actions', [])]
212 raise LfsRemoteError(
225 raise LfsRemoteError(
213 _('LFS server claims required objects do not exist:\n%s')
226 _('LFS server claims required objects do not exist:\n%s')
214 % '\n'.join(missing))
227 % '\n'.join(missing))
215
228
216 return filteredobjects
229 return filteredobjects
217
230
218 def _basictransfer(self, obj, action, localstore):
231 def _basictransfer(self, obj, action, localstore):
219 """Download or upload a single object using basic transfer protocol
232 """Download or upload a single object using basic transfer protocol
220
233
221 obj: dict, an object description returned by batch API
234 obj: dict, an object description returned by batch API
222 action: string, one of ['upload', 'download']
235 action: string, one of ['upload', 'download']
223 localstore: blobstore.local
236 localstore: blobstore.local
224
237
225 See https://github.com/git-lfs/git-lfs/blob/master/docs/api/\
238 See https://github.com/git-lfs/git-lfs/blob/master/docs/api/\
226 basic-transfers.md
239 basic-transfers.md
227 """
240 """
228 oid = str(obj['oid'])
241 oid = str(obj['oid'])
229
242
230 href = str(obj['actions'][action].get('href'))
243 href = str(obj['actions'][action].get('href'))
231 headers = obj['actions'][action].get('header', {}).items()
244 headers = obj['actions'][action].get('header', {}).items()
232
245
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
239 for k, v in headers:
254 for k, v in headers:
240 request.add_header(k, v)
255 request.add_header(k, v)
241
256
242 response = b''
257 response = b''
243 try:
258 try:
244 req = self.urlopener.open(request)
259 req = self.urlopener.open(request)
245 while True:
260 while True:
246 data = req.read(1048576)
261 data = req.read(1048576)
247 if not data:
262 if not data:
248 break
263 break
249 response += data
264 response += data
250 except util.urlerr.httperror as ex:
265 except util.urlerr.httperror as ex:
251 raise LfsRemoteError(_('HTTP error: %s (oid=%s, action=%s)')
266 raise LfsRemoteError(_('HTTP error: %s (oid=%s, action=%s)')
252 % (ex, oid, action))
267 % (ex, oid, action))
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']:
260 raise error.ProgrammingError('invalid Git-LFS action: %s' % action)
275 raise error.ProgrammingError('invalid Git-LFS action: %s' % action)
261
276
262 response = self._batchrequest(pointers, action)
277 response = self._batchrequest(pointers, action)
263 objects = self._extractobjects(response, pointers, action)
278 objects = self._extractobjects(response, pointers, action)
264 total = sum(x.get('size', 0) for x in objects)
279 total = sum(x.get('size', 0) for x in objects)
265 sizes = {}
280 sizes = {}
266 for obj in objects:
281 for obj in objects:
267 sizes[obj.get('oid')] = obj.get('size', 0)
282 sizes[obj.get('oid')] = obj.get('size', 0)
268 topic = {'upload': _('lfs uploading'),
283 topic = {'upload': _('lfs uploading'),
269 'download': _('lfs downloading')}[action]
284 'download': _('lfs downloading')}[action]
270 if self.ui.verbose and len(objects) > 1:
285 if self.ui.verbose and len(objects) > 1:
271 self.ui.write(_('lfs: need to transfer %d objects (%s)\n')
286 self.ui.write(_('lfs: need to transfer %d objects (%s)\n')
272 % (len(objects), util.bytecount(total)))
287 % (len(objects), util.bytecount(total)))
273 self.ui.progress(topic, 0, total=total)
288 self.ui.progress(topic, 0, total=total)
274 def transfer(chunk):
289 def transfer(chunk):
275 for obj in chunk:
290 for obj in chunk:
276 objsize = obj.get('size', 0)
291 objsize = obj.get('size', 0)
277 if self.ui.verbose:
292 if self.ui.verbose:
278 if action == 'download':
293 if action == 'download':
279 msg = _('lfs: downloading %s (%s)\n')
294 msg = _('lfs: downloading %s (%s)\n')
280 elif action == 'upload':
295 elif action == 'upload':
281 msg = _('lfs: uploading %s (%s)\n')
296 msg = _('lfs: uploading %s (%s)\n')
282 self.ui.write(msg % (obj.get('oid'),
297 self.ui.write(msg % (obj.get('oid'),
283 util.bytecount(objsize)))
298 util.bytecount(objsize)))
284 retry = self.retry
299 retry = self.retry
285 while True:
300 while True:
286 try:
301 try:
287 self._basictransfer(obj, action, localstore)
302 self._basictransfer(obj, action, localstore)
288 yield 1, obj.get('oid')
303 yield 1, obj.get('oid')
289 break
304 break
290 except socket.error as ex:
305 except socket.error as ex:
291 if retry > 0:
306 if retry > 0:
292 if self.ui.verbose:
307 if self.ui.verbose:
293 self.ui.write(
308 self.ui.write(
294 _('lfs: failed: %r (remaining retry %d)\n')
309 _('lfs: failed: %r (remaining retry %d)\n')
295 % (ex, retry))
310 % (ex, retry))
296 retry -= 1
311 retry -= 1
297 continue
312 continue
298 raise
313 raise
299
314
300 oids = worker.worker(self.ui, 0.1, transfer, (),
315 oids = worker.worker(self.ui, 0.1, transfer, (),
301 sorted(objects, key=lambda o: o.get('oid')))
316 sorted(objects, key=lambda o: o.get('oid')))
302 processed = 0
317 processed = 0
303 for _one, oid in oids:
318 for _one, oid in oids:
304 processed += sizes[oid]
319 processed += sizes[oid]
305 self.ui.progress(topic, processed, total=total)
320 self.ui.progress(topic, processed, total=total)
306 if self.ui.verbose:
321 if self.ui.verbose:
307 self.ui.write(_('lfs: processed: %s\n') % oid)
322 self.ui.write(_('lfs: processed: %s\n') % oid)
308 self.ui.progress(topic, pos=None, total=total)
323 self.ui.progress(topic, pos=None, total=total)
309
324
310 def __del__(self):
325 def __del__(self):
311 # copied from mercurial/httppeer.py
326 # copied from mercurial/httppeer.py
312 urlopener = getattr(self, 'urlopener', None)
327 urlopener = getattr(self, 'urlopener', None)
313 if urlopener:
328 if urlopener:
314 for h in urlopener.handlers:
329 for h in urlopener.handlers:
315 h.close()
330 h.close()
316 getattr(h, "close_all", lambda : None)()
331 getattr(h, "close_all", lambda : None)()
317
332
318 class _dummyremote(object):
333 class _dummyremote(object):
319 """Dummy store storing blobs to temp directory."""
334 """Dummy store storing blobs to temp directory."""
320
335
321 def __init__(self, repo, url):
336 def __init__(self, repo, url):
322 fullpath = repo.vfs.join('lfs', url.path)
337 fullpath = repo.vfs.join('lfs', url.path)
323 self.vfs = lfsvfs(fullpath)
338 self.vfs = lfsvfs(fullpath)
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."""
338
353
339 def __init__(self, repo, url):
354 def __init__(self, repo, url):
340 pass
355 pass
341
356
342 def writebatch(self, pointers, fromstore):
357 def writebatch(self, pointers, fromstore):
343 pass
358 pass
344
359
345 def readbatch(self, pointers, tostore):
360 def readbatch(self, pointers, tostore):
346 pass
361 pass
347
362
348 class _promptremote(object):
363 class _promptremote(object):
349 """Prompt user to set lfs.url when accessed."""
364 """Prompt user to set lfs.url when accessed."""
350
365
351 def __init__(self, repo, url):
366 def __init__(self, repo, url):
352 pass
367 pass
353
368
354 def writebatch(self, pointers, fromstore, ui=None):
369 def writebatch(self, pointers, fromstore, ui=None):
355 self._prompt()
370 self._prompt()
356
371
357 def readbatch(self, pointers, tostore, ui=None):
372 def readbatch(self, pointers, tostore, ui=None):
358 self._prompt()
373 self._prompt()
359
374
360 def _prompt(self):
375 def _prompt(self):
361 raise error.Abort(_('lfs.url needs to be configured'))
376 raise error.Abort(_('lfs.url needs to be configured'))
362
377
363 _storemap = {
378 _storemap = {
364 'https': _gitlfsremote,
379 'https': _gitlfsremote,
365 'http': _gitlfsremote,
380 'http': _gitlfsremote,
366 'file': _dummyremote,
381 'file': _dummyremote,
367 'null': _nullremote,
382 'null': _nullremote,
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 = ''
374
407
375 # convert deprecated configs to the new url. TODO: remove this if other
408 # convert deprecated configs to the new url. TODO: remove this if other
376 # places are migrated to the new url config.
409 # places are migrated to the new url config.
377 # deprecated config: lfs.remotestore
410 # deprecated config: lfs.remotestore
378 deprecatedstore = repo.ui.config('lfs', 'remotestore')
411 deprecatedstore = repo.ui.config('lfs', 'remotestore')
379 if deprecatedstore == 'dummy':
412 if deprecatedstore == 'dummy':
380 # deprecated config: lfs.remotepath
413 # deprecated config: lfs.remotepath
381 defaulturl = 'file://' + repo.ui.config('lfs', 'remotepath')
414 defaulturl = 'file://' + repo.ui.config('lfs', 'remotepath')
382 elif deprecatedstore == 'git-lfs':
415 elif deprecatedstore == 'git-lfs':
383 # deprecated config: lfs.remoteurl
416 # deprecated config: lfs.remoteurl
384 defaulturl = repo.ui.config('lfs', 'remoteurl')
417 defaulturl = repo.ui.config('lfs', 'remoteurl')
385 elif deprecatedstore == 'null':
418 elif deprecatedstore == 'null':
386 defaulturl = 'null://'
419 defaulturl = 'null://'
387
420
388 url = util.url(repo.ui.config('lfs', 'url', defaulturl))
421 url = util.url(repo.ui.config('lfs', 'url', defaulturl))
389 scheme = url.scheme
422 scheme = url.scheme
390 if scheme not in _storemap:
423 if scheme not in _storemap:
391 raise error.Abort(_('lfs: unknown url scheme: %s') % scheme)
424 raise error.Abort(_('lfs: unknown url scheme: %s') % scheme)
392 return _storemap[scheme](repo, url)
425 return _storemap[scheme](repo, url)
393
426
394 class LfsRemoteError(error.RevlogError):
427 class LfsRemoteError(error.RevlogError):
395 pass
428 pass
@@ -1,323 +1,325
1 # wrapper.py - methods wrapping core mercurial logic
1 # wrapper.py - methods wrapping core mercurial logic
2 #
2 #
3 # Copyright 2017 Facebook, Inc.
3 # Copyright 2017 Facebook, Inc.
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 hashlib
10 import hashlib
11
11
12 from mercurial.i18n import _
12 from mercurial.i18n import _
13 from mercurial.node import bin, nullid, short
13 from mercurial.node import bin, nullid, short
14
14
15 from mercurial import (
15 from mercurial import (
16 error,
16 error,
17 filelog,
17 filelog,
18 revlog,
18 revlog,
19 util,
19 util,
20 )
20 )
21
21
22 from ..largefiles import lfutil
22 from ..largefiles import lfutil
23
23
24 from . import (
24 from . import (
25 blobstore,
25 blobstore,
26 pointer,
26 pointer,
27 )
27 )
28
28
29 def supportedoutgoingversions(orig, repo):
29 def supportedoutgoingversions(orig, repo):
30 versions = orig(repo)
30 versions = orig(repo)
31 versions.discard('01')
31 versions.discard('01')
32 versions.discard('02')
32 versions.discard('02')
33 versions.add('03')
33 versions.add('03')
34 return versions
34 return versions
35
35
36 def allsupportedversions(orig, ui):
36 def allsupportedversions(orig, ui):
37 versions = orig(ui)
37 versions = orig(ui)
38 versions.add('03')
38 versions.add('03')
39 return versions
39 return versions
40
40
41 def bypasscheckhash(self, text):
41 def bypasscheckhash(self, text):
42 return False
42 return False
43
43
44 def readfromstore(self, text):
44 def readfromstore(self, text):
45 """Read filelog content from local blobstore transform for flagprocessor.
45 """Read filelog content from local blobstore transform for flagprocessor.
46
46
47 Default tranform for flagprocessor, returning contents from blobstore.
47 Default tranform for flagprocessor, returning contents from blobstore.
48 Returns a 2-typle (text, validatehash) where validatehash is True as the
48 Returns a 2-typle (text, validatehash) where validatehash is True as the
49 contents of the blobstore should be checked using checkhash.
49 contents of the blobstore should be checked using checkhash.
50 """
50 """
51 p = pointer.deserialize(text)
51 p = pointer.deserialize(text)
52 oid = p.oid()
52 oid = p.oid()
53 store = self.opener.lfslocalblobstore
53 store = self.opener.lfslocalblobstore
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 = {}
61 for k in p.keys():
63 for k in p.keys():
62 if k.startswith('x-hg-'):
64 if k.startswith('x-hg-'):
63 name = k[len('x-hg-'):]
65 name = k[len('x-hg-'):]
64 hgmeta[name] = p[k]
66 hgmeta[name] = p[k]
65 if hgmeta or text.startswith('\1\n'):
67 if hgmeta or text.startswith('\1\n'):
66 text = filelog.packmeta(hgmeta, text)
68 text = filelog.packmeta(hgmeta, text)
67
69
68 return (text, True)
70 return (text, True)
69
71
70 def writetostore(self, text):
72 def writetostore(self, text):
71 # hg filelog metadata (includes rename, etc)
73 # hg filelog metadata (includes rename, etc)
72 hgmeta, offset = filelog.parsemeta(text)
74 hgmeta, offset = filelog.parsemeta(text)
73 if offset and offset > 0:
75 if offset and offset > 0:
74 # lfs blob does not contain hg filelog metadata
76 # lfs blob does not contain hg filelog metadata
75 text = text[offset:]
77 text = text[offset:]
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
83 metadata = pointer.gitlfspointer(oid=longoid, size=str(len(text)))
85 metadata = pointer.gitlfspointer(oid=longoid, size=str(len(text)))
84
86
85 # by default, we expect the content to be binary. however, LFS could also
87 # by default, we expect the content to be binary. however, LFS could also
86 # be used for non-binary content. add a special entry for non-binary data.
88 # be used for non-binary content. add a special entry for non-binary data.
87 # this will be used by filectx.isbinary().
89 # this will be used by filectx.isbinary().
88 if not util.binary(text):
90 if not util.binary(text):
89 # not hg filelog metadata (affecting commit hash), no "x-hg-" prefix
91 # not hg filelog metadata (affecting commit hash), no "x-hg-" prefix
90 metadata['x-is-binary'] = '0'
92 metadata['x-is-binary'] = '0'
91
93
92 # translate hg filelog metadata to lfs metadata with "x-hg-" prefix
94 # translate hg filelog metadata to lfs metadata with "x-hg-" prefix
93 if hgmeta is not None:
95 if hgmeta is not None:
94 for k, v in hgmeta.iteritems():
96 for k, v in hgmeta.iteritems():
95 metadata['x-hg-%s' % k] = v
97 metadata['x-hg-%s' % k] = v
96
98
97 rawtext = metadata.serialize()
99 rawtext = metadata.serialize()
98 return (rawtext, False)
100 return (rawtext, False)
99
101
100 def _islfs(rlog, node=None, rev=None):
102 def _islfs(rlog, node=None, rev=None):
101 if rev is None:
103 if rev is None:
102 if node is None:
104 if node is None:
103 # both None - likely working copy content where node is not ready
105 # both None - likely working copy content where node is not ready
104 return False
106 return False
105 rev = rlog.rev(node)
107 rev = rlog.rev(node)
106 else:
108 else:
107 node = rlog.node(rev)
109 node = rlog.node(rev)
108 if node == nullid:
110 if node == nullid:
109 return False
111 return False
110 flags = rlog.flags(rev)
112 flags = rlog.flags(rev)
111 return bool(flags & revlog.REVIDX_EXTSTORED)
113 return bool(flags & revlog.REVIDX_EXTSTORED)
112
114
113 def filelogaddrevision(orig, self, text, transaction, link, p1, p2,
115 def filelogaddrevision(orig, self, text, transaction, link, p1, p2,
114 cachedelta=None, node=None,
116 cachedelta=None, node=None,
115 flags=revlog.REVIDX_DEFAULT_FLAGS, **kwds):
117 flags=revlog.REVIDX_DEFAULT_FLAGS, **kwds):
116 threshold = self.opener.options['lfsthreshold']
118 threshold = self.opener.options['lfsthreshold']
117 textlen = len(text)
119 textlen = len(text)
118 # exclude hg rename meta from file size
120 # exclude hg rename meta from file size
119 meta, offset = filelog.parsemeta(text)
121 meta, offset = filelog.parsemeta(text)
120 if offset:
122 if offset:
121 textlen -= offset
123 textlen -= offset
122
124
123 if threshold and textlen > threshold:
125 if threshold and textlen > threshold:
124 flags |= revlog.REVIDX_EXTSTORED
126 flags |= revlog.REVIDX_EXTSTORED
125
127
126 return orig(self, text, transaction, link, p1, p2, cachedelta=cachedelta,
128 return orig(self, text, transaction, link, p1, p2, cachedelta=cachedelta,
127 node=node, flags=flags, **kwds)
129 node=node, flags=flags, **kwds)
128
130
129 def filelogrenamed(orig, self, node):
131 def filelogrenamed(orig, self, node):
130 if _islfs(self, node):
132 if _islfs(self, node):
131 rawtext = self.revision(node, raw=True)
133 rawtext = self.revision(node, raw=True)
132 if not rawtext:
134 if not rawtext:
133 return False
135 return False
134 metadata = pointer.deserialize(rawtext)
136 metadata = pointer.deserialize(rawtext)
135 if 'x-hg-copy' in metadata and 'x-hg-copyrev' in metadata:
137 if 'x-hg-copy' in metadata and 'x-hg-copyrev' in metadata:
136 return metadata['x-hg-copy'], bin(metadata['x-hg-copyrev'])
138 return metadata['x-hg-copy'], bin(metadata['x-hg-copyrev'])
137 else:
139 else:
138 return False
140 return False
139 return orig(self, node)
141 return orig(self, node)
140
142
141 def filelogsize(orig, self, rev):
143 def filelogsize(orig, self, rev):
142 if _islfs(self, rev=rev):
144 if _islfs(self, rev=rev):
143 # fast path: use lfs metadata to answer size
145 # fast path: use lfs metadata to answer size
144 rawtext = self.revision(rev, raw=True)
146 rawtext = self.revision(rev, raw=True)
145 metadata = pointer.deserialize(rawtext)
147 metadata = pointer.deserialize(rawtext)
146 return int(metadata['size'])
148 return int(metadata['size'])
147 return orig(self, rev)
149 return orig(self, rev)
148
150
149 def filectxcmp(orig, self, fctx):
151 def filectxcmp(orig, self, fctx):
150 """returns True if text is different than fctx"""
152 """returns True if text is different than fctx"""
151 # some fctx (ex. hg-git) is not based on basefilectx and do not have islfs
153 # some fctx (ex. hg-git) is not based on basefilectx and do not have islfs
152 if self.islfs() and getattr(fctx, 'islfs', lambda: False)():
154 if self.islfs() and getattr(fctx, 'islfs', lambda: False)():
153 # fast path: check LFS oid
155 # fast path: check LFS oid
154 p1 = pointer.deserialize(self.rawdata())
156 p1 = pointer.deserialize(self.rawdata())
155 p2 = pointer.deserialize(fctx.rawdata())
157 p2 = pointer.deserialize(fctx.rawdata())
156 return p1.oid() != p2.oid()
158 return p1.oid() != p2.oid()
157 return orig(self, fctx)
159 return orig(self, fctx)
158
160
159 def filectxisbinary(orig, self):
161 def filectxisbinary(orig, self):
160 if self.islfs():
162 if self.islfs():
161 # fast path: use lfs metadata to answer isbinary
163 # fast path: use lfs metadata to answer isbinary
162 metadata = pointer.deserialize(self.rawdata())
164 metadata = pointer.deserialize(self.rawdata())
163 # if lfs metadata says nothing, assume it's binary by default
165 # if lfs metadata says nothing, assume it's binary by default
164 return bool(int(metadata.get('x-is-binary', 1)))
166 return bool(int(metadata.get('x-is-binary', 1)))
165 return orig(self)
167 return orig(self)
166
168
167 def filectxislfs(self):
169 def filectxislfs(self):
168 return _islfs(self.filelog(), self.filenode())
170 return _islfs(self.filelog(), self.filenode())
169
171
170 def convertsink(orig, sink):
172 def convertsink(orig, sink):
171 sink = orig(sink)
173 sink = orig(sink)
172 if sink.repotype == 'hg':
174 if sink.repotype == 'hg':
173 class lfssink(sink.__class__):
175 class lfssink(sink.__class__):
174 def putcommit(self, files, copies, parents, commit, source, revmap,
176 def putcommit(self, files, copies, parents, commit, source, revmap,
175 full, cleanp2):
177 full, cleanp2):
176 pc = super(lfssink, self).putcommit
178 pc = super(lfssink, self).putcommit
177 node = pc(files, copies, parents, commit, source, revmap, full,
179 node = pc(files, copies, parents, commit, source, revmap, full,
178 cleanp2)
180 cleanp2)
179
181
180 if 'lfs' not in self.repo.requirements:
182 if 'lfs' not in self.repo.requirements:
181 ctx = self.repo[node]
183 ctx = self.repo[node]
182
184
183 # The file list may contain removed files, so check for
185 # The file list may contain removed files, so check for
184 # membership before assuming it is in the context.
186 # membership before assuming it is in the context.
185 if any(f in ctx and ctx[f].islfs() for f, n in files):
187 if any(f in ctx and ctx[f].islfs() for f, n in files):
186 self.repo.requirements.add('lfs')
188 self.repo.requirements.add('lfs')
187 self.repo._writerequirements()
189 self.repo._writerequirements()
188
190
189 # Permanently enable lfs locally
191 # Permanently enable lfs locally
190 with self.repo.vfs('hgrc', 'a', text=True) as fp:
192 with self.repo.vfs('hgrc', 'a', text=True) as fp:
191 fp.write('\n[extensions]\nlfs=\n')
193 fp.write('\n[extensions]\nlfs=\n')
192
194
193 return node
195 return node
194
196
195 sink.__class__ = lfssink
197 sink.__class__ = lfssink
196
198
197 return sink
199 return sink
198
200
199 def vfsinit(orig, self, othervfs):
201 def vfsinit(orig, self, othervfs):
200 orig(self, othervfs)
202 orig(self, othervfs)
201 # copy lfs related options
203 # copy lfs related options
202 for k, v in othervfs.options.items():
204 for k, v in othervfs.options.items():
203 if k.startswith('lfs'):
205 if k.startswith('lfs'):
204 self.options[k] = v
206 self.options[k] = v
205 # also copy lfs blobstores. note: this can run before reposetup, so lfs
207 # also copy lfs blobstores. note: this can run before reposetup, so lfs
206 # blobstore attributes are not always ready at this time.
208 # blobstore attributes are not always ready at this time.
207 for name in ['lfslocalblobstore', 'lfsremoteblobstore']:
209 for name in ['lfslocalblobstore', 'lfsremoteblobstore']:
208 if util.safehasattr(othervfs, name):
210 if util.safehasattr(othervfs, name):
209 setattr(self, name, getattr(othervfs, name))
211 setattr(self, name, getattr(othervfs, name))
210
212
211 def hgclone(orig, ui, opts, *args, **kwargs):
213 def hgclone(orig, ui, opts, *args, **kwargs):
212 result = orig(ui, opts, *args, **kwargs)
214 result = orig(ui, opts, *args, **kwargs)
213
215
214 if result is not None:
216 if result is not None:
215 sourcerepo, destrepo = result
217 sourcerepo, destrepo = result
216 repo = destrepo.local()
218 repo = destrepo.local()
217
219
218 # When cloning to a remote repo (like through SSH), no repo is available
220 # When cloning to a remote repo (like through SSH), no repo is available
219 # from the peer. Therefore the hgrc can't be updated.
221 # from the peer. Therefore the hgrc can't be updated.
220 if not repo:
222 if not repo:
221 return result
223 return result
222
224
223 # If lfs is required for this repo, permanently enable it locally
225 # If lfs is required for this repo, permanently enable it locally
224 if 'lfs' in repo.requirements:
226 if 'lfs' in repo.requirements:
225 with repo.vfs('hgrc', 'a', text=True) as fp:
227 with repo.vfs('hgrc', 'a', text=True) as fp:
226 fp.write('\n[extensions]\nlfs=\n')
228 fp.write('\n[extensions]\nlfs=\n')
227
229
228 return result
230 return result
229
231
230 def hgpostshare(orig, sourcerepo, destrepo, bookmarks=True, defaultpath=None):
232 def hgpostshare(orig, sourcerepo, destrepo, bookmarks=True, defaultpath=None):
231 orig(sourcerepo, destrepo, bookmarks, defaultpath)
233 orig(sourcerepo, destrepo, bookmarks, defaultpath)
232
234
233 # If lfs is required for this repo, permanently enable it locally
235 # If lfs is required for this repo, permanently enable it locally
234 if 'lfs' in destrepo.requirements:
236 if 'lfs' in destrepo.requirements:
235 with destrepo.vfs('hgrc', 'a', text=True) as fp:
237 with destrepo.vfs('hgrc', 'a', text=True) as fp:
236 fp.write('\n[extensions]\nlfs=\n')
238 fp.write('\n[extensions]\nlfs=\n')
237
239
238 def _canskipupload(repo):
240 def _canskipupload(repo):
239 # if remotestore is a null store, upload is a no-op and can be skipped
241 # if remotestore is a null store, upload is a no-op and can be skipped
240 return isinstance(repo.svfs.lfsremoteblobstore, blobstore._nullremote)
242 return isinstance(repo.svfs.lfsremoteblobstore, blobstore._nullremote)
241
243
242 def candownload(repo):
244 def candownload(repo):
243 # if remotestore is a null store, downloads will lead to nothing
245 # if remotestore is a null store, downloads will lead to nothing
244 return not isinstance(repo.svfs.lfsremoteblobstore, blobstore._nullremote)
246 return not isinstance(repo.svfs.lfsremoteblobstore, blobstore._nullremote)
245
247
246 def uploadblobsfromrevs(repo, revs):
248 def uploadblobsfromrevs(repo, revs):
247 '''upload lfs blobs introduced by revs
249 '''upload lfs blobs introduced by revs
248
250
249 Note: also used by other extensions e. g. infinitepush. avoid renaming.
251 Note: also used by other extensions e. g. infinitepush. avoid renaming.
250 '''
252 '''
251 if _canskipupload(repo):
253 if _canskipupload(repo):
252 return
254 return
253 pointers = extractpointers(repo, revs)
255 pointers = extractpointers(repo, revs)
254 uploadblobs(repo, pointers)
256 uploadblobs(repo, pointers)
255
257
256 def prepush(pushop):
258 def prepush(pushop):
257 """Prepush hook.
259 """Prepush hook.
258
260
259 Read through the revisions to push, looking for filelog entries that can be
261 Read through the revisions to push, looking for filelog entries that can be
260 deserialized into metadata so that we can block the push on their upload to
262 deserialized into metadata so that we can block the push on their upload to
261 the remote blobstore.
263 the remote blobstore.
262 """
264 """
263 return uploadblobsfromrevs(pushop.repo, pushop.outgoing.missing)
265 return uploadblobsfromrevs(pushop.repo, pushop.outgoing.missing)
264
266
265 def writenewbundle(orig, ui, repo, source, filename, bundletype, outgoing,
267 def writenewbundle(orig, ui, repo, source, filename, bundletype, outgoing,
266 *args, **kwargs):
268 *args, **kwargs):
267 """upload LFS blobs added by outgoing revisions on 'hg bundle'"""
269 """upload LFS blobs added by outgoing revisions on 'hg bundle'"""
268 uploadblobsfromrevs(repo, outgoing.missing)
270 uploadblobsfromrevs(repo, outgoing.missing)
269 return orig(ui, repo, source, filename, bundletype, outgoing, *args,
271 return orig(ui, repo, source, filename, bundletype, outgoing, *args,
270 **kwargs)
272 **kwargs)
271
273
272 def extractpointers(repo, revs):
274 def extractpointers(repo, revs):
273 """return a list of lfs pointers added by given revs"""
275 """return a list of lfs pointers added by given revs"""
274 ui = repo.ui
276 ui = repo.ui
275 if ui.debugflag:
277 if ui.debugflag:
276 ui.write(_('lfs: computing set of blobs to upload\n'))
278 ui.write(_('lfs: computing set of blobs to upload\n'))
277 pointers = {}
279 pointers = {}
278 for r in revs:
280 for r in revs:
279 ctx = repo[r]
281 ctx = repo[r]
280 for p in pointersfromctx(ctx).values():
282 for p in pointersfromctx(ctx).values():
281 pointers[p.oid()] = p
283 pointers[p.oid()] = p
282 return sorted(pointers.values())
284 return sorted(pointers.values())
283
285
284 def pointersfromctx(ctx):
286 def pointersfromctx(ctx):
285 """return a dict {path: pointer} for given single changectx"""
287 """return a dict {path: pointer} for given single changectx"""
286 result = {}
288 result = {}
287 for f in ctx.files():
289 for f in ctx.files():
288 if f not in ctx:
290 if f not in ctx:
289 continue
291 continue
290 fctx = ctx[f]
292 fctx = ctx[f]
291 if not _islfs(fctx.filelog(), fctx.filenode()):
293 if not _islfs(fctx.filelog(), fctx.filenode()):
292 continue
294 continue
293 try:
295 try:
294 result[f] = pointer.deserialize(fctx.rawdata())
296 result[f] = pointer.deserialize(fctx.rawdata())
295 except pointer.InvalidPointer as ex:
297 except pointer.InvalidPointer as ex:
296 raise error.Abort(_('lfs: corrupted pointer (%s@%s): %s\n')
298 raise error.Abort(_('lfs: corrupted pointer (%s@%s): %s\n')
297 % (f, short(ctx.node()), ex))
299 % (f, short(ctx.node()), ex))
298 return result
300 return result
299
301
300 def uploadblobs(repo, pointers):
302 def uploadblobs(repo, pointers):
301 """upload given pointers from local blobstore"""
303 """upload given pointers from local blobstore"""
302 if not pointers:
304 if not pointers:
303 return
305 return
304
306
305 remoteblob = repo.svfs.lfsremoteblobstore
307 remoteblob = repo.svfs.lfsremoteblobstore
306 remoteblob.writebatch(pointers, repo.svfs.lfslocalblobstore)
308 remoteblob.writebatch(pointers, repo.svfs.lfslocalblobstore)
307
309
308 def upgradefinishdatamigration(orig, ui, srcrepo, dstrepo, requirements):
310 def upgradefinishdatamigration(orig, ui, srcrepo, dstrepo, requirements):
309 orig(ui, srcrepo, dstrepo, requirements)
311 orig(ui, srcrepo, dstrepo, requirements)
310
312
311 srclfsvfs = srcrepo.svfs.lfslocalblobstore.vfs
313 srclfsvfs = srcrepo.svfs.lfslocalblobstore.vfs
312 dstlfsvfs = dstrepo.svfs.lfslocalblobstore.vfs
314 dstlfsvfs = dstrepo.svfs.lfslocalblobstore.vfs
313
315
314 for dirpath, dirs, files in srclfsvfs.walk():
316 for dirpath, dirs, files in srclfsvfs.walk():
315 for oid in files:
317 for oid in files:
316 ui.write(_('copying lfs blob %s\n') % oid)
318 ui.write(_('copying lfs blob %s\n') % oid)
317 lfutil.link(srclfsvfs.join(oid), dstlfsvfs.join(oid))
319 lfutil.link(srclfsvfs.join(oid), dstlfsvfs.join(oid))
318
320
319 def upgraderequirements(orig, repo):
321 def upgraderequirements(orig, repo):
320 reqs = orig(repo)
322 reqs = orig(repo)
321 if 'lfs' in repo.requirements:
323 if 'lfs' in repo.requirements:
322 reqs.add('lfs')
324 reqs.add('lfs')
323 return reqs
325 return reqs
@@ -1,189 +1,187
1 #require lfs-test-server
1 #require lfs-test-server
2
2
3 $ LFS_LISTEN="tcp://:$HGPORT"
3 $ LFS_LISTEN="tcp://:$HGPORT"
4 $ LFS_HOST="localhost:$HGPORT"
4 $ LFS_HOST="localhost:$HGPORT"
5 $ LFS_PUBLIC=1
5 $ LFS_PUBLIC=1
6 $ export LFS_LISTEN LFS_HOST LFS_PUBLIC
6 $ export LFS_LISTEN LFS_HOST LFS_PUBLIC
7 #if no-windows
7 #if no-windows
8 $ lfs-test-server &> lfs-server.log &
8 $ lfs-test-server &> lfs-server.log &
9 $ echo $! >> $DAEMON_PIDS
9 $ echo $! >> $DAEMON_PIDS
10 #else
10 #else
11 $ cat >> $TESTTMP/spawn.py <<EOF
11 $ cat >> $TESTTMP/spawn.py <<EOF
12 > import os
12 > import os
13 > import subprocess
13 > import subprocess
14 > import sys
14 > import sys
15 >
15 >
16 > for path in os.environ["PATH"].split(os.pathsep):
16 > for path in os.environ["PATH"].split(os.pathsep):
17 > exe = os.path.join(path, 'lfs-test-server.exe')
17 > exe = os.path.join(path, 'lfs-test-server.exe')
18 > if os.path.exists(exe):
18 > if os.path.exists(exe):
19 > with open('lfs-server.log', 'wb') as out:
19 > with open('lfs-server.log', 'wb') as out:
20 > p = subprocess.Popen(exe, stdout=out, stderr=out)
20 > p = subprocess.Popen(exe, stdout=out, stderr=out)
21 > sys.stdout.write('%s\n' % p.pid)
21 > sys.stdout.write('%s\n' % p.pid)
22 > sys.exit(0)
22 > sys.exit(0)
23 > sys.exit(1)
23 > sys.exit(1)
24 > EOF
24 > EOF
25 $ $PYTHON $TESTTMP/spawn.py >> $DAEMON_PIDS
25 $ $PYTHON $TESTTMP/spawn.py >> $DAEMON_PIDS
26 #endif
26 #endif
27
27
28 $ cat >> $HGRCPATH <<EOF
28 $ cat >> $HGRCPATH <<EOF
29 > [extensions]
29 > [extensions]
30 > lfs=
30 > lfs=
31 > [lfs]
31 > [lfs]
32 > url=http://foo:bar@$LFS_HOST/
32 > url=http://foo:bar@$LFS_HOST/
33 > threshold=1
33 > threshold=1
34 > EOF
34 > EOF
35
35
36 $ hg init repo1
36 $ hg init repo1
37 $ cd repo1
37 $ cd repo1
38 $ echo THIS-IS-LFS > a
38 $ echo THIS-IS-LFS > a
39 $ hg commit -m a -A a
39 $ hg commit -m a -A a
40
40
41 $ hg init ../repo2
41 $ hg init ../repo2
42 $ hg push ../repo2 -v
42 $ hg push ../repo2 -v
43 pushing to ../repo2
43 pushing to ../repo2
44 searching for changes
44 searching for changes
45 lfs: uploading 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b (12 bytes)
45 lfs: uploading 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b (12 bytes)
46 lfs: processed: 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b
46 lfs: processed: 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b
47 1 changesets found
47 1 changesets found
48 uncompressed size of bundle content:
48 uncompressed size of bundle content:
49 * (changelog) (glob)
49 * (changelog) (glob)
50 * (manifests) (glob)
50 * (manifests) (glob)
51 * a (glob)
51 * a (glob)
52 adding changesets
52 adding changesets
53 adding manifests
53 adding manifests
54 adding file changes
54 adding file changes
55 added 1 changesets with 1 changes to 1 files
55 added 1 changesets with 1 changes to 1 files
56
56
57 Clear the cache to force a download
57 Clear the cache to force a download
58 $ rm -rf `hg config lfs.usercache`
58 $ rm -rf `hg config lfs.usercache`
59 $ cd ../repo2
59 $ cd ../repo2
60 $ hg update tip -v
60 $ hg update tip -v
61 resolving manifests
61 resolving manifests
62 getting a
62 getting a
63 lfs: downloading 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b (12 bytes)
63 lfs: downloading 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b (12 bytes)
64 lfs: adding 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b to the usercache
64 lfs: adding 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b to the usercache
65 lfs: processed: 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b
65 lfs: processed: 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b
66 lfs: found 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b in the local lfs store
66 lfs: found 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b in the local lfs store
67 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
67 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
68
68
69 When the server has some blobs already
69 When the server has some blobs already
70
70
71 $ hg mv a b
71 $ hg mv a b
72 $ echo ANOTHER-LARGE-FILE > c
72 $ echo ANOTHER-LARGE-FILE > c
73 $ echo ANOTHER-LARGE-FILE2 > d
73 $ echo ANOTHER-LARGE-FILE2 > d
74 $ hg commit -m b-and-c -A b c d
74 $ hg commit -m b-and-c -A b c d
75 $ hg push ../repo1 -v | grep -v '^ '
75 $ hg push ../repo1 -v | grep -v '^ '
76 pushing to ../repo1
76 pushing to ../repo1
77 searching for changes
77 searching for changes
78 lfs: need to transfer 2 objects (39 bytes)
78 lfs: need to transfer 2 objects (39 bytes)
79 lfs: uploading 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 (20 bytes)
79 lfs: uploading 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 (20 bytes)
80 lfs: processed: 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19
80 lfs: processed: 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19
81 lfs: uploading d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 (19 bytes)
81 lfs: uploading d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 (19 bytes)
82 lfs: processed: d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998
82 lfs: processed: d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998
83 1 changesets found
83 1 changesets found
84 uncompressed size of bundle content:
84 uncompressed size of bundle content:
85 adding changesets
85 adding changesets
86 adding manifests
86 adding manifests
87 adding file changes
87 adding file changes
88 added 1 changesets with 3 changes to 3 files
88 added 1 changesets with 3 changes to 3 files
89
89
90 Clear the cache to force a download
90 Clear the cache to force a download
91 $ rm -rf `hg config lfs.usercache`
91 $ rm -rf `hg config lfs.usercache`
92 $ hg --repo ../repo1 update tip -v
92 $ hg --repo ../repo1 update tip -v
93 resolving manifests
93 resolving manifests
94 getting b
94 getting b
95 lfs: found 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b in the local lfs store
95 lfs: found 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b in the local lfs store
96 getting c
96 getting c
97 lfs: downloading d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 (19 bytes)
97 lfs: downloading d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 (19 bytes)
98 lfs: adding d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 to the usercache
98 lfs: adding d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 to the usercache
99 lfs: processed: d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998
99 lfs: processed: d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998
100 lfs: found d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 in the local lfs store
100 lfs: found d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 in the local lfs store
101 getting d
101 getting d
102 lfs: downloading 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 (20 bytes)
102 lfs: downloading 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 (20 bytes)
103 lfs: adding 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 to the usercache
103 lfs: adding 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 to the usercache
104 lfs: processed: 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19
104 lfs: processed: 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19
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
121 lfs: found 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b in the local lfs store
120 lfs: found 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b in the local lfs store
122 getting b
121 getting b
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 lfs: adding d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 to the usercache
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 BUG: the corrupted blob was added to the usercache and local store
129 The corrupted blob is not added to the usercache or local store
133
130
134 $ cat ../repo1/.hg/store/lfs/objects/d1/1e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 | $TESTDIR/f --sha256
131 $ test -f ../repo1/.hg/store/lfs/objects/d1/1e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998
135 sha256=fa82ca222fc9813afad3559637960bf311170cdd80ed35287f4623eb2320a660
132 [1]
136 $ cat `hg config lfs.usercache`/d1/1e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 | $TESTDIR/f --sha256
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
141
138
142 $ echo 'another lfs blob' > b
139 $ echo 'another lfs blob' > b
143 $ hg ci -m 'another blob'
140 $ hg ci -m 'another blob'
144 $ echo 'damage' > .hg/store/lfs/objects/e6/59058e26b07b39d2a9c7145b3f99b41f797b6621c8076600e9cb7ee88291f0
141 $ echo 'damage' > .hg/store/lfs/objects/e6/59058e26b07b39d2a9c7145b3f99b41f797b6621c8076600e9cb7ee88291f0
145 $ hg push -v ../repo1
142 $ hg push -v ../repo1
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: HTTP error: HTTP Error 500: Internal Server Error (oid=e659058e26b07b39d2a9c7145b3f99b41f797b6621c8076600e9cb7ee88291f0, action=upload)!
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:
153
151
154 $ echo FFFFF > b
152 $ echo FFFFF > b
155 $ hg commit -m b -A b
153 $ hg commit -m b -A b
156 $ echo FFFFF >> b
154 $ echo FFFFF >> b
157 $ hg commit -m b b
155 $ hg commit -m b b
158 $ rm -rf .hg/store/lfs
156 $ rm -rf .hg/store/lfs
159 $ rm -rf `hg config lfs.usercache`
157 $ rm -rf `hg config lfs.usercache`
160 $ hg update -C '.^'
158 $ hg update -C '.^'
161 abort: LFS server claims required objects do not exist:
159 abort: LFS server claims required objects do not exist:
162 8e6ea5f6c066b44a0efa43bcce86aea73f17e6e23f0663df0251e7524e140a13!
160 8e6ea5f6c066b44a0efa43bcce86aea73f17e6e23f0663df0251e7524e140a13!
163 [255]
161 [255]
164
162
165 Check error message when object does not exist:
163 Check error message when object does not exist:
166
164
167 $ hg init test && cd test
165 $ hg init test && cd test
168 $ echo "[extensions]" >> .hg/hgrc
166 $ echo "[extensions]" >> .hg/hgrc
169 $ echo "lfs=" >> .hg/hgrc
167 $ echo "lfs=" >> .hg/hgrc
170 $ echo "[lfs]" >> .hg/hgrc
168 $ echo "[lfs]" >> .hg/hgrc
171 $ echo "threshold=1" >> .hg/hgrc
169 $ echo "threshold=1" >> .hg/hgrc
172 $ echo a > a
170 $ echo a > a
173 $ hg add a
171 $ hg add a
174 $ hg commit -m 'test'
172 $ hg commit -m 'test'
175 $ echo aaaaa > a
173 $ echo aaaaa > a
176 $ hg commit -m 'largefile'
174 $ hg commit -m 'largefile'
177 $ hg debugdata .hg/store/data/a.i 1 # verify this is no the file content but includes "oid", the LFS "pointer".
175 $ hg debugdata .hg/store/data/a.i 1 # verify this is no the file content but includes "oid", the LFS "pointer".
178 version https://git-lfs.github.com/spec/v1
176 version https://git-lfs.github.com/spec/v1
179 oid sha256:bdc26931acfb734b142a8d675f205becf27560dc461f501822de13274fe6fc8a
177 oid sha256:bdc26931acfb734b142a8d675f205becf27560dc461f501822de13274fe6fc8a
180 size 6
178 size 6
181 x-is-binary 0
179 x-is-binary 0
182 $ cd ..
180 $ cd ..
183 $ rm -rf `hg config lfs.usercache`
181 $ rm -rf `hg config lfs.usercache`
184 $ hg --config 'lfs.url=https://dewey-lfs.vip.facebook.com/lfs' clone test test2
182 $ hg --config 'lfs.url=https://dewey-lfs.vip.facebook.com/lfs' clone test test2
185 updating to branch default
183 updating to branch default
186 abort: LFS server error. Remote object for file data/a.i not found:(.*)! (re)
184 abort: LFS server error. Remote object for file data/a.i not found:(.*)! (re)
187 [255]
185 [255]
188
186
189 $ $PYTHON $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
187 $ $PYTHON $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
@@ -1,884 +1,867
1 # Initial setup
1 # Initial setup
2
2
3 $ cat >> $HGRCPATH << EOF
3 $ cat >> $HGRCPATH << EOF
4 > [extensions]
4 > [extensions]
5 > lfs=
5 > lfs=
6 > [lfs]
6 > [lfs]
7 > threshold=1000B
7 > threshold=1000B
8 > EOF
8 > EOF
9
9
10 $ LONG=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
10 $ LONG=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
11
11
12 # Prepare server and enable extension
12 # Prepare server and enable extension
13 $ hg init server
13 $ hg init server
14 $ hg clone -q server client
14 $ hg clone -q server client
15 $ cd client
15 $ cd client
16
16
17 # Commit small file
17 # Commit small file
18 $ echo s > smallfile
18 $ echo s > smallfile
19 $ hg commit -Aqm "add small file"
19 $ hg commit -Aqm "add small file"
20
20
21 # Commit large file
21 # Commit large file
22 $ echo $LONG > largefile
22 $ echo $LONG > largefile
23 $ grep lfs .hg/requires
23 $ grep lfs .hg/requires
24 [1]
24 [1]
25 $ hg commit --traceback -Aqm "add large file"
25 $ hg commit --traceback -Aqm "add large file"
26 $ grep lfs .hg/requires
26 $ grep lfs .hg/requires
27 lfs
27 lfs
28
28
29 # Ensure metadata is stored
29 # Ensure metadata is stored
30 $ hg debugdata largefile 0
30 $ hg debugdata largefile 0
31 version https://git-lfs.github.com/spec/v1
31 version https://git-lfs.github.com/spec/v1
32 oid sha256:f11e77c257047a398492d8d6cb9f6acf3aa7c4384bb23080b43546053e183e4b
32 oid sha256:f11e77c257047a398492d8d6cb9f6acf3aa7c4384bb23080b43546053e183e4b
33 size 1501
33 size 1501
34 x-is-binary 0
34 x-is-binary 0
35
35
36 # Check the blobstore is populated
36 # Check the blobstore is populated
37 $ find .hg/store/lfs/objects | sort
37 $ find .hg/store/lfs/objects | sort
38 .hg/store/lfs/objects
38 .hg/store/lfs/objects
39 .hg/store/lfs/objects/f1
39 .hg/store/lfs/objects/f1
40 .hg/store/lfs/objects/f1/1e77c257047a398492d8d6cb9f6acf3aa7c4384bb23080b43546053e183e4b
40 .hg/store/lfs/objects/f1/1e77c257047a398492d8d6cb9f6acf3aa7c4384bb23080b43546053e183e4b
41
41
42 # Check the blob stored contains the actual contents of the file
42 # Check the blob stored contains the actual contents of the file
43 $ cat .hg/store/lfs/objects/f1/1e77c257047a398492d8d6cb9f6acf3aa7c4384bb23080b43546053e183e4b
43 $ cat .hg/store/lfs/objects/f1/1e77c257047a398492d8d6cb9f6acf3aa7c4384bb23080b43546053e183e4b
44 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
44 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
45
45
46 # Push changes to the server
46 # Push changes to the server
47
47
48 $ hg push
48 $ hg push
49 pushing to $TESTTMP/server
49 pushing to $TESTTMP/server
50 searching for changes
50 searching for changes
51 abort: lfs.url needs to be configured
51 abort: lfs.url needs to be configured
52 [255]
52 [255]
53
53
54 $ cat >> $HGRCPATH << EOF
54 $ cat >> $HGRCPATH << EOF
55 > [lfs]
55 > [lfs]
56 > url=file:$TESTTMP/dummy-remote/
56 > url=file:$TESTTMP/dummy-remote/
57 > EOF
57 > EOF
58
58
59 $ hg push -v | egrep -v '^(uncompressed| )'
59 $ hg push -v | egrep -v '^(uncompressed| )'
60 pushing to $TESTTMP/server
60 pushing to $TESTTMP/server
61 searching for changes
61 searching for changes
62 lfs: found f11e77c257047a398492d8d6cb9f6acf3aa7c4384bb23080b43546053e183e4b in the local lfs store
62 lfs: found f11e77c257047a398492d8d6cb9f6acf3aa7c4384bb23080b43546053e183e4b in the local lfs store
63 2 changesets found
63 2 changesets found
64 adding changesets
64 adding changesets
65 adding manifests
65 adding manifests
66 adding file changes
66 adding file changes
67 added 2 changesets with 2 changes to 2 files
67 added 2 changesets with 2 changes to 2 files
68
68
69 # Unknown URL scheme
69 # Unknown URL scheme
70
70
71 $ hg push --config lfs.url=ftp://foobar
71 $ hg push --config lfs.url=ftp://foobar
72 abort: lfs: unknown url scheme: ftp
72 abort: lfs: unknown url scheme: ftp
73 [255]
73 [255]
74
74
75 $ cd ../
75 $ cd ../
76
76
77 # Initialize new client (not cloning) and setup extension
77 # Initialize new client (not cloning) and setup extension
78 $ hg init client2
78 $ hg init client2
79 $ cd client2
79 $ cd client2
80 $ cat >> .hg/hgrc <<EOF
80 $ cat >> .hg/hgrc <<EOF
81 > [paths]
81 > [paths]
82 > default = $TESTTMP/server
82 > default = $TESTTMP/server
83 > EOF
83 > EOF
84
84
85 # Pull from server
85 # Pull from server
86 $ hg pull default
86 $ hg pull default
87 pulling from $TESTTMP/server
87 pulling from $TESTTMP/server
88 requesting all changes
88 requesting all changes
89 adding changesets
89 adding changesets
90 adding manifests
90 adding manifests
91 adding file changes
91 adding file changes
92 added 2 changesets with 2 changes to 2 files
92 added 2 changesets with 2 changes to 2 files
93 new changesets b29ba743f89d:00c137947d30
93 new changesets b29ba743f89d:00c137947d30
94 (run 'hg update' to get a working copy)
94 (run 'hg update' to get a working copy)
95
95
96 # Check the blobstore is not yet populated
96 # Check the blobstore is not yet populated
97 $ [ -d .hg/store/lfs/objects ]
97 $ [ -d .hg/store/lfs/objects ]
98 [1]
98 [1]
99
99
100 # Update to the last revision containing the large file
100 # Update to the last revision containing the large file
101 $ hg update
101 $ hg update
102 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
102 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
103
103
104 # Check the blobstore has been populated on update
104 # Check the blobstore has been populated on update
105 $ find .hg/store/lfs/objects | sort
105 $ find .hg/store/lfs/objects | sort
106 .hg/store/lfs/objects
106 .hg/store/lfs/objects
107 .hg/store/lfs/objects/f1
107 .hg/store/lfs/objects/f1
108 .hg/store/lfs/objects/f1/1e77c257047a398492d8d6cb9f6acf3aa7c4384bb23080b43546053e183e4b
108 .hg/store/lfs/objects/f1/1e77c257047a398492d8d6cb9f6acf3aa7c4384bb23080b43546053e183e4b
109
109
110 # Check the contents of the file are fetched from blobstore when requested
110 # Check the contents of the file are fetched from blobstore when requested
111 $ hg cat -r . largefile
111 $ hg cat -r . largefile
112 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
112 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
113
113
114 # Check the file has been copied in the working copy
114 # Check the file has been copied in the working copy
115 $ cat largefile
115 $ cat largefile
116 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
116 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
117
117
118 $ cd ..
118 $ cd ..
119
119
120 # Check rename, and switch between large and small files
120 # Check rename, and switch between large and small files
121
121
122 $ hg init repo3
122 $ hg init repo3
123 $ cd repo3
123 $ cd repo3
124 $ cat >> .hg/hgrc << EOF
124 $ cat >> .hg/hgrc << EOF
125 > [lfs]
125 > [lfs]
126 > threshold=10B
126 > threshold=10B
127 > EOF
127 > EOF
128
128
129 $ echo LONGER-THAN-TEN-BYTES-WILL-TRIGGER-LFS > large
129 $ echo LONGER-THAN-TEN-BYTES-WILL-TRIGGER-LFS > large
130 $ echo SHORTER > small
130 $ echo SHORTER > small
131 $ hg add . -q
131 $ hg add . -q
132 $ hg commit -m 'commit with lfs content'
132 $ hg commit -m 'commit with lfs content'
133
133
134 $ hg mv large l
134 $ hg mv large l
135 $ hg mv small s
135 $ hg mv small s
136 $ hg commit -m 'renames'
136 $ hg commit -m 'renames'
137
137
138 $ echo SHORT > l
138 $ echo SHORT > l
139 $ echo BECOME-LARGER-FROM-SHORTER > s
139 $ echo BECOME-LARGER-FROM-SHORTER > s
140 $ hg commit -m 'large to small, small to large'
140 $ hg commit -m 'large to small, small to large'
141
141
142 $ echo 1 >> l
142 $ echo 1 >> l
143 $ echo 2 >> s
143 $ echo 2 >> s
144 $ hg commit -m 'random modifications'
144 $ hg commit -m 'random modifications'
145
145
146 $ echo RESTORE-TO-BE-LARGE > l
146 $ echo RESTORE-TO-BE-LARGE > l
147 $ echo SHORTER > s
147 $ echo SHORTER > s
148 $ hg commit -m 'switch large and small again'
148 $ hg commit -m 'switch large and small again'
149
149
150 # Test lfs_files template
150 # Test lfs_files template
151
151
152 $ hg log -r 'all()' -T '{rev} {join(lfs_files, ", ")}\n'
152 $ hg log -r 'all()' -T '{rev} {join(lfs_files, ", ")}\n'
153 0 large
153 0 large
154 1 l
154 1 l
155 2 s
155 2 s
156 3 s
156 3 s
157 4 l
157 4 l
158
158
159 # Push and pull the above repo
159 # Push and pull the above repo
160
160
161 $ hg --cwd .. init repo4
161 $ hg --cwd .. init repo4
162 $ hg push ../repo4
162 $ hg push ../repo4
163 pushing to ../repo4
163 pushing to ../repo4
164 searching for changes
164 searching for changes
165 adding changesets
165 adding changesets
166 adding manifests
166 adding manifests
167 adding file changes
167 adding file changes
168 added 5 changesets with 10 changes to 4 files
168 added 5 changesets with 10 changes to 4 files
169
169
170 $ hg --cwd .. init repo5
170 $ hg --cwd .. init repo5
171 $ hg --cwd ../repo5 pull ../repo3
171 $ hg --cwd ../repo5 pull ../repo3
172 pulling from ../repo3
172 pulling from ../repo3
173 requesting all changes
173 requesting all changes
174 adding changesets
174 adding changesets
175 adding manifests
175 adding manifests
176 adding file changes
176 adding file changes
177 added 5 changesets with 10 changes to 4 files
177 added 5 changesets with 10 changes to 4 files
178 new changesets fd47a419c4f7:5adf850972b9
178 new changesets fd47a419c4f7:5adf850972b9
179 (run 'hg update' to get a working copy)
179 (run 'hg update' to get a working copy)
180
180
181 $ cd ..
181 $ cd ..
182
182
183 # Test clone
183 # Test clone
184
184
185 $ hg init repo6
185 $ hg init repo6
186 $ cd repo6
186 $ cd repo6
187 $ cat >> .hg/hgrc << EOF
187 $ cat >> .hg/hgrc << EOF
188 > [lfs]
188 > [lfs]
189 > threshold=30B
189 > threshold=30B
190 > EOF
190 > EOF
191
191
192 $ echo LARGE-BECAUSE-IT-IS-MORE-THAN-30-BYTES > large
192 $ echo LARGE-BECAUSE-IT-IS-MORE-THAN-30-BYTES > large
193 $ echo SMALL > small
193 $ echo SMALL > small
194 $ hg commit -Aqm 'create a lfs file' large small
194 $ hg commit -Aqm 'create a lfs file' large small
195 $ hg debuglfsupload -r 'all()' -v
195 $ hg debuglfsupload -r 'all()' -v
196 lfs: found 8e92251415339ae9b148c8da89ed5ec665905166a1ab11b09dca8fad83344738 in the local lfs store
196 lfs: found 8e92251415339ae9b148c8da89ed5ec665905166a1ab11b09dca8fad83344738 in the local lfs store
197
197
198 $ cd ..
198 $ cd ..
199
199
200 $ hg clone repo6 repo7
200 $ hg clone repo6 repo7
201 updating to branch default
201 updating to branch default
202 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
202 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
203 $ cd repo7
203 $ cd repo7
204 $ hg config extensions --debug | grep lfs
204 $ hg config extensions --debug | grep lfs
205 $TESTTMP/repo7/.hg/hgrc:*: extensions.lfs= (glob)
205 $TESTTMP/repo7/.hg/hgrc:*: extensions.lfs= (glob)
206 $ cat large
206 $ cat large
207 LARGE-BECAUSE-IT-IS-MORE-THAN-30-BYTES
207 LARGE-BECAUSE-IT-IS-MORE-THAN-30-BYTES
208 $ cat small
208 $ cat small
209 SMALL
209 SMALL
210
210
211 $ cd ..
211 $ cd ..
212
212
213 $ hg --config extensions.share= share repo7 sharedrepo
213 $ hg --config extensions.share= share repo7 sharedrepo
214 updating working directory
214 updating working directory
215 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
215 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
216 $ hg -R sharedrepo config extensions --debug | grep lfs
216 $ hg -R sharedrepo config extensions --debug | grep lfs
217 $TESTTMP/sharedrepo/.hg/hgrc:*: extensions.lfs= (glob)
217 $TESTTMP/sharedrepo/.hg/hgrc:*: extensions.lfs= (glob)
218
218
219 # Test rename and status
219 # Test rename and status
220
220
221 $ hg init repo8
221 $ hg init repo8
222 $ cd repo8
222 $ cd repo8
223 $ cat >> .hg/hgrc << EOF
223 $ cat >> .hg/hgrc << EOF
224 > [lfs]
224 > [lfs]
225 > threshold=10B
225 > threshold=10B
226 > EOF
226 > EOF
227
227
228 $ echo THIS-IS-LFS-BECAUSE-10-BYTES > a1
228 $ echo THIS-IS-LFS-BECAUSE-10-BYTES > a1
229 $ echo SMALL > a2
229 $ echo SMALL > a2
230 $ hg commit -m a -A a1 a2
230 $ hg commit -m a -A a1 a2
231 $ hg status
231 $ hg status
232 $ hg mv a1 b1
232 $ hg mv a1 b1
233 $ hg mv a2 a1
233 $ hg mv a2 a1
234 $ hg mv b1 a2
234 $ hg mv b1 a2
235 $ hg commit -m b
235 $ hg commit -m b
236 $ hg status
236 $ hg status
237 >>> with open('a2', 'wb') as f:
237 >>> with open('a2', 'wb') as f:
238 ... f.write(b'\1\nSTART-WITH-HG-FILELOG-METADATA')
238 ... f.write(b'\1\nSTART-WITH-HG-FILELOG-METADATA')
239 >>> with open('a1', 'wb') as f:
239 >>> with open('a1', 'wb') as f:
240 ... f.write(b'\1\nMETA\n')
240 ... f.write(b'\1\nMETA\n')
241 $ hg commit -m meta
241 $ hg commit -m meta
242 $ hg status
242 $ hg status
243 $ hg log -T '{rev}: {file_copies} | {file_dels} | {file_adds}\n'
243 $ hg log -T '{rev}: {file_copies} | {file_dels} | {file_adds}\n'
244 2: | |
244 2: | |
245 1: a1 (a2)a2 (a1) | |
245 1: a1 (a2)a2 (a1) | |
246 0: | | a1 a2
246 0: | | a1 a2
247
247
248 $ for n in a1 a2; do
248 $ for n in a1 a2; do
249 > for r in 0 1 2; do
249 > for r in 0 1 2; do
250 > printf '\n%s @ %s\n' $n $r
250 > printf '\n%s @ %s\n' $n $r
251 > hg debugdata $n $r
251 > hg debugdata $n $r
252 > done
252 > done
253 > done
253 > done
254
254
255 a1 @ 0
255 a1 @ 0
256 version https://git-lfs.github.com/spec/v1
256 version https://git-lfs.github.com/spec/v1
257 oid sha256:5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
257 oid sha256:5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
258 size 29
258 size 29
259 x-is-binary 0
259 x-is-binary 0
260
260
261 a1 @ 1
261 a1 @ 1
262 \x01 (esc)
262 \x01 (esc)
263 copy: a2
263 copy: a2
264 copyrev: 50470ad23cf937b1f4b9f80bfe54df38e65b50d9
264 copyrev: 50470ad23cf937b1f4b9f80bfe54df38e65b50d9
265 \x01 (esc)
265 \x01 (esc)
266 SMALL
266 SMALL
267
267
268 a1 @ 2
268 a1 @ 2
269 \x01 (esc)
269 \x01 (esc)
270 \x01 (esc)
270 \x01 (esc)
271 \x01 (esc)
271 \x01 (esc)
272 META
272 META
273
273
274 a2 @ 0
274 a2 @ 0
275 SMALL
275 SMALL
276
276
277 a2 @ 1
277 a2 @ 1
278 version https://git-lfs.github.com/spec/v1
278 version https://git-lfs.github.com/spec/v1
279 oid sha256:5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
279 oid sha256:5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
280 size 29
280 size 29
281 x-hg-copy a1
281 x-hg-copy a1
282 x-hg-copyrev be23af27908a582af43e5cda209a5a9b319de8d4
282 x-hg-copyrev be23af27908a582af43e5cda209a5a9b319de8d4
283 x-is-binary 0
283 x-is-binary 0
284
284
285 a2 @ 2
285 a2 @ 2
286 version https://git-lfs.github.com/spec/v1
286 version https://git-lfs.github.com/spec/v1
287 oid sha256:876dadc86a8542f9798048f2c47f51dbf8e4359aed883e8ec80c5db825f0d943
287 oid sha256:876dadc86a8542f9798048f2c47f51dbf8e4359aed883e8ec80c5db825f0d943
288 size 32
288 size 32
289 x-is-binary 0
289 x-is-binary 0
290
290
291 # Verify commit hashes include rename metadata
291 # Verify commit hashes include rename metadata
292
292
293 $ hg log -T '{rev}:{node|short} {desc}\n'
293 $ hg log -T '{rev}:{node|short} {desc}\n'
294 2:0fae949de7fa meta
294 2:0fae949de7fa meta
295 1:9cd6bdffdac0 b
295 1:9cd6bdffdac0 b
296 0:7f96794915f7 a
296 0:7f96794915f7 a
297
297
298 $ cd ..
298 $ cd ..
299
299
300 # Test bundle
300 # Test bundle
301
301
302 $ hg init repo9
302 $ hg init repo9
303 $ cd repo9
303 $ cd repo9
304 $ cat >> .hg/hgrc << EOF
304 $ cat >> .hg/hgrc << EOF
305 > [lfs]
305 > [lfs]
306 > threshold=10B
306 > threshold=10B
307 > [diff]
307 > [diff]
308 > git=1
308 > git=1
309 > EOF
309 > EOF
310
310
311 $ for i in 0 single two three 4; do
311 $ for i in 0 single two three 4; do
312 > echo 'THIS-IS-LFS-'$i > a
312 > echo 'THIS-IS-LFS-'$i > a
313 > hg commit -m a-$i -A a
313 > hg commit -m a-$i -A a
314 > done
314 > done
315
315
316 $ hg update 2 -q
316 $ hg update 2 -q
317 $ echo 'THIS-IS-LFS-2-CHILD' > a
317 $ echo 'THIS-IS-LFS-2-CHILD' > a
318 $ hg commit -m branching -q
318 $ hg commit -m branching -q
319
319
320 $ hg bundle --base 1 bundle.hg -v
320 $ hg bundle --base 1 bundle.hg -v
321 lfs: found 5ab7a3739a5feec94a562d070a14f36dba7cad17e5484a4a89eea8e5f3166888 in the local lfs store
321 lfs: found 5ab7a3739a5feec94a562d070a14f36dba7cad17e5484a4a89eea8e5f3166888 in the local lfs store
322 lfs: found a9c7d1cd6ce2b9bbdf46ed9a862845228717b921c089d0d42e3bcaed29eb612e in the local lfs store
322 lfs: found a9c7d1cd6ce2b9bbdf46ed9a862845228717b921c089d0d42e3bcaed29eb612e in the local lfs store
323 lfs: found f693890c49c409ec33673b71e53f297681f76c1166daf33b2ad7ebf8b1d3237e in the local lfs store
323 lfs: found f693890c49c409ec33673b71e53f297681f76c1166daf33b2ad7ebf8b1d3237e in the local lfs store
324 lfs: found fda198fea753eb66a252e9856915e1f5cddbe41723bd4b695ece2604ad3c9f75 in the local lfs store
324 lfs: found fda198fea753eb66a252e9856915e1f5cddbe41723bd4b695ece2604ad3c9f75 in the local lfs store
325 4 changesets found
325 4 changesets found
326 uncompressed size of bundle content:
326 uncompressed size of bundle content:
327 * (changelog) (glob)
327 * (changelog) (glob)
328 * (manifests) (glob)
328 * (manifests) (glob)
329 * a (glob)
329 * a (glob)
330 $ hg --config extensions.strip= strip -r 2 --no-backup --force -q
330 $ hg --config extensions.strip= strip -r 2 --no-backup --force -q
331 $ hg -R bundle.hg log -p -T '{rev} {desc}\n' a
331 $ hg -R bundle.hg log -p -T '{rev} {desc}\n' a
332 5 branching
332 5 branching
333 diff --git a/a b/a
333 diff --git a/a b/a
334 --- a/a
334 --- a/a
335 +++ b/a
335 +++ b/a
336 @@ -1,1 +1,1 @@
336 @@ -1,1 +1,1 @@
337 -THIS-IS-LFS-two
337 -THIS-IS-LFS-two
338 +THIS-IS-LFS-2-CHILD
338 +THIS-IS-LFS-2-CHILD
339
339
340 4 a-4
340 4 a-4
341 diff --git a/a b/a
341 diff --git a/a b/a
342 --- a/a
342 --- a/a
343 +++ b/a
343 +++ b/a
344 @@ -1,1 +1,1 @@
344 @@ -1,1 +1,1 @@
345 -THIS-IS-LFS-three
345 -THIS-IS-LFS-three
346 +THIS-IS-LFS-4
346 +THIS-IS-LFS-4
347
347
348 3 a-three
348 3 a-three
349 diff --git a/a b/a
349 diff --git a/a b/a
350 --- a/a
350 --- a/a
351 +++ b/a
351 +++ b/a
352 @@ -1,1 +1,1 @@
352 @@ -1,1 +1,1 @@
353 -THIS-IS-LFS-two
353 -THIS-IS-LFS-two
354 +THIS-IS-LFS-three
354 +THIS-IS-LFS-three
355
355
356 2 a-two
356 2 a-two
357 diff --git a/a b/a
357 diff --git a/a b/a
358 --- a/a
358 --- a/a
359 +++ b/a
359 +++ b/a
360 @@ -1,1 +1,1 @@
360 @@ -1,1 +1,1 @@
361 -THIS-IS-LFS-single
361 -THIS-IS-LFS-single
362 +THIS-IS-LFS-two
362 +THIS-IS-LFS-two
363
363
364 1 a-single
364 1 a-single
365 diff --git a/a b/a
365 diff --git a/a b/a
366 --- a/a
366 --- a/a
367 +++ b/a
367 +++ b/a
368 @@ -1,1 +1,1 @@
368 @@ -1,1 +1,1 @@
369 -THIS-IS-LFS-0
369 -THIS-IS-LFS-0
370 +THIS-IS-LFS-single
370 +THIS-IS-LFS-single
371
371
372 0 a-0
372 0 a-0
373 diff --git a/a b/a
373 diff --git a/a b/a
374 new file mode 100644
374 new file mode 100644
375 --- /dev/null
375 --- /dev/null
376 +++ b/a
376 +++ b/a
377 @@ -0,0 +1,1 @@
377 @@ -0,0 +1,1 @@
378 +THIS-IS-LFS-0
378 +THIS-IS-LFS-0
379
379
380 $ hg bundle -R bundle.hg --base 1 bundle-again.hg -q
380 $ hg bundle -R bundle.hg --base 1 bundle-again.hg -q
381 $ hg -R bundle-again.hg log -p -T '{rev} {desc}\n' a
381 $ hg -R bundle-again.hg log -p -T '{rev} {desc}\n' a
382 5 branching
382 5 branching
383 diff --git a/a b/a
383 diff --git a/a b/a
384 --- a/a
384 --- a/a
385 +++ b/a
385 +++ b/a
386 @@ -1,1 +1,1 @@
386 @@ -1,1 +1,1 @@
387 -THIS-IS-LFS-two
387 -THIS-IS-LFS-two
388 +THIS-IS-LFS-2-CHILD
388 +THIS-IS-LFS-2-CHILD
389
389
390 4 a-4
390 4 a-4
391 diff --git a/a b/a
391 diff --git a/a b/a
392 --- a/a
392 --- a/a
393 +++ b/a
393 +++ b/a
394 @@ -1,1 +1,1 @@
394 @@ -1,1 +1,1 @@
395 -THIS-IS-LFS-three
395 -THIS-IS-LFS-three
396 +THIS-IS-LFS-4
396 +THIS-IS-LFS-4
397
397
398 3 a-three
398 3 a-three
399 diff --git a/a b/a
399 diff --git a/a b/a
400 --- a/a
400 --- a/a
401 +++ b/a
401 +++ b/a
402 @@ -1,1 +1,1 @@
402 @@ -1,1 +1,1 @@
403 -THIS-IS-LFS-two
403 -THIS-IS-LFS-two
404 +THIS-IS-LFS-three
404 +THIS-IS-LFS-three
405
405
406 2 a-two
406 2 a-two
407 diff --git a/a b/a
407 diff --git a/a b/a
408 --- a/a
408 --- a/a
409 +++ b/a
409 +++ b/a
410 @@ -1,1 +1,1 @@
410 @@ -1,1 +1,1 @@
411 -THIS-IS-LFS-single
411 -THIS-IS-LFS-single
412 +THIS-IS-LFS-two
412 +THIS-IS-LFS-two
413
413
414 1 a-single
414 1 a-single
415 diff --git a/a b/a
415 diff --git a/a b/a
416 --- a/a
416 --- a/a
417 +++ b/a
417 +++ b/a
418 @@ -1,1 +1,1 @@
418 @@ -1,1 +1,1 @@
419 -THIS-IS-LFS-0
419 -THIS-IS-LFS-0
420 +THIS-IS-LFS-single
420 +THIS-IS-LFS-single
421
421
422 0 a-0
422 0 a-0
423 diff --git a/a b/a
423 diff --git a/a b/a
424 new file mode 100644
424 new file mode 100644
425 --- /dev/null
425 --- /dev/null
426 +++ b/a
426 +++ b/a
427 @@ -0,0 +1,1 @@
427 @@ -0,0 +1,1 @@
428 +THIS-IS-LFS-0
428 +THIS-IS-LFS-0
429
429
430 $ cd ..
430 $ cd ..
431
431
432 # Test isbinary
432 # Test isbinary
433
433
434 $ hg init repo10
434 $ hg init repo10
435 $ cd repo10
435 $ cd repo10
436 $ cat >> .hg/hgrc << EOF
436 $ cat >> .hg/hgrc << EOF
437 > [extensions]
437 > [extensions]
438 > lfs=
438 > lfs=
439 > [lfs]
439 > [lfs]
440 > threshold=1
440 > threshold=1
441 > EOF
441 > EOF
442 $ $PYTHON <<'EOF'
442 $ $PYTHON <<'EOF'
443 > def write(path, content):
443 > def write(path, content):
444 > with open(path, 'wb') as f:
444 > with open(path, 'wb') as f:
445 > f.write(content)
445 > f.write(content)
446 > write('a', b'\0\0')
446 > write('a', b'\0\0')
447 > write('b', b'\1\n')
447 > write('b', b'\1\n')
448 > write('c', b'\1\n\0')
448 > write('c', b'\1\n\0')
449 > write('d', b'xx')
449 > write('d', b'xx')
450 > EOF
450 > EOF
451 $ hg add a b c d
451 $ hg add a b c d
452 $ hg diff --stat
452 $ hg diff --stat
453 a | Bin
453 a | Bin
454 b | 1 +
454 b | 1 +
455 c | Bin
455 c | Bin
456 d | 1 +
456 d | 1 +
457 4 files changed, 2 insertions(+), 0 deletions(-)
457 4 files changed, 2 insertions(+), 0 deletions(-)
458 $ hg commit -m binarytest
458 $ hg commit -m binarytest
459 $ cat > $TESTTMP/dumpbinary.py << EOF
459 $ cat > $TESTTMP/dumpbinary.py << EOF
460 > def reposetup(ui, repo):
460 > def reposetup(ui, repo):
461 > for n in 'abcd':
461 > for n in 'abcd':
462 > ui.write(('%s: binary=%s\n') % (n, repo['.'][n].isbinary()))
462 > ui.write(('%s: binary=%s\n') % (n, repo['.'][n].isbinary()))
463 > EOF
463 > EOF
464 $ hg --config extensions.dumpbinary=$TESTTMP/dumpbinary.py id --trace
464 $ hg --config extensions.dumpbinary=$TESTTMP/dumpbinary.py id --trace
465 a: binary=True
465 a: binary=True
466 b: binary=False
466 b: binary=False
467 c: binary=True
467 c: binary=True
468 d: binary=False
468 d: binary=False
469 b55353847f02 tip
469 b55353847f02 tip
470
470
471 $ cd ..
471 $ cd ..
472
472
473 # Test fctx.cmp fastpath - diff without LFS blobs
473 # Test fctx.cmp fastpath - diff without LFS blobs
474
474
475 $ hg init repo11
475 $ hg init repo11
476 $ cd repo11
476 $ cd repo11
477 $ cat >> .hg/hgrc <<EOF
477 $ cat >> .hg/hgrc <<EOF
478 > [lfs]
478 > [lfs]
479 > threshold=1
479 > threshold=1
480 > EOF
480 > EOF
481 $ cat > ../patch.diff <<EOF
481 $ cat > ../patch.diff <<EOF
482 > # HG changeset patch
482 > # HG changeset patch
483 > 2
483 > 2
484 >
484 >
485 > diff --git a/a b/a
485 > diff --git a/a b/a
486 > old mode 100644
486 > old mode 100644
487 > new mode 100755
487 > new mode 100755
488 > EOF
488 > EOF
489
489
490 $ for i in 1 2 3; do
490 $ for i in 1 2 3; do
491 > cp ../repo10/a a
491 > cp ../repo10/a a
492 > if [ $i = 3 ]; then
492 > if [ $i = 3 ]; then
493 > # make a content-only change
493 > # make a content-only change
494 > hg import -q --bypass ../patch.diff
494 > hg import -q --bypass ../patch.diff
495 > hg update -q
495 > hg update -q
496 > rm ../patch.diff
496 > rm ../patch.diff
497 > else
497 > else
498 > echo $i >> a
498 > echo $i >> a
499 > hg commit -m $i -A a
499 > hg commit -m $i -A a
500 > fi
500 > fi
501 > done
501 > done
502 $ [ -d .hg/store/lfs/objects ]
502 $ [ -d .hg/store/lfs/objects ]
503
503
504 $ cd ..
504 $ cd ..
505
505
506 $ hg clone repo11 repo12 --noupdate
506 $ hg clone repo11 repo12 --noupdate
507 $ cd repo12
507 $ cd repo12
508 $ hg log --removed -p a -T '{desc}\n' --config diff.nobinary=1 --git
508 $ hg log --removed -p a -T '{desc}\n' --config diff.nobinary=1 --git
509 2
509 2
510 diff --git a/a b/a
510 diff --git a/a b/a
511 old mode 100644
511 old mode 100644
512 new mode 100755
512 new mode 100755
513
513
514 2
514 2
515 diff --git a/a b/a
515 diff --git a/a b/a
516 Binary file a has changed
516 Binary file a has changed
517
517
518 1
518 1
519 diff --git a/a b/a
519 diff --git a/a b/a
520 new file mode 100644
520 new file mode 100644
521 Binary file a has changed
521 Binary file a has changed
522
522
523 $ [ -d .hg/store/lfs/objects ]
523 $ [ -d .hg/store/lfs/objects ]
524 [1]
524 [1]
525
525
526 $ cd ..
526 $ cd ..
527
527
528 # Verify the repos
528 # Verify the repos
529
529
530 $ cat > $TESTTMP/dumpflog.py << EOF
530 $ cat > $TESTTMP/dumpflog.py << EOF
531 > # print raw revision sizes, flags, and hashes for certain files
531 > # print raw revision sizes, flags, and hashes for certain files
532 > import hashlib
532 > import hashlib
533 > from mercurial import revlog
533 > from mercurial import revlog
534 > from mercurial.node import short
534 > from mercurial.node import short
535 > def hash(rawtext):
535 > def hash(rawtext):
536 > h = hashlib.sha512()
536 > h = hashlib.sha512()
537 > h.update(rawtext)
537 > h.update(rawtext)
538 > return h.hexdigest()[:4]
538 > return h.hexdigest()[:4]
539 > def reposetup(ui, repo):
539 > def reposetup(ui, repo):
540 > # these 2 files are interesting
540 > # these 2 files are interesting
541 > for name in ['l', 's']:
541 > for name in ['l', 's']:
542 > fl = repo.file(name)
542 > fl = repo.file(name)
543 > if len(fl) == 0:
543 > if len(fl) == 0:
544 > continue
544 > continue
545 > sizes = [revlog.revlog.rawsize(fl, i) for i in fl]
545 > sizes = [revlog.revlog.rawsize(fl, i) for i in fl]
546 > texts = [fl.revision(i, raw=True) for i in fl]
546 > texts = [fl.revision(i, raw=True) for i in fl]
547 > flags = [int(fl.flags(i)) for i in fl]
547 > flags = [int(fl.flags(i)) for i in fl]
548 > hashes = [hash(t) for t in texts]
548 > hashes = [hash(t) for t in texts]
549 > print(' %s: rawsizes=%r flags=%r hashes=%r'
549 > print(' %s: rawsizes=%r flags=%r hashes=%r'
550 > % (name, sizes, flags, hashes))
550 > % (name, sizes, flags, hashes))
551 > EOF
551 > EOF
552
552
553 $ for i in client client2 server repo3 repo4 repo5 repo6 repo7 repo8 repo9 \
553 $ for i in client client2 server repo3 repo4 repo5 repo6 repo7 repo8 repo9 \
554 > repo10; do
554 > repo10; do
555 > echo 'repo:' $i
555 > echo 'repo:' $i
556 > hg --cwd $i verify --config extensions.dumpflog=$TESTTMP/dumpflog.py -q
556 > hg --cwd $i verify --config extensions.dumpflog=$TESTTMP/dumpflog.py -q
557 > done
557 > done
558 repo: client
558 repo: client
559 repo: client2
559 repo: client2
560 repo: server
560 repo: server
561 repo: repo3
561 repo: repo3
562 l: rawsizes=[211, 6, 8, 141] flags=[8192, 0, 0, 8192] hashes=['d2b8', '948c', 'cc88', '724d']
562 l: rawsizes=[211, 6, 8, 141] flags=[8192, 0, 0, 8192] hashes=['d2b8', '948c', 'cc88', '724d']
563 s: rawsizes=[74, 141, 141, 8] flags=[0, 8192, 8192, 0] hashes=['3c80', 'fce0', '874a', '826b']
563 s: rawsizes=[74, 141, 141, 8] flags=[0, 8192, 8192, 0] hashes=['3c80', 'fce0', '874a', '826b']
564 repo: repo4
564 repo: repo4
565 l: rawsizes=[211, 6, 8, 141] flags=[8192, 0, 0, 8192] hashes=['d2b8', '948c', 'cc88', '724d']
565 l: rawsizes=[211, 6, 8, 141] flags=[8192, 0, 0, 8192] hashes=['d2b8', '948c', 'cc88', '724d']
566 s: rawsizes=[74, 141, 141, 8] flags=[0, 8192, 8192, 0] hashes=['3c80', 'fce0', '874a', '826b']
566 s: rawsizes=[74, 141, 141, 8] flags=[0, 8192, 8192, 0] hashes=['3c80', 'fce0', '874a', '826b']
567 repo: repo5
567 repo: repo5
568 l: rawsizes=[211, 6, 8, 141] flags=[8192, 0, 0, 8192] hashes=['d2b8', '948c', 'cc88', '724d']
568 l: rawsizes=[211, 6, 8, 141] flags=[8192, 0, 0, 8192] hashes=['d2b8', '948c', 'cc88', '724d']
569 s: rawsizes=[74, 141, 141, 8] flags=[0, 8192, 8192, 0] hashes=['3c80', 'fce0', '874a', '826b']
569 s: rawsizes=[74, 141, 141, 8] flags=[0, 8192, 8192, 0] hashes=['3c80', 'fce0', '874a', '826b']
570 repo: repo6
570 repo: repo6
571 repo: repo7
571 repo: repo7
572 repo: repo8
572 repo: repo8
573 repo: repo9
573 repo: repo9
574 repo: repo10
574 repo: repo10
575
575
576 repo12 doesn't have any cached lfs files and its source never pushed its
576 repo12 doesn't have any cached lfs files and its source never pushed its
577 files. Therefore, the files don't exist in the remote store. Use the files in
577 files. Therefore, the files don't exist in the remote store. Use the files in
578 the user cache.
578 the user cache.
579
579
580 $ test -d $TESTTMP/repo12/.hg/store/lfs/objects
580 $ test -d $TESTTMP/repo12/.hg/store/lfs/objects
581 [1]
581 [1]
582
582
583 $ hg --config extensions.share= share repo12 repo13
583 $ hg --config extensions.share= share repo12 repo13
584 updating working directory
584 updating working directory
585 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
585 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
586 $ hg -R repo13 -q verify
586 $ hg -R repo13 -q verify
587
587
588 $ hg clone repo12 repo14
588 $ hg clone repo12 repo14
589 updating to branch default
589 updating to branch default
590 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
590 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
591 $ hg -R repo14 -q verify
591 $ hg -R repo14 -q verify
592
592
593 If the source repo doesn't have the blob (maybe it was pulled or cloned with
593 If the source repo doesn't have the blob (maybe it was pulled or cloned with
594 --noupdate), the blob is still accessible via the global cache to send to the
594 --noupdate), the blob is still accessible via the global cache to send to the
595 remote store.
595 remote store.
596
596
597 $ rm -rf $TESTTMP/repo14/.hg/store/lfs
597 $ rm -rf $TESTTMP/repo14/.hg/store/lfs
598 $ hg init repo15
598 $ hg init repo15
599 $ hg -R repo14 push repo15
599 $ hg -R repo14 push repo15
600 pushing to repo15
600 pushing to repo15
601 searching for changes
601 searching for changes
602 adding changesets
602 adding changesets
603 adding manifests
603 adding manifests
604 adding file changes
604 adding file changes
605 added 3 changesets with 2 changes to 1 files
605 added 3 changesets with 2 changes to 1 files
606 $ hg -R repo14 -q verify
606 $ hg -R repo14 -q verify
607
607
608 Test damaged file scenarios. (This also damages the usercache because of the
608 Test damaged file scenarios. (This also damages the usercache because of the
609 hardlinks.)
609 hardlinks.)
610
610
611 $ echo 'damage' >> repo5/.hg/store/lfs/objects/66/100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e
611 $ echo 'damage' >> repo5/.hg/store/lfs/objects/66/100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e
612
612
613 Repo with damaged lfs objects in any revision will fail verification.
613 Repo with damaged lfs objects in any revision will fail verification.
614
614
615 $ hg -R repo5 verify
615 $ hg -R repo5 verify
616 checking changesets
616 checking changesets
617 checking manifests
617 checking manifests
618 crosschecking files in changesets and manifests
618 crosschecking files in changesets and manifests
619 checking files
619 checking files
620 l@1: unpacking 46a2f24864bc: integrity check failed on data/l.i:0
620 l@1: unpacking 46a2f24864bc: integrity check failed on data/l.i:0
621 large@0: unpacking 2c531e0992ff: integrity check failed on data/large.i:0
621 large@0: unpacking 2c531e0992ff: integrity check failed on data/large.i:0
622 4 files, 5 changesets, 10 total revisions
622 4 files, 5 changesets, 10 total revisions
623 2 integrity errors encountered!
623 2 integrity errors encountered!
624 (first damaged changeset appears to be 0)
624 (first damaged changeset appears to be 0)
625 [1]
625 [1]
626
626
627 Updates work after cloning a damaged repo, if the damaged lfs objects aren't in
627 Updates work after cloning a damaged repo, if the damaged lfs objects aren't in
628 the update destination. Those objects won't be added to the new repo's store
628 the update destination. Those objects won't be added to the new repo's store
629 because they aren't accessed.
629 because they aren't accessed.
630
630
631 $ hg clone -v repo5 fromcorrupt
631 $ hg clone -v repo5 fromcorrupt
632 updating to branch default
632 updating to branch default
633 resolving manifests
633 resolving manifests
634 getting l
634 getting l
635 lfs: found 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b in the usercache
635 lfs: found 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b in the usercache
636 getting s
636 getting s
637 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
637 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
638 $ test -f fromcorrupt/.hg/store/lfs/objects/66/100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e
638 $ test -f fromcorrupt/.hg/store/lfs/objects/66/100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e
639 [1]
639 [1]
640
640
641 Verify will copy/link all lfs objects into the local store that aren't already
641 Verify will copy/link all lfs objects into the local store that aren't already
642 present. Bypass the corrupted usercache to show that verify works when fed by
642 present. Bypass the corrupted usercache to show that verify works when fed by
643 the (uncorrupted) remote store.
643 the (uncorrupted) remote store.
644
644
645 $ hg -R fromcorrupt --config lfs.usercache=emptycache verify -v
645 $ hg -R fromcorrupt --config lfs.usercache=emptycache verify -v
646 repository uses revlog format 1
646 repository uses revlog format 1
647 checking changesets
647 checking changesets
648 checking manifests
648 checking manifests
649 crosschecking files in changesets and manifests
649 crosschecking files in changesets and manifests
650 checking files
650 checking files
651 lfs: adding 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e to the usercache
651 lfs: adding 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e to the usercache
652 lfs: found 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e in the local lfs store
652 lfs: found 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e in the local lfs store
653 lfs: found 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b in the local lfs store
653 lfs: found 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b in the local lfs store
654 lfs: found 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e in the local lfs store
654 lfs: found 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e in the local lfs store
655 lfs: adding 89b6070915a3d573ff3599d1cda305bc5e38549b15c4847ab034169da66e1ca8 to the usercache
655 lfs: adding 89b6070915a3d573ff3599d1cda305bc5e38549b15c4847ab034169da66e1ca8 to the usercache
656 lfs: found 89b6070915a3d573ff3599d1cda305bc5e38549b15c4847ab034169da66e1ca8 in the local lfs store
656 lfs: found 89b6070915a3d573ff3599d1cda305bc5e38549b15c4847ab034169da66e1ca8 in the local lfs store
657 lfs: adding b1a6ea88da0017a0e77db139a54618986e9a2489bee24af9fe596de9daac498c to the usercache
657 lfs: adding b1a6ea88da0017a0e77db139a54618986e9a2489bee24af9fe596de9daac498c to the usercache
658 lfs: found b1a6ea88da0017a0e77db139a54618986e9a2489bee24af9fe596de9daac498c in the local lfs store
658 lfs: found b1a6ea88da0017a0e77db139a54618986e9a2489bee24af9fe596de9daac498c in the local lfs store
659 4 files, 5 changesets, 10 total revisions
659 4 files, 5 changesets, 10 total revisions
660
660
661 BUG: Verify will copy/link a corrupted file from the usercache into the local
661 BUG: Verify will copy/link a corrupted file from the usercache into the local
662 store, and poison it. (The verify with a good remote now fails.)
662 store, and poison it. (The verify with a good remote now fails.)
663
663
664 $ rm -r fromcorrupt/.hg/store/lfs/objects/66/100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e
664 $ rm -r fromcorrupt/.hg/store/lfs/objects/66/100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e
665 $ hg -R fromcorrupt verify -v
665 $ hg -R fromcorrupt verify -v
666 repository uses revlog format 1
666 repository uses revlog format 1
667 checking changesets
667 checking changesets
668 checking manifests
668 checking manifests
669 crosschecking files in changesets and manifests
669 crosschecking files in changesets and manifests
670 checking files
670 checking files
671 lfs: found 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e in the usercache
671 lfs: found 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e in the usercache
672 l@1: unpacking 46a2f24864bc: integrity check failed on data/l.i:0
672 l@1: unpacking 46a2f24864bc: integrity check failed on data/l.i:0
673 lfs: found 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b in the local lfs store
673 lfs: found 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b in the local lfs store
674 lfs: found 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e in the local lfs store
674 lfs: found 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e in the local lfs store
675 large@0: unpacking 2c531e0992ff: integrity check failed on data/large.i:0
675 large@0: unpacking 2c531e0992ff: integrity check failed on data/large.i:0
676 lfs: found 89b6070915a3d573ff3599d1cda305bc5e38549b15c4847ab034169da66e1ca8 in the local lfs store
676 lfs: found 89b6070915a3d573ff3599d1cda305bc5e38549b15c4847ab034169da66e1ca8 in the local lfs store
677 lfs: found b1a6ea88da0017a0e77db139a54618986e9a2489bee24af9fe596de9daac498c in the local lfs store
677 lfs: found b1a6ea88da0017a0e77db139a54618986e9a2489bee24af9fe596de9daac498c in the local lfs store
678 4 files, 5 changesets, 10 total revisions
678 4 files, 5 changesets, 10 total revisions
679 2 integrity errors encountered!
679 2 integrity errors encountered!
680 (first damaged changeset appears to be 0)
680 (first damaged changeset appears to be 0)
681 [1]
681 [1]
682 $ hg -R fromcorrupt --config lfs.usercache=emptycache verify -v
682 $ hg -R fromcorrupt --config lfs.usercache=emptycache verify -v
683 repository uses revlog format 1
683 repository uses revlog format 1
684 checking changesets
684 checking changesets
685 checking manifests
685 checking manifests
686 crosschecking files in changesets and manifests
686 crosschecking files in changesets and manifests
687 checking files
687 checking files
688 lfs: found 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e in the local lfs store
688 lfs: found 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e in the local lfs store
689 l@1: unpacking 46a2f24864bc: integrity check failed on data/l.i:0
689 l@1: unpacking 46a2f24864bc: integrity check failed on data/l.i:0
690 lfs: found 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b in the local lfs store
690 lfs: found 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b in the local lfs store
691 lfs: found 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e in the local lfs store
691 lfs: found 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e in the local lfs store
692 large@0: unpacking 2c531e0992ff: integrity check failed on data/large.i:0
692 large@0: unpacking 2c531e0992ff: integrity check failed on data/large.i:0
693 lfs: found 89b6070915a3d573ff3599d1cda305bc5e38549b15c4847ab034169da66e1ca8 in the local lfs store
693 lfs: found 89b6070915a3d573ff3599d1cda305bc5e38549b15c4847ab034169da66e1ca8 in the local lfs store
694 lfs: found b1a6ea88da0017a0e77db139a54618986e9a2489bee24af9fe596de9daac498c in the local lfs store
694 lfs: found b1a6ea88da0017a0e77db139a54618986e9a2489bee24af9fe596de9daac498c in the local lfs store
695 4 files, 5 changesets, 10 total revisions
695 4 files, 5 changesets, 10 total revisions
696 2 integrity errors encountered!
696 2 integrity errors encountered!
697 (first damaged changeset appears to be 0)
697 (first damaged changeset appears to be 0)
698 [1]
698 [1]
699
699
700 Damaging a file required by the update destination fails the update.
700 Damaging a file required by the update destination fails the update.
701
701
702 $ echo 'damage' >> $TESTTMP/dummy-remote/22/f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b
702 $ echo 'damage' >> $TESTTMP/dummy-remote/22/f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b
703 $ hg --config lfs.usercache=emptycache clone -v repo5 fromcorrupt2
703 $ hg --config lfs.usercache=emptycache clone -v repo5 fromcorrupt2
704 updating to branch default
704 updating to branch default
705 resolving manifests
705 resolving manifests
706 getting l
706 getting l
707 lfs: adding 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b to the usercache
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 $ cat emptycache/22/f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b | $TESTDIR/f --sha256
714 $ test -f emptycache/22/f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b
716 sha256=40f67c7e91d554db4bc500f8f62c2e40f9f61daa5b62388e577bbae26f5396ff
715 [1]
717 $ cat fromcorrupt2/.hg/store/lfs/objects/22/f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b | $TESTDIR/f --sha256
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
722 checking manifests
721 checking manifests
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 3 integrity errors encountered!
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 BUG: push will happily send corrupt files upstream. (The alternate dummy remote
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
737 $ hg init dest
735 $ hg init dest
738 $ hg -R fromcorrupt2 --config lfs.url=file:///$TESTTMP/dummy-remote2 push -v dest
736 $ hg -R fromcorrupt2 --config lfs.url=file:///$TESTTMP/dummy-remote2 push -v dest
739 pushing to dest
737 pushing to dest
740 searching for changes
738 searching for changes
741 lfs: found 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b in the local lfs store
739 lfs: found 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b in the local lfs store
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
760 checking changesets
749 checking changesets
761 checking manifests
750 checking manifests
762 crosschecking files in changesets and manifests
751 crosschecking files in changesets and manifests
763 checking files
752 checking files
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 3 integrity errors encountered!
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
791 $ hg --cwd fromcorrupt2 cat -r 0 large
774 $ hg --cwd fromcorrupt2 cat -r 0 large
792 abort: integrity check failed on data/large.i:0!
775 abort: integrity check failed on data/large.i:0!
793 [255]
776 [255]
794
777
795 lfs -> normal -> lfs round trip conversions are possible. The threshold for the
778 lfs -> normal -> lfs round trip conversions are possible. The threshold for the
796 lfs destination is specified here because it was originally listed in the local
779 lfs destination is specified here because it was originally listed in the local
797 .hgrc, and the global one is too high to trigger lfs usage. For lfs -> normal,
780 .hgrc, and the global one is too high to trigger lfs usage. For lfs -> normal,
798 there's no 'lfs' destination repo requirement. For normal -> lfs, there is.
781 there's no 'lfs' destination repo requirement. For normal -> lfs, there is.
799
782
800 XXX: There's not a great way to ensure that the conversion to normal files
783 XXX: There's not a great way to ensure that the conversion to normal files
801 actually converts _everything_ to normal. The extension needs to be loaded for
784 actually converts _everything_ to normal. The extension needs to be loaded for
802 the source, but there's no way to disable it for the destination. The best that
785 the source, but there's no way to disable it for the destination. The best that
803 can be done is to raise the threshold so that lfs isn't used on the destination.
786 can be done is to raise the threshold so that lfs isn't used on the destination.
804 It doesn't like using '!' to unset the value on the command line.
787 It doesn't like using '!' to unset the value on the command line.
805
788
806 $ hg --config extensions.convert= --config lfs.threshold=1000M \
789 $ hg --config extensions.convert= --config lfs.threshold=1000M \
807 > convert repo8 convert_normal
790 > convert repo8 convert_normal
808 initializing destination convert_normal repository
791 initializing destination convert_normal repository
809 scanning source...
792 scanning source...
810 sorting...
793 sorting...
811 converting...
794 converting...
812 2 a
795 2 a
813 1 b
796 1 b
814 0 meta
797 0 meta
815 $ grep 'lfs' convert_normal/.hg/requires
798 $ grep 'lfs' convert_normal/.hg/requires
816 [1]
799 [1]
817 $ hg --cwd convert_normal debugdata a1 0
800 $ hg --cwd convert_normal debugdata a1 0
818 THIS-IS-LFS-BECAUSE-10-BYTES
801 THIS-IS-LFS-BECAUSE-10-BYTES
819
802
820 $ hg --config extensions.convert= --config lfs.threshold=10B \
803 $ hg --config extensions.convert= --config lfs.threshold=10B \
821 > convert convert_normal convert_lfs
804 > convert convert_normal convert_lfs
822 initializing destination convert_lfs repository
805 initializing destination convert_lfs repository
823 scanning source...
806 scanning source...
824 sorting...
807 sorting...
825 converting...
808 converting...
826 2 a
809 2 a
827 1 b
810 1 b
828 0 meta
811 0 meta
829 $ hg --cwd convert_lfs debugdata a1 0
812 $ hg --cwd convert_lfs debugdata a1 0
830 version https://git-lfs.github.com/spec/v1
813 version https://git-lfs.github.com/spec/v1
831 oid sha256:5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
814 oid sha256:5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
832 size 29
815 size 29
833 x-is-binary 0
816 x-is-binary 0
834 $ grep 'lfs' convert_lfs/.hg/requires
817 $ grep 'lfs' convert_lfs/.hg/requires
835 lfs
818 lfs
836
819
837 This convert is trickier, because it contains deleted files (via `hg mv`)
820 This convert is trickier, because it contains deleted files (via `hg mv`)
838
821
839 $ hg --config extensions.convert= --config lfs.threshold=1000M \
822 $ hg --config extensions.convert= --config lfs.threshold=1000M \
840 > convert repo3 convert_normal2
823 > convert repo3 convert_normal2
841 initializing destination convert_normal2 repository
824 initializing destination convert_normal2 repository
842 scanning source...
825 scanning source...
843 sorting...
826 sorting...
844 converting...
827 converting...
845 4 commit with lfs content
828 4 commit with lfs content
846 3 renames
829 3 renames
847 2 large to small, small to large
830 2 large to small, small to large
848 1 random modifications
831 1 random modifications
849 0 switch large and small again
832 0 switch large and small again
850 $ grep 'lfs' convert_normal2/.hg/requires
833 $ grep 'lfs' convert_normal2/.hg/requires
851 [1]
834 [1]
852 $ hg --cwd convert_normal2 debugdata large 0
835 $ hg --cwd convert_normal2 debugdata large 0
853 LONGER-THAN-TEN-BYTES-WILL-TRIGGER-LFS
836 LONGER-THAN-TEN-BYTES-WILL-TRIGGER-LFS
854
837
855 $ hg --config extensions.convert= --config lfs.threshold=10B \
838 $ hg --config extensions.convert= --config lfs.threshold=10B \
856 > convert convert_normal2 convert_lfs2
839 > convert convert_normal2 convert_lfs2
857 initializing destination convert_lfs2 repository
840 initializing destination convert_lfs2 repository
858 scanning source...
841 scanning source...
859 sorting...
842 sorting...
860 converting...
843 converting...
861 4 commit with lfs content
844 4 commit with lfs content
862 3 renames
845 3 renames
863 2 large to small, small to large
846 2 large to small, small to large
864 1 random modifications
847 1 random modifications
865 0 switch large and small again
848 0 switch large and small again
866 $ grep 'lfs' convert_lfs2/.hg/requires
849 $ grep 'lfs' convert_lfs2/.hg/requires
867 lfs
850 lfs
868 $ hg --cwd convert_lfs2 debugdata large 0
851 $ hg --cwd convert_lfs2 debugdata large 0
869 version https://git-lfs.github.com/spec/v1
852 version https://git-lfs.github.com/spec/v1
870 oid sha256:66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e
853 oid sha256:66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e
871 size 39
854 size 39
872 x-is-binary 0
855 x-is-binary 0
873
856
874 $ hg -R convert_lfs2 config --debug extensions | grep lfs
857 $ hg -R convert_lfs2 config --debug extensions | grep lfs
875 $TESTTMP/convert_lfs2/.hg/hgrc:*: extensions.lfs= (glob)
858 $TESTTMP/convert_lfs2/.hg/hgrc:*: extensions.lfs= (glob)
876
859
877 Committing deleted files works:
860 Committing deleted files works:
878
861
879 $ hg init $TESTTMP/repo-del
862 $ hg init $TESTTMP/repo-del
880 $ cd $TESTTMP/repo-del
863 $ cd $TESTTMP/repo-del
881 $ echo 1 > A
864 $ echo 1 > A
882 $ hg commit -m 'add A' -A A
865 $ hg commit -m 'add A' -A A
883 $ hg rm A
866 $ hg rm A
884 $ hg commit -m 'rm A'
867 $ hg commit -m 'rm A'
General Comments 0
You need to be logged in to leave comments. Login now