##// END OF EJS Templates
merge: add ancestor to the update function...
Matt Mackall -
r13874:9d67277c default
parent child Browse files
Show More
@@ -1,558 +1,562 b''
1 1 # merge.py - directory-level update/merge handling for Mercurial
2 2 #
3 3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import nullid, nullrev, hex, bin
9 9 from i18n import _
10 10 import util, filemerge, copies, subrepo
11 11 import errno, os, shutil
12 12
13 13 class mergestate(object):
14 14 '''track 3-way merge state of individual files'''
15 15 def __init__(self, repo):
16 16 self._repo = repo
17 17 self._dirty = False
18 18 self._read()
19 19 def reset(self, node=None):
20 20 self._state = {}
21 21 if node:
22 22 self._local = node
23 23 shutil.rmtree(self._repo.join("merge"), True)
24 24 self._dirty = False
25 25 def _read(self):
26 26 self._state = {}
27 27 try:
28 28 f = self._repo.opener("merge/state")
29 29 for i, l in enumerate(f):
30 30 if i == 0:
31 31 self._local = bin(l[:-1])
32 32 else:
33 33 bits = l[:-1].split("\0")
34 34 self._state[bits[0]] = bits[1:]
35 35 f.close()
36 36 except IOError, err:
37 37 if err.errno != errno.ENOENT:
38 38 raise
39 39 self._dirty = False
40 40 def commit(self):
41 41 if self._dirty:
42 42 f = self._repo.opener("merge/state", "w")
43 43 f.write(hex(self._local) + "\n")
44 44 for d, v in self._state.iteritems():
45 45 f.write("\0".join([d] + v) + "\n")
46 46 f.close()
47 47 self._dirty = False
48 48 def add(self, fcl, fco, fca, fd, flags):
49 49 hash = util.sha1(fcl.path()).hexdigest()
50 50 self._repo.opener("merge/" + hash, "w").write(fcl.data())
51 51 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
52 52 hex(fca.filenode()), fco.path(), 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 f = self._repo.opener("merge/" + hash)
71 71 self._repo.wwrite(dfile, f.read(), flags)
72 72 f.close()
73 73 fcd = wctx[dfile]
74 74 fco = octx[ofile]
75 75 fca = self._repo.filectx(afile, fileid=anode)
76 76 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
77 77 if r is None:
78 78 # no real conflict
79 79 del self._state[dfile]
80 80 elif not r:
81 81 self.mark(dfile, 'r')
82 82 return r
83 83
84 84 def _checkunknown(wctx, mctx):
85 85 "check for collisions between unknown files and files in mctx"
86 86 for f in wctx.unknown():
87 87 if f in mctx and mctx[f].cmp(wctx[f]):
88 88 raise util.Abort(_("untracked file in working directory differs"
89 89 " from file in requested revision: '%s'") % f)
90 90
91 91 def _checkcollision(mctx):
92 92 "check for case folding collisions in the destination context"
93 93 folded = {}
94 94 for fn in mctx:
95 95 fold = fn.lower()
96 96 if fold in folded:
97 97 raise util.Abort(_("case-folding collision between %s and %s")
98 98 % (fn, folded[fold]))
99 99 folded[fold] = fn
100 100
101 101 def _forgetremoved(wctx, mctx, branchmerge):
102 102 """
103 103 Forget removed files
104 104
105 105 If we're jumping between revisions (as opposed to merging), and if
106 106 neither the working directory nor the target rev has the file,
107 107 then we need to remove it from the dirstate, to prevent the
108 108 dirstate from listing the file when it is no longer in the
109 109 manifest.
110 110
111 111 If we're merging, and the other revision has removed a file
112 112 that is not present in the working directory, we need to mark it
113 113 as removed.
114 114 """
115 115
116 116 action = []
117 117 state = branchmerge and 'r' or 'f'
118 118 for f in wctx.deleted():
119 119 if f not in mctx:
120 120 action.append((f, state))
121 121
122 122 if not branchmerge:
123 123 for f in wctx.removed():
124 124 if f not in mctx:
125 125 action.append((f, "f"))
126 126
127 127 return action
128 128
129 129 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
130 130 """
131 131 Merge p1 and p2 with ancestor pa and generate merge action list
132 132
133 133 overwrite = whether we clobber working files
134 134 partial = function to filter file lists
135 135 """
136 136
137 137 def fmerge(f, f2, fa):
138 138 """merge flags"""
139 139 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
140 140 if m == n: # flags agree
141 141 return m # unchanged
142 142 if m and n and not a: # flags set, don't agree, differ from parent
143 143 r = repo.ui.promptchoice(
144 144 _(" conflicting flags for %s\n"
145 145 "(n)one, e(x)ec or sym(l)ink?") % f,
146 146 (_("&None"), _("E&xec"), _("Sym&link")), 0)
147 147 if r == 1:
148 148 return "x" # Exec
149 149 if r == 2:
150 150 return "l" # Symlink
151 151 return ""
152 152 if m and m != a: # changed from a to m
153 153 return m
154 154 if n and n != a: # changed from a to n
155 155 return n
156 156 return '' # flag was cleared
157 157
158 158 def act(msg, m, f, *args):
159 159 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
160 160 action.append((f, m) + args)
161 161
162 162 action, copy = [], {}
163 163
164 164 if overwrite:
165 165 pa = p1
166 166 elif pa == p2: # backwards
167 167 pa = p1.p1()
168 168 elif pa and repo.ui.configbool("merge", "followcopies", True):
169 169 dirs = repo.ui.configbool("merge", "followdirs", True)
170 170 copy, diverge = copies.copies(repo, p1, p2, pa, dirs)
171 171 for of, fl in diverge.iteritems():
172 172 act("divergent renames", "dr", of, fl)
173 173
174 174 repo.ui.note(_("resolving manifests\n"))
175 175 repo.ui.debug(" overwrite %s partial %s\n" % (overwrite, bool(partial)))
176 176 repo.ui.debug(" ancestor %s local %s remote %s\n" % (pa, p1, p2))
177 177
178 178 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
179 179 copied = set(copy.values())
180 180
181 181 if '.hgsubstate' in m1:
182 182 # check whether sub state is modified
183 183 for s in p1.substate:
184 184 if p1.sub(s).dirty():
185 185 m1['.hgsubstate'] += "+"
186 186 break
187 187
188 188 # Compare manifests
189 189 for f, n in m1.iteritems():
190 190 if partial and not partial(f):
191 191 continue
192 192 if f in m2:
193 193 rflags = fmerge(f, f, f)
194 194 a = ma.get(f, nullid)
195 195 if n == m2[f] or m2[f] == a: # same or local newer
196 196 # is file locally modified or flags need changing?
197 197 # dirstate flags may need to be made current
198 198 if m1.flags(f) != rflags or n[20:]:
199 199 act("update permissions", "e", f, rflags)
200 200 elif n == a: # remote newer
201 201 act("remote is newer", "g", f, rflags)
202 202 else: # both changed
203 203 act("versions differ", "m", f, f, f, rflags, False)
204 204 elif f in copied: # files we'll deal with on m2 side
205 205 pass
206 206 elif f in copy:
207 207 f2 = copy[f]
208 208 if f2 not in m2: # directory rename
209 209 act("remote renamed directory to " + f2, "d",
210 210 f, None, f2, m1.flags(f))
211 211 else: # case 2 A,B/B/B or case 4,21 A/B/B
212 212 act("local copied/moved to " + f2, "m",
213 213 f, f2, f, fmerge(f, f2, f2), False)
214 214 elif f in ma: # clean, a different, no remote
215 215 if n != ma[f]:
216 216 if repo.ui.promptchoice(
217 217 _(" local changed %s which remote deleted\n"
218 218 "use (c)hanged version or (d)elete?") % f,
219 219 (_("&Changed"), _("&Delete")), 0):
220 220 act("prompt delete", "r", f)
221 221 else:
222 222 act("prompt keep", "a", f)
223 223 elif n[20:] == "a": # added, no remote
224 224 act("remote deleted", "f", f)
225 225 elif n[20:] != "u":
226 226 act("other deleted", "r", f)
227 227
228 228 for f, n in m2.iteritems():
229 229 if partial and not partial(f):
230 230 continue
231 231 if f in m1 or f in copied: # files already visited
232 232 continue
233 233 if f in copy:
234 234 f2 = copy[f]
235 235 if f2 not in m1: # directory rename
236 236 act("local renamed directory to " + f2, "d",
237 237 None, f, f2, m2.flags(f))
238 238 elif f2 in m2: # rename case 1, A/A,B/A
239 239 act("remote copied to " + f, "m",
240 240 f2, f, f, fmerge(f2, f, f2), False)
241 241 else: # case 3,20 A/B/A
242 242 act("remote moved to " + f, "m",
243 243 f2, f, f, fmerge(f2, f, f2), True)
244 244 elif f not in ma:
245 245 act("remote created", "g", f, m2.flags(f))
246 246 elif n != ma[f]:
247 247 if repo.ui.promptchoice(
248 248 _("remote changed %s which local deleted\n"
249 249 "use (c)hanged version or leave (d)eleted?") % f,
250 250 (_("&Changed"), _("&Deleted")), 0) == 0:
251 251 act("prompt recreating", "g", f, m2.flags(f))
252 252
253 253 return action
254 254
255 255 def actionkey(a):
256 256 return a[1] == 'r' and -1 or 0, a
257 257
258 258 def applyupdates(repo, action, wctx, mctx, actx, overwrite):
259 259 """apply the merge action list to the working directory
260 260
261 261 wctx is the working copy context
262 262 mctx is the context to be merged into the working copy
263 263 actx is the context of the common ancestor
264 264
265 265 Return a tuple of counts (updated, merged, removed, unresolved) that
266 266 describes how many files were affected by the update.
267 267 """
268 268
269 269 updated, merged, removed, unresolved = 0, 0, 0, 0
270 270 ms = mergestate(repo)
271 271 ms.reset(wctx.parents()[0].node())
272 272 moves = []
273 273 action.sort(key=actionkey)
274 274 substate = wctx.substate # prime
275 275
276 276 # prescan for merges
277 277 u = repo.ui
278 278 for a in action:
279 279 f, m = a[:2]
280 280 if m == 'm': # merge
281 281 f2, fd, flags, move = a[2:]
282 282 if f == '.hgsubstate': # merged internally
283 283 continue
284 284 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
285 285 fcl = wctx[f]
286 286 fco = mctx[f2]
287 287 if mctx == actx: # backwards, use working dir parent as ancestor
288 288 if fcl.parents():
289 289 fca = fcl.parents()[0]
290 290 else:
291 291 fca = repo.filectx(f, fileid=nullrev)
292 292 else:
293 293 fca = fcl.ancestor(fco, actx)
294 294 if not fca:
295 295 fca = repo.filectx(f, fileid=nullrev)
296 296 ms.add(fcl, fco, fca, fd, flags)
297 297 if f != fd and move:
298 298 moves.append(f)
299 299
300 300 # remove renamed files after safely stored
301 301 for f in moves:
302 302 if os.path.lexists(repo.wjoin(f)):
303 303 repo.ui.debug("removing %s\n" % f)
304 304 os.unlink(repo.wjoin(f))
305 305
306 306 audit_path = util.path_auditor(repo.root)
307 307
308 308 numupdates = len(action)
309 309 for i, a in enumerate(action):
310 310 f, m = a[:2]
311 311 u.progress(_('updating'), i + 1, item=f, total=numupdates,
312 312 unit=_('files'))
313 313 if f and f[0] == "/":
314 314 continue
315 315 if m == "r": # remove
316 316 repo.ui.note(_("removing %s\n") % f)
317 317 audit_path(f)
318 318 if f == '.hgsubstate': # subrepo states need updating
319 319 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
320 320 try:
321 321 util.unlinkpath(repo.wjoin(f))
322 322 except OSError, inst:
323 323 if inst.errno != errno.ENOENT:
324 324 repo.ui.warn(_("update failed to remove %s: %s!\n") %
325 325 (f, inst.strerror))
326 326 removed += 1
327 327 elif m == "m": # merge
328 328 if f == '.hgsubstate': # subrepo states need updating
329 329 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx), overwrite)
330 330 continue
331 331 f2, fd, flags, move = a[2:]
332 332 r = ms.resolve(fd, wctx, mctx)
333 333 if r is not None and r > 0:
334 334 unresolved += 1
335 335 else:
336 336 if r is None:
337 337 updated += 1
338 338 else:
339 339 merged += 1
340 340 util.set_flags(repo.wjoin(fd), 'l' in flags, 'x' in flags)
341 341 if (move and repo.dirstate.normalize(fd) != f
342 342 and os.path.lexists(repo.wjoin(f))):
343 343 repo.ui.debug("removing %s\n" % f)
344 344 os.unlink(repo.wjoin(f))
345 345 elif m == "g": # get
346 346 flags = a[2]
347 347 repo.ui.note(_("getting %s\n") % f)
348 348 t = mctx.filectx(f).data()
349 349 repo.wwrite(f, t, flags)
350 350 t = None
351 351 updated += 1
352 352 if f == '.hgsubstate': # subrepo states need updating
353 353 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
354 354 elif m == "d": # directory rename
355 355 f2, fd, flags = a[2:]
356 356 if f:
357 357 repo.ui.note(_("moving %s to %s\n") % (f, fd))
358 358 t = wctx.filectx(f).data()
359 359 repo.wwrite(fd, t, flags)
360 360 util.unlinkpath(repo.wjoin(f))
361 361 if f2:
362 362 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
363 363 t = mctx.filectx(f2).data()
364 364 repo.wwrite(fd, t, flags)
365 365 updated += 1
366 366 elif m == "dr": # divergent renames
367 367 fl = a[2]
368 368 repo.ui.warn(_("note: possible conflict - %s was renamed "
369 369 "multiple times to:\n") % f)
370 370 for nf in fl:
371 371 repo.ui.warn(" %s\n" % nf)
372 372 elif m == "e": # exec
373 373 flags = a[2]
374 374 util.set_flags(repo.wjoin(f), 'l' in flags, 'x' in flags)
375 375 ms.commit()
376 376 u.progress(_('updating'), None, total=numupdates, unit=_('files'))
377 377
378 378 return updated, merged, removed, unresolved
379 379
380 380 def recordupdates(repo, action, branchmerge):
381 381 "record merge actions to the dirstate"
382 382
383 383 for a in action:
384 384 f, m = a[:2]
385 385 if m == "r": # remove
386 386 if branchmerge:
387 387 repo.dirstate.remove(f)
388 388 else:
389 389 repo.dirstate.forget(f)
390 390 elif m == "a": # re-add
391 391 if not branchmerge:
392 392 repo.dirstate.add(f)
393 393 elif m == "f": # forget
394 394 repo.dirstate.forget(f)
395 395 elif m == "e": # exec change
396 396 repo.dirstate.normallookup(f)
397 397 elif m == "g": # get
398 398 if branchmerge:
399 399 repo.dirstate.otherparent(f)
400 400 else:
401 401 repo.dirstate.normal(f)
402 402 elif m == "m": # merge
403 403 f2, fd, flag, move = a[2:]
404 404 if branchmerge:
405 405 # We've done a branch merge, mark this file as merged
406 406 # so that we properly record the merger later
407 407 repo.dirstate.merge(fd)
408 408 if f != f2: # copy/rename
409 409 if move:
410 410 repo.dirstate.remove(f)
411 411 if f != fd:
412 412 repo.dirstate.copy(f, fd)
413 413 else:
414 414 repo.dirstate.copy(f2, fd)
415 415 else:
416 416 # We've update-merged a locally modified file, so
417 417 # we set the dirstate to emulate a normal checkout
418 418 # of that file some time in the past. Thus our
419 419 # merge will appear as a normal local file
420 420 # modification.
421 421 if f2 == fd: # file not locally copied/moved
422 422 repo.dirstate.normallookup(fd)
423 423 if move:
424 424 repo.dirstate.forget(f)
425 425 elif m == "d": # directory rename
426 426 f2, fd, flag = a[2:]
427 427 if not f2 and f not in repo.dirstate:
428 428 # untracked file moved
429 429 continue
430 430 if branchmerge:
431 431 repo.dirstate.add(fd)
432 432 if f:
433 433 repo.dirstate.remove(f)
434 434 repo.dirstate.copy(f, fd)
435 435 if f2:
436 436 repo.dirstate.copy(f2, fd)
437 437 else:
438 438 repo.dirstate.normal(fd)
439 439 if f:
440 440 repo.dirstate.forget(f)
441 441
442 def update(repo, node, branchmerge, force, partial):
442 def update(repo, node, branchmerge, force, partial, ancestor=None):
443 443 """
444 444 Perform a merge between the working directory and the given node
445 445
446 446 node = the node to update to, or None if unspecified
447 447 branchmerge = whether to merge between branches
448 448 force = whether to force branch merging or file overwriting
449 449 partial = a function to filter file lists (dirstate not updated)
450 450
451 451 The table below shows all the behaviors of the update command
452 452 given the -c and -C or no options, whether the working directory
453 453 is dirty, whether a revision is specified, and the relationship of
454 454 the parent rev to the target rev (linear, on the same named
455 455 branch, or on another named branch).
456 456
457 457 This logic is tested by test-update-branches.t.
458 458
459 459 -c -C dirty rev | linear same cross
460 460 n n n n | ok (1) x
461 461 n n n y | ok ok ok
462 462 n n y * | merge (2) (2)
463 463 n y * * | --- discard ---
464 464 y n y * | --- (3) ---
465 465 y n n * | --- ok ---
466 466 y y * * | --- (4) ---
467 467
468 468 x = can't happen
469 469 * = don't-care
470 470 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
471 471 2 = abort: crosses branches (use 'hg merge' to merge or
472 472 use 'hg update -C' to discard changes)
473 473 3 = abort: uncommitted local changes
474 474 4 = incompatible options (checked in commands.py)
475 475
476 476 Return the same tuple as applyupdates().
477 477 """
478 478
479 479 onode = node
480 480 wlock = repo.wlock()
481 481 try:
482 482 wc = repo[None]
483 483 if node is None:
484 484 # tip of current branch
485 485 try:
486 486 node = repo.branchtags()[wc.branch()]
487 487 except KeyError:
488 488 if wc.branch() == "default": # no default branch!
489 489 node = repo.lookup("tip") # update to tip
490 490 else:
491 491 raise util.Abort(_("branch %s not found") % wc.branch())
492 492 overwrite = force and not branchmerge
493 493 pl = wc.parents()
494 494 p1, p2 = pl[0], repo[node]
495 pa = p1.ancestor(p2)
495 if ancestor:
496 pa = repo[ancestor]
497 else:
498 pa = p1.ancestor(p2)
499
496 500 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
497 501
498 502 ### check phase
499 503 if not overwrite and len(pl) > 1:
500 504 raise util.Abort(_("outstanding uncommitted merges"))
501 505 if branchmerge:
502 506 if pa == p2:
503 507 raise util.Abort(_("merging with a working directory ancestor"
504 508 " has no effect"))
505 509 elif pa == p1:
506 510 if p1.branch() == p2.branch():
507 511 raise util.Abort(_("nothing to merge (use 'hg update'"
508 512 " or check 'hg heads')"))
509 513 if not force and (wc.files() or wc.deleted()):
510 514 raise util.Abort(_("outstanding uncommitted changes "
511 515 "(use 'hg status' to list changes)"))
512 516 for s in wc.substate:
513 517 if wc.sub(s).dirty():
514 518 raise util.Abort(_("outstanding uncommitted changes in "
515 519 "subrepository '%s'") % s)
516 520
517 521 elif not overwrite:
518 522 if pa == p1 or pa == p2: # linear
519 523 pass # all good
520 524 elif wc.files() or wc.deleted():
521 525 raise util.Abort(_("crosses branches (merge branches or use"
522 526 " --clean to discard changes)"))
523 527 elif onode is None:
524 528 raise util.Abort(_("crosses branches (merge branches or use"
525 529 " --check to force update)"))
526 530 else:
527 531 # Allow jumping branches if clean and specific rev given
528 532 overwrite = True
529 533
530 534 ### calculate phase
531 535 action = []
532 536 wc.status(unknown=True) # prime cache
533 537 if not force:
534 538 _checkunknown(wc, p2)
535 539 if not util.checkcase(repo.path):
536 540 _checkcollision(p2)
537 541 action += _forgetremoved(wc, p2, branchmerge)
538 542 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
539 543
540 544 ### apply phase
541 545 if not branchmerge: # just jump to the new rev
542 546 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
543 547 if not partial:
544 548 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
545 549
546 550 stats = applyupdates(repo, action, wc, p2, pa, overwrite)
547 551
548 552 if not partial:
549 553 repo.dirstate.setparents(fp1, fp2)
550 554 recordupdates(repo, action, branchmerge)
551 555 if not branchmerge:
552 556 repo.dirstate.setbranch(p2.branch())
553 557 finally:
554 558 wlock.release()
555 559
556 560 if not partial:
557 561 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
558 562 return stats
General Comments 0
You need to be logged in to leave comments. Login now