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