##// END OF EJS Templates
filemerge: in ':prompt', use ':fail' tool rather than returning directly...
Siddharth Agarwal -
r27124:9c870e3e default
parent child Browse files
Show More
@@ -1,682 +1,682 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, 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 return 1, False
263 return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf)
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 # for change/delete conflicts write out the changed version, then fail
289 289 if fcd.isabsent():
290 290 repo.wwrite(fcd.path(), fco.data(), fco.flags())
291 291 return 1, False
292 292
293 293 def _premerge(repo, fcd, fco, fca, toolconf, files, labels=None):
294 294 tool, toolpath, binary, symlink = toolconf
295 295 if symlink or fcd.isabsent() or fco.isabsent():
296 296 return 1
297 297 a, b, c, back = files
298 298
299 299 ui = repo.ui
300 300
301 301 validkeep = ['keep', 'keep-merge3']
302 302
303 303 # do we attempt to simplemerge first?
304 304 try:
305 305 premerge = _toolbool(ui, tool, "premerge", not binary)
306 306 except error.ConfigError:
307 307 premerge = _toolstr(ui, tool, "premerge").lower()
308 308 if premerge not in validkeep:
309 309 _valid = ', '.join(["'" + v + "'" for v in validkeep])
310 310 raise error.ConfigError(_("%s.premerge not valid "
311 311 "('%s' is neither boolean nor %s)") %
312 312 (tool, premerge, _valid))
313 313
314 314 if premerge:
315 315 if premerge == 'keep-merge3':
316 316 if not labels:
317 317 labels = _defaultconflictlabels
318 318 if len(labels) < 3:
319 319 labels.append('base')
320 320 r = simplemerge.simplemerge(ui, a, b, c, quiet=True, label=labels)
321 321 if not r:
322 322 ui.debug(" premerge successful\n")
323 323 return 0
324 324 if premerge not in validkeep:
325 325 util.copyfile(back, a) # restore from backup and try again
326 326 return 1 # continue merging
327 327
328 328 def _mergecheck(repo, mynode, orig, fcd, fco, fca, toolconf):
329 329 tool, toolpath, binary, symlink = toolconf
330 330 if symlink:
331 331 repo.ui.warn(_('warning: internal %s cannot merge symlinks '
332 332 'for %s\n') % (tool, fcd.path()))
333 333 return False
334 334 if fcd.isabsent() or fco.isabsent():
335 335 repo.ui.warn(_('warning: internal %s cannot merge change/delete '
336 336 'conflict for %s\n') % (tool, fcd.path()))
337 337 return False
338 338 return True
339 339
340 340 def _merge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels, mode):
341 341 """
342 342 Uses the internal non-interactive simple merge algorithm for merging
343 343 files. It will fail if there are any conflicts and leave markers in
344 344 the partially merged file. Markers will have two sections, one for each side
345 345 of merge, unless mode equals 'union' which suppresses the markers."""
346 346 a, b, c, back = files
347 347
348 348 ui = repo.ui
349 349
350 350 r = simplemerge.simplemerge(ui, a, b, c, label=labels, mode=mode)
351 351 return True, r, False
352 352
353 353 @internaltool('union', fullmerge,
354 354 _("warning: conflicts while merging %s! "
355 355 "(edit, then use 'hg resolve --mark')\n"),
356 356 precheck=_mergecheck)
357 357 def _iunion(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
358 358 """
359 359 Uses the internal non-interactive simple merge algorithm for merging
360 360 files. It will use both left and right sides for conflict regions.
361 361 No markers are inserted."""
362 362 return _merge(repo, mynode, orig, fcd, fco, fca, toolconf,
363 363 files, labels, 'union')
364 364
365 365 @internaltool('merge', fullmerge,
366 366 _("warning: conflicts while merging %s! "
367 367 "(edit, then use 'hg resolve --mark')\n"),
368 368 precheck=_mergecheck)
369 369 def _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
370 370 """
371 371 Uses the internal non-interactive simple merge algorithm for merging
372 372 files. It will fail if there are any conflicts and leave markers in
373 373 the partially merged file. Markers will have two sections, one for each side
374 374 of merge."""
375 375 return _merge(repo, mynode, orig, fcd, fco, fca, toolconf,
376 376 files, labels, 'merge')
377 377
378 378 @internaltool('merge3', fullmerge,
379 379 _("warning: conflicts while merging %s! "
380 380 "(edit, then use 'hg resolve --mark')\n"),
381 381 precheck=_mergecheck)
382 382 def _imerge3(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
383 383 """
384 384 Uses the internal non-interactive simple merge algorithm for merging
385 385 files. It will fail if there are any conflicts and leave markers in
386 386 the partially merged file. Marker will have three sections, one from each
387 387 side of the merge and one for the base content."""
388 388 if not labels:
389 389 labels = _defaultconflictlabels
390 390 if len(labels) < 3:
391 391 labels.append('base')
392 392 return _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels)
393 393
394 394 def _imergeauto(repo, mynode, orig, fcd, fco, fca, toolconf, files,
395 395 labels=None, localorother=None):
396 396 """
397 397 Generic driver for _imergelocal and _imergeother
398 398 """
399 399 assert localorother is not None
400 400 tool, toolpath, binary, symlink = toolconf
401 401 a, b, c, back = files
402 402 r = simplemerge.simplemerge(repo.ui, a, b, c, label=labels,
403 403 localorother=localorother)
404 404 return True, r
405 405
406 406 @internaltool('merge-local', mergeonly, precheck=_mergecheck)
407 407 def _imergelocal(*args, **kwargs):
408 408 """
409 409 Like :merge, but resolve all conflicts non-interactively in favor
410 410 of the local changes."""
411 411 success, status = _imergeauto(localorother='local', *args, **kwargs)
412 412 return success, status, False
413 413
414 414 @internaltool('merge-other', mergeonly, precheck=_mergecheck)
415 415 def _imergeother(*args, **kwargs):
416 416 """
417 417 Like :merge, but resolve all conflicts non-interactively in favor
418 418 of the other changes."""
419 419 success, status = _imergeauto(localorother='other', *args, **kwargs)
420 420 return success, status, False
421 421
422 422 @internaltool('tagmerge', mergeonly,
423 423 _("automatic tag merging of %s failed! "
424 424 "(use 'hg resolve --tool :merge' or another merge "
425 425 "tool of your choice)\n"))
426 426 def _itagmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
427 427 """
428 428 Uses the internal tag merge algorithm (experimental).
429 429 """
430 430 success, status = tagmerge.merge(repo, fcd, fco, fca)
431 431 return success, status, False
432 432
433 433 @internaltool('dump', fullmerge)
434 434 def _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
435 435 """
436 436 Creates three versions of the files to merge, containing the
437 437 contents of local, other and base. These files can then be used to
438 438 perform a merge manually. If the file to be merged is named
439 439 ``a.txt``, these files will accordingly be named ``a.txt.local``,
440 440 ``a.txt.other`` and ``a.txt.base`` and they will be placed in the
441 441 same directory as ``a.txt``."""
442 442 a, b, c, back = files
443 443
444 444 fd = fcd.path()
445 445
446 446 util.copyfile(a, a + ".local")
447 447 repo.wwrite(fd + ".other", fco.data(), fco.flags())
448 448 repo.wwrite(fd + ".base", fca.data(), fca.flags())
449 449 return False, 1, False
450 450
451 451 def _xmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
452 452 tool, toolpath, binary, symlink = toolconf
453 453 if fcd.isabsent() or fco.isabsent():
454 454 repo.ui.warn(_('warning: %s cannot merge change/delete conflict '
455 455 'for %s\n') % (tool, fcd.path()))
456 456 return False, 1, None
457 457 a, b, c, back = files
458 458 out = ""
459 459 env = {'HG_FILE': fcd.path(),
460 460 'HG_MY_NODE': short(mynode),
461 461 'HG_OTHER_NODE': str(fco.changectx()),
462 462 'HG_BASE_NODE': str(fca.changectx()),
463 463 'HG_MY_ISLINK': 'l' in fcd.flags(),
464 464 'HG_OTHER_ISLINK': 'l' in fco.flags(),
465 465 'HG_BASE_ISLINK': 'l' in fca.flags(),
466 466 }
467 467
468 468 ui = repo.ui
469 469
470 470 args = _toolstr(ui, tool, "args", '$local $base $other')
471 471 if "$output" in args:
472 472 out, a = a, back # read input from backup, write to original
473 473 replace = {'local': a, 'base': b, 'other': c, 'output': out}
474 474 args = util.interpolate(r'\$', replace, args,
475 475 lambda s: util.shellquote(util.localpath(s)))
476 476 cmd = toolpath + ' ' + args
477 477 repo.ui.debug('launching merge tool: %s\n' % cmd)
478 478 r = ui.system(cmd, cwd=repo.root, environ=env)
479 479 repo.ui.debug('merge tool returned: %s\n' % r)
480 480 return True, r, False
481 481
482 482 def _formatconflictmarker(repo, ctx, template, label, pad):
483 483 """Applies the given template to the ctx, prefixed by the label.
484 484
485 485 Pad is the minimum width of the label prefix, so that multiple markers
486 486 can have aligned templated parts.
487 487 """
488 488 if ctx.node() is None:
489 489 ctx = ctx.p1()
490 490
491 491 props = templatekw.keywords.copy()
492 492 props['templ'] = template
493 493 props['ctx'] = ctx
494 494 props['repo'] = repo
495 495 templateresult = template('conflictmarker', **props)
496 496
497 497 label = ('%s:' % label).ljust(pad + 1)
498 498 mark = '%s %s' % (label, templater.stringify(templateresult))
499 499
500 500 if mark:
501 501 mark = mark.splitlines()[0] # split for safety
502 502
503 503 # 8 for the prefix of conflict marker lines (e.g. '<<<<<<< ')
504 504 return util.ellipsis(mark, 80 - 8)
505 505
506 506 _defaultconflictmarker = ('{node|short} ' +
507 507 '{ifeq(tags, "tip", "", "{tags} ")}' +
508 508 '{if(bookmarks, "{bookmarks} ")}' +
509 509 '{ifeq(branch, "default", "", "{branch} ")}' +
510 510 '- {author|user}: {desc|firstline}')
511 511
512 512 _defaultconflictlabels = ['local', 'other']
513 513
514 514 def _formatlabels(repo, fcd, fco, fca, labels):
515 515 """Formats the given labels using the conflict marker template.
516 516
517 517 Returns a list of formatted labels.
518 518 """
519 519 cd = fcd.changectx()
520 520 co = fco.changectx()
521 521 ca = fca.changectx()
522 522
523 523 ui = repo.ui
524 524 template = ui.config('ui', 'mergemarkertemplate', _defaultconflictmarker)
525 525 tmpl = templater.templater(None, cache={'conflictmarker': template})
526 526
527 527 pad = max(len(l) for l in labels)
528 528
529 529 newlabels = [_formatconflictmarker(repo, cd, tmpl, labels[0], pad),
530 530 _formatconflictmarker(repo, co, tmpl, labels[1], pad)]
531 531 if len(labels) > 2:
532 532 newlabels.append(_formatconflictmarker(repo, ca, tmpl, labels[2], pad))
533 533 return newlabels
534 534
535 535 def _filemerge(premerge, repo, mynode, orig, fcd, fco, fca, labels=None):
536 536 """perform a 3-way merge in the working directory
537 537
538 538 premerge = whether this is a premerge
539 539 mynode = parent node before merge
540 540 orig = original local filename before merge
541 541 fco = other file context
542 542 fca = ancestor file context
543 543 fcd = local file context for current/destination file
544 544
545 545 Returns whether the merge is complete, the return value of the merge, and
546 546 a boolean indicating whether the file was deleted from disk."""
547 547
548 548 def temp(prefix, ctx):
549 549 pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
550 550 (fd, name) = tempfile.mkstemp(prefix=pre)
551 551 data = repo.wwritedata(ctx.path(), ctx.data())
552 552 f = os.fdopen(fd, "wb")
553 553 f.write(data)
554 554 f.close()
555 555 return name
556 556
557 557 if not fco.cmp(fcd): # files identical?
558 558 return True, None, False
559 559
560 560 ui = repo.ui
561 561 fd = fcd.path()
562 562 binary = fcd.isbinary() or fco.isbinary() or fca.isbinary()
563 563 symlink = 'l' in fcd.flags() + fco.flags()
564 564 changedelete = fcd.isabsent() or fco.isabsent()
565 565 tool, toolpath = _picktool(repo, ui, fd, binary, symlink, changedelete)
566 566 if tool in internals and tool.startswith('internal:'):
567 567 # normalize to new-style names (':merge' etc)
568 568 tool = tool[len('internal'):]
569 569 ui.debug("picked tool '%s' for %s (binary %s symlink %s)\n" %
570 570 (tool, fd, binary, symlink))
571 571
572 572 if tool in internals:
573 573 func = internals[tool]
574 574 mergetype = func.mergetype
575 575 onfailure = func.onfailure
576 576 precheck = func.precheck
577 577 else:
578 578 func = _xmerge
579 579 mergetype = fullmerge
580 580 onfailure = _("merging %s failed!\n")
581 581 precheck = None
582 582
583 583 toolconf = tool, toolpath, binary, symlink
584 584
585 585 if mergetype == nomerge:
586 586 r, deleted = func(repo, mynode, orig, fcd, fco, fca, toolconf)
587 587 return True, r, deleted
588 588
589 589 if premerge:
590 590 if orig != fco.path():
591 591 ui.status(_("merging %s and %s to %s\n") % (orig, fco.path(), fd))
592 592 else:
593 593 ui.status(_("merging %s\n") % fd)
594 594
595 595 ui.debug("my %s other %s ancestor %s\n" % (fcd, fco, fca))
596 596
597 597 if precheck and not precheck(repo, mynode, orig, fcd, fco, fca,
598 598 toolconf):
599 599 if onfailure:
600 600 ui.warn(onfailure % fd)
601 601 return True, 1, False
602 602
603 603 a = repo.wjoin(fd)
604 604 b = temp("base", fca)
605 605 c = temp("other", fco)
606 606 if not fcd.isabsent():
607 607 back = cmdutil.origpath(ui, repo, a)
608 608 if premerge:
609 609 util.copyfile(a, back)
610 610 else:
611 611 back = None
612 612 files = (a, b, c, back)
613 613
614 614 r = 1
615 615 try:
616 616 markerstyle = ui.config('ui', 'mergemarkers', 'basic')
617 617 if not labels:
618 618 labels = _defaultconflictlabels
619 619 if markerstyle != 'basic':
620 620 labels = _formatlabels(repo, fcd, fco, fca, labels)
621 621
622 622 if premerge and mergetype == fullmerge:
623 623 r = _premerge(repo, fcd, fco, fca, toolconf, files, labels=labels)
624 624 # complete if premerge successful (r is 0)
625 625 return not r, r, False
626 626
627 627 needcheck, r, deleted = func(repo, mynode, orig, fcd, fco, fca,
628 628 toolconf, files, labels=labels)
629 629
630 630 if needcheck:
631 631 r = _check(r, ui, tool, fcd, files)
632 632
633 633 if r:
634 634 if onfailure:
635 635 ui.warn(onfailure % fd)
636 636
637 637 return True, r, deleted
638 638 finally:
639 639 if not r and back is not None:
640 640 util.unlink(back)
641 641 util.unlink(b)
642 642 util.unlink(c)
643 643
644 644 def _check(r, ui, tool, fcd, files):
645 645 fd = fcd.path()
646 646 a, b, c, back = files
647 647
648 648 if not r and (_toolbool(ui, tool, "checkconflicts") or
649 649 'conflicts' in _toollist(ui, tool, "check")):
650 650 if re.search("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcd.data(),
651 651 re.MULTILINE):
652 652 r = 1
653 653
654 654 checked = False
655 655 if 'prompt' in _toollist(ui, tool, "check"):
656 656 checked = True
657 657 if ui.promptchoice(_("was merge of '%s' successful (yn)?"
658 658 "$$ &Yes $$ &No") % fd, 1):
659 659 r = 1
660 660
661 661 if not r and not checked and (_toolbool(ui, tool, "checkchanged") or
662 662 'changed' in
663 663 _toollist(ui, tool, "check")):
664 664 if back is not None and filecmp.cmp(a, back):
665 665 if ui.promptchoice(_(" output file %s appears unchanged\n"
666 666 "was merge successful (yn)?"
667 667 "$$ &Yes $$ &No") % fd, 1):
668 668 r = 1
669 669
670 670 if back is not None and _toolbool(ui, tool, "fixeol"):
671 671 _matcheol(a, back)
672 672
673 673 return r
674 674
675 675 def premerge(repo, mynode, orig, fcd, fco, fca, labels=None):
676 676 return _filemerge(True, repo, mynode, orig, fcd, fco, fca, labels=labels)
677 677
678 678 def filemerge(repo, mynode, orig, fcd, fco, fca, labels=None):
679 679 return _filemerge(False, repo, mynode, orig, fcd, fco, fca, labels=labels)
680 680
681 681 # tell hggettext to extract docstrings from these functions:
682 682 i18nfunctions = internals.values()
General Comments 0
You need to be logged in to leave comments. Login now