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