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