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