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