comparison mercurial/merge.py @ 5045:f191bc3916f7

merge: do early copy to deal with issue636 Without copies/renames, merges source names are 1:1 with their targets. Copies and renames introduce the possibility that there will be two merges with the same input but different output. By doing the copy to the destination name before the merge, the actual merge becomes 1:1 again, and no source is the input to two different merges. - add a preliminary scan to applyupdates to do copies - for the merge action, pass the old name (for finding ancestors) and the new name (for input to the merge) to filemerge - eliminate the old post-merge copy - lookup file contents from new name in filemerge - pass new name to external merge helper - report merge failure at new name - add a test
author Matt Mackall <mpm@selenic.com>
date Wed, 01 Aug 2007 12:33:12 -0500
parents 60c54154ec4c
children bf444a9a9c23 8d9bdcbb2b18
comparison
equal deleted inserted replaced
5029:ac97e065cfc7 5045:f191bc3916f7
7 7
8 from node import * 8 from node import *
9 from i18n import _ 9 from i18n import _
10 import errno, util, os, tempfile, context 10 import errno, util, os, tempfile, context
11 11
12 def filemerge(repo, fw, fo, wctx, mctx): 12 def filemerge(repo, fw, fd, fo, wctx, mctx):
13 """perform a 3-way merge in the working directory 13 """perform a 3-way merge in the working directory
14 14
15 fw = filename in the working directory 15 fw = original filename in the working directory
16 fd = destination filename in the working directory
16 fo = filename in other parent 17 fo = filename in other parent
17 wctx, mctx = working and merge changecontexts 18 wctx, mctx = working and merge changecontexts
18 """ 19 """
19 20
20 def temp(prefix, ctx): 21 def temp(prefix, ctx):
25 f.write(data) 26 f.write(data)
26 f.close() 27 f.close()
27 return name 28 return name
28 29
29 fcm = wctx.filectx(fw) 30 fcm = wctx.filectx(fw)
31 fcmdata = wctx.filectx(fd).data()
30 fco = mctx.filectx(fo) 32 fco = mctx.filectx(fo)
31 33
32 if not fco.cmp(fcm.data()): # files identical? 34 if not fco.cmp(fcmdata): # files identical?
33 return None 35 return None
34 36
35 fca = fcm.ancestor(fco) 37 fca = fcm.ancestor(fco)
36 if not fca: 38 if not fca:
37 fca = repo.filectx(fw, fileid=nullrev) 39 fca = repo.filectx(fw, fileid=nullrev)
38 a = repo.wjoin(fw) 40 a = repo.wjoin(fd)
39 b = temp("base", fca) 41 b = temp("base", fca)
40 c = temp("other", fco) 42 c = temp("other", fco)
41 43
42 if fw != fo: 44 if fw != fo:
43 repo.ui.status(_("merging %s and %s\n") % (fw, fo)) 45 repo.ui.status(_("merging %s and %s\n") % (fw, fo))
47 repo.ui.debug(_("my %s other %s ancestor %s\n") % (fcm, fco, fca)) 49 repo.ui.debug(_("my %s other %s ancestor %s\n") % (fcm, fco, fca))
48 50
49 cmd = (os.environ.get("HGMERGE") or repo.ui.config("ui", "merge") 51 cmd = (os.environ.get("HGMERGE") or repo.ui.config("ui", "merge")
50 or "hgmerge") 52 or "hgmerge")
51 r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=repo.root, 53 r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=repo.root,
52 environ={'HG_FILE': fw, 54 environ={'HG_FILE': fd,
53 'HG_MY_NODE': str(wctx.parents()[0]), 55 'HG_MY_NODE': str(wctx.parents()[0]),
54 'HG_OTHER_NODE': str(mctx)}) 56 'HG_OTHER_NODE': str(mctx)})
55 if r: 57 if r:
56 repo.ui.warn(_("merging %s failed!\n") % fw) 58 repo.ui.warn(_("merging %s failed!\n") % fd)
57 59
58 os.unlink(b) 60 os.unlink(b)
59 os.unlink(c) 61 os.unlink(c)
60 return r 62 return r
61 63
378 def applyupdates(repo, action, wctx, mctx): 380 def applyupdates(repo, action, wctx, mctx):
379 "apply the merge action list to the working directory" 381 "apply the merge action list to the working directory"
380 382
381 updated, merged, removed, unresolved = 0, 0, 0, 0 383 updated, merged, removed, unresolved = 0, 0, 0, 0
382 action.sort() 384 action.sort()
385 # prescan for copy/renames
386 for a in action:
387 f, m = a[:2]
388 if m == 'm': # merge
389 f2, fd, flags, move = a[2:]
390 if f != fd:
391 repo.ui.debug(_("copying %s to %s\n") % (f, fd))
392 repo.wwrite(fd, repo.wread(f), flags)
393
383 for a in action: 394 for a in action:
384 f, m = a[:2] 395 f, m = a[:2]
385 if f and f[0] == "/": 396 if f and f[0] == "/":
386 continue 397 continue
387 if m == "r": # remove 398 if m == "r": # remove
394 repo.ui.warn(_("update failed to remove %s: %s!\n") % 405 repo.ui.warn(_("update failed to remove %s: %s!\n") %
395 (f, inst.strerror)) 406 (f, inst.strerror))
396 removed += 1 407 removed += 1
397 elif m == "m": # merge 408 elif m == "m": # merge
398 f2, fd, flags, move = a[2:] 409 f2, fd, flags, move = a[2:]
399 r = filemerge(repo, f, f2, wctx, mctx) 410 r = filemerge(repo, f, fd, f2, wctx, mctx)
400 if r > 0: 411 if r > 0:
401 unresolved += 1 412 unresolved += 1
402 else: 413 else:
403 if r is None: 414 if r is None:
404 updated += 1 415 updated += 1
405 else: 416 else:
406 merged += 1 417 merged += 1
407 if f != fd: 418 if f != fd and move:
408 repo.ui.debug(_("copying %s to %s\n") % (f, fd)) 419 repo.ui.debug(_("removing %s\n") % f)
409 repo.wwrite(fd, repo.wread(f), flags) 420 os.unlink(repo.wjoin(f))
410 if move:
411 repo.ui.debug(_("removing %s\n") % f)
412 os.unlink(repo.wjoin(f))
413 util.set_exec(repo.wjoin(fd), "x" in flags) 421 util.set_exec(repo.wjoin(fd), "x" in flags)
414 elif m == "g": # get 422 elif m == "g": # get
415 flags = a[2] 423 flags = a[2]
416 repo.ui.note(_("getting %s\n") % f) 424 repo.ui.note(_("getting %s\n") % f)
417 t = mctx.filectx(f).data() 425 t = mctx.filectx(f).data()