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