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