##// END OF EJS Templates
filemerge: drop extra white space...
Pierre-Yves David -
r22025:5f22975d default
parent child Browse files
Show More
@@ -1,444 +1,444 b''
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 or any later version.
7 7
8 8 from node import short
9 9 from i18n import _
10 10 import util, simplemerge, match, error, templater, templatekw
11 11 import os, tempfile, re, filecmp
12 12 import tagmerge
13 13
14 14 def _toolstr(ui, tool, part, default=""):
15 15 return ui.config("merge-tools", tool + "." + part, default)
16 16
17 17 def _toolbool(ui, tool, part, default=False):
18 18 return ui.configbool("merge-tools", tool + "." + part, default)
19 19
20 20 def _toollist(ui, tool, part, default=[]):
21 21 return ui.configlist("merge-tools", tool + "." + part, default)
22 22
23 23 internals = {}
24 24
25 25 def internaltool(name, trymerge, onfailure=None):
26 26 '''return a decorator for populating internal merge tool table'''
27 27 def decorator(func):
28 28 fullname = 'internal:' + name
29 29 func.__doc__ = "``%s``\n" % fullname + func.__doc__.strip()
30 30 internals[fullname] = func
31 31 func.trymerge = trymerge
32 32 func.onfailure = onfailure
33 33 return func
34 34 return decorator
35 35
36 36 def _findtool(ui, tool):
37 37 if tool in internals:
38 38 return tool
39 39 for kn in ("regkey", "regkeyalt"):
40 40 k = _toolstr(ui, tool, kn)
41 41 if not k:
42 42 continue
43 43 p = util.lookupreg(k, _toolstr(ui, tool, "regname"))
44 44 if p:
45 45 p = util.findexe(p + _toolstr(ui, tool, "regappend"))
46 46 if p:
47 47 return p
48 48 exe = _toolstr(ui, tool, "executable", tool)
49 49 return util.findexe(util.expandpath(exe))
50 50
51 51 def _picktool(repo, ui, path, binary, symlink):
52 52 def check(tool, pat, symlink, binary):
53 53 tmsg = tool
54 54 if pat:
55 55 tmsg += " specified for " + pat
56 56 if not _findtool(ui, tool):
57 57 if pat: # explicitly requested tool deserves a warning
58 58 ui.warn(_("couldn't find merge tool %s\n") % tmsg)
59 59 else: # configured but non-existing tools are more silent
60 60 ui.note(_("couldn't find merge tool %s\n") % tmsg)
61 61 elif symlink and not _toolbool(ui, tool, "symlink"):
62 62 ui.warn(_("tool %s can't handle symlinks\n") % tmsg)
63 63 elif binary and not _toolbool(ui, tool, "binary"):
64 64 ui.warn(_("tool %s can't handle binary\n") % tmsg)
65 65 elif not util.gui() and _toolbool(ui, tool, "gui"):
66 66 ui.warn(_("tool %s requires a GUI\n") % tmsg)
67 67 else:
68 68 return True
69 69 return False
70 70
71 71 # forcemerge comes from command line arguments, highest priority
72 72 force = ui.config('ui', 'forcemerge')
73 73 if force:
74 74 toolpath = _findtool(ui, force)
75 75 if toolpath:
76 76 return (force, util.shellquote(toolpath))
77 77 else:
78 78 # mimic HGMERGE if given tool not found
79 79 return (force, force)
80 80
81 81 # HGMERGE takes next precedence
82 82 hgmerge = os.environ.get("HGMERGE")
83 83 if hgmerge:
84 84 return (hgmerge, hgmerge)
85 85
86 86 # then patterns
87 87 for pat, tool in ui.configitems("merge-patterns"):
88 88 mf = match.match(repo.root, '', [pat])
89 89 if mf(path) and check(tool, pat, symlink, False):
90 90 toolpath = _findtool(ui, tool)
91 91 return (tool, util.shellquote(toolpath))
92 92
93 93 # then merge tools
94 94 tools = {}
95 95 for k, v in ui.configitems("merge-tools"):
96 96 t = k.split('.')[0]
97 97 if t not in tools:
98 98 tools[t] = int(_toolstr(ui, t, "priority", "0"))
99 99 names = tools.keys()
100 100 tools = sorted([(-p, t) for t, p in tools.items()])
101 101 uimerge = ui.config("ui", "merge")
102 102 if uimerge:
103 103 if uimerge not in names:
104 104 return (uimerge, uimerge)
105 105 tools.insert(0, (None, uimerge)) # highest priority
106 106 tools.append((None, "hgmerge")) # the old default, if found
107 107 for p, t in tools:
108 108 if check(t, None, symlink, binary):
109 109 toolpath = _findtool(ui, t)
110 110 return (t, util.shellquote(toolpath))
111 111
112 112 # internal merge or prompt as last resort
113 113 if symlink or binary:
114 114 return "internal:prompt", None
115 115 return "internal:merge", None
116 116
117 117 def _eoltype(data):
118 118 "Guess the EOL type of a file"
119 119 if '\0' in data: # binary
120 120 return None
121 121 if '\r\n' in data: # Windows
122 122 return '\r\n'
123 123 if '\r' in data: # Old Mac
124 124 return '\r'
125 125 if '\n' in data: # UNIX
126 126 return '\n'
127 127 return None # unknown
128 128
129 129 def _matcheol(file, origfile):
130 130 "Convert EOL markers in a file to match origfile"
131 131 tostyle = _eoltype(util.readfile(origfile))
132 132 if tostyle:
133 133 data = util.readfile(file)
134 134 style = _eoltype(data)
135 135 if style:
136 136 newdata = data.replace(style, tostyle)
137 137 if newdata != data:
138 138 util.writefile(file, newdata)
139 139
140 140 @internaltool('prompt', False)
141 141 def _iprompt(repo, mynode, orig, fcd, fco, fca, toolconf):
142 142 """Asks the user which of the local or the other version to keep as
143 143 the merged version."""
144 144 ui = repo.ui
145 145 fd = fcd.path()
146 146
147 147 if ui.promptchoice(_(" no tool found to merge %s\n"
148 148 "keep (l)ocal or take (o)ther?"
149 149 "$$ &Local $$ &Other") % fd, 0):
150 150 return _iother(repo, mynode, orig, fcd, fco, fca, toolconf)
151 151 else:
152 152 return _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf)
153 153
154 154 @internaltool('local', False)
155 155 def _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf):
156 156 """Uses the local version of files as the merged version."""
157 157 return 0
158 158
159 159 @internaltool('other', False)
160 160 def _iother(repo, mynode, orig, fcd, fco, fca, toolconf):
161 161 """Uses the other version of files as the merged version."""
162 162 repo.wwrite(fcd.path(), fco.data(), fco.flags())
163 163 return 0
164 164
165 165 @internaltool('fail', False)
166 166 def _ifail(repo, mynode, orig, fcd, fco, fca, toolconf):
167 167 """
168 168 Rather than attempting to merge files that were modified on both
169 169 branches, it marks them as unresolved. The resolve command must be
170 170 used to resolve these conflicts."""
171 171 return 1
172 172
173 173 def _premerge(repo, toolconf, files, labels=None):
174 174 tool, toolpath, binary, symlink = toolconf
175 175 if symlink:
176 176 return 1
177 177 a, b, c, back = files
178 178
179 179 ui = repo.ui
180 180
181 181 # do we attempt to simplemerge first?
182 182 try:
183 183 premerge = _toolbool(ui, tool, "premerge", not binary)
184 184 except error.ConfigError:
185 185 premerge = _toolstr(ui, tool, "premerge").lower()
186 186 valid = 'keep'.split()
187 187 if premerge not in valid:
188 188 _valid = ', '.join(["'" + v + "'" for v in valid])
189 189 raise error.ConfigError(_("%s.premerge not valid "
190 190 "('%s' is neither boolean nor %s)") %
191 191 (tool, premerge, _valid))
192 192
193 193 if premerge:
194 194 r = simplemerge.simplemerge(ui, a, b, c, quiet=True, label=labels)
195 195 if not r:
196 196 ui.debug(" premerge successful\n")
197 197 return 0
198 198 if premerge != 'keep':
199 199 util.copyfile(back, a) # restore from backup and try again
200 200 return 1 # continue merging
201 201
202 202 @internaltool('merge', True,
203 203 _("merging %s incomplete! "
204 204 "(edit conflicts, then use 'hg resolve --mark')\n"))
205 205 def _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
206 206 """
207 207 Uses the internal non-interactive simple merge algorithm for merging
208 208 files. It will fail if there are any conflicts and leave markers in
209 209 the partially merged file."""
210 210 tool, toolpath, binary, symlink = toolconf
211 211 if symlink:
212 212 repo.ui.warn(_('warning: internal:merge cannot merge symlinks '
213 213 'for %s\n') % fcd.path())
214 214 return False, 1
215 215 r = _premerge(repo, toolconf, files, labels=labels)
216 216 if r:
217 217 a, b, c, back = files
218 218
219 219 ui = repo.ui
220 220
221 221 r = simplemerge.simplemerge(ui, a, b, c, label=labels)
222 222 return True, r
223 223 return False, 0
224 224
225 225 @internaltool('tagmerge', True,
226 226 _("automatic tag merging of %s failed! "
227 227 "(use 'hg resolve --tool internal:merge' or another merge "
228 228 "tool of your choice)\n"))
229 229 def _itagmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
230 230 """
231 231 Uses the internal tag merge algorithm (experimental).
232 232 """
233 233 return tagmerge.merge(repo, fcd, fco, fca)
234 234
235 235 @internaltool('dump', True)
236 236 def _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
237 237 """
238 238 Creates three versions of the files to merge, containing the
239 239 contents of local, other and base. These files can then be used to
240 240 perform a merge manually. If the file to be merged is named
241 241 ``a.txt``, these files will accordingly be named ``a.txt.local``,
242 242 ``a.txt.other`` and ``a.txt.base`` and they will be placed in the
243 243 same directory as ``a.txt``."""
244 244 r = _premerge(repo, toolconf, files, labels=labels)
245 245 if r:
246 246 a, b, c, back = files
247 247
248 248 fd = fcd.path()
249 249
250 250 util.copyfile(a, a + ".local")
251 251 repo.wwrite(fd + ".other", fco.data(), fco.flags())
252 252 repo.wwrite(fd + ".base", fca.data(), fca.flags())
253 253 return False, r
254 254
255 255 def _xmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
256 256 r = _premerge(repo, toolconf, files, labels=labels)
257 257 if r:
258 258 tool, toolpath, binary, symlink = toolconf
259 259 a, b, c, back = files
260 260 out = ""
261 261 env = {'HG_FILE': fcd.path(),
262 262 'HG_MY_NODE': short(mynode),
263 263 'HG_OTHER_NODE': str(fco.changectx()),
264 264 'HG_BASE_NODE': str(fca.changectx()),
265 265 'HG_MY_ISLINK': 'l' in fcd.flags(),
266 266 'HG_OTHER_ISLINK': 'l' in fco.flags(),
267 267 'HG_BASE_ISLINK': 'l' in fca.flags(),
268 268 }
269 269
270 270 ui = repo.ui
271 271
272 272 args = _toolstr(ui, tool, "args", '$local $base $other')
273 273 if "$output" in args:
274 274 out, a = a, back # read input from backup, write to original
275 275 replace = {'local': a, 'base': b, 'other': c, 'output': out}
276 276 args = util.interpolate(r'\$', replace, args,
277 277 lambda s: util.shellquote(util.localpath(s)))
278 278 r = util.system(toolpath + ' ' + args, cwd=repo.root, environ=env,
279 279 out=ui.fout)
280 280 return True, r
281 281 return False, 0
282 282
283 283 def _formatconflictmarker(repo, ctx, template, label, pad):
284 284 """Applies the given template to the ctx, prefixed by the label.
285 285
286 286 Pad is the minimum width of the label prefix, so that multiple markers
287 287 can have aligned templated parts.
288 288 """
289 289 if ctx.node() is None:
290 290 ctx = ctx.p1()
291 291
292 292 props = templatekw.keywords.copy()
293 293 props['templ'] = template
294 294 props['ctx'] = ctx
295 295 props['repo'] = repo
296 296 templateresult = template('conflictmarker', **props)
297 297
298 298 label = ('%s:' % label).ljust(pad + 1)
299 299 mark = '%s %s' % (label, templater.stringify(templateresult))
300 300
301 301 if mark:
302 302 mark = mark.splitlines()[0] # split for safety
303 303
304 304 # 8 for the prefix of conflict marker lines (e.g. '<<<<<<< ')
305 305 return util.ellipsis(mark, 80 - 8)
306 306
307 307 _defaultconflictmarker = ('{node|short} ' +
308 308 '{ifeq(tags, "tip", "", "{tags} ")}' +
309 309 '{if(bookmarks, "{bookmarks} ")}' +
310 310 '{ifeq(branch, "default", "", "{branch} ")}' +
311 311 '- {author|user}: {desc|firstline}')
312 312
313 313 _defaultconflictlabels = ['local', 'other']
314 314
315 315 def _formatlabels(repo, fcd, fco, labels):
316 316 """Formats the given labels using the conflict marker template.
317 317
318 318 Returns a list of formatted labels.
319 319 """
320 320 cd = fcd.changectx()
321 321 co = fco.changectx()
322 322
323 323 ui = repo.ui
324 324 template = ui.config('ui', 'mergemarkertemplate', _defaultconflictmarker)
325 325 template = templater.parsestring(template, quoted=False)
326 tmpl = templater.templater(None, cache={ 'conflictmarker' : template })
326 tmpl = templater.templater(None, cache={'conflictmarker': template})
327 327
328 328 pad = max(len(labels[0]), len(labels[1]))
329 329
330 330 return [_formatconflictmarker(repo, cd, tmpl, labels[0], pad),
331 331 _formatconflictmarker(repo, co, tmpl, labels[1], pad)]
332 332
333 333 def filemerge(repo, mynode, orig, fcd, fco, fca, labels=None):
334 334 """perform a 3-way merge in the working directory
335 335
336 336 mynode = parent node before merge
337 337 orig = original local filename before merge
338 338 fco = other file context
339 339 fca = ancestor file context
340 340 fcd = local file context for current/destination file
341 341 """
342 342
343 343 def temp(prefix, ctx):
344 344 pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
345 345 (fd, name) = tempfile.mkstemp(prefix=pre)
346 346 data = repo.wwritedata(ctx.path(), ctx.data())
347 347 f = os.fdopen(fd, "wb")
348 348 f.write(data)
349 349 f.close()
350 350 return name
351 351
352 352 if not fco.cmp(fcd): # files identical?
353 353 return None
354 354
355 355 ui = repo.ui
356 356 fd = fcd.path()
357 357 binary = fcd.isbinary() or fco.isbinary() or fca.isbinary()
358 358 symlink = 'l' in fcd.flags() + fco.flags()
359 359 tool, toolpath = _picktool(repo, ui, fd, binary, symlink)
360 360 ui.debug("picked tool '%s' for %s (binary %s symlink %s)\n" %
361 361 (tool, fd, binary, symlink))
362 362
363 363 if tool in internals:
364 364 func = internals[tool]
365 365 trymerge = func.trymerge
366 366 onfailure = func.onfailure
367 367 else:
368 368 func = _xmerge
369 369 trymerge = True
370 370 onfailure = _("merging %s failed!\n")
371 371
372 372 toolconf = tool, toolpath, binary, symlink
373 373
374 374 if not trymerge:
375 375 return func(repo, mynode, orig, fcd, fco, fca, toolconf)
376 376
377 377 a = repo.wjoin(fd)
378 378 b = temp("base", fca)
379 379 c = temp("other", fco)
380 380 back = a + ".orig"
381 381 util.copyfile(a, back)
382 382
383 383 if orig != fco.path():
384 384 ui.status(_("merging %s and %s to %s\n") % (orig, fco.path(), fd))
385 385 else:
386 386 ui.status(_("merging %s\n") % fd)
387 387
388 388 ui.debug("my %s other %s ancestor %s\n" % (fcd, fco, fca))
389 389
390 390 markerstyle = ui.config('ui', 'mergemarkers', 'basic')
391 391 if not labels:
392 392 labels = _defaultconflictlabels
393 393 if markerstyle != 'basic':
394 394 labels = _formatlabels(repo, fcd, fco, labels)
395 395
396 396 needcheck, r = func(repo, mynode, orig, fcd, fco, fca, toolconf,
397 397 (a, b, c, back), labels=labels)
398 398 if not needcheck:
399 399 if r:
400 400 if onfailure:
401 401 ui.warn(onfailure % fd)
402 402 else:
403 403 util.unlink(back)
404 404
405 405 util.unlink(b)
406 406 util.unlink(c)
407 407 return r
408 408
409 409 if not r and (_toolbool(ui, tool, "checkconflicts") or
410 410 'conflicts' in _toollist(ui, tool, "check")):
411 411 if re.search("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcd.data(),
412 412 re.MULTILINE):
413 413 r = 1
414 414
415 415 checked = False
416 416 if 'prompt' in _toollist(ui, tool, "check"):
417 417 checked = True
418 418 if ui.promptchoice(_("was merge of '%s' successful (yn)?"
419 419 "$$ &Yes $$ &No") % fd, 1):
420 420 r = 1
421 421
422 422 if not r and not checked and (_toolbool(ui, tool, "checkchanged") or
423 423 'changed' in _toollist(ui, tool, "check")):
424 424 if filecmp.cmp(a, back):
425 425 if ui.promptchoice(_(" output file %s appears unchanged\n"
426 426 "was merge successful (yn)?"
427 427 "$$ &Yes $$ &No") % fd, 1):
428 428 r = 1
429 429
430 430 if _toolbool(ui, tool, "fixeol"):
431 431 _matcheol(a, back)
432 432
433 433 if r:
434 434 if onfailure:
435 435 ui.warn(onfailure % fd)
436 436 else:
437 437 util.unlink(back)
438 438
439 439 util.unlink(b)
440 440 util.unlink(c)
441 441 return r
442 442
443 443 # tell hggettext to extract docstrings from these functions:
444 444 i18nfunctions = internals.values()
General Comments 0
You need to be logged in to leave comments. Login now