##// END OF EJS Templates
largefiles: update standins only at the 1st commit of "hg rebase --continue"...
FUJIWARA Katsunori -
r23187:f726b05e default
parent child Browse files
Show More
@@ -1,539 +1,559
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 '''largefiles utility code: must not import other modules in this package.'''
10 10
11 11 import os
12 12 import platform
13 13 import shutil
14 14 import stat
15 15 import copy
16 16
17 17 from mercurial import dirstate, httpconnection, match as match_, util, scmutil
18 18 from mercurial.i18n import _
19 19 from mercurial import node
20 20
21 21 shortname = '.hglf'
22 22 shortnameslash = shortname + '/'
23 23 longname = 'largefiles'
24 24
25 25
26 26 # -- Private worker functions ------------------------------------------
27 27
28 28 def getminsize(ui, assumelfiles, opt, default=10):
29 29 lfsize = opt
30 30 if not lfsize and assumelfiles:
31 31 lfsize = ui.config(longname, 'minsize', default=default)
32 32 if lfsize:
33 33 try:
34 34 lfsize = float(lfsize)
35 35 except ValueError:
36 36 raise util.Abort(_('largefiles: size must be number (not %s)\n')
37 37 % lfsize)
38 38 if lfsize is None:
39 39 raise util.Abort(_('minimum size for largefiles must be specified'))
40 40 return lfsize
41 41
42 42 def link(src, dest):
43 43 util.makedirs(os.path.dirname(dest))
44 44 try:
45 45 util.oslink(src, dest)
46 46 except OSError:
47 47 # if hardlinks fail, fallback on atomic copy
48 48 dst = util.atomictempfile(dest)
49 49 for chunk in util.filechunkiter(open(src, 'rb')):
50 50 dst.write(chunk)
51 51 dst.close()
52 52 os.chmod(dest, os.stat(src).st_mode)
53 53
54 54 def usercachepath(ui, hash):
55 55 path = ui.configpath(longname, 'usercache', None)
56 56 if path:
57 57 path = os.path.join(path, hash)
58 58 else:
59 59 if os.name == 'nt':
60 60 appdata = os.getenv('LOCALAPPDATA', os.getenv('APPDATA'))
61 61 if appdata:
62 62 path = os.path.join(appdata, longname, hash)
63 63 elif platform.system() == 'Darwin':
64 64 home = os.getenv('HOME')
65 65 if home:
66 66 path = os.path.join(home, 'Library', 'Caches',
67 67 longname, hash)
68 68 elif os.name == 'posix':
69 69 path = os.getenv('XDG_CACHE_HOME')
70 70 if path:
71 71 path = os.path.join(path, longname, hash)
72 72 else:
73 73 home = os.getenv('HOME')
74 74 if home:
75 75 path = os.path.join(home, '.cache', longname, hash)
76 76 else:
77 77 raise util.Abort(_('unknown operating system: %s\n') % os.name)
78 78 return path
79 79
80 80 def inusercache(ui, hash):
81 81 path = usercachepath(ui, hash)
82 82 return path and os.path.exists(path)
83 83
84 84 def findfile(repo, hash):
85 85 if instore(repo, hash):
86 86 repo.ui.note(_('found %s in store\n') % hash)
87 87 return storepath(repo, hash)
88 88 elif inusercache(repo.ui, hash):
89 89 repo.ui.note(_('found %s in system cache\n') % hash)
90 90 path = storepath(repo, hash)
91 91 link(usercachepath(repo.ui, hash), path)
92 92 return path
93 93 return None
94 94
95 95 class largefilesdirstate(dirstate.dirstate):
96 96 def __getitem__(self, key):
97 97 return super(largefilesdirstate, self).__getitem__(unixpath(key))
98 98 def normal(self, f):
99 99 return super(largefilesdirstate, self).normal(unixpath(f))
100 100 def remove(self, f):
101 101 return super(largefilesdirstate, self).remove(unixpath(f))
102 102 def add(self, f):
103 103 return super(largefilesdirstate, self).add(unixpath(f))
104 104 def drop(self, f):
105 105 return super(largefilesdirstate, self).drop(unixpath(f))
106 106 def forget(self, f):
107 107 return super(largefilesdirstate, self).forget(unixpath(f))
108 108 def normallookup(self, f):
109 109 return super(largefilesdirstate, self).normallookup(unixpath(f))
110 110 def _ignore(self, f):
111 111 return False
112 112
113 113 def openlfdirstate(ui, repo, create=True):
114 114 '''
115 115 Return a dirstate object that tracks largefiles: i.e. its root is
116 116 the repo root, but it is saved in .hg/largefiles/dirstate.
117 117 '''
118 118 lfstoredir = repo.join(longname)
119 119 opener = scmutil.opener(lfstoredir)
120 120 lfdirstate = largefilesdirstate(opener, ui, repo.root,
121 121 repo.dirstate._validate)
122 122
123 123 # If the largefiles dirstate does not exist, populate and create
124 124 # it. This ensures that we create it on the first meaningful
125 125 # largefiles operation in a new clone.
126 126 if create and not os.path.exists(os.path.join(lfstoredir, 'dirstate')):
127 127 matcher = getstandinmatcher(repo)
128 128 standins = repo.dirstate.walk(matcher, [], False, False)
129 129
130 130 if len(standins) > 0:
131 131 util.makedirs(lfstoredir)
132 132
133 133 for standin in standins:
134 134 lfile = splitstandin(standin)
135 135 lfdirstate.normallookup(lfile)
136 136 return lfdirstate
137 137
138 138 def lfdirstatestatus(lfdirstate, repo):
139 139 wctx = repo['.']
140 140 match = match_.always(repo.root, repo.getcwd())
141 141 unsure, s = lfdirstate.status(match, [], False, False, False)
142 142 modified, clean = s.modified, s.clean
143 143 for lfile in unsure:
144 144 try:
145 145 fctx = wctx[standin(lfile)]
146 146 except LookupError:
147 147 fctx = None
148 148 if not fctx or fctx.data().strip() != hashfile(repo.wjoin(lfile)):
149 149 modified.append(lfile)
150 150 else:
151 151 clean.append(lfile)
152 152 lfdirstate.normal(lfile)
153 153 return s
154 154
155 155 def listlfiles(repo, rev=None, matcher=None):
156 156 '''return a list of largefiles in the working copy or the
157 157 specified changeset'''
158 158
159 159 if matcher is None:
160 160 matcher = getstandinmatcher(repo)
161 161
162 162 # ignore unknown files in working directory
163 163 return [splitstandin(f)
164 164 for f in repo[rev].walk(matcher)
165 165 if rev is not None or repo.dirstate[f] != '?']
166 166
167 167 def instore(repo, hash):
168 168 return os.path.exists(storepath(repo, hash))
169 169
170 170 def storepath(repo, hash):
171 171 return repo.join(os.path.join(longname, hash))
172 172
173 173 def copyfromcache(repo, hash, filename):
174 174 '''Copy the specified largefile from the repo or system cache to
175 175 filename in the repository. Return true on success or false if the
176 176 file was not found in either cache (which should not happened:
177 177 this is meant to be called only after ensuring that the needed
178 178 largefile exists in the cache).'''
179 179 path = findfile(repo, hash)
180 180 if path is None:
181 181 return False
182 182 util.makedirs(os.path.dirname(repo.wjoin(filename)))
183 183 # The write may fail before the file is fully written, but we
184 184 # don't use atomic writes in the working copy.
185 185 shutil.copy(path, repo.wjoin(filename))
186 186 return True
187 187
188 188 def copytostore(repo, rev, file, uploaded=False):
189 189 hash = readstandin(repo, file, rev)
190 190 if instore(repo, hash):
191 191 return
192 192 copytostoreabsolute(repo, repo.wjoin(file), hash)
193 193
194 194 def copyalltostore(repo, node):
195 195 '''Copy all largefiles in a given revision to the store'''
196 196
197 197 ctx = repo[node]
198 198 for filename in ctx.files():
199 199 if isstandin(filename) and filename in ctx.manifest():
200 200 realfile = splitstandin(filename)
201 201 copytostore(repo, ctx.node(), realfile)
202 202
203 203
204 204 def copytostoreabsolute(repo, file, hash):
205 205 if inusercache(repo.ui, hash):
206 206 link(usercachepath(repo.ui, hash), storepath(repo, hash))
207 207 elif not getattr(repo, "_isconverting", False):
208 208 util.makedirs(os.path.dirname(storepath(repo, hash)))
209 209 dst = util.atomictempfile(storepath(repo, hash),
210 210 createmode=repo.store.createmode)
211 211 for chunk in util.filechunkiter(open(file, 'rb')):
212 212 dst.write(chunk)
213 213 dst.close()
214 214 linktousercache(repo, hash)
215 215
216 216 def linktousercache(repo, hash):
217 217 path = usercachepath(repo.ui, hash)
218 218 if path:
219 219 link(storepath(repo, hash), path)
220 220
221 221 def getstandinmatcher(repo, pats=[], opts={}):
222 222 '''Return a match object that applies pats to the standin directory'''
223 223 standindir = repo.wjoin(shortname)
224 224 if pats:
225 225 pats = [os.path.join(standindir, pat) for pat in pats]
226 226 else:
227 227 # no patterns: relative to repo root
228 228 pats = [standindir]
229 229 # no warnings about missing files or directories
230 230 match = scmutil.match(repo[None], pats, opts)
231 231 match.bad = lambda f, msg: None
232 232 return match
233 233
234 234 def composestandinmatcher(repo, rmatcher):
235 235 '''Return a matcher that accepts standins corresponding to the
236 236 files accepted by rmatcher. Pass the list of files in the matcher
237 237 as the paths specified by the user.'''
238 238 smatcher = getstandinmatcher(repo, rmatcher.files())
239 239 isstandin = smatcher.matchfn
240 240 def composedmatchfn(f):
241 241 return isstandin(f) and rmatcher.matchfn(splitstandin(f))
242 242 smatcher.matchfn = composedmatchfn
243 243
244 244 return smatcher
245 245
246 246 def standin(filename):
247 247 '''Return the repo-relative path to the standin for the specified big
248 248 file.'''
249 249 # Notes:
250 250 # 1) Some callers want an absolute path, but for instance addlargefiles
251 251 # needs it repo-relative so it can be passed to repo[None].add(). So
252 252 # leave it up to the caller to use repo.wjoin() to get an absolute path.
253 253 # 2) Join with '/' because that's what dirstate always uses, even on
254 254 # Windows. Change existing separator to '/' first in case we are
255 255 # passed filenames from an external source (like the command line).
256 256 return shortnameslash + util.pconvert(filename)
257 257
258 258 def isstandin(filename):
259 259 '''Return true if filename is a big file standin. filename must be
260 260 in Mercurial's internal form (slash-separated).'''
261 261 return filename.startswith(shortnameslash)
262 262
263 263 def splitstandin(filename):
264 264 # Split on / because that's what dirstate always uses, even on Windows.
265 265 # Change local separator to / first just in case we are passed filenames
266 266 # from an external source (like the command line).
267 267 bits = util.pconvert(filename).split('/', 1)
268 268 if len(bits) == 2 and bits[0] == shortname:
269 269 return bits[1]
270 270 else:
271 271 return None
272 272
273 273 def updatestandin(repo, standin):
274 274 file = repo.wjoin(splitstandin(standin))
275 275 if os.path.exists(file):
276 276 hash = hashfile(file)
277 277 executable = getexecutable(file)
278 278 writestandin(repo, standin, hash, executable)
279 279
280 280 def readstandin(repo, filename, node=None):
281 281 '''read hex hash from standin for filename at given node, or working
282 282 directory if no node is given'''
283 283 return repo[node][standin(filename)].data().strip()
284 284
285 285 def writestandin(repo, standin, hash, executable):
286 286 '''write hash to <repo.root>/<standin>'''
287 287 repo.wwrite(standin, hash + '\n', executable and 'x' or '')
288 288
289 289 def copyandhash(instream, outfile):
290 290 '''Read bytes from instream (iterable) and write them to outfile,
291 291 computing the SHA-1 hash of the data along the way. Return the hash.'''
292 292 hasher = util.sha1('')
293 293 for data in instream:
294 294 hasher.update(data)
295 295 outfile.write(data)
296 296 return hasher.hexdigest()
297 297
298 298 def hashrepofile(repo, file):
299 299 return hashfile(repo.wjoin(file))
300 300
301 301 def hashfile(file):
302 302 if not os.path.exists(file):
303 303 return ''
304 304 hasher = util.sha1('')
305 305 fd = open(file, 'rb')
306 306 for data in util.filechunkiter(fd, 128 * 1024):
307 307 hasher.update(data)
308 308 fd.close()
309 309 return hasher.hexdigest()
310 310
311 311 def getexecutable(filename):
312 312 mode = os.stat(filename).st_mode
313 313 return ((mode & stat.S_IXUSR) and
314 314 (mode & stat.S_IXGRP) and
315 315 (mode & stat.S_IXOTH))
316 316
317 317 def urljoin(first, second, *arg):
318 318 def join(left, right):
319 319 if not left.endswith('/'):
320 320 left += '/'
321 321 if right.startswith('/'):
322 322 right = right[1:]
323 323 return left + right
324 324
325 325 url = join(first, second)
326 326 for a in arg:
327 327 url = join(url, a)
328 328 return url
329 329
330 330 def hexsha1(data):
331 331 """hexsha1 returns the hex-encoded sha1 sum of the data in the file-like
332 332 object data"""
333 333 h = util.sha1()
334 334 for chunk in util.filechunkiter(data):
335 335 h.update(chunk)
336 336 return h.hexdigest()
337 337
338 338 def httpsendfile(ui, filename):
339 339 return httpconnection.httpsendfile(ui, filename, 'rb')
340 340
341 341 def unixpath(path):
342 342 '''Return a version of path normalized for use with the lfdirstate.'''
343 343 return util.pconvert(os.path.normpath(path))
344 344
345 345 def islfilesrepo(repo):
346 346 if ('largefiles' in repo.requirements and
347 347 util.any(shortnameslash in f[0] for f in repo.store.datafiles())):
348 348 return True
349 349
350 350 return util.any(openlfdirstate(repo.ui, repo, False))
351 351
352 352 class storeprotonotcapable(Exception):
353 353 def __init__(self, storetypes):
354 354 self.storetypes = storetypes
355 355
356 356 def getstandinsstate(repo):
357 357 standins = []
358 358 matcher = getstandinmatcher(repo)
359 359 for standin in repo.dirstate.walk(matcher, [], False, False):
360 360 lfile = splitstandin(standin)
361 361 try:
362 362 hash = readstandin(repo, lfile)
363 363 except IOError:
364 364 hash = None
365 365 standins.append((lfile, hash))
366 366 return standins
367 367
368 368 def synclfdirstate(repo, lfdirstate, lfile, normallookup):
369 369 lfstandin = standin(lfile)
370 370 if lfstandin in repo.dirstate:
371 371 stat = repo.dirstate._map[lfstandin]
372 372 state, mtime = stat[0], stat[3]
373 373 else:
374 374 state, mtime = '?', -1
375 375 if state == 'n':
376 376 if normallookup or mtime < 0:
377 377 # state 'n' doesn't ensure 'clean' in this case
378 378 lfdirstate.normallookup(lfile)
379 379 else:
380 380 lfdirstate.normal(lfile)
381 381 elif state == 'm':
382 382 lfdirstate.normallookup(lfile)
383 383 elif state == 'r':
384 384 lfdirstate.remove(lfile)
385 385 elif state == 'a':
386 386 lfdirstate.add(lfile)
387 387 elif state == '?':
388 388 lfdirstate.drop(lfile)
389 389
390 390 def markcommitted(orig, ctx, node):
391 391 repo = ctx._repo
392 392
393 393 orig(node)
394 394
395 395 lfdirstate = openlfdirstate(repo.ui, repo)
396 396 for f in ctx.files():
397 397 if isstandin(f):
398 398 lfile = splitstandin(f)
399 399 synclfdirstate(repo, lfdirstate, lfile, False)
400 400 lfdirstate.write()
401 401
402 402 def getlfilestoupdate(oldstandins, newstandins):
403 403 changedstandins = set(oldstandins).symmetric_difference(set(newstandins))
404 404 filelist = []
405 405 for f in changedstandins:
406 406 if f[0] not in filelist:
407 407 filelist.append(f[0])
408 408 return filelist
409 409
410 410 def getlfilestoupload(repo, missing, addfunc):
411 411 for n in missing:
412 412 parents = [p for p in repo.changelog.parents(n) if p != node.nullid]
413 413 ctx = repo[n]
414 414 files = set(ctx.files())
415 415 if len(parents) == 2:
416 416 mc = ctx.manifest()
417 417 mp1 = ctx.parents()[0].manifest()
418 418 mp2 = ctx.parents()[1].manifest()
419 419 for f in mp1:
420 420 if f not in mc:
421 421 files.add(f)
422 422 for f in mp2:
423 423 if f not in mc:
424 424 files.add(f)
425 425 for f in mc:
426 426 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
427 427 files.add(f)
428 428 for fn in files:
429 429 if isstandin(fn) and fn in ctx:
430 430 addfunc(fn, ctx[fn].data().strip())
431 431
432 432 def updatestandinsbymatch(repo, match):
433 433 '''Update standins in the working directory according to specified match
434 434
435 435 This returns (possibly modified) ``match`` object to be used for
436 436 subsequent commit process.
437 437 '''
438 438
439 439 ui = repo.ui
440 440
441 441 # Case 0: Automated committing
442 442 #
443 443 # While automated committing (like rebase, transplant
444 444 # and so on), this code path is used to avoid:
445 445 # (1) updating standins, because standins should
446 446 # be already updated at this point
447 447 # (2) aborting when standins are matched by "match",
448 448 # because automated committing may specify them directly
449 449 #
450 if getattr(repo, "_isrebasing", False) or \
451 getattr(repo, "_istransplanting", False):
450 if getattr(repo, "_istransplanting", False):
452 451 return match
453 452
454 453 # Case 1: user calls commit with no specific files or
455 454 # include/exclude patterns: refresh and commit all files that
456 455 # are "dirty".
457 456 if match is None or match.always():
458 457 # Spend a bit of time here to get a list of files we know
459 458 # are modified so we can compare only against those.
460 459 # It can cost a lot of time (several seconds)
461 460 # otherwise to update all standins if the largefiles are
462 461 # large.
463 462 lfdirstate = openlfdirstate(ui, repo)
464 463 dirtymatch = match_.always(repo.root, repo.getcwd())
465 464 unsure, s = lfdirstate.status(dirtymatch, [], False, False,
466 465 False)
467 466 modifiedfiles = unsure + s.modified + s.added + s.removed
468 467 lfiles = listlfiles(repo)
469 468 # this only loops through largefiles that exist (not
470 469 # removed/renamed)
471 470 for lfile in lfiles:
472 471 if lfile in modifiedfiles:
473 472 if os.path.exists(
474 473 repo.wjoin(standin(lfile))):
475 474 # this handles the case where a rebase is being
476 475 # performed and the working copy is not updated
477 476 # yet.
478 477 if os.path.exists(repo.wjoin(lfile)):
479 478 updatestandin(repo,
480 479 standin(lfile))
481 480
482 481 return match
483 482
484 483 lfiles = listlfiles(repo)
485 484 match._files = repo._subdirlfs(match.files(), lfiles)
486 485
487 486 # Case 2: user calls commit with specified patterns: refresh
488 487 # any matching big files.
489 488 smatcher = composestandinmatcher(repo, match)
490 489 standins = repo.dirstate.walk(smatcher, [], False, False)
491 490
492 491 # No matching big files: get out of the way and pass control to
493 492 # the usual commit() method.
494 493 if not standins:
495 494 return match
496 495
497 496 # Refresh all matching big files. It's possible that the
498 497 # commit will end up failing, in which case the big files will
499 498 # stay refreshed. No harm done: the user modified them and
500 499 # asked to commit them, so sooner or later we're going to
501 500 # refresh the standins. Might as well leave them refreshed.
502 501 lfdirstate = openlfdirstate(ui, repo)
503 502 for fstandin in standins:
504 503 lfile = splitstandin(fstandin)
505 504 if lfdirstate[lfile] != 'r':
506 505 updatestandin(repo, fstandin)
507 506
508 507 # Cook up a new matcher that only matches regular files or
509 508 # standins corresponding to the big files requested by the
510 509 # user. Have to modify _files to prevent commit() from
511 510 # complaining "not tracked" for big files.
512 511 match = copy.copy(match)
513 512 origmatchfn = match.matchfn
514 513
515 514 # Check both the list of largefiles and the list of
516 515 # standins because if a largefile was removed, it
517 516 # won't be in the list of largefiles at this point
518 517 match._files += sorted(standins)
519 518
520 519 actualfiles = []
521 520 for f in match._files:
522 521 fstandin = standin(f)
523 522
524 523 # ignore known largefiles and standins
525 524 if f in lfiles or fstandin in standins:
526 525 continue
527 526
528 527 actualfiles.append(f)
529 528 match._files = actualfiles
530 529
531 530 def matchfn(f):
532 531 if origmatchfn(f):
533 532 return f not in lfiles
534 533 else:
535 534 return f in standins
536 535
537 536 match.matchfn = matchfn
538 537
539 538 return match
539
540 class automatedcommithook(object):
541 '''Statefull hook to update standins at the 1st commit of resuming
542
543 For efficiency, updating standins in the working directory should
544 be avoided while automated committing (like rebase, transplant and
545 so on), because they should be updated before committing.
546
547 But the 1st commit of resuming automated committing (e.g. ``rebase
548 --continue``) should update them, because largefiles may be
549 modified manually.
550 '''
551 def __init__(self, resuming):
552 self.resuming = resuming
553
554 def __call__(self, repo, match):
555 if self.resuming:
556 self.resuming = False # avoids updating at subsequent commits
557 return updatestandinsbymatch(repo, match)
558 else:
559 return match
@@ -1,1298 +1,1301
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, util, cmdutil, scmutil, match as match_, \
15 15 archival, pathutil, revset
16 16 from mercurial.i18n import _
17 17 from mercurial.node import hex
18 18
19 19 import lfutil
20 20 import lfcommands
21 21 import basestore
22 22
23 23 # -- Utility functions: commonly/repeatedly needed functionality ---------------
24 24
25 25 def installnormalfilesmatchfn(manifest):
26 26 '''installmatchfn with a matchfn that ignores all largefiles'''
27 27 def overridematch(ctx, pats=[], opts={}, globbed=False,
28 28 default='relpath'):
29 29 match = oldmatch(ctx, pats, opts, globbed, default)
30 30 m = copy.copy(match)
31 31 notlfile = lambda f: not (lfutil.isstandin(f) or lfutil.standin(f) in
32 32 manifest)
33 33 m._files = filter(notlfile, m._files)
34 34 m._fmap = set(m._files)
35 35 m._always = False
36 36 origmatchfn = m.matchfn
37 37 m.matchfn = lambda f: notlfile(f) and origmatchfn(f)
38 38 return m
39 39 oldmatch = installmatchfn(overridematch)
40 40
41 41 def installmatchfn(f):
42 42 '''monkey patch the scmutil module with a custom match function.
43 43 Warning: it is monkey patching the _module_ on runtime! Not thread safe!'''
44 44 oldmatch = scmutil.match
45 45 setattr(f, 'oldmatch', oldmatch)
46 46 scmutil.match = f
47 47 return oldmatch
48 48
49 49 def restorematchfn():
50 50 '''restores scmutil.match to what it was before installmatchfn
51 51 was called. no-op if scmutil.match is its original function.
52 52
53 53 Note that n calls to installmatchfn will require n calls to
54 54 restore matchfn to reverse'''
55 55 scmutil.match = getattr(scmutil.match, 'oldmatch')
56 56
57 57 def installmatchandpatsfn(f):
58 58 oldmatchandpats = scmutil.matchandpats
59 59 setattr(f, 'oldmatchandpats', oldmatchandpats)
60 60 scmutil.matchandpats = f
61 61 return oldmatchandpats
62 62
63 63 def restorematchandpatsfn():
64 64 '''restores scmutil.matchandpats to what it was before
65 65 installmatchandpatsfn was called. No-op if scmutil.matchandpats
66 66 is its original function.
67 67
68 68 Note that n calls to installmatchandpatsfn will require n calls
69 69 to restore matchfn to reverse'''
70 70 scmutil.matchandpats = getattr(scmutil.matchandpats, 'oldmatchandpats',
71 71 scmutil.matchandpats)
72 72
73 73 def addlargefiles(ui, repo, *pats, **opts):
74 74 large = opts.pop('large', None)
75 75 lfsize = lfutil.getminsize(
76 76 ui, lfutil.islfilesrepo(repo), opts.pop('lfsize', None))
77 77
78 78 lfmatcher = None
79 79 if lfutil.islfilesrepo(repo):
80 80 lfpats = ui.configlist(lfutil.longname, 'patterns', default=[])
81 81 if lfpats:
82 82 lfmatcher = match_.match(repo.root, '', list(lfpats))
83 83
84 84 lfnames = []
85 85 m = scmutil.match(repo[None], pats, opts)
86 86 m.bad = lambda x, y: None
87 87 wctx = repo[None]
88 88 for f in repo.walk(m):
89 89 exact = m.exact(f)
90 90 lfile = lfutil.standin(f) in wctx
91 91 nfile = f in wctx
92 92 exists = lfile or nfile
93 93
94 94 # Don't warn the user when they attempt to add a normal tracked file.
95 95 # The normal add code will do that for us.
96 96 if exact and exists:
97 97 if lfile:
98 98 ui.warn(_('%s already a largefile\n') % f)
99 99 continue
100 100
101 101 if (exact or not exists) and not lfutil.isstandin(f):
102 102 wfile = repo.wjoin(f)
103 103
104 104 # In case the file was removed previously, but not committed
105 105 # (issue3507)
106 106 if not os.path.exists(wfile):
107 107 continue
108 108
109 109 abovemin = (lfsize and
110 110 os.lstat(wfile).st_size >= lfsize * 1024 * 1024)
111 111 if large or abovemin or (lfmatcher and lfmatcher(f)):
112 112 lfnames.append(f)
113 113 if ui.verbose or not exact:
114 114 ui.status(_('adding %s as a largefile\n') % m.rel(f))
115 115
116 116 bad = []
117 117
118 118 # Need to lock, otherwise there could be a race condition between
119 119 # when standins are created and added to the repo.
120 120 wlock = repo.wlock()
121 121 try:
122 122 if not opts.get('dry_run'):
123 123 standins = []
124 124 lfdirstate = lfutil.openlfdirstate(ui, repo)
125 125 for f in lfnames:
126 126 standinname = lfutil.standin(f)
127 127 lfutil.writestandin(repo, standinname, hash='',
128 128 executable=lfutil.getexecutable(repo.wjoin(f)))
129 129 standins.append(standinname)
130 130 if lfdirstate[f] == 'r':
131 131 lfdirstate.normallookup(f)
132 132 else:
133 133 lfdirstate.add(f)
134 134 lfdirstate.write()
135 135 bad += [lfutil.splitstandin(f)
136 136 for f in repo[None].add(standins)
137 137 if f in m.files()]
138 138 finally:
139 139 wlock.release()
140 140 return bad
141 141
142 142 def removelargefiles(ui, repo, isaddremove, *pats, **opts):
143 143 after = opts.get('after')
144 144 if not pats and not after:
145 145 raise util.Abort(_('no files specified'))
146 146 m = scmutil.match(repo[None], pats, opts)
147 147 try:
148 148 repo.lfstatus = True
149 149 s = repo.status(match=m, clean=True)
150 150 finally:
151 151 repo.lfstatus = False
152 152 manifest = repo[None].manifest()
153 153 modified, added, deleted, clean = [[f for f in list
154 154 if lfutil.standin(f) in manifest]
155 155 for list in (s.modified, s.added,
156 156 s.deleted, s.clean)]
157 157
158 158 def warn(files, msg):
159 159 for f in files:
160 160 ui.warn(msg % m.rel(f))
161 161 return int(len(files) > 0)
162 162
163 163 result = 0
164 164
165 165 if after:
166 166 remove = deleted
167 167 result = warn(modified + added + clean,
168 168 _('not removing %s: file still exists\n'))
169 169 else:
170 170 remove = deleted + clean
171 171 result = warn(modified, _('not removing %s: file is modified (use -f'
172 172 ' to force removal)\n'))
173 173 result = warn(added, _('not removing %s: file has been marked for add'
174 174 ' (use forget to undo)\n')) or result
175 175
176 176 for f in sorted(remove):
177 177 if ui.verbose or not m.exact(f):
178 178 ui.status(_('removing %s\n') % m.rel(f))
179 179
180 180 # Need to lock because standin files are deleted then removed from the
181 181 # repository and we could race in-between.
182 182 wlock = repo.wlock()
183 183 try:
184 184 lfdirstate = lfutil.openlfdirstate(ui, repo)
185 185 for f in remove:
186 186 if not after:
187 187 # If this is being called by addremove, notify the user that we
188 188 # are removing the file.
189 189 if isaddremove:
190 190 ui.status(_('removing %s\n') % f)
191 191 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
192 192 lfdirstate.remove(f)
193 193 lfdirstate.write()
194 194 remove = [lfutil.standin(f) for f in remove]
195 195 # If this is being called by addremove, let the original addremove
196 196 # function handle this.
197 197 if not isaddremove:
198 198 for f in remove:
199 199 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
200 200 repo[None].forget(remove)
201 201 finally:
202 202 wlock.release()
203 203
204 204 return result
205 205
206 206 # For overriding mercurial.hgweb.webcommands so that largefiles will
207 207 # appear at their right place in the manifests.
208 208 def decodepath(orig, path):
209 209 return lfutil.splitstandin(path) or path
210 210
211 211 # -- Wrappers: modify existing commands --------------------------------
212 212
213 213 # Add works by going through the files that the user wanted to add and
214 214 # checking if they should be added as largefiles. Then it makes a new
215 215 # matcher which matches only the normal files and runs the original
216 216 # version of add.
217 217 def overrideadd(orig, ui, repo, *pats, **opts):
218 218 normal = opts.pop('normal')
219 219 if normal:
220 220 if opts.get('large'):
221 221 raise util.Abort(_('--normal cannot be used with --large'))
222 222 return orig(ui, repo, *pats, **opts)
223 223 bad = addlargefiles(ui, repo, *pats, **opts)
224 224 installnormalfilesmatchfn(repo[None].manifest())
225 225 result = orig(ui, repo, *pats, **opts)
226 226 restorematchfn()
227 227
228 228 return (result == 1 or bad) and 1 or 0
229 229
230 230 def overrideremove(orig, ui, repo, *pats, **opts):
231 231 installnormalfilesmatchfn(repo[None].manifest())
232 232 result = orig(ui, repo, *pats, **opts)
233 233 restorematchfn()
234 234 return removelargefiles(ui, repo, False, *pats, **opts) or result
235 235
236 236 def overridestatusfn(orig, repo, rev2, **opts):
237 237 try:
238 238 repo._repo.lfstatus = True
239 239 return orig(repo, rev2, **opts)
240 240 finally:
241 241 repo._repo.lfstatus = False
242 242
243 243 def overridestatus(orig, ui, repo, *pats, **opts):
244 244 try:
245 245 repo.lfstatus = True
246 246 return orig(ui, repo, *pats, **opts)
247 247 finally:
248 248 repo.lfstatus = False
249 249
250 250 def overridedirty(orig, repo, ignoreupdate=False):
251 251 try:
252 252 repo._repo.lfstatus = True
253 253 return orig(repo, ignoreupdate)
254 254 finally:
255 255 repo._repo.lfstatus = False
256 256
257 257 def overridelog(orig, ui, repo, *pats, **opts):
258 258 def overridematchandpats(ctx, pats=[], opts={}, globbed=False,
259 259 default='relpath'):
260 260 """Matcher that merges root directory with .hglf, suitable for log.
261 261 It is still possible to match .hglf directly.
262 262 For any listed files run log on the standin too.
263 263 matchfn tries both the given filename and with .hglf stripped.
264 264 """
265 265 matchandpats = oldmatchandpats(ctx, pats, opts, globbed, default)
266 266 m, p = copy.copy(matchandpats)
267 267
268 268 if m.always():
269 269 # We want to match everything anyway, so there's no benefit trying
270 270 # to add standins.
271 271 return matchandpats
272 272
273 273 pats = set(p)
274 274 # TODO: handling of patterns in both cases below
275 275 if m._cwd:
276 276 if os.path.isabs(m._cwd):
277 277 # TODO: handle largefile magic when invoked from other cwd
278 278 return matchandpats
279 279 back = (m._cwd.count('/') + 1) * '../'
280 280 pats.update(back + lfutil.standin(m._cwd + '/' + f) for f in p)
281 281 else:
282 282 pats.update(lfutil.standin(f) for f in p)
283 283
284 284 for i in range(0, len(m._files)):
285 285 standin = lfutil.standin(m._files[i])
286 286 if standin in repo[ctx.node()]:
287 287 m._files[i] = standin
288 288 elif m._files[i] not in repo[ctx.node()]:
289 289 m._files.append(standin)
290 290 pats.add(standin)
291 291
292 292 m._fmap = set(m._files)
293 293 m._always = False
294 294 origmatchfn = m.matchfn
295 295 def lfmatchfn(f):
296 296 lf = lfutil.splitstandin(f)
297 297 if lf is not None and origmatchfn(lf):
298 298 return True
299 299 r = origmatchfn(f)
300 300 return r
301 301 m.matchfn = lfmatchfn
302 302
303 303 return m, pats
304 304
305 305 # For hg log --patch, the match object is used in two different senses:
306 306 # (1) to determine what revisions should be printed out, and
307 307 # (2) to determine what files to print out diffs for.
308 308 # The magic matchandpats override should be used for case (1) but not for
309 309 # case (2).
310 310 def overridemakelogfilematcher(repo, pats, opts):
311 311 pctx = repo[None]
312 312 match, pats = oldmatchandpats(pctx, pats, opts)
313 313 return lambda rev: match
314 314
315 315 oldmatchandpats = installmatchandpatsfn(overridematchandpats)
316 316 oldmakelogfilematcher = cmdutil._makenofollowlogfilematcher
317 317 setattr(cmdutil, '_makenofollowlogfilematcher', overridemakelogfilematcher)
318 318
319 319 try:
320 320 return orig(ui, repo, *pats, **opts)
321 321 finally:
322 322 restorematchandpatsfn()
323 323 setattr(cmdutil, '_makenofollowlogfilematcher', oldmakelogfilematcher)
324 324
325 325 def overrideverify(orig, ui, repo, *pats, **opts):
326 326 large = opts.pop('large', False)
327 327 all = opts.pop('lfa', False)
328 328 contents = opts.pop('lfc', False)
329 329
330 330 result = orig(ui, repo, *pats, **opts)
331 331 if large or all or contents:
332 332 result = result or lfcommands.verifylfiles(ui, repo, all, contents)
333 333 return result
334 334
335 335 def overridedebugstate(orig, ui, repo, *pats, **opts):
336 336 large = opts.pop('large', False)
337 337 if large:
338 338 class fakerepo(object):
339 339 dirstate = lfutil.openlfdirstate(ui, repo)
340 340 orig(ui, fakerepo, *pats, **opts)
341 341 else:
342 342 orig(ui, repo, *pats, **opts)
343 343
344 344 # Override needs to refresh standins so that update's normal merge
345 345 # will go through properly. Then the other update hook (overriding repo.update)
346 346 # will get the new files. Filemerge is also overridden so that the merge
347 347 # will merge standins correctly.
348 348 def overrideupdate(orig, ui, repo, *pats, **opts):
349 349 # Need to lock between the standins getting updated and their
350 350 # largefiles getting updated
351 351 wlock = repo.wlock()
352 352 try:
353 353 if opts['check']:
354 354 lfdirstate = lfutil.openlfdirstate(ui, repo)
355 355 unsure, s = lfdirstate.status(
356 356 match_.always(repo.root, repo.getcwd()),
357 357 [], False, False, False)
358 358
359 359 mod = len(s.modified) > 0
360 360 for lfile in unsure:
361 361 standin = lfutil.standin(lfile)
362 362 if repo['.'][standin].data().strip() != \
363 363 lfutil.hashfile(repo.wjoin(lfile)):
364 364 mod = True
365 365 else:
366 366 lfdirstate.normal(lfile)
367 367 lfdirstate.write()
368 368 if mod:
369 369 raise util.Abort(_('uncommitted changes'))
370 370 return orig(ui, repo, *pats, **opts)
371 371 finally:
372 372 wlock.release()
373 373
374 374 # Before starting the manifest merge, merge.updates will call
375 375 # _checkunknown to check if there are any files in the merged-in
376 376 # changeset that collide with unknown files in the working copy.
377 377 #
378 378 # The largefiles are seen as unknown, so this prevents us from merging
379 379 # in a file 'foo' if we already have a largefile with the same name.
380 380 #
381 381 # The overridden function filters the unknown files by removing any
382 382 # largefiles. This makes the merge proceed and we can then handle this
383 383 # case further in the overridden manifestmerge function below.
384 384 def overridecheckunknownfile(origfn, repo, wctx, mctx, f):
385 385 if lfutil.standin(repo.dirstate.normalize(f)) in wctx:
386 386 return False
387 387 return origfn(repo, wctx, mctx, f)
388 388
389 389 # The manifest merge handles conflicts on the manifest level. We want
390 390 # to handle changes in largefile-ness of files at this level too.
391 391 #
392 392 # The strategy is to run the original manifestmerge and then process
393 393 # the action list it outputs. There are two cases we need to deal with:
394 394 #
395 395 # 1. Normal file in p1, largefile in p2. Here the largefile is
396 396 # detected via its standin file, which will enter the working copy
397 397 # with a "get" action. It is not "merge" since the standin is all
398 398 # Mercurial is concerned with at this level -- the link to the
399 399 # existing normal file is not relevant here.
400 400 #
401 401 # 2. Largefile in p1, normal file in p2. Here we get a "merge" action
402 402 # since the largefile will be present in the working copy and
403 403 # different from the normal file in p2. Mercurial therefore
404 404 # triggers a merge action.
405 405 #
406 406 # In both cases, we prompt the user and emit new actions to either
407 407 # remove the standin (if the normal file was kept) or to remove the
408 408 # normal file and get the standin (if the largefile was kept). The
409 409 # default prompt answer is to use the largefile version since it was
410 410 # presumably changed on purpose.
411 411 #
412 412 # Finally, the merge.applyupdates function will then take care of
413 413 # writing the files into the working copy and lfcommands.updatelfiles
414 414 # will update the largefiles.
415 415 def overridecalculateupdates(origfn, repo, p1, p2, pas, branchmerge, force,
416 416 partial, acceptremote, followcopies):
417 417 overwrite = force and not branchmerge
418 418 actions = origfn(repo, p1, p2, pas, branchmerge, force, partial,
419 419 acceptremote, followcopies)
420 420
421 421 if overwrite:
422 422 return actions
423 423
424 424 removes = set(a[0] for a in actions['r'])
425 425
426 426 newglist = []
427 427 lfmr = [] # LargeFiles: Mark as Removed
428 428 for action in actions['g']:
429 429 f, args, msg = action
430 430 splitstandin = f and lfutil.splitstandin(f)
431 431 if (splitstandin is not None and
432 432 splitstandin in p1 and splitstandin not in removes):
433 433 # Case 1: normal file in the working copy, largefile in
434 434 # the second parent
435 435 lfile = splitstandin
436 436 standin = f
437 437 msg = _('remote turned local normal file %s into a largefile\n'
438 438 'use (l)argefile or keep (n)ormal file?'
439 439 '$$ &Largefile $$ &Normal file') % lfile
440 440 if repo.ui.promptchoice(msg, 0) == 0:
441 441 actions['r'].append((lfile, None, msg))
442 442 newglist.append((standin, (p2.flags(standin),), msg))
443 443 else:
444 444 actions['r'].append((standin, None, msg))
445 445 elif lfutil.standin(f) in p1 and lfutil.standin(f) not in removes:
446 446 # Case 2: largefile in the working copy, normal file in
447 447 # the second parent
448 448 standin = lfutil.standin(f)
449 449 lfile = f
450 450 msg = _('remote turned local largefile %s into a normal file\n'
451 451 'keep (l)argefile or use (n)ormal file?'
452 452 '$$ &Largefile $$ &Normal file') % lfile
453 453 if repo.ui.promptchoice(msg, 0) == 0:
454 454 if branchmerge:
455 455 # largefile can be restored from standin safely
456 456 actions['r'].append((lfile, None, msg))
457 457 else:
458 458 # "lfile" should be marked as "removed" without
459 459 # removal of itself
460 460 lfmr.append((lfile, None, msg))
461 461
462 462 # linear-merge should treat this largefile as 're-added'
463 463 actions['a'].append((standin, None, msg))
464 464 else:
465 465 actions['r'].append((standin, None, msg))
466 466 newglist.append((lfile, (p2.flags(lfile),), msg))
467 467 else:
468 468 newglist.append(action)
469 469
470 470 newglist.sort()
471 471 actions['g'] = newglist
472 472 if lfmr:
473 473 lfmr.sort()
474 474 actions['lfmr'] = lfmr
475 475
476 476 return actions
477 477
478 478 def mergerecordupdates(orig, repo, actions, branchmerge):
479 479 if 'lfmr' in actions:
480 480 # this should be executed before 'orig', to execute 'remove'
481 481 # before all other actions
482 482 for lfile, args, msg in actions['lfmr']:
483 483 repo.dirstate.remove(lfile)
484 484
485 485 return orig(repo, actions, branchmerge)
486 486
487 487
488 488 # Override filemerge to prompt the user about how they wish to merge
489 489 # largefiles. This will handle identical edits without prompting the user.
490 490 def overridefilemerge(origfn, repo, mynode, orig, fcd, fco, fca, labels=None):
491 491 if not lfutil.isstandin(orig):
492 492 return origfn(repo, mynode, orig, fcd, fco, fca, labels=labels)
493 493
494 494 ahash = fca.data().strip().lower()
495 495 dhash = fcd.data().strip().lower()
496 496 ohash = fco.data().strip().lower()
497 497 if (ohash != ahash and
498 498 ohash != dhash and
499 499 (dhash == ahash or
500 500 repo.ui.promptchoice(
501 501 _('largefile %s has a merge conflict\nancestor was %s\n'
502 502 'keep (l)ocal %s or\ntake (o)ther %s?'
503 503 '$$ &Local $$ &Other') %
504 504 (lfutil.splitstandin(orig), ahash, dhash, ohash),
505 505 0) == 1)):
506 506 repo.wwrite(fcd.path(), fco.data(), fco.flags())
507 507 return 0
508 508
509 509 # Copy first changes the matchers to match standins instead of
510 510 # largefiles. Then it overrides util.copyfile in that function it
511 511 # checks if the destination largefile already exists. It also keeps a
512 512 # list of copied files so that the largefiles can be copied and the
513 513 # dirstate updated.
514 514 def overridecopy(orig, ui, repo, pats, opts, rename=False):
515 515 # doesn't remove largefile on rename
516 516 if len(pats) < 2:
517 517 # this isn't legal, let the original function deal with it
518 518 return orig(ui, repo, pats, opts, rename)
519 519
520 520 def makestandin(relpath):
521 521 path = pathutil.canonpath(repo.root, repo.getcwd(), relpath)
522 522 return os.path.join(repo.wjoin(lfutil.standin(path)))
523 523
524 524 fullpats = scmutil.expandpats(pats)
525 525 dest = fullpats[-1]
526 526
527 527 if os.path.isdir(dest):
528 528 if not os.path.isdir(makestandin(dest)):
529 529 os.makedirs(makestandin(dest))
530 530 # This could copy both lfiles and normal files in one command,
531 531 # but we don't want to do that. First replace their matcher to
532 532 # only match normal files and run it, then replace it to just
533 533 # match largefiles and run it again.
534 534 nonormalfiles = False
535 535 nolfiles = False
536 536 installnormalfilesmatchfn(repo[None].manifest())
537 537 try:
538 538 try:
539 539 result = orig(ui, repo, pats, opts, rename)
540 540 except util.Abort, e:
541 541 if str(e) != _('no files to copy'):
542 542 raise e
543 543 else:
544 544 nonormalfiles = True
545 545 result = 0
546 546 finally:
547 547 restorematchfn()
548 548
549 549 # The first rename can cause our current working directory to be removed.
550 550 # In that case there is nothing left to copy/rename so just quit.
551 551 try:
552 552 repo.getcwd()
553 553 except OSError:
554 554 return result
555 555
556 556 try:
557 557 try:
558 558 # When we call orig below it creates the standins but we don't add
559 559 # them to the dir state until later so lock during that time.
560 560 wlock = repo.wlock()
561 561
562 562 manifest = repo[None].manifest()
563 563 def overridematch(ctx, pats=[], opts={}, globbed=False,
564 564 default='relpath'):
565 565 newpats = []
566 566 # The patterns were previously mangled to add the standin
567 567 # directory; we need to remove that now
568 568 for pat in pats:
569 569 if match_.patkind(pat) is None and lfutil.shortname in pat:
570 570 newpats.append(pat.replace(lfutil.shortname, ''))
571 571 else:
572 572 newpats.append(pat)
573 573 match = oldmatch(ctx, newpats, opts, globbed, default)
574 574 m = copy.copy(match)
575 575 lfile = lambda f: lfutil.standin(f) in manifest
576 576 m._files = [lfutil.standin(f) for f in m._files if lfile(f)]
577 577 m._fmap = set(m._files)
578 578 origmatchfn = m.matchfn
579 579 m.matchfn = lambda f: (lfutil.isstandin(f) and
580 580 (f in manifest) and
581 581 origmatchfn(lfutil.splitstandin(f)) or
582 582 None)
583 583 return m
584 584 oldmatch = installmatchfn(overridematch)
585 585 listpats = []
586 586 for pat in pats:
587 587 if match_.patkind(pat) is not None:
588 588 listpats.append(pat)
589 589 else:
590 590 listpats.append(makestandin(pat))
591 591
592 592 try:
593 593 origcopyfile = util.copyfile
594 594 copiedfiles = []
595 595 def overridecopyfile(src, dest):
596 596 if (lfutil.shortname in src and
597 597 dest.startswith(repo.wjoin(lfutil.shortname))):
598 598 destlfile = dest.replace(lfutil.shortname, '')
599 599 if not opts['force'] and os.path.exists(destlfile):
600 600 raise IOError('',
601 601 _('destination largefile already exists'))
602 602 copiedfiles.append((src, dest))
603 603 origcopyfile(src, dest)
604 604
605 605 util.copyfile = overridecopyfile
606 606 result += orig(ui, repo, listpats, opts, rename)
607 607 finally:
608 608 util.copyfile = origcopyfile
609 609
610 610 lfdirstate = lfutil.openlfdirstate(ui, repo)
611 611 for (src, dest) in copiedfiles:
612 612 if (lfutil.shortname in src and
613 613 dest.startswith(repo.wjoin(lfutil.shortname))):
614 614 srclfile = src.replace(repo.wjoin(lfutil.standin('')), '')
615 615 destlfile = dest.replace(repo.wjoin(lfutil.standin('')), '')
616 616 destlfiledir = os.path.dirname(repo.wjoin(destlfile)) or '.'
617 617 if not os.path.isdir(destlfiledir):
618 618 os.makedirs(destlfiledir)
619 619 if rename:
620 620 os.rename(repo.wjoin(srclfile), repo.wjoin(destlfile))
621 621
622 622 # The file is gone, but this deletes any empty parent
623 623 # directories as a side-effect.
624 624 util.unlinkpath(repo.wjoin(srclfile), True)
625 625 lfdirstate.remove(srclfile)
626 626 else:
627 627 util.copyfile(repo.wjoin(srclfile),
628 628 repo.wjoin(destlfile))
629 629
630 630 lfdirstate.add(destlfile)
631 631 lfdirstate.write()
632 632 except util.Abort, e:
633 633 if str(e) != _('no files to copy'):
634 634 raise e
635 635 else:
636 636 nolfiles = True
637 637 finally:
638 638 restorematchfn()
639 639 wlock.release()
640 640
641 641 if nolfiles and nonormalfiles:
642 642 raise util.Abort(_('no files to copy'))
643 643
644 644 return result
645 645
646 646 # When the user calls revert, we have to be careful to not revert any
647 647 # changes to other largefiles accidentally. This means we have to keep
648 648 # track of the largefiles that are being reverted so we only pull down
649 649 # the necessary largefiles.
650 650 #
651 651 # Standins are only updated (to match the hash of largefiles) before
652 652 # commits. Update the standins then run the original revert, changing
653 653 # the matcher to hit standins instead of largefiles. Based on the
654 654 # resulting standins update the largefiles.
655 655 def overriderevert(orig, ui, repo, *pats, **opts):
656 656 # Because we put the standins in a bad state (by updating them)
657 657 # and then return them to a correct state we need to lock to
658 658 # prevent others from changing them in their incorrect state.
659 659 wlock = repo.wlock()
660 660 try:
661 661 lfdirstate = lfutil.openlfdirstate(ui, repo)
662 662 s = lfutil.lfdirstatestatus(lfdirstate, repo)
663 663 lfdirstate.write()
664 664 for lfile in s.modified:
665 665 lfutil.updatestandin(repo, lfutil.standin(lfile))
666 666 for lfile in s.deleted:
667 667 if (os.path.exists(repo.wjoin(lfutil.standin(lfile)))):
668 668 os.unlink(repo.wjoin(lfutil.standin(lfile)))
669 669
670 670 oldstandins = lfutil.getstandinsstate(repo)
671 671
672 672 def overridematch(ctx, pats=[], opts={}, globbed=False,
673 673 default='relpath'):
674 674 match = oldmatch(ctx, pats, opts, globbed, default)
675 675 m = copy.copy(match)
676 676 def tostandin(f):
677 677 if lfutil.standin(f) in ctx:
678 678 return lfutil.standin(f)
679 679 elif lfutil.standin(f) in repo[None]:
680 680 return None
681 681 return f
682 682 m._files = [tostandin(f) for f in m._files]
683 683 m._files = [f for f in m._files if f is not None]
684 684 m._fmap = set(m._files)
685 685 origmatchfn = m.matchfn
686 686 def matchfn(f):
687 687 if lfutil.isstandin(f):
688 688 return (origmatchfn(lfutil.splitstandin(f)) and
689 689 (f in repo[None] or f in ctx))
690 690 return origmatchfn(f)
691 691 m.matchfn = matchfn
692 692 return m
693 693 oldmatch = installmatchfn(overridematch)
694 694 try:
695 695 orig(ui, repo, *pats, **opts)
696 696 finally:
697 697 restorematchfn()
698 698
699 699 newstandins = lfutil.getstandinsstate(repo)
700 700 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
701 701 # lfdirstate should be 'normallookup'-ed for updated files,
702 702 # because reverting doesn't touch dirstate for 'normal' files
703 703 # when target revision is explicitly specified: in such case,
704 704 # 'n' and valid timestamp in dirstate doesn't ensure 'clean'
705 705 # of target (standin) file.
706 706 lfcommands.updatelfiles(ui, repo, filelist, printmessage=False,
707 707 normallookup=True)
708 708
709 709 finally:
710 710 wlock.release()
711 711
712 712 # after pulling changesets, we need to take some extra care to get
713 713 # largefiles updated remotely
714 714 def overridepull(orig, ui, repo, source=None, **opts):
715 715 revsprepull = len(repo)
716 716 if not source:
717 717 source = 'default'
718 718 repo.lfpullsource = source
719 719 result = orig(ui, repo, source, **opts)
720 720 revspostpull = len(repo)
721 721 lfrevs = opts.get('lfrev', [])
722 722 if opts.get('all_largefiles'):
723 723 lfrevs.append('pulled()')
724 724 if lfrevs and revspostpull > revsprepull:
725 725 numcached = 0
726 726 repo.firstpulled = revsprepull # for pulled() revset expression
727 727 try:
728 728 for rev in scmutil.revrange(repo, lfrevs):
729 729 ui.note(_('pulling largefiles for revision %s\n') % rev)
730 730 (cached, missing) = lfcommands.cachelfiles(ui, repo, rev)
731 731 numcached += len(cached)
732 732 finally:
733 733 del repo.firstpulled
734 734 ui.status(_("%d largefiles cached\n") % numcached)
735 735 return result
736 736
737 737 def pulledrevsetsymbol(repo, subset, x):
738 738 """``pulled()``
739 739 Changesets that just has been pulled.
740 740
741 741 Only available with largefiles from pull --lfrev expressions.
742 742
743 743 .. container:: verbose
744 744
745 745 Some examples:
746 746
747 747 - pull largefiles for all new changesets::
748 748
749 749 hg pull -lfrev "pulled()"
750 750
751 751 - pull largefiles for all new branch heads::
752 752
753 753 hg pull -lfrev "head(pulled()) and not closed()"
754 754
755 755 """
756 756
757 757 try:
758 758 firstpulled = repo.firstpulled
759 759 except AttributeError:
760 760 raise util.Abort(_("pulled() only available in --lfrev"))
761 761 return revset.baseset([r for r in subset if r >= firstpulled])
762 762
763 763 def overrideclone(orig, ui, source, dest=None, **opts):
764 764 d = dest
765 765 if d is None:
766 766 d = hg.defaultdest(source)
767 767 if opts.get('all_largefiles') and not hg.islocal(d):
768 768 raise util.Abort(_(
769 769 '--all-largefiles is incompatible with non-local destination %s') %
770 770 d)
771 771
772 772 return orig(ui, source, dest, **opts)
773 773
774 774 def hgclone(orig, ui, opts, *args, **kwargs):
775 775 result = orig(ui, opts, *args, **kwargs)
776 776
777 777 if result is not None:
778 778 sourcerepo, destrepo = result
779 779 repo = destrepo.local()
780 780
781 781 # Caching is implicitly limited to 'rev' option, since the dest repo was
782 782 # truncated at that point. The user may expect a download count with
783 783 # this option, so attempt whether or not this is a largefile repo.
784 784 if opts.get('all_largefiles'):
785 785 success, missing = lfcommands.downloadlfiles(ui, repo, None)
786 786
787 787 if missing != 0:
788 788 return None
789 789
790 790 return result
791 791
792 792 def overriderebase(orig, ui, repo, **opts):
793 resuming = opts.get('continue')
794 repo._lfcommithooks.append(lfutil.automatedcommithook(resuming))
793 795 repo._isrebasing = True
794 796 try:
795 797 return orig(ui, repo, **opts)
796 798 finally:
797 799 repo._isrebasing = False
800 repo._lfcommithooks.pop()
798 801
799 802 def overridearchive(orig, repo, dest, node, kind, decode=True, matchfn=None,
800 803 prefix=None, mtime=None, subrepos=None):
801 804 # No need to lock because we are only reading history and
802 805 # largefile caches, neither of which are modified.
803 806 lfcommands.cachelfiles(repo.ui, repo, node)
804 807
805 808 if kind not in archival.archivers:
806 809 raise util.Abort(_("unknown archive type '%s'") % kind)
807 810
808 811 ctx = repo[node]
809 812
810 813 if kind == 'files':
811 814 if prefix:
812 815 raise util.Abort(
813 816 _('cannot give prefix when archiving to files'))
814 817 else:
815 818 prefix = archival.tidyprefix(dest, kind, prefix)
816 819
817 820 def write(name, mode, islink, getdata):
818 821 if matchfn and not matchfn(name):
819 822 return
820 823 data = getdata()
821 824 if decode:
822 825 data = repo.wwritedata(name, data)
823 826 archiver.addfile(prefix + name, mode, islink, data)
824 827
825 828 archiver = archival.archivers[kind](dest, mtime or ctx.date()[0])
826 829
827 830 if repo.ui.configbool("ui", "archivemeta", True):
828 831 def metadata():
829 832 base = 'repo: %s\nnode: %s\nbranch: %s\n' % (
830 833 hex(repo.changelog.node(0)), hex(node), ctx.branch())
831 834
832 835 tags = ''.join('tag: %s\n' % t for t in ctx.tags()
833 836 if repo.tagtype(t) == 'global')
834 837 if not tags:
835 838 repo.ui.pushbuffer()
836 839 opts = {'template': '{latesttag}\n{latesttagdistance}',
837 840 'style': '', 'patch': None, 'git': None}
838 841 cmdutil.show_changeset(repo.ui, repo, opts).show(ctx)
839 842 ltags, dist = repo.ui.popbuffer().split('\n')
840 843 tags = ''.join('latesttag: %s\n' % t for t in ltags.split(':'))
841 844 tags += 'latesttagdistance: %s\n' % dist
842 845
843 846 return base + tags
844 847
845 848 write('.hg_archival.txt', 0644, False, metadata)
846 849
847 850 for f in ctx:
848 851 ff = ctx.flags(f)
849 852 getdata = ctx[f].data
850 853 if lfutil.isstandin(f):
851 854 path = lfutil.findfile(repo, getdata().strip())
852 855 if path is None:
853 856 raise util.Abort(
854 857 _('largefile %s not found in repo store or system cache')
855 858 % lfutil.splitstandin(f))
856 859 f = lfutil.splitstandin(f)
857 860
858 861 def getdatafn():
859 862 fd = None
860 863 try:
861 864 fd = open(path, 'rb')
862 865 return fd.read()
863 866 finally:
864 867 if fd:
865 868 fd.close()
866 869
867 870 getdata = getdatafn
868 871 write(f, 'x' in ff and 0755 or 0644, 'l' in ff, getdata)
869 872
870 873 if subrepos:
871 874 for subpath in sorted(ctx.substate):
872 875 sub = ctx.sub(subpath)
873 876 submatch = match_.narrowmatcher(subpath, matchfn)
874 877 sub.archive(repo.ui, archiver, prefix, submatch)
875 878
876 879 archiver.done()
877 880
878 881 def hgsubrepoarchive(orig, repo, ui, archiver, prefix, match=None):
879 882 repo._get(repo._state + ('hg',))
880 883 rev = repo._state[1]
881 884 ctx = repo._repo[rev]
882 885
883 886 lfcommands.cachelfiles(ui, repo._repo, ctx.node())
884 887
885 888 def write(name, mode, islink, getdata):
886 889 # At this point, the standin has been replaced with the largefile name,
887 890 # so the normal matcher works here without the lfutil variants.
888 891 if match and not match(f):
889 892 return
890 893 data = getdata()
891 894
892 895 archiver.addfile(prefix + repo._path + '/' + name, mode, islink, data)
893 896
894 897 for f in ctx:
895 898 ff = ctx.flags(f)
896 899 getdata = ctx[f].data
897 900 if lfutil.isstandin(f):
898 901 path = lfutil.findfile(repo._repo, getdata().strip())
899 902 if path is None:
900 903 raise util.Abort(
901 904 _('largefile %s not found in repo store or system cache')
902 905 % lfutil.splitstandin(f))
903 906 f = lfutil.splitstandin(f)
904 907
905 908 def getdatafn():
906 909 fd = None
907 910 try:
908 911 fd = open(os.path.join(prefix, path), 'rb')
909 912 return fd.read()
910 913 finally:
911 914 if fd:
912 915 fd.close()
913 916
914 917 getdata = getdatafn
915 918
916 919 write(f, 'x' in ff and 0755 or 0644, 'l' in ff, getdata)
917 920
918 921 for subpath in sorted(ctx.substate):
919 922 sub = ctx.sub(subpath)
920 923 submatch = match_.narrowmatcher(subpath, match)
921 924 sub.archive(ui, archiver, os.path.join(prefix, repo._path) + '/',
922 925 submatch)
923 926
924 927 # If a largefile is modified, the change is not reflected in its
925 928 # standin until a commit. cmdutil.bailifchanged() raises an exception
926 929 # if the repo has uncommitted changes. Wrap it to also check if
927 930 # largefiles were changed. This is used by bisect and backout.
928 931 def overridebailifchanged(orig, repo):
929 932 orig(repo)
930 933 repo.lfstatus = True
931 934 s = repo.status()
932 935 repo.lfstatus = False
933 936 if s.modified or s.added or s.removed or s.deleted:
934 937 raise util.Abort(_('uncommitted changes'))
935 938
936 939 # Fetch doesn't use cmdutil.bailifchanged so override it to add the check
937 940 def overridefetch(orig, ui, repo, *pats, **opts):
938 941 repo.lfstatus = True
939 942 s = repo.status()
940 943 repo.lfstatus = False
941 944 if s.modified or s.added or s.removed or s.deleted:
942 945 raise util.Abort(_('uncommitted changes'))
943 946 return orig(ui, repo, *pats, **opts)
944 947
945 948 def overrideforget(orig, ui, repo, *pats, **opts):
946 949 installnormalfilesmatchfn(repo[None].manifest())
947 950 result = orig(ui, repo, *pats, **opts)
948 951 restorematchfn()
949 952 m = scmutil.match(repo[None], pats, opts)
950 953
951 954 try:
952 955 repo.lfstatus = True
953 956 s = repo.status(match=m, clean=True)
954 957 finally:
955 958 repo.lfstatus = False
956 959 forget = sorted(s.modified + s.added + s.deleted + s.clean)
957 960 forget = [f for f in forget if lfutil.standin(f) in repo[None].manifest()]
958 961
959 962 for f in forget:
960 963 if lfutil.standin(f) not in repo.dirstate and not \
961 964 os.path.isdir(m.rel(lfutil.standin(f))):
962 965 ui.warn(_('not removing %s: file is already untracked\n')
963 966 % m.rel(f))
964 967 result = 1
965 968
966 969 for f in forget:
967 970 if ui.verbose or not m.exact(f):
968 971 ui.status(_('removing %s\n') % m.rel(f))
969 972
970 973 # Need to lock because standin files are deleted then removed from the
971 974 # repository and we could race in-between.
972 975 wlock = repo.wlock()
973 976 try:
974 977 lfdirstate = lfutil.openlfdirstate(ui, repo)
975 978 for f in forget:
976 979 if lfdirstate[f] == 'a':
977 980 lfdirstate.drop(f)
978 981 else:
979 982 lfdirstate.remove(f)
980 983 lfdirstate.write()
981 984 standins = [lfutil.standin(f) for f in forget]
982 985 for f in standins:
983 986 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
984 987 repo[None].forget(standins)
985 988 finally:
986 989 wlock.release()
987 990
988 991 return result
989 992
990 993 def _getoutgoings(repo, other, missing, addfunc):
991 994 """get pairs of filename and largefile hash in outgoing revisions
992 995 in 'missing'.
993 996
994 997 largefiles already existing on 'other' repository are ignored.
995 998
996 999 'addfunc' is invoked with each unique pairs of filename and
997 1000 largefile hash value.
998 1001 """
999 1002 knowns = set()
1000 1003 lfhashes = set()
1001 1004 def dedup(fn, lfhash):
1002 1005 k = (fn, lfhash)
1003 1006 if k not in knowns:
1004 1007 knowns.add(k)
1005 1008 lfhashes.add(lfhash)
1006 1009 lfutil.getlfilestoupload(repo, missing, dedup)
1007 1010 if lfhashes:
1008 1011 lfexists = basestore._openstore(repo, other).exists(lfhashes)
1009 1012 for fn, lfhash in knowns:
1010 1013 if not lfexists[lfhash]: # lfhash doesn't exist on "other"
1011 1014 addfunc(fn, lfhash)
1012 1015
1013 1016 def outgoinghook(ui, repo, other, opts, missing):
1014 1017 if opts.pop('large', None):
1015 1018 lfhashes = set()
1016 1019 if ui.debugflag:
1017 1020 toupload = {}
1018 1021 def addfunc(fn, lfhash):
1019 1022 if fn not in toupload:
1020 1023 toupload[fn] = []
1021 1024 toupload[fn].append(lfhash)
1022 1025 lfhashes.add(lfhash)
1023 1026 def showhashes(fn):
1024 1027 for lfhash in sorted(toupload[fn]):
1025 1028 ui.debug(' %s\n' % (lfhash))
1026 1029 else:
1027 1030 toupload = set()
1028 1031 def addfunc(fn, lfhash):
1029 1032 toupload.add(fn)
1030 1033 lfhashes.add(lfhash)
1031 1034 def showhashes(fn):
1032 1035 pass
1033 1036 _getoutgoings(repo, other, missing, addfunc)
1034 1037
1035 1038 if not toupload:
1036 1039 ui.status(_('largefiles: no files to upload\n'))
1037 1040 else:
1038 1041 ui.status(_('largefiles to upload (%d entities):\n')
1039 1042 % (len(lfhashes)))
1040 1043 for file in sorted(toupload):
1041 1044 ui.status(lfutil.splitstandin(file) + '\n')
1042 1045 showhashes(file)
1043 1046 ui.status('\n')
1044 1047
1045 1048 def summaryremotehook(ui, repo, opts, changes):
1046 1049 largeopt = opts.get('large', False)
1047 1050 if changes is None:
1048 1051 if largeopt:
1049 1052 return (False, True) # only outgoing check is needed
1050 1053 else:
1051 1054 return (False, False)
1052 1055 elif largeopt:
1053 1056 url, branch, peer, outgoing = changes[1]
1054 1057 if peer is None:
1055 1058 # i18n: column positioning for "hg summary"
1056 1059 ui.status(_('largefiles: (no remote repo)\n'))
1057 1060 return
1058 1061
1059 1062 toupload = set()
1060 1063 lfhashes = set()
1061 1064 def addfunc(fn, lfhash):
1062 1065 toupload.add(fn)
1063 1066 lfhashes.add(lfhash)
1064 1067 _getoutgoings(repo, peer, outgoing.missing, addfunc)
1065 1068
1066 1069 if not toupload:
1067 1070 # i18n: column positioning for "hg summary"
1068 1071 ui.status(_('largefiles: (no files to upload)\n'))
1069 1072 else:
1070 1073 # i18n: column positioning for "hg summary"
1071 1074 ui.status(_('largefiles: %d entities for %d files to upload\n')
1072 1075 % (len(lfhashes), len(toupload)))
1073 1076
1074 1077 def overridesummary(orig, ui, repo, *pats, **opts):
1075 1078 try:
1076 1079 repo.lfstatus = True
1077 1080 orig(ui, repo, *pats, **opts)
1078 1081 finally:
1079 1082 repo.lfstatus = False
1080 1083
1081 1084 def scmutiladdremove(orig, repo, pats=[], opts={}, dry_run=None,
1082 1085 similarity=None):
1083 1086 if not lfutil.islfilesrepo(repo):
1084 1087 return orig(repo, pats, opts, dry_run, similarity)
1085 1088 # Get the list of missing largefiles so we can remove them
1086 1089 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1087 1090 unsure, s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [],
1088 1091 False, False, False)
1089 1092
1090 1093 # Call into the normal remove code, but the removing of the standin, we want
1091 1094 # to have handled by original addremove. Monkey patching here makes sure
1092 1095 # we don't remove the standin in the largefiles code, preventing a very
1093 1096 # confused state later.
1094 1097 if s.deleted:
1095 1098 m = [repo.wjoin(f) for f in s.deleted]
1096 1099 removelargefiles(repo.ui, repo, True, *m, **opts)
1097 1100 # Call into the normal add code, and any files that *should* be added as
1098 1101 # largefiles will be
1099 1102 addlargefiles(repo.ui, repo, *pats, **opts)
1100 1103 # Now that we've handled largefiles, hand off to the original addremove
1101 1104 # function to take care of the rest. Make sure it doesn't do anything with
1102 1105 # largefiles by installing a matcher that will ignore them.
1103 1106 installnormalfilesmatchfn(repo[None].manifest())
1104 1107 result = orig(repo, pats, opts, dry_run, similarity)
1105 1108 restorematchfn()
1106 1109 return result
1107 1110
1108 1111 # Calling purge with --all will cause the largefiles to be deleted.
1109 1112 # Override repo.status to prevent this from happening.
1110 1113 def overridepurge(orig, ui, repo, *dirs, **opts):
1111 1114 # XXX large file status is buggy when used on repo proxy.
1112 1115 # XXX this needs to be investigate.
1113 1116 repo = repo.unfiltered()
1114 1117 oldstatus = repo.status
1115 1118 def overridestatus(node1='.', node2=None, match=None, ignored=False,
1116 1119 clean=False, unknown=False, listsubrepos=False):
1117 1120 r = oldstatus(node1, node2, match, ignored, clean, unknown,
1118 1121 listsubrepos)
1119 1122 lfdirstate = lfutil.openlfdirstate(ui, repo)
1120 1123 unknown = [f for f in r.unknown if lfdirstate[f] == '?']
1121 1124 ignored = [f for f in r.ignored if lfdirstate[f] == '?']
1122 1125 return scmutil.status(r.modified, r.added, r.removed, r.deleted,
1123 1126 unknown, ignored, r.clean)
1124 1127 repo.status = overridestatus
1125 1128 orig(ui, repo, *dirs, **opts)
1126 1129 repo.status = oldstatus
1127 1130 def overriderollback(orig, ui, repo, **opts):
1128 1131 wlock = repo.wlock()
1129 1132 try:
1130 1133 before = repo.dirstate.parents()
1131 1134 orphans = set(f for f in repo.dirstate
1132 1135 if lfutil.isstandin(f) and repo.dirstate[f] != 'r')
1133 1136 result = orig(ui, repo, **opts)
1134 1137 after = repo.dirstate.parents()
1135 1138 if before == after:
1136 1139 return result # no need to restore standins
1137 1140
1138 1141 pctx = repo['.']
1139 1142 for f in repo.dirstate:
1140 1143 if lfutil.isstandin(f):
1141 1144 orphans.discard(f)
1142 1145 if repo.dirstate[f] == 'r':
1143 1146 repo.wvfs.unlinkpath(f, ignoremissing=True)
1144 1147 elif f in pctx:
1145 1148 fctx = pctx[f]
1146 1149 repo.wwrite(f, fctx.data(), fctx.flags())
1147 1150 else:
1148 1151 # content of standin is not so important in 'a',
1149 1152 # 'm' or 'n' (coming from the 2nd parent) cases
1150 1153 lfutil.writestandin(repo, f, '', False)
1151 1154 for standin in orphans:
1152 1155 repo.wvfs.unlinkpath(standin, ignoremissing=True)
1153 1156
1154 1157 lfdirstate = lfutil.openlfdirstate(ui, repo)
1155 1158 orphans = set(lfdirstate)
1156 1159 lfiles = lfutil.listlfiles(repo)
1157 1160 for file in lfiles:
1158 1161 lfutil.synclfdirstate(repo, lfdirstate, file, True)
1159 1162 orphans.discard(file)
1160 1163 for lfile in orphans:
1161 1164 lfdirstate.drop(lfile)
1162 1165 lfdirstate.write()
1163 1166 finally:
1164 1167 wlock.release()
1165 1168 return result
1166 1169
1167 1170 def overridetransplant(orig, ui, repo, *revs, **opts):
1168 1171 try:
1169 1172 oldstandins = lfutil.getstandinsstate(repo)
1170 1173 repo._istransplanting = True
1171 1174 result = orig(ui, repo, *revs, **opts)
1172 1175 newstandins = lfutil.getstandinsstate(repo)
1173 1176 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
1174 1177 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1175 1178 printmessage=True)
1176 1179 finally:
1177 1180 repo._istransplanting = False
1178 1181 return result
1179 1182
1180 1183 def overridecat(orig, ui, repo, file1, *pats, **opts):
1181 1184 ctx = scmutil.revsingle(repo, opts.get('rev'))
1182 1185 err = 1
1183 1186 notbad = set()
1184 1187 m = scmutil.match(ctx, (file1,) + pats, opts)
1185 1188 origmatchfn = m.matchfn
1186 1189 def lfmatchfn(f):
1187 1190 if origmatchfn(f):
1188 1191 return True
1189 1192 lf = lfutil.splitstandin(f)
1190 1193 if lf is None:
1191 1194 return False
1192 1195 notbad.add(lf)
1193 1196 return origmatchfn(lf)
1194 1197 m.matchfn = lfmatchfn
1195 1198 origbadfn = m.bad
1196 1199 def lfbadfn(f, msg):
1197 1200 if not f in notbad:
1198 1201 origbadfn(f, msg)
1199 1202 m.bad = lfbadfn
1200 1203 for f in ctx.walk(m):
1201 1204 fp = cmdutil.makefileobj(repo, opts.get('output'), ctx.node(),
1202 1205 pathname=f)
1203 1206 lf = lfutil.splitstandin(f)
1204 1207 if lf is None or origmatchfn(f):
1205 1208 # duplicating unreachable code from commands.cat
1206 1209 data = ctx[f].data()
1207 1210 if opts.get('decode'):
1208 1211 data = repo.wwritedata(f, data)
1209 1212 fp.write(data)
1210 1213 else:
1211 1214 hash = lfutil.readstandin(repo, lf, ctx.rev())
1212 1215 if not lfutil.inusercache(repo.ui, hash):
1213 1216 store = basestore._openstore(repo)
1214 1217 success, missing = store.get([(lf, hash)])
1215 1218 if len(success) != 1:
1216 1219 raise util.Abort(
1217 1220 _('largefile %s is not in cache and could not be '
1218 1221 'downloaded') % lf)
1219 1222 path = lfutil.usercachepath(repo.ui, hash)
1220 1223 fpin = open(path, "rb")
1221 1224 for chunk in util.filechunkiter(fpin, 128 * 1024):
1222 1225 fp.write(chunk)
1223 1226 fpin.close()
1224 1227 fp.close()
1225 1228 err = 0
1226 1229 return err
1227 1230
1228 1231 def mercurialsinkbefore(orig, sink):
1229 1232 sink.repo._isconverting = True
1230 1233 orig(sink)
1231 1234
1232 1235 def mercurialsinkafter(orig, sink):
1233 1236 sink.repo._isconverting = False
1234 1237 orig(sink)
1235 1238
1236 1239 def mergeupdate(orig, repo, node, branchmerge, force, partial,
1237 1240 *args, **kwargs):
1238 1241 wlock = repo.wlock()
1239 1242 try:
1240 1243 # branch | | |
1241 1244 # merge | force | partial | action
1242 1245 # -------+-------+---------+--------------
1243 1246 # x | x | x | linear-merge
1244 1247 # o | x | x | branch-merge
1245 1248 # x | o | x | overwrite (as clean update)
1246 1249 # o | o | x | force-branch-merge (*1)
1247 1250 # x | x | o | (*)
1248 1251 # o | x | o | (*)
1249 1252 # x | o | o | overwrite (as revert)
1250 1253 # o | o | o | (*)
1251 1254 #
1252 1255 # (*) don't care
1253 1256 # (*1) deprecated, but used internally (e.g: "rebase --collapse")
1254 1257
1255 1258 linearmerge = not branchmerge and not force and not partial
1256 1259
1257 1260 if linearmerge or (branchmerge and force and not partial):
1258 1261 # update standins for linear-merge or force-branch-merge,
1259 1262 # because largefiles in the working directory may be modified
1260 1263 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1261 1264 unsure, s = lfdirstate.status(match_.always(repo.root,
1262 1265 repo.getcwd()),
1263 1266 [], False, False, False)
1264 1267 for lfile in unsure + s.modified + s.added:
1265 1268 lfutil.updatestandin(repo, lfutil.standin(lfile))
1266 1269
1267 1270 if linearmerge:
1268 1271 # Only call updatelfiles on the standins that have changed
1269 1272 # to save time
1270 1273 oldstandins = lfutil.getstandinsstate(repo)
1271 1274
1272 1275 result = orig(repo, node, branchmerge, force, partial, *args, **kwargs)
1273 1276
1274 1277 filelist = None
1275 1278 if linearmerge:
1276 1279 newstandins = lfutil.getstandinsstate(repo)
1277 1280 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
1278 1281
1279 1282 # suppress status message while automated committing
1280 1283 printmessage = not (getattr(repo, "_isrebasing", False) or
1281 1284 getattr(repo, "_istransplanting", False))
1282 1285 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1283 1286 printmessage=printmessage,
1284 1287 normallookup=partial)
1285 1288
1286 1289 return result
1287 1290 finally:
1288 1291 wlock.release()
1289 1292
1290 1293 def scmutilmarktouched(orig, repo, files, *args, **kwargs):
1291 1294 result = orig(repo, files, *args, **kwargs)
1292 1295
1293 1296 filelist = [lfutil.splitstandin(f) for f in files if lfutil.isstandin(f)]
1294 1297 if filelist:
1295 1298 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1296 1299 printmessage=False, normallookup=True)
1297 1300
1298 1301 return result
@@ -1,582 +1,599
1 1 This file focuses mainly on updating largefiles in the working
2 2 directory (and ".hg/largefiles/dirstate")
3 3
4 4 $ cat >> $HGRCPATH <<EOF
5 5 > [ui]
6 6 > merge = internal:fail
7 7 > [extensions]
8 8 > largefiles =
9 9 > EOF
10 10
11 11 $ hg init repo
12 12 $ cd repo
13 13
14 14 $ echo large1 > large1
15 15 $ echo large2 > large2
16 16 $ hg add --large large1 large2
17 17 $ echo normal1 > normal1
18 18 $ hg add normal1
19 19 $ hg commit -m '#0'
20 20 $ echo 'large1 in #1' > large1
21 21 $ echo 'normal1 in #1' > normal1
22 22 $ hg commit -m '#1'
23 23 $ hg update -q -C 0
24 24 $ echo 'large2 in #2' > large2
25 25 $ hg commit -m '#2'
26 26 created new head
27 27
28 28 Test that "hg merge" updates largefiles from "other" correctly
29 29
30 30 (getting largefiles from "other" normally)
31 31
32 32 $ hg status -A large1
33 33 C large1
34 34 $ cat large1
35 35 large1
36 36 $ cat .hglf/large1
37 37 4669e532d5b2c093a78eca010077e708a071bb64
38 38 $ hg merge --config debug.dirstate.delaywrite=2
39 39 getting changed largefiles
40 40 1 largefiles updated, 0 removed
41 41 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
42 42 (branch merge, don't forget to commit)
43 43 $ hg status -A large1
44 44 M large1
45 45 $ cat large1
46 46 large1 in #1
47 47 $ cat .hglf/large1
48 48 58e24f733a964da346e2407a2bee99d9001184f5
49 49 $ hg diff -c 1 --nodates .hglf/large1 | grep '^[+-][0-9a-z]'
50 50 -4669e532d5b2c093a78eca010077e708a071bb64
51 51 +58e24f733a964da346e2407a2bee99d9001184f5
52 52
53 53 (getting largefiles from "other" via conflict prompt)
54 54
55 55 $ hg update -q -C 2
56 56 $ echo 'large1 in #3' > large1
57 57 $ echo 'normal1 in #3' > normal1
58 58 $ hg commit -m '#3'
59 59 $ cat .hglf/large1
60 60 e5bb990443d6a92aaf7223813720f7566c9dd05b
61 61 $ hg merge --config debug.dirstate.delaywrite=2 --config ui.interactive=True <<EOF
62 62 > o
63 63 > EOF
64 64 largefile large1 has a merge conflict
65 65 ancestor was 4669e532d5b2c093a78eca010077e708a071bb64
66 66 keep (l)ocal e5bb990443d6a92aaf7223813720f7566c9dd05b or
67 67 take (o)ther 58e24f733a964da346e2407a2bee99d9001184f5? o
68 68 merging normal1
69 69 warning: conflicts during merge.
70 70 merging normal1 incomplete! (edit conflicts, then use 'hg resolve --mark')
71 71 getting changed largefiles
72 72 1 largefiles updated, 0 removed
73 73 0 files updated, 1 files merged, 0 files removed, 1 files unresolved
74 74 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
75 75 [1]
76 76 $ hg status -A large1
77 77 M large1
78 78 $ cat large1
79 79 large1 in #1
80 80 $ cat .hglf/large1
81 81 58e24f733a964da346e2407a2bee99d9001184f5
82 82
83 83 Test that "hg revert -r REV" updates largefiles from "REV" correctly
84 84
85 85 $ hg update -q -C 3
86 86 $ hg status -A large1
87 87 C large1
88 88 $ cat large1
89 89 large1 in #3
90 90 $ cat .hglf/large1
91 91 e5bb990443d6a92aaf7223813720f7566c9dd05b
92 92 $ hg diff -c 1 --nodates .hglf/large1 | grep '^[+-][0-9a-z]'
93 93 -4669e532d5b2c093a78eca010077e708a071bb64
94 94 +58e24f733a964da346e2407a2bee99d9001184f5
95 95 $ hg revert --no-backup -r 1 --config debug.dirstate.delaywrite=2 large1
96 96 $ hg status -A large1
97 97 M large1
98 98 $ cat large1
99 99 large1 in #1
100 100 $ cat .hglf/large1
101 101 58e24f733a964da346e2407a2bee99d9001184f5
102 102
103 103 Test that "hg rollback" restores status of largefiles correctly
104 104
105 105 $ hg update -C -q
106 106 $ hg remove large1
107 107 $ test -f .hglf/large1
108 108 [1]
109 109 $ hg forget large2
110 110 $ test -f .hglf/large2
111 111 [1]
112 112 $ echo largeX > largeX
113 113 $ hg add --large largeX
114 114 $ cat .hglf/largeX
115 115
116 116 $ hg commit -m 'will be rollback-ed soon'
117 117 $ echo largeY > largeY
118 118 $ hg add --large largeY
119 119 #if windows
120 120 $ hg status -A large1
121 121 large1: * (glob)
122 122 #else
123 123 $ hg status -A large1
124 124 large1: No such file or directory
125 125 #endif
126 126 $ hg status -A large2
127 127 ? large2
128 128 $ hg status -A largeX
129 129 C largeX
130 130 $ hg status -A largeY
131 131 A largeY
132 132 $ hg rollback
133 133 repository tip rolled back to revision 3 (undo commit)
134 134 working directory now based on revision 3
135 135 $ hg status -A large1
136 136 R large1
137 137 $ test -f .hglf/large1
138 138 [1]
139 139 $ hg status -A large2
140 140 R large2
141 141 $ test -f .hglf/large2
142 142 [1]
143 143 $ hg status -A largeX
144 144 A largeX
145 145 $ cat .hglf/largeX
146 146
147 147 $ hg status -A largeY
148 148 ? largeY
149 149 $ test -f .hglf/largeY
150 150 [1]
151 151
152 152 Test that "hg rollback" restores standins correctly
153 153
154 154 $ hg commit -m 'will be rollback-ed soon'
155 155 $ hg update -q -C 2
156 156 $ cat large1
157 157 large1
158 158 $ cat .hglf/large1
159 159 4669e532d5b2c093a78eca010077e708a071bb64
160 160 $ cat large2
161 161 large2 in #2
162 162 $ cat .hglf/large2
163 163 3cfce6277e7668985707b6887ce56f9f62f6ccd9
164 164
165 165 $ hg rollback -q -f
166 166 $ cat large1
167 167 large1
168 168 $ cat .hglf/large1
169 169 4669e532d5b2c093a78eca010077e708a071bb64
170 170 $ cat large2
171 171 large2 in #2
172 172 $ cat .hglf/large2
173 173 3cfce6277e7668985707b6887ce56f9f62f6ccd9
174 174
175 175 (rollback the parent of the working directory, when the parent of it
176 176 is not branch-tip)
177 177
178 178 $ hg update -q -C 1
179 179 $ cat .hglf/large1
180 180 58e24f733a964da346e2407a2bee99d9001184f5
181 181 $ cat .hglf/large2
182 182 1deebade43c8c498a3c8daddac0244dc55d1331d
183 183
184 184 $ echo normalX > normalX
185 185 $ hg add normalX
186 186 $ hg commit -m 'will be rollback-ed soon'
187 187 $ hg rollback -q
188 188
189 189 $ cat .hglf/large1
190 190 58e24f733a964da346e2407a2bee99d9001184f5
191 191 $ cat .hglf/large2
192 192 1deebade43c8c498a3c8daddac0244dc55d1331d
193 193
194 194 Test that "hg status" shows status of largefiles correctly just after
195 195 automated commit like rebase/transplant
196 196
197 197 $ cat >> .hg/hgrc <<EOF
198 198 > [extensions]
199 199 > rebase =
200 200 > strip =
201 201 > transplant =
202 202 > EOF
203 203 $ hg update -q -C 1
204 204 $ hg remove large1
205 205 $ echo largeX > largeX
206 206 $ hg add --large largeX
207 207 $ hg commit -m '#4'
208 208
209 209 $ hg rebase -s 1 -d 2 --keep
210 210 #if windows
211 211 $ hg status -A large1
212 212 large1: * (glob)
213 213 #else
214 214 $ hg status -A large1
215 215 large1: No such file or directory
216 216 #endif
217 217 $ hg status -A largeX
218 218 C largeX
219 219 $ hg strip -q 5
220 220
221 221 $ hg update -q -C 2
222 222 $ hg transplant -q 1 4
223 223 #if windows
224 224 $ hg status -A large1
225 225 large1: * (glob)
226 226 #else
227 227 $ hg status -A large1
228 228 large1: No such file or directory
229 229 #endif
230 230 $ hg status -A largeX
231 231 C largeX
232 232 $ hg strip -q 5
233 233
234 234 $ hg update -q -C 2
235 235 $ hg transplant -q --merge 1 --merge 4
236 236 #if windows
237 237 $ hg status -A large1
238 238 large1: * (glob)
239 239 #else
240 240 $ hg status -A large1
241 241 large1: No such file or directory
242 242 #endif
243 243 $ hg status -A largeX
244 244 C largeX
245 245 $ hg strip -q 5
246 246
247 247 Test that linear merge can detect modification (and conflict) correctly
248 248
249 249 (linear merge without conflict)
250 250
251 251 $ echo 'large2 for linear merge (no conflict)' > large2
252 252 $ hg update 3 --config debug.dirstate.delaywrite=2
253 253 getting changed largefiles
254 254 1 largefiles updated, 0 removed
255 255 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
256 256 $ hg status -A large2
257 257 M large2
258 258 $ cat large2
259 259 large2 for linear merge (no conflict)
260 260 $ cat .hglf/large2
261 261 9c4bf8f1b33536d6e5f89447e10620cfe52ea710
262 262
263 263 (linear merge with conflict, choosing "other")
264 264
265 265 $ hg update -q -C 2
266 266 $ echo 'large1 for linear merge (conflict)' > large1
267 267 $ hg update 3 --config ui.interactive=True <<EOF
268 268 > o
269 269 > EOF
270 270 largefile large1 has a merge conflict
271 271 ancestor was 4669e532d5b2c093a78eca010077e708a071bb64
272 272 keep (l)ocal ba94c2efe5b7c5e0af8d189295ce00553b0612b7 or
273 273 take (o)ther e5bb990443d6a92aaf7223813720f7566c9dd05b? o
274 274 getting changed largefiles
275 275 1 largefiles updated, 0 removed
276 276 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
277 277 $ hg status -A large1
278 278 C large1
279 279 $ cat large1
280 280 large1 in #3
281 281 $ cat .hglf/large1
282 282 e5bb990443d6a92aaf7223813720f7566c9dd05b
283 283
284 284 (linear merge with conflict, choosing "local")
285 285
286 286 $ hg update -q -C 2
287 287 $ echo 'large1 for linear merge (conflict)' > large1
288 288 $ hg update 3 --config debug.dirstate.delaywrite=2
289 289 largefile large1 has a merge conflict
290 290 ancestor was 4669e532d5b2c093a78eca010077e708a071bb64
291 291 keep (l)ocal ba94c2efe5b7c5e0af8d189295ce00553b0612b7 or
292 292 take (o)ther e5bb990443d6a92aaf7223813720f7566c9dd05b? l
293 293 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
294 294 $ hg status -A large1
295 295 M large1
296 296 $ cat large1
297 297 large1 for linear merge (conflict)
298 298 $ cat .hglf/large1
299 299 ba94c2efe5b7c5e0af8d189295ce00553b0612b7
300 300
301 301 Test a linear merge to a revision containing same-name normal file
302 302
303 303 $ hg update -q -C 3
304 304 $ hg remove large2
305 305 $ echo 'large2 as normal file' > large2
306 306 $ hg add large2
307 307 $ echo 'large3 as normal file' > large3
308 308 $ hg add large3
309 309 $ hg commit -m '#5'
310 310 $ hg manifest
311 311 .hglf/large1
312 312 large2
313 313 large3
314 314 normal1
315 315
316 316 (modified largefile is already switched to normal)
317 317
318 318 $ hg update -q -C 2
319 319 $ echo 'modified large2 for linear merge' > large2
320 320 $ hg update -q 5
321 321 local changed .hglf/large2 which remote deleted
322 322 use (c)hanged version or (d)elete? c
323 323 remote turned local largefile large2 into a normal file
324 324 keep (l)argefile or use (n)ormal file? l
325 325 $ hg debugdirstate --nodates | grep large2
326 326 a 0 -1 .hglf/large2
327 327 r 0 0 large2
328 328 $ hg status -A large2
329 329 A large2
330 330 $ cat large2
331 331 modified large2 for linear merge
332 332
333 333 (added largefile is already committed as normal)
334 334
335 335 $ hg update -q -C 2
336 336 $ echo 'large3 as large file for linear merge' > large3
337 337 $ hg add --large large3
338 338 $ hg update -q 5
339 339 remote turned local largefile large3 into a normal file
340 340 keep (l)argefile or use (n)ormal file? l
341 341 $ hg debugdirstate --nodates | grep large3
342 342 a 0 -1 .hglf/large3
343 343 r 0 0 large3
344 344 $ hg status -A large3
345 345 A large3
346 346 $ cat large3
347 347 large3 as large file for linear merge
348 348 $ rm -f large3 .hglf/large3
349 349
350 350 Test that the internal linear merging works correctly
351 351 (both heads are stripped to keep pairing of revision number and commit log)
352 352
353 353 $ hg update -q -C 2
354 354 $ hg strip 3 4
355 355 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/9530e27857f7-backup.hg (glob)
356 356 $ mv .hg/strip-backup/9530e27857f7-backup.hg $TESTTMP
357 357
358 358 (internal linear merging at "hg pull --update")
359 359
360 360 $ echo 'large1 for linear merge (conflict)' > large1
361 361 $ echo 'large2 for linear merge (conflict with normal file)' > large2
362 362 $ hg pull --update --config debug.dirstate.delaywrite=2 $TESTTMP/9530e27857f7-backup.hg
363 363 pulling from $TESTTMP/9530e27857f7-backup.hg (glob)
364 364 searching for changes
365 365 adding changesets
366 366 adding manifests
367 367 adding file changes
368 368 added 3 changesets with 5 changes to 5 files
369 369 local changed .hglf/large2 which remote deleted
370 370 use (c)hanged version or (d)elete? c
371 371 remote turned local largefile large2 into a normal file
372 372 keep (l)argefile or use (n)ormal file? l
373 373 largefile large1 has a merge conflict
374 374 ancestor was 4669e532d5b2c093a78eca010077e708a071bb64
375 375 keep (l)ocal ba94c2efe5b7c5e0af8d189295ce00553b0612b7 or
376 376 take (o)ther e5bb990443d6a92aaf7223813720f7566c9dd05b? l
377 377 2 files updated, 1 files merged, 0 files removed, 0 files unresolved
378 378
379 379 $ hg status -A large1
380 380 M large1
381 381 $ cat large1
382 382 large1 for linear merge (conflict)
383 383 $ cat .hglf/large1
384 384 ba94c2efe5b7c5e0af8d189295ce00553b0612b7
385 385 $ hg status -A large2
386 386 A large2
387 387 $ cat large2
388 388 large2 for linear merge (conflict with normal file)
389 389 $ cat .hglf/large2
390 390 d7591fe9be0f6227d90bddf3e4f52ff41fc1f544
391 391
392 392 (internal linear merging at "hg unbundle --update")
393 393
394 394 $ hg update -q -C 2
395 395 $ hg rollback -q
396 396
397 397 $ echo 'large1 for linear merge (conflict)' > large1
398 398 $ echo 'large2 for linear merge (conflict with normal file)' > large2
399 399 $ hg unbundle --update --config debug.dirstate.delaywrite=2 $TESTTMP/9530e27857f7-backup.hg
400 400 adding changesets
401 401 adding manifests
402 402 adding file changes
403 403 added 3 changesets with 5 changes to 5 files
404 404 local changed .hglf/large2 which remote deleted
405 405 use (c)hanged version or (d)elete? c
406 406 remote turned local largefile large2 into a normal file
407 407 keep (l)argefile or use (n)ormal file? l
408 408 largefile large1 has a merge conflict
409 409 ancestor was 4669e532d5b2c093a78eca010077e708a071bb64
410 410 keep (l)ocal ba94c2efe5b7c5e0af8d189295ce00553b0612b7 or
411 411 take (o)ther e5bb990443d6a92aaf7223813720f7566c9dd05b? l
412 412 2 files updated, 1 files merged, 0 files removed, 0 files unresolved
413 413
414 414 $ hg status -A large1
415 415 M large1
416 416 $ cat large1
417 417 large1 for linear merge (conflict)
418 418 $ cat .hglf/large1
419 419 ba94c2efe5b7c5e0af8d189295ce00553b0612b7
420 420 $ hg status -A large2
421 421 A large2
422 422 $ cat large2
423 423 large2 for linear merge (conflict with normal file)
424 424 $ cat .hglf/large2
425 425 d7591fe9be0f6227d90bddf3e4f52ff41fc1f544
426 426
427 427 (internal linear merging in subrepo at "hg update")
428 428
429 429 $ cd ..
430 430 $ hg init subparent
431 431 $ cd subparent
432 432
433 433 $ hg clone -q -u 2 ../repo sub
434 434 $ cat > .hgsub <<EOF
435 435 > sub = sub
436 436 > EOF
437 437 $ hg add .hgsub
438 438 $ hg commit -m '#0@parent'
439 439 $ cat .hgsubstate
440 440 f74e50bd9e5594b7cf1e6c5cbab86ddd25f3ca2f sub
441 441 $ hg -R sub update -q
442 442 $ hg commit -m '#1@parent'
443 443 $ cat .hgsubstate
444 444 d65e59e952a9638e2ce863b41a420ca723dd3e8d sub
445 445 $ hg update -q 0
446 446
447 447 $ echo 'large1 for linear merge (conflict)' > sub/large1
448 448 $ echo 'large2 for linear merge (conflict with normal file)' > sub/large2
449 449 $ hg update --config ui.interactive=True --config debug.dirstate.delaywrite=2 <<EOF
450 450 > m
451 451 > r
452 452 > c
453 453 > l
454 454 > l
455 455 > EOF
456 456 subrepository sub diverged (local revision: f74e50bd9e55, remote revision: d65e59e952a9)
457 457 (M)erge, keep (l)ocal or keep (r)emote? m
458 458 subrepository sources for sub differ (in checked out version)
459 459 use (l)ocal source (f74e50bd9e55) or (r)emote source (d65e59e952a9)? r
460 460 local changed .hglf/large2 which remote deleted
461 461 use (c)hanged version or (d)elete? c
462 462 remote turned local largefile large2 into a normal file
463 463 keep (l)argefile or use (n)ormal file? l
464 464 largefile large1 has a merge conflict
465 465 ancestor was 4669e532d5b2c093a78eca010077e708a071bb64
466 466 keep (l)ocal ba94c2efe5b7c5e0af8d189295ce00553b0612b7 or
467 467 take (o)ther e5bb990443d6a92aaf7223813720f7566c9dd05b? l
468 468 2 files updated, 1 files merged, 0 files removed, 0 files unresolved
469 469 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
470 470
471 471 $ hg -R sub status -A sub/large1
472 472 M sub/large1
473 473 $ cat sub/large1
474 474 large1 for linear merge (conflict)
475 475 $ cat sub/.hglf/large1
476 476 ba94c2efe5b7c5e0af8d189295ce00553b0612b7
477 477 $ hg -R sub status -A sub/large2
478 478 A sub/large2
479 479 $ cat sub/large2
480 480 large2 for linear merge (conflict with normal file)
481 481 $ cat sub/.hglf/large2
482 482 d7591fe9be0f6227d90bddf3e4f52ff41fc1f544
483 483
484 484 $ cd ..
485 485 $ cd repo
486 486
487 487 Test that rebase updates largefiles in the working directory even if
488 488 it is aborted by conflict.
489 489
490 490 $ hg update -q -C 3
491 491 $ cat .hglf/large1
492 492 e5bb990443d6a92aaf7223813720f7566c9dd05b
493 493 $ cat large1
494 494 large1 in #3
495 495 $ hg rebase -s 1 -d 3 --keep --config ui.interactive=True <<EOF
496 496 > o
497 497 > EOF
498 498 largefile large1 has a merge conflict
499 499 ancestor was 4669e532d5b2c093a78eca010077e708a071bb64
500 500 keep (l)ocal e5bb990443d6a92aaf7223813720f7566c9dd05b or
501 501 take (o)ther 58e24f733a964da346e2407a2bee99d9001184f5? o
502 502 merging normal1
503 503 warning: conflicts during merge.
504 504 merging normal1 incomplete! (edit conflicts, then use 'hg resolve --mark')
505 505 unresolved conflicts (see hg resolve, then hg rebase --continue)
506 506 [1]
507 507 $ cat .hglf/large1
508 508 58e24f733a964da346e2407a2bee99d9001184f5
509 509 $ cat large1
510 510 large1 in #1
511 511
512 $ hg rebase -q --abort
513 rebase aborted
512 Test that rebase updates standins for manually modified largefiles at
513 the 1st commit of resuming.
514
515 $ echo "manually modified before 'hg rebase --continue'" > large1
516 $ hg resolve -m normal1
517 (no more unresolved files)
518 $ hg rebase --continue --config ui.interactive=True <<EOF
519 > c
520 > EOF
521 local changed .hglf/large1 which remote deleted
522 use (c)hanged version or (d)elete? c
523
524 $ hg diff -c "tip~1" --nodates .hglf/large1 | grep '^[+-][0-9a-z]'
525 -e5bb990443d6a92aaf7223813720f7566c9dd05b
526 +8a4f783556e7dea21139ca0466eafce954c75c13
527 $ rm -f large1
528 $ hg update -q -C tip
529 $ cat large1
530 manually modified before 'hg rebase --continue'
514 531
515 532 Test that transplant updates largefiles, of which standins are safely
516 533 changed, even if it is aborted by conflict of other.
517 534
518 535 $ hg update -q -C 5
519 536 $ cat .hglf/large1
520 537 e5bb990443d6a92aaf7223813720f7566c9dd05b
521 538 $ cat large1
522 539 large1 in #3
523 540 $ hg diff -c 4 .hglf/largeX | grep '^[+-][0-9a-z]'
524 541 +fa44618ea25181aff4f48b70428294790cec9f61
525 542 $ hg transplant 4
526 543 applying 07d6153b5c04
527 544 patching file .hglf/large1
528 545 Hunk #1 FAILED at 0
529 546 1 out of 1 hunks FAILED -- saving rejects to file .hglf/large1.rej
530 547 patch failed to apply
531 548 abort: fix up the merge and run hg transplant --continue
532 549 [255]
533 550 $ hg status -A large1
534 551 C large1
535 552 $ cat .hglf/large1
536 553 e5bb990443d6a92aaf7223813720f7566c9dd05b
537 554 $ cat large1
538 555 large1 in #3
539 556 $ hg status -A largeX
540 557 A largeX
541 558 $ cat .hglf/largeX
542 559 fa44618ea25181aff4f48b70428294790cec9f61
543 560 $ cat largeX
544 561 largeX
545 562
546 563 Test that "hg status" doesn't show removal of largefiles not managed
547 564 in the target context.
548 565
549 566 $ hg update -q -C 4
550 567 $ hg remove largeX
551 568 $ hg status -A largeX
552 569 R largeX
553 570 $ hg status -A --rev '.^1' largeX
554 571
555 572 #if execbit
556 573
557 574 Test that "hg status" against revisions other than parent notices exec
558 575 bit changes of largefiles.
559 576
560 577 $ hg update -q -C 4
561 578
562 579 (the case that large2 doesn't have exec bit in the target context but
563 580 in the working context)
564 581
565 582 $ chmod +x large2
566 583 $ hg status -A --rev 0 large2
567 584 M large2
568 585 $ hg commit -m 'chmod +x large2'
569 586
570 587 (the case that large2 has exec bit in the target context but not in
571 588 the working context)
572 589
573 590 $ echo dummy > dummy
574 591 $ hg add dummy
575 592 $ hg commit -m 'revision for separation'
576 593 $ chmod -x large2
577 594 $ hg status -A --rev '.^1' large2
578 595 M large2
579 596
580 597 #endif
581 598
582 599 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now