##// END OF EJS Templates
use internal merge tool when specified for a merge-pattern in hgrc...
Dov Feldstern -
r6522:2b181fb3 default
parent child Browse files
Show More
@@ -1,217 +1,219
1 1 # filemerge.py - file-level merge handling for Mercurial
2 2 #
3 3 # Copyright 2006, 2007, 2008 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 nullrev
9 9 from i18n import _
10 10 import util, os, tempfile, simplemerge, re, filecmp
11 11
12 12 def _toolstr(ui, tool, part, default=""):
13 13 return ui.config("merge-tools", tool + "." + part, default)
14 14
15 15 def _toolbool(ui, tool, part, default=False):
16 16 return ui.configbool("merge-tools", tool + "." + part, default)
17 17
18 18 def _findtool(ui, tool):
19 if tool in ("internal:fail", "internal:local", "internal:other"):
20 return tool
19 21 k = _toolstr(ui, tool, "regkey")
20 22 if k:
21 23 p = util.lookup_reg(k, _toolstr(ui, tool, "regname"))
22 24 if p:
23 25 p = util.find_exe(p + _toolstr(ui, tool, "regappend"))
24 26 if p:
25 27 return p
26 28 return util.find_exe(_toolstr(ui, tool, "executable", tool))
27 29
28 30 def _picktool(repo, ui, path, binary, symlink):
29 31 def check(tool, pat, symlink, binary):
30 32 tmsg = tool
31 33 if pat:
32 34 tmsg += " specified for " + pat
33 35 if pat and not _findtool(ui, tool): # skip search if not matching
34 36 ui.warn(_("couldn't find merge tool %s\n") % tmsg)
35 37 elif symlink and not _toolbool(ui, tool, "symlink"):
36 38 ui.warn(_("tool %s can't handle symlinks\n") % tmsg)
37 39 elif binary and not _toolbool(ui, tool, "binary"):
38 40 ui.warn(_("tool %s can't handle binary\n") % tmsg)
39 41 elif not util.gui() and _toolbool(ui, tool, "gui"):
40 42 ui.warn(_("tool %s requires a GUI\n") % tmsg)
41 43 else:
42 44 return True
43 45 return False
44 46
45 47 # HGMERGE takes precedence
46 48 hgmerge = os.environ.get("HGMERGE")
47 49 if hgmerge:
48 50 return (hgmerge, hgmerge)
49 51
50 52 # then patterns
51 53 for pat, tool in ui.configitems("merge-patterns"):
52 54 mf = util.matcher(repo.root, "", [pat], [], [])[1]
53 55 if mf(path) and check(tool, pat, symlink, False):
54 56 toolpath = _findtool(ui, tool)
55 57 return (tool, '"' + toolpath + '"')
56 58
57 59 # then merge tools
58 60 tools = {}
59 61 for k,v in ui.configitems("merge-tools"):
60 62 t = k.split('.')[0]
61 63 if t not in tools:
62 64 tools[t] = int(_toolstr(ui, t, "priority", "0"))
63 65 names = tools.keys()
64 66 tools = [(-p,t) for t,p in tools.items()]
65 67 tools.sort()
66 68 uimerge = ui.config("ui", "merge")
67 69 if uimerge:
68 70 if uimerge not in names:
69 71 return (uimerge, uimerge)
70 72 tools.insert(0, (None, uimerge)) # highest priority
71 73 tools.append((None, "hgmerge")) # the old default, if found
72 74 for p,t in tools:
73 75 toolpath = _findtool(ui, t)
74 76 if toolpath and check(t, None, symlink, binary):
75 77 return (t, '"' + toolpath + '"')
76 78 # internal merge as last resort
77 79 return (not (symlink or binary) and "internal:merge" or None, None)
78 80
79 81 def _eoltype(data):
80 82 "Guess the EOL type of a file"
81 83 if '\0' in data: # binary
82 84 return None
83 85 if '\r\n' in data: # Windows
84 86 return '\r\n'
85 87 if '\r' in data: # Old Mac
86 88 return '\r'
87 89 if '\n' in data: # UNIX
88 90 return '\n'
89 91 return None # unknown
90 92
91 93 def _matcheol(file, origfile):
92 94 "Convert EOL markers in a file to match origfile"
93 95 tostyle = _eoltype(open(origfile, "rb").read())
94 96 if tostyle:
95 97 data = open(file, "rb").read()
96 98 style = _eoltype(data)
97 99 if style:
98 100 newdata = data.replace(style, tostyle)
99 101 if newdata != data:
100 102 open(file, "wb").write(newdata)
101 103
102 104 def filemerge(repo, fw, fd, fo, wctx, mctx):
103 105 """perform a 3-way merge in the working directory
104 106
105 107 fw = original filename in the working directory
106 108 fd = destination filename in the working directory
107 109 fo = filename in other parent
108 110 wctx, mctx = working and merge changecontexts
109 111 """
110 112
111 113 def temp(prefix, ctx):
112 114 pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
113 115 (fd, name) = tempfile.mkstemp(prefix=pre)
114 116 data = repo.wwritedata(ctx.path(), ctx.data())
115 117 f = os.fdopen(fd, "wb")
116 118 f.write(data)
117 119 f.close()
118 120 return name
119 121
120 122 def isbin(ctx):
121 123 try:
122 124 return util.binary(ctx.data())
123 125 except IOError:
124 126 return False
125 127
126 128 fco = mctx.filectx(fo)
127 129 if not fco.cmp(wctx.filectx(fd).data()): # files identical?
128 130 return None
129 131
130 132 ui = repo.ui
131 133 fcm = wctx.filectx(fw)
132 134 fca = fcm.ancestor(fco) or repo.filectx(fw, fileid=nullrev)
133 135 binary = isbin(fcm) or isbin(fco) or isbin(fca)
134 136 symlink = fcm.islink() or fco.islink()
135 137 tool, toolpath = _picktool(repo, ui, fw, binary, symlink)
136 138 ui.debug(_("picked tool '%s' for %s (binary %s symlink %s)\n") %
137 139 (tool, fw, binary, symlink))
138 140
139 141 if not tool:
140 142 tool = "internal:local"
141 143 if ui.prompt(_(" no tool found to merge %s\n"
142 144 "keep (l)ocal or take (o)ther?") % fw,
143 145 _("[lo]"), _("l")) != _("l"):
144 146 tool = "internal:other"
145 147 if tool == "internal:local":
146 148 return 0
147 149 if tool == "internal:other":
148 150 repo.wwrite(fd, fco.data(), fco.fileflags())
149 151 return 0
150 152 if tool == "internal:fail":
151 153 return 1
152 154
153 155 # do the actual merge
154 156 a = repo.wjoin(fd)
155 157 b = temp("base", fca)
156 158 c = temp("other", fco)
157 159 out = ""
158 160 back = a + ".orig"
159 161 util.copyfile(a, back)
160 162
161 163 if fw != fo:
162 164 repo.ui.status(_("merging %s and %s\n") % (fw, fo))
163 165 else:
164 166 repo.ui.status(_("merging %s\n") % fw)
165 167 repo.ui.debug(_("my %s other %s ancestor %s\n") % (fcm, fco, fca))
166 168
167 169 # do we attempt to simplemerge first?
168 170 if _toolbool(ui, tool, "premerge", not (binary or symlink)):
169 171 r = simplemerge.simplemerge(a, b, c, quiet=True)
170 172 if not r:
171 173 ui.debug(_(" premerge successful\n"))
172 174 os.unlink(back)
173 175 os.unlink(b)
174 176 os.unlink(c)
175 177 return 0
176 178 util.copyfile(back, a) # restore from backup and try again
177 179
178 180 env = dict(HG_FILE=fd,
179 181 HG_MY_NODE=str(wctx.parents()[0]),
180 182 HG_OTHER_NODE=str(mctx),
181 183 HG_MY_ISLINK=fcm.islink(),
182 184 HG_OTHER_ISLINK=fco.islink(),
183 185 HG_BASE_ISLINK=fca.islink())
184 186
185 187 if tool == "internal:merge":
186 188 r = simplemerge.simplemerge(a, b, c, label=['local', 'other'])
187 189 else:
188 190 args = _toolstr(ui, tool, "args", '$local $base $other')
189 191 if "$output" in args:
190 192 out, a = a, back # read input from backup, write to original
191 193 replace = dict(local=a, base=b, other=c, output=out)
192 194 args = re.sub("\$(local|base|other|output)",
193 195 lambda x: '"%s"' % replace[x.group()[1:]], args)
194 196 r = util.system(toolpath + ' ' + args, cwd=repo.root, environ=env)
195 197
196 198 if not r and _toolbool(ui, tool, "checkconflicts"):
197 199 if re.match("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcm.data()):
198 200 r = 1
199 201
200 202 if not r and _toolbool(ui, tool, "checkchanged"):
201 203 if filecmp.cmp(repo.wjoin(fd), back):
202 204 if ui.prompt(_(" output file %s appears unchanged\n"
203 205 "was merge successful (yn)?") % fd,
204 206 _("[yn]"), _("n")) != _("y"):
205 207 r = 1
206 208
207 209 if _toolbool(ui, tool, "fixeol"):
208 210 _matcheol(repo.wjoin(fd), back)
209 211
210 212 if r:
211 213 repo.ui.warn(_("merging %s failed!\n") % fd)
212 214 else:
213 215 os.unlink(back)
214 216
215 217 os.unlink(b)
216 218 os.unlink(c)
217 219 return r
General Comments 0
You need to be logged in to leave comments. Login now