15168
|
1 # Copyright 2009-2010 Gregory P. Ward
|
|
2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
|
|
3 # Copyright 2010-2011 Fog Creek Software
|
|
4 # Copyright 2010-2011 Unity Technologies
|
|
5 #
|
|
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.
|
|
8
|
|
9 '''Overridden Mercurial commands and functions for the largefiles extension'''
|
|
10
|
|
11 import os
|
|
12 import copy
|
|
13
|
|
14 from mercurial import hg, commands, util, cmdutil, match as match_, node, \
|
|
15 archival, error, merge
|
|
16 from mercurial.i18n import _
|
|
17 from mercurial.node import hex
|
|
18 from hgext import rebase
|
|
19
|
|
20 try:
|
|
21 from mercurial import scmutil
|
|
22 except ImportError:
|
|
23 pass
|
|
24
|
|
25 import lfutil
|
|
26 import lfcommands
|
|
27
|
|
28 def installnormalfilesmatchfn(manifest):
|
|
29 '''overrides scmutil.match so that the matcher it returns will ignore all
|
|
30 largefiles'''
|
|
31 oldmatch = None # for the closure
|
|
32 def override_match(repo, pats=[], opts={}, globbed=False,
|
|
33 default='relpath'):
|
|
34 match = oldmatch(repo, pats, opts, globbed, default)
|
|
35 m = copy.copy(match)
|
|
36 notlfile = lambda f: not (lfutil.isstandin(f) or lfutil.standin(f) in
|
|
37 manifest)
|
|
38 m._files = filter(notlfile, m._files)
|
|
39 m._fmap = set(m._files)
|
|
40 orig_matchfn = m.matchfn
|
|
41 m.matchfn = lambda f: notlfile(f) and orig_matchfn(f) or None
|
|
42 return m
|
|
43 oldmatch = installmatchfn(override_match)
|
|
44
|
|
45 def installmatchfn(f):
|
|
46 try:
|
|
47 # Mercurial >= 1.9
|
|
48 oldmatch = scmutil.match
|
|
49 except ImportError:
|
|
50 # Mercurial <= 1.8
|
|
51 oldmatch = cmdutil.match
|
|
52 setattr(f, 'oldmatch', oldmatch)
|
|
53 try:
|
|
54 # Mercurial >= 1.9
|
|
55 scmutil.match = f
|
|
56 except ImportError:
|
|
57 # Mercurial <= 1.8
|
|
58 cmdutil.match = f
|
|
59 return oldmatch
|
|
60
|
|
61 def restorematchfn():
|
|
62 '''restores scmutil.match to what it was before installnormalfilesmatchfn
|
|
63 was called. no-op if scmutil.match is its original function.
|
|
64
|
|
65 Note that n calls to installnormalfilesmatchfn will require n calls to
|
|
66 restore matchfn to reverse'''
|
|
67 try:
|
|
68 # Mercurial >= 1.9
|
|
69 scmutil.match = getattr(scmutil.match, 'oldmatch', scmutil.match)
|
|
70 except ImportError:
|
|
71 # Mercurial <= 1.8
|
|
72 cmdutil.match = getattr(cmdutil.match, 'oldmatch', cmdutil.match)
|
|
73
|
|
74 # -- Wrappers: modify existing commands --------------------------------
|
|
75
|
|
76 # Add works by going through the files that the user wanted to add
|
|
77 # and checking if they should be added as lfiles. Then making a new
|
|
78 # matcher which matches only the normal files and running the original
|
|
79 # version of add.
|
|
80 def override_add(orig, ui, repo, *pats, **opts):
|
|
81 large = opts.pop('large', None)
|
|
82
|
|
83 lfsize = opts.pop('lfsize', None)
|
|
84 if not lfsize and lfutil.islfilesrepo(repo):
|
|
85 lfsize = ui.config(lfutil.longname, 'size', default='10')
|
|
86 if lfsize:
|
|
87 try:
|
|
88 lfsize = int(lfsize)
|
|
89 except ValueError:
|
|
90 raise util.Abort(_('largefiles: size must be an integer, was %s\n') % lfsize)
|
|
91
|
|
92 lfmatcher = None
|
|
93 if os.path.exists(repo.wjoin(lfutil.shortname)):
|
|
94 lfpats = ui.config(lfutil.longname, 'patterns', default=())
|
|
95 if lfpats:
|
|
96 lfpats = lfpats.split(' ')
|
|
97 lfmatcher = match_.match(repo.root, '', list(lfpats))
|
|
98
|
|
99 lfnames = []
|
|
100 try:
|
|
101 # Mercurial >= 1.9
|
|
102 m = scmutil.match(repo[None], pats, opts)
|
|
103 except ImportError:
|
|
104 # Mercurial <= 1.8
|
|
105 m = cmdutil.match(repo, pats, opts)
|
|
106 m.bad = lambda x, y: None
|
|
107 wctx = repo[None]
|
|
108 for f in repo.walk(m):
|
|
109 exact = m.exact(f)
|
|
110 lfile = lfutil.standin(f) in wctx
|
|
111 nfile = f in wctx
|
|
112 exists = lfile or nfile
|
|
113
|
|
114 # Don't warn the user when they attempt to add a normal tracked file.
|
|
115 # The normal add code will do that for us.
|
|
116 if exact and exists:
|
|
117 if lfile:
|
|
118 ui.warn(_('%s already a largefile\n') % f)
|
|
119 continue
|
|
120
|
|
121 if exact or not exists:
|
|
122 if large or (lfsize and os.path.getsize(repo.wjoin(f)) >= \
|
|
123 lfsize * 1024 * 1024) or (lfmatcher and lfmatcher(f)):
|
|
124 lfnames.append(f)
|
|
125 if ui.verbose or not exact:
|
|
126 ui.status(_('adding %s as a largefile\n') % m.rel(f))
|
|
127
|
|
128 bad = []
|
|
129 standins = []
|
|
130
|
|
131 # Need to lock otherwise there could be a race condition inbetween when
|
|
132 # standins are created and added to the repo
|
|
133 wlock = repo.wlock()
|
|
134 try:
|
|
135 if not opts.get('dry_run'):
|
|
136 lfdirstate = lfutil.openlfdirstate(ui, repo)
|
|
137 for f in lfnames:
|
|
138 standinname = lfutil.standin(f)
|
|
139 lfutil.writestandin(repo, standinname, hash='',
|
|
140 executable=lfutil.getexecutable(repo.wjoin(f)))
|
|
141 standins.append(standinname)
|
|
142 if lfdirstate[f] == 'r':
|
|
143 lfdirstate.normallookup(f)
|
|
144 else:
|
|
145 lfdirstate.add(f)
|
|
146 lfdirstate.write()
|
|
147 bad += [lfutil.splitstandin(f) for f in lfutil.repo_add(repo,
|
|
148 standins) if f in m.files()]
|
|
149 finally:
|
|
150 wlock.release()
|
|
151
|
|
152 installnormalfilesmatchfn(repo[None].manifest())
|
|
153 result = orig(ui, repo, *pats, **opts)
|
|
154 restorematchfn()
|
|
155
|
|
156 return (result == 1 or bad) and 1 or 0
|
|
157
|
|
158 def override_remove(orig, ui, repo, *pats, **opts):
|
|
159 manifest = repo[None].manifest()
|
|
160 installnormalfilesmatchfn(manifest)
|
|
161 orig(ui, repo, *pats, **opts)
|
|
162 restorematchfn()
|
|
163
|
|
164 after, force = opts.get('after'), opts.get('force')
|
|
165 if not pats and not after:
|
|
166 raise util.Abort(_('no files specified'))
|
|
167 try:
|
|
168 # Mercurial >= 1.9
|
|
169 m = scmutil.match(repo[None], pats, opts)
|
|
170 except ImportError:
|
|
171 # Mercurial <= 1.8
|
|
172 m = cmdutil.match(repo, pats, opts)
|
|
173 try:
|
|
174 repo.lfstatus = True
|
|
175 s = repo.status(match=m, clean=True)
|
|
176 finally:
|
|
177 repo.lfstatus = False
|
|
178 modified, added, deleted, clean = [[f for f in list if lfutil.standin(f) \
|
|
179 in manifest] for list in [s[0], s[1], s[3], s[6]]]
|
|
180
|
|
181 def warn(files, reason):
|
|
182 for f in files:
|
|
183 ui.warn(_('not removing %s: file %s (use -f to force removal)\n')
|
|
184 % (m.rel(f), reason))
|
|
185
|
|
186 if force:
|
|
187 remove, forget = modified + deleted + clean, added
|
|
188 elif after:
|
|
189 remove, forget = deleted, []
|
|
190 warn(modified + added + clean, _('still exists'))
|
|
191 else:
|
|
192 remove, forget = deleted + clean, []
|
|
193 warn(modified, _('is modified'))
|
|
194 warn(added, _('has been marked for add'))
|
|
195
|
|
196 for f in sorted(remove + forget):
|
|
197 if ui.verbose or not m.exact(f):
|
|
198 ui.status(_('removing %s\n') % m.rel(f))
|
|
199
|
|
200 # Need to lock because standin files are deleted then removed from the
|
|
201 # repository and we could race inbetween.
|
|
202 wlock = repo.wlock()
|
|
203 try:
|
|
204 lfdirstate = lfutil.openlfdirstate(ui, repo)
|
|
205 for f in remove:
|
|
206 if not after:
|
|
207 os.unlink(repo.wjoin(f))
|
|
208 currentdir = os.path.split(f)[0]
|
|
209 while currentdir and not os.listdir(repo.wjoin(currentdir)):
|
|
210 os.rmdir(repo.wjoin(currentdir))
|
|
211 currentdir = os.path.split(currentdir)[0]
|
|
212 lfdirstate.remove(f)
|
|
213 lfdirstate.write()
|
|
214
|
|
215 forget = [lfutil.standin(f) for f in forget]
|
|
216 remove = [lfutil.standin(f) for f in remove]
|
|
217 lfutil.repo_forget(repo, forget)
|
|
218 lfutil.repo_remove(repo, remove, unlink=True)
|
|
219 finally:
|
|
220 wlock.release()
|
|
221
|
|
222 def override_status(orig, ui, repo, *pats, **opts):
|
|
223 try:
|
|
224 repo.lfstatus = True
|
|
225 return orig(ui, repo, *pats, **opts)
|
|
226 finally:
|
|
227 repo.lfstatus = False
|
|
228
|
|
229 def override_log(orig, ui, repo, *pats, **opts):
|
|
230 try:
|
|
231 repo.lfstatus = True
|
|
232 orig(ui, repo, *pats, **opts)
|
|
233 finally:
|
|
234 repo.lfstatus = False
|
|
235
|
|
236 def override_verify(orig, ui, repo, *pats, **opts):
|
|
237 large = opts.pop('large', False)
|
|
238 all = opts.pop('lfa', False)
|
|
239 contents = opts.pop('lfc', False)
|
|
240
|
|
241 result = orig(ui, repo, *pats, **opts)
|
|
242 if large:
|
|
243 result = result or lfcommands.verifylfiles(ui, repo, all, contents)
|
|
244 return result
|
|
245
|
|
246 # Override needs to refresh standins so that update's normal merge
|
|
247 # will go through properly. Then the other update hook (overriding repo.update)
|
|
248 # will get the new files. Filemerge is also overriden so that the merge
|
|
249 # will merge standins correctly.
|
|
250 def override_update(orig, ui, repo, *pats, **opts):
|
|
251 lfdirstate = lfutil.openlfdirstate(ui, repo)
|
|
252 s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [], False,
|
|
253 False, False)
|
|
254 (unsure, modified, added, removed, missing, unknown, ignored, clean) = s
|
|
255
|
|
256 # Need to lock between the standins getting updated and their lfiles
|
|
257 # getting updated
|
|
258 wlock = repo.wlock()
|
|
259 try:
|
|
260 if opts['check']:
|
|
261 mod = len(modified) > 0
|
|
262 for lfile in unsure:
|
|
263 standin = lfutil.standin(lfile)
|
|
264 if repo['.'][standin].data().strip() != \
|
|
265 lfutil.hashfile(repo.wjoin(lfile)):
|
|
266 mod = True
|
|
267 else:
|
|
268 lfdirstate.normal(lfile)
|
|
269 lfdirstate.write()
|
|
270 if mod:
|
|
271 raise util.Abort(_('uncommitted local changes'))
|
|
272 # XXX handle removed differently
|
|
273 if not opts['clean']:
|
|
274 for lfile in unsure + modified + added:
|
|
275 lfutil.updatestandin(repo, lfutil.standin(lfile))
|
|
276 finally:
|
|
277 wlock.release()
|
|
278 return orig(ui, repo, *pats, **opts)
|
|
279
|
|
280 # Override filemerge to prompt the user about how they wish to merge lfiles.
|
|
281 # This will handle identical edits, and copy/rename + edit without prompting
|
|
282 # the user.
|
|
283 def override_filemerge(origfn, repo, mynode, orig, fcd, fco, fca):
|
|
284 # Use better variable names here. Because this is a wrapper we cannot
|
|
285 # change the variable names in the function declaration.
|
|
286 fcdest, fcother, fcancestor = fcd, fco, fca
|
|
287 if not lfutil.isstandin(orig):
|
|
288 return origfn(repo, mynode, orig, fcdest, fcother, fcancestor)
|
|
289 else:
|
|
290 if not fcother.cmp(fcdest): # files identical?
|
|
291 return None
|
|
292
|
|
293 # backwards, use working dir parent as ancestor
|
|
294 if fcancestor == fcother:
|
|
295 fcancestor = fcdest.parents()[0]
|
|
296
|
|
297 if orig != fcother.path():
|
|
298 repo.ui.status(_('merging %s and %s to %s\n')
|
|
299 % (lfutil.splitstandin(orig),
|
|
300 lfutil.splitstandin(fcother.path()),
|
|
301 lfutil.splitstandin(fcdest.path())))
|
|
302 else:
|
|
303 repo.ui.status(_('merging %s\n')
|
|
304 % lfutil.splitstandin(fcdest.path()))
|
|
305
|
|
306 if fcancestor.path() != fcother.path() and fcother.data() == \
|
|
307 fcancestor.data():
|
|
308 return 0
|
|
309 if fcancestor.path() != fcdest.path() and fcdest.data() == \
|
|
310 fcancestor.data():
|
|
311 repo.wwrite(fcdest.path(), fcother.data(), fcother.flags())
|
|
312 return 0
|
|
313
|
|
314 if repo.ui.promptchoice(_('largefile %s has a merge conflict\n'
|
|
315 'keep (l)ocal or take (o)ther?') %
|
|
316 lfutil.splitstandin(orig),
|
|
317 (_('&Local'), _('&Other')), 0) == 0:
|
|
318 return 0
|
|
319 else:
|
|
320 repo.wwrite(fcdest.path(), fcother.data(), fcother.flags())
|
|
321 return 0
|
|
322
|
|
323 # Copy first changes the matchers to match standins instead of lfiles.
|
|
324 # Then it overrides util.copyfile in that function it checks if the destination
|
|
325 # lfile already exists. It also keeps a list of copied files so that the lfiles
|
|
326 # can be copied and the dirstate updated.
|
|
327 def override_copy(orig, ui, repo, pats, opts, rename=False):
|
|
328 # doesn't remove lfile on rename
|
|
329 if len(pats) < 2:
|
|
330 # this isn't legal, let the original function deal with it
|
|
331 return orig(ui, repo, pats, opts, rename)
|
|
332
|
|
333 def makestandin(relpath):
|
|
334 try:
|
|
335 # Mercurial >= 1.9
|
|
336 path = scmutil.canonpath(repo.root, repo.getcwd(), relpath)
|
|
337 except ImportError:
|
|
338 # Mercurial <= 1.8
|
|
339 path = util.canonpath(repo.root, repo.getcwd(), relpath)
|
|
340 return os.path.join(os.path.relpath('.', repo.getcwd()),
|
|
341 lfutil.standin(path))
|
|
342
|
|
343 try:
|
|
344 # Mercurial >= 1.9
|
|
345 fullpats = scmutil.expandpats(pats)
|
|
346 except ImportError:
|
|
347 # Mercurial <= 1.8
|
|
348 fullpats = cmdutil.expandpats(pats)
|
|
349 dest = fullpats[-1]
|
|
350
|
|
351 if os.path.isdir(dest):
|
|
352 if not os.path.isdir(makestandin(dest)):
|
|
353 os.makedirs(makestandin(dest))
|
|
354 # This could copy both lfiles and normal files in one command, but we don't
|
|
355 # want to do that first replace their matcher to only match normal files
|
|
356 # and run it then replace it to just match lfiles and run it again
|
|
357 nonormalfiles = False
|
|
358 nolfiles = False
|
|
359 try:
|
|
360 installnormalfilesmatchfn(repo[None].manifest())
|
|
361 result = orig(ui, repo, pats, opts, rename)
|
|
362 except util.Abort, e:
|
|
363 if str(e) != 'no files to copy':
|
|
364 raise e
|
|
365 else:
|
|
366 nonormalfiles = True
|
|
367 result = 0
|
|
368 finally:
|
|
369 restorematchfn()
|
|
370
|
|
371 # The first rename can cause our current working directory to be removed.
|
|
372 # In that case there is nothing left to copy/rename so just quit.
|
|
373 try:
|
|
374 repo.getcwd()
|
|
375 except OSError:
|
|
376 return result
|
|
377
|
|
378 try:
|
|
379 # When we call orig below it creates the standins but we don't add them
|
|
380 # to the dir state until later so lock during that time.
|
|
381 wlock = repo.wlock()
|
|
382
|
|
383 manifest = repo[None].manifest()
|
|
384 oldmatch = None # for the closure
|
|
385 def override_match(repo, pats=[], opts={}, globbed=False,
|
|
386 default='relpath'):
|
|
387 newpats = []
|
|
388 # The patterns were previously mangled to add the standin
|
|
389 # directory; we need to remove that now
|
|
390 for pat in pats:
|
|
391 if match_.patkind(pat) is None and lfutil.shortname in pat:
|
|
392 newpats.append(pat.replace(lfutil.shortname, ''))
|
|
393 else:
|
|
394 newpats.append(pat)
|
|
395 match = oldmatch(repo, newpats, opts, globbed, default)
|
|
396 m = copy.copy(match)
|
|
397 lfile = lambda f: lfutil.standin(f) in manifest
|
|
398 m._files = [lfutil.standin(f) for f in m._files if lfile(f)]
|
|
399 m._fmap = set(m._files)
|
|
400 orig_matchfn = m.matchfn
|
|
401 m.matchfn = lambda f: lfutil.isstandin(f) and \
|
|
402 lfile(lfutil.splitstandin(f)) and \
|
|
403 orig_matchfn(lfutil.splitstandin(f)) or None
|
|
404 return m
|
|
405 oldmatch = installmatchfn(override_match)
|
|
406 listpats = []
|
|
407 for pat in pats:
|
|
408 if match_.patkind(pat) is not None:
|
|
409 listpats.append(pat)
|
|
410 else:
|
|
411 listpats.append(makestandin(pat))
|
|
412
|
|
413 try:
|
|
414 origcopyfile = util.copyfile
|
|
415 copiedfiles = []
|
|
416 def override_copyfile(src, dest):
|
|
417 if lfutil.shortname in src and lfutil.shortname in dest:
|
|
418 destlfile = dest.replace(lfutil.shortname, '')
|
|
419 if not opts['force'] and os.path.exists(destlfile):
|
|
420 raise IOError('',
|
|
421 _('destination largefile already exists'))
|
|
422 copiedfiles.append((src, dest))
|
|
423 origcopyfile(src, dest)
|
|
424
|
|
425 util.copyfile = override_copyfile
|
|
426 result += orig(ui, repo, listpats, opts, rename)
|
|
427 finally:
|
|
428 util.copyfile = origcopyfile
|
|
429
|
|
430 lfdirstate = lfutil.openlfdirstate(ui, repo)
|
|
431 for (src, dest) in copiedfiles:
|
|
432 if lfutil.shortname in src and lfutil.shortname in dest:
|
|
433 srclfile = src.replace(lfutil.shortname, '')
|
|
434 destlfile = dest.replace(lfutil.shortname, '')
|
|
435 destlfiledir = os.path.dirname(destlfile) or '.'
|
|
436 if not os.path.isdir(destlfiledir):
|
|
437 os.makedirs(destlfiledir)
|
|
438 if rename:
|
|
439 os.rename(srclfile, destlfile)
|
|
440 lfdirstate.remove(os.path.relpath(srclfile,
|
|
441 repo.root))
|
|
442 else:
|
|
443 util.copyfile(srclfile, destlfile)
|
|
444 lfdirstate.add(os.path.relpath(destlfile,
|
|
445 repo.root))
|
|
446 lfdirstate.write()
|
|
447 except util.Abort, e:
|
|
448 if str(e) != 'no files to copy':
|
|
449 raise e
|
|
450 else:
|
|
451 nolfiles = True
|
|
452 finally:
|
|
453 restorematchfn()
|
|
454 wlock.release()
|
|
455
|
|
456 if nolfiles and nonormalfiles:
|
|
457 raise util.Abort(_('no files to copy'))
|
|
458
|
|
459 return result
|
|
460
|
|
461 # When the user calls revert, we have to be careful to not revert any changes
|
|
462 # to other lfiles accidentally. This means we have to keep track of the lfiles
|
|
463 # that are being reverted so we only pull down the necessary lfiles.
|
|
464 #
|
|
465 # Standins are only updated (to match the hash of lfiles) before commits.
|
|
466 # Update the standins then run the original revert (changing the matcher to hit
|
|
467 # standins instead of lfiles). Based on the resulting standins update the
|
|
468 # lfiles. Then return the standins to their proper state
|
|
469 def override_revert(orig, ui, repo, *pats, **opts):
|
|
470 # Because we put the standins in a bad state (by updating them) and then
|
|
471 # return them to a correct state we need to lock to prevent others from
|
|
472 # changing them in their incorrect state.
|
|
473 wlock = repo.wlock()
|
|
474 try:
|
|
475 lfdirstate = lfutil.openlfdirstate(ui, repo)
|
|
476 (modified, added, removed, missing, unknown, ignored, clean) = \
|
|
477 lfutil.lfdirstate_status(lfdirstate, repo, repo['.'].rev())
|
|
478 for lfile in modified:
|
|
479 lfutil.updatestandin(repo, lfutil.standin(lfile))
|
|
480
|
|
481 try:
|
|
482 ctx = repo[opts.get('rev')]
|
|
483 oldmatch = None # for the closure
|
|
484 def override_match(ctxorrepo, pats=[], opts={}, globbed=False,
|
|
485 default='relpath'):
|
|
486 if hasattr(ctxorrepo, 'match'):
|
|
487 ctx0 = ctxorrepo
|
|
488 else:
|
|
489 ctx0 = ctxorrepo[None]
|
|
490 match = oldmatch(ctxorrepo, pats, opts, globbed, default)
|
|
491 m = copy.copy(match)
|
|
492 def tostandin(f):
|
|
493 if lfutil.standin(f) in ctx0 or lfutil.standin(f) in ctx:
|
|
494 return lfutil.standin(f)
|
|
495 elif lfutil.standin(f) in repo[None]:
|
|
496 return None
|
|
497 return f
|
|
498 m._files = [tostandin(f) for f in m._files]
|
|
499 m._files = [f for f in m._files if f is not None]
|
|
500 m._fmap = set(m._files)
|
|
501 orig_matchfn = m.matchfn
|
|
502 def matchfn(f):
|
|
503 if lfutil.isstandin(f):
|
|
504 # We need to keep track of what lfiles are being
|
|
505 # matched so we know which ones to update later
|
|
506 # (otherwise we revert changes to other lfiles
|
|
507 # accidentally). This is repo specific, so duckpunch
|
|
508 # the repo object to keep the list of lfiles for us
|
|
509 # later.
|
|
510 if orig_matchfn(lfutil.splitstandin(f)) and \
|
|
511 (f in repo[None] or f in ctx):
|
|
512 lfileslist = getattr(repo, '_lfilestoupdate', [])
|
|
513 lfileslist.append(lfutil.splitstandin(f))
|
|
514 repo._lfilestoupdate = lfileslist
|
|
515 return True
|
|
516 else:
|
|
517 return False
|
|
518 return orig_matchfn(f)
|
|
519 m.matchfn = matchfn
|
|
520 return m
|
|
521 oldmatch = installmatchfn(override_match)
|
|
522 try:
|
|
523 # Mercurial >= 1.9
|
|
524 scmutil.match
|
|
525 matches = override_match(repo[None], pats, opts)
|
|
526 except ImportError:
|
|
527 # Mercurial <= 1.8
|
|
528 matches = override_match(repo, pats, opts)
|
|
529 orig(ui, repo, *pats, **opts)
|
|
530 finally:
|
|
531 restorematchfn()
|
|
532 lfileslist = getattr(repo, '_lfilestoupdate', [])
|
|
533 lfcommands.updatelfiles(ui, repo, filelist=lfileslist, printmessage=False)
|
|
534 # Empty out the lfiles list so we start fresh next time
|
|
535 repo._lfilestoupdate = []
|
|
536 for lfile in modified:
|
|
537 if lfile in lfileslist:
|
|
538 if os.path.exists(repo.wjoin(lfutil.standin(lfile))) and lfile\
|
|
539 in repo['.']:
|
|
540 lfutil.writestandin(repo, lfutil.standin(lfile),
|
|
541 repo['.'][lfile].data().strip(),
|
|
542 'x' in repo['.'][lfile].flags())
|
|
543 lfdirstate = lfutil.openlfdirstate(ui, repo)
|
|
544 for lfile in added:
|
|
545 standin = lfutil.standin(lfile)
|
|
546 if standin not in ctx and (standin in matches or opts.get('all')):
|
|
547 if lfile in lfdirstate:
|
|
548 try:
|
|
549 # Mercurial >= 1.9
|
|
550 lfdirstate.drop(lfile)
|
|
551 except AttributeError:
|
|
552 # Mercurial <= 1.8
|
|
553 lfdirstate.forget(lfile)
|
|
554 util.unlinkpath(repo.wjoin(standin))
|
|
555 lfdirstate.write()
|
|
556 finally:
|
|
557 wlock.release()
|
|
558
|
|
559 def hg_update(orig, repo, node):
|
|
560 result = orig(repo, node)
|
|
561 # XXX check if it worked first
|
|
562 lfcommands.updatelfiles(repo.ui, repo)
|
|
563 return result
|
|
564
|
|
565 def hg_clean(orig, repo, node, show_stats=True):
|
|
566 result = orig(repo, node, show_stats)
|
|
567 lfcommands.updatelfiles(repo.ui, repo)
|
|
568 return result
|
|
569
|
|
570 def hg_merge(orig, repo, node, force=None, remind=True):
|
|
571 result = orig(repo, node, force, remind)
|
|
572 lfcommands.updatelfiles(repo.ui, repo)
|
|
573 return result
|
|
574
|
|
575 # When we rebase a repository with remotely changed lfiles, we need
|
|
576 # to take some extra care so that the lfiles are correctly updated
|
|
577 # in the working copy
|
|
578 def override_pull(orig, ui, repo, source=None, **opts):
|
|
579 if opts.get('rebase', False):
|
|
580 repo._isrebasing = True
|
|
581 try:
|
|
582 if opts.get('update'):
|
|
583 del opts['update']
|
|
584 ui.debug('--update and --rebase are not compatible, ignoring '
|
|
585 'the update flag\n')
|
|
586 del opts['rebase']
|
|
587 try:
|
|
588 # Mercurial >= 1.9
|
|
589 cmdutil.bailifchanged(repo)
|
|
590 except AttributeError:
|
|
591 # Mercurial <= 1.8
|
|
592 cmdutil.bail_if_changed(repo)
|
|
593 revsprepull = len(repo)
|
|
594 origpostincoming = commands.postincoming
|
|
595 def _dummy(*args, **kwargs):
|
|
596 pass
|
|
597 commands.postincoming = _dummy
|
|
598 repo.lfpullsource = source
|
|
599 if not source:
|
|
600 source = 'default'
|
|
601 try:
|
|
602 result = commands.pull(ui, repo, source, **opts)
|
|
603 finally:
|
|
604 commands.postincoming = origpostincoming
|
|
605 revspostpull = len(repo)
|
|
606 if revspostpull > revsprepull:
|
|
607 result = result or rebase.rebase(ui, repo)
|
|
608 finally:
|
|
609 repo._isrebasing = False
|
|
610 else:
|
|
611 repo.lfpullsource = source
|
|
612 if not source:
|
|
613 source = 'default'
|
|
614 result = orig(ui, repo, source, **opts)
|
|
615 return result
|
|
616
|
|
617 def override_rebase(orig, ui, repo, **opts):
|
|
618 repo._isrebasing = True
|
|
619 try:
|
|
620 orig(ui, repo, **opts)
|
|
621 finally:
|
|
622 repo._isrebasing = False
|
|
623
|
|
624 def override_archive(orig, repo, dest, node, kind, decode=True, matchfn=None,
|
|
625 prefix=None, mtime=None, subrepos=None):
|
|
626 # No need to lock because we are only reading history and lfile caches
|
|
627 # neither of which are modified
|
|
628
|
|
629 lfcommands.cachelfiles(repo.ui, repo, node)
|
|
630
|
|
631 if kind not in archival.archivers:
|
|
632 raise util.Abort(_("unknown archive type '%s'") % kind)
|
|
633
|
|
634 ctx = repo[node]
|
|
635
|
|
636 # In Mercurial <= 1.5 the prefix is passed to the archiver so try that
|
|
637 # if that doesn't work we are probably in Mercurial >= 1.6 where the
|
|
638 # prefix is not handled by the archiver
|
|
639 try:
|
|
640 archiver = archival.archivers[kind](dest, prefix, mtime or \
|
|
641 ctx.date()[0])
|
|
642
|
|
643 def write(name, mode, islink, getdata):
|
|
644 if matchfn and not matchfn(name):
|
|
645 return
|
|
646 data = getdata()
|
|
647 if decode:
|
|
648 data = repo.wwritedata(name, data)
|
|
649 archiver.addfile(name, mode, islink, data)
|
|
650 except TypeError:
|
|
651 if kind == 'files':
|
|
652 if prefix:
|
|
653 raise util.Abort(
|
|
654 _('cannot give prefix when archiving to files'))
|
|
655 else:
|
|
656 prefix = archival.tidyprefix(dest, kind, prefix)
|
|
657
|
|
658 def write(name, mode, islink, getdata):
|
|
659 if matchfn and not matchfn(name):
|
|
660 return
|
|
661 data = getdata()
|
|
662 if decode:
|
|
663 data = repo.wwritedata(name, data)
|
|
664 archiver.addfile(prefix + name, mode, islink, data)
|
|
665
|
|
666 archiver = archival.archivers[kind](dest, mtime or ctx.date()[0])
|
|
667
|
|
668 if repo.ui.configbool("ui", "archivemeta", True):
|
|
669 def metadata():
|
|
670 base = 'repo: %s\nnode: %s\nbranch: %s\n' % (
|
|
671 hex(repo.changelog.node(0)), hex(node), ctx.branch())
|
|
672
|
|
673 tags = ''.join('tag: %s\n' % t for t in ctx.tags()
|
|
674 if repo.tagtype(t) == 'global')
|
|
675 if not tags:
|
|
676 repo.ui.pushbuffer()
|
|
677 opts = {'template': '{latesttag}\n{latesttagdistance}',
|
|
678 'style': '', 'patch': None, 'git': None}
|
|
679 cmdutil.show_changeset(repo.ui, repo, opts).show(ctx)
|
|
680 ltags, dist = repo.ui.popbuffer().split('\n')
|
|
681 tags = ''.join('latesttag: %s\n' % t for t in ltags.split(':'))
|
|
682 tags += 'latesttagdistance: %s\n' % dist
|
|
683
|
|
684 return base + tags
|
|
685
|
|
686 write('.hg_archival.txt', 0644, False, metadata)
|
|
687
|
|
688 for f in ctx:
|
|
689 ff = ctx.flags(f)
|
|
690 getdata = ctx[f].data
|
|
691 if lfutil.isstandin(f):
|
|
692 path = lfutil.findfile(repo, getdata().strip())
|
|
693 f = lfutil.splitstandin(f)
|
|
694
|
|
695 def getdatafn():
|
|
696 try:
|
|
697 fd = open(path, 'rb')
|
|
698 return fd.read()
|
|
699 finally:
|
|
700 fd.close()
|
|
701
|
|
702 getdata = getdatafn
|
|
703 write(f, 'x' in ff and 0755 or 0644, 'l' in ff, getdata)
|
|
704
|
|
705 if subrepos:
|
|
706 for subpath in ctx.substate:
|
|
707 sub = ctx.sub(subpath)
|
|
708 try:
|
|
709 sub.archive(repo.ui, archiver, prefix)
|
|
710 except TypeError:
|
|
711 sub.archive(archiver, prefix)
|
|
712
|
|
713 archiver.done()
|
|
714
|
|
715 # If a lfile is modified the change is not reflected in its standin until a
|
|
716 # commit. cmdutil.bailifchanged raises an exception if the repo has
|
|
717 # uncommitted changes. Wrap it to also check if lfiles were changed. This is
|
|
718 # used by bisect and backout.
|
|
719 def override_bailifchanged(orig, repo):
|
|
720 orig(repo)
|
|
721 repo.lfstatus = True
|
|
722 modified, added, removed, deleted = repo.status()[:4]
|
|
723 repo.lfstatus = False
|
|
724 if modified or added or removed or deleted:
|
|
725 raise util.Abort(_('outstanding uncommitted changes'))
|
|
726
|
|
727 # Fetch doesn't use cmdutil.bail_if_changed so override it to add the check
|
|
728 def override_fetch(orig, ui, repo, *pats, **opts):
|
|
729 repo.lfstatus = True
|
|
730 modified, added, removed, deleted = repo.status()[:4]
|
|
731 repo.lfstatus = False
|
|
732 if modified or added or removed or deleted:
|
|
733 raise util.Abort(_('outstanding uncommitted changes'))
|
|
734 return orig(ui, repo, *pats, **opts)
|
|
735
|
|
736 def override_forget(orig, ui, repo, *pats, **opts):
|
|
737 installnormalfilesmatchfn(repo[None].manifest())
|
|
738 orig(ui, repo, *pats, **opts)
|
|
739 restorematchfn()
|
|
740 try:
|
|
741 # Mercurial >= 1.9
|
|
742 m = scmutil.match(repo[None], pats, opts)
|
|
743 except ImportError:
|
|
744 # Mercurial <= 1.8
|
|
745 m = cmdutil.match(repo, pats, opts)
|
|
746
|
|
747 try:
|
|
748 repo.lfstatus = True
|
|
749 s = repo.status(match=m, clean=True)
|
|
750 finally:
|
|
751 repo.lfstatus = False
|
|
752 forget = sorted(s[0] + s[1] + s[3] + s[6])
|
|
753 forget = [f for f in forget if lfutil.standin(f) in repo[None].manifest()]
|
|
754
|
|
755 for f in forget:
|
|
756 if lfutil.standin(f) not in repo.dirstate and not \
|
|
757 os.path.isdir(m.rel(lfutil.standin(f))):
|
|
758 ui.warn(_('not removing %s: file is already untracked\n')
|
|
759 % m.rel(f))
|
|
760
|
|
761 for f in forget:
|
|
762 if ui.verbose or not m.exact(f):
|
|
763 ui.status(_('removing %s\n') % m.rel(f))
|
|
764
|
|
765 # Need to lock because standin files are deleted then removed from the
|
|
766 # repository and we could race inbetween.
|
|
767 wlock = repo.wlock()
|
|
768 try:
|
|
769 lfdirstate = lfutil.openlfdirstate(ui, repo)
|
|
770 for f in forget:
|
|
771 if lfdirstate[f] == 'a':
|
|
772 lfdirstate.drop(f)
|
|
773 else:
|
|
774 lfdirstate.remove(f)
|
|
775 lfdirstate.write()
|
|
776 lfutil.repo_remove(repo, [lfutil.standin(f) for f in forget],
|
|
777 unlink=True)
|
|
778 finally:
|
|
779 wlock.release()
|
|
780
|
|
781 def getoutgoinglfiles(ui, repo, dest=None, **opts):
|
|
782 dest = ui.expandpath(dest or 'default-push', dest or 'default')
|
|
783 dest, branches = hg.parseurl(dest, opts.get('branch'))
|
|
784 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
|
|
785 if revs:
|
|
786 revs = [repo.lookup(rev) for rev in revs]
|
|
787
|
|
788 # Mercurial <= 1.5 had remoteui in cmdutil, then it moved to hg
|
|
789 try:
|
|
790 remoteui = cmdutil.remoteui
|
|
791 except AttributeError:
|
|
792 remoteui = hg.remoteui
|
|
793
|
|
794 try:
|
|
795 remote = hg.repository(remoteui(repo, opts), dest)
|
|
796 except error.RepoError:
|
|
797 return None
|
|
798 o = lfutil.findoutgoing(repo, remote, False)
|
|
799 if not o:
|
|
800 return None
|
|
801 o = repo.changelog.nodesbetween(o, revs)[0]
|
|
802 if opts.get('newest_first'):
|
|
803 o.reverse()
|
|
804
|
|
805 toupload = set()
|
|
806 for n in o:
|
|
807 parents = [p for p in repo.changelog.parents(n) if p != node.nullid]
|
|
808 ctx = repo[n]
|
|
809 files = set(ctx.files())
|
|
810 if len(parents) == 2:
|
|
811 mc = ctx.manifest()
|
|
812 mp1 = ctx.parents()[0].manifest()
|
|
813 mp2 = ctx.parents()[1].manifest()
|
|
814 for f in mp1:
|
|
815 if f not in mc:
|
|
816 files.add(f)
|
|
817 for f in mp2:
|
|
818 if f not in mc:
|
|
819 files.add(f)
|
|
820 for f in mc:
|
|
821 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
|
|
822 files.add(f)
|
|
823 toupload = toupload.union(set([f for f in files if lfutil.isstandin(f)\
|
|
824 and f in ctx]))
|
|
825 return toupload
|
|
826
|
|
827 def override_outgoing(orig, ui, repo, dest=None, **opts):
|
|
828 orig(ui, repo, dest, **opts)
|
|
829
|
|
830 if opts.pop('large', None):
|
|
831 toupload = getoutgoinglfiles(ui, repo, dest, **opts)
|
|
832 if toupload is None:
|
|
833 ui.status(_('largefiles: No remote repo\n'))
|
|
834 else:
|
|
835 ui.status(_('largefiles to upload:\n'))
|
|
836 for file in toupload:
|
|
837 ui.status(lfutil.splitstandin(file) + '\n')
|
|
838 ui.status('\n')
|
|
839
|
|
840 def override_summary(orig, ui, repo, *pats, **opts):
|
|
841 orig(ui, repo, *pats, **opts)
|
|
842
|
|
843 if opts.pop('large', None):
|
|
844 toupload = getoutgoinglfiles(ui, repo, None, **opts)
|
|
845 if toupload is None:
|
|
846 ui.status(_('largefiles: No remote repo\n'))
|
|
847 else:
|
|
848 ui.status(_('largefiles: %d to upload\n') % len(toupload))
|
|
849
|
|
850 def override_addremove(orig, ui, repo, *pats, **opts):
|
|
851 # Check if the parent or child has lfiles if they do don't allow it. If
|
|
852 # there is a symlink in the manifest then getting the manifest throws an
|
|
853 # exception catch it and let addremove deal with it. This happens in
|
|
854 # Mercurial's test test-addremove-symlink
|
|
855 try:
|
|
856 manifesttip = set(repo['tip'].manifest())
|
|
857 except util.Abort:
|
|
858 manifesttip = set()
|
|
859 try:
|
|
860 manifestworking = set(repo[None].manifest())
|
|
861 except util.Abort:
|
|
862 manifestworking = set()
|
|
863
|
|
864 # Manifests are only iterable so turn them into sets then union
|
|
865 for file in manifesttip.union(manifestworking):
|
|
866 if file.startswith(lfutil.shortname):
|
|
867 raise util.Abort(
|
|
868 _('addremove cannot be run on a repo with largefiles'))
|
|
869
|
|
870 return orig(ui, repo, *pats, **opts)
|
|
871
|
|
872 # Calling purge with --all will cause the lfiles to be deleted.
|
|
873 # Override repo.status to prevent this from happening.
|
|
874 def override_purge(orig, ui, repo, *dirs, **opts):
|
|
875 oldstatus = repo.status
|
|
876 def override_status(node1='.', node2=None, match=None, ignored=False,
|
|
877 clean=False, unknown=False, listsubrepos=False):
|
|
878 r = oldstatus(node1, node2, match, ignored, clean, unknown,
|
|
879 listsubrepos)
|
|
880 lfdirstate = lfutil.openlfdirstate(ui, repo)
|
|
881 modified, added, removed, deleted, unknown, ignored, clean = r
|
|
882 unknown = [f for f in unknown if lfdirstate[f] == '?']
|
|
883 ignored = [f for f in ignored if lfdirstate[f] == '?']
|
|
884 return modified, added, removed, deleted, unknown, ignored, clean
|
|
885 repo.status = override_status
|
|
886 orig(ui, repo, *dirs, **opts)
|
|
887 repo.status = oldstatus
|
|
888
|
|
889 def override_rollback(orig, ui, repo, **opts):
|
|
890 result = orig(ui, repo, **opts)
|
|
891 merge.update(repo, node=None, branchmerge=False, force=True,
|
|
892 partial=lfutil.isstandin)
|
|
893 lfdirstate = lfutil.openlfdirstate(ui, repo)
|
|
894 lfiles = lfutil.listlfiles(repo)
|
|
895 oldlfiles = lfutil.listlfiles(repo, repo[None].parents()[0].rev())
|
|
896 for file in lfiles:
|
|
897 if file in oldlfiles:
|
|
898 lfdirstate.normallookup(file)
|
|
899 else:
|
|
900 lfdirstate.add(file)
|
|
901 lfdirstate.write()
|
|
902 return result
|