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