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