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