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