##// END OF EJS Templates
merge: simplify merge tests, fix exec flag bug...
Matt Mackall -
r5700:9cedc3fb default
parent child Browse files
Show More
@@ -1,671 +1,675 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
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from node import *
9 9 from i18n import _
10 10 import errno, util, os, tempfile, context, heapq
11 11
12 12 def filemerge(repo, fw, fd, fo, wctx, mctx):
13 13 """perform a 3-way merge in the working directory
14 14
15 15 fw = original filename in the working directory
16 16 fd = destination filename in the working directory
17 17 fo = filename in other parent
18 18 wctx, mctx = working and merge changecontexts
19 19 """
20 20
21 21 def temp(prefix, ctx):
22 22 pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
23 23 (fd, name) = tempfile.mkstemp(prefix=pre)
24 24 data = repo.wwritedata(ctx.path(), ctx.data())
25 25 f = os.fdopen(fd, "wb")
26 26 f.write(data)
27 27 f.close()
28 28 return name
29 29
30 30 fcm = wctx.filectx(fw)
31 31 fcmdata = wctx.filectx(fd).data()
32 32 fco = mctx.filectx(fo)
33 33
34 34 if not fco.cmp(fcmdata): # files identical?
35 35 return None
36 36
37 37 fca = fcm.ancestor(fco)
38 38 if not fca:
39 39 fca = repo.filectx(fw, fileid=nullrev)
40 40 a = repo.wjoin(fd)
41 41 b = temp("base", fca)
42 42 c = temp("other", fco)
43 43
44 44 if fw != fo:
45 45 repo.ui.status(_("merging %s and %s\n") % (fw, fo))
46 46 else:
47 47 repo.ui.status(_("merging %s\n") % fw)
48 48
49 49 repo.ui.debug(_("my %s other %s ancestor %s\n") % (fcm, fco, fca))
50 50
51 51 cmd = (os.environ.get("HGMERGE") or repo.ui.config("ui", "merge")
52 52 or "hgmerge")
53 53 r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=repo.root,
54 54 environ={'HG_FILE': fd,
55 55 'HG_MY_NODE': str(wctx.parents()[0]),
56 56 'HG_OTHER_NODE': str(mctx),
57 57 'HG_MY_ISLINK': fcm.islink(),
58 58 'HG_OTHER_ISLINK': fco.islink(),
59 59 'HG_BASE_ISLINK': fca.islink(),})
60 60 if r:
61 61 repo.ui.warn(_("merging %s failed!\n") % fd)
62 62
63 63 os.unlink(b)
64 64 os.unlink(c)
65 65 return r
66 66
67 67 def checkunknown(wctx, mctx):
68 68 "check for collisions between unknown files and files in mctx"
69 69 man = mctx.manifest()
70 70 for f in wctx.unknown():
71 71 if f in man:
72 72 if mctx.filectx(f).cmp(wctx.filectx(f).data()):
73 73 raise util.Abort(_("untracked file in working directory differs"
74 74 " from file in requested revision: '%s'")
75 75 % f)
76 76
77 77 def checkcollision(mctx):
78 78 "check for case folding collisions in the destination context"
79 79 folded = {}
80 80 for fn in mctx.manifest():
81 81 fold = fn.lower()
82 82 if fold in folded:
83 83 raise util.Abort(_("case-folding collision between %s and %s")
84 84 % (fn, folded[fold]))
85 85 folded[fold] = fn
86 86
87 87 def forgetremoved(wctx, mctx):
88 88 """
89 89 Forget removed files
90 90
91 91 If we're jumping between revisions (as opposed to merging), and if
92 92 neither the working directory nor the target rev has the file,
93 93 then we need to remove it from the dirstate, to prevent the
94 94 dirstate from listing the file when it is no longer in the
95 95 manifest.
96 96 """
97 97
98 98 action = []
99 99 man = mctx.manifest()
100 100 for f in wctx.deleted() + wctx.removed():
101 101 if f not in man:
102 102 action.append((f, "f"))
103 103
104 104 return action
105 105
106 106 def findcopies(repo, m1, m2, ma, limit):
107 107 """
108 108 Find moves and copies between m1 and m2 back to limit linkrev
109 109 """
110 110
111 111 def nonoverlap(d1, d2, d3):
112 112 "Return list of elements in d1 not in d2 or d3"
113 113 l = [d for d in d1 if d not in d3 and d not in d2]
114 114 l.sort()
115 115 return l
116 116
117 117 def dirname(f):
118 118 s = f.rfind("/")
119 119 if s == -1:
120 120 return ""
121 121 return f[:s]
122 122
123 123 def dirs(files):
124 124 d = {}
125 125 for f in files:
126 126 f = dirname(f)
127 127 while f not in d:
128 128 d[f] = True
129 129 f = dirname(f)
130 130 return d
131 131
132 132 wctx = repo.workingctx()
133 133
134 134 def makectx(f, n):
135 135 if len(n) == 20:
136 136 return repo.filectx(f, fileid=n)
137 137 return wctx.filectx(f)
138 138 ctx = util.cachefunc(makectx)
139 139
140 140 def findold(fctx):
141 141 "find files that path was copied from, back to linkrev limit"
142 142 old = {}
143 143 seen = {}
144 144 orig = fctx.path()
145 145 visit = [fctx]
146 146 while visit:
147 147 fc = visit.pop()
148 148 s = str(fc)
149 149 if s in seen:
150 150 continue
151 151 seen[s] = 1
152 152 if fc.path() != orig and fc.path() not in old:
153 153 old[fc.path()] = 1
154 154 if fc.rev() < limit:
155 155 continue
156 156 visit += fc.parents()
157 157
158 158 old = old.keys()
159 159 old.sort()
160 160 return old
161 161
162 162 copy = {}
163 163 fullcopy = {}
164 164 diverge = {}
165 165
166 166 def checkcopies(c, man, aman):
167 167 '''check possible copies for filectx c'''
168 168 for of in findold(c):
169 169 fullcopy[c.path()] = of # remember for dir rename detection
170 170 if of not in man: # original file not in other manifest?
171 171 if of in ma:
172 172 diverge.setdefault(of, []).append(c.path())
173 173 continue
174 174 # if the original file is unchanged on the other branch,
175 175 # no merge needed
176 176 if man[of] == aman.get(of):
177 177 continue
178 178 c2 = ctx(of, man[of])
179 179 ca = c.ancestor(c2)
180 180 if not ca: # unrelated?
181 181 continue
182 182 # named changed on only one side?
183 183 if ca.path() == c.path() or ca.path() == c2.path():
184 184 if c == ca and c2 == ca: # no merge needed, ignore copy
185 185 continue
186 186 copy[c.path()] = of
187 187
188 188 if not repo.ui.configbool("merge", "followcopies", True):
189 189 return {}, {}
190 190
191 191 # avoid silly behavior for update from empty dir
192 192 if not m1 or not m2 or not ma:
193 193 return {}, {}
194 194
195 195 repo.ui.debug(_(" searching for copies back to rev %d\n") % limit)
196 196
197 197 u1 = nonoverlap(m1, m2, ma)
198 198 u2 = nonoverlap(m2, m1, ma)
199 199
200 200 if u1:
201 201 repo.ui.debug(_(" unmatched files in local:\n %s\n")
202 202 % "\n ".join(u1))
203 203 if u2:
204 204 repo.ui.debug(_(" unmatched files in other:\n %s\n")
205 205 % "\n ".join(u2))
206 206
207 207 for f in u1:
208 208 checkcopies(ctx(f, m1[f]), m2, ma)
209 209
210 210 for f in u2:
211 211 checkcopies(ctx(f, m2[f]), m1, ma)
212 212
213 213 d2 = {}
214 214 for of, fl in diverge.items():
215 215 for f in fl:
216 216 fo = list(fl)
217 217 fo.remove(f)
218 218 d2[f] = (of, fo)
219 219
220 220 if fullcopy:
221 221 repo.ui.debug(_(" all copies found (* = to merge, ! = divergent):\n"))
222 222 for f in fullcopy:
223 223 note = ""
224 224 if f in copy: note += "*"
225 225 if f in diverge: note += "!"
226 226 repo.ui.debug(_(" %s -> %s %s\n") % (f, fullcopy[f], note))
227 227
228 228 if not fullcopy or not repo.ui.configbool("merge", "followdirs", True):
229 229 return copy, diverge
230 230
231 231 repo.ui.debug(_(" checking for directory renames\n"))
232 232
233 233 # generate a directory move map
234 234 d1, d2 = dirs(m1), dirs(m2)
235 235 invalid = {}
236 236 dirmove = {}
237 237
238 238 # examine each file copy for a potential directory move, which is
239 239 # when all the files in a directory are moved to a new directory
240 240 for dst, src in fullcopy.items():
241 241 dsrc, ddst = dirname(src), dirname(dst)
242 242 if dsrc in invalid:
243 243 # already seen to be uninteresting
244 244 continue
245 245 elif dsrc in d1 and ddst in d1:
246 246 # directory wasn't entirely moved locally
247 247 invalid[dsrc] = True
248 248 elif dsrc in d2 and ddst in d2:
249 249 # directory wasn't entirely moved remotely
250 250 invalid[dsrc] = True
251 251 elif dsrc in dirmove and dirmove[dsrc] != ddst:
252 252 # files from the same directory moved to two different places
253 253 invalid[dsrc] = True
254 254 else:
255 255 # looks good so far
256 256 dirmove[dsrc + "/"] = ddst + "/"
257 257
258 258 for i in invalid:
259 259 if i in dirmove:
260 260 del dirmove[i]
261 261
262 262 del d1, d2, invalid
263 263
264 264 if not dirmove:
265 265 return copy, diverge
266 266
267 267 for d in dirmove:
268 268 repo.ui.debug(_(" dir %s -> %s\n") % (d, dirmove[d]))
269 269
270 270 # check unaccounted nonoverlapping files against directory moves
271 271 for f in u1 + u2:
272 272 if f not in fullcopy:
273 273 for d in dirmove:
274 274 if f.startswith(d):
275 275 # new file added in a directory that was moved, move it
276 276 copy[f] = dirmove[d] + f[len(d):]
277 277 repo.ui.debug(_(" file %s -> %s\n") % (f, copy[f]))
278 278 break
279 279
280 280 return copy, diverge
281 281
282 282 def symmetricdifference(repo, rev1, rev2):
283 283 """symmetric difference of the sets of ancestors of rev1 and rev2
284 284
285 285 I.e. revisions that are ancestors of rev1 or rev2, but not both.
286 286 """
287 287 # basic idea:
288 288 # - mark rev1 and rev2 with different colors
289 289 # - walk the graph in topological order with the help of a heap;
290 290 # for each revision r:
291 291 # - if r has only one color, we want to return it
292 292 # - add colors[r] to its parents
293 293 #
294 294 # We keep track of the number of revisions in the heap that
295 295 # we may be interested in. We stop walking the graph as soon
296 296 # as this number reaches 0.
297 297 WHITE = 1
298 298 BLACK = 2
299 299 ALLCOLORS = WHITE | BLACK
300 300 colors = {rev1: WHITE, rev2: BLACK}
301 301
302 302 cl = repo.changelog
303 303
304 304 visit = [-rev1, -rev2]
305 305 heapq.heapify(visit)
306 306 n_wanted = len(visit)
307 307 ret = []
308 308
309 309 while n_wanted:
310 310 r = -heapq.heappop(visit)
311 311 wanted = colors[r] != ALLCOLORS
312 312 n_wanted -= wanted
313 313 if wanted:
314 314 ret.append(r)
315 315
316 316 for p in cl.parentrevs(r):
317 317 if p == nullrev:
318 318 continue
319 319 if p not in colors:
320 320 # first time we see p; add it to visit
321 321 n_wanted += wanted
322 322 colors[p] = colors[r]
323 323 heapq.heappush(visit, -p)
324 324 elif colors[p] != ALLCOLORS and colors[p] != colors[r]:
325 325 # at first we thought we wanted p, but now
326 326 # we know we don't really want it
327 327 n_wanted -= 1
328 328 colors[p] |= colors[r]
329 329
330 330 del colors[r]
331 331
332 332 return ret
333 333
334 334 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
335 335 """
336 336 Merge p1 and p2 with ancestor ma and generate merge action list
337 337
338 338 overwrite = whether we clobber working files
339 339 partial = function to filter file lists
340 340 """
341 341
342 342 repo.ui.note(_("resolving manifests\n"))
343 343 repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial)))
344 344 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2))
345 345
346 346 m1 = p1.manifest()
347 347 m2 = p2.manifest()
348 348 ma = pa.manifest()
349 349 backwards = (pa == p2)
350 350 action = []
351 351 copy = {}
352 352 diverge = {}
353 353
354 354 def fmerge(f, f2=None, fa=None):
355 355 """merge flags"""
356 356 if not f2:
357 357 f2 = f
358 358 fa = f
359 359 a, b, c = ma.execf(fa), m1.execf(f), m2.execf(f2)
360 360 if ((a^b) | (a^c)) ^ a:
361 361 return 'x'
362 362 a, b, c = ma.linkf(fa), m1.linkf(f), m2.linkf(f2)
363 363 if ((a^b) | (a^c)) ^ a:
364 364 return 'l'
365 365 return ''
366 366
367 367 def act(msg, m, f, *args):
368 368 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
369 369 action.append((f, m) + args)
370 370
371 371 if not (backwards or overwrite):
372 372 rev1 = p1.rev()
373 373 if rev1 is None:
374 374 # p1 is a workingctx
375 375 rev1 = p1.parents()[0].rev()
376 376 limit = min(symmetricdifference(repo, rev1, p2.rev()))
377 377 copy, diverge = findcopies(repo, m1, m2, ma, limit)
378 378
379 379 for of, fl in diverge.items():
380 380 act("divergent renames", "dr", of, fl)
381 381
382 382 copied = dict.fromkeys(copy.values())
383 383
384 384 # Compare manifests
385 385 for f, n in m1.iteritems():
386 386 if partial and not partial(f):
387 387 continue
388 388 if f in m2:
389 389 # are files different?
390 390 if n != m2[f]:
391 391 a = ma.get(f, nullid)
392 # are we clobbering?
393 if overwrite:
394 act("clobbering", "g", f, m2.flags(f))
395 # or are we going back in time and clean?
396 elif backwards and not n[20:]:
397 act("reverting", "g", f, m2.flags(f))
392 398 # are both different from the ancestor?
393 if not overwrite and n != a and m2[f] != a:
399 elif n != a and m2[f] != a:
394 400 act("versions differ", "m", f, f, f, fmerge(f), False)
395 # are we clobbering?
396 401 # is remote's version newer?
397 # or are we going back in time and clean?
398 elif overwrite or m2[f] != a or (backwards and not n[20:]):
399 act("remote is newer", "g", f, m2.flags(f))
402 elif m2[f] != a:
403 act("remote is newer", "g", f, fmerge(f))
400 404 # local is newer, not overwrite, check mode bits
401 405 elif fmerge(f) != m1.flags(f):
402 406 act("update permissions", "e", f, m2.flags(f))
403 407 # contents same, check mode bits
404 408 elif m1.flags(f) != m2.flags(f):
405 409 # are we clobbering?
406 410 # is remote's version newer?
407 411 # or are we going back?
408 412 if overwrite or fmerge(f) != m1.flags(f) or backwards:
409 413 act("update permissions", "e", f, m2.flags(f))
410 414 elif f in copied:
411 415 continue
412 416 elif f in copy:
413 417 f2 = copy[f]
414 418 if f2 not in m2: # directory rename
415 419 act("remote renamed directory to " + f2, "d",
416 420 f, None, f2, m1.flags(f))
417 421 elif f2 in m1: # case 2 A,B/B/B
418 422 act("local copied to " + f2, "m",
419 423 f, f2, f, fmerge(f, f2, f2), False)
420 424 else: # case 4,21 A/B/B
421 425 act("local moved to " + f2, "m",
422 426 f, f2, f, fmerge(f, f2, f2), False)
423 427 elif f in ma:
424 428 if n != ma[f] and not overwrite:
425 429 if repo.ui.prompt(
426 430 _(" local changed %s which remote deleted\n"
427 431 "use (c)hanged version or (d)elete?") % f,
428 432 _("[cd]"), _("c")) == _("d"):
429 433 act("prompt delete", "r", f)
430 434 else:
431 435 act("other deleted", "r", f)
432 436 else:
433 437 # file is created on branch or in working directory
434 438 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
435 439 act("remote deleted", "r", f)
436 440
437 441 for f, n in m2.iteritems():
438 442 if partial and not partial(f):
439 443 continue
440 444 if f in m1:
441 445 continue
442 446 if f in copied:
443 447 continue
444 448 if f in copy:
445 449 f2 = copy[f]
446 450 if f2 not in m1: # directory rename
447 451 act("local renamed directory to " + f2, "d",
448 452 None, f, f2, m2.flags(f))
449 453 elif f2 in m2: # rename case 1, A/A,B/A
450 454 act("remote copied to " + f, "m",
451 455 f2, f, f, fmerge(f2, f, f2), False)
452 456 else: # case 3,20 A/B/A
453 457 act("remote moved to " + f, "m",
454 458 f2, f, f, fmerge(f2, f, f2), True)
455 459 elif f in ma:
456 460 if overwrite or backwards:
457 461 act("recreating", "g", f, m2.flags(f))
458 462 elif n != ma[f]:
459 463 if repo.ui.prompt(
460 464 _("remote changed %s which local deleted\n"
461 465 "use (c)hanged version or leave (d)eleted?") % f,
462 466 _("[cd]"), _("c")) == _("c"):
463 467 act("prompt recreating", "g", f, m2.flags(f))
464 468 else:
465 469 act("remote created", "g", f, m2.flags(f))
466 470
467 471 return action
468 472
469 473 def applyupdates(repo, action, wctx, mctx):
470 474 "apply the merge action list to the working directory"
471 475
472 476 updated, merged, removed, unresolved = 0, 0, 0, 0
473 477 action.sort()
474 478 # prescan for copy/renames
475 479 for a in action:
476 480 f, m = a[:2]
477 481 if m == 'm': # merge
478 482 f2, fd, flags, move = a[2:]
479 483 if f != fd:
480 484 repo.ui.debug(_("copying %s to %s\n") % (f, fd))
481 485 repo.wwrite(fd, repo.wread(f), flags)
482 486
483 487 audit_path = util.path_auditor(repo.root)
484 488
485 489 for a in action:
486 490 f, m = a[:2]
487 491 if f and f[0] == "/":
488 492 continue
489 493 if m == "r": # remove
490 494 repo.ui.note(_("removing %s\n") % f)
491 495 audit_path(f)
492 496 try:
493 497 util.unlink(repo.wjoin(f))
494 498 except OSError, inst:
495 499 if inst.errno != errno.ENOENT:
496 500 repo.ui.warn(_("update failed to remove %s: %s!\n") %
497 501 (f, inst.strerror))
498 502 removed += 1
499 503 elif m == "m": # merge
500 504 f2, fd, flags, move = a[2:]
501 505 r = filemerge(repo, f, fd, f2, wctx, mctx)
502 506 if r > 0:
503 507 unresolved += 1
504 508 else:
505 509 if r is None:
506 510 updated += 1
507 511 else:
508 512 merged += 1
509 513 util.set_exec(repo.wjoin(fd), "x" in flags)
510 514 if f != fd and move and util.lexists(repo.wjoin(f)):
511 515 repo.ui.debug(_("removing %s\n") % f)
512 516 os.unlink(repo.wjoin(f))
513 517 elif m == "g": # get
514 518 flags = a[2]
515 519 repo.ui.note(_("getting %s\n") % f)
516 520 t = mctx.filectx(f).data()
517 521 repo.wwrite(f, t, flags)
518 522 updated += 1
519 523 elif m == "d": # directory rename
520 524 f2, fd, flags = a[2:]
521 525 if f:
522 526 repo.ui.note(_("moving %s to %s\n") % (f, fd))
523 527 t = wctx.filectx(f).data()
524 528 repo.wwrite(fd, t, flags)
525 529 util.unlink(repo.wjoin(f))
526 530 if f2:
527 531 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
528 532 t = mctx.filectx(f2).data()
529 533 repo.wwrite(fd, t, flags)
530 534 updated += 1
531 535 elif m == "dr": # divergent renames
532 536 fl = a[2]
533 537 repo.ui.warn("warning: detected divergent renames of %s to:\n" % f)
534 538 for nf in fl:
535 539 repo.ui.warn(" %s\n" % nf)
536 540 elif m == "e": # exec
537 541 flags = a[2]
538 542 util.set_exec(repo.wjoin(f), flags)
539 543
540 544 return updated, merged, removed, unresolved
541 545
542 546 def recordupdates(repo, action, branchmerge):
543 547 "record merge actions to the dirstate"
544 548
545 549 for a in action:
546 550 f, m = a[:2]
547 551 if m == "r": # remove
548 552 if branchmerge:
549 553 repo.dirstate.remove(f)
550 554 else:
551 555 repo.dirstate.forget(f)
552 556 elif m == "f": # forget
553 557 repo.dirstate.forget(f)
554 558 elif m in "ge": # get or exec change
555 559 if branchmerge:
556 560 repo.dirstate.normaldirty(f)
557 561 else:
558 562 repo.dirstate.normal(f)
559 563 elif m == "m": # merge
560 564 f2, fd, flag, move = a[2:]
561 565 if branchmerge:
562 566 # We've done a branch merge, mark this file as merged
563 567 # so that we properly record the merger later
564 568 repo.dirstate.merge(fd)
565 569 if f != f2: # copy/rename
566 570 if move:
567 571 repo.dirstate.remove(f)
568 572 if f != fd:
569 573 repo.dirstate.copy(f, fd)
570 574 else:
571 575 repo.dirstate.copy(f2, fd)
572 576 else:
573 577 # We've update-merged a locally modified file, so
574 578 # we set the dirstate to emulate a normal checkout
575 579 # of that file some time in the past. Thus our
576 580 # merge will appear as a normal local file
577 581 # modification.
578 582 repo.dirstate.normallookup(fd)
579 583 if move:
580 584 repo.dirstate.forget(f)
581 585 elif m == "d": # directory rename
582 586 f2, fd, flag = a[2:]
583 587 if not f2 and f not in repo.dirstate:
584 588 # untracked file moved
585 589 continue
586 590 if branchmerge:
587 591 repo.dirstate.add(fd)
588 592 if f:
589 593 repo.dirstate.remove(f)
590 594 repo.dirstate.copy(f, fd)
591 595 if f2:
592 596 repo.dirstate.copy(f2, fd)
593 597 else:
594 598 repo.dirstate.normal(fd)
595 599 if f:
596 600 repo.dirstate.forget(f)
597 601
598 602 def update(repo, node, branchmerge, force, partial):
599 603 """
600 604 Perform a merge between the working directory and the given node
601 605
602 606 branchmerge = whether to merge between branches
603 607 force = whether to force branch merging or file overwriting
604 608 partial = a function to filter file lists (dirstate not updated)
605 609 """
606 610
607 611 wlock = repo.wlock()
608 612 try:
609 613 wc = repo.workingctx()
610 614 if node is None:
611 615 # tip of current branch
612 616 try:
613 617 node = repo.branchtags()[wc.branch()]
614 618 except KeyError:
615 619 if wc.branch() == "default": # no default branch!
616 620 node = repo.lookup("tip") # update to tip
617 621 else:
618 622 raise util.Abort(_("branch %s not found") % wc.branch())
619 623 overwrite = force and not branchmerge
620 624 forcemerge = force and branchmerge
621 625 pl = wc.parents()
622 626 p1, p2 = pl[0], repo.changectx(node)
623 627 pa = p1.ancestor(p2)
624 628 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
625 629 fastforward = False
626 630
627 631 ### check phase
628 632 if not overwrite and len(pl) > 1:
629 633 raise util.Abort(_("outstanding uncommitted merges"))
630 634 if pa == p1 or pa == p2: # is there a linear path from p1 to p2?
631 635 if branchmerge:
632 636 if p1.branch() != p2.branch() and pa != p2:
633 637 fastforward = True
634 638 else:
635 639 raise util.Abort(_("there is nothing to merge, just use "
636 640 "'hg update' or look at 'hg heads'"))
637 641 elif not (overwrite or branchmerge):
638 642 raise util.Abort(_("update spans branches, use 'hg merge' "
639 643 "or 'hg update -C' to lose changes"))
640 644 if branchmerge and not forcemerge:
641 645 if wc.files():
642 646 raise util.Abort(_("outstanding uncommitted changes"))
643 647
644 648 ### calculate phase
645 649 action = []
646 650 if not force:
647 651 checkunknown(wc, p2)
648 652 if not util.checkfolding(repo.path):
649 653 checkcollision(p2)
650 654 if not branchmerge:
651 655 action += forgetremoved(wc, p2)
652 656 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
653 657
654 658 ### apply phase
655 659 if not branchmerge: # just jump to the new rev
656 660 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
657 661 if not partial:
658 662 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
659 663
660 664 stats = applyupdates(repo, action, wc, p2)
661 665
662 666 if not partial:
663 667 recordupdates(repo, action, branchmerge)
664 668 repo.dirstate.setparents(fp1, fp2)
665 669 if not branchmerge and not fastforward:
666 670 repo.dirstate.setbranch(p2.branch())
667 671 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
668 672
669 673 return stats
670 674 finally:
671 675 del wlock
General Comments 0
You need to be logged in to leave comments. Login now