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