##// END OF EJS Templates
largefiles: rearrange how lfconvert detects non-local repos...
Greg Ward -
r15340:0e58513c stable
parent child Browse files
Show More
@@ -1,472 +1,473 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
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 from mercurial.i18n import _
15 from mercurial.i18n import _
16
16
17 import lfutil
17 import lfutil
18 import basestore
18 import basestore
19
19
20 # -- Commands ----------------------------------------------------------
20 # -- Commands ----------------------------------------------------------
21
21
22 def lfconvert(ui, src, dest, *pats, **opts):
22 def lfconvert(ui, src, dest, *pats, **opts):
23 '''convert a normal repository to a largefiles repository
23 '''convert a normal repository to a largefiles repository
24
24
25 Convert repository SOURCE to a new repository DEST, identical to
25 Convert repository SOURCE to a new repository DEST, identical to
26 SOURCE except that certain files will be converted as largefiles:
26 SOURCE except that certain files will be converted as largefiles:
27 specifically, any file that matches any PATTERN *or* whose size is
27 specifically, any file that matches any PATTERN *or* whose size is
28 above the minimum size threshold is converted as a largefile. The
28 above the minimum size threshold is converted as a largefile. The
29 size used to determine whether or not to track a file as a
29 size used to determine whether or not to track a file as a
30 largefile is the size of the first version of the file. The
30 largefile is the size of the first version of the file. The
31 minimum size can be specified either with --size or in
31 minimum size can be specified either with --size or in
32 configuration as ``largefiles.size``.
32 configuration as ``largefiles.size``.
33
33
34 After running this command you will need to make sure that
34 After running this command you will need to make sure that
35 largefiles is enabled anywhere you intend to push the new
35 largefiles is enabled anywhere you intend to push the new
36 repository.
36 repository.
37
37
38 Use --to-normal to convert largefiles back to normal files; after
38 Use --to-normal to convert largefiles back to normal files; after
39 this, the DEST repository can be used without largefiles at all.'''
39 this, the DEST repository can be used without largefiles at all.'''
40
40
41 if opts['to_normal']:
41 if opts['to_normal']:
42 tolfile = False
42 tolfile = False
43 else:
43 else:
44 tolfile = True
44 tolfile = True
45 size = lfutil.getminsize(ui, True, opts.get('size'), default=None)
45 size = lfutil.getminsize(ui, True, opts.get('size'), default=None)
46
47 if not hg.islocal(src):
48 raise util.Abort(_('%s is not a local Mercurial repo') % src)
49 if not hg.islocal(dest):
50 raise util.Abort(_('%s is not a local Mercurial repo') % dest)
51
46 rsrc = hg.repository(ui, src)
52 rsrc = hg.repository(ui, src)
47 if not rsrc.local():
48 raise util.Abort(_('%s is not a local Mercurial repo') % src)
49
50 ui.status(_('initializing destination %s\n') % dest)
53 ui.status(_('initializing destination %s\n') % dest)
51 rdst = hg.repository(ui, dest, create=True)
54 rdst = hg.repository(ui, dest, create=True)
52 if not rdst.local():
53 raise util.Abort(_('%s is not a local Mercurial repo') % dest)
54
55
55 success = False
56 success = False
56 try:
57 try:
57 # Lock destination to prevent modification while it is converted to.
58 # Lock destination to prevent modification while it is converted to.
58 # Don't need to lock src because we are just reading from its history
59 # Don't need to lock src because we are just reading from its history
59 # which can't change.
60 # which can't change.
60 dst_lock = rdst.lock()
61 dst_lock = rdst.lock()
61
62
62 # Get a list of all changesets in the source. The easy way to do this
63 # Get a list of all changesets in the source. The easy way to do this
63 # is to simply walk the changelog, using changelog.nodesbewteen().
64 # is to simply walk the changelog, using changelog.nodesbewteen().
64 # Take a look at mercurial/revlog.py:639 for more details.
65 # Take a look at mercurial/revlog.py:639 for more details.
65 # Use a generator instead of a list to decrease memory usage
66 # Use a generator instead of a list to decrease memory usage
66 ctxs = (rsrc[ctx] for ctx in rsrc.changelog.nodesbetween(None,
67 ctxs = (rsrc[ctx] for ctx in rsrc.changelog.nodesbetween(None,
67 rsrc.heads())[0])
68 rsrc.heads())[0])
68 revmap = {node.nullid: node.nullid}
69 revmap = {node.nullid: node.nullid}
69 if tolfile:
70 if tolfile:
70 lfiles = set()
71 lfiles = set()
71 normalfiles = set()
72 normalfiles = set()
72 if not pats:
73 if not pats:
73 pats = ui.config(lfutil.longname, 'patterns', default=())
74 pats = ui.config(lfutil.longname, 'patterns', default=())
74 if pats:
75 if pats:
75 pats = pats.split(' ')
76 pats = pats.split(' ')
76 if pats:
77 if pats:
77 matcher = match_.match(rsrc.root, '', list(pats))
78 matcher = match_.match(rsrc.root, '', list(pats))
78 else:
79 else:
79 matcher = None
80 matcher = None
80
81
81 lfiletohash = {}
82 lfiletohash = {}
82 for ctx in ctxs:
83 for ctx in ctxs:
83 ui.progress(_('converting revisions'), ctx.rev(),
84 ui.progress(_('converting revisions'), ctx.rev(),
84 unit=_('revision'), total=rsrc['tip'].rev())
85 unit=_('revision'), total=rsrc['tip'].rev())
85 _lfconvert_addchangeset(rsrc, rdst, ctx, revmap,
86 _lfconvert_addchangeset(rsrc, rdst, ctx, revmap,
86 lfiles, normalfiles, matcher, size, lfiletohash)
87 lfiles, normalfiles, matcher, size, lfiletohash)
87 ui.progress(_('converting revisions'), None)
88 ui.progress(_('converting revisions'), None)
88
89
89 if os.path.exists(rdst.wjoin(lfutil.shortname)):
90 if os.path.exists(rdst.wjoin(lfutil.shortname)):
90 shutil.rmtree(rdst.wjoin(lfutil.shortname))
91 shutil.rmtree(rdst.wjoin(lfutil.shortname))
91
92
92 for f in lfiletohash.keys():
93 for f in lfiletohash.keys():
93 if os.path.isfile(rdst.wjoin(f)):
94 if os.path.isfile(rdst.wjoin(f)):
94 os.unlink(rdst.wjoin(f))
95 os.unlink(rdst.wjoin(f))
95 try:
96 try:
96 os.removedirs(os.path.dirname(rdst.wjoin(f)))
97 os.removedirs(os.path.dirname(rdst.wjoin(f)))
97 except OSError:
98 except OSError:
98 pass
99 pass
99
100
100 # If there were any files converted to largefiles, add largefiles
101 # If there were any files converted to largefiles, add largefiles
101 # to the destination repository's requirements.
102 # to the destination repository's requirements.
102 if lfiles:
103 if lfiles:
103 rdst.requirements.add('largefiles')
104 rdst.requirements.add('largefiles')
104 rdst._writerequirements()
105 rdst._writerequirements()
105 else:
106 else:
106 for ctx in ctxs:
107 for ctx in ctxs:
107 ui.progress(_('converting revisions'), ctx.rev(),
108 ui.progress(_('converting revisions'), ctx.rev(),
108 unit=_('revision'), total=rsrc['tip'].rev())
109 unit=_('revision'), total=rsrc['tip'].rev())
109 _addchangeset(ui, rsrc, rdst, ctx, revmap)
110 _addchangeset(ui, rsrc, rdst, ctx, revmap)
110
111
111 ui.progress(_('converting revisions'), None)
112 ui.progress(_('converting revisions'), None)
112 success = True
113 success = True
113 finally:
114 finally:
114 if not success:
115 if not success:
115 # we failed, remove the new directory
116 # we failed, remove the new directory
116 shutil.rmtree(rdst.root)
117 shutil.rmtree(rdst.root)
117 dst_lock.release()
118 dst_lock.release()
118
119
119 def _addchangeset(ui, rsrc, rdst, ctx, revmap):
120 def _addchangeset(ui, rsrc, rdst, ctx, revmap):
120 # Convert src parents to dst parents
121 # Convert src parents to dst parents
121 parents = []
122 parents = []
122 for p in ctx.parents():
123 for p in ctx.parents():
123 parents.append(revmap[p.node()])
124 parents.append(revmap[p.node()])
124 while len(parents) < 2:
125 while len(parents) < 2:
125 parents.append(node.nullid)
126 parents.append(node.nullid)
126
127
127 # Generate list of changed files
128 # Generate list of changed files
128 files = set(ctx.files())
129 files = set(ctx.files())
129 if node.nullid not in parents:
130 if node.nullid not in parents:
130 mc = ctx.manifest()
131 mc = ctx.manifest()
131 mp1 = ctx.parents()[0].manifest()
132 mp1 = ctx.parents()[0].manifest()
132 mp2 = ctx.parents()[1].manifest()
133 mp2 = ctx.parents()[1].manifest()
133 files |= (set(mp1) | set(mp2)) - set(mc)
134 files |= (set(mp1) | set(mp2)) - set(mc)
134 for f in mc:
135 for f in mc:
135 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
136 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
136 files.add(f)
137 files.add(f)
137
138
138 def getfilectx(repo, memctx, f):
139 def getfilectx(repo, memctx, f):
139 if lfutil.standin(f) in files:
140 if lfutil.standin(f) in files:
140 # if the file isn't in the manifest then it was removed
141 # if the file isn't in the manifest then it was removed
141 # or renamed, raise IOError to indicate this
142 # or renamed, raise IOError to indicate this
142 try:
143 try:
143 fctx = ctx.filectx(lfutil.standin(f))
144 fctx = ctx.filectx(lfutil.standin(f))
144 except error.LookupError:
145 except error.LookupError:
145 raise IOError()
146 raise IOError()
146 renamed = fctx.renamed()
147 renamed = fctx.renamed()
147 if renamed:
148 if renamed:
148 renamed = lfutil.splitstandin(renamed[0])
149 renamed = lfutil.splitstandin(renamed[0])
149
150
150 hash = fctx.data().strip()
151 hash = fctx.data().strip()
151 path = lfutil.findfile(rsrc, hash)
152 path = lfutil.findfile(rsrc, hash)
152 ### TODO: What if the file is not cached?
153 ### TODO: What if the file is not cached?
153 data = ''
154 data = ''
154 fd = None
155 fd = None
155 try:
156 try:
156 fd = open(path, 'rb')
157 fd = open(path, 'rb')
157 data = fd.read()
158 data = fd.read()
158 finally:
159 finally:
159 if fd:
160 if fd:
160 fd.close()
161 fd.close()
161 return context.memfilectx(f, data, 'l' in fctx.flags(),
162 return context.memfilectx(f, data, 'l' in fctx.flags(),
162 'x' in fctx.flags(), renamed)
163 'x' in fctx.flags(), renamed)
163 else:
164 else:
164 try:
165 try:
165 fctx = ctx.filectx(f)
166 fctx = ctx.filectx(f)
166 except error.LookupError:
167 except error.LookupError:
167 raise IOError()
168 raise IOError()
168 renamed = fctx.renamed()
169 renamed = fctx.renamed()
169 if renamed:
170 if renamed:
170 renamed = renamed[0]
171 renamed = renamed[0]
171 data = fctx.data()
172 data = fctx.data()
172 if f == '.hgtags':
173 if f == '.hgtags':
173 newdata = []
174 newdata = []
174 for line in data.splitlines():
175 for line in data.splitlines():
175 id, name = line.split(' ', 1)
176 id, name = line.split(' ', 1)
176 newdata.append('%s %s\n' % (node.hex(revmap[node.bin(id)]),
177 newdata.append('%s %s\n' % (node.hex(revmap[node.bin(id)]),
177 name))
178 name))
178 data = ''.join(newdata)
179 data = ''.join(newdata)
179 return context.memfilectx(f, data, 'l' in fctx.flags(),
180 return context.memfilectx(f, data, 'l' in fctx.flags(),
180 'x' in fctx.flags(), renamed)
181 'x' in fctx.flags(), renamed)
181
182
182 dstfiles = []
183 dstfiles = []
183 for file in files:
184 for file in files:
184 if lfutil.isstandin(file):
185 if lfutil.isstandin(file):
185 dstfiles.append(lfutil.splitstandin(file))
186 dstfiles.append(lfutil.splitstandin(file))
186 else:
187 else:
187 dstfiles.append(file)
188 dstfiles.append(file)
188 # Commit
189 # Commit
189 mctx = context.memctx(rdst, parents, ctx.description(), dstfiles,
190 mctx = context.memctx(rdst, parents, ctx.description(), dstfiles,
190 getfilectx, ctx.user(), ctx.date(), ctx.extra())
191 getfilectx, ctx.user(), ctx.date(), ctx.extra())
191 ret = rdst.commitctx(mctx)
192 ret = rdst.commitctx(mctx)
192 rdst.dirstate.setparents(ret)
193 rdst.dirstate.setparents(ret)
193 revmap[ctx.node()] = rdst.changelog.tip()
194 revmap[ctx.node()] = rdst.changelog.tip()
194
195
195 def _lfconvert_addchangeset(rsrc, rdst, ctx, revmap, lfiles, normalfiles,
196 def _lfconvert_addchangeset(rsrc, rdst, ctx, revmap, lfiles, normalfiles,
196 matcher, size, lfiletohash):
197 matcher, size, lfiletohash):
197 # Convert src parents to dst parents
198 # Convert src parents to dst parents
198 parents = []
199 parents = []
199 for p in ctx.parents():
200 for p in ctx.parents():
200 parents.append(revmap[p.node()])
201 parents.append(revmap[p.node()])
201 while len(parents) < 2:
202 while len(parents) < 2:
202 parents.append(node.nullid)
203 parents.append(node.nullid)
203
204
204 # Generate list of changed files
205 # Generate list of changed files
205 files = set(ctx.files())
206 files = set(ctx.files())
206 if node.nullid not in parents:
207 if node.nullid not in parents:
207 mc = ctx.manifest()
208 mc = ctx.manifest()
208 mp1 = ctx.parents()[0].manifest()
209 mp1 = ctx.parents()[0].manifest()
209 mp2 = ctx.parents()[1].manifest()
210 mp2 = ctx.parents()[1].manifest()
210 files |= (set(mp1) | set(mp2)) - set(mc)
211 files |= (set(mp1) | set(mp2)) - set(mc)
211 for f in mc:
212 for f in mc:
212 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
213 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
213 files.add(f)
214 files.add(f)
214
215
215 dstfiles = []
216 dstfiles = []
216 for f in files:
217 for f in files:
217 if f not in lfiles and f not in normalfiles:
218 if f not in lfiles and f not in normalfiles:
218 islfile = _islfile(f, ctx, matcher, size)
219 islfile = _islfile(f, ctx, matcher, size)
219 # If this file was renamed or copied then copy
220 # If this file was renamed or copied then copy
220 # the lfileness of its predecessor
221 # the lfileness of its predecessor
221 if f in ctx.manifest():
222 if f in ctx.manifest():
222 fctx = ctx.filectx(f)
223 fctx = ctx.filectx(f)
223 renamed = fctx.renamed()
224 renamed = fctx.renamed()
224 renamedlfile = renamed and renamed[0] in lfiles
225 renamedlfile = renamed and renamed[0] in lfiles
225 islfile |= renamedlfile
226 islfile |= renamedlfile
226 if 'l' in fctx.flags():
227 if 'l' in fctx.flags():
227 if renamedlfile:
228 if renamedlfile:
228 raise util.Abort(
229 raise util.Abort(
229 _('Renamed/copied largefile %s becomes symlink')
230 _('Renamed/copied largefile %s becomes symlink')
230 % f)
231 % f)
231 islfile = False
232 islfile = False
232 if islfile:
233 if islfile:
233 lfiles.add(f)
234 lfiles.add(f)
234 else:
235 else:
235 normalfiles.add(f)
236 normalfiles.add(f)
236
237
237 if f in lfiles:
238 if f in lfiles:
238 dstfiles.append(lfutil.standin(f))
239 dstfiles.append(lfutil.standin(f))
239 # largefile in manifest if it has not been removed/renamed
240 # largefile in manifest if it has not been removed/renamed
240 if f in ctx.manifest():
241 if f in ctx.manifest():
241 if 'l' in ctx.filectx(f).flags():
242 if 'l' in ctx.filectx(f).flags():
242 if renamed and renamed[0] in lfiles:
243 if renamed and renamed[0] in lfiles:
243 raise util.Abort(_('largefile %s becomes symlink') % f)
244 raise util.Abort(_('largefile %s becomes symlink') % f)
244
245
245 # largefile was modified, update standins
246 # largefile was modified, update standins
246 fullpath = rdst.wjoin(f)
247 fullpath = rdst.wjoin(f)
247 lfutil.createdir(os.path.dirname(fullpath))
248 lfutil.createdir(os.path.dirname(fullpath))
248 m = util.sha1('')
249 m = util.sha1('')
249 m.update(ctx[f].data())
250 m.update(ctx[f].data())
250 hash = m.hexdigest()
251 hash = m.hexdigest()
251 if f not in lfiletohash or lfiletohash[f] != hash:
252 if f not in lfiletohash or lfiletohash[f] != hash:
252 try:
253 try:
253 fd = open(fullpath, 'wb')
254 fd = open(fullpath, 'wb')
254 fd.write(ctx[f].data())
255 fd.write(ctx[f].data())
255 finally:
256 finally:
256 if fd:
257 if fd:
257 fd.close()
258 fd.close()
258 executable = 'x' in ctx[f].flags()
259 executable = 'x' in ctx[f].flags()
259 os.chmod(fullpath, lfutil.getmode(executable))
260 os.chmod(fullpath, lfutil.getmode(executable))
260 lfutil.writestandin(rdst, lfutil.standin(f), hash,
261 lfutil.writestandin(rdst, lfutil.standin(f), hash,
261 executable)
262 executable)
262 lfiletohash[f] = hash
263 lfiletohash[f] = hash
263 else:
264 else:
264 # normal file
265 # normal file
265 dstfiles.append(f)
266 dstfiles.append(f)
266
267
267 def getfilectx(repo, memctx, f):
268 def getfilectx(repo, memctx, f):
268 if lfutil.isstandin(f):
269 if lfutil.isstandin(f):
269 # if the file isn't in the manifest then it was removed
270 # if the file isn't in the manifest then it was removed
270 # or renamed, raise IOError to indicate this
271 # or renamed, raise IOError to indicate this
271 srcfname = lfutil.splitstandin(f)
272 srcfname = lfutil.splitstandin(f)
272 try:
273 try:
273 fctx = ctx.filectx(srcfname)
274 fctx = ctx.filectx(srcfname)
274 except error.LookupError:
275 except error.LookupError:
275 raise IOError()
276 raise IOError()
276 renamed = fctx.renamed()
277 renamed = fctx.renamed()
277 if renamed:
278 if renamed:
278 # standin is always a largefile because largefile-ness
279 # standin is always a largefile because largefile-ness
279 # doesn't change after rename or copy
280 # doesn't change after rename or copy
280 renamed = lfutil.standin(renamed[0])
281 renamed = lfutil.standin(renamed[0])
281
282
282 return context.memfilectx(f, lfiletohash[srcfname] + '\n', 'l' in
283 return context.memfilectx(f, lfiletohash[srcfname] + '\n', 'l' in
283 fctx.flags(), 'x' in fctx.flags(), renamed)
284 fctx.flags(), 'x' in fctx.flags(), renamed)
284 else:
285 else:
285 try:
286 try:
286 fctx = ctx.filectx(f)
287 fctx = ctx.filectx(f)
287 except error.LookupError:
288 except error.LookupError:
288 raise IOError()
289 raise IOError()
289 renamed = fctx.renamed()
290 renamed = fctx.renamed()
290 if renamed:
291 if renamed:
291 renamed = renamed[0]
292 renamed = renamed[0]
292
293
293 data = fctx.data()
294 data = fctx.data()
294 if f == '.hgtags':
295 if f == '.hgtags':
295 newdata = []
296 newdata = []
296 for line in data.splitlines():
297 for line in data.splitlines():
297 id, name = line.split(' ', 1)
298 id, name = line.split(' ', 1)
298 newdata.append('%s %s\n' % (node.hex(revmap[node.bin(id)]),
299 newdata.append('%s %s\n' % (node.hex(revmap[node.bin(id)]),
299 name))
300 name))
300 data = ''.join(newdata)
301 data = ''.join(newdata)
301 return context.memfilectx(f, data, 'l' in fctx.flags(),
302 return context.memfilectx(f, data, 'l' in fctx.flags(),
302 'x' in fctx.flags(), renamed)
303 'x' in fctx.flags(), renamed)
303
304
304 # Commit
305 # Commit
305 mctx = context.memctx(rdst, parents, ctx.description(), dstfiles,
306 mctx = context.memctx(rdst, parents, ctx.description(), dstfiles,
306 getfilectx, ctx.user(), ctx.date(), ctx.extra())
307 getfilectx, ctx.user(), ctx.date(), ctx.extra())
307 ret = rdst.commitctx(mctx)
308 ret = rdst.commitctx(mctx)
308 rdst.dirstate.setparents(ret)
309 rdst.dirstate.setparents(ret)
309 revmap[ctx.node()] = rdst.changelog.tip()
310 revmap[ctx.node()] = rdst.changelog.tip()
310
311
311 def _islfile(file, ctx, matcher, size):
312 def _islfile(file, ctx, matcher, size):
312 '''Return true if file should be considered a largefile, i.e.
313 '''Return true if file should be considered a largefile, i.e.
313 matcher matches it or it is larger than size.'''
314 matcher matches it or it is larger than size.'''
314 # never store special .hg* files as largefiles
315 # never store special .hg* files as largefiles
315 if file == '.hgtags' or file == '.hgignore' or file == '.hgsigs':
316 if file == '.hgtags' or file == '.hgignore' or file == '.hgsigs':
316 return False
317 return False
317 if matcher and matcher(file):
318 if matcher and matcher(file):
318 return True
319 return True
319 try:
320 try:
320 return ctx.filectx(file).size() >= size * 1024 * 1024
321 return ctx.filectx(file).size() >= size * 1024 * 1024
321 except error.LookupError:
322 except error.LookupError:
322 return False
323 return False
323
324
324 def uploadlfiles(ui, rsrc, rdst, files):
325 def uploadlfiles(ui, rsrc, rdst, files):
325 '''upload largefiles to the central store'''
326 '''upload largefiles to the central store'''
326
327
327 if not files:
328 if not files:
328 return
329 return
329
330
330 store = basestore._openstore(rsrc, rdst, put=True)
331 store = basestore._openstore(rsrc, rdst, put=True)
331
332
332 at = 0
333 at = 0
333 files = filter(lambda h: not store.exists(h), files)
334 files = filter(lambda h: not store.exists(h), files)
334 for hash in files:
335 for hash in files:
335 ui.progress(_('uploading largefiles'), at, unit='largefile',
336 ui.progress(_('uploading largefiles'), at, unit='largefile',
336 total=len(files))
337 total=len(files))
337 source = lfutil.findfile(rsrc, hash)
338 source = lfutil.findfile(rsrc, hash)
338 if not source:
339 if not source:
339 raise util.Abort(_('largefile %s missing from store'
340 raise util.Abort(_('largefile %s missing from store'
340 ' (needs to be uploaded)') % hash)
341 ' (needs to be uploaded)') % hash)
341 # XXX check for errors here
342 # XXX check for errors here
342 store.put(source, hash)
343 store.put(source, hash)
343 at += 1
344 at += 1
344 ui.progress(_('uploading largefiles'), None)
345 ui.progress(_('uploading largefiles'), None)
345
346
346 def verifylfiles(ui, repo, all=False, contents=False):
347 def verifylfiles(ui, repo, all=False, contents=False):
347 '''Verify that every big file revision in the current changeset
348 '''Verify that every big file revision in the current changeset
348 exists in the central store. With --contents, also verify that
349 exists in the central store. With --contents, also verify that
349 the contents of each big file revision are correct (SHA-1 hash
350 the contents of each big file revision are correct (SHA-1 hash
350 matches the revision ID). With --all, check every changeset in
351 matches the revision ID). With --all, check every changeset in
351 this repository.'''
352 this repository.'''
352 if all:
353 if all:
353 # Pass a list to the function rather than an iterator because we know a
354 # Pass a list to the function rather than an iterator because we know a
354 # list will work.
355 # list will work.
355 revs = range(len(repo))
356 revs = range(len(repo))
356 else:
357 else:
357 revs = ['.']
358 revs = ['.']
358
359
359 store = basestore._openstore(repo)
360 store = basestore._openstore(repo)
360 return store.verify(revs, contents=contents)
361 return store.verify(revs, contents=contents)
361
362
362 def cachelfiles(ui, repo, node):
363 def cachelfiles(ui, repo, node):
363 '''cachelfiles ensures that all largefiles needed by the specified revision
364 '''cachelfiles ensures that all largefiles needed by the specified revision
364 are present in the repository's largefile cache.
365 are present in the repository's largefile cache.
365
366
366 returns a tuple (cached, missing). cached is the list of files downloaded
367 returns a tuple (cached, missing). cached is the list of files downloaded
367 by this operation; missing is the list of files that were needed but could
368 by this operation; missing is the list of files that were needed but could
368 not be found.'''
369 not be found.'''
369 lfiles = lfutil.listlfiles(repo, node)
370 lfiles = lfutil.listlfiles(repo, node)
370 toget = []
371 toget = []
371
372
372 for lfile in lfiles:
373 for lfile in lfiles:
373 expectedhash = repo[node][lfutil.standin(lfile)].data().strip()
374 expectedhash = repo[node][lfutil.standin(lfile)].data().strip()
374 # if it exists and its hash matches, it might have been locally
375 # if it exists and its hash matches, it might have been locally
375 # modified before updating and the user chose 'local'. in this case,
376 # modified before updating and the user chose 'local'. in this case,
376 # it will not be in any store, so don't look for it.
377 # it will not be in any store, so don't look for it.
377 if ((not os.path.exists(repo.wjoin(lfile)) or
378 if ((not os.path.exists(repo.wjoin(lfile)) or
378 expectedhash != lfutil.hashfile(repo.wjoin(lfile))) and
379 expectedhash != lfutil.hashfile(repo.wjoin(lfile))) and
379 not lfutil.findfile(repo, expectedhash)):
380 not lfutil.findfile(repo, expectedhash)):
380 toget.append((lfile, expectedhash))
381 toget.append((lfile, expectedhash))
381
382
382 if toget:
383 if toget:
383 store = basestore._openstore(repo)
384 store = basestore._openstore(repo)
384 ret = store.get(toget)
385 ret = store.get(toget)
385 return ret
386 return ret
386
387
387 return ([], [])
388 return ([], [])
388
389
389 def updatelfiles(ui, repo, filelist=None, printmessage=True):
390 def updatelfiles(ui, repo, filelist=None, printmessage=True):
390 wlock = repo.wlock()
391 wlock = repo.wlock()
391 try:
392 try:
392 lfdirstate = lfutil.openlfdirstate(ui, repo)
393 lfdirstate = lfutil.openlfdirstate(ui, repo)
393 lfiles = set(lfutil.listlfiles(repo)) | set(lfdirstate)
394 lfiles = set(lfutil.listlfiles(repo)) | set(lfdirstate)
394
395
395 if filelist is not None:
396 if filelist is not None:
396 lfiles = [f for f in lfiles if f in filelist]
397 lfiles = [f for f in lfiles if f in filelist]
397
398
398 printed = False
399 printed = False
399 if printmessage and lfiles:
400 if printmessage and lfiles:
400 ui.status(_('getting changed largefiles\n'))
401 ui.status(_('getting changed largefiles\n'))
401 printed = True
402 printed = True
402 cachelfiles(ui, repo, '.')
403 cachelfiles(ui, repo, '.')
403
404
404 updated, removed = 0, 0
405 updated, removed = 0, 0
405 for i in map(lambda f: _updatelfile(repo, lfdirstate, f), lfiles):
406 for i in map(lambda f: _updatelfile(repo, lfdirstate, f), lfiles):
406 # increment the appropriate counter according to _updatelfile's
407 # increment the appropriate counter according to _updatelfile's
407 # return value
408 # return value
408 updated += i > 0 and i or 0
409 updated += i > 0 and i or 0
409 removed -= i < 0 and i or 0
410 removed -= i < 0 and i or 0
410 if printmessage and (removed or updated) and not printed:
411 if printmessage and (removed or updated) and not printed:
411 ui.status(_('getting changed largefiles\n'))
412 ui.status(_('getting changed largefiles\n'))
412 printed = True
413 printed = True
413
414
414 lfdirstate.write()
415 lfdirstate.write()
415 if printed and printmessage:
416 if printed and printmessage:
416 ui.status(_('%d largefiles updated, %d removed\n') % (updated,
417 ui.status(_('%d largefiles updated, %d removed\n') % (updated,
417 removed))
418 removed))
418 finally:
419 finally:
419 wlock.release()
420 wlock.release()
420
421
421 def _updatelfile(repo, lfdirstate, lfile):
422 def _updatelfile(repo, lfdirstate, lfile):
422 '''updates a single largefile and copies the state of its standin from
423 '''updates a single largefile and copies the state of its standin from
423 the repository's dirstate to its state in the lfdirstate.
424 the repository's dirstate to its state in the lfdirstate.
424
425
425 returns 1 if the file was modified, -1 if the file was removed, 0 if the
426 returns 1 if the file was modified, -1 if the file was removed, 0 if the
426 file was unchanged, and None if the needed largefile was missing from the
427 file was unchanged, and None if the needed largefile was missing from the
427 cache.'''
428 cache.'''
428 ret = 0
429 ret = 0
429 abslfile = repo.wjoin(lfile)
430 abslfile = repo.wjoin(lfile)
430 absstandin = repo.wjoin(lfutil.standin(lfile))
431 absstandin = repo.wjoin(lfutil.standin(lfile))
431 if os.path.exists(absstandin):
432 if os.path.exists(absstandin):
432 if os.path.exists(absstandin+'.orig'):
433 if os.path.exists(absstandin+'.orig'):
433 shutil.copyfile(abslfile, abslfile+'.orig')
434 shutil.copyfile(abslfile, abslfile+'.orig')
434 expecthash = lfutil.readstandin(repo, lfile)
435 expecthash = lfutil.readstandin(repo, lfile)
435 if (expecthash != '' and
436 if (expecthash != '' and
436 (not os.path.exists(abslfile) or
437 (not os.path.exists(abslfile) or
437 expecthash != lfutil.hashfile(abslfile))):
438 expecthash != lfutil.hashfile(abslfile))):
438 if not lfutil.copyfromcache(repo, expecthash, lfile):
439 if not lfutil.copyfromcache(repo, expecthash, lfile):
439 return None # don't try to set the mode or update the dirstate
440 return None # don't try to set the mode or update the dirstate
440 ret = 1
441 ret = 1
441 mode = os.stat(absstandin).st_mode
442 mode = os.stat(absstandin).st_mode
442 if mode != os.stat(abslfile).st_mode:
443 if mode != os.stat(abslfile).st_mode:
443 os.chmod(abslfile, mode)
444 os.chmod(abslfile, mode)
444 ret = 1
445 ret = 1
445 else:
446 else:
446 if os.path.exists(abslfile):
447 if os.path.exists(abslfile):
447 os.unlink(abslfile)
448 os.unlink(abslfile)
448 ret = -1
449 ret = -1
449 state = repo.dirstate[lfutil.standin(lfile)]
450 state = repo.dirstate[lfutil.standin(lfile)]
450 if state == 'n':
451 if state == 'n':
451 lfdirstate.normal(lfile)
452 lfdirstate.normal(lfile)
452 elif state == 'r':
453 elif state == 'r':
453 lfdirstate.remove(lfile)
454 lfdirstate.remove(lfile)
454 elif state == 'a':
455 elif state == 'a':
455 lfdirstate.add(lfile)
456 lfdirstate.add(lfile)
456 elif state == '?':
457 elif state == '?':
457 lfdirstate.drop(lfile)
458 lfdirstate.drop(lfile)
458 return ret
459 return ret
459
460
460 # -- hg commands declarations ------------------------------------------------
461 # -- hg commands declarations ------------------------------------------------
461
462
462 cmdtable = {
463 cmdtable = {
463 'lfconvert': (lfconvert,
464 'lfconvert': (lfconvert,
464 [('s', 'size', '',
465 [('s', 'size', '',
465 _('minimum size (MB) for files to be converted '
466 _('minimum size (MB) for files to be converted '
466 'as largefiles'),
467 'as largefiles'),
467 'SIZE'),
468 'SIZE'),
468 ('', 'to-normal', False,
469 ('', 'to-normal', False,
469 _('convert from a largefiles repo to a normal repo')),
470 _('convert from a largefiles repo to a normal repo')),
470 ],
471 ],
471 _('hg lfconvert SOURCE DEST [FILE ...]')),
472 _('hg lfconvert SOURCE DEST [FILE ...]')),
472 }
473 }
@@ -1,80 +1,86 b''
1 $ cat >> $HGRCPATH <<EOF
1 $ cat >> $HGRCPATH <<EOF
2 > [extensions]
2 > [extensions]
3 > largefiles =
3 > largefiles =
4 > share =
4 > share =
5 > [largefiles]
5 > [largefiles]
6 > minsize = 0.5
6 > minsize = 0.5
7 > patterns = **.dat
7 > patterns = **.dat
8 > EOF
8 > EOF
9
9
10 "lfconvert" works
10 "lfconvert" works
11 $ hg init bigfile-repo
11 $ hg init bigfile-repo
12 $ cd bigfile-repo
12 $ cd bigfile-repo
13 $ dd if=/dev/zero bs=1k count=256 > a-large-file 2> /dev/null
13 $ dd if=/dev/zero bs=1k count=256 > a-large-file 2> /dev/null
14 $ hg addremove
14 $ hg addremove
15 adding a-large-file
15 adding a-large-file
16 $ hg commit -m "add a-large-file (as a normal file)"
16 $ hg commit -m "add a-large-file (as a normal file)"
17 $ find .hg/largefiles
17 $ find .hg/largefiles
18 .hg/largefiles
18 .hg/largefiles
19 $ cd ..
19 $ cd ..
20 $ hg lfconvert --size 0.2 bigfile-repo largefiles-repo
20 $ hg lfconvert --size 0.2 bigfile-repo largefiles-repo
21 initializing destination largefiles-repo
21 initializing destination largefiles-repo
22
22
23 "lfconvert" adds 'largefiles' to .hg/requires.
23 "lfconvert" adds 'largefiles' to .hg/requires.
24 $ cat largefiles-repo/.hg/requires
24 $ cat largefiles-repo/.hg/requires
25 largefiles
25 largefiles
26 revlogv1
26 revlogv1
27 fncache
27 fncache
28 store
28 store
29 dotencode
29 dotencode
30
30
31 "lfconvert" includes a newline at the end of the standin files.
31 "lfconvert" includes a newline at the end of the standin files.
32 $ cd largefiles-repo
32 $ cd largefiles-repo
33 $ hg up
33 $ hg up
34 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
34 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
35 getting changed largefiles
35 getting changed largefiles
36 1 largefiles updated, 0 removed
36 1 largefiles updated, 0 removed
37 $ cat .hglf/a-large-file
37 $ cat .hglf/a-large-file
38 2e000fa7e85759c7f4c254d4d9c33ef481e459a7
38 2e000fa7e85759c7f4c254d4d9c33ef481e459a7
39 $ dd if=/dev/zero bs=1k count=1k > another-large-file 2> /dev/null
39 $ dd if=/dev/zero bs=1k count=1k > another-large-file 2> /dev/null
40 $ hg add --lfsize=1 another-large-file
40 $ hg add --lfsize=1 another-large-file
41 $ hg commit -m "add another-large-file (should be a largefile)"
41 $ hg commit -m "add another-large-file (should be a largefile)"
42 $ cat .hglf/a-large-file .hglf/another-large-file
42 $ cat .hglf/a-large-file .hglf/another-large-file
43 2e000fa7e85759c7f4c254d4d9c33ef481e459a7
43 2e000fa7e85759c7f4c254d4d9c33ef481e459a7
44 3b71f43ff30f4b15b5cd85dd9e95ebc7e84eb5a3
44 3b71f43ff30f4b15b5cd85dd9e95ebc7e84eb5a3
45 $ cd ..
45 $ cd ..
46
46
47 "lfconvert" error cases
47 "lfconvert" error cases
48 $ hg lfconvert http://localhost/foo foo
49 abort: http://localhost/foo is not a local Mercurial repo
50 [255]
51 $ hg lfconvert foo ssh://localhost/foo
52 abort: ssh://localhost/foo is not a local Mercurial repo
53 [255]
48 $ hg lfconvert nosuchrepo foo
54 $ hg lfconvert nosuchrepo foo
49 abort: repository nosuchrepo not found!
55 abort: repository nosuchrepo not found!
50 [255]
56 [255]
51 $ hg share -q -U bigfile-repo shared
57 $ hg share -q -U bigfile-repo shared
52 $ echo -n bogus > shared/.hg/sharedpath
58 $ echo -n bogus > shared/.hg/sharedpath
53 $ hg lfconvert shared foo
59 $ hg lfconvert shared foo
54 abort: .hg/sharedpath points to nonexistent directory $TESTTMP/bogus!
60 abort: .hg/sharedpath points to nonexistent directory $TESTTMP/bogus!
55 [255]
61 [255]
56 $ hg lfconvert bigfile-repo largefiles-repo
62 $ hg lfconvert bigfile-repo largefiles-repo
57 initializing destination largefiles-repo
63 initializing destination largefiles-repo
58 abort: repository largefiles-repo already exists!
64 abort: repository largefiles-repo already exists!
59 [255]
65 [255]
60
66
61 Convert back to a normal (non-largefiles) repo
67 Convert back to a normal (non-largefiles) repo
62 $ cd largefiles-repo
68 $ cd largefiles-repo
63 $ hg lfconvert --to-normal . ../normal-repo
69 $ hg lfconvert --to-normal . ../normal-repo
64 initializing destination ../normal-repo
70 initializing destination ../normal-repo
65 $ cd ../normal-repo
71 $ cd ../normal-repo
66 $ cat >> .hg/hgrc <<EOF
72 $ cat >> .hg/hgrc <<EOF
67 > [extensions]
73 > [extensions]
68 > largefiles = !
74 > largefiles = !
69 > EOF
75 > EOF
70 $ hg update
76 $ hg update
71 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
77 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
72 $ hg locate
78 $ hg locate
73 a-large-file
79 a-large-file
74 another-large-file
80 another-large-file
75 $ [ -d .hg/largefiles ] && echo fail || echo pass
81 $ [ -d .hg/largefiles ] && echo fail || echo pass
76 pass
82 pass
77
83
78 Cleanup
84 Cleanup
79 $ cd ..
85 $ cd ..
80 $ rm -rf bigfile-repo largefiles-repo normal-repo
86 $ rm -rf bigfile-repo largefiles-repo normal-repo
General Comments 0
You need to be logged in to leave comments. Login now