##// END OF EJS Templates
largefiles: allow minimum size to be a float...
Greg Ward -
r15228:ee625de3 default
parent child Browse files
Show More
@@ -1,473 +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 functions: lfadd() et. al, plus the cmdtable.'''
9 '''High-level command functions: lfadd() et. al, 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 source repository creating an identical repository, except that all
25 Convert source repository creating an identical repository, except that all
26 files that match the patterns given, or are over the given size will be
26 files that match the patterns given, or are over the given size will be
27 added as largefiles. The size used to determine whether or not to track a
27 added as largefiles. The size used to determine whether or not to track a
28 file as a largefile is the size of the first version of the file. After
28 file as a largefile is the size of the first version of the file. After
29 running this command you will need to make sure that largefiles is enabled
29 running this command you will need to make sure that largefiles is enabled
30 anywhere you intend to push the new repository.'''
30 anywhere you intend to push the new repository.'''
31
31
32 if opts['tonormal']:
32 if opts['tonormal']:
33 tolfile = False
33 tolfile = False
34 else:
34 else:
35 tolfile = True
35 tolfile = True
36 size = lfutil.getminsize(ui, True, opts.get('size'), default=None)
36 size = lfutil.getminsize(ui, True, opts.get('size'), default=None)
37 try:
37 try:
38 rsrc = hg.repository(ui, src)
38 rsrc = hg.repository(ui, src)
39 if not rsrc.local():
39 if not rsrc.local():
40 raise util.Abort(_('%s is not a local Mercurial repo') % src)
40 raise util.Abort(_('%s is not a local Mercurial repo') % src)
41 except error.RepoError, err:
41 except error.RepoError, err:
42 ui.traceback()
42 ui.traceback()
43 raise util.Abort(err.args[0])
43 raise util.Abort(err.args[0])
44 if os.path.exists(dest):
44 if os.path.exists(dest):
45 if not os.path.isdir(dest):
45 if not os.path.isdir(dest):
46 raise util.Abort(_('destination %s already exists') % dest)
46 raise util.Abort(_('destination %s already exists') % dest)
47 elif os.listdir(dest):
47 elif os.listdir(dest):
48 raise util.Abort(_('destination %s is not empty') % dest)
48 raise util.Abort(_('destination %s is not empty') % dest)
49 try:
49 try:
50 ui.status(_('initializing destination %s\n') % dest)
50 ui.status(_('initializing destination %s\n') % dest)
51 rdst = hg.repository(ui, dest, create=True)
51 rdst = hg.repository(ui, dest, create=True)
52 if not rdst.local():
52 if not rdst.local():
53 raise util.Abort(_('%s is not a local Mercurial repo') % dest)
53 raise util.Abort(_('%s is not a local Mercurial repo') % dest)
54 except error.RepoError:
54 except error.RepoError:
55 ui.traceback()
55 ui.traceback()
56 raise util.Abort(_('%s is not a repo') % dest)
56 raise util.Abort(_('%s is not a repo') % dest)
57
57
58 success = False
58 success = False
59 try:
59 try:
60 # Lock destination to prevent modification while it is converted to.
60 # Lock destination to prevent modification while it is converted to.
61 # Don't need to lock src because we are just reading from its history
61 # Don't need to lock src because we are just reading from its history
62 # which can't change.
62 # which can't change.
63 dst_lock = rdst.lock()
63 dst_lock = rdst.lock()
64
64
65 # Get a list of all changesets in the source. The easy way to do this
65 # Get a list of all changesets in the source. The easy way to do this
66 # is to simply walk the changelog, using changelog.nodesbewteen().
66 # is to simply walk the changelog, using changelog.nodesbewteen().
67 # Take a look at mercurial/revlog.py:639 for more details.
67 # Take a look at mercurial/revlog.py:639 for more details.
68 # Use a generator instead of a list to decrease memory usage
68 # Use a generator instead of a list to decrease memory usage
69 ctxs = (rsrc[ctx] for ctx in rsrc.changelog.nodesbetween(None,
69 ctxs = (rsrc[ctx] for ctx in rsrc.changelog.nodesbetween(None,
70 rsrc.heads())[0])
70 rsrc.heads())[0])
71 revmap = {node.nullid: node.nullid}
71 revmap = {node.nullid: node.nullid}
72 if tolfile:
72 if tolfile:
73 lfiles = set()
73 lfiles = set()
74 normalfiles = set()
74 normalfiles = set()
75 if not pats:
75 if not pats:
76 pats = ui.config(lfutil.longname, 'patterns', default=())
76 pats = ui.config(lfutil.longname, 'patterns', default=())
77 if pats:
77 if pats:
78 pats = pats.split(' ')
78 pats = pats.split(' ')
79 if pats:
79 if pats:
80 matcher = match_.match(rsrc.root, '', list(pats))
80 matcher = match_.match(rsrc.root, '', list(pats))
81 else:
81 else:
82 matcher = None
82 matcher = None
83
83
84 lfiletohash = {}
84 lfiletohash = {}
85 for ctx in ctxs:
85 for ctx in ctxs:
86 ui.progress(_('converting revisions'), ctx.rev(),
86 ui.progress(_('converting revisions'), ctx.rev(),
87 unit=_('revision'), total=rsrc['tip'].rev())
87 unit=_('revision'), total=rsrc['tip'].rev())
88 _lfconvert_addchangeset(rsrc, rdst, ctx, revmap,
88 _lfconvert_addchangeset(rsrc, rdst, ctx, revmap,
89 lfiles, normalfiles, matcher, size, lfiletohash)
89 lfiles, normalfiles, matcher, size, lfiletohash)
90 ui.progress(_('converting revisions'), None)
90 ui.progress(_('converting revisions'), None)
91
91
92 if os.path.exists(rdst.wjoin(lfutil.shortname)):
92 if os.path.exists(rdst.wjoin(lfutil.shortname)):
93 shutil.rmtree(rdst.wjoin(lfutil.shortname))
93 shutil.rmtree(rdst.wjoin(lfutil.shortname))
94
94
95 for f in lfiletohash.keys():
95 for f in lfiletohash.keys():
96 if os.path.isfile(rdst.wjoin(f)):
96 if os.path.isfile(rdst.wjoin(f)):
97 os.unlink(rdst.wjoin(f))
97 os.unlink(rdst.wjoin(f))
98 try:
98 try:
99 os.removedirs(os.path.dirname(rdst.wjoin(f)))
99 os.removedirs(os.path.dirname(rdst.wjoin(f)))
100 except OSError:
100 except OSError:
101 pass
101 pass
102
102
103 else:
103 else:
104 for ctx in ctxs:
104 for ctx in ctxs:
105 ui.progress(_('converting revisions'), ctx.rev(),
105 ui.progress(_('converting revisions'), ctx.rev(),
106 unit=_('revision'), total=rsrc['tip'].rev())
106 unit=_('revision'), total=rsrc['tip'].rev())
107 _addchangeset(ui, rsrc, rdst, ctx, revmap)
107 _addchangeset(ui, rsrc, rdst, ctx, revmap)
108
108
109 ui.progress(_('converting revisions'), None)
109 ui.progress(_('converting revisions'), None)
110 success = True
110 success = True
111 finally:
111 finally:
112 if not success:
112 if not success:
113 # we failed, remove the new directory
113 # we failed, remove the new directory
114 shutil.rmtree(rdst.root)
114 shutil.rmtree(rdst.root)
115 dst_lock.release()
115 dst_lock.release()
116
116
117 def _addchangeset(ui, rsrc, rdst, ctx, revmap):
117 def _addchangeset(ui, rsrc, rdst, ctx, revmap):
118 # Convert src parents to dst parents
118 # Convert src parents to dst parents
119 parents = []
119 parents = []
120 for p in ctx.parents():
120 for p in ctx.parents():
121 parents.append(revmap[p.node()])
121 parents.append(revmap[p.node()])
122 while len(parents) < 2:
122 while len(parents) < 2:
123 parents.append(node.nullid)
123 parents.append(node.nullid)
124
124
125 # Generate list of changed files
125 # Generate list of changed files
126 files = set(ctx.files())
126 files = set(ctx.files())
127 if node.nullid not in parents:
127 if node.nullid not in parents:
128 mc = ctx.manifest()
128 mc = ctx.manifest()
129 mp1 = ctx.parents()[0].manifest()
129 mp1 = ctx.parents()[0].manifest()
130 mp2 = ctx.parents()[1].manifest()
130 mp2 = ctx.parents()[1].manifest()
131 files |= (set(mp1) | set(mp2)) - set(mc)
131 files |= (set(mp1) | set(mp2)) - set(mc)
132 for f in mc:
132 for f in mc:
133 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
133 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
134 files.add(f)
134 files.add(f)
135
135
136 def getfilectx(repo, memctx, f):
136 def getfilectx(repo, memctx, f):
137 if lfutil.standin(f) in files:
137 if lfutil.standin(f) in files:
138 # if the file isn't in the manifest then it was removed
138 # if the file isn't in the manifest then it was removed
139 # or renamed, raise IOError to indicate this
139 # or renamed, raise IOError to indicate this
140 try:
140 try:
141 fctx = ctx.filectx(lfutil.standin(f))
141 fctx = ctx.filectx(lfutil.standin(f))
142 except error.LookupError:
142 except error.LookupError:
143 raise IOError()
143 raise IOError()
144 renamed = fctx.renamed()
144 renamed = fctx.renamed()
145 if renamed:
145 if renamed:
146 renamed = lfutil.splitstandin(renamed[0])
146 renamed = lfutil.splitstandin(renamed[0])
147
147
148 hash = fctx.data().strip()
148 hash = fctx.data().strip()
149 path = lfutil.findfile(rsrc, hash)
149 path = lfutil.findfile(rsrc, hash)
150 ### TODO: What if the file is not cached?
150 ### TODO: What if the file is not cached?
151 data = ''
151 data = ''
152 fd = None
152 fd = None
153 try:
153 try:
154 fd = open(path, 'rb')
154 fd = open(path, 'rb')
155 data = fd.read()
155 data = fd.read()
156 finally:
156 finally:
157 if fd:
157 if fd:
158 fd.close()
158 fd.close()
159 return context.memfilectx(f, data, 'l' in fctx.flags(),
159 return context.memfilectx(f, data, 'l' in fctx.flags(),
160 'x' in fctx.flags(), renamed)
160 'x' in fctx.flags(), renamed)
161 else:
161 else:
162 try:
162 try:
163 fctx = ctx.filectx(f)
163 fctx = ctx.filectx(f)
164 except error.LookupError:
164 except error.LookupError:
165 raise IOError()
165 raise IOError()
166 renamed = fctx.renamed()
166 renamed = fctx.renamed()
167 if renamed:
167 if renamed:
168 renamed = renamed[0]
168 renamed = renamed[0]
169 data = fctx.data()
169 data = fctx.data()
170 if f == '.hgtags':
170 if f == '.hgtags':
171 newdata = []
171 newdata = []
172 for line in data.splitlines():
172 for line in data.splitlines():
173 id, name = line.split(' ', 1)
173 id, name = line.split(' ', 1)
174 newdata.append('%s %s\n' % (node.hex(revmap[node.bin(id)]),
174 newdata.append('%s %s\n' % (node.hex(revmap[node.bin(id)]),
175 name))
175 name))
176 data = ''.join(newdata)
176 data = ''.join(newdata)
177 return context.memfilectx(f, data, 'l' in fctx.flags(),
177 return context.memfilectx(f, data, 'l' in fctx.flags(),
178 'x' in fctx.flags(), renamed)
178 'x' in fctx.flags(), renamed)
179
179
180 dstfiles = []
180 dstfiles = []
181 for file in files:
181 for file in files:
182 if lfutil.isstandin(file):
182 if lfutil.isstandin(file):
183 dstfiles.append(lfutil.splitstandin(file))
183 dstfiles.append(lfutil.splitstandin(file))
184 else:
184 else:
185 dstfiles.append(file)
185 dstfiles.append(file)
186 # Commit
186 # Commit
187 mctx = context.memctx(rdst, parents, ctx.description(), dstfiles,
187 mctx = context.memctx(rdst, parents, ctx.description(), dstfiles,
188 getfilectx, ctx.user(), ctx.date(), ctx.extra())
188 getfilectx, ctx.user(), ctx.date(), ctx.extra())
189 ret = rdst.commitctx(mctx)
189 ret = rdst.commitctx(mctx)
190 rdst.dirstate.setparents(ret)
190 rdst.dirstate.setparents(ret)
191 revmap[ctx.node()] = rdst.changelog.tip()
191 revmap[ctx.node()] = rdst.changelog.tip()
192
192
193 def _lfconvert_addchangeset(rsrc, rdst, ctx, revmap, lfiles, normalfiles,
193 def _lfconvert_addchangeset(rsrc, rdst, ctx, revmap, lfiles, normalfiles,
194 matcher, size, lfiletohash):
194 matcher, size, lfiletohash):
195 # Convert src parents to dst parents
195 # Convert src parents to dst parents
196 parents = []
196 parents = []
197 for p in ctx.parents():
197 for p in ctx.parents():
198 parents.append(revmap[p.node()])
198 parents.append(revmap[p.node()])
199 while len(parents) < 2:
199 while len(parents) < 2:
200 parents.append(node.nullid)
200 parents.append(node.nullid)
201
201
202 # Generate list of changed files
202 # Generate list of changed files
203 files = set(ctx.files())
203 files = set(ctx.files())
204 if node.nullid not in parents:
204 if node.nullid not in parents:
205 mc = ctx.manifest()
205 mc = ctx.manifest()
206 mp1 = ctx.parents()[0].manifest()
206 mp1 = ctx.parents()[0].manifest()
207 mp2 = ctx.parents()[1].manifest()
207 mp2 = ctx.parents()[1].manifest()
208 files |= (set(mp1) | set(mp2)) - set(mc)
208 files |= (set(mp1) | set(mp2)) - set(mc)
209 for f in mc:
209 for f in mc:
210 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
210 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
211 files.add(f)
211 files.add(f)
212
212
213 dstfiles = []
213 dstfiles = []
214 for f in files:
214 for f in files:
215 if f not in lfiles and f not in normalfiles:
215 if f not in lfiles and f not in normalfiles:
216 islfile = _islfile(f, ctx, matcher, size)
216 islfile = _islfile(f, ctx, matcher, size)
217 # If this file was renamed or copied then copy
217 # If this file was renamed or copied then copy
218 # the lfileness of its predecessor
218 # the lfileness of its predecessor
219 if f in ctx.manifest():
219 if f in ctx.manifest():
220 fctx = ctx.filectx(f)
220 fctx = ctx.filectx(f)
221 renamed = fctx.renamed()
221 renamed = fctx.renamed()
222 renamedlfile = renamed and renamed[0] in lfiles
222 renamedlfile = renamed and renamed[0] in lfiles
223 islfile |= renamedlfile
223 islfile |= renamedlfile
224 if 'l' in fctx.flags():
224 if 'l' in fctx.flags():
225 if renamedlfile:
225 if renamedlfile:
226 raise util.Abort(
226 raise util.Abort(
227 _('Renamed/copied largefile %s becomes symlink')
227 _('Renamed/copied largefile %s becomes symlink')
228 % f)
228 % f)
229 islfile = False
229 islfile = False
230 if islfile:
230 if islfile:
231 lfiles.add(f)
231 lfiles.add(f)
232 else:
232 else:
233 normalfiles.add(f)
233 normalfiles.add(f)
234
234
235 if f in lfiles:
235 if f in lfiles:
236 dstfiles.append(lfutil.standin(f))
236 dstfiles.append(lfutil.standin(f))
237 # lfile in manifest if it has not been removed/renamed
237 # lfile in manifest if it has not been removed/renamed
238 if f in ctx.manifest():
238 if f in ctx.manifest():
239 if 'l' in ctx.filectx(f).flags():
239 if 'l' in ctx.filectx(f).flags():
240 if renamed and renamed[0] in lfiles:
240 if renamed and renamed[0] in lfiles:
241 raise util.Abort(_('largefile %s becomes symlink') % f)
241 raise util.Abort(_('largefile %s becomes symlink') % f)
242
242
243 # lfile was modified, update standins
243 # lfile was modified, update standins
244 fullpath = rdst.wjoin(f)
244 fullpath = rdst.wjoin(f)
245 lfutil.createdir(os.path.dirname(fullpath))
245 lfutil.createdir(os.path.dirname(fullpath))
246 m = util.sha1('')
246 m = util.sha1('')
247 m.update(ctx[f].data())
247 m.update(ctx[f].data())
248 hash = m.hexdigest()
248 hash = m.hexdigest()
249 if f not in lfiletohash or lfiletohash[f] != hash:
249 if f not in lfiletohash or lfiletohash[f] != hash:
250 try:
250 try:
251 fd = open(fullpath, 'wb')
251 fd = open(fullpath, 'wb')
252 fd.write(ctx[f].data())
252 fd.write(ctx[f].data())
253 finally:
253 finally:
254 if fd:
254 if fd:
255 fd.close()
255 fd.close()
256 executable = 'x' in ctx[f].flags()
256 executable = 'x' in ctx[f].flags()
257 os.chmod(fullpath, lfutil.getmode(executable))
257 os.chmod(fullpath, lfutil.getmode(executable))
258 lfutil.writestandin(rdst, lfutil.standin(f), hash,
258 lfutil.writestandin(rdst, lfutil.standin(f), hash,
259 executable)
259 executable)
260 lfiletohash[f] = hash
260 lfiletohash[f] = hash
261 else:
261 else:
262 # normal file
262 # normal file
263 dstfiles.append(f)
263 dstfiles.append(f)
264
264
265 def getfilectx(repo, memctx, f):
265 def getfilectx(repo, memctx, f):
266 if lfutil.isstandin(f):
266 if lfutil.isstandin(f):
267 # if the file isn't in the manifest then it was removed
267 # if the file isn't in the manifest then it was removed
268 # or renamed, raise IOError to indicate this
268 # or renamed, raise IOError to indicate this
269 srcfname = lfutil.splitstandin(f)
269 srcfname = lfutil.splitstandin(f)
270 try:
270 try:
271 fctx = ctx.filectx(srcfname)
271 fctx = ctx.filectx(srcfname)
272 except error.LookupError:
272 except error.LookupError:
273 raise IOError()
273 raise IOError()
274 renamed = fctx.renamed()
274 renamed = fctx.renamed()
275 if renamed:
275 if renamed:
276 # standin is always a lfile because lfileness
276 # standin is always a lfile because lfileness
277 # doesn't change after rename or copy
277 # doesn't change after rename or copy
278 renamed = lfutil.standin(renamed[0])
278 renamed = lfutil.standin(renamed[0])
279
279
280 return context.memfilectx(f, lfiletohash[srcfname], 'l' in
280 return context.memfilectx(f, lfiletohash[srcfname], 'l' in
281 fctx.flags(), 'x' in fctx.flags(), renamed)
281 fctx.flags(), 'x' in fctx.flags(), renamed)
282 else:
282 else:
283 try:
283 try:
284 fctx = ctx.filectx(f)
284 fctx = ctx.filectx(f)
285 except error.LookupError:
285 except error.LookupError:
286 raise IOError()
286 raise IOError()
287 renamed = fctx.renamed()
287 renamed = fctx.renamed()
288 if renamed:
288 if renamed:
289 renamed = renamed[0]
289 renamed = renamed[0]
290
290
291 data = fctx.data()
291 data = fctx.data()
292 if f == '.hgtags':
292 if f == '.hgtags':
293 newdata = []
293 newdata = []
294 for line in data.splitlines():
294 for line in data.splitlines():
295 id, name = line.split(' ', 1)
295 id, name = line.split(' ', 1)
296 newdata.append('%s %s\n' % (node.hex(revmap[node.bin(id)]),
296 newdata.append('%s %s\n' % (node.hex(revmap[node.bin(id)]),
297 name))
297 name))
298 data = ''.join(newdata)
298 data = ''.join(newdata)
299 return context.memfilectx(f, data, 'l' in fctx.flags(),
299 return context.memfilectx(f, data, 'l' in fctx.flags(),
300 'x' in fctx.flags(), renamed)
300 'x' in fctx.flags(), renamed)
301
301
302 # Commit
302 # Commit
303 mctx = context.memctx(rdst, parents, ctx.description(), dstfiles,
303 mctx = context.memctx(rdst, parents, ctx.description(), dstfiles,
304 getfilectx, ctx.user(), ctx.date(), ctx.extra())
304 getfilectx, ctx.user(), ctx.date(), ctx.extra())
305 ret = rdst.commitctx(mctx)
305 ret = rdst.commitctx(mctx)
306 rdst.dirstate.setparents(ret)
306 rdst.dirstate.setparents(ret)
307 revmap[ctx.node()] = rdst.changelog.tip()
307 revmap[ctx.node()] = rdst.changelog.tip()
308
308
309 def _islfile(file, ctx, matcher, size):
309 def _islfile(file, ctx, matcher, size):
310 '''
310 '''
311 A file is a lfile if it matches a pattern or is over
311 A file is a lfile if it matches a pattern or is over
312 the given size.
312 the given size.
313 '''
313 '''
314 # Never store hgtags or hgignore as lfiles
314 # Never store hgtags or hgignore as lfiles
315 if file == '.hgtags' or file == '.hgignore' or file == '.hgsigs':
315 if file == '.hgtags' or file == '.hgignore' or file == '.hgsigs':
316 return False
316 return False
317 if matcher and matcher(file):
317 if matcher and matcher(file):
318 return True
318 return True
319 try:
319 try:
320 return ctx.filectx(file).size() >= size * 1024 * 1024
320 return ctx.filectx(file).size() >= size * 1024 * 1024
321 except error.LookupError:
321 except error.LookupError:
322 return False
322 return False
323
323
324 def uploadlfiles(ui, rsrc, rdst, files):
324 def uploadlfiles(ui, rsrc, rdst, files):
325 '''upload largefiles to the central store'''
325 '''upload largefiles to the central store'''
326
326
327 # Don't upload locally. All largefiles are in the system wide cache
327 # Don't upload locally. All largefiles are in the system wide cache
328 # so the other repo can just get them from there.
328 # so the other repo can just get them from there.
329 if not files or rdst.local():
329 if not files or rdst.local():
330 return
330 return
331
331
332 store = basestore._openstore(rsrc, rdst, put=True)
332 store = basestore._openstore(rsrc, rdst, put=True)
333
333
334 at = 0
334 at = 0
335 files = filter(lambda h: not store.exists(h), files)
335 files = filter(lambda h: not store.exists(h), files)
336 for hash in files:
336 for hash in files:
337 ui.progress(_('uploading largefiles'), at, unit='largefile',
337 ui.progress(_('uploading largefiles'), at, unit='largefile',
338 total=len(files))
338 total=len(files))
339 source = lfutil.findfile(rsrc, hash)
339 source = lfutil.findfile(rsrc, hash)
340 if not source:
340 if not source:
341 raise util.Abort(_('Missing largefile %s needs to be uploaded')
341 raise util.Abort(_('Missing largefile %s needs to be uploaded')
342 % hash)
342 % hash)
343 # XXX check for errors here
343 # XXX check for errors here
344 store.put(source, hash)
344 store.put(source, hash)
345 at += 1
345 at += 1
346 ui.progress(_('uploading largefiles'), None)
346 ui.progress(_('uploading largefiles'), None)
347
347
348 def verifylfiles(ui, repo, all=False, contents=False):
348 def verifylfiles(ui, repo, all=False, contents=False):
349 '''Verify that every big file revision in the current changeset
349 '''Verify that every big file revision in the current changeset
350 exists in the central store. With --contents, also verify that
350 exists in the central store. With --contents, also verify that
351 the contents of each big file revision are correct (SHA-1 hash
351 the contents of each big file revision are correct (SHA-1 hash
352 matches the revision ID). With --all, check every changeset in
352 matches the revision ID). With --all, check every changeset in
353 this repository.'''
353 this repository.'''
354 if all:
354 if all:
355 # Pass a list to the function rather than an iterator because we know a
355 # Pass a list to the function rather than an iterator because we know a
356 # list will work.
356 # list will work.
357 revs = range(len(repo))
357 revs = range(len(repo))
358 else:
358 else:
359 revs = ['.']
359 revs = ['.']
360
360
361 store = basestore._openstore(repo)
361 store = basestore._openstore(repo)
362 return store.verify(revs, contents=contents)
362 return store.verify(revs, contents=contents)
363
363
364 def cachelfiles(ui, repo, node):
364 def cachelfiles(ui, repo, node):
365 '''cachelfiles ensures that all largefiles needed by the specified revision
365 '''cachelfiles ensures that all largefiles needed by the specified revision
366 are present in the repository's largefile cache.
366 are present in the repository's largefile cache.
367
367
368 returns a tuple (cached, missing). cached is the list of files downloaded
368 returns a tuple (cached, missing). cached is the list of files downloaded
369 by this operation; missing is the list of files that were needed but could
369 by this operation; missing is the list of files that were needed but could
370 not be found.'''
370 not be found.'''
371 lfiles = lfutil.listlfiles(repo, node)
371 lfiles = lfutil.listlfiles(repo, node)
372 toget = []
372 toget = []
373
373
374 for lfile in lfiles:
374 for lfile in lfiles:
375 expectedhash = repo[node][lfutil.standin(lfile)].data().strip()
375 expectedhash = repo[node][lfutil.standin(lfile)].data().strip()
376 # if it exists and its hash matches, it might have been locally
376 # if it exists and its hash matches, it might have been locally
377 # modified before updating and the user chose 'local'. in this case,
377 # modified before updating and the user chose 'local'. in this case,
378 # it will not be in any store, so don't look for it.
378 # it will not be in any store, so don't look for it.
379 if (not os.path.exists(repo.wjoin(lfile)) \
379 if (not os.path.exists(repo.wjoin(lfile)) \
380 or expectedhash != lfutil.hashfile(repo.wjoin(lfile))) and \
380 or expectedhash != lfutil.hashfile(repo.wjoin(lfile))) and \
381 not lfutil.findfile(repo, expectedhash):
381 not lfutil.findfile(repo, expectedhash):
382 toget.append((lfile, expectedhash))
382 toget.append((lfile, expectedhash))
383
383
384 if toget:
384 if toget:
385 store = basestore._openstore(repo)
385 store = basestore._openstore(repo)
386 ret = store.get(toget)
386 ret = store.get(toget)
387 return ret
387 return ret
388
388
389 return ([], [])
389 return ([], [])
390
390
391 def updatelfiles(ui, repo, filelist=None, printmessage=True):
391 def updatelfiles(ui, repo, filelist=None, printmessage=True):
392 wlock = repo.wlock()
392 wlock = repo.wlock()
393 try:
393 try:
394 lfdirstate = lfutil.openlfdirstate(ui, repo)
394 lfdirstate = lfutil.openlfdirstate(ui, repo)
395 lfiles = set(lfutil.listlfiles(repo)) | set(lfdirstate)
395 lfiles = set(lfutil.listlfiles(repo)) | set(lfdirstate)
396
396
397 if filelist is not None:
397 if filelist is not None:
398 lfiles = [f for f in lfiles if f in filelist]
398 lfiles = [f for f in lfiles if f in filelist]
399
399
400 printed = False
400 printed = False
401 if printmessage and lfiles:
401 if printmessage and lfiles:
402 ui.status(_('getting changed largefiles\n'))
402 ui.status(_('getting changed largefiles\n'))
403 printed = True
403 printed = True
404 cachelfiles(ui, repo, '.')
404 cachelfiles(ui, repo, '.')
405
405
406 updated, removed = 0, 0
406 updated, removed = 0, 0
407 for i in map(lambda f: _updatelfile(repo, lfdirstate, f), lfiles):
407 for i in map(lambda f: _updatelfile(repo, lfdirstate, f), lfiles):
408 # increment the appropriate counter according to _updatelfile's
408 # increment the appropriate counter according to _updatelfile's
409 # return value
409 # return value
410 updated += i > 0 and i or 0
410 updated += i > 0 and i or 0
411 removed -= i < 0 and i or 0
411 removed -= i < 0 and i or 0
412 if printmessage and (removed or updated) and not printed:
412 if printmessage and (removed or updated) and not printed:
413 ui.status(_('getting changed largefiles\n'))
413 ui.status(_('getting changed largefiles\n'))
414 printed = True
414 printed = True
415
415
416 lfdirstate.write()
416 lfdirstate.write()
417 if printed and printmessage:
417 if printed and printmessage:
418 ui.status(_('%d largefiles updated, %d removed\n') % (updated,
418 ui.status(_('%d largefiles updated, %d removed\n') % (updated,
419 removed))
419 removed))
420 finally:
420 finally:
421 wlock.release()
421 wlock.release()
422
422
423 def _updatelfile(repo, lfdirstate, lfile):
423 def _updatelfile(repo, lfdirstate, lfile):
424 '''updates a single largefile and copies the state of its standin from
424 '''updates a single largefile and copies the state of its standin from
425 the repository's dirstate to its state in the lfdirstate.
425 the repository's dirstate to its state in the lfdirstate.
426
426
427 returns 1 if the file was modified, -1 if the file was removed, 0 if the
427 returns 1 if the file was modified, -1 if the file was removed, 0 if the
428 file was unchanged, and None if the needed largefile was missing from the
428 file was unchanged, and None if the needed largefile was missing from the
429 cache.'''
429 cache.'''
430 ret = 0
430 ret = 0
431 abslfile = repo.wjoin(lfile)
431 abslfile = repo.wjoin(lfile)
432 absstandin = repo.wjoin(lfutil.standin(lfile))
432 absstandin = repo.wjoin(lfutil.standin(lfile))
433 if os.path.exists(absstandin):
433 if os.path.exists(absstandin):
434 if os.path.exists(absstandin+'.orig'):
434 if os.path.exists(absstandin+'.orig'):
435 shutil.copyfile(abslfile, abslfile+'.orig')
435 shutil.copyfile(abslfile, abslfile+'.orig')
436 expecthash = lfutil.readstandin(repo, lfile)
436 expecthash = lfutil.readstandin(repo, lfile)
437 if expecthash != '' and \
437 if expecthash != '' and \
438 (not os.path.exists(abslfile) or \
438 (not os.path.exists(abslfile) or \
439 expecthash != lfutil.hashfile(abslfile)):
439 expecthash != lfutil.hashfile(abslfile)):
440 if not lfutil.copyfromcache(repo, expecthash, lfile):
440 if not lfutil.copyfromcache(repo, expecthash, lfile):
441 return None # don't try to set the mode or update the dirstate
441 return None # don't try to set the mode or update the dirstate
442 ret = 1
442 ret = 1
443 mode = os.stat(absstandin).st_mode
443 mode = os.stat(absstandin).st_mode
444 if mode != os.stat(abslfile).st_mode:
444 if mode != os.stat(abslfile).st_mode:
445 os.chmod(abslfile, mode)
445 os.chmod(abslfile, mode)
446 ret = 1
446 ret = 1
447 else:
447 else:
448 if os.path.exists(abslfile):
448 if os.path.exists(abslfile):
449 os.unlink(abslfile)
449 os.unlink(abslfile)
450 ret = -1
450 ret = -1
451 state = repo.dirstate[lfutil.standin(lfile)]
451 state = repo.dirstate[lfutil.standin(lfile)]
452 if state == 'n':
452 if state == 'n':
453 lfdirstate.normal(lfile)
453 lfdirstate.normal(lfile)
454 elif state == 'r':
454 elif state == 'r':
455 lfdirstate.remove(lfile)
455 lfdirstate.remove(lfile)
456 elif state == 'a':
456 elif state == 'a':
457 lfdirstate.add(lfile)
457 lfdirstate.add(lfile)
458 elif state == '?':
458 elif state == '?':
459 lfdirstate.drop(lfile)
459 lfdirstate.drop(lfile)
460 return ret
460 return ret
461
461
462 # -- hg commands declarations ------------------------------------------------
462 # -- hg commands declarations ------------------------------------------------
463
463
464
464
465 cmdtable = {
465 cmdtable = {
466 'lfconvert': (lfconvert,
466 'lfconvert': (lfconvert,
467 [('s', 'size', 0, 'All files over this size (in megabytes) '
467 [('s', 'size', '', 'All files over this size (in megabytes) '
468 'will be considered largefiles. This can also be specified '
468 'will be considered largefiles. This can also be specified '
469 'in your hgrc as [largefiles].size.'),
469 'in your hgrc as [largefiles].size.'),
470 ('','tonormal',False,
470 ('','tonormal',False,
471 'Convert from a largefiles repo to a normal repo')],
471 'Convert from a largefiles repo to a normal repo')],
472 _('hg lfconvert SOURCE DEST [FILE ...]')),
472 _('hg lfconvert SOURCE DEST [FILE ...]')),
473 }
473 }
@@ -1,445 +1,445 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 '''largefiles utility code: must not import other modules in this package.'''
9 '''largefiles utility code: must not import other modules in this package.'''
10
10
11 import os
11 import os
12 import errno
12 import errno
13 import shutil
13 import shutil
14 import stat
14 import stat
15 import hashlib
15 import hashlib
16
16
17 from mercurial import dirstate, httpconnection, match as match_, util, scmutil
17 from mercurial import dirstate, httpconnection, match as match_, util, scmutil
18 from mercurial.i18n import _
18 from mercurial.i18n import _
19
19
20 shortname = '.hglf'
20 shortname = '.hglf'
21 longname = 'largefiles'
21 longname = 'largefiles'
22
22
23
23
24 # -- Portability wrappers ----------------------------------------------
24 # -- Portability wrappers ----------------------------------------------
25
25
26 def dirstate_walk(dirstate, matcher, unknown=False, ignored=False):
26 def dirstate_walk(dirstate, matcher, unknown=False, ignored=False):
27 return dirstate.walk(matcher, [], unknown, ignored)
27 return dirstate.walk(matcher, [], unknown, ignored)
28
28
29 def repo_add(repo, list):
29 def repo_add(repo, list):
30 add = repo[None].add
30 add = repo[None].add
31 return add(list)
31 return add(list)
32
32
33 def repo_remove(repo, list, unlink=False):
33 def repo_remove(repo, list, unlink=False):
34 def remove(list, unlink):
34 def remove(list, unlink):
35 wlock = repo.wlock()
35 wlock = repo.wlock()
36 try:
36 try:
37 if unlink:
37 if unlink:
38 for f in list:
38 for f in list:
39 try:
39 try:
40 util.unlinkpath(repo.wjoin(f))
40 util.unlinkpath(repo.wjoin(f))
41 except OSError, inst:
41 except OSError, inst:
42 if inst.errno != errno.ENOENT:
42 if inst.errno != errno.ENOENT:
43 raise
43 raise
44 repo[None].forget(list)
44 repo[None].forget(list)
45 finally:
45 finally:
46 wlock.release()
46 wlock.release()
47 return remove(list, unlink=unlink)
47 return remove(list, unlink=unlink)
48
48
49 def repo_forget(repo, list):
49 def repo_forget(repo, list):
50 forget = repo[None].forget
50 forget = repo[None].forget
51 return forget(list)
51 return forget(list)
52
52
53 def findoutgoing(repo, remote, force):
53 def findoutgoing(repo, remote, force):
54 from mercurial import discovery
54 from mercurial import discovery
55 common, _anyinc, _heads = discovery.findcommonincoming(repo,
55 common, _anyinc, _heads = discovery.findcommonincoming(repo,
56 remote, force=force)
56 remote, force=force)
57 return repo.changelog.findmissing(common)
57 return repo.changelog.findmissing(common)
58
58
59 # -- Private worker functions ------------------------------------------
59 # -- Private worker functions ------------------------------------------
60
60
61 def getminsize(ui, assumelfiles, opt, default=10):
61 def getminsize(ui, assumelfiles, opt, default=10):
62 lfsize = opt
62 lfsize = opt
63 if not lfsize and assumelfiles:
63 if not lfsize and assumelfiles:
64 lfsize = ui.config(longname, 'size', default=default)
64 lfsize = ui.config(longname, 'size', default=default)
65 if lfsize:
65 if lfsize:
66 try:
66 try:
67 lfsize = int(lfsize)
67 lfsize = float(lfsize)
68 except ValueError:
68 except ValueError:
69 raise util.Abort(_('largefiles: size must be an integer, was %s\n')
69 raise util.Abort(_('largefiles: size must be number (not %s)\n')
70 % lfsize)
70 % lfsize)
71 if lfsize is None:
71 if lfsize is None:
72 raise util.Abort(_('minimum size for largefiles must be specified'))
72 raise util.Abort(_('minimum size for largefiles must be specified'))
73 return lfsize
73 return lfsize
74
74
75 def link(src, dest):
75 def link(src, dest):
76 try:
76 try:
77 util.oslink(src, dest)
77 util.oslink(src, dest)
78 except OSError:
78 except OSError:
79 # If hardlinks fail fall back on copy
79 # If hardlinks fail fall back on copy
80 shutil.copyfile(src, dest)
80 shutil.copyfile(src, dest)
81 os.chmod(dest, os.stat(src).st_mode)
81 os.chmod(dest, os.stat(src).st_mode)
82
82
83 def systemcachepath(ui, hash):
83 def systemcachepath(ui, hash):
84 path = ui.config(longname, 'systemcache', None)
84 path = ui.config(longname, 'systemcache', None)
85 if path:
85 if path:
86 path = os.path.join(path, hash)
86 path = os.path.join(path, hash)
87 else:
87 else:
88 if os.name == 'nt':
88 if os.name == 'nt':
89 path = os.path.join(os.getenv('LOCALAPPDATA') or \
89 path = os.path.join(os.getenv('LOCALAPPDATA') or \
90 os.getenv('APPDATA'), longname, hash)
90 os.getenv('APPDATA'), longname, hash)
91 elif os.name == 'posix':
91 elif os.name == 'posix':
92 path = os.path.join(os.getenv('HOME'), '.' + longname, hash)
92 path = os.path.join(os.getenv('HOME'), '.' + longname, hash)
93 else:
93 else:
94 raise util.Abort(_('Unknown operating system: %s\n') % os.name)
94 raise util.Abort(_('Unknown operating system: %s\n') % os.name)
95 return path
95 return path
96
96
97 def insystemcache(ui, hash):
97 def insystemcache(ui, hash):
98 return os.path.exists(systemcachepath(ui, hash))
98 return os.path.exists(systemcachepath(ui, hash))
99
99
100 def findfile(repo, hash):
100 def findfile(repo, hash):
101 if incache(repo, hash):
101 if incache(repo, hash):
102 repo.ui.note(_('Found %s in cache\n') % hash)
102 repo.ui.note(_('Found %s in cache\n') % hash)
103 return cachepath(repo, hash)
103 return cachepath(repo, hash)
104 if insystemcache(repo.ui, hash):
104 if insystemcache(repo.ui, hash):
105 repo.ui.note(_('Found %s in system cache\n') % hash)
105 repo.ui.note(_('Found %s in system cache\n') % hash)
106 return systemcachepath(repo.ui, hash)
106 return systemcachepath(repo.ui, hash)
107 return None
107 return None
108
108
109 class largefiles_dirstate(dirstate.dirstate):
109 class largefiles_dirstate(dirstate.dirstate):
110 def __getitem__(self, key):
110 def __getitem__(self, key):
111 return super(largefiles_dirstate, self).__getitem__(unixpath(key))
111 return super(largefiles_dirstate, self).__getitem__(unixpath(key))
112 def normal(self, f):
112 def normal(self, f):
113 return super(largefiles_dirstate, self).normal(unixpath(f))
113 return super(largefiles_dirstate, self).normal(unixpath(f))
114 def remove(self, f):
114 def remove(self, f):
115 return super(largefiles_dirstate, self).remove(unixpath(f))
115 return super(largefiles_dirstate, self).remove(unixpath(f))
116 def add(self, f):
116 def add(self, f):
117 return super(largefiles_dirstate, self).add(unixpath(f))
117 return super(largefiles_dirstate, self).add(unixpath(f))
118 def drop(self, f):
118 def drop(self, f):
119 return super(largefiles_dirstate, self).drop(unixpath(f))
119 return super(largefiles_dirstate, self).drop(unixpath(f))
120 def forget(self, f):
120 def forget(self, f):
121 return super(largefiles_dirstate, self).forget(unixpath(f))
121 return super(largefiles_dirstate, self).forget(unixpath(f))
122
122
123 def openlfdirstate(ui, repo):
123 def openlfdirstate(ui, repo):
124 '''
124 '''
125 Return a dirstate object that tracks big files: i.e. its root is the
125 Return a dirstate object that tracks big files: i.e. its root is the
126 repo root, but it is saved in .hg/largefiles/dirstate.
126 repo root, but it is saved in .hg/largefiles/dirstate.
127 '''
127 '''
128 admin = repo.join(longname)
128 admin = repo.join(longname)
129 opener = scmutil.opener(admin)
129 opener = scmutil.opener(admin)
130 if util.safehasattr(repo.dirstate, '_validate'):
130 if util.safehasattr(repo.dirstate, '_validate'):
131 lfdirstate = largefiles_dirstate(opener, ui, repo.root,
131 lfdirstate = largefiles_dirstate(opener, ui, repo.root,
132 repo.dirstate._validate)
132 repo.dirstate._validate)
133 else:
133 else:
134 lfdirstate = largefiles_dirstate(opener, ui, repo.root)
134 lfdirstate = largefiles_dirstate(opener, ui, repo.root)
135
135
136 # If the largefiles dirstate does not exist, populate and create it. This
136 # If the largefiles dirstate does not exist, populate and create it. This
137 # ensures that we create it on the first meaningful largefiles operation in
137 # ensures that we create it on the first meaningful largefiles operation in
138 # a new clone. It also gives us an easy way to forcibly rebuild largefiles
138 # a new clone. It also gives us an easy way to forcibly rebuild largefiles
139 # state:
139 # state:
140 # rm .hg/largefiles/dirstate && hg status
140 # rm .hg/largefiles/dirstate && hg status
141 # Or even, if things are really messed up:
141 # Or even, if things are really messed up:
142 # rm -rf .hg/largefiles && hg status
142 # rm -rf .hg/largefiles && hg status
143 if not os.path.exists(os.path.join(admin, 'dirstate')):
143 if not os.path.exists(os.path.join(admin, 'dirstate')):
144 util.makedirs(admin)
144 util.makedirs(admin)
145 matcher = getstandinmatcher(repo)
145 matcher = getstandinmatcher(repo)
146 for standin in dirstate_walk(repo.dirstate, matcher):
146 for standin in dirstate_walk(repo.dirstate, matcher):
147 lfile = splitstandin(standin)
147 lfile = splitstandin(standin)
148 hash = readstandin(repo, lfile)
148 hash = readstandin(repo, lfile)
149 lfdirstate.normallookup(lfile)
149 lfdirstate.normallookup(lfile)
150 try:
150 try:
151 if hash == hashfile(lfile):
151 if hash == hashfile(lfile):
152 lfdirstate.normal(lfile)
152 lfdirstate.normal(lfile)
153 except IOError, err:
153 except IOError, err:
154 if err.errno != errno.ENOENT:
154 if err.errno != errno.ENOENT:
155 raise
155 raise
156
156
157 lfdirstate.write()
157 lfdirstate.write()
158
158
159 return lfdirstate
159 return lfdirstate
160
160
161 def lfdirstate_status(lfdirstate, repo, rev):
161 def lfdirstate_status(lfdirstate, repo, rev):
162 wlock = repo.wlock()
162 wlock = repo.wlock()
163 try:
163 try:
164 match = match_.always(repo.root, repo.getcwd())
164 match = match_.always(repo.root, repo.getcwd())
165 s = lfdirstate.status(match, [], False, False, False)
165 s = lfdirstate.status(match, [], False, False, False)
166 unsure, modified, added, removed, missing, unknown, ignored, clean = s
166 unsure, modified, added, removed, missing, unknown, ignored, clean = s
167 for lfile in unsure:
167 for lfile in unsure:
168 if repo[rev][standin(lfile)].data().strip() != \
168 if repo[rev][standin(lfile)].data().strip() != \
169 hashfile(repo.wjoin(lfile)):
169 hashfile(repo.wjoin(lfile)):
170 modified.append(lfile)
170 modified.append(lfile)
171 else:
171 else:
172 clean.append(lfile)
172 clean.append(lfile)
173 lfdirstate.normal(lfile)
173 lfdirstate.normal(lfile)
174 lfdirstate.write()
174 lfdirstate.write()
175 finally:
175 finally:
176 wlock.release()
176 wlock.release()
177 return (modified, added, removed, missing, unknown, ignored, clean)
177 return (modified, added, removed, missing, unknown, ignored, clean)
178
178
179 def listlfiles(repo, rev=None, matcher=None):
179 def listlfiles(repo, rev=None, matcher=None):
180 '''list largefiles in the working copy or specified changeset'''
180 '''list largefiles in the working copy or specified changeset'''
181
181
182 if matcher is None:
182 if matcher is None:
183 matcher = getstandinmatcher(repo)
183 matcher = getstandinmatcher(repo)
184
184
185 # ignore unknown files in working directory
185 # ignore unknown files in working directory
186 return [splitstandin(f) for f in repo[rev].walk(matcher) \
186 return [splitstandin(f) for f in repo[rev].walk(matcher) \
187 if rev is not None or repo.dirstate[f] != '?']
187 if rev is not None or repo.dirstate[f] != '?']
188
188
189 def incache(repo, hash):
189 def incache(repo, hash):
190 return os.path.exists(cachepath(repo, hash))
190 return os.path.exists(cachepath(repo, hash))
191
191
192 def createdir(dir):
192 def createdir(dir):
193 if not os.path.exists(dir):
193 if not os.path.exists(dir):
194 os.makedirs(dir)
194 os.makedirs(dir)
195
195
196 def cachepath(repo, hash):
196 def cachepath(repo, hash):
197 return repo.join(os.path.join(longname, hash))
197 return repo.join(os.path.join(longname, hash))
198
198
199 def copyfromcache(repo, hash, filename):
199 def copyfromcache(repo, hash, filename):
200 '''copyfromcache copies the specified largefile from the repo or system
200 '''copyfromcache copies the specified largefile from the repo or system
201 cache to the specified location in the repository. It will not throw an
201 cache to the specified location in the repository. It will not throw an
202 exception on failure, as it is meant to be called only after ensuring that
202 exception on failure, as it is meant to be called only after ensuring that
203 the needed largefile exists in the cache.'''
203 the needed largefile exists in the cache.'''
204 path = findfile(repo, hash)
204 path = findfile(repo, hash)
205 if path is None:
205 if path is None:
206 return False
206 return False
207 util.makedirs(os.path.dirname(repo.wjoin(filename)))
207 util.makedirs(os.path.dirname(repo.wjoin(filename)))
208 shutil.copy(path, repo.wjoin(filename))
208 shutil.copy(path, repo.wjoin(filename))
209 return True
209 return True
210
210
211 def copytocache(repo, rev, file, uploaded=False):
211 def copytocache(repo, rev, file, uploaded=False):
212 hash = readstandin(repo, file)
212 hash = readstandin(repo, file)
213 if incache(repo, hash):
213 if incache(repo, hash):
214 return
214 return
215 copytocacheabsolute(repo, repo.wjoin(file), hash)
215 copytocacheabsolute(repo, repo.wjoin(file), hash)
216
216
217 def copytocacheabsolute(repo, file, hash):
217 def copytocacheabsolute(repo, file, hash):
218 createdir(os.path.dirname(cachepath(repo, hash)))
218 createdir(os.path.dirname(cachepath(repo, hash)))
219 if insystemcache(repo.ui, hash):
219 if insystemcache(repo.ui, hash):
220 link(systemcachepath(repo.ui, hash), cachepath(repo, hash))
220 link(systemcachepath(repo.ui, hash), cachepath(repo, hash))
221 else:
221 else:
222 shutil.copyfile(file, cachepath(repo, hash))
222 shutil.copyfile(file, cachepath(repo, hash))
223 os.chmod(cachepath(repo, hash), os.stat(file).st_mode)
223 os.chmod(cachepath(repo, hash), os.stat(file).st_mode)
224 linktosystemcache(repo, hash)
224 linktosystemcache(repo, hash)
225
225
226 def linktosystemcache(repo, hash):
226 def linktosystemcache(repo, hash):
227 createdir(os.path.dirname(systemcachepath(repo.ui, hash)))
227 createdir(os.path.dirname(systemcachepath(repo.ui, hash)))
228 link(cachepath(repo, hash), systemcachepath(repo.ui, hash))
228 link(cachepath(repo, hash), systemcachepath(repo.ui, hash))
229
229
230 def getstandinmatcher(repo, pats=[], opts={}):
230 def getstandinmatcher(repo, pats=[], opts={}):
231 '''Return a match object that applies pats to the standin directory'''
231 '''Return a match object that applies pats to the standin directory'''
232 standindir = repo.pathto(shortname)
232 standindir = repo.pathto(shortname)
233 if pats:
233 if pats:
234 # patterns supplied: search standin directory relative to current dir
234 # patterns supplied: search standin directory relative to current dir
235 cwd = repo.getcwd()
235 cwd = repo.getcwd()
236 if os.path.isabs(cwd):
236 if os.path.isabs(cwd):
237 # cwd is an absolute path for hg -R <reponame>
237 # cwd is an absolute path for hg -R <reponame>
238 # work relative to the repository root in this case
238 # work relative to the repository root in this case
239 cwd = ''
239 cwd = ''
240 pats = [os.path.join(standindir, cwd, pat) for pat in pats]
240 pats = [os.path.join(standindir, cwd, pat) for pat in pats]
241 elif os.path.isdir(standindir):
241 elif os.path.isdir(standindir):
242 # no patterns: relative to repo root
242 # no patterns: relative to repo root
243 pats = [standindir]
243 pats = [standindir]
244 else:
244 else:
245 # no patterns and no standin dir: return matcher that matches nothing
245 # no patterns and no standin dir: return matcher that matches nothing
246 match = match_.match(repo.root, None, [], exact=True)
246 match = match_.match(repo.root, None, [], exact=True)
247 match.matchfn = lambda f: False
247 match.matchfn = lambda f: False
248 return match
248 return match
249 return getmatcher(repo, pats, opts, showbad=False)
249 return getmatcher(repo, pats, opts, showbad=False)
250
250
251 def getmatcher(repo, pats=[], opts={}, showbad=True):
251 def getmatcher(repo, pats=[], opts={}, showbad=True):
252 '''Wrapper around scmutil.match() that adds showbad: if false, neuter
252 '''Wrapper around scmutil.match() that adds showbad: if false, neuter
253 the match object\'s bad() method so it does not print any warnings
253 the match object\'s bad() method so it does not print any warnings
254 about missing files or directories.'''
254 about missing files or directories.'''
255 match = scmutil.match(repo[None], pats, opts)
255 match = scmutil.match(repo[None], pats, opts)
256
256
257 if not showbad:
257 if not showbad:
258 match.bad = lambda f, msg: None
258 match.bad = lambda f, msg: None
259 return match
259 return match
260
260
261 def composestandinmatcher(repo, rmatcher):
261 def composestandinmatcher(repo, rmatcher):
262 '''Return a matcher that accepts standins corresponding to the files
262 '''Return a matcher that accepts standins corresponding to the files
263 accepted by rmatcher. Pass the list of files in the matcher as the
263 accepted by rmatcher. Pass the list of files in the matcher as the
264 paths specified by the user.'''
264 paths specified by the user.'''
265 smatcher = getstandinmatcher(repo, rmatcher.files())
265 smatcher = getstandinmatcher(repo, rmatcher.files())
266 isstandin = smatcher.matchfn
266 isstandin = smatcher.matchfn
267 def composed_matchfn(f):
267 def composed_matchfn(f):
268 return isstandin(f) and rmatcher.matchfn(splitstandin(f))
268 return isstandin(f) and rmatcher.matchfn(splitstandin(f))
269 smatcher.matchfn = composed_matchfn
269 smatcher.matchfn = composed_matchfn
270
270
271 return smatcher
271 return smatcher
272
272
273 def standin(filename):
273 def standin(filename):
274 '''Return the repo-relative path to the standin for the specified big
274 '''Return the repo-relative path to the standin for the specified big
275 file.'''
275 file.'''
276 # Notes:
276 # Notes:
277 # 1) Most callers want an absolute path, but _create_standin() needs
277 # 1) Most callers want an absolute path, but _create_standin() needs
278 # it repo-relative so lfadd() can pass it to repo_add(). So leave
278 # it repo-relative so lfadd() can pass it to repo_add(). So leave
279 # it up to the caller to use repo.wjoin() to get an absolute path.
279 # it up to the caller to use repo.wjoin() to get an absolute path.
280 # 2) Join with '/' because that's what dirstate always uses, even on
280 # 2) Join with '/' because that's what dirstate always uses, even on
281 # Windows. Change existing separator to '/' first in case we are
281 # Windows. Change existing separator to '/' first in case we are
282 # passed filenames from an external source (like the command line).
282 # passed filenames from an external source (like the command line).
283 return shortname + '/' + filename.replace(os.sep, '/')
283 return shortname + '/' + filename.replace(os.sep, '/')
284
284
285 def isstandin(filename):
285 def isstandin(filename):
286 '''Return true if filename is a big file standin. filename must
286 '''Return true if filename is a big file standin. filename must
287 be in Mercurial\'s internal form (slash-separated).'''
287 be in Mercurial\'s internal form (slash-separated).'''
288 return filename.startswith(shortname + '/')
288 return filename.startswith(shortname + '/')
289
289
290 def splitstandin(filename):
290 def splitstandin(filename):
291 # Split on / because that's what dirstate always uses, even on Windows.
291 # Split on / because that's what dirstate always uses, even on Windows.
292 # Change local separator to / first just in case we are passed filenames
292 # Change local separator to / first just in case we are passed filenames
293 # from an external source (like the command line).
293 # from an external source (like the command line).
294 bits = filename.replace(os.sep, '/').split('/', 1)
294 bits = filename.replace(os.sep, '/').split('/', 1)
295 if len(bits) == 2 and bits[0] == shortname:
295 if len(bits) == 2 and bits[0] == shortname:
296 return bits[1]
296 return bits[1]
297 else:
297 else:
298 return None
298 return None
299
299
300 def updatestandin(repo, standin):
300 def updatestandin(repo, standin):
301 file = repo.wjoin(splitstandin(standin))
301 file = repo.wjoin(splitstandin(standin))
302 if os.path.exists(file):
302 if os.path.exists(file):
303 hash = hashfile(file)
303 hash = hashfile(file)
304 executable = getexecutable(file)
304 executable = getexecutable(file)
305 writestandin(repo, standin, hash, executable)
305 writestandin(repo, standin, hash, executable)
306
306
307 def readstandin(repo, filename, node=None):
307 def readstandin(repo, filename, node=None):
308 '''read hex hash from standin for filename at given node, or working
308 '''read hex hash from standin for filename at given node, or working
309 directory if no node is given'''
309 directory if no node is given'''
310 return repo[node][standin(filename)].data().strip()
310 return repo[node][standin(filename)].data().strip()
311
311
312 def writestandin(repo, standin, hash, executable):
312 def writestandin(repo, standin, hash, executable):
313 '''write hhash to <repo.root>/<standin>'''
313 '''write hhash to <repo.root>/<standin>'''
314 writehash(hash, repo.wjoin(standin), executable)
314 writehash(hash, repo.wjoin(standin), executable)
315
315
316 def copyandhash(instream, outfile):
316 def copyandhash(instream, outfile):
317 '''Read bytes from instream (iterable) and write them to outfile,
317 '''Read bytes from instream (iterable) and write them to outfile,
318 computing the SHA-1 hash of the data along the way. Close outfile
318 computing the SHA-1 hash of the data along the way. Close outfile
319 when done and return the binary hash.'''
319 when done and return the binary hash.'''
320 hasher = util.sha1('')
320 hasher = util.sha1('')
321 for data in instream:
321 for data in instream:
322 hasher.update(data)
322 hasher.update(data)
323 outfile.write(data)
323 outfile.write(data)
324
324
325 # Blecch: closing a file that somebody else opened is rude and
325 # Blecch: closing a file that somebody else opened is rude and
326 # wrong. But it's so darn convenient and practical! After all,
326 # wrong. But it's so darn convenient and practical! After all,
327 # outfile was opened just to copy and hash.
327 # outfile was opened just to copy and hash.
328 outfile.close()
328 outfile.close()
329
329
330 return hasher.digest()
330 return hasher.digest()
331
331
332 def hashrepofile(repo, file):
332 def hashrepofile(repo, file):
333 return hashfile(repo.wjoin(file))
333 return hashfile(repo.wjoin(file))
334
334
335 def hashfile(file):
335 def hashfile(file):
336 if not os.path.exists(file):
336 if not os.path.exists(file):
337 return ''
337 return ''
338 hasher = util.sha1('')
338 hasher = util.sha1('')
339 fd = open(file, 'rb')
339 fd = open(file, 'rb')
340 for data in blockstream(fd):
340 for data in blockstream(fd):
341 hasher.update(data)
341 hasher.update(data)
342 fd.close()
342 fd.close()
343 return hasher.hexdigest()
343 return hasher.hexdigest()
344
344
345 class limitreader(object):
345 class limitreader(object):
346 def __init__(self, f, limit):
346 def __init__(self, f, limit):
347 self.f = f
347 self.f = f
348 self.limit = limit
348 self.limit = limit
349
349
350 def read(self, length):
350 def read(self, length):
351 if self.limit == 0:
351 if self.limit == 0:
352 return ''
352 return ''
353 length = length > self.limit and self.limit or length
353 length = length > self.limit and self.limit or length
354 self.limit -= length
354 self.limit -= length
355 return self.f.read(length)
355 return self.f.read(length)
356
356
357 def close(self):
357 def close(self):
358 pass
358 pass
359
359
360 def blockstream(infile, blocksize=128 * 1024):
360 def blockstream(infile, blocksize=128 * 1024):
361 """Generator that yields blocks of data from infile and closes infile."""
361 """Generator that yields blocks of data from infile and closes infile."""
362 while True:
362 while True:
363 data = infile.read(blocksize)
363 data = infile.read(blocksize)
364 if not data:
364 if not data:
365 break
365 break
366 yield data
366 yield data
367 # Same blecch as above.
367 # Same blecch as above.
368 infile.close()
368 infile.close()
369
369
370 def readhash(filename):
370 def readhash(filename):
371 rfile = open(filename, 'rb')
371 rfile = open(filename, 'rb')
372 hash = rfile.read(40)
372 hash = rfile.read(40)
373 rfile.close()
373 rfile.close()
374 if len(hash) < 40:
374 if len(hash) < 40:
375 raise util.Abort(_('bad hash in \'%s\' (only %d bytes long)')
375 raise util.Abort(_('bad hash in \'%s\' (only %d bytes long)')
376 % (filename, len(hash)))
376 % (filename, len(hash)))
377 return hash
377 return hash
378
378
379 def writehash(hash, filename, executable):
379 def writehash(hash, filename, executable):
380 util.makedirs(os.path.dirname(filename))
380 util.makedirs(os.path.dirname(filename))
381 if os.path.exists(filename):
381 if os.path.exists(filename):
382 os.unlink(filename)
382 os.unlink(filename)
383 wfile = open(filename, 'wb')
383 wfile = open(filename, 'wb')
384
384
385 try:
385 try:
386 wfile.write(hash)
386 wfile.write(hash)
387 wfile.write('\n')
387 wfile.write('\n')
388 finally:
388 finally:
389 wfile.close()
389 wfile.close()
390 if os.path.exists(filename):
390 if os.path.exists(filename):
391 os.chmod(filename, getmode(executable))
391 os.chmod(filename, getmode(executable))
392
392
393 def getexecutable(filename):
393 def getexecutable(filename):
394 mode = os.stat(filename).st_mode
394 mode = os.stat(filename).st_mode
395 return (mode & stat.S_IXUSR) and (mode & stat.S_IXGRP) and (mode & \
395 return (mode & stat.S_IXUSR) and (mode & stat.S_IXGRP) and (mode & \
396 stat.S_IXOTH)
396 stat.S_IXOTH)
397
397
398 def getmode(executable):
398 def getmode(executable):
399 if executable:
399 if executable:
400 return 0755
400 return 0755
401 else:
401 else:
402 return 0644
402 return 0644
403
403
404 def urljoin(first, second, *arg):
404 def urljoin(first, second, *arg):
405 def join(left, right):
405 def join(left, right):
406 if not left.endswith('/'):
406 if not left.endswith('/'):
407 left += '/'
407 left += '/'
408 if right.startswith('/'):
408 if right.startswith('/'):
409 right = right[1:]
409 right = right[1:]
410 return left + right
410 return left + right
411
411
412 url = join(first, second)
412 url = join(first, second)
413 for a in arg:
413 for a in arg:
414 url = join(url, a)
414 url = join(url, a)
415 return url
415 return url
416
416
417 def hexsha1(data):
417 def hexsha1(data):
418 """hexsha1 returns the hex-encoded sha1 sum of the data in the file-like
418 """hexsha1 returns the hex-encoded sha1 sum of the data in the file-like
419 object data"""
419 object data"""
420 h = hashlib.sha1()
420 h = hashlib.sha1()
421 for chunk in util.filechunkiter(data):
421 for chunk in util.filechunkiter(data):
422 h.update(chunk)
422 h.update(chunk)
423 return h.hexdigest()
423 return h.hexdigest()
424
424
425 def httpsendfile(ui, filename):
425 def httpsendfile(ui, filename):
426 return httpconnection.httpsendfile(ui, filename, 'rb')
426 return httpconnection.httpsendfile(ui, filename, 'rb')
427
427
428 # Convert a path to a unix style path. This is used to give a
428 # Convert a path to a unix style path. This is used to give a
429 # canonical path to the lfdirstate.
429 # canonical path to the lfdirstate.
430 def unixpath(path):
430 def unixpath(path):
431 return os.path.normpath(path).replace(os.sep, '/')
431 return os.path.normpath(path).replace(os.sep, '/')
432
432
433 def islfilesrepo(repo):
433 def islfilesrepo(repo):
434 return ('largefiles' in repo.requirements and
434 return ('largefiles' in repo.requirements and
435 any_(shortname + '/' in f[0] for f in repo.store.datafiles()))
435 any_(shortname + '/' in f[0] for f in repo.store.datafiles()))
436
436
437 def any_(gen):
437 def any_(gen):
438 for x in gen:
438 for x in gen:
439 if x:
439 if x:
440 return True
440 return True
441 return False
441 return False
442
442
443 class storeprotonotcapable(BaseException):
443 class storeprotonotcapable(BaseException):
444 def __init__(self, storetypes):
444 def __init__(self, storetypes):
445 self.storetypes = storetypes
445 self.storetypes = storetypes
General Comments 0
You need to be logged in to leave comments. Login now