##// END OF EJS Templates
manifestmerge: fix order in which manifests are fetched...
Siddharth Agarwal -
r18651:e5566593 default
parent child Browse files
Show More
@@ -1,708 +1,716
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, worker
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 179 actions.append((f, state, None, "forget deleted"))
180 180
181 181 if not branchmerge:
182 182 for f in wctx.removed():
183 183 if f not in mctx:
184 184 actions.append((f, "f", None, "forget removed"))
185 185
186 186 return actions
187 187
188 188 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial):
189 189 """
190 190 Merge p1 and p2 with ancestor pa and generate merge action list
191 191
192 192 branchmerge and force are as passed in to update
193 193 partial = function to filter file lists
194 194 """
195 195
196 196 overwrite = force and not branchmerge
197 197 actions, copy, movewithdir = [], {}, {}
198 198
199 followcopies = False
199 200 if overwrite:
200 201 pa = wctx
201 202 elif pa == p2: # backwards
202 203 pa = wctx.p1()
203 204 elif not branchmerge and not wctx.dirty(missing=True):
204 205 pass
205 206 elif pa and repo.ui.configbool("merge", "followcopies", True):
207 followcopies = True
208
209 # manifests fetched in order are going to be faster, so prime the caches
210 [x.manifest() for x in
211 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
212
213 if followcopies:
206 214 ret = copies.mergecopies(repo, wctx, p2, pa)
207 215 copy, movewithdir, diverge, renamedelete = ret
208 216 for of, fl in diverge.iteritems():
209 217 actions.append((of, "dr", (fl,), "divergent renames"))
210 218 for of, fl in renamedelete.iteritems():
211 219 actions.append((of, "rd", (fl,), "rename and delete"))
212 220
213 221 repo.ui.note(_("resolving manifests\n"))
214 222 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
215 223 % (bool(branchmerge), bool(force), bool(partial)))
216 224 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
217 225
218 226 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
219 227 copied = set(copy.values())
220 228 copied.update(movewithdir.values())
221 229
222 230 if '.hgsubstate' in m1:
223 231 # check whether sub state is modified
224 232 for s in sorted(wctx.substate):
225 233 if wctx.sub(s).dirty():
226 234 m1['.hgsubstate'] += "+"
227 235 break
228 236
229 237 aborts, prompts = [], []
230 238 # Compare manifests
231 239 for f, n in m1.iteritems():
232 240 if partial and not partial(f):
233 241 continue
234 242 if f in m2:
235 243 n2 = m2[f]
236 244 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
237 245 nol = 'l' not in fl1 + fl2 + fla
238 246 a = ma.get(f, nullid)
239 247 if n == n2 and fl1 == fl2:
240 248 pass # same - keep local
241 249 elif n2 == a and fl2 == fla:
242 250 pass # remote unchanged - keep local
243 251 elif n == a and fl1 == fla: # local unchanged - use remote
244 252 if n == n2: # optimization: keep local content
245 253 actions.append((f, "e", (fl2,), "update permissions"))
246 254 else:
247 255 actions.append((f, "g", (fl2,), "remote is newer"))
248 256 elif nol and n2 == a: # remote only changed 'x'
249 257 actions.append((f, "e", (fl2,), "update permissions"))
250 258 elif nol and n == a: # local only changed 'x'
251 259 actions.append((f, "g", (fl1,), "remote is newer"))
252 260 else: # both changed something
253 261 actions.append((f, "m", (f, f, False), "versions differ"))
254 262 elif f in copied: # files we'll deal with on m2 side
255 263 pass
256 264 elif f in movewithdir: # directory rename
257 265 f2 = movewithdir[f]
258 266 actions.append((f, "d", (None, f2, m1.flags(f)),
259 267 "remote renamed directory to " + f2))
260 268 elif f in copy:
261 269 f2 = copy[f]
262 270 actions.append((f, "m", (f2, f, False),
263 271 "local copied/moved to " + f2))
264 272 elif f in ma: # clean, a different, no remote
265 273 if n != ma[f]:
266 274 prompts.append((f, "cd")) # prompt changed/deleted
267 275 elif n[20:] == "a": # added, no remote
268 276 actions.append((f, "f", None, "remote deleted"))
269 277 else:
270 278 actions.append((f, "r", None, "other deleted"))
271 279
272 280 for f, n in m2.iteritems():
273 281 if partial and not partial(f):
274 282 continue
275 283 if f in m1 or f in copied: # files already visited
276 284 continue
277 285 if f in movewithdir:
278 286 f2 = movewithdir[f]
279 287 actions.append((None, "d", (f, f2, m2.flags(f)),
280 288 "local renamed directory to " + f2))
281 289 elif f in copy:
282 290 f2 = copy[f]
283 291 if f2 in m2:
284 292 actions.append((f2, "m", (f, f, False),
285 293 "remote copied to " + f))
286 294 else:
287 295 actions.append((f2, "m", (f, f, True),
288 296 "remote moved to " + f))
289 297 elif f not in ma:
290 298 # local unknown, remote created: the logic is described by the
291 299 # following table:
292 300 #
293 301 # force branchmerge different | action
294 302 # n * n | get
295 303 # n * y | abort
296 304 # y n * | get
297 305 # y y n | get
298 306 # y y y | merge
299 307 #
300 308 # Checking whether the files are different is expensive, so we
301 309 # don't do that when we can avoid it.
302 310 if force and not branchmerge:
303 311 actions.append((f, "g", (m2.flags(f),), "remote created"))
304 312 else:
305 313 different = _checkunknownfile(repo, wctx, p2, f)
306 314 if force and branchmerge and different:
307 315 actions.append((f, "m", (f, f, False),
308 316 "remote differs from untracked local"))
309 317 elif not force and different:
310 318 aborts.append((f, "ud"))
311 319 else:
312 320 actions.append((f, "g", (m2.flags(f),), "remote created"))
313 321 elif n != ma[f]:
314 322 prompts.append((f, "dc")) # prompt deleted/changed
315 323
316 324 for f, m in sorted(aborts):
317 325 if m == "ud":
318 326 repo.ui.warn(_("%s: untracked file differs\n") % f)
319 327 else: assert False, m
320 328 if aborts:
321 329 raise util.Abort(_("untracked files in working directory differ "
322 330 "from files in requested revision"))
323 331
324 332 for f, m in sorted(prompts):
325 333 if m == "cd":
326 334 if repo.ui.promptchoice(
327 335 _("local changed %s which remote deleted\n"
328 336 "use (c)hanged version or (d)elete?") % f,
329 337 (_("&Changed"), _("&Delete")), 0):
330 338 actions.append((f, "r", None, "prompt delete"))
331 339 else:
332 340 actions.append((f, "a", None, "prompt keep"))
333 341 elif m == "dc":
334 342 if repo.ui.promptchoice(
335 343 _("remote changed %s which local deleted\n"
336 344 "use (c)hanged version or leave (d)eleted?") % f,
337 345 (_("&Changed"), _("&Deleted")), 0) == 0:
338 346 actions.append((f, "g", (m2.flags(f),), "prompt recreating"))
339 347 else: assert False, m
340 348 return actions
341 349
342 350 def actionkey(a):
343 351 return a[1] == "r" and -1 or 0, a
344 352
345 353 def getremove(repo, mctx, overwrite, args):
346 354 """apply usually-non-interactive updates to the working directory
347 355
348 356 mctx is the context to be merged into the working copy
349 357
350 358 yields tuples for progress updates
351 359 """
352 360 verbose = repo.ui.verbose
353 361 unlink = util.unlinkpath
354 362 wjoin = repo.wjoin
355 363 fctx = mctx.filectx
356 364 wwrite = repo.wwrite
357 365 audit = repo.wopener.audit
358 366 i = 0
359 367 for arg in args:
360 368 f = arg[0]
361 369 if arg[1] == 'r':
362 370 if verbose:
363 371 repo.ui.note(_("removing %s\n") % f)
364 372 audit(f)
365 373 try:
366 374 unlink(wjoin(f), ignoremissing=True)
367 375 except OSError, inst:
368 376 repo.ui.warn(_("update failed to remove %s: %s!\n") %
369 377 (f, inst.strerror))
370 378 else:
371 379 if verbose:
372 380 repo.ui.note(_("getting %s\n") % f)
373 381 wwrite(f, fctx(f).data(), arg[2][0])
374 382 if i == 100:
375 383 yield i, f
376 384 i = 0
377 385 i += 1
378 386 if i > 0:
379 387 yield i, f
380 388
381 389 def applyupdates(repo, actions, wctx, mctx, actx, overwrite):
382 390 """apply the merge action list to the working directory
383 391
384 392 wctx is the working copy context
385 393 mctx is the context to be merged into the working copy
386 394 actx is the context of the common ancestor
387 395
388 396 Return a tuple of counts (updated, merged, removed, unresolved) that
389 397 describes how many files were affected by the update.
390 398 """
391 399
392 400 updated, merged, removed, unresolved = 0, 0, 0, 0
393 401 ms = mergestate(repo)
394 402 ms.reset(wctx.p1().node())
395 403 moves = []
396 404 actions.sort(key=actionkey)
397 405
398 406 # prescan for merges
399 407 for a in actions:
400 408 f, m, args, msg = a
401 409 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
402 410 if m == "m": # merge
403 411 f2, fd, move = args
404 412 if fd == '.hgsubstate': # merged internally
405 413 continue
406 414 repo.ui.debug(" preserving %s for resolve of %s\n" % (f, fd))
407 415 fcl = wctx[f]
408 416 fco = mctx[f2]
409 417 if mctx == actx: # backwards, use working dir parent as ancestor
410 418 if fcl.parents():
411 419 fca = fcl.p1()
412 420 else:
413 421 fca = repo.filectx(f, fileid=nullrev)
414 422 else:
415 423 fca = fcl.ancestor(fco, actx)
416 424 if not fca:
417 425 fca = repo.filectx(f, fileid=nullrev)
418 426 ms.add(fcl, fco, fca, fd)
419 427 if f != fd and move:
420 428 moves.append(f)
421 429
422 430 audit = repo.wopener.audit
423 431
424 432 # remove renamed files after safely stored
425 433 for f in moves:
426 434 if os.path.lexists(repo.wjoin(f)):
427 435 repo.ui.debug("removing %s\n" % f)
428 436 audit(f)
429 437 util.unlinkpath(repo.wjoin(f))
430 438
431 439 numupdates = len(actions)
432 440 workeractions = [a for a in actions if a[1] in 'gr']
433 441 updated = len([a for a in workeractions if a[1] == 'g'])
434 442 removed = len([a for a in workeractions if a[1] == 'r'])
435 443 actions = [a for a in actions if a[1] not in 'gr']
436 444
437 445 hgsub = [a[1] for a in workeractions if a[0] == '.hgsubstate']
438 446 if hgsub and hgsub[0] == 'r':
439 447 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
440 448
441 449 z = 0
442 450 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
443 451 workeractions)
444 452 for i, item in prog:
445 453 z += i
446 454 repo.ui.progress(_('updating'), z, item=item, total=numupdates,
447 455 unit=_('files'))
448 456
449 457 if hgsub and hgsub[0] == 'g':
450 458 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
451 459
452 460 _updating = _('updating')
453 461 _files = _('files')
454 462 progress = repo.ui.progress
455 463
456 464 for i, a in enumerate(actions):
457 465 f, m, args, msg = a
458 466 progress(_updating, z + i + 1, item=f, total=numupdates, unit=_files)
459 467 if m == "m": # merge
460 468 if fd == '.hgsubstate': # subrepo states need updating
461 469 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
462 470 overwrite)
463 471 continue
464 472 f2, fd, move = args
465 473 audit(fd)
466 474 r = ms.resolve(fd, wctx, mctx)
467 475 if r is not None and r > 0:
468 476 unresolved += 1
469 477 else:
470 478 if r is None:
471 479 updated += 1
472 480 else:
473 481 merged += 1
474 482 elif m == "d": # directory rename
475 483 f2, fd, flags = args
476 484 if f:
477 485 repo.ui.note(_("moving %s to %s\n") % (f, fd))
478 486 audit(f)
479 487 repo.wwrite(fd, wctx.filectx(f).data(), flags)
480 488 util.unlinkpath(repo.wjoin(f))
481 489 if f2:
482 490 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
483 491 repo.wwrite(fd, mctx.filectx(f2).data(), flags)
484 492 updated += 1
485 493 elif m == "dr": # divergent renames
486 494 fl, = args
487 495 repo.ui.warn(_("note: possible conflict - %s was renamed "
488 496 "multiple times to:\n") % f)
489 497 for nf in fl:
490 498 repo.ui.warn(" %s\n" % nf)
491 499 elif m == "rd": # rename and delete
492 500 fl, = args
493 501 repo.ui.warn(_("note: possible conflict - %s was deleted "
494 502 "and renamed to:\n") % f)
495 503 for nf in fl:
496 504 repo.ui.warn(" %s\n" % nf)
497 505 elif m == "e": # exec
498 506 flags, = args
499 507 audit(f)
500 508 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
501 509 updated += 1
502 510 ms.commit()
503 511 progress(_updating, None, total=numupdates, unit=_files)
504 512
505 513 return updated, merged, removed, unresolved
506 514
507 515 def calculateupdates(repo, tctx, mctx, ancestor, branchmerge, force, partial):
508 516 "Calculate the actions needed to merge mctx into tctx"
509 517 actions = []
510 518 folding = not util.checkcase(repo.path)
511 519 if folding:
512 520 # collision check is not needed for clean update
513 521 if (not branchmerge and
514 522 (force or not tctx.dirty(missing=True, branch=False))):
515 523 _checkcollision(mctx, None)
516 524 else:
517 525 _checkcollision(mctx, (tctx, ancestor))
518 526 actions += manifestmerge(repo, tctx, mctx,
519 527 ancestor,
520 528 branchmerge, force,
521 529 partial)
522 530 if tctx.rev() is None:
523 531 actions += _forgetremoved(tctx, mctx, branchmerge)
524 532 return actions
525 533
526 534 def recordupdates(repo, actions, branchmerge):
527 535 "record merge actions to the dirstate"
528 536
529 537 for a in actions:
530 538 f, m, args, msg = a
531 539 if m == "r": # remove
532 540 if branchmerge:
533 541 repo.dirstate.remove(f)
534 542 else:
535 543 repo.dirstate.drop(f)
536 544 elif m == "a": # re-add
537 545 if not branchmerge:
538 546 repo.dirstate.add(f)
539 547 elif m == "f": # forget
540 548 repo.dirstate.drop(f)
541 549 elif m == "e": # exec change
542 550 repo.dirstate.normallookup(f)
543 551 elif m == "g": # get
544 552 if branchmerge:
545 553 repo.dirstate.otherparent(f)
546 554 else:
547 555 repo.dirstate.normal(f)
548 556 elif m == "m": # merge
549 557 f2, fd, move = args
550 558 if branchmerge:
551 559 # We've done a branch merge, mark this file as merged
552 560 # so that we properly record the merger later
553 561 repo.dirstate.merge(fd)
554 562 if f != f2: # copy/rename
555 563 if move:
556 564 repo.dirstate.remove(f)
557 565 if f != fd:
558 566 repo.dirstate.copy(f, fd)
559 567 else:
560 568 repo.dirstate.copy(f2, fd)
561 569 else:
562 570 # We've update-merged a locally modified file, so
563 571 # we set the dirstate to emulate a normal checkout
564 572 # of that file some time in the past. Thus our
565 573 # merge will appear as a normal local file
566 574 # modification.
567 575 if f2 == fd: # file not locally copied/moved
568 576 repo.dirstate.normallookup(fd)
569 577 if move:
570 578 repo.dirstate.drop(f)
571 579 elif m == "d": # directory rename
572 580 f2, fd, flag = args
573 581 if not f2 and f not in repo.dirstate:
574 582 # untracked file moved
575 583 continue
576 584 if branchmerge:
577 585 repo.dirstate.add(fd)
578 586 if f:
579 587 repo.dirstate.remove(f)
580 588 repo.dirstate.copy(f, fd)
581 589 if f2:
582 590 repo.dirstate.copy(f2, fd)
583 591 else:
584 592 repo.dirstate.normal(fd)
585 593 if f:
586 594 repo.dirstate.drop(f)
587 595
588 596 def update(repo, node, branchmerge, force, partial, ancestor=None,
589 597 mergeancestor=False):
590 598 """
591 599 Perform a merge between the working directory and the given node
592 600
593 601 node = the node to update to, or None if unspecified
594 602 branchmerge = whether to merge between branches
595 603 force = whether to force branch merging or file overwriting
596 604 partial = a function to filter file lists (dirstate not updated)
597 605 mergeancestor = if false, merging with an ancestor (fast-forward)
598 606 is only allowed between different named branches. This flag
599 607 is used by rebase extension as a temporary fix and should be
600 608 avoided in general.
601 609
602 610 The table below shows all the behaviors of the update command
603 611 given the -c and -C or no options, whether the working directory
604 612 is dirty, whether a revision is specified, and the relationship of
605 613 the parent rev to the target rev (linear, on the same named
606 614 branch, or on another named branch).
607 615
608 616 This logic is tested by test-update-branches.t.
609 617
610 618 -c -C dirty rev | linear same cross
611 619 n n n n | ok (1) x
612 620 n n n y | ok ok ok
613 621 n n y * | merge (2) (2)
614 622 n y * * | --- discard ---
615 623 y n y * | --- (3) ---
616 624 y n n * | --- ok ---
617 625 y y * * | --- (4) ---
618 626
619 627 x = can't happen
620 628 * = don't-care
621 629 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
622 630 2 = abort: crosses branches (use 'hg merge' to merge or
623 631 use 'hg update -C' to discard changes)
624 632 3 = abort: uncommitted local changes
625 633 4 = incompatible options (checked in commands.py)
626 634
627 635 Return the same tuple as applyupdates().
628 636 """
629 637
630 638 onode = node
631 639 wlock = repo.wlock()
632 640 try:
633 641 wc = repo[None]
634 642 if node is None:
635 643 # tip of current branch
636 644 try:
637 645 node = repo.branchtip(wc.branch())
638 646 except error.RepoLookupError:
639 647 if wc.branch() == "default": # no default branch!
640 648 node = repo.lookup("tip") # update to tip
641 649 else:
642 650 raise util.Abort(_("branch %s not found") % wc.branch())
643 651 overwrite = force and not branchmerge
644 652 pl = wc.parents()
645 653 p1, p2 = pl[0], repo[node]
646 654 if ancestor:
647 655 pa = repo[ancestor]
648 656 else:
649 657 pa = p1.ancestor(p2)
650 658
651 659 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
652 660
653 661 ### check phase
654 662 if not overwrite and len(pl) > 1:
655 663 raise util.Abort(_("outstanding uncommitted merges"))
656 664 if branchmerge:
657 665 if pa == p2:
658 666 raise util.Abort(_("merging with a working directory ancestor"
659 667 " has no effect"))
660 668 elif pa == p1:
661 669 if not mergeancestor and p1.branch() == p2.branch():
662 670 raise util.Abort(_("nothing to merge"),
663 671 hint=_("use 'hg update' "
664 672 "or check 'hg heads'"))
665 673 if not force and (wc.files() or wc.deleted()):
666 674 raise util.Abort(_("outstanding uncommitted changes"),
667 675 hint=_("use 'hg status' to list changes"))
668 676 for s in sorted(wc.substate):
669 677 if wc.sub(s).dirty():
670 678 raise util.Abort(_("outstanding uncommitted changes in "
671 679 "subrepository '%s'") % s)
672 680
673 681 elif not overwrite:
674 682 if pa == p1 or pa == p2: # linear
675 683 pass # all good
676 684 elif wc.dirty(missing=True):
677 685 raise util.Abort(_("crosses branches (merge branches or use"
678 686 " --clean to discard changes)"))
679 687 elif onode is None:
680 688 raise util.Abort(_("crosses branches (merge branches or update"
681 689 " --check to force update)"))
682 690 else:
683 691 # Allow jumping branches if clean and specific rev given
684 692 pa = p1
685 693
686 694 ### calculate phase
687 695 actions = calculateupdates(repo, wc, p2, pa,
688 696 branchmerge, force, partial)
689 697
690 698 ### apply phase
691 699 if not branchmerge: # just jump to the new rev
692 700 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
693 701 if not partial:
694 702 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
695 703
696 704 stats = applyupdates(repo, actions, wc, p2, pa, overwrite)
697 705
698 706 if not partial:
699 707 repo.setparents(fp1, fp2)
700 708 recordupdates(repo, actions, branchmerge)
701 709 if not branchmerge:
702 710 repo.dirstate.setbranch(p2.branch())
703 711 finally:
704 712 wlock.release()
705 713
706 714 if not partial:
707 715 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
708 716 return stats
General Comments 0
You need to be logged in to leave comments. Login now