##// END OF EJS Templates
largefiles: correctly download new largefiles when merging...
Na'Tosha Bard -
r15860:3ecce805 default
parent child Browse files
Show More
@@ -1,491 +1,499 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
46
47 if not hg.islocal(src):
47 if not hg.islocal(src):
48 raise util.Abort(_('%s is not a local Mercurial repo') % src)
48 raise util.Abort(_('%s is not a local Mercurial repo') % src)
49 if not hg.islocal(dest):
49 if not hg.islocal(dest):
50 raise util.Abort(_('%s is not a local Mercurial repo') % dest)
50 raise util.Abort(_('%s is not a local Mercurial repo') % dest)
51
51
52 rsrc = hg.repository(ui, src)
52 rsrc = hg.repository(ui, src)
53 ui.status(_('initializing destination %s\n') % dest)
53 ui.status(_('initializing destination %s\n') % dest)
54 rdst = hg.repository(ui, dest, create=True)
54 rdst = hg.repository(ui, dest, create=True)
55
55
56 success = False
56 success = False
57 try:
57 try:
58 # Lock destination to prevent modification while it is converted to.
58 # Lock destination to prevent modification while it is converted to.
59 # 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
60 # which can't change.
60 # which can't change.
61 dst_lock = rdst.lock()
61 dst_lock = rdst.lock()
62
62
63 # 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
64 # is to simply walk the changelog, using changelog.nodesbewteen().
64 # is to simply walk the changelog, using changelog.nodesbewteen().
65 # Take a look at mercurial/revlog.py:639 for more details.
65 # Take a look at mercurial/revlog.py:639 for more details.
66 # Use a generator instead of a list to decrease memory usage
66 # Use a generator instead of a list to decrease memory usage
67 ctxs = (rsrc[ctx] for ctx in rsrc.changelog.nodesbetween(None,
67 ctxs = (rsrc[ctx] for ctx in rsrc.changelog.nodesbetween(None,
68 rsrc.heads())[0])
68 rsrc.heads())[0])
69 revmap = {node.nullid: node.nullid}
69 revmap = {node.nullid: node.nullid}
70 if tolfile:
70 if tolfile:
71 lfiles = set()
71 lfiles = set()
72 normalfiles = set()
72 normalfiles = set()
73 if not pats:
73 if not pats:
74 pats = ui.configlist(lfutil.longname, 'patterns', default=[])
74 pats = ui.configlist(lfutil.longname, 'patterns', default=[])
75 if pats:
75 if pats:
76 matcher = match_.match(rsrc.root, '', list(pats))
76 matcher = match_.match(rsrc.root, '', list(pats))
77 else:
77 else:
78 matcher = None
78 matcher = None
79
79
80 lfiletohash = {}
80 lfiletohash = {}
81 for ctx in ctxs:
81 for ctx in ctxs:
82 ui.progress(_('converting revisions'), ctx.rev(),
82 ui.progress(_('converting revisions'), ctx.rev(),
83 unit=_('revision'), total=rsrc['tip'].rev())
83 unit=_('revision'), total=rsrc['tip'].rev())
84 _lfconvert_addchangeset(rsrc, rdst, ctx, revmap,
84 _lfconvert_addchangeset(rsrc, rdst, ctx, revmap,
85 lfiles, normalfiles, matcher, size, lfiletohash)
85 lfiles, normalfiles, matcher, size, lfiletohash)
86 ui.progress(_('converting revisions'), None)
86 ui.progress(_('converting revisions'), None)
87
87
88 if os.path.exists(rdst.wjoin(lfutil.shortname)):
88 if os.path.exists(rdst.wjoin(lfutil.shortname)):
89 shutil.rmtree(rdst.wjoin(lfutil.shortname))
89 shutil.rmtree(rdst.wjoin(lfutil.shortname))
90
90
91 for f in lfiletohash.keys():
91 for f in lfiletohash.keys():
92 if os.path.isfile(rdst.wjoin(f)):
92 if os.path.isfile(rdst.wjoin(f)):
93 os.unlink(rdst.wjoin(f))
93 os.unlink(rdst.wjoin(f))
94 try:
94 try:
95 os.removedirs(os.path.dirname(rdst.wjoin(f)))
95 os.removedirs(os.path.dirname(rdst.wjoin(f)))
96 except OSError:
96 except OSError:
97 pass
97 pass
98
98
99 # If there were any files converted to largefiles, add largefiles
99 # If there were any files converted to largefiles, add largefiles
100 # to the destination repository's requirements.
100 # to the destination repository's requirements.
101 if lfiles:
101 if lfiles:
102 rdst.requirements.add('largefiles')
102 rdst.requirements.add('largefiles')
103 rdst._writerequirements()
103 rdst._writerequirements()
104 else:
104 else:
105 for ctx in ctxs:
105 for ctx in ctxs:
106 ui.progress(_('converting revisions'), ctx.rev(),
106 ui.progress(_('converting revisions'), ctx.rev(),
107 unit=_('revision'), total=rsrc['tip'].rev())
107 unit=_('revision'), total=rsrc['tip'].rev())
108 _addchangeset(ui, rsrc, rdst, ctx, revmap)
108 _addchangeset(ui, rsrc, rdst, ctx, revmap)
109
109
110 ui.progress(_('converting revisions'), None)
110 ui.progress(_('converting revisions'), None)
111 success = True
111 success = True
112 finally:
112 finally:
113 if not success:
113 if not success:
114 # we failed, remove the new directory
114 # we failed, remove the new directory
115 shutil.rmtree(rdst.root)
115 shutil.rmtree(rdst.root)
116 dst_lock.release()
116 dst_lock.release()
117
117
118 def _addchangeset(ui, rsrc, rdst, ctx, revmap):
118 def _addchangeset(ui, rsrc, rdst, ctx, revmap):
119 # Convert src parents to dst parents
119 # Convert src parents to dst parents
120 parents = _convertparents(ctx, revmap)
120 parents = _convertparents(ctx, revmap)
121
121
122 # Generate list of changed files
122 # Generate list of changed files
123 files = _getchangedfiles(ctx, parents)
123 files = _getchangedfiles(ctx, parents)
124
124
125 def getfilectx(repo, memctx, f):
125 def getfilectx(repo, memctx, f):
126 if lfutil.standin(f) in files:
126 if lfutil.standin(f) in files:
127 # if the file isn't in the manifest then it was removed
127 # if the file isn't in the manifest then it was removed
128 # or renamed, raise IOError to indicate this
128 # or renamed, raise IOError to indicate this
129 try:
129 try:
130 fctx = ctx.filectx(lfutil.standin(f))
130 fctx = ctx.filectx(lfutil.standin(f))
131 except error.LookupError:
131 except error.LookupError:
132 raise IOError()
132 raise IOError()
133 renamed = fctx.renamed()
133 renamed = fctx.renamed()
134 if renamed:
134 if renamed:
135 renamed = lfutil.splitstandin(renamed[0])
135 renamed = lfutil.splitstandin(renamed[0])
136
136
137 hash = fctx.data().strip()
137 hash = fctx.data().strip()
138 path = lfutil.findfile(rsrc, hash)
138 path = lfutil.findfile(rsrc, hash)
139 ### TODO: What if the file is not cached?
139 ### TODO: What if the file is not cached?
140 data = ''
140 data = ''
141 fd = None
141 fd = None
142 try:
142 try:
143 fd = open(path, 'rb')
143 fd = open(path, 'rb')
144 data = fd.read()
144 data = fd.read()
145 finally:
145 finally:
146 if fd:
146 if fd:
147 fd.close()
147 fd.close()
148 return context.memfilectx(f, data, 'l' in fctx.flags(),
148 return context.memfilectx(f, data, 'l' in fctx.flags(),
149 'x' in fctx.flags(), renamed)
149 'x' in fctx.flags(), renamed)
150 else:
150 else:
151 return _getnormalcontext(repo.ui, ctx, f, revmap)
151 return _getnormalcontext(repo.ui, ctx, f, revmap)
152
152
153 dstfiles = []
153 dstfiles = []
154 for file in files:
154 for file in files:
155 if lfutil.isstandin(file):
155 if lfutil.isstandin(file):
156 dstfiles.append(lfutil.splitstandin(file))
156 dstfiles.append(lfutil.splitstandin(file))
157 else:
157 else:
158 dstfiles.append(file)
158 dstfiles.append(file)
159 # Commit
159 # Commit
160 _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap)
160 _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap)
161
161
162 def _lfconvert_addchangeset(rsrc, rdst, ctx, revmap, lfiles, normalfiles,
162 def _lfconvert_addchangeset(rsrc, rdst, ctx, revmap, lfiles, normalfiles,
163 matcher, size, lfiletohash):
163 matcher, size, lfiletohash):
164 # Convert src parents to dst parents
164 # Convert src parents to dst parents
165 parents = _convertparents(ctx, revmap)
165 parents = _convertparents(ctx, revmap)
166
166
167 # Generate list of changed files
167 # Generate list of changed files
168 files = _getchangedfiles(ctx, parents)
168 files = _getchangedfiles(ctx, parents)
169
169
170 dstfiles = []
170 dstfiles = []
171 for f in files:
171 for f in files:
172 if f not in lfiles and f not in normalfiles:
172 if f not in lfiles and f not in normalfiles:
173 islfile = _islfile(f, ctx, matcher, size)
173 islfile = _islfile(f, ctx, matcher, size)
174 # If this file was renamed or copied then copy
174 # If this file was renamed or copied then copy
175 # the lfileness of its predecessor
175 # the lfileness of its predecessor
176 if f in ctx.manifest():
176 if f in ctx.manifest():
177 fctx = ctx.filectx(f)
177 fctx = ctx.filectx(f)
178 renamed = fctx.renamed()
178 renamed = fctx.renamed()
179 renamedlfile = renamed and renamed[0] in lfiles
179 renamedlfile = renamed and renamed[0] in lfiles
180 islfile |= renamedlfile
180 islfile |= renamedlfile
181 if 'l' in fctx.flags():
181 if 'l' in fctx.flags():
182 if renamedlfile:
182 if renamedlfile:
183 raise util.Abort(
183 raise util.Abort(
184 _('renamed/copied largefile %s becomes symlink')
184 _('renamed/copied largefile %s becomes symlink')
185 % f)
185 % f)
186 islfile = False
186 islfile = False
187 if islfile:
187 if islfile:
188 lfiles.add(f)
188 lfiles.add(f)
189 else:
189 else:
190 normalfiles.add(f)
190 normalfiles.add(f)
191
191
192 if f in lfiles:
192 if f in lfiles:
193 dstfiles.append(lfutil.standin(f))
193 dstfiles.append(lfutil.standin(f))
194 # largefile in manifest if it has not been removed/renamed
194 # largefile in manifest if it has not been removed/renamed
195 if f in ctx.manifest():
195 if f in ctx.manifest():
196 fctx = ctx.filectx(f)
196 fctx = ctx.filectx(f)
197 if 'l' in fctx.flags():
197 if 'l' in fctx.flags():
198 renamed = fctx.renamed()
198 renamed = fctx.renamed()
199 if renamed and renamed[0] in lfiles:
199 if renamed and renamed[0] in lfiles:
200 raise util.Abort(_('largefile %s becomes symlink') % f)
200 raise util.Abort(_('largefile %s becomes symlink') % f)
201
201
202 # largefile was modified, update standins
202 # largefile was modified, update standins
203 fullpath = rdst.wjoin(f)
203 fullpath = rdst.wjoin(f)
204 util.makedirs(os.path.dirname(fullpath))
204 util.makedirs(os.path.dirname(fullpath))
205 m = util.sha1('')
205 m = util.sha1('')
206 m.update(ctx[f].data())
206 m.update(ctx[f].data())
207 hash = m.hexdigest()
207 hash = m.hexdigest()
208 if f not in lfiletohash or lfiletohash[f] != hash:
208 if f not in lfiletohash or lfiletohash[f] != hash:
209 try:
209 try:
210 fd = open(fullpath, 'wb')
210 fd = open(fullpath, 'wb')
211 fd.write(ctx[f].data())
211 fd.write(ctx[f].data())
212 finally:
212 finally:
213 if fd:
213 if fd:
214 fd.close()
214 fd.close()
215 executable = 'x' in ctx[f].flags()
215 executable = 'x' in ctx[f].flags()
216 os.chmod(fullpath, lfutil.getmode(executable))
216 os.chmod(fullpath, lfutil.getmode(executable))
217 lfutil.writestandin(rdst, lfutil.standin(f), hash,
217 lfutil.writestandin(rdst, lfutil.standin(f), hash,
218 executable)
218 executable)
219 lfiletohash[f] = hash
219 lfiletohash[f] = hash
220 else:
220 else:
221 # normal file
221 # normal file
222 dstfiles.append(f)
222 dstfiles.append(f)
223
223
224 def getfilectx(repo, memctx, f):
224 def getfilectx(repo, memctx, f):
225 if lfutil.isstandin(f):
225 if lfutil.isstandin(f):
226 # if the file isn't in the manifest then it was removed
226 # if the file isn't in the manifest then it was removed
227 # or renamed, raise IOError to indicate this
227 # or renamed, raise IOError to indicate this
228 srcfname = lfutil.splitstandin(f)
228 srcfname = lfutil.splitstandin(f)
229 try:
229 try:
230 fctx = ctx.filectx(srcfname)
230 fctx = ctx.filectx(srcfname)
231 except error.LookupError:
231 except error.LookupError:
232 raise IOError()
232 raise IOError()
233 renamed = fctx.renamed()
233 renamed = fctx.renamed()
234 if renamed:
234 if renamed:
235 # standin is always a largefile because largefile-ness
235 # standin is always a largefile because largefile-ness
236 # doesn't change after rename or copy
236 # doesn't change after rename or copy
237 renamed = lfutil.standin(renamed[0])
237 renamed = lfutil.standin(renamed[0])
238
238
239 return context.memfilectx(f, lfiletohash[srcfname] + '\n', 'l' in
239 return context.memfilectx(f, lfiletohash[srcfname] + '\n', 'l' in
240 fctx.flags(), 'x' in fctx.flags(), renamed)
240 fctx.flags(), 'x' in fctx.flags(), renamed)
241 else:
241 else:
242 return _getnormalcontext(repo.ui, ctx, f, revmap)
242 return _getnormalcontext(repo.ui, ctx, f, revmap)
243
243
244 # Commit
244 # Commit
245 _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap)
245 _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap)
246
246
247 def _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap):
247 def _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap):
248 mctx = context.memctx(rdst, parents, ctx.description(), dstfiles,
248 mctx = context.memctx(rdst, parents, ctx.description(), dstfiles,
249 getfilectx, ctx.user(), ctx.date(), ctx.extra())
249 getfilectx, ctx.user(), ctx.date(), ctx.extra())
250 ret = rdst.commitctx(mctx)
250 ret = rdst.commitctx(mctx)
251 rdst.dirstate.setparents(ret)
251 rdst.dirstate.setparents(ret)
252 revmap[ctx.node()] = rdst.changelog.tip()
252 revmap[ctx.node()] = rdst.changelog.tip()
253
253
254 # Generate list of changed files
254 # Generate list of changed files
255 def _getchangedfiles(ctx, parents):
255 def _getchangedfiles(ctx, parents):
256 files = set(ctx.files())
256 files = set(ctx.files())
257 if node.nullid not in parents:
257 if node.nullid not in parents:
258 mc = ctx.manifest()
258 mc = ctx.manifest()
259 mp1 = ctx.parents()[0].manifest()
259 mp1 = ctx.parents()[0].manifest()
260 mp2 = ctx.parents()[1].manifest()
260 mp2 = ctx.parents()[1].manifest()
261 files |= (set(mp1) | set(mp2)) - set(mc)
261 files |= (set(mp1) | set(mp2)) - set(mc)
262 for f in mc:
262 for f in mc:
263 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
263 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
264 files.add(f)
264 files.add(f)
265 return files
265 return files
266
266
267 # Convert src parents to dst parents
267 # Convert src parents to dst parents
268 def _convertparents(ctx, revmap):
268 def _convertparents(ctx, revmap):
269 parents = []
269 parents = []
270 for p in ctx.parents():
270 for p in ctx.parents():
271 parents.append(revmap[p.node()])
271 parents.append(revmap[p.node()])
272 while len(parents) < 2:
272 while len(parents) < 2:
273 parents.append(node.nullid)
273 parents.append(node.nullid)
274 return parents
274 return parents
275
275
276 # Get memfilectx for a normal file
276 # Get memfilectx for a normal file
277 def _getnormalcontext(ui, ctx, f, revmap):
277 def _getnormalcontext(ui, ctx, f, revmap):
278 try:
278 try:
279 fctx = ctx.filectx(f)
279 fctx = ctx.filectx(f)
280 except error.LookupError:
280 except error.LookupError:
281 raise IOError()
281 raise IOError()
282 renamed = fctx.renamed()
282 renamed = fctx.renamed()
283 if renamed:
283 if renamed:
284 renamed = renamed[0]
284 renamed = renamed[0]
285
285
286 data = fctx.data()
286 data = fctx.data()
287 if f == '.hgtags':
287 if f == '.hgtags':
288 data = _converttags (ui, revmap, data)
288 data = _converttags (ui, revmap, data)
289 return context.memfilectx(f, data, 'l' in fctx.flags(),
289 return context.memfilectx(f, data, 'l' in fctx.flags(),
290 'x' in fctx.flags(), renamed)
290 'x' in fctx.flags(), renamed)
291
291
292 # Remap tag data using a revision map
292 # Remap tag data using a revision map
293 def _converttags(ui, revmap, data):
293 def _converttags(ui, revmap, data):
294 newdata = []
294 newdata = []
295 for line in data.splitlines():
295 for line in data.splitlines():
296 try:
296 try:
297 id, name = line.split(' ', 1)
297 id, name = line.split(' ', 1)
298 except ValueError:
298 except ValueError:
299 ui.warn(_('skipping incorrectly formatted tag %s\n'
299 ui.warn(_('skipping incorrectly formatted tag %s\n'
300 % line))
300 % line))
301 continue
301 continue
302 try:
302 try:
303 newid = node.bin(id)
303 newid = node.bin(id)
304 except TypeError:
304 except TypeError:
305 ui.warn(_('skipping incorrectly formatted id %s\n'
305 ui.warn(_('skipping incorrectly formatted id %s\n'
306 % id))
306 % id))
307 continue
307 continue
308 try:
308 try:
309 newdata.append('%s %s\n' % (node.hex(revmap[newid]),
309 newdata.append('%s %s\n' % (node.hex(revmap[newid]),
310 name))
310 name))
311 except KeyError:
311 except KeyError:
312 ui.warn(_('no mapping for id %s\n' % id))
312 ui.warn(_('no mapping for id %s\n' % id))
313 continue
313 continue
314 return ''.join(newdata)
314 return ''.join(newdata)
315
315
316 def _islfile(file, ctx, matcher, size):
316 def _islfile(file, ctx, matcher, size):
317 '''Return true if file should be considered a largefile, i.e.
317 '''Return true if file should be considered a largefile, i.e.
318 matcher matches it or it is larger than size.'''
318 matcher matches it or it is larger than size.'''
319 # never store special .hg* files as largefiles
319 # never store special .hg* files as largefiles
320 if file == '.hgtags' or file == '.hgignore' or file == '.hgsigs':
320 if file == '.hgtags' or file == '.hgignore' or file == '.hgsigs':
321 return False
321 return False
322 if matcher and matcher(file):
322 if matcher and matcher(file):
323 return True
323 return True
324 try:
324 try:
325 return ctx.filectx(file).size() >= size * 1024 * 1024
325 return ctx.filectx(file).size() >= size * 1024 * 1024
326 except error.LookupError:
326 except error.LookupError:
327 return False
327 return False
328
328
329 def uploadlfiles(ui, rsrc, rdst, files):
329 def uploadlfiles(ui, rsrc, rdst, files):
330 '''upload largefiles to the central store'''
330 '''upload largefiles to the central store'''
331
331
332 if not files:
332 if not files:
333 return
333 return
334
334
335 store = basestore._openstore(rsrc, rdst, put=True)
335 store = basestore._openstore(rsrc, rdst, put=True)
336
336
337 at = 0
337 at = 0
338 files = filter(lambda h: not store.exists(h), files)
338 files = filter(lambda h: not store.exists(h), files)
339 for hash in files:
339 for hash in files:
340 ui.progress(_('uploading largefiles'), at, unit='largefile',
340 ui.progress(_('uploading largefiles'), at, unit='largefile',
341 total=len(files))
341 total=len(files))
342 source = lfutil.findfile(rsrc, hash)
342 source = lfutil.findfile(rsrc, hash)
343 if not source:
343 if not source:
344 raise util.Abort(_('largefile %s missing from store'
344 raise util.Abort(_('largefile %s missing from store'
345 ' (needs to be uploaded)') % hash)
345 ' (needs to be uploaded)') % hash)
346 # XXX check for errors here
346 # XXX check for errors here
347 store.put(source, hash)
347 store.put(source, hash)
348 at += 1
348 at += 1
349 ui.progress(_('uploading largefiles'), None)
349 ui.progress(_('uploading largefiles'), None)
350
350
351 def verifylfiles(ui, repo, all=False, contents=False):
351 def verifylfiles(ui, repo, all=False, contents=False):
352 '''Verify that every big file revision in the current changeset
352 '''Verify that every big file revision in the current changeset
353 exists in the central store. With --contents, also verify that
353 exists in the central store. With --contents, also verify that
354 the contents of each big file revision are correct (SHA-1 hash
354 the contents of each big file revision are correct (SHA-1 hash
355 matches the revision ID). With --all, check every changeset in
355 matches the revision ID). With --all, check every changeset in
356 this repository.'''
356 this repository.'''
357 if all:
357 if all:
358 # Pass a list to the function rather than an iterator because we know a
358 # Pass a list to the function rather than an iterator because we know a
359 # list will work.
359 # list will work.
360 revs = range(len(repo))
360 revs = range(len(repo))
361 else:
361 else:
362 revs = ['.']
362 revs = ['.']
363
363
364 store = basestore._openstore(repo)
364 store = basestore._openstore(repo)
365 return store.verify(revs, contents=contents)
365 return store.verify(revs, contents=contents)
366
366
367 def cachelfiles(ui, repo, node):
367 def cachelfiles(ui, repo, node):
368 '''cachelfiles ensures that all largefiles needed by the specified revision
368 '''cachelfiles ensures that all largefiles needed by the specified revision
369 are present in the repository's largefile cache.
369 are present in the repository's largefile cache.
370
370
371 returns a tuple (cached, missing). cached is the list of files downloaded
371 returns a tuple (cached, missing). cached is the list of files downloaded
372 by this operation; missing is the list of files that were needed but could
372 by this operation; missing is the list of files that were needed but could
373 not be found.'''
373 not be found.'''
374 lfiles = lfutil.listlfiles(repo, node)
374 lfiles = lfutil.listlfiles(repo, node)
375 toget = []
375 toget = []
376
376
377 for lfile in lfiles:
377 for lfile in lfiles:
378 expectedhash = repo[node][lfutil.standin(lfile)].data().strip()
378 # If we are mid-merge, then we have to trust the standin that is in the
379 # working copy to have the correct hashvalue. This is because the
380 # original hg.merge() already updated the standin as part of the normal
381 # merge process -- we just have to udpate the largefile to match.
382 if getattr(repo, "_ismerging", False):
383 expectedhash = lfutil.readstandin(repo, lfile)
384 else:
385 expectedhash = repo[node][lfutil.standin(lfile)].data().strip()
386
379 # if it exists and its hash matches, it might have been locally
387 # if it exists and its hash matches, it might have been locally
380 # modified before updating and the user chose 'local'. in this case,
388 # modified before updating and the user chose 'local'. in this case,
381 # it will not be in any store, so don't look for it.
389 # it will not be in any store, so don't look for it.
382 if ((not os.path.exists(repo.wjoin(lfile)) or
390 if ((not os.path.exists(repo.wjoin(lfile)) or
383 expectedhash != lfutil.hashfile(repo.wjoin(lfile))) and
391 expectedhash != lfutil.hashfile(repo.wjoin(lfile))) and
384 not lfutil.findfile(repo, expectedhash)):
392 not lfutil.findfile(repo, expectedhash)):
385 toget.append((lfile, expectedhash))
393 toget.append((lfile, expectedhash))
386
394
387 if toget:
395 if toget:
388 store = basestore._openstore(repo)
396 store = basestore._openstore(repo)
389 ret = store.get(toget)
397 ret = store.get(toget)
390 return ret
398 return ret
391
399
392 return ([], [])
400 return ([], [])
393
401
394 def updatelfiles(ui, repo, filelist=None, printmessage=True):
402 def updatelfiles(ui, repo, filelist=None, printmessage=True):
395 wlock = repo.wlock()
403 wlock = repo.wlock()
396 try:
404 try:
397 lfdirstate = lfutil.openlfdirstate(ui, repo)
405 lfdirstate = lfutil.openlfdirstate(ui, repo)
398 lfiles = set(lfutil.listlfiles(repo)) | set(lfdirstate)
406 lfiles = set(lfutil.listlfiles(repo)) | set(lfdirstate)
399
407
400 if filelist is not None:
408 if filelist is not None:
401 lfiles = [f for f in lfiles if f in filelist]
409 lfiles = [f for f in lfiles if f in filelist]
402
410
403 printed = False
411 printed = False
404 if printmessage and lfiles:
412 if printmessage and lfiles:
405 ui.status(_('getting changed largefiles\n'))
413 ui.status(_('getting changed largefiles\n'))
406 printed = True
414 printed = True
407 cachelfiles(ui, repo, '.')
415 cachelfiles(ui, repo, '.')
408
416
409 updated, removed = 0, 0
417 updated, removed = 0, 0
410 for i in map(lambda f: _updatelfile(repo, lfdirstate, f), lfiles):
418 for i in map(lambda f: _updatelfile(repo, lfdirstate, f), lfiles):
411 # increment the appropriate counter according to _updatelfile's
419 # increment the appropriate counter according to _updatelfile's
412 # return value
420 # return value
413 updated += i > 0 and i or 0
421 updated += i > 0 and i or 0
414 removed -= i < 0 and i or 0
422 removed -= i < 0 and i or 0
415 if printmessage and (removed or updated) and not printed:
423 if printmessage and (removed or updated) and not printed:
416 ui.status(_('getting changed largefiles\n'))
424 ui.status(_('getting changed largefiles\n'))
417 printed = True
425 printed = True
418
426
419 lfdirstate.write()
427 lfdirstate.write()
420 if printed and printmessage:
428 if printed and printmessage:
421 ui.status(_('%d largefiles updated, %d removed\n') % (updated,
429 ui.status(_('%d largefiles updated, %d removed\n') % (updated,
422 removed))
430 removed))
423 finally:
431 finally:
424 wlock.release()
432 wlock.release()
425
433
426 def _updatelfile(repo, lfdirstate, lfile):
434 def _updatelfile(repo, lfdirstate, lfile):
427 '''updates a single largefile and copies the state of its standin from
435 '''updates a single largefile and copies the state of its standin from
428 the repository's dirstate to its state in the lfdirstate.
436 the repository's dirstate to its state in the lfdirstate.
429
437
430 returns 1 if the file was modified, -1 if the file was removed, 0 if the
438 returns 1 if the file was modified, -1 if the file was removed, 0 if the
431 file was unchanged, and None if the needed largefile was missing from the
439 file was unchanged, and None if the needed largefile was missing from the
432 cache.'''
440 cache.'''
433 ret = 0
441 ret = 0
434 abslfile = repo.wjoin(lfile)
442 abslfile = repo.wjoin(lfile)
435 absstandin = repo.wjoin(lfutil.standin(lfile))
443 absstandin = repo.wjoin(lfutil.standin(lfile))
436 if os.path.exists(absstandin):
444 if os.path.exists(absstandin):
437 if os.path.exists(absstandin+'.orig'):
445 if os.path.exists(absstandin+'.orig'):
438 shutil.copyfile(abslfile, abslfile+'.orig')
446 shutil.copyfile(abslfile, abslfile+'.orig')
439 expecthash = lfutil.readstandin(repo, lfile)
447 expecthash = lfutil.readstandin(repo, lfile)
440 if (expecthash != '' and
448 if (expecthash != '' and
441 (not os.path.exists(abslfile) or
449 (not os.path.exists(abslfile) or
442 expecthash != lfutil.hashfile(abslfile))):
450 expecthash != lfutil.hashfile(abslfile))):
443 if not lfutil.copyfromcache(repo, expecthash, lfile):
451 if not lfutil.copyfromcache(repo, expecthash, lfile):
444 # use normallookup() to allocate entry in largefiles dirstate,
452 # use normallookup() to allocate entry in largefiles dirstate,
445 # because lack of it misleads lfiles_repo.status() into
453 # because lack of it misleads lfiles_repo.status() into
446 # recognition that such cache missing files are REMOVED.
454 # recognition that such cache missing files are REMOVED.
447 lfdirstate.normallookup(lfile)
455 lfdirstate.normallookup(lfile)
448 return None # don't try to set the mode
456 return None # don't try to set the mode
449 ret = 1
457 ret = 1
450 mode = os.stat(absstandin).st_mode
458 mode = os.stat(absstandin).st_mode
451 if mode != os.stat(abslfile).st_mode:
459 if mode != os.stat(abslfile).st_mode:
452 os.chmod(abslfile, mode)
460 os.chmod(abslfile, mode)
453 ret = 1
461 ret = 1
454 else:
462 else:
455 # Remove lfiles for which the standin is deleted, unless the
463 # Remove lfiles for which the standin is deleted, unless the
456 # lfile is added to the repository again. This happens when a
464 # lfile is added to the repository again. This happens when a
457 # largefile is converted back to a normal file: the standin
465 # largefile is converted back to a normal file: the standin
458 # disappears, but a new (normal) file appears as the lfile.
466 # disappears, but a new (normal) file appears as the lfile.
459 if os.path.exists(abslfile) and lfile not in repo[None]:
467 if os.path.exists(abslfile) and lfile not in repo[None]:
460 os.unlink(abslfile)
468 os.unlink(abslfile)
461 ret = -1
469 ret = -1
462 state = repo.dirstate[lfutil.standin(lfile)]
470 state = repo.dirstate[lfutil.standin(lfile)]
463 if state == 'n':
471 if state == 'n':
464 # When rebasing, we need to synchronize the standin and the largefile,
472 # When rebasing, we need to synchronize the standin and the largefile,
465 # because otherwise the largefile will get reverted. But for commit's
473 # because otherwise the largefile will get reverted. But for commit's
466 # sake, we have to mark the file as unclean.
474 # sake, we have to mark the file as unclean.
467 if getattr(repo, "_isrebasing", False):
475 if getattr(repo, "_isrebasing", False):
468 lfdirstate.normallookup(lfile)
476 lfdirstate.normallookup(lfile)
469 else:
477 else:
470 lfdirstate.normal(lfile)
478 lfdirstate.normal(lfile)
471 elif state == 'r':
479 elif state == 'r':
472 lfdirstate.remove(lfile)
480 lfdirstate.remove(lfile)
473 elif state == 'a':
481 elif state == 'a':
474 lfdirstate.add(lfile)
482 lfdirstate.add(lfile)
475 elif state == '?':
483 elif state == '?':
476 lfdirstate.drop(lfile)
484 lfdirstate.drop(lfile)
477 return ret
485 return ret
478
486
479 # -- hg commands declarations ------------------------------------------------
487 # -- hg commands declarations ------------------------------------------------
480
488
481 cmdtable = {
489 cmdtable = {
482 'lfconvert': (lfconvert,
490 'lfconvert': (lfconvert,
483 [('s', 'size', '',
491 [('s', 'size', '',
484 _('minimum size (MB) for files to be converted '
492 _('minimum size (MB) for files to be converted '
485 'as largefiles'),
493 'as largefiles'),
486 'SIZE'),
494 'SIZE'),
487 ('', 'to-normal', False,
495 ('', 'to-normal', False,
488 _('convert from a largefiles repo to a normal repo')),
496 _('convert from a largefiles repo to a normal repo')),
489 ],
497 ],
490 _('hg lfconvert SOURCE DEST [FILE ...]')),
498 _('hg lfconvert SOURCE DEST [FILE ...]')),
491 }
499 }
@@ -1,931 +1,938 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 '''Overridden Mercurial commands and functions for the largefiles extension'''
9 '''Overridden Mercurial commands and functions for the largefiles extension'''
10
10
11 import os
11 import os
12 import copy
12 import copy
13
13
14 from mercurial import hg, commands, util, cmdutil, scmutil, match as match_, \
14 from mercurial import hg, commands, util, cmdutil, scmutil, match as match_, \
15 node, archival, error, merge
15 node, archival, error, merge
16 from mercurial.i18n import _
16 from mercurial.i18n import _
17 from mercurial.node import hex
17 from mercurial.node import hex
18 from hgext import rebase
18 from hgext import rebase
19
19
20 import lfutil
20 import lfutil
21 import lfcommands
21 import lfcommands
22
22
23 # -- Utility functions: commonly/repeatedly needed functionality ---------------
23 # -- Utility functions: commonly/repeatedly needed functionality ---------------
24
24
25 def installnormalfilesmatchfn(manifest):
25 def installnormalfilesmatchfn(manifest):
26 '''overrides scmutil.match so that the matcher it returns will ignore all
26 '''overrides scmutil.match so that the matcher it returns will ignore all
27 largefiles'''
27 largefiles'''
28 oldmatch = None # for the closure
28 oldmatch = None # for the closure
29 def override_match(ctx, pats=[], opts={}, globbed=False,
29 def override_match(ctx, pats=[], opts={}, globbed=False,
30 default='relpath'):
30 default='relpath'):
31 match = oldmatch(ctx, pats, opts, globbed, default)
31 match = oldmatch(ctx, pats, opts, globbed, default)
32 m = copy.copy(match)
32 m = copy.copy(match)
33 notlfile = lambda f: not (lfutil.isstandin(f) or lfutil.standin(f) in
33 notlfile = lambda f: not (lfutil.isstandin(f) or lfutil.standin(f) in
34 manifest)
34 manifest)
35 m._files = filter(notlfile, m._files)
35 m._files = filter(notlfile, m._files)
36 m._fmap = set(m._files)
36 m._fmap = set(m._files)
37 orig_matchfn = m.matchfn
37 orig_matchfn = m.matchfn
38 m.matchfn = lambda f: notlfile(f) and orig_matchfn(f) or None
38 m.matchfn = lambda f: notlfile(f) and orig_matchfn(f) or None
39 return m
39 return m
40 oldmatch = installmatchfn(override_match)
40 oldmatch = installmatchfn(override_match)
41
41
42 def installmatchfn(f):
42 def installmatchfn(f):
43 oldmatch = scmutil.match
43 oldmatch = scmutil.match
44 setattr(f, 'oldmatch', oldmatch)
44 setattr(f, 'oldmatch', oldmatch)
45 scmutil.match = f
45 scmutil.match = f
46 return oldmatch
46 return oldmatch
47
47
48 def restorematchfn():
48 def restorematchfn():
49 '''restores scmutil.match to what it was before installnormalfilesmatchfn
49 '''restores scmutil.match to what it was before installnormalfilesmatchfn
50 was called. no-op if scmutil.match is its original function.
50 was called. no-op if scmutil.match is its original function.
51
51
52 Note that n calls to installnormalfilesmatchfn will require n calls to
52 Note that n calls to installnormalfilesmatchfn will require n calls to
53 restore matchfn to reverse'''
53 restore matchfn to reverse'''
54 scmutil.match = getattr(scmutil.match, 'oldmatch', scmutil.match)
54 scmutil.match = getattr(scmutil.match, 'oldmatch', scmutil.match)
55
55
56 def add_largefiles(ui, repo, *pats, **opts):
56 def add_largefiles(ui, repo, *pats, **opts):
57 large = opts.pop('large', None)
57 large = opts.pop('large', None)
58 lfsize = lfutil.getminsize(
58 lfsize = lfutil.getminsize(
59 ui, lfutil.islfilesrepo(repo), opts.pop('lfsize', None))
59 ui, lfutil.islfilesrepo(repo), opts.pop('lfsize', None))
60
60
61 lfmatcher = None
61 lfmatcher = None
62 if lfutil.islfilesrepo(repo):
62 if lfutil.islfilesrepo(repo):
63 lfpats = ui.configlist(lfutil.longname, 'patterns', default=[])
63 lfpats = ui.configlist(lfutil.longname, 'patterns', default=[])
64 if lfpats:
64 if lfpats:
65 lfmatcher = match_.match(repo.root, '', list(lfpats))
65 lfmatcher = match_.match(repo.root, '', list(lfpats))
66
66
67 lfnames = []
67 lfnames = []
68 m = scmutil.match(repo[None], pats, opts)
68 m = scmutil.match(repo[None], pats, opts)
69 m.bad = lambda x, y: None
69 m.bad = lambda x, y: None
70 wctx = repo[None]
70 wctx = repo[None]
71 for f in repo.walk(m):
71 for f in repo.walk(m):
72 exact = m.exact(f)
72 exact = m.exact(f)
73 lfile = lfutil.standin(f) in wctx
73 lfile = lfutil.standin(f) in wctx
74 nfile = f in wctx
74 nfile = f in wctx
75 exists = lfile or nfile
75 exists = lfile or nfile
76
76
77 # Don't warn the user when they attempt to add a normal tracked file.
77 # Don't warn the user when they attempt to add a normal tracked file.
78 # The normal add code will do that for us.
78 # The normal add code will do that for us.
79 if exact and exists:
79 if exact and exists:
80 if lfile:
80 if lfile:
81 ui.warn(_('%s already a largefile\n') % f)
81 ui.warn(_('%s already a largefile\n') % f)
82 continue
82 continue
83
83
84 if exact or not exists:
84 if exact or not exists:
85 abovemin = (lfsize and
85 abovemin = (lfsize and
86 os.lstat(repo.wjoin(f)).st_size >= lfsize * 1024 * 1024)
86 os.lstat(repo.wjoin(f)).st_size >= lfsize * 1024 * 1024)
87 if large or abovemin or (lfmatcher and lfmatcher(f)):
87 if large or abovemin or (lfmatcher and lfmatcher(f)):
88 lfnames.append(f)
88 lfnames.append(f)
89 if ui.verbose or not exact:
89 if ui.verbose or not exact:
90 ui.status(_('adding %s as a largefile\n') % m.rel(f))
90 ui.status(_('adding %s as a largefile\n') % m.rel(f))
91
91
92 bad = []
92 bad = []
93 standins = []
93 standins = []
94
94
95 # Need to lock, otherwise there could be a race condition between
95 # Need to lock, otherwise there could be a race condition between
96 # when standins are created and added to the repo.
96 # when standins are created and added to the repo.
97 wlock = repo.wlock()
97 wlock = repo.wlock()
98 try:
98 try:
99 if not opts.get('dry_run'):
99 if not opts.get('dry_run'):
100 lfdirstate = lfutil.openlfdirstate(ui, repo)
100 lfdirstate = lfutil.openlfdirstate(ui, repo)
101 for f in lfnames:
101 for f in lfnames:
102 standinname = lfutil.standin(f)
102 standinname = lfutil.standin(f)
103 lfutil.writestandin(repo, standinname, hash='',
103 lfutil.writestandin(repo, standinname, hash='',
104 executable=lfutil.getexecutable(repo.wjoin(f)))
104 executable=lfutil.getexecutable(repo.wjoin(f)))
105 standins.append(standinname)
105 standins.append(standinname)
106 if lfdirstate[f] == 'r':
106 if lfdirstate[f] == 'r':
107 lfdirstate.normallookup(f)
107 lfdirstate.normallookup(f)
108 else:
108 else:
109 lfdirstate.add(f)
109 lfdirstate.add(f)
110 lfdirstate.write()
110 lfdirstate.write()
111 bad += [lfutil.splitstandin(f)
111 bad += [lfutil.splitstandin(f)
112 for f in lfutil.repo_add(repo, standins)
112 for f in lfutil.repo_add(repo, standins)
113 if f in m.files()]
113 if f in m.files()]
114 finally:
114 finally:
115 wlock.release()
115 wlock.release()
116 return bad
116 return bad
117
117
118 def remove_largefiles(ui, repo, *pats, **opts):
118 def remove_largefiles(ui, repo, *pats, **opts):
119 after = opts.get('after')
119 after = opts.get('after')
120 if not pats and not after:
120 if not pats and not after:
121 raise util.Abort(_('no files specified'))
121 raise util.Abort(_('no files specified'))
122 m = scmutil.match(repo[None], pats, opts)
122 m = scmutil.match(repo[None], pats, opts)
123 try:
123 try:
124 repo.lfstatus = True
124 repo.lfstatus = True
125 s = repo.status(match=m, clean=True)
125 s = repo.status(match=m, clean=True)
126 finally:
126 finally:
127 repo.lfstatus = False
127 repo.lfstatus = False
128 manifest = repo[None].manifest()
128 manifest = repo[None].manifest()
129 modified, added, deleted, clean = [[f for f in list
129 modified, added, deleted, clean = [[f for f in list
130 if lfutil.standin(f) in manifest]
130 if lfutil.standin(f) in manifest]
131 for list in [s[0], s[1], s[3], s[6]]]
131 for list in [s[0], s[1], s[3], s[6]]]
132
132
133 def warn(files, reason):
133 def warn(files, reason):
134 for f in files:
134 for f in files:
135 ui.warn(_('not removing %s: %s (use forget to undo)\n')
135 ui.warn(_('not removing %s: %s (use forget to undo)\n')
136 % (m.rel(f), reason))
136 % (m.rel(f), reason))
137
137
138 if after:
138 if after:
139 remove, forget = deleted, []
139 remove, forget = deleted, []
140 warn(modified + added + clean, _('file still exists'))
140 warn(modified + added + clean, _('file still exists'))
141 else:
141 else:
142 remove, forget = deleted + clean, []
142 remove, forget = deleted + clean, []
143 warn(modified, _('file is modified'))
143 warn(modified, _('file is modified'))
144 warn(added, _('file has been marked for add'))
144 warn(added, _('file has been marked for add'))
145
145
146 for f in sorted(remove + forget):
146 for f in sorted(remove + forget):
147 if ui.verbose or not m.exact(f):
147 if ui.verbose or not m.exact(f):
148 ui.status(_('removing %s\n') % m.rel(f))
148 ui.status(_('removing %s\n') % m.rel(f))
149
149
150 # Need to lock because standin files are deleted then removed from the
150 # Need to lock because standin files are deleted then removed from the
151 # repository and we could race inbetween.
151 # repository and we could race inbetween.
152 wlock = repo.wlock()
152 wlock = repo.wlock()
153 try:
153 try:
154 lfdirstate = lfutil.openlfdirstate(ui, repo)
154 lfdirstate = lfutil.openlfdirstate(ui, repo)
155 for f in remove:
155 for f in remove:
156 if not after:
156 if not after:
157 # If this is being called by addremove, notify the user that we
157 # If this is being called by addremove, notify the user that we
158 # are removing the file.
158 # are removing the file.
159 if getattr(repo, "_isaddremove", False):
159 if getattr(repo, "_isaddremove", False):
160 ui.status(_('removing %s\n' % f))
160 ui.status(_('removing %s\n' % f))
161 if os.path.exists(repo.wjoin(f)):
161 if os.path.exists(repo.wjoin(f)):
162 os.unlink(repo.wjoin(f))
162 os.unlink(repo.wjoin(f))
163 currentdir = os.path.split(f)[0]
163 currentdir = os.path.split(f)[0]
164 while currentdir and not os.listdir(repo.wjoin(currentdir)):
164 while currentdir and not os.listdir(repo.wjoin(currentdir)):
165 os.rmdir(repo.wjoin(currentdir))
165 os.rmdir(repo.wjoin(currentdir))
166 currentdir = os.path.split(currentdir)[0]
166 currentdir = os.path.split(currentdir)[0]
167 lfdirstate.remove(f)
167 lfdirstate.remove(f)
168 lfdirstate.write()
168 lfdirstate.write()
169 forget = [lfutil.standin(f) for f in forget]
169 forget = [lfutil.standin(f) for f in forget]
170 remove = [lfutil.standin(f) for f in remove]
170 remove = [lfutil.standin(f) for f in remove]
171 lfutil.repo_forget(repo, forget)
171 lfutil.repo_forget(repo, forget)
172 # If this is being called by addremove, let the original addremove
172 # If this is being called by addremove, let the original addremove
173 # function handle this.
173 # function handle this.
174 if not getattr(repo, "_isaddremove", False):
174 if not getattr(repo, "_isaddremove", False):
175 lfutil.repo_remove(repo, remove, unlink=True)
175 lfutil.repo_remove(repo, remove, unlink=True)
176 finally:
176 finally:
177 wlock.release()
177 wlock.release()
178
178
179 # -- Wrappers: modify existing commands --------------------------------
179 # -- Wrappers: modify existing commands --------------------------------
180
180
181 # Add works by going through the files that the user wanted to add and
181 # Add works by going through the files that the user wanted to add and
182 # checking if they should be added as largefiles. Then it makes a new
182 # checking if they should be added as largefiles. Then it makes a new
183 # matcher which matches only the normal files and runs the original
183 # matcher which matches only the normal files and runs the original
184 # version of add.
184 # version of add.
185 def override_add(orig, ui, repo, *pats, **opts):
185 def override_add(orig, ui, repo, *pats, **opts):
186 bad = add_largefiles(ui, repo, *pats, **opts)
186 bad = add_largefiles(ui, repo, *pats, **opts)
187 installnormalfilesmatchfn(repo[None].manifest())
187 installnormalfilesmatchfn(repo[None].manifest())
188 result = orig(ui, repo, *pats, **opts)
188 result = orig(ui, repo, *pats, **opts)
189 restorematchfn()
189 restorematchfn()
190
190
191 return (result == 1 or bad) and 1 or 0
191 return (result == 1 or bad) and 1 or 0
192
192
193 def override_remove(orig, ui, repo, *pats, **opts):
193 def override_remove(orig, ui, repo, *pats, **opts):
194 installnormalfilesmatchfn(repo[None].manifest())
194 installnormalfilesmatchfn(repo[None].manifest())
195 orig(ui, repo, *pats, **opts)
195 orig(ui, repo, *pats, **opts)
196 restorematchfn()
196 restorematchfn()
197 remove_largefiles(ui, repo, *pats, **opts)
197 remove_largefiles(ui, repo, *pats, **opts)
198
198
199 def override_status(orig, ui, repo, *pats, **opts):
199 def override_status(orig, ui, repo, *pats, **opts):
200 try:
200 try:
201 repo.lfstatus = True
201 repo.lfstatus = True
202 return orig(ui, repo, *pats, **opts)
202 return orig(ui, repo, *pats, **opts)
203 finally:
203 finally:
204 repo.lfstatus = False
204 repo.lfstatus = False
205
205
206 def override_log(orig, ui, repo, *pats, **opts):
206 def override_log(orig, ui, repo, *pats, **opts):
207 try:
207 try:
208 repo.lfstatus = True
208 repo.lfstatus = True
209 orig(ui, repo, *pats, **opts)
209 orig(ui, repo, *pats, **opts)
210 finally:
210 finally:
211 repo.lfstatus = False
211 repo.lfstatus = False
212
212
213 def override_verify(orig, ui, repo, *pats, **opts):
213 def override_verify(orig, ui, repo, *pats, **opts):
214 large = opts.pop('large', False)
214 large = opts.pop('large', False)
215 all = opts.pop('lfa', False)
215 all = opts.pop('lfa', False)
216 contents = opts.pop('lfc', False)
216 contents = opts.pop('lfc', False)
217
217
218 result = orig(ui, repo, *pats, **opts)
218 result = orig(ui, repo, *pats, **opts)
219 if large:
219 if large:
220 result = result or lfcommands.verifylfiles(ui, repo, all, contents)
220 result = result or lfcommands.verifylfiles(ui, repo, all, contents)
221 return result
221 return result
222
222
223 # Override needs to refresh standins so that update's normal merge
223 # Override needs to refresh standins so that update's normal merge
224 # will go through properly. Then the other update hook (overriding repo.update)
224 # will go through properly. Then the other update hook (overriding repo.update)
225 # will get the new files. Filemerge is also overriden so that the merge
225 # will get the new files. Filemerge is also overriden so that the merge
226 # will merge standins correctly.
226 # will merge standins correctly.
227 def override_update(orig, ui, repo, *pats, **opts):
227 def override_update(orig, ui, repo, *pats, **opts):
228 lfdirstate = lfutil.openlfdirstate(ui, repo)
228 lfdirstate = lfutil.openlfdirstate(ui, repo)
229 s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [], False,
229 s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [], False,
230 False, False)
230 False, False)
231 (unsure, modified, added, removed, missing, unknown, ignored, clean) = s
231 (unsure, modified, added, removed, missing, unknown, ignored, clean) = s
232
232
233 # Need to lock between the standins getting updated and their
233 # Need to lock between the standins getting updated and their
234 # largefiles getting updated
234 # largefiles getting updated
235 wlock = repo.wlock()
235 wlock = repo.wlock()
236 try:
236 try:
237 if opts['check']:
237 if opts['check']:
238 mod = len(modified) > 0
238 mod = len(modified) > 0
239 for lfile in unsure:
239 for lfile in unsure:
240 standin = lfutil.standin(lfile)
240 standin = lfutil.standin(lfile)
241 if repo['.'][standin].data().strip() != \
241 if repo['.'][standin].data().strip() != \
242 lfutil.hashfile(repo.wjoin(lfile)):
242 lfutil.hashfile(repo.wjoin(lfile)):
243 mod = True
243 mod = True
244 else:
244 else:
245 lfdirstate.normal(lfile)
245 lfdirstate.normal(lfile)
246 lfdirstate.write()
246 lfdirstate.write()
247 if mod:
247 if mod:
248 raise util.Abort(_('uncommitted local changes'))
248 raise util.Abort(_('uncommitted local changes'))
249 # XXX handle removed differently
249 # XXX handle removed differently
250 if not opts['clean']:
250 if not opts['clean']:
251 for lfile in unsure + modified + added:
251 for lfile in unsure + modified + added:
252 lfutil.updatestandin(repo, lfutil.standin(lfile))
252 lfutil.updatestandin(repo, lfutil.standin(lfile))
253 finally:
253 finally:
254 wlock.release()
254 wlock.release()
255 return orig(ui, repo, *pats, **opts)
255 return orig(ui, repo, *pats, **opts)
256
256
257 # Before starting the manifest merge, merge.updates will call
257 # Before starting the manifest merge, merge.updates will call
258 # _checkunknown to check if there are any files in the merged-in
258 # _checkunknown to check if there are any files in the merged-in
259 # changeset that collide with unknown files in the working copy.
259 # changeset that collide with unknown files in the working copy.
260 #
260 #
261 # The largefiles are seen as unknown, so this prevents us from merging
261 # The largefiles are seen as unknown, so this prevents us from merging
262 # in a file 'foo' if we already have a largefile with the same name.
262 # in a file 'foo' if we already have a largefile with the same name.
263 #
263 #
264 # The overridden function filters the unknown files by removing any
264 # The overridden function filters the unknown files by removing any
265 # largefiles. This makes the merge proceed and we can then handle this
265 # largefiles. This makes the merge proceed and we can then handle this
266 # case further in the overridden manifestmerge function below.
266 # case further in the overridden manifestmerge function below.
267 def override_checkunknown(origfn, wctx, mctx, folding):
267 def override_checkunknown(origfn, wctx, mctx, folding):
268 origunknown = wctx.unknown()
268 origunknown = wctx.unknown()
269 wctx._unknown = filter(lambda f: lfutil.standin(f) not in wctx, origunknown)
269 wctx._unknown = filter(lambda f: lfutil.standin(f) not in wctx, origunknown)
270 try:
270 try:
271 return origfn(wctx, mctx, folding)
271 return origfn(wctx, mctx, folding)
272 finally:
272 finally:
273 wctx._unknown = origunknown
273 wctx._unknown = origunknown
274
274
275 # The manifest merge handles conflicts on the manifest level. We want
275 # The manifest merge handles conflicts on the manifest level. We want
276 # to handle changes in largefile-ness of files at this level too.
276 # to handle changes in largefile-ness of files at this level too.
277 #
277 #
278 # The strategy is to run the original manifestmerge and then process
278 # The strategy is to run the original manifestmerge and then process
279 # the action list it outputs. There are two cases we need to deal with:
279 # the action list it outputs. There are two cases we need to deal with:
280 #
280 #
281 # 1. Normal file in p1, largefile in p2. Here the largefile is
281 # 1. Normal file in p1, largefile in p2. Here the largefile is
282 # detected via its standin file, which will enter the working copy
282 # detected via its standin file, which will enter the working copy
283 # with a "get" action. It is not "merge" since the standin is all
283 # with a "get" action. It is not "merge" since the standin is all
284 # Mercurial is concerned with at this level -- the link to the
284 # Mercurial is concerned with at this level -- the link to the
285 # existing normal file is not relevant here.
285 # existing normal file is not relevant here.
286 #
286 #
287 # 2. Largefile in p1, normal file in p2. Here we get a "merge" action
287 # 2. Largefile in p1, normal file in p2. Here we get a "merge" action
288 # since the largefile will be present in the working copy and
288 # since the largefile will be present in the working copy and
289 # different from the normal file in p2. Mercurial therefore
289 # different from the normal file in p2. Mercurial therefore
290 # triggers a merge action.
290 # triggers a merge action.
291 #
291 #
292 # In both cases, we prompt the user and emit new actions to either
292 # In both cases, we prompt the user and emit new actions to either
293 # remove the standin (if the normal file was kept) or to remove the
293 # remove the standin (if the normal file was kept) or to remove the
294 # normal file and get the standin (if the largefile was kept). The
294 # normal file and get the standin (if the largefile was kept). The
295 # default prompt answer is to use the largefile version since it was
295 # default prompt answer is to use the largefile version since it was
296 # presumably changed on purpose.
296 # presumably changed on purpose.
297 #
297 #
298 # Finally, the merge.applyupdates function will then take care of
298 # Finally, the merge.applyupdates function will then take care of
299 # writing the files into the working copy and lfcommands.updatelfiles
299 # writing the files into the working copy and lfcommands.updatelfiles
300 # will update the largefiles.
300 # will update the largefiles.
301 def override_manifestmerge(origfn, repo, p1, p2, pa, overwrite, partial):
301 def override_manifestmerge(origfn, repo, p1, p2, pa, overwrite, partial):
302 actions = origfn(repo, p1, p2, pa, overwrite, partial)
302 actions = origfn(repo, p1, p2, pa, overwrite, partial)
303 processed = []
303 processed = []
304
304
305 for action in actions:
305 for action in actions:
306 if overwrite:
306 if overwrite:
307 processed.append(action)
307 processed.append(action)
308 continue
308 continue
309 f, m = action[:2]
309 f, m = action[:2]
310
310
311 choices = (_('&Largefile'), _('&Normal file'))
311 choices = (_('&Largefile'), _('&Normal file'))
312 if m == "g" and lfutil.splitstandin(f) in p1 and f in p2:
312 if m == "g" and lfutil.splitstandin(f) in p1 and f in p2:
313 # Case 1: normal file in the working copy, largefile in
313 # Case 1: normal file in the working copy, largefile in
314 # the second parent
314 # the second parent
315 lfile = lfutil.splitstandin(f)
315 lfile = lfutil.splitstandin(f)
316 standin = f
316 standin = f
317 msg = _('%s has been turned into a largefile\n'
317 msg = _('%s has been turned into a largefile\n'
318 'use (l)argefile or keep as (n)ormal file?') % lfile
318 'use (l)argefile or keep as (n)ormal file?') % lfile
319 if repo.ui.promptchoice(msg, choices, 0) == 0:
319 if repo.ui.promptchoice(msg, choices, 0) == 0:
320 processed.append((lfile, "r"))
320 processed.append((lfile, "r"))
321 processed.append((standin, "g", p2.flags(standin)))
321 processed.append((standin, "g", p2.flags(standin)))
322 else:
322 else:
323 processed.append((standin, "r"))
323 processed.append((standin, "r"))
324 elif m == "m" and lfutil.standin(f) in p1 and f in p2:
324 elif m == "m" and lfutil.standin(f) in p1 and f in p2:
325 # Case 2: largefile in the working copy, normal file in
325 # Case 2: largefile in the working copy, normal file in
326 # the second parent
326 # the second parent
327 standin = lfutil.standin(f)
327 standin = lfutil.standin(f)
328 lfile = f
328 lfile = f
329 msg = _('%s has been turned into a normal file\n'
329 msg = _('%s has been turned into a normal file\n'
330 'keep as (l)argefile or use (n)ormal file?') % lfile
330 'keep as (l)argefile or use (n)ormal file?') % lfile
331 if repo.ui.promptchoice(msg, choices, 0) == 0:
331 if repo.ui.promptchoice(msg, choices, 0) == 0:
332 processed.append((lfile, "r"))
332 processed.append((lfile, "r"))
333 else:
333 else:
334 processed.append((standin, "r"))
334 processed.append((standin, "r"))
335 processed.append((lfile, "g", p2.flags(lfile)))
335 processed.append((lfile, "g", p2.flags(lfile)))
336 else:
336 else:
337 processed.append(action)
337 processed.append(action)
338
338
339 return processed
339 return processed
340
340
341 # Override filemerge to prompt the user about how they wish to merge
341 # Override filemerge to prompt the user about how they wish to merge
342 # largefiles. This will handle identical edits, and copy/rename +
342 # largefiles. This will handle identical edits, and copy/rename +
343 # edit without prompting the user.
343 # edit without prompting the user.
344 def override_filemerge(origfn, repo, mynode, orig, fcd, fco, fca):
344 def override_filemerge(origfn, repo, mynode, orig, fcd, fco, fca):
345 # Use better variable names here. Because this is a wrapper we cannot
345 # Use better variable names here. Because this is a wrapper we cannot
346 # change the variable names in the function declaration.
346 # change the variable names in the function declaration.
347 fcdest, fcother, fcancestor = fcd, fco, fca
347 fcdest, fcother, fcancestor = fcd, fco, fca
348 if not lfutil.isstandin(orig):
348 if not lfutil.isstandin(orig):
349 return origfn(repo, mynode, orig, fcdest, fcother, fcancestor)
349 return origfn(repo, mynode, orig, fcdest, fcother, fcancestor)
350 else:
350 else:
351 if not fcother.cmp(fcdest): # files identical?
351 if not fcother.cmp(fcdest): # files identical?
352 return None
352 return None
353
353
354 # backwards, use working dir parent as ancestor
354 # backwards, use working dir parent as ancestor
355 if fcancestor == fcother:
355 if fcancestor == fcother:
356 fcancestor = fcdest.parents()[0]
356 fcancestor = fcdest.parents()[0]
357
357
358 if orig != fcother.path():
358 if orig != fcother.path():
359 repo.ui.status(_('merging %s and %s to %s\n')
359 repo.ui.status(_('merging %s and %s to %s\n')
360 % (lfutil.splitstandin(orig),
360 % (lfutil.splitstandin(orig),
361 lfutil.splitstandin(fcother.path()),
361 lfutil.splitstandin(fcother.path()),
362 lfutil.splitstandin(fcdest.path())))
362 lfutil.splitstandin(fcdest.path())))
363 else:
363 else:
364 repo.ui.status(_('merging %s\n')
364 repo.ui.status(_('merging %s\n')
365 % lfutil.splitstandin(fcdest.path()))
365 % lfutil.splitstandin(fcdest.path()))
366
366
367 if fcancestor.path() != fcother.path() and fcother.data() == \
367 if fcancestor.path() != fcother.path() and fcother.data() == \
368 fcancestor.data():
368 fcancestor.data():
369 return 0
369 return 0
370 if fcancestor.path() != fcdest.path() and fcdest.data() == \
370 if fcancestor.path() != fcdest.path() and fcdest.data() == \
371 fcancestor.data():
371 fcancestor.data():
372 repo.wwrite(fcdest.path(), fcother.data(), fcother.flags())
372 repo.wwrite(fcdest.path(), fcother.data(), fcother.flags())
373 return 0
373 return 0
374
374
375 if repo.ui.promptchoice(_('largefile %s has a merge conflict\n'
375 if repo.ui.promptchoice(_('largefile %s has a merge conflict\n'
376 'keep (l)ocal or take (o)ther?') %
376 'keep (l)ocal or take (o)ther?') %
377 lfutil.splitstandin(orig),
377 lfutil.splitstandin(orig),
378 (_('&Local'), _('&Other')), 0) == 0:
378 (_('&Local'), _('&Other')), 0) == 0:
379 return 0
379 return 0
380 else:
380 else:
381 repo.wwrite(fcdest.path(), fcother.data(), fcother.flags())
381 repo.wwrite(fcdest.path(), fcother.data(), fcother.flags())
382 return 0
382 return 0
383
383
384 # Copy first changes the matchers to match standins instead of
384 # Copy first changes the matchers to match standins instead of
385 # largefiles. Then it overrides util.copyfile in that function it
385 # largefiles. Then it overrides util.copyfile in that function it
386 # checks if the destination largefile already exists. It also keeps a
386 # checks if the destination largefile already exists. It also keeps a
387 # list of copied files so that the largefiles can be copied and the
387 # list of copied files so that the largefiles can be copied and the
388 # dirstate updated.
388 # dirstate updated.
389 def override_copy(orig, ui, repo, pats, opts, rename=False):
389 def override_copy(orig, ui, repo, pats, opts, rename=False):
390 # doesn't remove largefile on rename
390 # doesn't remove largefile on rename
391 if len(pats) < 2:
391 if len(pats) < 2:
392 # this isn't legal, let the original function deal with it
392 # this isn't legal, let the original function deal with it
393 return orig(ui, repo, pats, opts, rename)
393 return orig(ui, repo, pats, opts, rename)
394
394
395 def makestandin(relpath):
395 def makestandin(relpath):
396 path = scmutil.canonpath(repo.root, repo.getcwd(), relpath)
396 path = scmutil.canonpath(repo.root, repo.getcwd(), relpath)
397 return os.path.join(repo.wjoin(lfutil.standin(path)))
397 return os.path.join(repo.wjoin(lfutil.standin(path)))
398
398
399 fullpats = scmutil.expandpats(pats)
399 fullpats = scmutil.expandpats(pats)
400 dest = fullpats[-1]
400 dest = fullpats[-1]
401
401
402 if os.path.isdir(dest):
402 if os.path.isdir(dest):
403 if not os.path.isdir(makestandin(dest)):
403 if not os.path.isdir(makestandin(dest)):
404 os.makedirs(makestandin(dest))
404 os.makedirs(makestandin(dest))
405 # This could copy both lfiles and normal files in one command,
405 # This could copy both lfiles and normal files in one command,
406 # but we don't want to do that. First replace their matcher to
406 # but we don't want to do that. First replace their matcher to
407 # only match normal files and run it, then replace it to just
407 # only match normal files and run it, then replace it to just
408 # match largefiles and run it again.
408 # match largefiles and run it again.
409 nonormalfiles = False
409 nonormalfiles = False
410 nolfiles = False
410 nolfiles = False
411 try:
411 try:
412 try:
412 try:
413 installnormalfilesmatchfn(repo[None].manifest())
413 installnormalfilesmatchfn(repo[None].manifest())
414 result = orig(ui, repo, pats, opts, rename)
414 result = orig(ui, repo, pats, opts, rename)
415 except util.Abort, e:
415 except util.Abort, e:
416 if str(e) != 'no files to copy':
416 if str(e) != 'no files to copy':
417 raise e
417 raise e
418 else:
418 else:
419 nonormalfiles = True
419 nonormalfiles = True
420 result = 0
420 result = 0
421 finally:
421 finally:
422 restorematchfn()
422 restorematchfn()
423
423
424 # The first rename can cause our current working directory to be removed.
424 # The first rename can cause our current working directory to be removed.
425 # In that case there is nothing left to copy/rename so just quit.
425 # In that case there is nothing left to copy/rename so just quit.
426 try:
426 try:
427 repo.getcwd()
427 repo.getcwd()
428 except OSError:
428 except OSError:
429 return result
429 return result
430
430
431 try:
431 try:
432 try:
432 try:
433 # When we call orig below it creates the standins but we don't add them
433 # When we call orig below it creates the standins but we don't add them
434 # to the dir state until later so lock during that time.
434 # to the dir state until later so lock during that time.
435 wlock = repo.wlock()
435 wlock = repo.wlock()
436
436
437 manifest = repo[None].manifest()
437 manifest = repo[None].manifest()
438 oldmatch = None # for the closure
438 oldmatch = None # for the closure
439 def override_match(ctx, pats=[], opts={}, globbed=False,
439 def override_match(ctx, pats=[], opts={}, globbed=False,
440 default='relpath'):
440 default='relpath'):
441 newpats = []
441 newpats = []
442 # The patterns were previously mangled to add the standin
442 # The patterns were previously mangled to add the standin
443 # directory; we need to remove that now
443 # directory; we need to remove that now
444 for pat in pats:
444 for pat in pats:
445 if match_.patkind(pat) is None and lfutil.shortname in pat:
445 if match_.patkind(pat) is None and lfutil.shortname in pat:
446 newpats.append(pat.replace(lfutil.shortname, ''))
446 newpats.append(pat.replace(lfutil.shortname, ''))
447 else:
447 else:
448 newpats.append(pat)
448 newpats.append(pat)
449 match = oldmatch(ctx, newpats, opts, globbed, default)
449 match = oldmatch(ctx, newpats, opts, globbed, default)
450 m = copy.copy(match)
450 m = copy.copy(match)
451 lfile = lambda f: lfutil.standin(f) in manifest
451 lfile = lambda f: lfutil.standin(f) in manifest
452 m._files = [lfutil.standin(f) for f in m._files if lfile(f)]
452 m._files = [lfutil.standin(f) for f in m._files if lfile(f)]
453 m._fmap = set(m._files)
453 m._fmap = set(m._files)
454 orig_matchfn = m.matchfn
454 orig_matchfn = m.matchfn
455 m.matchfn = lambda f: (lfutil.isstandin(f) and
455 m.matchfn = lambda f: (lfutil.isstandin(f) and
456 lfile(lfutil.splitstandin(f)) and
456 lfile(lfutil.splitstandin(f)) and
457 orig_matchfn(lfutil.splitstandin(f)) or
457 orig_matchfn(lfutil.splitstandin(f)) or
458 None)
458 None)
459 return m
459 return m
460 oldmatch = installmatchfn(override_match)
460 oldmatch = installmatchfn(override_match)
461 listpats = []
461 listpats = []
462 for pat in pats:
462 for pat in pats:
463 if match_.patkind(pat) is not None:
463 if match_.patkind(pat) is not None:
464 listpats.append(pat)
464 listpats.append(pat)
465 else:
465 else:
466 listpats.append(makestandin(pat))
466 listpats.append(makestandin(pat))
467
467
468 try:
468 try:
469 origcopyfile = util.copyfile
469 origcopyfile = util.copyfile
470 copiedfiles = []
470 copiedfiles = []
471 def override_copyfile(src, dest):
471 def override_copyfile(src, dest):
472 if (lfutil.shortname in src and
472 if (lfutil.shortname in src and
473 dest.startswith(repo.wjoin(lfutil.shortname))):
473 dest.startswith(repo.wjoin(lfutil.shortname))):
474 destlfile = dest.replace(lfutil.shortname, '')
474 destlfile = dest.replace(lfutil.shortname, '')
475 if not opts['force'] and os.path.exists(destlfile):
475 if not opts['force'] and os.path.exists(destlfile):
476 raise IOError('',
476 raise IOError('',
477 _('destination largefile already exists'))
477 _('destination largefile already exists'))
478 copiedfiles.append((src, dest))
478 copiedfiles.append((src, dest))
479 origcopyfile(src, dest)
479 origcopyfile(src, dest)
480
480
481 util.copyfile = override_copyfile
481 util.copyfile = override_copyfile
482 result += orig(ui, repo, listpats, opts, rename)
482 result += orig(ui, repo, listpats, opts, rename)
483 finally:
483 finally:
484 util.copyfile = origcopyfile
484 util.copyfile = origcopyfile
485
485
486 lfdirstate = lfutil.openlfdirstate(ui, repo)
486 lfdirstate = lfutil.openlfdirstate(ui, repo)
487 for (src, dest) in copiedfiles:
487 for (src, dest) in copiedfiles:
488 if (lfutil.shortname in src and
488 if (lfutil.shortname in src and
489 dest.startswith(repo.wjoin(lfutil.shortname))):
489 dest.startswith(repo.wjoin(lfutil.shortname))):
490 srclfile = src.replace(repo.wjoin(lfutil.standin('')), '')
490 srclfile = src.replace(repo.wjoin(lfutil.standin('')), '')
491 destlfile = dest.replace(repo.wjoin(lfutil.standin('')), '')
491 destlfile = dest.replace(repo.wjoin(lfutil.standin('')), '')
492 destlfiledir = os.path.dirname(destlfile) or '.'
492 destlfiledir = os.path.dirname(destlfile) or '.'
493 if not os.path.isdir(destlfiledir):
493 if not os.path.isdir(destlfiledir):
494 os.makedirs(destlfiledir)
494 os.makedirs(destlfiledir)
495 if rename:
495 if rename:
496 os.rename(repo.wjoin(srclfile), repo.wjoin(destlfile))
496 os.rename(repo.wjoin(srclfile), repo.wjoin(destlfile))
497 lfdirstate.remove(srclfile)
497 lfdirstate.remove(srclfile)
498 else:
498 else:
499 util.copyfile(srclfile, destlfile)
499 util.copyfile(srclfile, destlfile)
500 lfdirstate.add(destlfile)
500 lfdirstate.add(destlfile)
501 lfdirstate.write()
501 lfdirstate.write()
502 except util.Abort, e:
502 except util.Abort, e:
503 if str(e) != 'no files to copy':
503 if str(e) != 'no files to copy':
504 raise e
504 raise e
505 else:
505 else:
506 nolfiles = True
506 nolfiles = True
507 finally:
507 finally:
508 restorematchfn()
508 restorematchfn()
509 wlock.release()
509 wlock.release()
510
510
511 if nolfiles and nonormalfiles:
511 if nolfiles and nonormalfiles:
512 raise util.Abort(_('no files to copy'))
512 raise util.Abort(_('no files to copy'))
513
513
514 return result
514 return result
515
515
516 # When the user calls revert, we have to be careful to not revert any
516 # When the user calls revert, we have to be careful to not revert any
517 # changes to other largefiles accidentally. This means we have to keep
517 # changes to other largefiles accidentally. This means we have to keep
518 # track of the largefiles that are being reverted so we only pull down
518 # track of the largefiles that are being reverted so we only pull down
519 # the necessary largefiles.
519 # the necessary largefiles.
520 #
520 #
521 # Standins are only updated (to match the hash of largefiles) before
521 # Standins are only updated (to match the hash of largefiles) before
522 # commits. Update the standins then run the original revert, changing
522 # commits. Update the standins then run the original revert, changing
523 # the matcher to hit standins instead of largefiles. Based on the
523 # the matcher to hit standins instead of largefiles. Based on the
524 # resulting standins update the largefiles. Then return the standins
524 # resulting standins update the largefiles. Then return the standins
525 # to their proper state
525 # to their proper state
526 def override_revert(orig, ui, repo, *pats, **opts):
526 def override_revert(orig, ui, repo, *pats, **opts):
527 # Because we put the standins in a bad state (by updating them)
527 # Because we put the standins in a bad state (by updating them)
528 # and then return them to a correct state we need to lock to
528 # and then return them to a correct state we need to lock to
529 # prevent others from changing them in their incorrect state.
529 # prevent others from changing them in their incorrect state.
530 wlock = repo.wlock()
530 wlock = repo.wlock()
531 try:
531 try:
532 lfdirstate = lfutil.openlfdirstate(ui, repo)
532 lfdirstate = lfutil.openlfdirstate(ui, repo)
533 (modified, added, removed, missing, unknown, ignored, clean) = \
533 (modified, added, removed, missing, unknown, ignored, clean) = \
534 lfutil.lfdirstate_status(lfdirstate, repo, repo['.'].rev())
534 lfutil.lfdirstate_status(lfdirstate, repo, repo['.'].rev())
535 for lfile in modified:
535 for lfile in modified:
536 lfutil.updatestandin(repo, lfutil.standin(lfile))
536 lfutil.updatestandin(repo, lfutil.standin(lfile))
537
537
538 try:
538 try:
539 ctx = repo[opts.get('rev')]
539 ctx = repo[opts.get('rev')]
540 oldmatch = None # for the closure
540 oldmatch = None # for the closure
541 def override_match(ctx, pats=[], opts={}, globbed=False,
541 def override_match(ctx, pats=[], opts={}, globbed=False,
542 default='relpath'):
542 default='relpath'):
543 match = oldmatch(ctx, pats, opts, globbed, default)
543 match = oldmatch(ctx, pats, opts, globbed, default)
544 m = copy.copy(match)
544 m = copy.copy(match)
545 def tostandin(f):
545 def tostandin(f):
546 if lfutil.standin(f) in ctx or lfutil.standin(f) in ctx:
546 if lfutil.standin(f) in ctx or lfutil.standin(f) in ctx:
547 return lfutil.standin(f)
547 return lfutil.standin(f)
548 elif lfutil.standin(f) in repo[None]:
548 elif lfutil.standin(f) in repo[None]:
549 return None
549 return None
550 return f
550 return f
551 m._files = [tostandin(f) for f in m._files]
551 m._files = [tostandin(f) for f in m._files]
552 m._files = [f for f in m._files if f is not None]
552 m._files = [f for f in m._files if f is not None]
553 m._fmap = set(m._files)
553 m._fmap = set(m._files)
554 orig_matchfn = m.matchfn
554 orig_matchfn = m.matchfn
555 def matchfn(f):
555 def matchfn(f):
556 if lfutil.isstandin(f):
556 if lfutil.isstandin(f):
557 # We need to keep track of what largefiles are being
557 # We need to keep track of what largefiles are being
558 # matched so we know which ones to update later --
558 # matched so we know which ones to update later --
559 # otherwise we accidentally revert changes to other
559 # otherwise we accidentally revert changes to other
560 # largefiles. This is repo-specific, so duckpunch the
560 # largefiles. This is repo-specific, so duckpunch the
561 # repo object to keep the list of largefiles for us
561 # repo object to keep the list of largefiles for us
562 # later.
562 # later.
563 if orig_matchfn(lfutil.splitstandin(f)) and \
563 if orig_matchfn(lfutil.splitstandin(f)) and \
564 (f in repo[None] or f in ctx):
564 (f in repo[None] or f in ctx):
565 lfileslist = getattr(repo, '_lfilestoupdate', [])
565 lfileslist = getattr(repo, '_lfilestoupdate', [])
566 lfileslist.append(lfutil.splitstandin(f))
566 lfileslist.append(lfutil.splitstandin(f))
567 repo._lfilestoupdate = lfileslist
567 repo._lfilestoupdate = lfileslist
568 return True
568 return True
569 else:
569 else:
570 return False
570 return False
571 return orig_matchfn(f)
571 return orig_matchfn(f)
572 m.matchfn = matchfn
572 m.matchfn = matchfn
573 return m
573 return m
574 oldmatch = installmatchfn(override_match)
574 oldmatch = installmatchfn(override_match)
575 scmutil.match
575 scmutil.match
576 matches = override_match(repo[None], pats, opts)
576 matches = override_match(repo[None], pats, opts)
577 orig(ui, repo, *pats, **opts)
577 orig(ui, repo, *pats, **opts)
578 finally:
578 finally:
579 restorematchfn()
579 restorematchfn()
580 lfileslist = getattr(repo, '_lfilestoupdate', [])
580 lfileslist = getattr(repo, '_lfilestoupdate', [])
581 lfcommands.updatelfiles(ui, repo, filelist=lfileslist,
581 lfcommands.updatelfiles(ui, repo, filelist=lfileslist,
582 printmessage=False)
582 printmessage=False)
583
583
584 # empty out the largefiles list so we start fresh next time
584 # empty out the largefiles list so we start fresh next time
585 repo._lfilestoupdate = []
585 repo._lfilestoupdate = []
586 for lfile in modified:
586 for lfile in modified:
587 if lfile in lfileslist:
587 if lfile in lfileslist:
588 if os.path.exists(repo.wjoin(lfutil.standin(lfile))) and lfile\
588 if os.path.exists(repo.wjoin(lfutil.standin(lfile))) and lfile\
589 in repo['.']:
589 in repo['.']:
590 lfutil.writestandin(repo, lfutil.standin(lfile),
590 lfutil.writestandin(repo, lfutil.standin(lfile),
591 repo['.'][lfile].data().strip(),
591 repo['.'][lfile].data().strip(),
592 'x' in repo['.'][lfile].flags())
592 'x' in repo['.'][lfile].flags())
593 lfdirstate = lfutil.openlfdirstate(ui, repo)
593 lfdirstate = lfutil.openlfdirstate(ui, repo)
594 for lfile in added:
594 for lfile in added:
595 standin = lfutil.standin(lfile)
595 standin = lfutil.standin(lfile)
596 if standin not in ctx and (standin in matches or opts.get('all')):
596 if standin not in ctx and (standin in matches or opts.get('all')):
597 if lfile in lfdirstate:
597 if lfile in lfdirstate:
598 lfdirstate.drop(lfile)
598 lfdirstate.drop(lfile)
599 util.unlinkpath(repo.wjoin(standin))
599 util.unlinkpath(repo.wjoin(standin))
600 lfdirstate.write()
600 lfdirstate.write()
601 finally:
601 finally:
602 wlock.release()
602 wlock.release()
603
603
604 def hg_update(orig, repo, node):
604 def hg_update(orig, repo, node):
605 result = orig(repo, node)
605 result = orig(repo, node)
606 lfcommands.updatelfiles(repo.ui, repo)
606 lfcommands.updatelfiles(repo.ui, repo)
607 return result
607 return result
608
608
609 def hg_clean(orig, repo, node, show_stats=True):
609 def hg_clean(orig, repo, node, show_stats=True):
610 result = orig(repo, node, show_stats)
610 result = orig(repo, node, show_stats)
611 lfcommands.updatelfiles(repo.ui, repo)
611 lfcommands.updatelfiles(repo.ui, repo)
612 return result
612 return result
613
613
614 def hg_merge(orig, repo, node, force=None, remind=True):
614 def hg_merge(orig, repo, node, force=None, remind=True):
615 result = orig(repo, node, force, remind)
615 # Mark the repo as being in the middle of a merge, so that
616 lfcommands.updatelfiles(repo.ui, repo)
616 # updatelfiles() will know that it needs to trust the standins in
617 # the working copy, not in the standins in the current node
618 repo._ismerging = True
619 try:
620 result = orig(repo, node, force, remind)
621 lfcommands.updatelfiles(repo.ui, repo)
622 finally:
623 repo._ismerging = False
617 return result
624 return result
618
625
619 # When we rebase a repository with remotely changed largefiles, we need to
626 # When we rebase a repository with remotely changed largefiles, we need to
620 # take some extra care so that the largefiles are correctly updated in the
627 # take some extra care so that the largefiles are correctly updated in the
621 # working copy
628 # working copy
622 def override_pull(orig, ui, repo, source=None, **opts):
629 def override_pull(orig, ui, repo, source=None, **opts):
623 if opts.get('rebase', False):
630 if opts.get('rebase', False):
624 repo._isrebasing = True
631 repo._isrebasing = True
625 try:
632 try:
626 if opts.get('update'):
633 if opts.get('update'):
627 del opts['update']
634 del opts['update']
628 ui.debug('--update and --rebase are not compatible, ignoring '
635 ui.debug('--update and --rebase are not compatible, ignoring '
629 'the update flag\n')
636 'the update flag\n')
630 del opts['rebase']
637 del opts['rebase']
631 cmdutil.bailifchanged(repo)
638 cmdutil.bailifchanged(repo)
632 revsprepull = len(repo)
639 revsprepull = len(repo)
633 origpostincoming = commands.postincoming
640 origpostincoming = commands.postincoming
634 def _dummy(*args, **kwargs):
641 def _dummy(*args, **kwargs):
635 pass
642 pass
636 commands.postincoming = _dummy
643 commands.postincoming = _dummy
637 repo.lfpullsource = source
644 repo.lfpullsource = source
638 if not source:
645 if not source:
639 source = 'default'
646 source = 'default'
640 try:
647 try:
641 result = commands.pull(ui, repo, source, **opts)
648 result = commands.pull(ui, repo, source, **opts)
642 finally:
649 finally:
643 commands.postincoming = origpostincoming
650 commands.postincoming = origpostincoming
644 revspostpull = len(repo)
651 revspostpull = len(repo)
645 if revspostpull > revsprepull:
652 if revspostpull > revsprepull:
646 result = result or rebase.rebase(ui, repo)
653 result = result or rebase.rebase(ui, repo)
647 finally:
654 finally:
648 repo._isrebasing = False
655 repo._isrebasing = False
649 else:
656 else:
650 repo.lfpullsource = source
657 repo.lfpullsource = source
651 if not source:
658 if not source:
652 source = 'default'
659 source = 'default'
653 result = orig(ui, repo, source, **opts)
660 result = orig(ui, repo, source, **opts)
654 return result
661 return result
655
662
656 def override_rebase(orig, ui, repo, **opts):
663 def override_rebase(orig, ui, repo, **opts):
657 repo._isrebasing = True
664 repo._isrebasing = True
658 try:
665 try:
659 orig(ui, repo, **opts)
666 orig(ui, repo, **opts)
660 finally:
667 finally:
661 repo._isrebasing = False
668 repo._isrebasing = False
662
669
663 def override_archive(orig, repo, dest, node, kind, decode=True, matchfn=None,
670 def override_archive(orig, repo, dest, node, kind, decode=True, matchfn=None,
664 prefix=None, mtime=None, subrepos=None):
671 prefix=None, mtime=None, subrepos=None):
665 # No need to lock because we are only reading history and
672 # No need to lock because we are only reading history and
666 # largefile caches, neither of which are modified.
673 # largefile caches, neither of which are modified.
667 lfcommands.cachelfiles(repo.ui, repo, node)
674 lfcommands.cachelfiles(repo.ui, repo, node)
668
675
669 if kind not in archival.archivers:
676 if kind not in archival.archivers:
670 raise util.Abort(_("unknown archive type '%s'") % kind)
677 raise util.Abort(_("unknown archive type '%s'") % kind)
671
678
672 ctx = repo[node]
679 ctx = repo[node]
673
680
674 if kind == 'files':
681 if kind == 'files':
675 if prefix:
682 if prefix:
676 raise util.Abort(
683 raise util.Abort(
677 _('cannot give prefix when archiving to files'))
684 _('cannot give prefix when archiving to files'))
678 else:
685 else:
679 prefix = archival.tidyprefix(dest, kind, prefix)
686 prefix = archival.tidyprefix(dest, kind, prefix)
680
687
681 def write(name, mode, islink, getdata):
688 def write(name, mode, islink, getdata):
682 if matchfn and not matchfn(name):
689 if matchfn and not matchfn(name):
683 return
690 return
684 data = getdata()
691 data = getdata()
685 if decode:
692 if decode:
686 data = repo.wwritedata(name, data)
693 data = repo.wwritedata(name, data)
687 archiver.addfile(prefix + name, mode, islink, data)
694 archiver.addfile(prefix + name, mode, islink, data)
688
695
689 archiver = archival.archivers[kind](dest, mtime or ctx.date()[0])
696 archiver = archival.archivers[kind](dest, mtime or ctx.date()[0])
690
697
691 if repo.ui.configbool("ui", "archivemeta", True):
698 if repo.ui.configbool("ui", "archivemeta", True):
692 def metadata():
699 def metadata():
693 base = 'repo: %s\nnode: %s\nbranch: %s\n' % (
700 base = 'repo: %s\nnode: %s\nbranch: %s\n' % (
694 hex(repo.changelog.node(0)), hex(node), ctx.branch())
701 hex(repo.changelog.node(0)), hex(node), ctx.branch())
695
702
696 tags = ''.join('tag: %s\n' % t for t in ctx.tags()
703 tags = ''.join('tag: %s\n' % t for t in ctx.tags()
697 if repo.tagtype(t) == 'global')
704 if repo.tagtype(t) == 'global')
698 if not tags:
705 if not tags:
699 repo.ui.pushbuffer()
706 repo.ui.pushbuffer()
700 opts = {'template': '{latesttag}\n{latesttagdistance}',
707 opts = {'template': '{latesttag}\n{latesttagdistance}',
701 'style': '', 'patch': None, 'git': None}
708 'style': '', 'patch': None, 'git': None}
702 cmdutil.show_changeset(repo.ui, repo, opts).show(ctx)
709 cmdutil.show_changeset(repo.ui, repo, opts).show(ctx)
703 ltags, dist = repo.ui.popbuffer().split('\n')
710 ltags, dist = repo.ui.popbuffer().split('\n')
704 tags = ''.join('latesttag: %s\n' % t for t in ltags.split(':'))
711 tags = ''.join('latesttag: %s\n' % t for t in ltags.split(':'))
705 tags += 'latesttagdistance: %s\n' % dist
712 tags += 'latesttagdistance: %s\n' % dist
706
713
707 return base + tags
714 return base + tags
708
715
709 write('.hg_archival.txt', 0644, False, metadata)
716 write('.hg_archival.txt', 0644, False, metadata)
710
717
711 for f in ctx:
718 for f in ctx:
712 ff = ctx.flags(f)
719 ff = ctx.flags(f)
713 getdata = ctx[f].data
720 getdata = ctx[f].data
714 if lfutil.isstandin(f):
721 if lfutil.isstandin(f):
715 path = lfutil.findfile(repo, getdata().strip())
722 path = lfutil.findfile(repo, getdata().strip())
716 f = lfutil.splitstandin(f)
723 f = lfutil.splitstandin(f)
717
724
718 def getdatafn():
725 def getdatafn():
719 fd = None
726 fd = None
720 try:
727 try:
721 fd = open(path, 'rb')
728 fd = open(path, 'rb')
722 return fd.read()
729 return fd.read()
723 finally:
730 finally:
724 if fd:
731 if fd:
725 fd.close()
732 fd.close()
726
733
727 getdata = getdatafn
734 getdata = getdatafn
728 write(f, 'x' in ff and 0755 or 0644, 'l' in ff, getdata)
735 write(f, 'x' in ff and 0755 or 0644, 'l' in ff, getdata)
729
736
730 if subrepos:
737 if subrepos:
731 for subpath in ctx.substate:
738 for subpath in ctx.substate:
732 sub = ctx.sub(subpath)
739 sub = ctx.sub(subpath)
733 sub.archive(repo.ui, archiver, prefix)
740 sub.archive(repo.ui, archiver, prefix)
734
741
735 archiver.done()
742 archiver.done()
736
743
737 # If a largefile is modified, the change is not reflected in its
744 # If a largefile is modified, the change is not reflected in its
738 # standin until a commit. cmdutil.bailifchanged() raises an exception
745 # standin until a commit. cmdutil.bailifchanged() raises an exception
739 # if the repo has uncommitted changes. Wrap it to also check if
746 # if the repo has uncommitted changes. Wrap it to also check if
740 # largefiles were changed. This is used by bisect and backout.
747 # largefiles were changed. This is used by bisect and backout.
741 def override_bailifchanged(orig, repo):
748 def override_bailifchanged(orig, repo):
742 orig(repo)
749 orig(repo)
743 repo.lfstatus = True
750 repo.lfstatus = True
744 modified, added, removed, deleted = repo.status()[:4]
751 modified, added, removed, deleted = repo.status()[:4]
745 repo.lfstatus = False
752 repo.lfstatus = False
746 if modified or added or removed or deleted:
753 if modified or added or removed or deleted:
747 raise util.Abort(_('outstanding uncommitted changes'))
754 raise util.Abort(_('outstanding uncommitted changes'))
748
755
749 # Fetch doesn't use cmdutil.bail_if_changed so override it to add the check
756 # Fetch doesn't use cmdutil.bail_if_changed so override it to add the check
750 def override_fetch(orig, ui, repo, *pats, **opts):
757 def override_fetch(orig, ui, repo, *pats, **opts):
751 repo.lfstatus = True
758 repo.lfstatus = True
752 modified, added, removed, deleted = repo.status()[:4]
759 modified, added, removed, deleted = repo.status()[:4]
753 repo.lfstatus = False
760 repo.lfstatus = False
754 if modified or added or removed or deleted:
761 if modified or added or removed or deleted:
755 raise util.Abort(_('outstanding uncommitted changes'))
762 raise util.Abort(_('outstanding uncommitted changes'))
756 return orig(ui, repo, *pats, **opts)
763 return orig(ui, repo, *pats, **opts)
757
764
758 def override_forget(orig, ui, repo, *pats, **opts):
765 def override_forget(orig, ui, repo, *pats, **opts):
759 installnormalfilesmatchfn(repo[None].manifest())
766 installnormalfilesmatchfn(repo[None].manifest())
760 orig(ui, repo, *pats, **opts)
767 orig(ui, repo, *pats, **opts)
761 restorematchfn()
768 restorematchfn()
762 m = scmutil.match(repo[None], pats, opts)
769 m = scmutil.match(repo[None], pats, opts)
763
770
764 try:
771 try:
765 repo.lfstatus = True
772 repo.lfstatus = True
766 s = repo.status(match=m, clean=True)
773 s = repo.status(match=m, clean=True)
767 finally:
774 finally:
768 repo.lfstatus = False
775 repo.lfstatus = False
769 forget = sorted(s[0] + s[1] + s[3] + s[6])
776 forget = sorted(s[0] + s[1] + s[3] + s[6])
770 forget = [f for f in forget if lfutil.standin(f) in repo[None].manifest()]
777 forget = [f for f in forget if lfutil.standin(f) in repo[None].manifest()]
771
778
772 for f in forget:
779 for f in forget:
773 if lfutil.standin(f) not in repo.dirstate and not \
780 if lfutil.standin(f) not in repo.dirstate and not \
774 os.path.isdir(m.rel(lfutil.standin(f))):
781 os.path.isdir(m.rel(lfutil.standin(f))):
775 ui.warn(_('not removing %s: file is already untracked\n')
782 ui.warn(_('not removing %s: file is already untracked\n')
776 % m.rel(f))
783 % m.rel(f))
777
784
778 for f in forget:
785 for f in forget:
779 if ui.verbose or not m.exact(f):
786 if ui.verbose or not m.exact(f):
780 ui.status(_('removing %s\n') % m.rel(f))
787 ui.status(_('removing %s\n') % m.rel(f))
781
788
782 # Need to lock because standin files are deleted then removed from the
789 # Need to lock because standin files are deleted then removed from the
783 # repository and we could race inbetween.
790 # repository and we could race inbetween.
784 wlock = repo.wlock()
791 wlock = repo.wlock()
785 try:
792 try:
786 lfdirstate = lfutil.openlfdirstate(ui, repo)
793 lfdirstate = lfutil.openlfdirstate(ui, repo)
787 for f in forget:
794 for f in forget:
788 if lfdirstate[f] == 'a':
795 if lfdirstate[f] == 'a':
789 lfdirstate.drop(f)
796 lfdirstate.drop(f)
790 else:
797 else:
791 lfdirstate.remove(f)
798 lfdirstate.remove(f)
792 lfdirstate.write()
799 lfdirstate.write()
793 lfutil.repo_remove(repo, [lfutil.standin(f) for f in forget],
800 lfutil.repo_remove(repo, [lfutil.standin(f) for f in forget],
794 unlink=True)
801 unlink=True)
795 finally:
802 finally:
796 wlock.release()
803 wlock.release()
797
804
798 def getoutgoinglfiles(ui, repo, dest=None, **opts):
805 def getoutgoinglfiles(ui, repo, dest=None, **opts):
799 dest = ui.expandpath(dest or 'default-push', dest or 'default')
806 dest = ui.expandpath(dest or 'default-push', dest or 'default')
800 dest, branches = hg.parseurl(dest, opts.get('branch'))
807 dest, branches = hg.parseurl(dest, opts.get('branch'))
801 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
808 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
802 if revs:
809 if revs:
803 revs = [repo.lookup(rev) for rev in revs]
810 revs = [repo.lookup(rev) for rev in revs]
804
811
805 remoteui = hg.remoteui
812 remoteui = hg.remoteui
806
813
807 try:
814 try:
808 remote = hg.repository(remoteui(repo, opts), dest)
815 remote = hg.repository(remoteui(repo, opts), dest)
809 except error.RepoError:
816 except error.RepoError:
810 return None
817 return None
811 o = lfutil.findoutgoing(repo, remote, False)
818 o = lfutil.findoutgoing(repo, remote, False)
812 if not o:
819 if not o:
813 return None
820 return None
814 o = repo.changelog.nodesbetween(o, revs)[0]
821 o = repo.changelog.nodesbetween(o, revs)[0]
815 if opts.get('newest_first'):
822 if opts.get('newest_first'):
816 o.reverse()
823 o.reverse()
817
824
818 toupload = set()
825 toupload = set()
819 for n in o:
826 for n in o:
820 parents = [p for p in repo.changelog.parents(n) if p != node.nullid]
827 parents = [p for p in repo.changelog.parents(n) if p != node.nullid]
821 ctx = repo[n]
828 ctx = repo[n]
822 files = set(ctx.files())
829 files = set(ctx.files())
823 if len(parents) == 2:
830 if len(parents) == 2:
824 mc = ctx.manifest()
831 mc = ctx.manifest()
825 mp1 = ctx.parents()[0].manifest()
832 mp1 = ctx.parents()[0].manifest()
826 mp2 = ctx.parents()[1].manifest()
833 mp2 = ctx.parents()[1].manifest()
827 for f in mp1:
834 for f in mp1:
828 if f not in mc:
835 if f not in mc:
829 files.add(f)
836 files.add(f)
830 for f in mp2:
837 for f in mp2:
831 if f not in mc:
838 if f not in mc:
832 files.add(f)
839 files.add(f)
833 for f in mc:
840 for f in mc:
834 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
841 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
835 files.add(f)
842 files.add(f)
836 toupload = toupload.union(
843 toupload = toupload.union(
837 set([f for f in files if lfutil.isstandin(f) and f in ctx]))
844 set([f for f in files if lfutil.isstandin(f) and f in ctx]))
838 return toupload
845 return toupload
839
846
840 def override_outgoing(orig, ui, repo, dest=None, **opts):
847 def override_outgoing(orig, ui, repo, dest=None, **opts):
841 orig(ui, repo, dest, **opts)
848 orig(ui, repo, dest, **opts)
842
849
843 if opts.pop('large', None):
850 if opts.pop('large', None):
844 toupload = getoutgoinglfiles(ui, repo, dest, **opts)
851 toupload = getoutgoinglfiles(ui, repo, dest, **opts)
845 if toupload is None:
852 if toupload is None:
846 ui.status(_('largefiles: No remote repo\n'))
853 ui.status(_('largefiles: No remote repo\n'))
847 else:
854 else:
848 ui.status(_('largefiles to upload:\n'))
855 ui.status(_('largefiles to upload:\n'))
849 for file in toupload:
856 for file in toupload:
850 ui.status(lfutil.splitstandin(file) + '\n')
857 ui.status(lfutil.splitstandin(file) + '\n')
851 ui.status('\n')
858 ui.status('\n')
852
859
853 def override_summary(orig, ui, repo, *pats, **opts):
860 def override_summary(orig, ui, repo, *pats, **opts):
854 try:
861 try:
855 repo.lfstatus = True
862 repo.lfstatus = True
856 orig(ui, repo, *pats, **opts)
863 orig(ui, repo, *pats, **opts)
857 finally:
864 finally:
858 repo.lfstatus = False
865 repo.lfstatus = False
859
866
860 if opts.pop('large', None):
867 if opts.pop('large', None):
861 toupload = getoutgoinglfiles(ui, repo, None, **opts)
868 toupload = getoutgoinglfiles(ui, repo, None, **opts)
862 if toupload is None:
869 if toupload is None:
863 ui.status(_('largefiles: No remote repo\n'))
870 ui.status(_('largefiles: No remote repo\n'))
864 else:
871 else:
865 ui.status(_('largefiles: %d to upload\n') % len(toupload))
872 ui.status(_('largefiles: %d to upload\n') % len(toupload))
866
873
867 def override_addremove(orig, ui, repo, *pats, **opts):
874 def override_addremove(orig, ui, repo, *pats, **opts):
868 # Get the list of missing largefiles so we can remove them
875 # Get the list of missing largefiles so we can remove them
869 lfdirstate = lfutil.openlfdirstate(ui, repo)
876 lfdirstate = lfutil.openlfdirstate(ui, repo)
870 s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [], False,
877 s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [], False,
871 False, False)
878 False, False)
872 (unsure, modified, added, removed, missing, unknown, ignored, clean) = s
879 (unsure, modified, added, removed, missing, unknown, ignored, clean) = s
873
880
874 # Call into the normal remove code, but the removing of the standin, we want
881 # Call into the normal remove code, but the removing of the standin, we want
875 # to have handled by original addremove. Monkey patching here makes sure
882 # to have handled by original addremove. Monkey patching here makes sure
876 # we don't remove the standin in the largefiles code, preventing a very
883 # we don't remove the standin in the largefiles code, preventing a very
877 # confused state later.
884 # confused state later.
878 repo._isaddremove = True
885 repo._isaddremove = True
879 remove_largefiles(ui, repo, *missing, **opts)
886 remove_largefiles(ui, repo, *missing, **opts)
880 repo._isaddremove = False
887 repo._isaddremove = False
881 # Call into the normal add code, and any files that *should* be added as
888 # Call into the normal add code, and any files that *should* be added as
882 # largefiles will be
889 # largefiles will be
883 add_largefiles(ui, repo, *pats, **opts)
890 add_largefiles(ui, repo, *pats, **opts)
884 # Now that we've handled largefiles, hand off to the original addremove
891 # Now that we've handled largefiles, hand off to the original addremove
885 # function to take care of the rest. Make sure it doesn't do anything with
892 # function to take care of the rest. Make sure it doesn't do anything with
886 # largefiles by installing a matcher that will ignore them.
893 # largefiles by installing a matcher that will ignore them.
887 installnormalfilesmatchfn(repo[None].manifest())
894 installnormalfilesmatchfn(repo[None].manifest())
888 result = orig(ui, repo, *pats, **opts)
895 result = orig(ui, repo, *pats, **opts)
889 restorematchfn()
896 restorematchfn()
890 return result
897 return result
891
898
892 # Calling purge with --all will cause the largefiles to be deleted.
899 # Calling purge with --all will cause the largefiles to be deleted.
893 # Override repo.status to prevent this from happening.
900 # Override repo.status to prevent this from happening.
894 def override_purge(orig, ui, repo, *dirs, **opts):
901 def override_purge(orig, ui, repo, *dirs, **opts):
895 oldstatus = repo.status
902 oldstatus = repo.status
896 def override_status(node1='.', node2=None, match=None, ignored=False,
903 def override_status(node1='.', node2=None, match=None, ignored=False,
897 clean=False, unknown=False, listsubrepos=False):
904 clean=False, unknown=False, listsubrepos=False):
898 r = oldstatus(node1, node2, match, ignored, clean, unknown,
905 r = oldstatus(node1, node2, match, ignored, clean, unknown,
899 listsubrepos)
906 listsubrepos)
900 lfdirstate = lfutil.openlfdirstate(ui, repo)
907 lfdirstate = lfutil.openlfdirstate(ui, repo)
901 modified, added, removed, deleted, unknown, ignored, clean = r
908 modified, added, removed, deleted, unknown, ignored, clean = r
902 unknown = [f for f in unknown if lfdirstate[f] == '?']
909 unknown = [f for f in unknown if lfdirstate[f] == '?']
903 ignored = [f for f in ignored if lfdirstate[f] == '?']
910 ignored = [f for f in ignored if lfdirstate[f] == '?']
904 return modified, added, removed, deleted, unknown, ignored, clean
911 return modified, added, removed, deleted, unknown, ignored, clean
905 repo.status = override_status
912 repo.status = override_status
906 orig(ui, repo, *dirs, **opts)
913 orig(ui, repo, *dirs, **opts)
907 repo.status = oldstatus
914 repo.status = oldstatus
908
915
909 def override_rollback(orig, ui, repo, **opts):
916 def override_rollback(orig, ui, repo, **opts):
910 result = orig(ui, repo, **opts)
917 result = orig(ui, repo, **opts)
911 merge.update(repo, node=None, branchmerge=False, force=True,
918 merge.update(repo, node=None, branchmerge=False, force=True,
912 partial=lfutil.isstandin)
919 partial=lfutil.isstandin)
913 wlock = repo.wlock()
920 wlock = repo.wlock()
914 try:
921 try:
915 lfdirstate = lfutil.openlfdirstate(ui, repo)
922 lfdirstate = lfutil.openlfdirstate(ui, repo)
916 lfiles = lfutil.listlfiles(repo)
923 lfiles = lfutil.listlfiles(repo)
917 oldlfiles = lfutil.listlfiles(repo, repo[None].parents()[0].rev())
924 oldlfiles = lfutil.listlfiles(repo, repo[None].parents()[0].rev())
918 for file in lfiles:
925 for file in lfiles:
919 if file in oldlfiles:
926 if file in oldlfiles:
920 lfdirstate.normallookup(file)
927 lfdirstate.normallookup(file)
921 else:
928 else:
922 lfdirstate.add(file)
929 lfdirstate.add(file)
923 lfdirstate.write()
930 lfdirstate.write()
924 finally:
931 finally:
925 wlock.release()
932 wlock.release()
926 return result
933 return result
927
934
928 def override_transplant(orig, ui, repo, *revs, **opts):
935 def override_transplant(orig, ui, repo, *revs, **opts):
929 result = orig(ui, repo, *revs, **opts)
936 result = orig(ui, repo, *revs, **opts)
930 lfcommands.updatelfiles(repo.ui, repo)
937 lfcommands.updatelfiles(repo.ui, repo)
931 return result
938 return result
General Comments 0
You need to be logged in to leave comments. Login now