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