##// END OF EJS Templates
merge: make all action tuples have the same length - keep args as tuple
Mads Kiilerich -
r18540:139529b0 default
parent child Browse files
Show More
@@ -1,1178 +1,1178 b''
1 1 # Copyright 2009-2010 Gregory P. Ward
2 2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
3 3 # Copyright 2010-2011 Fog Creek Software
4 4 # Copyright 2010-2011 Unity Technologies
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 '''Overridden Mercurial commands and functions for the largefiles extension'''
10 10
11 11 import os
12 12 import copy
13 13
14 14 from mercurial import hg, commands, util, cmdutil, scmutil, match as match_, \
15 15 node, archival, error, merge, discovery
16 16 from mercurial.i18n import _
17 17 from mercurial.node import hex
18 18 from hgext import rebase
19 19
20 20 import lfutil
21 21 import lfcommands
22 22
23 23 # -- Utility functions: commonly/repeatedly needed functionality ---------------
24 24
25 25 def installnormalfilesmatchfn(manifest):
26 26 '''overrides scmutil.match so that the matcher it returns will ignore all
27 27 largefiles'''
28 28 oldmatch = None # for the closure
29 29 def overridematch(ctx, pats=[], opts={}, globbed=False,
30 30 default='relpath'):
31 31 match = oldmatch(ctx, pats, opts, globbed, default)
32 32 m = copy.copy(match)
33 33 notlfile = lambda f: not (lfutil.isstandin(f) or lfutil.standin(f) in
34 34 manifest)
35 35 m._files = filter(notlfile, m._files)
36 36 m._fmap = set(m._files)
37 37 origmatchfn = m.matchfn
38 38 m.matchfn = lambda f: notlfile(f) and origmatchfn(f) or None
39 39 return m
40 40 oldmatch = installmatchfn(overridematch)
41 41
42 42 def installmatchfn(f):
43 43 oldmatch = scmutil.match
44 44 setattr(f, 'oldmatch', oldmatch)
45 45 scmutil.match = f
46 46 return oldmatch
47 47
48 48 def restorematchfn():
49 49 '''restores scmutil.match to what it was before installnormalfilesmatchfn
50 50 was called. no-op if scmutil.match is its original function.
51 51
52 52 Note that n calls to installnormalfilesmatchfn will require n calls to
53 53 restore matchfn to reverse'''
54 54 scmutil.match = getattr(scmutil.match, 'oldmatch', scmutil.match)
55 55
56 56 def addlargefiles(ui, repo, *pats, **opts):
57 57 large = opts.pop('large', None)
58 58 lfsize = lfutil.getminsize(
59 59 ui, lfutil.islfilesrepo(repo), opts.pop('lfsize', None))
60 60
61 61 lfmatcher = None
62 62 if lfutil.islfilesrepo(repo):
63 63 lfpats = ui.configlist(lfutil.longname, 'patterns', default=[])
64 64 if lfpats:
65 65 lfmatcher = match_.match(repo.root, '', list(lfpats))
66 66
67 67 lfnames = []
68 68 m = scmutil.match(repo[None], pats, opts)
69 69 m.bad = lambda x, y: None
70 70 wctx = repo[None]
71 71 for f in repo.walk(m):
72 72 exact = m.exact(f)
73 73 lfile = lfutil.standin(f) in wctx
74 74 nfile = f in wctx
75 75 exists = lfile or nfile
76 76
77 77 # Don't warn the user when they attempt to add a normal tracked file.
78 78 # The normal add code will do that for us.
79 79 if exact and exists:
80 80 if lfile:
81 81 ui.warn(_('%s already a largefile\n') % f)
82 82 continue
83 83
84 84 if (exact or not exists) and not lfutil.isstandin(f):
85 85 wfile = repo.wjoin(f)
86 86
87 87 # In case the file was removed previously, but not committed
88 88 # (issue3507)
89 89 if not os.path.exists(wfile):
90 90 continue
91 91
92 92 abovemin = (lfsize and
93 93 os.lstat(wfile).st_size >= lfsize * 1024 * 1024)
94 94 if large or abovemin or (lfmatcher and lfmatcher(f)):
95 95 lfnames.append(f)
96 96 if ui.verbose or not exact:
97 97 ui.status(_('adding %s as a largefile\n') % m.rel(f))
98 98
99 99 bad = []
100 100 standins = []
101 101
102 102 # Need to lock, otherwise there could be a race condition between
103 103 # when standins are created and added to the repo.
104 104 wlock = repo.wlock()
105 105 try:
106 106 if not opts.get('dry_run'):
107 107 lfdirstate = lfutil.openlfdirstate(ui, repo)
108 108 for f in lfnames:
109 109 standinname = lfutil.standin(f)
110 110 lfutil.writestandin(repo, standinname, hash='',
111 111 executable=lfutil.getexecutable(repo.wjoin(f)))
112 112 standins.append(standinname)
113 113 if lfdirstate[f] == 'r':
114 114 lfdirstate.normallookup(f)
115 115 else:
116 116 lfdirstate.add(f)
117 117 lfdirstate.write()
118 118 bad += [lfutil.splitstandin(f)
119 119 for f in repo[None].add(standins)
120 120 if f in m.files()]
121 121 finally:
122 122 wlock.release()
123 123 return bad
124 124
125 125 def removelargefiles(ui, repo, *pats, **opts):
126 126 after = opts.get('after')
127 127 if not pats and not after:
128 128 raise util.Abort(_('no files specified'))
129 129 m = scmutil.match(repo[None], pats, opts)
130 130 try:
131 131 repo.lfstatus = True
132 132 s = repo.status(match=m, clean=True)
133 133 finally:
134 134 repo.lfstatus = False
135 135 manifest = repo[None].manifest()
136 136 modified, added, deleted, clean = [[f for f in list
137 137 if lfutil.standin(f) in manifest]
138 138 for list in [s[0], s[1], s[3], s[6]]]
139 139
140 140 def warn(files, msg):
141 141 for f in files:
142 142 ui.warn(msg % m.rel(f))
143 143 return int(len(files) > 0)
144 144
145 145 result = 0
146 146
147 147 if after:
148 148 remove, forget = deleted, []
149 149 result = warn(modified + added + clean,
150 150 _('not removing %s: file still exists\n'))
151 151 else:
152 152 remove, forget = deleted + clean, []
153 153 result = warn(modified, _('not removing %s: file is modified (use -f'
154 154 ' to force removal)\n'))
155 155 result = warn(added, _('not removing %s: file has been marked for add'
156 156 ' (use forget to undo)\n')) or result
157 157
158 158 for f in sorted(remove + forget):
159 159 if ui.verbose or not m.exact(f):
160 160 ui.status(_('removing %s\n') % m.rel(f))
161 161
162 162 # Need to lock because standin files are deleted then removed from the
163 163 # repository and we could race in-between.
164 164 wlock = repo.wlock()
165 165 try:
166 166 lfdirstate = lfutil.openlfdirstate(ui, repo)
167 167 for f in remove:
168 168 if not after:
169 169 # If this is being called by addremove, notify the user that we
170 170 # are removing the file.
171 171 if getattr(repo, "_isaddremove", False):
172 172 ui.status(_('removing %s\n') % f)
173 173 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
174 174 lfdirstate.remove(f)
175 175 lfdirstate.write()
176 176 forget = [lfutil.standin(f) for f in forget]
177 177 remove = [lfutil.standin(f) for f in remove]
178 178 repo[None].forget(forget)
179 179 # If this is being called by addremove, let the original addremove
180 180 # function handle this.
181 181 if not getattr(repo, "_isaddremove", False):
182 182 for f in remove:
183 183 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
184 184 repo[None].forget(remove)
185 185 finally:
186 186 wlock.release()
187 187
188 188 return result
189 189
190 190 # For overriding mercurial.hgweb.webcommands so that largefiles will
191 191 # appear at their right place in the manifests.
192 192 def decodepath(orig, path):
193 193 return lfutil.splitstandin(path) or path
194 194
195 195 # -- Wrappers: modify existing commands --------------------------------
196 196
197 197 # Add works by going through the files that the user wanted to add and
198 198 # checking if they should be added as largefiles. Then it makes a new
199 199 # matcher which matches only the normal files and runs the original
200 200 # version of add.
201 201 def overrideadd(orig, ui, repo, *pats, **opts):
202 202 normal = opts.pop('normal')
203 203 if normal:
204 204 if opts.get('large'):
205 205 raise util.Abort(_('--normal cannot be used with --large'))
206 206 return orig(ui, repo, *pats, **opts)
207 207 bad = addlargefiles(ui, repo, *pats, **opts)
208 208 installnormalfilesmatchfn(repo[None].manifest())
209 209 result = orig(ui, repo, *pats, **opts)
210 210 restorematchfn()
211 211
212 212 return (result == 1 or bad) and 1 or 0
213 213
214 214 def overrideremove(orig, ui, repo, *pats, **opts):
215 215 installnormalfilesmatchfn(repo[None].manifest())
216 216 result = orig(ui, repo, *pats, **opts)
217 217 restorematchfn()
218 218 return removelargefiles(ui, repo, *pats, **opts) or result
219 219
220 220 def overridestatusfn(orig, repo, rev2, **opts):
221 221 try:
222 222 repo._repo.lfstatus = True
223 223 return orig(repo, rev2, **opts)
224 224 finally:
225 225 repo._repo.lfstatus = False
226 226
227 227 def overridestatus(orig, ui, repo, *pats, **opts):
228 228 try:
229 229 repo.lfstatus = True
230 230 return orig(ui, repo, *pats, **opts)
231 231 finally:
232 232 repo.lfstatus = False
233 233
234 234 def overridedirty(orig, repo, ignoreupdate=False):
235 235 try:
236 236 repo._repo.lfstatus = True
237 237 return orig(repo, ignoreupdate)
238 238 finally:
239 239 repo._repo.lfstatus = False
240 240
241 241 def overridelog(orig, ui, repo, *pats, **opts):
242 242 def overridematch(ctx, pats=[], opts={}, globbed=False,
243 243 default='relpath'):
244 244 """Matcher that merges root directory with .hglf, suitable for log.
245 245 It is still possible to match .hglf directly.
246 246 For any listed files run log on the standin too.
247 247 matchfn tries both the given filename and with .hglf stripped.
248 248 """
249 249 match = oldmatch(ctx, pats, opts, globbed, default)
250 250 m = copy.copy(match)
251 251 standins = [lfutil.standin(f) for f in m._files]
252 252 m._files.extend(standins)
253 253 m._fmap = set(m._files)
254 254 origmatchfn = m.matchfn
255 255 def lfmatchfn(f):
256 256 lf = lfutil.splitstandin(f)
257 257 if lf is not None and origmatchfn(lf):
258 258 return True
259 259 r = origmatchfn(f)
260 260 return r
261 261 m.matchfn = lfmatchfn
262 262 return m
263 263 oldmatch = installmatchfn(overridematch)
264 264 try:
265 265 repo.lfstatus = True
266 266 return orig(ui, repo, *pats, **opts)
267 267 finally:
268 268 repo.lfstatus = False
269 269 restorematchfn()
270 270
271 271 def overrideverify(orig, ui, repo, *pats, **opts):
272 272 large = opts.pop('large', False)
273 273 all = opts.pop('lfa', False)
274 274 contents = opts.pop('lfc', False)
275 275
276 276 result = orig(ui, repo, *pats, **opts)
277 277 if large:
278 278 result = result or lfcommands.verifylfiles(ui, repo, all, contents)
279 279 return result
280 280
281 281 def overridedebugstate(orig, ui, repo, *pats, **opts):
282 282 large = opts.pop('large', False)
283 283 if large:
284 284 lfcommands.debugdirstate(ui, repo)
285 285 else:
286 286 orig(ui, repo, *pats, **opts)
287 287
288 288 # Override needs to refresh standins so that update's normal merge
289 289 # will go through properly. Then the other update hook (overriding repo.update)
290 290 # will get the new files. Filemerge is also overridden so that the merge
291 291 # will merge standins correctly.
292 292 def overrideupdate(orig, ui, repo, *pats, **opts):
293 293 lfdirstate = lfutil.openlfdirstate(ui, repo)
294 294 s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [], False,
295 295 False, False)
296 296 (unsure, modified, added, removed, missing, unknown, ignored, clean) = s
297 297
298 298 # Need to lock between the standins getting updated and their
299 299 # largefiles getting updated
300 300 wlock = repo.wlock()
301 301 try:
302 302 if opts['check']:
303 303 mod = len(modified) > 0
304 304 for lfile in unsure:
305 305 standin = lfutil.standin(lfile)
306 306 if repo['.'][standin].data().strip() != \
307 307 lfutil.hashfile(repo.wjoin(lfile)):
308 308 mod = True
309 309 else:
310 310 lfdirstate.normal(lfile)
311 311 lfdirstate.write()
312 312 if mod:
313 313 raise util.Abort(_('uncommitted local changes'))
314 314 # XXX handle removed differently
315 315 if not opts['clean']:
316 316 for lfile in unsure + modified + added:
317 317 lfutil.updatestandin(repo, lfutil.standin(lfile))
318 318 finally:
319 319 wlock.release()
320 320 return orig(ui, repo, *pats, **opts)
321 321
322 322 # Before starting the manifest merge, merge.updates will call
323 323 # _checkunknown to check if there are any files in the merged-in
324 324 # changeset that collide with unknown files in the working copy.
325 325 #
326 326 # The largefiles are seen as unknown, so this prevents us from merging
327 327 # in a file 'foo' if we already have a largefile with the same name.
328 328 #
329 329 # The overridden function filters the unknown files by removing any
330 330 # largefiles. This makes the merge proceed and we can then handle this
331 331 # case further in the overridden manifestmerge function below.
332 332 def overridecheckunknownfile(origfn, repo, wctx, mctx, f):
333 333 if lfutil.standin(f) in wctx:
334 334 return False
335 335 return origfn(repo, wctx, mctx, f)
336 336
337 337 # The manifest merge handles conflicts on the manifest level. We want
338 338 # to handle changes in largefile-ness of files at this level too.
339 339 #
340 340 # The strategy is to run the original manifestmerge and then process
341 341 # the action list it outputs. There are two cases we need to deal with:
342 342 #
343 343 # 1. Normal file in p1, largefile in p2. Here the largefile is
344 344 # detected via its standin file, which will enter the working copy
345 345 # with a "get" action. It is not "merge" since the standin is all
346 346 # Mercurial is concerned with at this level -- the link to the
347 347 # existing normal file is not relevant here.
348 348 #
349 349 # 2. Largefile in p1, normal file in p2. Here we get a "merge" action
350 350 # since the largefile will be present in the working copy and
351 351 # different from the normal file in p2. Mercurial therefore
352 352 # triggers a merge action.
353 353 #
354 354 # In both cases, we prompt the user and emit new actions to either
355 355 # remove the standin (if the normal file was kept) or to remove the
356 356 # normal file and get the standin (if the largefile was kept). The
357 357 # default prompt answer is to use the largefile version since it was
358 358 # presumably changed on purpose.
359 359 #
360 360 # Finally, the merge.applyupdates function will then take care of
361 361 # writing the files into the working copy and lfcommands.updatelfiles
362 362 # will update the largefiles.
363 363 def overridemanifestmerge(origfn, repo, p1, p2, pa, overwrite, partial):
364 364 actions = origfn(repo, p1, p2, pa, overwrite, partial)
365 365 processed = []
366 366
367 367 for action in actions:
368 368 if overwrite:
369 369 processed.append(action)
370 370 continue
371 f, m = action[:2]
371 f, m, args = action
372 372
373 373 choices = (_('&Largefile'), _('&Normal file'))
374 374 if m == "g" and lfutil.splitstandin(f) in p1 and f in p2:
375 375 # Case 1: normal file in the working copy, largefile in
376 376 # the second parent
377 377 lfile = lfutil.splitstandin(f)
378 378 standin = f
379 379 msg = _('%s has been turned into a largefile\n'
380 380 'use (l)argefile or keep as (n)ormal file?') % lfile
381 381 if repo.ui.promptchoice(msg, choices, 0) == 0:
382 processed.append((lfile, "r"))
383 processed.append((standin, "g", p2.flags(standin)))
382 processed.append((lfile, "r", None))
383 processed.append((standin, "g", (p2.flags(standin),)))
384 384 else:
385 processed.append((standin, "r"))
385 processed.append((standin, "r", None))
386 386 elif m == "g" and lfutil.standin(f) in p1 and f in p2:
387 387 # Case 2: largefile in the working copy, normal file in
388 388 # the second parent
389 389 standin = lfutil.standin(f)
390 390 lfile = f
391 391 msg = _('%s has been turned into a normal file\n'
392 392 'keep as (l)argefile or use (n)ormal file?') % lfile
393 393 if repo.ui.promptchoice(msg, choices, 0) == 0:
394 processed.append((lfile, "r"))
394 processed.append((lfile, "r", None))
395 395 else:
396 processed.append((standin, "r"))
397 processed.append((lfile, "g", p2.flags(lfile)))
396 processed.append((standin, "r", None))
397 processed.append((lfile, "g", (p2.flags(lfile),)))
398 398 else:
399 399 processed.append(action)
400 400
401 401 return processed
402 402
403 403 # Override filemerge to prompt the user about how they wish to merge
404 404 # largefiles. This will handle identical edits, and copy/rename +
405 405 # edit without prompting the user.
406 406 def overridefilemerge(origfn, repo, mynode, orig, fcd, fco, fca):
407 407 # Use better variable names here. Because this is a wrapper we cannot
408 408 # change the variable names in the function declaration.
409 409 fcdest, fcother, fcancestor = fcd, fco, fca
410 410 if not lfutil.isstandin(orig):
411 411 return origfn(repo, mynode, orig, fcdest, fcother, fcancestor)
412 412 else:
413 413 if not fcother.cmp(fcdest): # files identical?
414 414 return None
415 415
416 416 # backwards, use working dir parent as ancestor
417 417 if fcancestor == fcother:
418 418 fcancestor = fcdest.parents()[0]
419 419
420 420 if orig != fcother.path():
421 421 repo.ui.status(_('merging %s and %s to %s\n')
422 422 % (lfutil.splitstandin(orig),
423 423 lfutil.splitstandin(fcother.path()),
424 424 lfutil.splitstandin(fcdest.path())))
425 425 else:
426 426 repo.ui.status(_('merging %s\n')
427 427 % lfutil.splitstandin(fcdest.path()))
428 428
429 429 if fcancestor.path() != fcother.path() and fcother.data() == \
430 430 fcancestor.data():
431 431 return 0
432 432 if fcancestor.path() != fcdest.path() and fcdest.data() == \
433 433 fcancestor.data():
434 434 repo.wwrite(fcdest.path(), fcother.data(), fcother.flags())
435 435 return 0
436 436
437 437 if repo.ui.promptchoice(_('largefile %s has a merge conflict\n'
438 438 'keep (l)ocal or take (o)ther?') %
439 439 lfutil.splitstandin(orig),
440 440 (_('&Local'), _('&Other')), 0) == 0:
441 441 return 0
442 442 else:
443 443 repo.wwrite(fcdest.path(), fcother.data(), fcother.flags())
444 444 return 0
445 445
446 446 # Copy first changes the matchers to match standins instead of
447 447 # largefiles. Then it overrides util.copyfile in that function it
448 448 # checks if the destination largefile already exists. It also keeps a
449 449 # list of copied files so that the largefiles can be copied and the
450 450 # dirstate updated.
451 451 def overridecopy(orig, ui, repo, pats, opts, rename=False):
452 452 # doesn't remove largefile on rename
453 453 if len(pats) < 2:
454 454 # this isn't legal, let the original function deal with it
455 455 return orig(ui, repo, pats, opts, rename)
456 456
457 457 def makestandin(relpath):
458 458 path = scmutil.canonpath(repo.root, repo.getcwd(), relpath)
459 459 return os.path.join(repo.wjoin(lfutil.standin(path)))
460 460
461 461 fullpats = scmutil.expandpats(pats)
462 462 dest = fullpats[-1]
463 463
464 464 if os.path.isdir(dest):
465 465 if not os.path.isdir(makestandin(dest)):
466 466 os.makedirs(makestandin(dest))
467 467 # This could copy both lfiles and normal files in one command,
468 468 # but we don't want to do that. First replace their matcher to
469 469 # only match normal files and run it, then replace it to just
470 470 # match largefiles and run it again.
471 471 nonormalfiles = False
472 472 nolfiles = False
473 473 try:
474 474 try:
475 475 installnormalfilesmatchfn(repo[None].manifest())
476 476 result = orig(ui, repo, pats, opts, rename)
477 477 except util.Abort, e:
478 478 if str(e) != _('no files to copy'):
479 479 raise e
480 480 else:
481 481 nonormalfiles = True
482 482 result = 0
483 483 finally:
484 484 restorematchfn()
485 485
486 486 # The first rename can cause our current working directory to be removed.
487 487 # In that case there is nothing left to copy/rename so just quit.
488 488 try:
489 489 repo.getcwd()
490 490 except OSError:
491 491 return result
492 492
493 493 try:
494 494 try:
495 495 # When we call orig below it creates the standins but we don't add
496 496 # them to the dir state until later so lock during that time.
497 497 wlock = repo.wlock()
498 498
499 499 manifest = repo[None].manifest()
500 500 oldmatch = None # for the closure
501 501 def overridematch(ctx, pats=[], opts={}, globbed=False,
502 502 default='relpath'):
503 503 newpats = []
504 504 # The patterns were previously mangled to add the standin
505 505 # directory; we need to remove that now
506 506 for pat in pats:
507 507 if match_.patkind(pat) is None and lfutil.shortname in pat:
508 508 newpats.append(pat.replace(lfutil.shortname, ''))
509 509 else:
510 510 newpats.append(pat)
511 511 match = oldmatch(ctx, newpats, opts, globbed, default)
512 512 m = copy.copy(match)
513 513 lfile = lambda f: lfutil.standin(f) in manifest
514 514 m._files = [lfutil.standin(f) for f in m._files if lfile(f)]
515 515 m._fmap = set(m._files)
516 516 origmatchfn = m.matchfn
517 517 m.matchfn = lambda f: (lfutil.isstandin(f) and
518 518 (f in manifest) and
519 519 origmatchfn(lfutil.splitstandin(f)) or
520 520 None)
521 521 return m
522 522 oldmatch = installmatchfn(overridematch)
523 523 listpats = []
524 524 for pat in pats:
525 525 if match_.patkind(pat) is not None:
526 526 listpats.append(pat)
527 527 else:
528 528 listpats.append(makestandin(pat))
529 529
530 530 try:
531 531 origcopyfile = util.copyfile
532 532 copiedfiles = []
533 533 def overridecopyfile(src, dest):
534 534 if (lfutil.shortname in src and
535 535 dest.startswith(repo.wjoin(lfutil.shortname))):
536 536 destlfile = dest.replace(lfutil.shortname, '')
537 537 if not opts['force'] and os.path.exists(destlfile):
538 538 raise IOError('',
539 539 _('destination largefile already exists'))
540 540 copiedfiles.append((src, dest))
541 541 origcopyfile(src, dest)
542 542
543 543 util.copyfile = overridecopyfile
544 544 result += orig(ui, repo, listpats, opts, rename)
545 545 finally:
546 546 util.copyfile = origcopyfile
547 547
548 548 lfdirstate = lfutil.openlfdirstate(ui, repo)
549 549 for (src, dest) in copiedfiles:
550 550 if (lfutil.shortname in src and
551 551 dest.startswith(repo.wjoin(lfutil.shortname))):
552 552 srclfile = src.replace(repo.wjoin(lfutil.standin('')), '')
553 553 destlfile = dest.replace(repo.wjoin(lfutil.standin('')), '')
554 554 destlfiledir = os.path.dirname(repo.wjoin(destlfile)) or '.'
555 555 if not os.path.isdir(destlfiledir):
556 556 os.makedirs(destlfiledir)
557 557 if rename:
558 558 os.rename(repo.wjoin(srclfile), repo.wjoin(destlfile))
559 559 lfdirstate.remove(srclfile)
560 560 else:
561 561 util.copyfile(repo.wjoin(srclfile),
562 562 repo.wjoin(destlfile))
563 563
564 564 lfdirstate.add(destlfile)
565 565 lfdirstate.write()
566 566 except util.Abort, e:
567 567 if str(e) != _('no files to copy'):
568 568 raise e
569 569 else:
570 570 nolfiles = True
571 571 finally:
572 572 restorematchfn()
573 573 wlock.release()
574 574
575 575 if nolfiles and nonormalfiles:
576 576 raise util.Abort(_('no files to copy'))
577 577
578 578 return result
579 579
580 580 # When the user calls revert, we have to be careful to not revert any
581 581 # changes to other largefiles accidentally. This means we have to keep
582 582 # track of the largefiles that are being reverted so we only pull down
583 583 # the necessary largefiles.
584 584 #
585 585 # Standins are only updated (to match the hash of largefiles) before
586 586 # commits. Update the standins then run the original revert, changing
587 587 # the matcher to hit standins instead of largefiles. Based on the
588 588 # resulting standins update the largefiles. Then return the standins
589 589 # to their proper state
590 590 def overriderevert(orig, ui, repo, *pats, **opts):
591 591 # Because we put the standins in a bad state (by updating them)
592 592 # and then return them to a correct state we need to lock to
593 593 # prevent others from changing them in their incorrect state.
594 594 wlock = repo.wlock()
595 595 try:
596 596 lfdirstate = lfutil.openlfdirstate(ui, repo)
597 597 (modified, added, removed, missing, unknown, ignored, clean) = \
598 598 lfutil.lfdirstatestatus(lfdirstate, repo, repo['.'].rev())
599 599 lfdirstate.write()
600 600 for lfile in modified:
601 601 lfutil.updatestandin(repo, lfutil.standin(lfile))
602 602 for lfile in missing:
603 603 if (os.path.exists(repo.wjoin(lfutil.standin(lfile)))):
604 604 os.unlink(repo.wjoin(lfutil.standin(lfile)))
605 605
606 606 try:
607 607 ctx = scmutil.revsingle(repo, opts.get('rev'))
608 608 oldmatch = None # for the closure
609 609 def overridematch(ctx, pats=[], opts={}, globbed=False,
610 610 default='relpath'):
611 611 match = oldmatch(ctx, pats, opts, globbed, default)
612 612 m = copy.copy(match)
613 613 def tostandin(f):
614 614 if lfutil.standin(f) in ctx:
615 615 return lfutil.standin(f)
616 616 elif lfutil.standin(f) in repo[None]:
617 617 return None
618 618 return f
619 619 m._files = [tostandin(f) for f in m._files]
620 620 m._files = [f for f in m._files if f is not None]
621 621 m._fmap = set(m._files)
622 622 origmatchfn = m.matchfn
623 623 def matchfn(f):
624 624 if lfutil.isstandin(f):
625 625 # We need to keep track of what largefiles are being
626 626 # matched so we know which ones to update later --
627 627 # otherwise we accidentally revert changes to other
628 628 # largefiles. This is repo-specific, so duckpunch the
629 629 # repo object to keep the list of largefiles for us
630 630 # later.
631 631 if origmatchfn(lfutil.splitstandin(f)) and \
632 632 (f in repo[None] or f in ctx):
633 633 lfileslist = getattr(repo, '_lfilestoupdate', [])
634 634 lfileslist.append(lfutil.splitstandin(f))
635 635 repo._lfilestoupdate = lfileslist
636 636 return True
637 637 else:
638 638 return False
639 639 return origmatchfn(f)
640 640 m.matchfn = matchfn
641 641 return m
642 642 oldmatch = installmatchfn(overridematch)
643 643 scmutil.match
644 644 matches = overridematch(repo[None], pats, opts)
645 645 orig(ui, repo, *pats, **opts)
646 646 finally:
647 647 restorematchfn()
648 648 lfileslist = getattr(repo, '_lfilestoupdate', [])
649 649 lfcommands.updatelfiles(ui, repo, filelist=lfileslist,
650 650 printmessage=False)
651 651
652 652 # empty out the largefiles list so we start fresh next time
653 653 repo._lfilestoupdate = []
654 654 for lfile in modified:
655 655 if lfile in lfileslist:
656 656 if os.path.exists(repo.wjoin(lfutil.standin(lfile))) and lfile\
657 657 in repo['.']:
658 658 lfutil.writestandin(repo, lfutil.standin(lfile),
659 659 repo['.'][lfile].data().strip(),
660 660 'x' in repo['.'][lfile].flags())
661 661 lfdirstate = lfutil.openlfdirstate(ui, repo)
662 662 for lfile in added:
663 663 standin = lfutil.standin(lfile)
664 664 if standin not in ctx and (standin in matches or opts.get('all')):
665 665 if lfile in lfdirstate:
666 666 lfdirstate.drop(lfile)
667 667 util.unlinkpath(repo.wjoin(standin))
668 668 lfdirstate.write()
669 669 finally:
670 670 wlock.release()
671 671
672 672 def hgupdaterepo(orig, repo, node, overwrite):
673 673 if not overwrite:
674 674 # Only call updatelfiles on the standins that have changed to save time
675 675 oldstandins = lfutil.getstandinsstate(repo)
676 676
677 677 result = orig(repo, node, overwrite)
678 678
679 679 filelist = None
680 680 if not overwrite:
681 681 newstandins = lfutil.getstandinsstate(repo)
682 682 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
683 683 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist)
684 684 return result
685 685
686 686 def hgmerge(orig, repo, node, force=None, remind=True):
687 687 # Mark the repo as being in the middle of a merge, so that
688 688 # updatelfiles() will know that it needs to trust the standins in
689 689 # the working copy, not in the standins in the current node
690 690 repo._ismerging = True
691 691 try:
692 692 result = orig(repo, node, force, remind)
693 693 lfcommands.updatelfiles(repo.ui, repo)
694 694 finally:
695 695 repo._ismerging = False
696 696 return result
697 697
698 698 # When we rebase a repository with remotely changed largefiles, we need to
699 699 # take some extra care so that the largefiles are correctly updated in the
700 700 # working copy
701 701 def overridepull(orig, ui, repo, source=None, **opts):
702 702 revsprepull = len(repo)
703 703 if opts.get('rebase', False):
704 704 repo._isrebasing = True
705 705 try:
706 706 if opts.get('update'):
707 707 del opts['update']
708 708 ui.debug('--update and --rebase are not compatible, ignoring '
709 709 'the update flag\n')
710 710 del opts['rebase']
711 711 cmdutil.bailifchanged(repo)
712 712 origpostincoming = commands.postincoming
713 713 def _dummy(*args, **kwargs):
714 714 pass
715 715 commands.postincoming = _dummy
716 716 if not source:
717 717 source = 'default'
718 718 repo.lfpullsource = source
719 719 try:
720 720 result = commands.pull(ui, repo, source, **opts)
721 721 finally:
722 722 commands.postincoming = origpostincoming
723 723 revspostpull = len(repo)
724 724 if revspostpull > revsprepull:
725 725 result = result or rebase.rebase(ui, repo)
726 726 finally:
727 727 repo._isrebasing = False
728 728 else:
729 729 if not source:
730 730 source = 'default'
731 731 repo.lfpullsource = source
732 732 oldheads = lfutil.getcurrentheads(repo)
733 733 result = orig(ui, repo, source, **opts)
734 734 # If we do not have the new largefiles for any new heads we pulled, we
735 735 # will run into a problem later if we try to merge or rebase with one of
736 736 # these heads, so cache the largefiles now directly into the system
737 737 # cache.
738 738 ui.status(_("caching new largefiles\n"))
739 739 numcached = 0
740 740 heads = lfutil.getcurrentheads(repo)
741 741 newheads = set(heads).difference(set(oldheads))
742 742 for head in newheads:
743 743 (cached, missing) = lfcommands.cachelfiles(ui, repo, head)
744 744 numcached += len(cached)
745 745 ui.status(_("%d largefiles cached\n") % numcached)
746 746 if opts.get('all_largefiles'):
747 747 revspostpull = len(repo)
748 748 revs = []
749 749 for rev in xrange(revsprepull + 1, revspostpull):
750 750 revs.append(repo[rev].rev())
751 751 lfcommands.downloadlfiles(ui, repo, revs)
752 752 return result
753 753
754 754 def overrideclone(orig, ui, source, dest=None, **opts):
755 755 d = dest
756 756 if d is None:
757 757 d = hg.defaultdest(source)
758 758 if opts.get('all_largefiles') and not hg.islocal(d):
759 759 raise util.Abort(_(
760 760 '--all-largefiles is incompatible with non-local destination %s' %
761 761 d))
762 762
763 763 return orig(ui, source, dest, **opts)
764 764
765 765 def hgclone(orig, ui, opts, *args, **kwargs):
766 766 result = orig(ui, opts, *args, **kwargs)
767 767
768 768 if result is not None:
769 769 sourcerepo, destrepo = result
770 770 repo = destrepo.local()
771 771
772 772 # The .hglf directory must exist for the standin matcher to match
773 773 # anything (which listlfiles uses for each rev), and .hg/largefiles is
774 774 # assumed to exist by the code that caches the downloaded file. These
775 775 # directories exist if clone updated to any rev. (If the repo does not
776 776 # have largefiles, download never gets to the point of needing
777 777 # .hg/largefiles, and the standin matcher won't match anything anyway.)
778 778 if 'largefiles' in repo.requirements:
779 779 if opts.get('noupdate'):
780 780 util.makedirs(repo.wjoin(lfutil.shortname))
781 781 util.makedirs(repo.join(lfutil.longname))
782 782
783 783 # Caching is implicitly limited to 'rev' option, since the dest repo was
784 784 # truncated at that point. The user may expect a download count with
785 785 # this option, so attempt whether or not this is a largefile repo.
786 786 if opts.get('all_largefiles'):
787 787 success, missing = lfcommands.downloadlfiles(ui, repo, None)
788 788
789 789 if missing != 0:
790 790 return None
791 791
792 792 return result
793 793
794 794 def overriderebase(orig, ui, repo, **opts):
795 795 repo._isrebasing = True
796 796 try:
797 797 return orig(ui, repo, **opts)
798 798 finally:
799 799 repo._isrebasing = False
800 800
801 801 def overridearchive(orig, repo, dest, node, kind, decode=True, matchfn=None,
802 802 prefix=None, mtime=None, subrepos=None):
803 803 # No need to lock because we are only reading history and
804 804 # largefile caches, neither of which are modified.
805 805 lfcommands.cachelfiles(repo.ui, repo, node)
806 806
807 807 if kind not in archival.archivers:
808 808 raise util.Abort(_("unknown archive type '%s'") % kind)
809 809
810 810 ctx = repo[node]
811 811
812 812 if kind == 'files':
813 813 if prefix:
814 814 raise util.Abort(
815 815 _('cannot give prefix when archiving to files'))
816 816 else:
817 817 prefix = archival.tidyprefix(dest, kind, prefix)
818 818
819 819 def write(name, mode, islink, getdata):
820 820 if matchfn and not matchfn(name):
821 821 return
822 822 data = getdata()
823 823 if decode:
824 824 data = repo.wwritedata(name, data)
825 825 archiver.addfile(prefix + name, mode, islink, data)
826 826
827 827 archiver = archival.archivers[kind](dest, mtime or ctx.date()[0])
828 828
829 829 if repo.ui.configbool("ui", "archivemeta", True):
830 830 def metadata():
831 831 base = 'repo: %s\nnode: %s\nbranch: %s\n' % (
832 832 hex(repo.changelog.node(0)), hex(node), ctx.branch())
833 833
834 834 tags = ''.join('tag: %s\n' % t for t in ctx.tags()
835 835 if repo.tagtype(t) == 'global')
836 836 if not tags:
837 837 repo.ui.pushbuffer()
838 838 opts = {'template': '{latesttag}\n{latesttagdistance}',
839 839 'style': '', 'patch': None, 'git': None}
840 840 cmdutil.show_changeset(repo.ui, repo, opts).show(ctx)
841 841 ltags, dist = repo.ui.popbuffer().split('\n')
842 842 tags = ''.join('latesttag: %s\n' % t for t in ltags.split(':'))
843 843 tags += 'latesttagdistance: %s\n' % dist
844 844
845 845 return base + tags
846 846
847 847 write('.hg_archival.txt', 0644, False, metadata)
848 848
849 849 for f in ctx:
850 850 ff = ctx.flags(f)
851 851 getdata = ctx[f].data
852 852 if lfutil.isstandin(f):
853 853 path = lfutil.findfile(repo, getdata().strip())
854 854 if path is None:
855 855 raise util.Abort(
856 856 _('largefile %s not found in repo store or system cache')
857 857 % lfutil.splitstandin(f))
858 858 f = lfutil.splitstandin(f)
859 859
860 860 def getdatafn():
861 861 fd = None
862 862 try:
863 863 fd = open(path, 'rb')
864 864 return fd.read()
865 865 finally:
866 866 if fd:
867 867 fd.close()
868 868
869 869 getdata = getdatafn
870 870 write(f, 'x' in ff and 0755 or 0644, 'l' in ff, getdata)
871 871
872 872 if subrepos:
873 873 for subpath in sorted(ctx.substate):
874 874 sub = ctx.sub(subpath)
875 875 submatch = match_.narrowmatcher(subpath, matchfn)
876 876 sub.archive(repo.ui, archiver, prefix, submatch)
877 877
878 878 archiver.done()
879 879
880 880 def hgsubrepoarchive(orig, repo, ui, archiver, prefix, match=None):
881 881 repo._get(repo._state + ('hg',))
882 882 rev = repo._state[1]
883 883 ctx = repo._repo[rev]
884 884
885 885 lfcommands.cachelfiles(ui, repo._repo, ctx.node())
886 886
887 887 def write(name, mode, islink, getdata):
888 888 # At this point, the standin has been replaced with the largefile name,
889 889 # so the normal matcher works here without the lfutil variants.
890 890 if match and not match(f):
891 891 return
892 892 data = getdata()
893 893
894 894 archiver.addfile(prefix + repo._path + '/' + name, mode, islink, data)
895 895
896 896 for f in ctx:
897 897 ff = ctx.flags(f)
898 898 getdata = ctx[f].data
899 899 if lfutil.isstandin(f):
900 900 path = lfutil.findfile(repo._repo, getdata().strip())
901 901 if path is None:
902 902 raise util.Abort(
903 903 _('largefile %s not found in repo store or system cache')
904 904 % lfutil.splitstandin(f))
905 905 f = lfutil.splitstandin(f)
906 906
907 907 def getdatafn():
908 908 fd = None
909 909 try:
910 910 fd = open(os.path.join(prefix, path), 'rb')
911 911 return fd.read()
912 912 finally:
913 913 if fd:
914 914 fd.close()
915 915
916 916 getdata = getdatafn
917 917
918 918 write(f, 'x' in ff and 0755 or 0644, 'l' in ff, getdata)
919 919
920 920 for subpath in sorted(ctx.substate):
921 921 sub = ctx.sub(subpath)
922 922 submatch = match_.narrowmatcher(subpath, match)
923 923 sub.archive(ui, archiver, os.path.join(prefix, repo._path) + '/',
924 924 submatch)
925 925
926 926 # If a largefile is modified, the change is not reflected in its
927 927 # standin until a commit. cmdutil.bailifchanged() raises an exception
928 928 # if the repo has uncommitted changes. Wrap it to also check if
929 929 # largefiles were changed. This is used by bisect and backout.
930 930 def overridebailifchanged(orig, repo):
931 931 orig(repo)
932 932 repo.lfstatus = True
933 933 modified, added, removed, deleted = repo.status()[:4]
934 934 repo.lfstatus = False
935 935 if modified or added or removed or deleted:
936 936 raise util.Abort(_('outstanding uncommitted changes'))
937 937
938 938 # Fetch doesn't use cmdutil.bailifchanged so override it to add the check
939 939 def overridefetch(orig, ui, repo, *pats, **opts):
940 940 repo.lfstatus = True
941 941 modified, added, removed, deleted = repo.status()[:4]
942 942 repo.lfstatus = False
943 943 if modified or added or removed or deleted:
944 944 raise util.Abort(_('outstanding uncommitted changes'))
945 945 return orig(ui, repo, *pats, **opts)
946 946
947 947 def overrideforget(orig, ui, repo, *pats, **opts):
948 948 installnormalfilesmatchfn(repo[None].manifest())
949 949 result = orig(ui, repo, *pats, **opts)
950 950 restorematchfn()
951 951 m = scmutil.match(repo[None], pats, opts)
952 952
953 953 try:
954 954 repo.lfstatus = True
955 955 s = repo.status(match=m, clean=True)
956 956 finally:
957 957 repo.lfstatus = False
958 958 forget = sorted(s[0] + s[1] + s[3] + s[6])
959 959 forget = [f for f in forget if lfutil.standin(f) in repo[None].manifest()]
960 960
961 961 for f in forget:
962 962 if lfutil.standin(f) not in repo.dirstate and not \
963 963 os.path.isdir(m.rel(lfutil.standin(f))):
964 964 ui.warn(_('not removing %s: file is already untracked\n')
965 965 % m.rel(f))
966 966 result = 1
967 967
968 968 for f in forget:
969 969 if ui.verbose or not m.exact(f):
970 970 ui.status(_('removing %s\n') % m.rel(f))
971 971
972 972 # Need to lock because standin files are deleted then removed from the
973 973 # repository and we could race in-between.
974 974 wlock = repo.wlock()
975 975 try:
976 976 lfdirstate = lfutil.openlfdirstate(ui, repo)
977 977 for f in forget:
978 978 if lfdirstate[f] == 'a':
979 979 lfdirstate.drop(f)
980 980 else:
981 981 lfdirstate.remove(f)
982 982 lfdirstate.write()
983 983 standins = [lfutil.standin(f) for f in forget]
984 984 for f in standins:
985 985 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
986 986 repo[None].forget(standins)
987 987 finally:
988 988 wlock.release()
989 989
990 990 return result
991 991
992 992 def getoutgoinglfiles(ui, repo, dest=None, **opts):
993 993 dest = ui.expandpath(dest or 'default-push', dest or 'default')
994 994 dest, branches = hg.parseurl(dest, opts.get('branch'))
995 995 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
996 996 if revs:
997 997 revs = [repo.lookup(rev) for rev in scmutil.revrange(repo, revs)]
998 998
999 999 try:
1000 1000 remote = hg.peer(repo, opts, dest)
1001 1001 except error.RepoError:
1002 1002 return None
1003 1003 outgoing = discovery.findcommonoutgoing(repo, remote.peer(), force=False)
1004 1004 if not outgoing.missing:
1005 1005 return outgoing.missing
1006 1006 o = repo.changelog.nodesbetween(outgoing.missing, revs)[0]
1007 1007 if opts.get('newest_first'):
1008 1008 o.reverse()
1009 1009
1010 1010 toupload = set()
1011 1011 for n in o:
1012 1012 parents = [p for p in repo.changelog.parents(n) if p != node.nullid]
1013 1013 ctx = repo[n]
1014 1014 files = set(ctx.files())
1015 1015 if len(parents) == 2:
1016 1016 mc = ctx.manifest()
1017 1017 mp1 = ctx.parents()[0].manifest()
1018 1018 mp2 = ctx.parents()[1].manifest()
1019 1019 for f in mp1:
1020 1020 if f not in mc:
1021 1021 files.add(f)
1022 1022 for f in mp2:
1023 1023 if f not in mc:
1024 1024 files.add(f)
1025 1025 for f in mc:
1026 1026 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
1027 1027 files.add(f)
1028 1028 toupload = toupload.union(
1029 1029 set([f for f in files if lfutil.isstandin(f) and f in ctx]))
1030 1030 return sorted(toupload)
1031 1031
1032 1032 def overrideoutgoing(orig, ui, repo, dest=None, **opts):
1033 1033 result = orig(ui, repo, dest, **opts)
1034 1034
1035 1035 if opts.pop('large', None):
1036 1036 toupload = getoutgoinglfiles(ui, repo, dest, **opts)
1037 1037 if toupload is None:
1038 1038 ui.status(_('largefiles: No remote repo\n'))
1039 1039 elif not toupload:
1040 1040 ui.status(_('largefiles: no files to upload\n'))
1041 1041 else:
1042 1042 ui.status(_('largefiles to upload:\n'))
1043 1043 for file in toupload:
1044 1044 ui.status(lfutil.splitstandin(file) + '\n')
1045 1045 ui.status('\n')
1046 1046
1047 1047 return result
1048 1048
1049 1049 def overridesummary(orig, ui, repo, *pats, **opts):
1050 1050 try:
1051 1051 repo.lfstatus = True
1052 1052 orig(ui, repo, *pats, **opts)
1053 1053 finally:
1054 1054 repo.lfstatus = False
1055 1055
1056 1056 if opts.pop('large', None):
1057 1057 toupload = getoutgoinglfiles(ui, repo, None, **opts)
1058 1058 if toupload is None:
1059 1059 # i18n: column positioning for "hg summary"
1060 1060 ui.status(_('largefiles: (no remote repo)\n'))
1061 1061 elif not toupload:
1062 1062 # i18n: column positioning for "hg summary"
1063 1063 ui.status(_('largefiles: (no files to upload)\n'))
1064 1064 else:
1065 1065 # i18n: column positioning for "hg summary"
1066 1066 ui.status(_('largefiles: %d to upload\n') % len(toupload))
1067 1067
1068 1068 def scmutiladdremove(orig, repo, pats=[], opts={}, dry_run=None,
1069 1069 similarity=None):
1070 1070 if not lfutil.islfilesrepo(repo):
1071 1071 return orig(repo, pats, opts, dry_run, similarity)
1072 1072 # Get the list of missing largefiles so we can remove them
1073 1073 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1074 1074 s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [], False,
1075 1075 False, False)
1076 1076 (unsure, modified, added, removed, missing, unknown, ignored, clean) = s
1077 1077
1078 1078 # Call into the normal remove code, but the removing of the standin, we want
1079 1079 # to have handled by original addremove. Monkey patching here makes sure
1080 1080 # we don't remove the standin in the largefiles code, preventing a very
1081 1081 # confused state later.
1082 1082 if missing:
1083 1083 m = [repo.wjoin(f) for f in missing]
1084 1084 repo._isaddremove = True
1085 1085 removelargefiles(repo.ui, repo, *m, **opts)
1086 1086 repo._isaddremove = False
1087 1087 # Call into the normal add code, and any files that *should* be added as
1088 1088 # largefiles will be
1089 1089 addlargefiles(repo.ui, repo, *pats, **opts)
1090 1090 # Now that we've handled largefiles, hand off to the original addremove
1091 1091 # function to take care of the rest. Make sure it doesn't do anything with
1092 1092 # largefiles by installing a matcher that will ignore them.
1093 1093 installnormalfilesmatchfn(repo[None].manifest())
1094 1094 result = orig(repo, pats, opts, dry_run, similarity)
1095 1095 restorematchfn()
1096 1096 return result
1097 1097
1098 1098 # Calling purge with --all will cause the largefiles to be deleted.
1099 1099 # Override repo.status to prevent this from happening.
1100 1100 def overridepurge(orig, ui, repo, *dirs, **opts):
1101 1101 # XXX large file status is buggy when used on repo proxy.
1102 1102 # XXX this needs to be investigate.
1103 1103 repo = repo.unfiltered()
1104 1104 oldstatus = repo.status
1105 1105 def overridestatus(node1='.', node2=None, match=None, ignored=False,
1106 1106 clean=False, unknown=False, listsubrepos=False):
1107 1107 r = oldstatus(node1, node2, match, ignored, clean, unknown,
1108 1108 listsubrepos)
1109 1109 lfdirstate = lfutil.openlfdirstate(ui, repo)
1110 1110 modified, added, removed, deleted, unknown, ignored, clean = r
1111 1111 unknown = [f for f in unknown if lfdirstate[f] == '?']
1112 1112 ignored = [f for f in ignored if lfdirstate[f] == '?']
1113 1113 return modified, added, removed, deleted, unknown, ignored, clean
1114 1114 repo.status = overridestatus
1115 1115 orig(ui, repo, *dirs, **opts)
1116 1116 repo.status = oldstatus
1117 1117
1118 1118 def overriderollback(orig, ui, repo, **opts):
1119 1119 result = orig(ui, repo, **opts)
1120 1120 merge.update(repo, node=None, branchmerge=False, force=True,
1121 1121 partial=lfutil.isstandin)
1122 1122 wlock = repo.wlock()
1123 1123 try:
1124 1124 lfdirstate = lfutil.openlfdirstate(ui, repo)
1125 1125 lfiles = lfutil.listlfiles(repo)
1126 1126 oldlfiles = lfutil.listlfiles(repo, repo[None].parents()[0].rev())
1127 1127 for file in lfiles:
1128 1128 if file in oldlfiles:
1129 1129 lfdirstate.normallookup(file)
1130 1130 else:
1131 1131 lfdirstate.add(file)
1132 1132 lfdirstate.write()
1133 1133 finally:
1134 1134 wlock.release()
1135 1135 return result
1136 1136
1137 1137 def overridetransplant(orig, ui, repo, *revs, **opts):
1138 1138 try:
1139 1139 oldstandins = lfutil.getstandinsstate(repo)
1140 1140 repo._istransplanting = True
1141 1141 result = orig(ui, repo, *revs, **opts)
1142 1142 newstandins = lfutil.getstandinsstate(repo)
1143 1143 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
1144 1144 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1145 1145 printmessage=True)
1146 1146 finally:
1147 1147 repo._istransplanting = False
1148 1148 return result
1149 1149
1150 1150 def overridecat(orig, ui, repo, file1, *pats, **opts):
1151 1151 ctx = scmutil.revsingle(repo, opts.get('rev'))
1152 1152 err = 1
1153 1153 notbad = set()
1154 1154 m = scmutil.match(ctx, (file1,) + pats, opts)
1155 1155 origmatchfn = m.matchfn
1156 1156 def lfmatchfn(f):
1157 1157 lf = lfutil.splitstandin(f)
1158 1158 if lf is None:
1159 1159 return origmatchfn(f)
1160 1160 notbad.add(lf)
1161 1161 return origmatchfn(lf)
1162 1162 m.matchfn = lfmatchfn
1163 1163 m.bad = lambda f, msg: f not in notbad
1164 1164 for f in ctx.walk(m):
1165 1165 lf = lfutil.splitstandin(f)
1166 1166 if lf is None:
1167 1167 err = orig(ui, repo, f, **opts)
1168 1168 else:
1169 1169 err = lfcommands.catlfile(repo, lf, ctx.rev(), opts.get('output'))
1170 1170 return err
1171 1171
1172 1172 def mercurialsinkbefore(orig, sink):
1173 1173 sink.repo._isconverting = True
1174 1174 orig(sink)
1175 1175
1176 1176 def mercurialsinkafter(orig, sink):
1177 1177 sink.repo._isconverting = False
1178 1178 orig(sink)
@@ -1,649 +1,649 b''
1 1 # merge.py - directory-level update/merge handling for Mercurial
2 2 #
3 3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import nullid, nullrev, hex, bin
9 9 from i18n import _
10 10 import error, util, filemerge, copies, subrepo
11 11 import errno, os, shutil
12 12
13 13 class mergestate(object):
14 14 '''track 3-way merge state of individual files'''
15 15 def __init__(self, repo):
16 16 self._repo = repo
17 17 self._dirty = False
18 18 self._read()
19 19 def reset(self, node=None):
20 20 self._state = {}
21 21 if node:
22 22 self._local = node
23 23 shutil.rmtree(self._repo.join("merge"), True)
24 24 self._dirty = False
25 25 def _read(self):
26 26 self._state = {}
27 27 try:
28 28 f = self._repo.opener("merge/state")
29 29 for i, l in enumerate(f):
30 30 if i == 0:
31 31 self._local = bin(l[:-1])
32 32 else:
33 33 bits = l[:-1].split("\0")
34 34 self._state[bits[0]] = bits[1:]
35 35 f.close()
36 36 except IOError, err:
37 37 if err.errno != errno.ENOENT:
38 38 raise
39 39 self._dirty = False
40 40 def commit(self):
41 41 if self._dirty:
42 42 f = self._repo.opener("merge/state", "w")
43 43 f.write(hex(self._local) + "\n")
44 44 for d, v in self._state.iteritems():
45 45 f.write("\0".join([d] + v) + "\n")
46 46 f.close()
47 47 self._dirty = False
48 48 def add(self, fcl, fco, fca, fd):
49 49 hash = util.sha1(fcl.path()).hexdigest()
50 50 self._repo.opener.write("merge/" + hash, fcl.data())
51 51 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
52 52 hex(fca.filenode()), fco.path(), fcl.flags()]
53 53 self._dirty = True
54 54 def __contains__(self, dfile):
55 55 return dfile in self._state
56 56 def __getitem__(self, dfile):
57 57 return self._state[dfile][0]
58 58 def __iter__(self):
59 59 l = self._state.keys()
60 60 l.sort()
61 61 for f in l:
62 62 yield f
63 63 def mark(self, dfile, state):
64 64 self._state[dfile][0] = state
65 65 self._dirty = True
66 66 def resolve(self, dfile, wctx, octx):
67 67 if self[dfile] == 'r':
68 68 return 0
69 69 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
70 70 fcd = wctx[dfile]
71 71 fco = octx[ofile]
72 72 fca = self._repo.filectx(afile, fileid=anode)
73 73 # "premerge" x flags
74 74 flo = fco.flags()
75 75 fla = fca.flags()
76 76 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
77 77 if fca.node() == nullid:
78 78 self._repo.ui.warn(_('warning: cannot merge flags for %s\n') %
79 79 afile)
80 80 elif flags == fla:
81 81 flags = flo
82 82 # restore local
83 83 f = self._repo.opener("merge/" + hash)
84 84 self._repo.wwrite(dfile, f.read(), flags)
85 85 f.close()
86 86 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
87 87 if r is None:
88 88 # no real conflict
89 89 del self._state[dfile]
90 90 elif not r:
91 91 self.mark(dfile, 'r')
92 92 return r
93 93
94 94 def _checkunknownfile(repo, wctx, mctx, f):
95 95 return (not repo.dirstate._ignore(f)
96 96 and os.path.isfile(repo.wjoin(f))
97 97 and repo.dirstate.normalize(f) not in repo.dirstate
98 98 and mctx[f].cmp(wctx[f]))
99 99
100 100 def _checkunknown(repo, wctx, mctx):
101 101 "check for collisions between unknown files and files in mctx"
102 102
103 103 error = False
104 104 for f in mctx:
105 105 if f not in wctx and _checkunknownfile(repo, wctx, mctx, f):
106 106 error = True
107 107 wctx._repo.ui.warn(_("%s: untracked file differs\n") % f)
108 108 if error:
109 109 raise util.Abort(_("untracked files in working directory differ "
110 110 "from files in requested revision"))
111 111
112 112 def _remains(f, m, ma, workingctx=False):
113 113 """check whether specified file remains after merge.
114 114
115 115 It is assumed that specified file is not contained in the manifest
116 116 of the other context.
117 117 """
118 118 if f in ma:
119 119 n = m[f]
120 120 if n != ma[f]:
121 121 return True # because it is changed locally
122 122 # even though it doesn't remain, if "remote deleted" is
123 123 # chosen in manifestmerge()
124 124 elif workingctx and n[20:] == "a":
125 125 return True # because it is added locally (linear merge specific)
126 126 else:
127 127 return False # because it is removed remotely
128 128 else:
129 129 return True # because it is added locally
130 130
131 131 def _checkcollision(mctx, extractxs):
132 132 "check for case folding collisions in the destination context"
133 133 folded = {}
134 134 for fn in mctx:
135 135 fold = util.normcase(fn)
136 136 if fold in folded:
137 137 raise util.Abort(_("case-folding collision between %s and %s")
138 138 % (fn, folded[fold]))
139 139 folded[fold] = fn
140 140
141 141 if extractxs:
142 142 wctx, actx = extractxs
143 143 # class to delay looking up copy mapping
144 144 class pathcopies(object):
145 145 @util.propertycache
146 146 def map(self):
147 147 # {dst@mctx: src@wctx} copy mapping
148 148 return copies.pathcopies(wctx, mctx)
149 149 pc = pathcopies()
150 150
151 151 for fn in wctx:
152 152 fold = util.normcase(fn)
153 153 mfn = folded.get(fold, None)
154 154 if (mfn and mfn != fn and pc.map.get(mfn) != fn and
155 155 _remains(fn, wctx.manifest(), actx.manifest(), True) and
156 156 _remains(mfn, mctx.manifest(), actx.manifest())):
157 157 raise util.Abort(_("case-folding collision between %s and %s")
158 158 % (mfn, fn))
159 159
160 160 def _forgetremoved(wctx, mctx, branchmerge):
161 161 """
162 162 Forget removed files
163 163
164 164 If we're jumping between revisions (as opposed to merging), and if
165 165 neither the working directory nor the target rev has the file,
166 166 then we need to remove it from the dirstate, to prevent the
167 167 dirstate from listing the file when it is no longer in the
168 168 manifest.
169 169
170 170 If we're merging, and the other revision has removed a file
171 171 that is not present in the working directory, we need to mark it
172 172 as removed.
173 173 """
174 174
175 175 actions = []
176 176 state = branchmerge and 'r' or 'f'
177 177 for f in wctx.deleted():
178 178 if f not in mctx:
179 actions.append((f, state))
179 actions.append((f, state, None))
180 180
181 181 if not branchmerge:
182 182 for f in wctx.removed():
183 183 if f not in mctx:
184 actions.append((f, "f"))
184 actions.append((f, "f", None))
185 185
186 186 return actions
187 187
188 188 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
189 189 """
190 190 Merge p1 and p2 with ancestor pa and generate merge action list
191 191
192 192 overwrite = whether we clobber working files
193 193 partial = function to filter file lists
194 194 """
195 195
196 196 def act(msg, m, f, *args):
197 197 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
198 actions.append((f, m) + args)
198 actions.append((f, m, args))
199 199
200 200 actions, copy, movewithdir = [], {}, {}
201 201
202 202 if overwrite:
203 203 pa = p1
204 204 elif pa == p2: # backwards
205 205 pa = p1.p1()
206 206 elif pa and repo.ui.configbool("merge", "followcopies", True):
207 207 ret = copies.mergecopies(repo, p1, p2, pa)
208 208 copy, movewithdir, diverge, renamedelete = ret
209 209 for of, fl in diverge.iteritems():
210 210 act("divergent renames", "dr", of, fl)
211 211 for of, fl in renamedelete.iteritems():
212 212 act("rename and delete", "rd", of, fl)
213 213
214 214 repo.ui.note(_("resolving manifests\n"))
215 215 repo.ui.debug(" overwrite: %s, partial: %s\n"
216 216 % (bool(overwrite), bool(partial)))
217 217 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, p1, p2))
218 218
219 219 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
220 220 copied = set(copy.values())
221 221 copied.update(movewithdir.values())
222 222
223 223 if '.hgsubstate' in m1:
224 224 # check whether sub state is modified
225 225 for s in sorted(p1.substate):
226 226 if p1.sub(s).dirty():
227 227 m1['.hgsubstate'] += "+"
228 228 break
229 229
230 230 prompts = []
231 231 # Compare manifests
232 232 visit = m1.iteritems()
233 233 if repo.ui.debugflag:
234 234 visit = sorted(visit)
235 235 for f, n in visit:
236 236 if partial and not partial(f):
237 237 continue
238 238 if f in m2:
239 239 n2 = m2[f]
240 240 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
241 241 nol = 'l' not in fl1 + fl2 + fla
242 242 a = ma.get(f, nullid)
243 243 if n == n2 and fl1 == fl2:
244 244 pass # same - keep local
245 245 elif n2 == a and fl2 == fla:
246 246 pass # remote unchanged - keep local
247 247 elif n == a and fl1 == fla: # local unchanged - use remote
248 248 if n == n2: # optimization: keep local content
249 249 act("update permissions", "e", f, fl2)
250 250 else:
251 251 act("remote is newer", "g", f, fl2)
252 252 elif nol and n2 == a: # remote only changed 'x'
253 253 act("update permissions", "e", f, fl2)
254 254 elif nol and n == a: # local only changed 'x'
255 255 act("remote is newer", "g", f, fl1)
256 256 else: # both changed something
257 257 act("versions differ", "m", f, f, f, False)
258 258 elif f in copied: # files we'll deal with on m2 side
259 259 pass
260 260 elif f in movewithdir: # directory rename
261 261 f2 = movewithdir[f]
262 262 act("remote renamed directory to " + f2, "d", f, None, f2,
263 263 m1.flags(f))
264 264 elif f in copy:
265 265 f2 = copy[f]
266 266 act("local copied/moved to " + f2, "m", f, f2, f, False)
267 267 elif f in ma: # clean, a different, no remote
268 268 if n != ma[f]:
269 269 prompts.append((f, "cd")) # prompt changed/deleted
270 270 elif n[20:] == "a": # added, no remote
271 271 act("remote deleted", "f", f)
272 272 else:
273 273 act("other deleted", "r", f)
274 274
275 275 visit = m2.iteritems()
276 276 if repo.ui.debugflag:
277 277 visit = sorted(visit)
278 278 for f, n in visit:
279 279 if partial and not partial(f):
280 280 continue
281 281 if f in m1 or f in copied: # files already visited
282 282 continue
283 283 if f in movewithdir:
284 284 f2 = movewithdir[f]
285 285 act("local renamed directory to " + f2, "d", None, f, f2,
286 286 m2.flags(f))
287 287 elif f in copy:
288 288 f2 = copy[f]
289 289 if f2 in m2:
290 290 act("remote copied to " + f, "m",
291 291 f2, f, f, False)
292 292 else:
293 293 act("remote moved to " + f, "m",
294 294 f2, f, f, True)
295 295 elif f not in ma:
296 296 if (not overwrite
297 297 and _checkunknownfile(repo, p1, p2, f)):
298 298 act("remote differs from untracked local",
299 299 "m", f, f, f, False)
300 300 else:
301 301 act("remote created", "g", f, m2.flags(f))
302 302 elif n != ma[f]:
303 303 prompts.append((f, "dc")) # prompt deleted/changed
304 304
305 305 for f, m in sorted(prompts):
306 306 if m == "cd":
307 307 if repo.ui.promptchoice(
308 308 _(" local changed %s which remote deleted\n"
309 309 "use (c)hanged version or (d)elete?") % f,
310 310 (_("&Changed"), _("&Delete")), 0):
311 311 act("prompt delete", "r", f)
312 312 else:
313 313 act("prompt keep", "a", f)
314 314 elif m == "dc":
315 315 if repo.ui.promptchoice(
316 316 _("remote changed %s which local deleted\n"
317 317 "use (c)hanged version or leave (d)eleted?") % f,
318 318 (_("&Changed"), _("&Deleted")), 0) == 0:
319 319 act("prompt recreating", "g", f, m2.flags(f))
320 320 else: assert False, m
321 321 return actions
322 322
323 323 def actionkey(a):
324 324 return a[1] == "r" and -1 or 0, a
325 325
326 326 def applyupdates(repo, actions, wctx, mctx, actx, overwrite):
327 327 """apply the merge action list to the working directory
328 328
329 329 wctx is the working copy context
330 330 mctx is the context to be merged into the working copy
331 331 actx is the context of the common ancestor
332 332
333 333 Return a tuple of counts (updated, merged, removed, unresolved) that
334 334 describes how many files were affected by the update.
335 335 """
336 336
337 337 updated, merged, removed, unresolved = 0, 0, 0, 0
338 338 ms = mergestate(repo)
339 339 ms.reset(wctx.p1().node())
340 340 moves = []
341 341 actions.sort(key=actionkey)
342 342
343 343 # prescan for merges
344 344 for a in actions:
345 f, m = a[:2]
345 f, m, args = a
346 346 if m == "m": # merge
347 f2, fd, move = a[2:]
347 f2, fd, move = args
348 348 if fd == '.hgsubstate': # merged internally
349 349 continue
350 350 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
351 351 fcl = wctx[f]
352 352 fco = mctx[f2]
353 353 if mctx == actx: # backwards, use working dir parent as ancestor
354 354 if fcl.parents():
355 355 fca = fcl.p1()
356 356 else:
357 357 fca = repo.filectx(f, fileid=nullrev)
358 358 else:
359 359 fca = fcl.ancestor(fco, actx)
360 360 if not fca:
361 361 fca = repo.filectx(f, fileid=nullrev)
362 362 ms.add(fcl, fco, fca, fd)
363 363 if f != fd and move:
364 364 moves.append(f)
365 365
366 366 audit = repo.wopener.audit
367 367
368 368 # remove renamed files after safely stored
369 369 for f in moves:
370 370 if os.path.lexists(repo.wjoin(f)):
371 371 repo.ui.debug("removing %s\n" % f)
372 372 audit(f)
373 373 util.unlinkpath(repo.wjoin(f))
374 374
375 375 numupdates = len(actions)
376 376 for i, a in enumerate(actions):
377 f, m = a[:2]
377 f, m, args = a
378 378 repo.ui.progress(_('updating'), i + 1, item=f, total=numupdates,
379 379 unit=_('files'))
380 380 if m == "r": # remove
381 381 repo.ui.note(_("removing %s\n") % f)
382 382 audit(f)
383 383 if f == '.hgsubstate': # subrepo states need updating
384 384 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
385 385 try:
386 386 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
387 387 except OSError, inst:
388 388 repo.ui.warn(_("update failed to remove %s: %s!\n") %
389 389 (f, inst.strerror))
390 390 removed += 1
391 391 elif m == "m": # merge
392 392 if fd == '.hgsubstate': # subrepo states need updating
393 393 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
394 394 overwrite)
395 395 continue
396 f2, fd, move = a[2:]
396 f2, fd, move = args
397 397 audit(fd)
398 398 r = ms.resolve(fd, wctx, mctx)
399 399 if r is not None and r > 0:
400 400 unresolved += 1
401 401 else:
402 402 if r is None:
403 403 updated += 1
404 404 else:
405 405 merged += 1
406 406 elif m == "g": # get
407 flags = a[2]
407 flags, = args
408 408 repo.ui.note(_("getting %s\n") % f)
409 409 repo.wwrite(f, mctx.filectx(f).data(), flags)
410 410 updated += 1
411 411 if f == '.hgsubstate': # subrepo states need updating
412 412 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
413 413 elif m == "d": # directory rename
414 f2, fd, flags = a[2:]
414 f2, fd, flags = args
415 415 if f:
416 416 repo.ui.note(_("moving %s to %s\n") % (f, fd))
417 417 audit(f)
418 418 repo.wwrite(fd, wctx.filectx(f).data(), flags)
419 419 util.unlinkpath(repo.wjoin(f))
420 420 if f2:
421 421 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
422 422 repo.wwrite(fd, mctx.filectx(f2).data(), flags)
423 423 updated += 1
424 424 elif m == "dr": # divergent renames
425 fl = a[2]
425 fl, = args
426 426 repo.ui.warn(_("note: possible conflict - %s was renamed "
427 427 "multiple times to:\n") % f)
428 428 for nf in fl:
429 429 repo.ui.warn(" %s\n" % nf)
430 430 elif m == "rd": # rename and delete
431 fl = a[2]
431 fl, = args
432 432 repo.ui.warn(_("note: possible conflict - %s was deleted "
433 433 "and renamed to:\n") % f)
434 434 for nf in fl:
435 435 repo.ui.warn(" %s\n" % nf)
436 436 elif m == "e": # exec
437 flags = a[2]
437 flags, = args
438 438 audit(f)
439 439 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
440 440 updated += 1
441 441 ms.commit()
442 442 repo.ui.progress(_('updating'), None, total=numupdates, unit=_('files'))
443 443
444 444 return updated, merged, removed, unresolved
445 445
446 446 def calculateupdates(repo, tctx, mctx, ancestor, branchmerge, force, partial):
447 447 "Calculate the actions needed to merge mctx into tctx"
448 448 actions = []
449 449 folding = not util.checkcase(repo.path)
450 450 if folding:
451 451 # collision check is not needed for clean update
452 452 if (not branchmerge and
453 453 (force or not tctx.dirty(missing=True, branch=False))):
454 454 _checkcollision(mctx, None)
455 455 else:
456 456 _checkcollision(mctx, (tctx, ancestor))
457 457 if not force:
458 458 _checkunknown(repo, tctx, mctx)
459 459 if tctx.rev() is None:
460 460 actions += _forgetremoved(tctx, mctx, branchmerge)
461 461 actions += manifestmerge(repo, tctx, mctx,
462 462 ancestor,
463 463 force and not branchmerge,
464 464 partial)
465 465 return actions
466 466
467 467 def recordupdates(repo, actions, branchmerge):
468 468 "record merge actions to the dirstate"
469 469
470 470 for a in actions:
471 f, m = a[:2]
471 f, m, args = a
472 472 if m == "r": # remove
473 473 if branchmerge:
474 474 repo.dirstate.remove(f)
475 475 else:
476 476 repo.dirstate.drop(f)
477 477 elif m == "a": # re-add
478 478 if not branchmerge:
479 479 repo.dirstate.add(f)
480 480 elif m == "f": # forget
481 481 repo.dirstate.drop(f)
482 482 elif m == "e": # exec change
483 483 repo.dirstate.normallookup(f)
484 484 elif m == "g": # get
485 485 if branchmerge:
486 486 repo.dirstate.otherparent(f)
487 487 else:
488 488 repo.dirstate.normal(f)
489 489 elif m == "m": # merge
490 f2, fd, move = a[2:]
490 f2, fd, move = args
491 491 if branchmerge:
492 492 # We've done a branch merge, mark this file as merged
493 493 # so that we properly record the merger later
494 494 repo.dirstate.merge(fd)
495 495 if f != f2: # copy/rename
496 496 if move:
497 497 repo.dirstate.remove(f)
498 498 if f != fd:
499 499 repo.dirstate.copy(f, fd)
500 500 else:
501 501 repo.dirstate.copy(f2, fd)
502 502 else:
503 503 # We've update-merged a locally modified file, so
504 504 # we set the dirstate to emulate a normal checkout
505 505 # of that file some time in the past. Thus our
506 506 # merge will appear as a normal local file
507 507 # modification.
508 508 if f2 == fd: # file not locally copied/moved
509 509 repo.dirstate.normallookup(fd)
510 510 if move:
511 511 repo.dirstate.drop(f)
512 512 elif m == "d": # directory rename
513 f2, fd, flag = a[2:]
513 f2, fd, flag = args
514 514 if not f2 and f not in repo.dirstate:
515 515 # untracked file moved
516 516 continue
517 517 if branchmerge:
518 518 repo.dirstate.add(fd)
519 519 if f:
520 520 repo.dirstate.remove(f)
521 521 repo.dirstate.copy(f, fd)
522 522 if f2:
523 523 repo.dirstate.copy(f2, fd)
524 524 else:
525 525 repo.dirstate.normal(fd)
526 526 if f:
527 527 repo.dirstate.drop(f)
528 528
529 529 def update(repo, node, branchmerge, force, partial, ancestor=None,
530 530 mergeancestor=False):
531 531 """
532 532 Perform a merge between the working directory and the given node
533 533
534 534 node = the node to update to, or None if unspecified
535 535 branchmerge = whether to merge between branches
536 536 force = whether to force branch merging or file overwriting
537 537 partial = a function to filter file lists (dirstate not updated)
538 538 mergeancestor = if false, merging with an ancestor (fast-forward)
539 539 is only allowed between different named branches. This flag
540 540 is used by rebase extension as a temporary fix and should be
541 541 avoided in general.
542 542
543 543 The table below shows all the behaviors of the update command
544 544 given the -c and -C or no options, whether the working directory
545 545 is dirty, whether a revision is specified, and the relationship of
546 546 the parent rev to the target rev (linear, on the same named
547 547 branch, or on another named branch).
548 548
549 549 This logic is tested by test-update-branches.t.
550 550
551 551 -c -C dirty rev | linear same cross
552 552 n n n n | ok (1) x
553 553 n n n y | ok ok ok
554 554 n n y * | merge (2) (2)
555 555 n y * * | --- discard ---
556 556 y n y * | --- (3) ---
557 557 y n n * | --- ok ---
558 558 y y * * | --- (4) ---
559 559
560 560 x = can't happen
561 561 * = don't-care
562 562 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
563 563 2 = abort: crosses branches (use 'hg merge' to merge or
564 564 use 'hg update -C' to discard changes)
565 565 3 = abort: uncommitted local changes
566 566 4 = incompatible options (checked in commands.py)
567 567
568 568 Return the same tuple as applyupdates().
569 569 """
570 570
571 571 onode = node
572 572 wlock = repo.wlock()
573 573 try:
574 574 wc = repo[None]
575 575 if node is None:
576 576 # tip of current branch
577 577 try:
578 578 node = repo.branchtip(wc.branch())
579 579 except error.RepoLookupError:
580 580 if wc.branch() == "default": # no default branch!
581 581 node = repo.lookup("tip") # update to tip
582 582 else:
583 583 raise util.Abort(_("branch %s not found") % wc.branch())
584 584 overwrite = force and not branchmerge
585 585 pl = wc.parents()
586 586 p1, p2 = pl[0], repo[node]
587 587 if ancestor:
588 588 pa = repo[ancestor]
589 589 else:
590 590 pa = p1.ancestor(p2)
591 591
592 592 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
593 593
594 594 ### check phase
595 595 if not overwrite and len(pl) > 1:
596 596 raise util.Abort(_("outstanding uncommitted merges"))
597 597 if branchmerge:
598 598 if pa == p2:
599 599 raise util.Abort(_("merging with a working directory ancestor"
600 600 " has no effect"))
601 601 elif pa == p1:
602 602 if not mergeancestor and p1.branch() == p2.branch():
603 603 raise util.Abort(_("nothing to merge"),
604 604 hint=_("use 'hg update' "
605 605 "or check 'hg heads'"))
606 606 if not force and (wc.files() or wc.deleted()):
607 607 raise util.Abort(_("outstanding uncommitted changes"),
608 608 hint=_("use 'hg status' to list changes"))
609 609 for s in sorted(wc.substate):
610 610 if wc.sub(s).dirty():
611 611 raise util.Abort(_("outstanding uncommitted changes in "
612 612 "subrepository '%s'") % s)
613 613
614 614 elif not overwrite:
615 615 if pa == p1 or pa == p2: # linear
616 616 pass # all good
617 617 elif wc.dirty(missing=True):
618 618 raise util.Abort(_("crosses branches (merge branches or use"
619 619 " --clean to discard changes)"))
620 620 elif onode is None:
621 621 raise util.Abort(_("crosses branches (merge branches or update"
622 622 " --check to force update)"))
623 623 else:
624 624 # Allow jumping branches if clean and specific rev given
625 625 pa = p1
626 626
627 627 ### calculate phase
628 628 actions = calculateupdates(repo, wc, p2, pa,
629 629 branchmerge, force, partial)
630 630
631 631 ### apply phase
632 632 if not branchmerge: # just jump to the new rev
633 633 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
634 634 if not partial:
635 635 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
636 636
637 637 stats = applyupdates(repo, actions, wc, p2, pa, overwrite)
638 638
639 639 if not partial:
640 640 repo.setparents(fp1, fp2)
641 641 recordupdates(repo, actions, branchmerge)
642 642 if not branchmerge:
643 643 repo.dirstate.setbranch(p2.branch())
644 644 finally:
645 645 wlock.release()
646 646
647 647 if not partial:
648 648 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
649 649 return stats
General Comments 0
You need to be logged in to leave comments. Login now