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