##// END OF EJS Templates
largefiles: fix a traceback in lfconvert if a largefile is missing (issue3519)...
Matt Harbison -
r17823:0fc1ce27 default
parent child Browse files
Show More
@@ -1,553 +1,563
1 # Copyright 2009-2010 Gregory P. Ward
1 # Copyright 2009-2010 Gregory P. Ward
2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
3 # Copyright 2010-2011 Fog Creek Software
3 # Copyright 2010-2011 Fog Creek Software
4 # Copyright 2010-2011 Unity Technologies
4 # Copyright 2010-2011 Unity Technologies
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 '''High-level command function for lfconvert, plus the cmdtable.'''
9 '''High-level command function for lfconvert, plus the cmdtable.'''
10
10
11 import os
11 import os
12 import shutil
12 import shutil
13
13
14 from mercurial import util, match as match_, hg, node, context, error, \
14 from mercurial import util, match as match_, hg, node, context, error, \
15 cmdutil, scmutil, commands
15 cmdutil, scmutil, commands
16 from mercurial.i18n import _
16 from mercurial.i18n import _
17 from mercurial.lock import release
17 from mercurial.lock import release
18
18
19 import lfutil
19 import lfutil
20 import basestore
20 import basestore
21
21
22 # -- Commands ----------------------------------------------------------
22 # -- Commands ----------------------------------------------------------
23
23
24 def lfconvert(ui, src, dest, *pats, **opts):
24 def lfconvert(ui, src, dest, *pats, **opts):
25 '''convert a normal repository to a largefiles repository
25 '''convert a normal repository to a largefiles repository
26
26
27 Convert repository SOURCE to a new repository DEST, identical to
27 Convert repository SOURCE to a new repository DEST, identical to
28 SOURCE except that certain files will be converted as largefiles:
28 SOURCE except that certain files will be converted as largefiles:
29 specifically, any file that matches any PATTERN *or* whose size is
29 specifically, any file that matches any PATTERN *or* whose size is
30 above the minimum size threshold is converted as a largefile. The
30 above the minimum size threshold is converted as a largefile. The
31 size used to determine whether or not to track a file as a
31 size used to determine whether or not to track a file as a
32 largefile is the size of the first version of the file. The
32 largefile is the size of the first version of the file. The
33 minimum size can be specified either with --size or in
33 minimum size can be specified either with --size or in
34 configuration as ``largefiles.size``.
34 configuration as ``largefiles.size``.
35
35
36 After running this command you will need to make sure that
36 After running this command you will need to make sure that
37 largefiles is enabled anywhere you intend to push the new
37 largefiles is enabled anywhere you intend to push the new
38 repository.
38 repository.
39
39
40 Use --to-normal to convert largefiles back to normal files; after
40 Use --to-normal to convert largefiles back to normal files; after
41 this, the DEST repository can be used without largefiles at all.'''
41 this, the DEST repository can be used without largefiles at all.'''
42
42
43 if opts['to_normal']:
43 if opts['to_normal']:
44 tolfile = False
44 tolfile = False
45 else:
45 else:
46 tolfile = True
46 tolfile = True
47 size = lfutil.getminsize(ui, True, opts.get('size'), default=None)
47 size = lfutil.getminsize(ui, True, opts.get('size'), default=None)
48
48
49 if not hg.islocal(src):
49 if not hg.islocal(src):
50 raise util.Abort(_('%s is not a local Mercurial repo') % src)
50 raise util.Abort(_('%s is not a local Mercurial repo') % src)
51 if not hg.islocal(dest):
51 if not hg.islocal(dest):
52 raise util.Abort(_('%s is not a local Mercurial repo') % dest)
52 raise util.Abort(_('%s is not a local Mercurial repo') % dest)
53
53
54 rsrc = hg.repository(ui, src)
54 rsrc = hg.repository(ui, src)
55 ui.status(_('initializing destination %s\n') % dest)
55 ui.status(_('initializing destination %s\n') % dest)
56 rdst = hg.repository(ui, dest, create=True)
56 rdst = hg.repository(ui, dest, create=True)
57
57
58 success = False
58 success = False
59 dstwlock = dstlock = None
59 dstwlock = dstlock = None
60 try:
60 try:
61 # Lock destination to prevent modification while it is converted to.
61 # Lock destination to prevent modification while it is converted to.
62 # Don't need to lock src because we are just reading from its history
62 # Don't need to lock src because we are just reading from its history
63 # which can't change.
63 # which can't change.
64 dstwlock = rdst.wlock()
64 dstwlock = rdst.wlock()
65 dstlock = rdst.lock()
65 dstlock = rdst.lock()
66
66
67 # Get a list of all changesets in the source. The easy way to do this
67 # Get a list of all changesets in the source. The easy way to do this
68 # is to simply walk the changelog, using changelog.nodesbetween().
68 # is to simply walk the changelog, using changelog.nodesbetween().
69 # Take a look at mercurial/revlog.py:639 for more details.
69 # Take a look at mercurial/revlog.py:639 for more details.
70 # Use a generator instead of a list to decrease memory usage
70 # Use a generator instead of a list to decrease memory usage
71 ctxs = (rsrc[ctx] for ctx in rsrc.changelog.nodesbetween(None,
71 ctxs = (rsrc[ctx] for ctx in rsrc.changelog.nodesbetween(None,
72 rsrc.heads())[0])
72 rsrc.heads())[0])
73 revmap = {node.nullid: node.nullid}
73 revmap = {node.nullid: node.nullid}
74 if tolfile:
74 if tolfile:
75 lfiles = set()
75 lfiles = set()
76 normalfiles = set()
76 normalfiles = set()
77 if not pats:
77 if not pats:
78 pats = ui.configlist(lfutil.longname, 'patterns', default=[])
78 pats = ui.configlist(lfutil.longname, 'patterns', default=[])
79 if pats:
79 if pats:
80 matcher = match_.match(rsrc.root, '', list(pats))
80 matcher = match_.match(rsrc.root, '', list(pats))
81 else:
81 else:
82 matcher = None
82 matcher = None
83
83
84 lfiletohash = {}
84 lfiletohash = {}
85 for ctx in ctxs:
85 for ctx in ctxs:
86 ui.progress(_('converting revisions'), ctx.rev(),
86 ui.progress(_('converting revisions'), ctx.rev(),
87 unit=_('revision'), total=rsrc['tip'].rev())
87 unit=_('revision'), total=rsrc['tip'].rev())
88 _lfconvert_addchangeset(rsrc, rdst, ctx, revmap,
88 _lfconvert_addchangeset(rsrc, rdst, ctx, revmap,
89 lfiles, normalfiles, matcher, size, lfiletohash)
89 lfiles, normalfiles, matcher, size, lfiletohash)
90 ui.progress(_('converting revisions'), None)
90 ui.progress(_('converting revisions'), None)
91
91
92 if os.path.exists(rdst.wjoin(lfutil.shortname)):
92 if os.path.exists(rdst.wjoin(lfutil.shortname)):
93 shutil.rmtree(rdst.wjoin(lfutil.shortname))
93 shutil.rmtree(rdst.wjoin(lfutil.shortname))
94
94
95 for f in lfiletohash.keys():
95 for f in lfiletohash.keys():
96 if os.path.isfile(rdst.wjoin(f)):
96 if os.path.isfile(rdst.wjoin(f)):
97 os.unlink(rdst.wjoin(f))
97 os.unlink(rdst.wjoin(f))
98 try:
98 try:
99 os.removedirs(os.path.dirname(rdst.wjoin(f)))
99 os.removedirs(os.path.dirname(rdst.wjoin(f)))
100 except OSError:
100 except OSError:
101 pass
101 pass
102
102
103 # If there were any files converted to largefiles, add largefiles
103 # If there were any files converted to largefiles, add largefiles
104 # to the destination repository's requirements.
104 # to the destination repository's requirements.
105 if lfiles:
105 if lfiles:
106 rdst.requirements.add('largefiles')
106 rdst.requirements.add('largefiles')
107 rdst._writerequirements()
107 rdst._writerequirements()
108 else:
108 else:
109 for ctx in ctxs:
109 for ctx in ctxs:
110 ui.progress(_('converting revisions'), ctx.rev(),
110 ui.progress(_('converting revisions'), ctx.rev(),
111 unit=_('revision'), total=rsrc['tip'].rev())
111 unit=_('revision'), total=rsrc['tip'].rev())
112 _addchangeset(ui, rsrc, rdst, ctx, revmap)
112 _addchangeset(ui, rsrc, rdst, ctx, revmap)
113
113
114 ui.progress(_('converting revisions'), None)
114 ui.progress(_('converting revisions'), None)
115 success = True
115 success = True
116 finally:
116 finally:
117 rdst.dirstate.clear()
117 rdst.dirstate.clear()
118 release(dstlock, dstwlock)
118 release(dstlock, dstwlock)
119 if not success:
119 if not success:
120 # we failed, remove the new directory
120 # we failed, remove the new directory
121 shutil.rmtree(rdst.root)
121 shutil.rmtree(rdst.root)
122
122
123 def _addchangeset(ui, rsrc, rdst, ctx, revmap):
123 def _addchangeset(ui, rsrc, rdst, ctx, revmap):
124 # Convert src parents to dst parents
124 # Convert src parents to dst parents
125 parents = _convertparents(ctx, revmap)
125 parents = _convertparents(ctx, revmap)
126
126
127 # Generate list of changed files
127 # Generate list of changed files
128 files = _getchangedfiles(ctx, parents)
128 files = _getchangedfiles(ctx, parents)
129
129
130 def getfilectx(repo, memctx, f):
130 def getfilectx(repo, memctx, f):
131 if lfutil.standin(f) in files:
131 if lfutil.standin(f) in files:
132 # if the file isn't in the manifest then it was removed
132 # if the file isn't in the manifest then it was removed
133 # or renamed, raise IOError to indicate this
133 # or renamed, raise IOError to indicate this
134 try:
134 try:
135 fctx = ctx.filectx(lfutil.standin(f))
135 fctx = ctx.filectx(lfutil.standin(f))
136 except error.LookupError:
136 except error.LookupError:
137 raise IOError
137 raise IOError
138 renamed = fctx.renamed()
138 renamed = fctx.renamed()
139 if renamed:
139 if renamed:
140 renamed = lfutil.splitstandin(renamed[0])
140 renamed = lfutil.splitstandin(renamed[0])
141
141
142 hash = fctx.data().strip()
142 hash = fctx.data().strip()
143 path = lfutil.findfile(rsrc, hash)
143 path = lfutil.findfile(rsrc, hash)
144 ### TODO: What if the file is not cached?
144
145 # If one file is missing, likely all files from this rev are
146 if path is None:
147 cachelfiles(ui, rsrc, ctx.node())
148 path = lfutil.findfile(rsrc, hash)
149
150 if path is None:
151 raise util.Abort(
152 _("missing largefile \'%s\' from revision %s")
153 % (f, node.hex(ctx.node())))
154
145 data = ''
155 data = ''
146 fd = None
156 fd = None
147 try:
157 try:
148 fd = open(path, 'rb')
158 fd = open(path, 'rb')
149 data = fd.read()
159 data = fd.read()
150 finally:
160 finally:
151 if fd:
161 if fd:
152 fd.close()
162 fd.close()
153 return context.memfilectx(f, data, 'l' in fctx.flags(),
163 return context.memfilectx(f, data, 'l' in fctx.flags(),
154 'x' in fctx.flags(), renamed)
164 'x' in fctx.flags(), renamed)
155 else:
165 else:
156 return _getnormalcontext(repo.ui, ctx, f, revmap)
166 return _getnormalcontext(repo.ui, ctx, f, revmap)
157
167
158 dstfiles = []
168 dstfiles = []
159 for file in files:
169 for file in files:
160 if lfutil.isstandin(file):
170 if lfutil.isstandin(file):
161 dstfiles.append(lfutil.splitstandin(file))
171 dstfiles.append(lfutil.splitstandin(file))
162 else:
172 else:
163 dstfiles.append(file)
173 dstfiles.append(file)
164 # Commit
174 # Commit
165 _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap)
175 _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap)
166
176
167 def _lfconvert_addchangeset(rsrc, rdst, ctx, revmap, lfiles, normalfiles,
177 def _lfconvert_addchangeset(rsrc, rdst, ctx, revmap, lfiles, normalfiles,
168 matcher, size, lfiletohash):
178 matcher, size, lfiletohash):
169 # Convert src parents to dst parents
179 # Convert src parents to dst parents
170 parents = _convertparents(ctx, revmap)
180 parents = _convertparents(ctx, revmap)
171
181
172 # Generate list of changed files
182 # Generate list of changed files
173 files = _getchangedfiles(ctx, parents)
183 files = _getchangedfiles(ctx, parents)
174
184
175 dstfiles = []
185 dstfiles = []
176 for f in files:
186 for f in files:
177 if f not in lfiles and f not in normalfiles:
187 if f not in lfiles and f not in normalfiles:
178 islfile = _islfile(f, ctx, matcher, size)
188 islfile = _islfile(f, ctx, matcher, size)
179 # If this file was renamed or copied then copy
189 # If this file was renamed or copied then copy
180 # the largefile-ness of its predecessor
190 # the largefile-ness of its predecessor
181 if f in ctx.manifest():
191 if f in ctx.manifest():
182 fctx = ctx.filectx(f)
192 fctx = ctx.filectx(f)
183 renamed = fctx.renamed()
193 renamed = fctx.renamed()
184 renamedlfile = renamed and renamed[0] in lfiles
194 renamedlfile = renamed and renamed[0] in lfiles
185 islfile |= renamedlfile
195 islfile |= renamedlfile
186 if 'l' in fctx.flags():
196 if 'l' in fctx.flags():
187 if renamedlfile:
197 if renamedlfile:
188 raise util.Abort(
198 raise util.Abort(
189 _('renamed/copied largefile %s becomes symlink')
199 _('renamed/copied largefile %s becomes symlink')
190 % f)
200 % f)
191 islfile = False
201 islfile = False
192 if islfile:
202 if islfile:
193 lfiles.add(f)
203 lfiles.add(f)
194 else:
204 else:
195 normalfiles.add(f)
205 normalfiles.add(f)
196
206
197 if f in lfiles:
207 if f in lfiles:
198 dstfiles.append(lfutil.standin(f))
208 dstfiles.append(lfutil.standin(f))
199 # largefile in manifest if it has not been removed/renamed
209 # largefile in manifest if it has not been removed/renamed
200 if f in ctx.manifest():
210 if f in ctx.manifest():
201 fctx = ctx.filectx(f)
211 fctx = ctx.filectx(f)
202 if 'l' in fctx.flags():
212 if 'l' in fctx.flags():
203 renamed = fctx.renamed()
213 renamed = fctx.renamed()
204 if renamed and renamed[0] in lfiles:
214 if renamed and renamed[0] in lfiles:
205 raise util.Abort(_('largefile %s becomes symlink') % f)
215 raise util.Abort(_('largefile %s becomes symlink') % f)
206
216
207 # largefile was modified, update standins
217 # largefile was modified, update standins
208 fullpath = rdst.wjoin(f)
218 fullpath = rdst.wjoin(f)
209 util.makedirs(os.path.dirname(fullpath))
219 util.makedirs(os.path.dirname(fullpath))
210 m = util.sha1('')
220 m = util.sha1('')
211 m.update(ctx[f].data())
221 m.update(ctx[f].data())
212 hash = m.hexdigest()
222 hash = m.hexdigest()
213 if f not in lfiletohash or lfiletohash[f] != hash:
223 if f not in lfiletohash or lfiletohash[f] != hash:
214 try:
224 try:
215 fd = open(fullpath, 'wb')
225 fd = open(fullpath, 'wb')
216 fd.write(ctx[f].data())
226 fd.write(ctx[f].data())
217 finally:
227 finally:
218 if fd:
228 if fd:
219 fd.close()
229 fd.close()
220 executable = 'x' in ctx[f].flags()
230 executable = 'x' in ctx[f].flags()
221 os.chmod(fullpath, lfutil.getmode(executable))
231 os.chmod(fullpath, lfutil.getmode(executable))
222 lfutil.writestandin(rdst, lfutil.standin(f), hash,
232 lfutil.writestandin(rdst, lfutil.standin(f), hash,
223 executable)
233 executable)
224 lfiletohash[f] = hash
234 lfiletohash[f] = hash
225 else:
235 else:
226 # normal file
236 # normal file
227 dstfiles.append(f)
237 dstfiles.append(f)
228
238
229 def getfilectx(repo, memctx, f):
239 def getfilectx(repo, memctx, f):
230 if lfutil.isstandin(f):
240 if lfutil.isstandin(f):
231 # if the file isn't in the manifest then it was removed
241 # if the file isn't in the manifest then it was removed
232 # or renamed, raise IOError to indicate this
242 # or renamed, raise IOError to indicate this
233 srcfname = lfutil.splitstandin(f)
243 srcfname = lfutil.splitstandin(f)
234 try:
244 try:
235 fctx = ctx.filectx(srcfname)
245 fctx = ctx.filectx(srcfname)
236 except error.LookupError:
246 except error.LookupError:
237 raise IOError
247 raise IOError
238 renamed = fctx.renamed()
248 renamed = fctx.renamed()
239 if renamed:
249 if renamed:
240 # standin is always a largefile because largefile-ness
250 # standin is always a largefile because largefile-ness
241 # doesn't change after rename or copy
251 # doesn't change after rename or copy
242 renamed = lfutil.standin(renamed[0])
252 renamed = lfutil.standin(renamed[0])
243
253
244 return context.memfilectx(f, lfiletohash[srcfname] + '\n', 'l' in
254 return context.memfilectx(f, lfiletohash[srcfname] + '\n', 'l' in
245 fctx.flags(), 'x' in fctx.flags(), renamed)
255 fctx.flags(), 'x' in fctx.flags(), renamed)
246 else:
256 else:
247 return _getnormalcontext(repo.ui, ctx, f, revmap)
257 return _getnormalcontext(repo.ui, ctx, f, revmap)
248
258
249 # Commit
259 # Commit
250 _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap)
260 _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap)
251
261
252 def _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap):
262 def _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap):
253 mctx = context.memctx(rdst, parents, ctx.description(), dstfiles,
263 mctx = context.memctx(rdst, parents, ctx.description(), dstfiles,
254 getfilectx, ctx.user(), ctx.date(), ctx.extra())
264 getfilectx, ctx.user(), ctx.date(), ctx.extra())
255 ret = rdst.commitctx(mctx)
265 ret = rdst.commitctx(mctx)
256 rdst.setparents(ret)
266 rdst.setparents(ret)
257 revmap[ctx.node()] = rdst.changelog.tip()
267 revmap[ctx.node()] = rdst.changelog.tip()
258
268
259 # Generate list of changed files
269 # Generate list of changed files
260 def _getchangedfiles(ctx, parents):
270 def _getchangedfiles(ctx, parents):
261 files = set(ctx.files())
271 files = set(ctx.files())
262 if node.nullid not in parents:
272 if node.nullid not in parents:
263 mc = ctx.manifest()
273 mc = ctx.manifest()
264 mp1 = ctx.parents()[0].manifest()
274 mp1 = ctx.parents()[0].manifest()
265 mp2 = ctx.parents()[1].manifest()
275 mp2 = ctx.parents()[1].manifest()
266 files |= (set(mp1) | set(mp2)) - set(mc)
276 files |= (set(mp1) | set(mp2)) - set(mc)
267 for f in mc:
277 for f in mc:
268 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
278 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
269 files.add(f)
279 files.add(f)
270 return files
280 return files
271
281
272 # Convert src parents to dst parents
282 # Convert src parents to dst parents
273 def _convertparents(ctx, revmap):
283 def _convertparents(ctx, revmap):
274 parents = []
284 parents = []
275 for p in ctx.parents():
285 for p in ctx.parents():
276 parents.append(revmap[p.node()])
286 parents.append(revmap[p.node()])
277 while len(parents) < 2:
287 while len(parents) < 2:
278 parents.append(node.nullid)
288 parents.append(node.nullid)
279 return parents
289 return parents
280
290
281 # Get memfilectx for a normal file
291 # Get memfilectx for a normal file
282 def _getnormalcontext(ui, ctx, f, revmap):
292 def _getnormalcontext(ui, ctx, f, revmap):
283 try:
293 try:
284 fctx = ctx.filectx(f)
294 fctx = ctx.filectx(f)
285 except error.LookupError:
295 except error.LookupError:
286 raise IOError
296 raise IOError
287 renamed = fctx.renamed()
297 renamed = fctx.renamed()
288 if renamed:
298 if renamed:
289 renamed = renamed[0]
299 renamed = renamed[0]
290
300
291 data = fctx.data()
301 data = fctx.data()
292 if f == '.hgtags':
302 if f == '.hgtags':
293 data = _converttags (ui, revmap, data)
303 data = _converttags (ui, revmap, data)
294 return context.memfilectx(f, data, 'l' in fctx.flags(),
304 return context.memfilectx(f, data, 'l' in fctx.flags(),
295 'x' in fctx.flags(), renamed)
305 'x' in fctx.flags(), renamed)
296
306
297 # Remap tag data using a revision map
307 # Remap tag data using a revision map
298 def _converttags(ui, revmap, data):
308 def _converttags(ui, revmap, data):
299 newdata = []
309 newdata = []
300 for line in data.splitlines():
310 for line in data.splitlines():
301 try:
311 try:
302 id, name = line.split(' ', 1)
312 id, name = line.split(' ', 1)
303 except ValueError:
313 except ValueError:
304 ui.warn(_('skipping incorrectly formatted tag %s\n'
314 ui.warn(_('skipping incorrectly formatted tag %s\n'
305 % line))
315 % line))
306 continue
316 continue
307 try:
317 try:
308 newid = node.bin(id)
318 newid = node.bin(id)
309 except TypeError:
319 except TypeError:
310 ui.warn(_('skipping incorrectly formatted id %s\n'
320 ui.warn(_('skipping incorrectly formatted id %s\n'
311 % id))
321 % id))
312 continue
322 continue
313 try:
323 try:
314 newdata.append('%s %s\n' % (node.hex(revmap[newid]),
324 newdata.append('%s %s\n' % (node.hex(revmap[newid]),
315 name))
325 name))
316 except KeyError:
326 except KeyError:
317 ui.warn(_('no mapping for id %s\n') % id)
327 ui.warn(_('no mapping for id %s\n') % id)
318 continue
328 continue
319 return ''.join(newdata)
329 return ''.join(newdata)
320
330
321 def _islfile(file, ctx, matcher, size):
331 def _islfile(file, ctx, matcher, size):
322 '''Return true if file should be considered a largefile, i.e.
332 '''Return true if file should be considered a largefile, i.e.
323 matcher matches it or it is larger than size.'''
333 matcher matches it or it is larger than size.'''
324 # never store special .hg* files as largefiles
334 # never store special .hg* files as largefiles
325 if file == '.hgtags' or file == '.hgignore' or file == '.hgsigs':
335 if file == '.hgtags' or file == '.hgignore' or file == '.hgsigs':
326 return False
336 return False
327 if matcher and matcher(file):
337 if matcher and matcher(file):
328 return True
338 return True
329 try:
339 try:
330 return ctx.filectx(file).size() >= size * 1024 * 1024
340 return ctx.filectx(file).size() >= size * 1024 * 1024
331 except error.LookupError:
341 except error.LookupError:
332 return False
342 return False
333
343
334 def uploadlfiles(ui, rsrc, rdst, files):
344 def uploadlfiles(ui, rsrc, rdst, files):
335 '''upload largefiles to the central store'''
345 '''upload largefiles to the central store'''
336
346
337 if not files:
347 if not files:
338 return
348 return
339
349
340 store = basestore._openstore(rsrc, rdst, put=True)
350 store = basestore._openstore(rsrc, rdst, put=True)
341
351
342 at = 0
352 at = 0
343 ui.debug("sending statlfile command for %d largefiles\n" % len(files))
353 ui.debug("sending statlfile command for %d largefiles\n" % len(files))
344 retval = store.exists(files)
354 retval = store.exists(files)
345 files = filter(lambda h: not retval[h], files)
355 files = filter(lambda h: not retval[h], files)
346 ui.debug("%d largefiles need to be uploaded\n" % len(files))
356 ui.debug("%d largefiles need to be uploaded\n" % len(files))
347
357
348 for hash in files:
358 for hash in files:
349 ui.progress(_('uploading largefiles'), at, unit='largefile',
359 ui.progress(_('uploading largefiles'), at, unit='largefile',
350 total=len(files))
360 total=len(files))
351 source = lfutil.findfile(rsrc, hash)
361 source = lfutil.findfile(rsrc, hash)
352 if not source:
362 if not source:
353 raise util.Abort(_('largefile %s missing from store'
363 raise util.Abort(_('largefile %s missing from store'
354 ' (needs to be uploaded)') % hash)
364 ' (needs to be uploaded)') % hash)
355 # XXX check for errors here
365 # XXX check for errors here
356 store.put(source, hash)
366 store.put(source, hash)
357 at += 1
367 at += 1
358 ui.progress(_('uploading largefiles'), None)
368 ui.progress(_('uploading largefiles'), None)
359
369
360 def verifylfiles(ui, repo, all=False, contents=False):
370 def verifylfiles(ui, repo, all=False, contents=False):
361 '''Verify that every big file revision in the current changeset
371 '''Verify that every big file revision in the current changeset
362 exists in the central store. With --contents, also verify that
372 exists in the central store. With --contents, also verify that
363 the contents of each big file revision are correct (SHA-1 hash
373 the contents of each big file revision are correct (SHA-1 hash
364 matches the revision ID). With --all, check every changeset in
374 matches the revision ID). With --all, check every changeset in
365 this repository.'''
375 this repository.'''
366 if all:
376 if all:
367 # Pass a list to the function rather than an iterator because we know a
377 # Pass a list to the function rather than an iterator because we know a
368 # list will work.
378 # list will work.
369 revs = range(len(repo))
379 revs = range(len(repo))
370 else:
380 else:
371 revs = ['.']
381 revs = ['.']
372
382
373 store = basestore._openstore(repo)
383 store = basestore._openstore(repo)
374 return store.verify(revs, contents=contents)
384 return store.verify(revs, contents=contents)
375
385
376 def cachelfiles(ui, repo, node, filelist=None):
386 def cachelfiles(ui, repo, node, filelist=None):
377 '''cachelfiles ensures that all largefiles needed by the specified revision
387 '''cachelfiles ensures that all largefiles needed by the specified revision
378 are present in the repository's largefile cache.
388 are present in the repository's largefile cache.
379
389
380 returns a tuple (cached, missing). cached is the list of files downloaded
390 returns a tuple (cached, missing). cached is the list of files downloaded
381 by this operation; missing is the list of files that were needed but could
391 by this operation; missing is the list of files that were needed but could
382 not be found.'''
392 not be found.'''
383 lfiles = lfutil.listlfiles(repo, node)
393 lfiles = lfutil.listlfiles(repo, node)
384 if filelist:
394 if filelist:
385 lfiles = set(lfiles) & set(filelist)
395 lfiles = set(lfiles) & set(filelist)
386 toget = []
396 toget = []
387
397
388 for lfile in lfiles:
398 for lfile in lfiles:
389 # If we are mid-merge, then we have to trust the standin that is in the
399 # If we are mid-merge, then we have to trust the standin that is in the
390 # working copy to have the correct hashvalue. This is because the
400 # working copy to have the correct hashvalue. This is because the
391 # original hg.merge() already updated the standin as part of the normal
401 # original hg.merge() already updated the standin as part of the normal
392 # merge process -- we just have to update the largefile to match.
402 # merge process -- we just have to update the largefile to match.
393 if (getattr(repo, "_ismerging", False) and
403 if (getattr(repo, "_ismerging", False) and
394 os.path.exists(repo.wjoin(lfutil.standin(lfile)))):
404 os.path.exists(repo.wjoin(lfutil.standin(lfile)))):
395 expectedhash = lfutil.readstandin(repo, lfile)
405 expectedhash = lfutil.readstandin(repo, lfile)
396 else:
406 else:
397 expectedhash = repo[node][lfutil.standin(lfile)].data().strip()
407 expectedhash = repo[node][lfutil.standin(lfile)].data().strip()
398
408
399 # if it exists and its hash matches, it might have been locally
409 # if it exists and its hash matches, it might have been locally
400 # modified before updating and the user chose 'local'. in this case,
410 # modified before updating and the user chose 'local'. in this case,
401 # it will not be in any store, so don't look for it.
411 # it will not be in any store, so don't look for it.
402 if ((not os.path.exists(repo.wjoin(lfile)) or
412 if ((not os.path.exists(repo.wjoin(lfile)) or
403 expectedhash != lfutil.hashfile(repo.wjoin(lfile))) and
413 expectedhash != lfutil.hashfile(repo.wjoin(lfile))) and
404 not lfutil.findfile(repo, expectedhash)):
414 not lfutil.findfile(repo, expectedhash)):
405 toget.append((lfile, expectedhash))
415 toget.append((lfile, expectedhash))
406
416
407 if toget:
417 if toget:
408 store = basestore._openstore(repo)
418 store = basestore._openstore(repo)
409 ret = store.get(toget)
419 ret = store.get(toget)
410 return ret
420 return ret
411
421
412 return ([], [])
422 return ([], [])
413
423
414 def downloadlfiles(ui, repo, rev=None):
424 def downloadlfiles(ui, repo, rev=None):
415 matchfn = scmutil.match(repo[None],
425 matchfn = scmutil.match(repo[None],
416 [repo.wjoin(lfutil.shortname)], {})
426 [repo.wjoin(lfutil.shortname)], {})
417 def prepare(ctx, fns):
427 def prepare(ctx, fns):
418 pass
428 pass
419 totalsuccess = 0
429 totalsuccess = 0
420 totalmissing = 0
430 totalmissing = 0
421 for ctx in cmdutil.walkchangerevs(repo, matchfn, {'rev' : rev},
431 for ctx in cmdutil.walkchangerevs(repo, matchfn, {'rev' : rev},
422 prepare):
432 prepare):
423 success, missing = cachelfiles(ui, repo, ctx.node())
433 success, missing = cachelfiles(ui, repo, ctx.node())
424 totalsuccess += len(success)
434 totalsuccess += len(success)
425 totalmissing += len(missing)
435 totalmissing += len(missing)
426 ui.status(_("%d additional largefiles cached\n") % totalsuccess)
436 ui.status(_("%d additional largefiles cached\n") % totalsuccess)
427 if totalmissing > 0:
437 if totalmissing > 0:
428 ui.status(_("%d largefiles failed to download\n") % totalmissing)
438 ui.status(_("%d largefiles failed to download\n") % totalmissing)
429 return totalsuccess, totalmissing
439 return totalsuccess, totalmissing
430
440
431 def updatelfiles(ui, repo, filelist=None, printmessage=True):
441 def updatelfiles(ui, repo, filelist=None, printmessage=True):
432 wlock = repo.wlock()
442 wlock = repo.wlock()
433 try:
443 try:
434 lfdirstate = lfutil.openlfdirstate(ui, repo)
444 lfdirstate = lfutil.openlfdirstate(ui, repo)
435 lfiles = set(lfutil.listlfiles(repo)) | set(lfdirstate)
445 lfiles = set(lfutil.listlfiles(repo)) | set(lfdirstate)
436
446
437 if filelist is not None:
447 if filelist is not None:
438 lfiles = [f for f in lfiles if f in filelist]
448 lfiles = [f for f in lfiles if f in filelist]
439
449
440 printed = False
450 printed = False
441 if printmessage and lfiles:
451 if printmessage and lfiles:
442 ui.status(_('getting changed largefiles\n'))
452 ui.status(_('getting changed largefiles\n'))
443 printed = True
453 printed = True
444 cachelfiles(ui, repo, '.', lfiles)
454 cachelfiles(ui, repo, '.', lfiles)
445
455
446 updated, removed = 0, 0
456 updated, removed = 0, 0
447 for f in lfiles:
457 for f in lfiles:
448 i = _updatelfile(repo, lfdirstate, f)
458 i = _updatelfile(repo, lfdirstate, f)
449 if i:
459 if i:
450 if i > 0:
460 if i > 0:
451 updated += i
461 updated += i
452 else:
462 else:
453 removed -= i
463 removed -= i
454 if printmessage and (removed or updated) and not printed:
464 if printmessage and (removed or updated) and not printed:
455 ui.status(_('getting changed largefiles\n'))
465 ui.status(_('getting changed largefiles\n'))
456 printed = True
466 printed = True
457
467
458 lfdirstate.write()
468 lfdirstate.write()
459 if printed and printmessage:
469 if printed and printmessage:
460 ui.status(_('%d largefiles updated, %d removed\n') % (updated,
470 ui.status(_('%d largefiles updated, %d removed\n') % (updated,
461 removed))
471 removed))
462 finally:
472 finally:
463 wlock.release()
473 wlock.release()
464
474
465 def _updatelfile(repo, lfdirstate, lfile):
475 def _updatelfile(repo, lfdirstate, lfile):
466 '''updates a single largefile and copies the state of its standin from
476 '''updates a single largefile and copies the state of its standin from
467 the repository's dirstate to its state in the lfdirstate.
477 the repository's dirstate to its state in the lfdirstate.
468
478
469 returns 1 if the file was modified, -1 if the file was removed, 0 if the
479 returns 1 if the file was modified, -1 if the file was removed, 0 if the
470 file was unchanged, and None if the needed largefile was missing from the
480 file was unchanged, and None if the needed largefile was missing from the
471 cache.'''
481 cache.'''
472 ret = 0
482 ret = 0
473 abslfile = repo.wjoin(lfile)
483 abslfile = repo.wjoin(lfile)
474 absstandin = repo.wjoin(lfutil.standin(lfile))
484 absstandin = repo.wjoin(lfutil.standin(lfile))
475 if os.path.exists(absstandin):
485 if os.path.exists(absstandin):
476 if os.path.exists(absstandin+'.orig'):
486 if os.path.exists(absstandin+'.orig'):
477 shutil.copyfile(abslfile, abslfile+'.orig')
487 shutil.copyfile(abslfile, abslfile+'.orig')
478 expecthash = lfutil.readstandin(repo, lfile)
488 expecthash = lfutil.readstandin(repo, lfile)
479 if (expecthash != '' and
489 if (expecthash != '' and
480 (not os.path.exists(abslfile) or
490 (not os.path.exists(abslfile) or
481 expecthash != lfutil.hashfile(abslfile))):
491 expecthash != lfutil.hashfile(abslfile))):
482 if not lfutil.copyfromcache(repo, expecthash, lfile):
492 if not lfutil.copyfromcache(repo, expecthash, lfile):
483 # use normallookup() to allocate entry in largefiles dirstate,
493 # use normallookup() to allocate entry in largefiles dirstate,
484 # because lack of it misleads lfilesrepo.status() into
494 # because lack of it misleads lfilesrepo.status() into
485 # recognition that such cache missing files are REMOVED.
495 # recognition that such cache missing files are REMOVED.
486 lfdirstate.normallookup(lfile)
496 lfdirstate.normallookup(lfile)
487 return None # don't try to set the mode
497 return None # don't try to set the mode
488 else:
498 else:
489 # Synchronize largefile dirstate to the last modified time of
499 # Synchronize largefile dirstate to the last modified time of
490 # the file
500 # the file
491 lfdirstate.normal(lfile)
501 lfdirstate.normal(lfile)
492 ret = 1
502 ret = 1
493 mode = os.stat(absstandin).st_mode
503 mode = os.stat(absstandin).st_mode
494 if mode != os.stat(abslfile).st_mode:
504 if mode != os.stat(abslfile).st_mode:
495 os.chmod(abslfile, mode)
505 os.chmod(abslfile, mode)
496 ret = 1
506 ret = 1
497 else:
507 else:
498 # Remove lfiles for which the standin is deleted, unless the
508 # Remove lfiles for which the standin is deleted, unless the
499 # lfile is added to the repository again. This happens when a
509 # lfile is added to the repository again. This happens when a
500 # largefile is converted back to a normal file: the standin
510 # largefile is converted back to a normal file: the standin
501 # disappears, but a new (normal) file appears as the lfile.
511 # disappears, but a new (normal) file appears as the lfile.
502 if os.path.exists(abslfile) and lfile not in repo[None]:
512 if os.path.exists(abslfile) and lfile not in repo[None]:
503 util.unlinkpath(abslfile)
513 util.unlinkpath(abslfile)
504 ret = -1
514 ret = -1
505 state = repo.dirstate[lfutil.standin(lfile)]
515 state = repo.dirstate[lfutil.standin(lfile)]
506 if state == 'n':
516 if state == 'n':
507 # When rebasing, we need to synchronize the standin and the largefile,
517 # When rebasing, we need to synchronize the standin and the largefile,
508 # because otherwise the largefile will get reverted. But for commit's
518 # because otherwise the largefile will get reverted. But for commit's
509 # sake, we have to mark the file as unclean.
519 # sake, we have to mark the file as unclean.
510 if getattr(repo, "_isrebasing", False):
520 if getattr(repo, "_isrebasing", False):
511 lfdirstate.normallookup(lfile)
521 lfdirstate.normallookup(lfile)
512 else:
522 else:
513 lfdirstate.normal(lfile)
523 lfdirstate.normal(lfile)
514 elif state == 'r':
524 elif state == 'r':
515 lfdirstate.remove(lfile)
525 lfdirstate.remove(lfile)
516 elif state == 'a':
526 elif state == 'a':
517 lfdirstate.add(lfile)
527 lfdirstate.add(lfile)
518 elif state == '?':
528 elif state == '?':
519 lfdirstate.drop(lfile)
529 lfdirstate.drop(lfile)
520 return ret
530 return ret
521
531
522 def catlfile(repo, lfile, rev, filename):
532 def catlfile(repo, lfile, rev, filename):
523 hash = lfutil.readstandin(repo, lfile, rev)
533 hash = lfutil.readstandin(repo, lfile, rev)
524 if not lfutil.inusercache(repo.ui, hash):
534 if not lfutil.inusercache(repo.ui, hash):
525 store = basestore._openstore(repo)
535 store = basestore._openstore(repo)
526 success, missing = store.get([(lfile, hash)])
536 success, missing = store.get([(lfile, hash)])
527 if len(success) != 1:
537 if len(success) != 1:
528 raise util.Abort(
538 raise util.Abort(
529 _('largefile %s is not in cache and could not be downloaded')
539 _('largefile %s is not in cache and could not be downloaded')
530 % lfile)
540 % lfile)
531 path = lfutil.usercachepath(repo.ui, hash)
541 path = lfutil.usercachepath(repo.ui, hash)
532 fpout = cmdutil.makefileobj(repo, filename)
542 fpout = cmdutil.makefileobj(repo, filename)
533 fpin = open(path, "rb")
543 fpin = open(path, "rb")
534 fpout.write(fpin.read())
544 fpout.write(fpin.read())
535 fpout.close()
545 fpout.close()
536 fpin.close()
546 fpin.close()
537 return 0
547 return 0
538
548
539 # -- hg commands declarations ------------------------------------------------
549 # -- hg commands declarations ------------------------------------------------
540
550
541 cmdtable = {
551 cmdtable = {
542 'lfconvert': (lfconvert,
552 'lfconvert': (lfconvert,
543 [('s', 'size', '',
553 [('s', 'size', '',
544 _('minimum size (MB) for files to be converted '
554 _('minimum size (MB) for files to be converted '
545 'as largefiles'),
555 'as largefiles'),
546 'SIZE'),
556 'SIZE'),
547 ('', 'to-normal', False,
557 ('', 'to-normal', False,
548 _('convert from a largefiles repo to a normal repo')),
558 _('convert from a largefiles repo to a normal repo')),
549 ],
559 ],
550 _('hg lfconvert SOURCE DEST [FILE ...]')),
560 _('hg lfconvert SOURCE DEST [FILE ...]')),
551 }
561 }
552
562
553 commands.inferrepo += " lfconvert"
563 commands.inferrepo += " lfconvert"
@@ -1,272 +1,286
1 $ USERCACHE="$TESTTMP/cache"; export USERCACHE
2 $ mkdir "${USERCACHE}"
1 $ cat >> $HGRCPATH <<EOF
3 $ cat >> $HGRCPATH <<EOF
2 > [extensions]
4 > [extensions]
3 > largefiles =
5 > largefiles =
4 > share =
6 > share =
5 > graphlog =
7 > graphlog =
6 > mq =
8 > mq =
7 > [largefiles]
9 > [largefiles]
8 > minsize = 0.5
10 > minsize = 0.5
9 > patterns = **.other
11 > patterns = **.other
10 > **.dat
12 > **.dat
13 > usercache=${USERCACHE}
11 > EOF
14 > EOF
12
15
13 "lfconvert" works
16 "lfconvert" works
14 $ hg init bigfile-repo
17 $ hg init bigfile-repo
15 $ cd bigfile-repo
18 $ cd bigfile-repo
16 $ cat >> .hg/hgrc <<EOF
19 $ cat >> .hg/hgrc <<EOF
17 > [extensions]
20 > [extensions]
18 > largefiles = !
21 > largefiles = !
19 > EOF
22 > EOF
20 $ mkdir sub
23 $ mkdir sub
21 $ dd if=/dev/zero bs=1k count=256 > large 2> /dev/null
24 $ dd if=/dev/zero bs=1k count=256 > large 2> /dev/null
22 $ dd if=/dev/zero bs=1k count=256 > large2 2> /dev/null
25 $ dd if=/dev/zero bs=1k count=256 > large2 2> /dev/null
23 $ echo normal > normal1
26 $ echo normal > normal1
24 $ echo alsonormal > sub/normal2
27 $ echo alsonormal > sub/normal2
25 $ dd if=/dev/zero bs=1k count=10 > sub/maybelarge.dat 2> /dev/null
28 $ dd if=/dev/zero bs=1k count=10 > sub/maybelarge.dat 2> /dev/null
26 $ hg addremove
29 $ hg addremove
27 adding large
30 adding large
28 adding large2
31 adding large2
29 adding normal1
32 adding normal1
30 adding sub/maybelarge.dat
33 adding sub/maybelarge.dat
31 adding sub/normal2
34 adding sub/normal2
32 $ hg commit -m"add large, normal1" large normal1
35 $ hg commit -m"add large, normal1" large normal1
33 $ hg commit -m"add sub/*" sub
36 $ hg commit -m"add sub/*" sub
34
37
35 Test tag parsing
38 Test tag parsing
36 $ cat >> .hgtags <<EOF
39 $ cat >> .hgtags <<EOF
37 > IncorrectlyFormattedTag!
40 > IncorrectlyFormattedTag!
38 > invalidhash sometag
41 > invalidhash sometag
39 > 0123456789abcdef anothertag
42 > 0123456789abcdef anothertag
40 > EOF
43 > EOF
41 $ hg add .hgtags
44 $ hg add .hgtags
42 $ hg commit -m"add large2" large2 .hgtags
45 $ hg commit -m"add large2" large2 .hgtags
43
46
44 Test link+rename largefile codepath
47 Test link+rename largefile codepath
45 $ [ -d .hg/largefiles ] && echo fail || echo pass
48 $ [ -d .hg/largefiles ] && echo fail || echo pass
46 pass
49 pass
47 $ cd ..
50 $ cd ..
48 $ hg lfconvert --size 0.2 bigfile-repo largefiles-repo
51 $ hg lfconvert --size 0.2 bigfile-repo largefiles-repo
49 initializing destination largefiles-repo
52 initializing destination largefiles-repo
50 skipping incorrectly formatted tag IncorrectlyFormattedTag!
53 skipping incorrectly formatted tag IncorrectlyFormattedTag!
51 skipping incorrectly formatted id invalidhash
54 skipping incorrectly formatted id invalidhash
52 no mapping for id 0123456789abcdef
55 no mapping for id 0123456789abcdef
53 #if symlink
56 #if symlink
54 $ hg --cwd bigfile-repo rename large2 large3
57 $ hg --cwd bigfile-repo rename large2 large3
55 $ ln -sf large bigfile-repo/large3
58 $ ln -sf large bigfile-repo/large3
56 $ hg --cwd bigfile-repo commit -m"make large2 a symlink" large2 large3
59 $ hg --cwd bigfile-repo commit -m"make large2 a symlink" large2 large3
57 $ hg lfconvert --size 0.2 bigfile-repo largefiles-repo-symlink
60 $ hg lfconvert --size 0.2 bigfile-repo largefiles-repo-symlink
58 initializing destination largefiles-repo-symlink
61 initializing destination largefiles-repo-symlink
59 skipping incorrectly formatted tag IncorrectlyFormattedTag!
62 skipping incorrectly formatted tag IncorrectlyFormattedTag!
60 skipping incorrectly formatted id invalidhash
63 skipping incorrectly formatted id invalidhash
61 no mapping for id 0123456789abcdef
64 no mapping for id 0123456789abcdef
62 abort: renamed/copied largefile large3 becomes symlink
65 abort: renamed/copied largefile large3 becomes symlink
63 [255]
66 [255]
64 #endif
67 #endif
65 $ cd bigfile-repo
68 $ cd bigfile-repo
66 $ hg strip --no-backup 2
69 $ hg strip --no-backup 2
67 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
70 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
68 $ cd ..
71 $ cd ..
69 $ rm -rf largefiles-repo largefiles-repo-symlink
72 $ rm -rf largefiles-repo largefiles-repo-symlink
70
73
71 $ hg lfconvert --size 0.2 bigfile-repo largefiles-repo
74 $ hg lfconvert --size 0.2 bigfile-repo largefiles-repo
72 initializing destination largefiles-repo
75 initializing destination largefiles-repo
73
76
74 "lfconvert" converts content correctly
77 "lfconvert" converts content correctly
75 $ cd largefiles-repo
78 $ cd largefiles-repo
76 $ hg up
79 $ hg up
77 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
80 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
78 getting changed largefiles
81 getting changed largefiles
79 2 largefiles updated, 0 removed
82 2 largefiles updated, 0 removed
80 $ hg locate
83 $ hg locate
81 .hglf/large
84 .hglf/large
82 .hglf/sub/maybelarge.dat
85 .hglf/sub/maybelarge.dat
83 normal1
86 normal1
84 sub/normal2
87 sub/normal2
85 $ cat normal1
88 $ cat normal1
86 normal
89 normal
87 $ cat sub/normal2
90 $ cat sub/normal2
88 alsonormal
91 alsonormal
89 $ "$TESTDIR/md5sum.py" large sub/maybelarge.dat
92 $ "$TESTDIR/md5sum.py" large sub/maybelarge.dat
90 ec87a838931d4d5d2e94a04644788a55 large
93 ec87a838931d4d5d2e94a04644788a55 large
91 1276481102f218c981e0324180bafd9f sub/maybelarge.dat
94 1276481102f218c981e0324180bafd9f sub/maybelarge.dat
92
95
93 "lfconvert" adds 'largefiles' to .hg/requires.
96 "lfconvert" adds 'largefiles' to .hg/requires.
94 $ cat .hg/requires
97 $ cat .hg/requires
95 largefiles
98 largefiles
96 revlogv1
99 revlogv1
97 fncache
100 fncache
98 store
101 store
99 dotencode
102 dotencode
100
103
101 "lfconvert" includes a newline at the end of the standin files.
104 "lfconvert" includes a newline at the end of the standin files.
102 $ cat .hglf/large .hglf/sub/maybelarge.dat
105 $ cat .hglf/large .hglf/sub/maybelarge.dat
103 2e000fa7e85759c7f4c254d4d9c33ef481e459a7
106 2e000fa7e85759c7f4c254d4d9c33ef481e459a7
104 34e163be8e43c5631d8b92e9c43ab0bf0fa62b9c
107 34e163be8e43c5631d8b92e9c43ab0bf0fa62b9c
105 $ cd ..
108 $ cd ..
106
109
107 add some changesets to rename/remove/merge
110 add some changesets to rename/remove/merge
108 $ cd bigfile-repo
111 $ cd bigfile-repo
109 $ hg mv -q sub stuff
112 $ hg mv -q sub stuff
110 $ hg commit -m"rename sub/ to stuff/"
113 $ hg commit -m"rename sub/ to stuff/"
111 $ hg update -q 1
114 $ hg update -q 1
112 $ echo blah >> normal3
115 $ echo blah >> normal3
113 $ echo blah >> sub/normal2
116 $ echo blah >> sub/normal2
114 $ echo blah >> sub/maybelarge.dat
117 $ echo blah >> sub/maybelarge.dat
115 $ "$TESTDIR/md5sum.py" sub/maybelarge.dat
118 $ "$TESTDIR/md5sum.py" sub/maybelarge.dat
116 1dd0b99ff80e19cff409702a1d3f5e15 sub/maybelarge.dat
119 1dd0b99ff80e19cff409702a1d3f5e15 sub/maybelarge.dat
117 $ hg commit -A -m"add normal3, modify sub/*"
120 $ hg commit -A -m"add normal3, modify sub/*"
118 adding normal3
121 adding normal3
119 created new head
122 created new head
120 $ hg rm large normal3
123 $ hg rm large normal3
121 $ hg commit -q -m"remove large, normal3"
124 $ hg commit -q -m"remove large, normal3"
122 $ hg merge
125 $ hg merge
123 merging sub/maybelarge.dat and stuff/maybelarge.dat to stuff/maybelarge.dat
126 merging sub/maybelarge.dat and stuff/maybelarge.dat to stuff/maybelarge.dat
124 warning: $TESTTMP/bigfile-repo/stuff/maybelarge.dat looks like a binary file. (glob)
127 warning: $TESTTMP/bigfile-repo/stuff/maybelarge.dat looks like a binary file. (glob)
125 merging stuff/maybelarge.dat incomplete! (edit conflicts, then use 'hg resolve --mark')
128 merging stuff/maybelarge.dat incomplete! (edit conflicts, then use 'hg resolve --mark')
126 merging sub/normal2 and stuff/normal2 to stuff/normal2
129 merging sub/normal2 and stuff/normal2 to stuff/normal2
127 0 files updated, 1 files merged, 0 files removed, 1 files unresolved
130 0 files updated, 1 files merged, 0 files removed, 1 files unresolved
128 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
131 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
129 [1]
132 [1]
130 $ hg cat -r . sub/maybelarge.dat > stuff/maybelarge.dat
133 $ hg cat -r . sub/maybelarge.dat > stuff/maybelarge.dat
131 $ hg resolve -m stuff/maybelarge.dat
134 $ hg resolve -m stuff/maybelarge.dat
132 $ hg commit -m"merge"
135 $ hg commit -m"merge"
133 $ hg glog --template "{rev}:{node|short} {desc|firstline}\n"
136 $ hg glog --template "{rev}:{node|short} {desc|firstline}\n"
134 @ 5:4884f215abda merge
137 @ 5:4884f215abda merge
135 |\
138 |\
136 | o 4:7285f817b77e remove large, normal3
139 | o 4:7285f817b77e remove large, normal3
137 | |
140 | |
138 | o 3:67e3892e3534 add normal3, modify sub/*
141 | o 3:67e3892e3534 add normal3, modify sub/*
139 | |
142 | |
140 o | 2:c96c8beb5d56 rename sub/ to stuff/
143 o | 2:c96c8beb5d56 rename sub/ to stuff/
141 |/
144 |/
142 o 1:020c65d24e11 add sub/*
145 o 1:020c65d24e11 add sub/*
143 |
146 |
144 o 0:117b8328f97a add large, normal1
147 o 0:117b8328f97a add large, normal1
145
148
146 $ cd ..
149 $ cd ..
147
150
148 lfconvert with rename, merge, and remove
151 lfconvert with rename, merge, and remove
149 $ rm -rf largefiles-repo
152 $ rm -rf largefiles-repo
150 $ hg lfconvert --size 0.2 bigfile-repo largefiles-repo
153 $ hg lfconvert --size 0.2 bigfile-repo largefiles-repo
151 initializing destination largefiles-repo
154 initializing destination largefiles-repo
152 $ cd largefiles-repo
155 $ cd largefiles-repo
153 $ hg glog --template "{rev}:{node|short} {desc|firstline}\n"
156 $ hg glog --template "{rev}:{node|short} {desc|firstline}\n"
154 o 5:8e05f5f2b77e merge
157 o 5:8e05f5f2b77e merge
155 |\
158 |\
156 | o 4:a5a02de7a8e4 remove large, normal3
159 | o 4:a5a02de7a8e4 remove large, normal3
157 | |
160 | |
158 | o 3:55759520c76f add normal3, modify sub/*
161 | o 3:55759520c76f add normal3, modify sub/*
159 | |
162 | |
160 o | 2:261ad3f3f037 rename sub/ to stuff/
163 o | 2:261ad3f3f037 rename sub/ to stuff/
161 |/
164 |/
162 o 1:334e5237836d add sub/*
165 o 1:334e5237836d add sub/*
163 |
166 |
164 o 0:d4892ec57ce2 add large, normal1
167 o 0:d4892ec57ce2 add large, normal1
165
168
166 $ hg locate -r 2
169 $ hg locate -r 2
167 .hglf/large
170 .hglf/large
168 .hglf/stuff/maybelarge.dat
171 .hglf/stuff/maybelarge.dat
169 normal1
172 normal1
170 stuff/normal2
173 stuff/normal2
171 $ hg locate -r 3
174 $ hg locate -r 3
172 .hglf/large
175 .hglf/large
173 .hglf/sub/maybelarge.dat
176 .hglf/sub/maybelarge.dat
174 normal1
177 normal1
175 normal3
178 normal3
176 sub/normal2
179 sub/normal2
177 $ hg locate -r 4
180 $ hg locate -r 4
178 .hglf/sub/maybelarge.dat
181 .hglf/sub/maybelarge.dat
179 normal1
182 normal1
180 sub/normal2
183 sub/normal2
181 $ hg locate -r 5
184 $ hg locate -r 5
182 .hglf/stuff/maybelarge.dat
185 .hglf/stuff/maybelarge.dat
183 normal1
186 normal1
184 stuff/normal2
187 stuff/normal2
185 $ hg update
188 $ hg update
186 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
189 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
187 getting changed largefiles
190 getting changed largefiles
188 1 largefiles updated, 0 removed
191 1 largefiles updated, 0 removed
189 $ cat stuff/normal2
192 $ cat stuff/normal2
190 alsonormal
193 alsonormal
191 blah
194 blah
192 $ "$TESTDIR/md5sum.py" stuff/maybelarge.dat
195 $ "$TESTDIR/md5sum.py" stuff/maybelarge.dat
193 1dd0b99ff80e19cff409702a1d3f5e15 stuff/maybelarge.dat
196 1dd0b99ff80e19cff409702a1d3f5e15 stuff/maybelarge.dat
194 $ cat .hglf/stuff/maybelarge.dat
197 $ cat .hglf/stuff/maybelarge.dat
195 76236b6a2c6102826c61af4297dd738fb3b1de38
198 76236b6a2c6102826c61af4297dd738fb3b1de38
196 $ cd ..
199 $ cd ..
197
200
198 "lfconvert" error cases
201 "lfconvert" error cases
199 $ hg lfconvert http://localhost/foo foo
202 $ hg lfconvert http://localhost/foo foo
200 abort: http://localhost/foo is not a local Mercurial repo
203 abort: http://localhost/foo is not a local Mercurial repo
201 [255]
204 [255]
202 $ hg lfconvert foo ssh://localhost/foo
205 $ hg lfconvert foo ssh://localhost/foo
203 abort: ssh://localhost/foo is not a local Mercurial repo
206 abort: ssh://localhost/foo is not a local Mercurial repo
204 [255]
207 [255]
205 $ hg lfconvert nosuchrepo foo
208 $ hg lfconvert nosuchrepo foo
206 abort: repository nosuchrepo not found!
209 abort: repository nosuchrepo not found!
207 [255]
210 [255]
208 $ hg share -q -U bigfile-repo shared
211 $ hg share -q -U bigfile-repo shared
209 $ printf 'bogus' > shared/.hg/sharedpath
212 $ printf 'bogus' > shared/.hg/sharedpath
210 $ hg lfconvert shared foo
213 $ hg lfconvert shared foo
211 abort: .hg/sharedpath points to nonexistent directory $TESTTMP/bogus! (glob)
214 abort: .hg/sharedpath points to nonexistent directory $TESTTMP/bogus! (glob)
212 [255]
215 [255]
213 $ hg lfconvert bigfile-repo largefiles-repo
216 $ hg lfconvert bigfile-repo largefiles-repo
214 initializing destination largefiles-repo
217 initializing destination largefiles-repo
215 abort: repository largefiles-repo already exists!
218 abort: repository largefiles-repo already exists!
216 [255]
219 [255]
217
220
218 add another largefile to the new largefiles repo
221 add another largefile to the new largefiles repo
219 $ cd largefiles-repo
222 $ cd largefiles-repo
220 $ dd if=/dev/zero bs=1k count=1k > anotherlarge 2> /dev/null
223 $ dd if=/dev/zero bs=1k count=1k > anotherlarge 2> /dev/null
221 $ hg add --lfsize=1 anotherlarge
224 $ hg add --lfsize=1 anotherlarge
222 $ hg commit -m "add anotherlarge (should be a largefile)"
225 $ hg commit -m "add anotherlarge (should be a largefile)"
223 $ cat .hglf/anotherlarge
226 $ cat .hglf/anotherlarge
224 3b71f43ff30f4b15b5cd85dd9e95ebc7e84eb5a3
227 3b71f43ff30f4b15b5cd85dd9e95ebc7e84eb5a3
225 $ cd ..
228 $ cd ..
226
229
227 round-trip: converting back to a normal (non-largefiles) repo with
230 round-trip: converting back to a normal (non-largefiles) repo with
228 "lfconvert --to-normal" should give the same as ../bigfile-repo
231 "lfconvert --to-normal" should give the same as ../bigfile-repo
229 $ cd largefiles-repo
232 $ cd largefiles-repo
230 $ hg lfconvert --to-normal . ../normal-repo
233 $ hg lfconvert --to-normal . ../normal-repo
231 initializing destination ../normal-repo
234 initializing destination ../normal-repo
232 $ cd ../normal-repo
235 $ cd ../normal-repo
233 $ cat >> .hg/hgrc <<EOF
236 $ cat >> .hg/hgrc <<EOF
234 > [extensions]
237 > [extensions]
235 > largefiles = !
238 > largefiles = !
236 > EOF
239 > EOF
237
240
238 # Hmmm: the changeset ID for rev 5 is different from the original
241 # Hmmm: the changeset ID for rev 5 is different from the original
239 # normal repo (../bigfile-repo), because the changelog filelist
242 # normal repo (../bigfile-repo), because the changelog filelist
240 # differs between the two incarnations of rev 5: this repo includes
243 # differs between the two incarnations of rev 5: this repo includes
241 # 'large' in the list, but ../bigfile-repo does not. Since rev 5
244 # 'large' in the list, but ../bigfile-repo does not. Since rev 5
242 # removes 'large' relative to the first parent in both repos, it seems
245 # removes 'large' relative to the first parent in both repos, it seems
243 # to me that lfconvert is doing a *better* job than
246 # to me that lfconvert is doing a *better* job than
244 # "hg remove" + "hg merge" + "hg commit".
247 # "hg remove" + "hg merge" + "hg commit".
245 # $ hg -R ../bigfile-repo debugdata -c 5
248 # $ hg -R ../bigfile-repo debugdata -c 5
246 # $ hg debugdata -c 5
249 # $ hg debugdata -c 5
247 $ hg glog --template "{rev}:{node|short} {desc|firstline}\n"
250 $ hg glog --template "{rev}:{node|short} {desc|firstline}\n"
248 o 6:1635824e6f59 add anotherlarge (should be a largefile)
251 o 6:1635824e6f59 add anotherlarge (should be a largefile)
249 |
252 |
250 o 5:7215f8deeaaf merge
253 o 5:7215f8deeaaf merge
251 |\
254 |\
252 | o 4:7285f817b77e remove large, normal3
255 | o 4:7285f817b77e remove large, normal3
253 | |
256 | |
254 | o 3:67e3892e3534 add normal3, modify sub/*
257 | o 3:67e3892e3534 add normal3, modify sub/*
255 | |
258 | |
256 o | 2:c96c8beb5d56 rename sub/ to stuff/
259 o | 2:c96c8beb5d56 rename sub/ to stuff/
257 |/
260 |/
258 o 1:020c65d24e11 add sub/*
261 o 1:020c65d24e11 add sub/*
259 |
262 |
260 o 0:117b8328f97a add large, normal1
263 o 0:117b8328f97a add large, normal1
261
264
262 $ hg update
265 $ hg update
263 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
266 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
264 $ hg locate
267 $ hg locate
265 anotherlarge
268 anotherlarge
266 normal1
269 normal1
267 stuff/maybelarge.dat
270 stuff/maybelarge.dat
268 stuff/normal2
271 stuff/normal2
269 $ [ -d .hg/largefiles ] && echo fail || echo pass
272 $ [ -d .hg/largefiles ] && echo fail || echo pass
270 pass
273 pass
271
274
272 $ cd ..
275 $ cd ..
276
277 Avoid a traceback if a largefile isn't available (issue3519)
278
279 $ hg clone -U largefiles-repo issue3519
280 $ rm "${USERCACHE}"/*
281 $ hg lfconvert --to-normal issue3519 normalized3519
282 initializing destination normalized3519
283 abort: missing largefile 'large' from revision d4892ec57ce212905215fad1d9018f56b99202ad
284 [255]
285
286
General Comments 0
You need to be logged in to leave comments. Login now