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