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