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