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