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