##// END OF EJS Templates
filemerge: remove unused arguments from `_merge()`...
Martin von Zweigbergk -
r49428:f970bc61 default
parent child Browse files
Show More
@@ -1,1290 +1,1278 b''
1 1 # filemerge.py - file-level merge handling for Mercurial
2 2 #
3 3 # Copyright 2006, 2007, 2008 Olivia Mackall <olivia@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 contextlib
11 11 import os
12 12 import re
13 13 import shutil
14 14
15 15 from .i18n import _
16 16 from .node import (
17 17 hex,
18 18 short,
19 19 )
20 20 from .pycompat import (
21 21 getattr,
22 22 open,
23 23 )
24 24
25 25 from . import (
26 26 encoding,
27 27 error,
28 28 formatter,
29 29 match,
30 30 pycompat,
31 31 registrar,
32 32 scmutil,
33 33 simplemerge,
34 34 tagmerge,
35 35 templatekw,
36 36 templater,
37 37 templateutil,
38 38 util,
39 39 )
40 40
41 41 from .utils import (
42 42 procutil,
43 43 stringutil,
44 44 )
45 45
46 46
47 47 def _toolstr(ui, tool, part, *args):
48 48 return ui.config(b"merge-tools", tool + b"." + part, *args)
49 49
50 50
51 51 def _toolbool(ui, tool, part, *args):
52 52 return ui.configbool(b"merge-tools", tool + b"." + part, *args)
53 53
54 54
55 55 def _toollist(ui, tool, part):
56 56 return ui.configlist(b"merge-tools", tool + b"." + part)
57 57
58 58
59 59 internals = {}
60 60 # Merge tools to document.
61 61 internalsdoc = {}
62 62
63 63 internaltool = registrar.internalmerge()
64 64
65 65 # internal tool merge types
66 66 nomerge = internaltool.nomerge
67 67 mergeonly = internaltool.mergeonly # just the full merge, no premerge
68 68 fullmerge = internaltool.fullmerge # both premerge and merge
69 69
70 70 # IMPORTANT: keep the last line of this prompt very short ("What do you want to
71 71 # do?") because of issue6158, ideally to <40 English characters (to allow other
72 72 # languages that may take more columns to still have a chance to fit in an
73 73 # 80-column screen).
74 74 _localchangedotherdeletedmsg = _(
75 75 b"file '%(fd)s' was deleted in other%(o)s but was modified in local%(l)s.\n"
76 76 b"You can use (c)hanged version, (d)elete, or leave (u)nresolved.\n"
77 77 b"What do you want to do?"
78 78 b"$$ &Changed $$ &Delete $$ &Unresolved"
79 79 )
80 80
81 81 _otherchangedlocaldeletedmsg = _(
82 82 b"file '%(fd)s' was deleted in local%(l)s but was modified in other%(o)s.\n"
83 83 b"You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved.\n"
84 84 b"What do you want to do?"
85 85 b"$$ &Changed $$ &Deleted $$ &Unresolved"
86 86 )
87 87
88 88
89 89 class absentfilectx(object):
90 90 """Represents a file that's ostensibly in a context but is actually not
91 91 present in it.
92 92
93 93 This is here because it's very specific to the filemerge code for now --
94 94 other code is likely going to break with the values this returns."""
95 95
96 96 def __init__(self, ctx, f):
97 97 self._ctx = ctx
98 98 self._f = f
99 99
100 100 def __bytes__(self):
101 101 return b'absent file %s@%s' % (self._f, self._ctx)
102 102
103 103 def path(self):
104 104 return self._f
105 105
106 106 def size(self):
107 107 return None
108 108
109 109 def data(self):
110 110 return None
111 111
112 112 def filenode(self):
113 113 return self._ctx.repo().nullid
114 114
115 115 _customcmp = True
116 116
117 117 def cmp(self, fctx):
118 118 """compare with other file context
119 119
120 120 returns True if different from fctx.
121 121 """
122 122 return not (
123 123 fctx.isabsent()
124 124 and fctx.changectx() == self.changectx()
125 125 and fctx.path() == self.path()
126 126 )
127 127
128 128 def flags(self):
129 129 return b''
130 130
131 131 def changectx(self):
132 132 return self._ctx
133 133
134 134 def isbinary(self):
135 135 return False
136 136
137 137 def isabsent(self):
138 138 return True
139 139
140 140
141 141 def _findtool(ui, tool):
142 142 if tool in internals:
143 143 return tool
144 144 cmd = _toolstr(ui, tool, b"executable", tool)
145 145 if cmd.startswith(b'python:'):
146 146 return cmd
147 147 return findexternaltool(ui, tool)
148 148
149 149
150 150 def _quotetoolpath(cmd):
151 151 if cmd.startswith(b'python:'):
152 152 return cmd
153 153 return procutil.shellquote(cmd)
154 154
155 155
156 156 def findexternaltool(ui, tool):
157 157 for kn in (b"regkey", b"regkeyalt"):
158 158 k = _toolstr(ui, tool, kn)
159 159 if not k:
160 160 continue
161 161 p = util.lookupreg(k, _toolstr(ui, tool, b"regname"))
162 162 if p:
163 163 p = procutil.findexe(p + _toolstr(ui, tool, b"regappend", b""))
164 164 if p:
165 165 return p
166 166 exe = _toolstr(ui, tool, b"executable", tool)
167 167 return procutil.findexe(util.expandpath(exe))
168 168
169 169
170 170 def _picktool(repo, ui, path, binary, symlink, changedelete):
171 171 strictcheck = ui.configbool(b'merge', b'strict-capability-check')
172 172
173 173 def hascapability(tool, capability, strict=False):
174 174 if tool in internals:
175 175 return strict and internals[tool].capabilities.get(capability)
176 176 return _toolbool(ui, tool, capability)
177 177
178 178 def supportscd(tool):
179 179 return tool in internals and internals[tool].mergetype == nomerge
180 180
181 181 def check(tool, pat, symlink, binary, changedelete):
182 182 tmsg = tool
183 183 if pat:
184 184 tmsg = _(b"%s (for pattern %s)") % (tool, pat)
185 185 if not _findtool(ui, tool):
186 186 if pat: # explicitly requested tool deserves a warning
187 187 ui.warn(_(b"couldn't find merge tool %s\n") % tmsg)
188 188 else: # configured but non-existing tools are more silent
189 189 ui.note(_(b"couldn't find merge tool %s\n") % tmsg)
190 190 elif symlink and not hascapability(tool, b"symlink", strictcheck):
191 191 ui.warn(_(b"tool %s can't handle symlinks\n") % tmsg)
192 192 elif binary and not hascapability(tool, b"binary", strictcheck):
193 193 ui.warn(_(b"tool %s can't handle binary\n") % tmsg)
194 194 elif changedelete and not supportscd(tool):
195 195 # the nomerge tools are the only tools that support change/delete
196 196 # conflicts
197 197 pass
198 198 elif not procutil.gui() and _toolbool(ui, tool, b"gui"):
199 199 ui.warn(_(b"tool %s requires a GUI\n") % tmsg)
200 200 else:
201 201 return True
202 202 return False
203 203
204 204 # internal config: ui.forcemerge
205 205 # forcemerge comes from command line arguments, highest priority
206 206 force = ui.config(b'ui', b'forcemerge')
207 207 if force:
208 208 toolpath = _findtool(ui, force)
209 209 if changedelete and not supportscd(toolpath):
210 210 return b":prompt", None
211 211 else:
212 212 if toolpath:
213 213 return (force, _quotetoolpath(toolpath))
214 214 else:
215 215 # mimic HGMERGE if given tool not found
216 216 return (force, force)
217 217
218 218 # HGMERGE takes next precedence
219 219 hgmerge = encoding.environ.get(b"HGMERGE")
220 220 if hgmerge:
221 221 if changedelete and not supportscd(hgmerge):
222 222 return b":prompt", None
223 223 else:
224 224 return (hgmerge, hgmerge)
225 225
226 226 # then patterns
227 227
228 228 # whether binary capability should be checked strictly
229 229 binarycap = binary and strictcheck
230 230
231 231 for pat, tool in ui.configitems(b"merge-patterns"):
232 232 mf = match.match(repo.root, b'', [pat])
233 233 if mf(path) and check(tool, pat, symlink, binarycap, changedelete):
234 234 if binary and not hascapability(tool, b"binary", strict=True):
235 235 ui.warn(
236 236 _(
237 237 b"warning: check merge-patterns configurations,"
238 238 b" if %r for binary file %r is unintentional\n"
239 239 b"(see 'hg help merge-tools'"
240 240 b" for binary files capability)\n"
241 241 )
242 242 % (pycompat.bytestr(tool), pycompat.bytestr(path))
243 243 )
244 244 toolpath = _findtool(ui, tool)
245 245 return (tool, _quotetoolpath(toolpath))
246 246
247 247 # then merge tools
248 248 tools = {}
249 249 disabled = set()
250 250 for k, v in ui.configitems(b"merge-tools"):
251 251 t = k.split(b'.')[0]
252 252 if t not in tools:
253 253 tools[t] = int(_toolstr(ui, t, b"priority"))
254 254 if _toolbool(ui, t, b"disabled"):
255 255 disabled.add(t)
256 256 names = tools.keys()
257 257 tools = sorted(
258 258 [(-p, tool) for tool, p in tools.items() if tool not in disabled]
259 259 )
260 260 uimerge = ui.config(b"ui", b"merge")
261 261 if uimerge:
262 262 # external tools defined in uimerge won't be able to handle
263 263 # change/delete conflicts
264 264 if check(uimerge, path, symlink, binary, changedelete):
265 265 if uimerge not in names and not changedelete:
266 266 return (uimerge, uimerge)
267 267 tools.insert(0, (None, uimerge)) # highest priority
268 268 tools.append((None, b"hgmerge")) # the old default, if found
269 269 for p, t in tools:
270 270 if check(t, None, symlink, binary, changedelete):
271 271 toolpath = _findtool(ui, t)
272 272 return (t, _quotetoolpath(toolpath))
273 273
274 274 # internal merge or prompt as last resort
275 275 if symlink or binary or changedelete:
276 276 if not changedelete and len(tools):
277 277 # any tool is rejected by capability for symlink or binary
278 278 ui.warn(_(b"no tool found to merge %s\n") % path)
279 279 return b":prompt", None
280 280 return b":merge", None
281 281
282 282
283 283 def _eoltype(data):
284 284 """Guess the EOL type of a file"""
285 285 if b'\0' in data: # binary
286 286 return None
287 287 if b'\r\n' in data: # Windows
288 288 return b'\r\n'
289 289 if b'\r' in data: # Old Mac
290 290 return b'\r'
291 291 if b'\n' in data: # UNIX
292 292 return b'\n'
293 293 return None # unknown
294 294
295 295
296 296 def _matcheol(file, backup):
297 297 """Convert EOL markers in a file to match origfile"""
298 298 tostyle = _eoltype(backup.data()) # No repo.wread filters?
299 299 if tostyle:
300 300 data = util.readfile(file)
301 301 style = _eoltype(data)
302 302 if style:
303 303 newdata = data.replace(style, tostyle)
304 304 if newdata != data:
305 305 util.writefile(file, newdata)
306 306
307 307
308 308 @internaltool(b'prompt', nomerge)
309 309 def _iprompt(repo, mynode, fcd, fco, fca, toolconf, labels=None):
310 310 """Asks the user which of the local `p1()` or the other `p2()` version to
311 311 keep as the merged version."""
312 312 ui = repo.ui
313 313 fd = fcd.path()
314 314 uipathfn = scmutil.getuipathfn(repo)
315 315
316 316 # Avoid prompting during an in-memory merge since it doesn't support merge
317 317 # conflicts.
318 318 if fcd.changectx().isinmemory():
319 319 raise error.InMemoryMergeConflictsError(
320 320 b'in-memory merge does not support file conflicts'
321 321 )
322 322
323 323 prompts = partextras(labels)
324 324 prompts[b'fd'] = uipathfn(fd)
325 325 try:
326 326 if fco.isabsent():
327 327 index = ui.promptchoice(_localchangedotherdeletedmsg % prompts, 2)
328 328 choice = [b'local', b'other', b'unresolved'][index]
329 329 elif fcd.isabsent():
330 330 index = ui.promptchoice(_otherchangedlocaldeletedmsg % prompts, 2)
331 331 choice = [b'other', b'local', b'unresolved'][index]
332 332 else:
333 333 # IMPORTANT: keep the last line of this prompt ("What do you want to
334 334 # do?") very short, see comment next to _localchangedotherdeletedmsg
335 335 # at the top of the file for details.
336 336 index = ui.promptchoice(
337 337 _(
338 338 b"file '%(fd)s' needs to be resolved.\n"
339 339 b"You can keep (l)ocal%(l)s, take (o)ther%(o)s, or leave "
340 340 b"(u)nresolved.\n"
341 341 b"What do you want to do?"
342 342 b"$$ &Local $$ &Other $$ &Unresolved"
343 343 )
344 344 % prompts,
345 345 2,
346 346 )
347 347 choice = [b'local', b'other', b'unresolved'][index]
348 348
349 349 if choice == b'other':
350 350 return _iother(repo, mynode, fcd, fco, fca, toolconf, labels)
351 351 elif choice == b'local':
352 352 return _ilocal(repo, mynode, fcd, fco, fca, toolconf, labels)
353 353 elif choice == b'unresolved':
354 354 return _ifail(repo, mynode, fcd, fco, fca, toolconf, labels)
355 355 except error.ResponseExpected:
356 356 ui.write(b"\n")
357 357 return _ifail(repo, mynode, fcd, fco, fca, toolconf, labels)
358 358
359 359
360 360 @internaltool(b'local', nomerge)
361 361 def _ilocal(repo, mynode, fcd, fco, fca, toolconf, labels=None):
362 362 """Uses the local `p1()` version of files as the merged version."""
363 363 return 0, fcd.isabsent()
364 364
365 365
366 366 @internaltool(b'other', nomerge)
367 367 def _iother(repo, mynode, fcd, fco, fca, toolconf, labels=None):
368 368 """Uses the other `p2()` version of files as the merged version."""
369 369 if fco.isabsent():
370 370 # local changed, remote deleted -- 'deleted' picked
371 371 _underlyingfctxifabsent(fcd).remove()
372 372 deleted = True
373 373 else:
374 374 _underlyingfctxifabsent(fcd).write(fco.data(), fco.flags())
375 375 deleted = False
376 376 return 0, deleted
377 377
378 378
379 379 @internaltool(b'fail', nomerge)
380 380 def _ifail(repo, mynode, fcd, fco, fca, toolconf, labels=None):
381 381 """
382 382 Rather than attempting to merge files that were modified on both
383 383 branches, it marks them as unresolved. The resolve command must be
384 384 used to resolve these conflicts."""
385 385 # for change/delete conflicts write out the changed version, then fail
386 386 if fcd.isabsent():
387 387 _underlyingfctxifabsent(fcd).write(fco.data(), fco.flags())
388 388 return 1, False
389 389
390 390
391 391 def _underlyingfctxifabsent(filectx):
392 392 """Sometimes when resolving, our fcd is actually an absentfilectx, but
393 393 we want to write to it (to do the resolve). This helper returns the
394 394 underyling workingfilectx in that case.
395 395 """
396 396 if filectx.isabsent():
397 397 return filectx.changectx()[filectx.path()]
398 398 else:
399 399 return filectx
400 400
401 401
402 402 def _premerge(repo, fcd, fco, fca, toolconf, backup, labels):
403 403 tool, toolpath, binary, symlink, scriptfn = toolconf
404 404 if symlink or fcd.isabsent() or fco.isabsent():
405 405 return 1
406 406
407 407 ui = repo.ui
408 408
409 409 validkeep = [b'keep', b'keep-merge3', b'keep-mergediff']
410 410
411 411 # do we attempt to simplemerge first?
412 412 try:
413 413 premerge = _toolbool(ui, tool, b"premerge", not binary)
414 414 except error.ConfigError:
415 415 premerge = _toolstr(ui, tool, b"premerge", b"").lower()
416 416 if premerge not in validkeep:
417 417 _valid = b', '.join([b"'" + v + b"'" for v in validkeep])
418 418 raise error.ConfigError(
419 419 _(b"%s.premerge not valid ('%s' is neither boolean nor %s)")
420 420 % (tool, premerge, _valid)
421 421 )
422 422
423 423 if premerge:
424 424 if len(labels) < 3:
425 425 labels.append(b'base')
426 426 mode = b'merge'
427 427 if premerge == b'keep-mergediff':
428 428 mode = b'mergediff'
429 429 elif premerge == b'keep-merge3':
430 430 mode = b'merge3'
431 431 local = simplemerge.MergeInput(fcd, labels[0])
432 432 other = simplemerge.MergeInput(fco, labels[1])
433 433 base = simplemerge.MergeInput(fca, labels[2])
434 434 r = simplemerge.simplemerge(
435 435 ui, local, base, other, quiet=True, mode=mode
436 436 )
437 437 if not r:
438 438 ui.debug(b" premerge successful\n")
439 439 return 0
440 440 if premerge not in validkeep:
441 441 # restore from backup and try again
442 442 _restorebackup(fcd, backup)
443 443 return 1 # continue merging
444 444
445 445
446 446 def _mergecheck(repo, mynode, fcd, fco, fca, toolconf):
447 447 tool, toolpath, binary, symlink, scriptfn = toolconf
448 448 uipathfn = scmutil.getuipathfn(repo)
449 449 if symlink:
450 450 repo.ui.warn(
451 451 _(b'warning: internal %s cannot merge symlinks for %s\n')
452 452 % (tool, uipathfn(fcd.path()))
453 453 )
454 454 return False
455 455 if fcd.isabsent() or fco.isabsent():
456 456 repo.ui.warn(
457 457 _(
458 458 b'warning: internal %s cannot merge change/delete '
459 459 b'conflict for %s\n'
460 460 )
461 461 % (tool, uipathfn(fcd.path()))
462 462 )
463 463 return False
464 464 return True
465 465
466 466
467 def _merge(repo, mynode, fcd, fco, fca, toolconf, backup, labels, mode):
467 def _merge(repo, fcd, fco, fca, labels, mode):
468 468 """
469 469 Uses the internal non-interactive simple merge algorithm for merging
470 470 files. It will fail if there are any conflicts and leave markers in
471 471 the partially merged file. Markers will have two sections, one for each side
472 472 of merge, unless mode equals 'union' which suppresses the markers."""
473 473 ui = repo.ui
474 474
475 475 local = simplemerge.MergeInput(fcd)
476 476 if len(labels) > 0:
477 477 local.label = labels[0]
478 478 other = simplemerge.MergeInput(fco)
479 479 if len(labels) > 1:
480 480 other.label = labels[1]
481 481 base = simplemerge.MergeInput(fca)
482 482 if len(labels) > 2:
483 483 base.label = labels[2]
484 484 r = simplemerge.simplemerge(ui, local, base, other, mode=mode)
485 485 return True, r, False
486 486
487 487
488 488 @internaltool(
489 489 b'union',
490 490 fullmerge,
491 491 _(
492 492 b"warning: conflicts while merging %s! "
493 493 b"(edit, then use 'hg resolve --mark')\n"
494 494 ),
495 495 precheck=_mergecheck,
496 496 )
497 497 def _iunion(repo, mynode, fcd, fco, fca, toolconf, backup, labels=None):
498 498 """
499 499 Uses the internal non-interactive simple merge algorithm for merging
500 500 files. It will use both left and right sides for conflict regions.
501 501 No markers are inserted."""
502 return _merge(
503 repo, mynode, fcd, fco, fca, toolconf, backup, labels, b'union'
504 )
502 return _merge(repo, fcd, fco, fca, labels, b'union')
505 503
506 504
507 505 @internaltool(
508 506 b'merge',
509 507 fullmerge,
510 508 _(
511 509 b"warning: conflicts while merging %s! "
512 510 b"(edit, then use 'hg resolve --mark')\n"
513 511 ),
514 512 precheck=_mergecheck,
515 513 )
516 514 def _imerge(repo, mynode, fcd, fco, fca, toolconf, backup, labels=None):
517 515 """
518 516 Uses the internal non-interactive simple merge algorithm for merging
519 517 files. It will fail if there are any conflicts and leave markers in
520 518 the partially merged file. Markers will have two sections, one for each side
521 519 of merge."""
522 return _merge(
523 repo, mynode, fcd, fco, fca, toolconf, backup, labels, b'merge'
524 )
520 return _merge(repo, fcd, fco, fca, labels, b'merge')
525 521
526 522
527 523 @internaltool(
528 524 b'merge3',
529 525 fullmerge,
530 526 _(
531 527 b"warning: conflicts while merging %s! "
532 528 b"(edit, then use 'hg resolve --mark')\n"
533 529 ),
534 530 precheck=_mergecheck,
535 531 )
536 532 def _imerge3(repo, mynode, fcd, fco, fca, toolconf, backup, labels=None):
537 533 """
538 534 Uses the internal non-interactive simple merge algorithm for merging
539 535 files. It will fail if there are any conflicts and leave markers in
540 536 the partially merged file. Marker will have three sections, one from each
541 537 side of the merge and one for the base content."""
542 538 if not labels:
543 539 labels = _defaultconflictlabels
544 540 if len(labels) < 3:
545 541 labels.append(b'base')
546 return _merge(
547 repo, mynode, fcd, fco, fca, toolconf, backup, labels, b'merge3'
548 )
542 return _merge(repo, fcd, fco, fca, labels, b'merge3')
549 543
550 544
551 545 @internaltool(
552 546 b'merge3-lie-about-conflicts',
553 547 fullmerge,
554 548 b'',
555 549 precheck=_mergecheck,
556 550 )
557 551 def _imerge3alwaysgood(*args, **kwargs):
558 552 # Like merge3, but record conflicts as resolved with markers in place.
559 553 #
560 554 # This is used for `diff.merge` to show the differences between
561 555 # the auto-merge state and the committed merge state. It may be
562 556 # useful for other things.
563 557 b1, junk, b2 = _imerge3(*args, **kwargs)
564 558 # TODO is this right? I'm not sure what these return values mean,
565 559 # but as far as I can tell this will indicate to callers tha the
566 560 # merge succeeded.
567 561 return b1, False, b2
568 562
569 563
570 564 @internaltool(
571 565 b'mergediff',
572 566 fullmerge,
573 567 _(
574 568 b"warning: conflicts while merging %s! "
575 569 b"(edit, then use 'hg resolve --mark')\n"
576 570 ),
577 571 precheck=_mergecheck,
578 572 )
579 573 def _imerge_diff(repo, mynode, fcd, fco, fca, toolconf, backup, labels=None):
580 574 """
581 575 Uses the internal non-interactive simple merge algorithm for merging
582 576 files. It will fail if there are any conflicts and leave markers in
583 577 the partially merged file. The marker will have two sections, one with the
584 578 content from one side of the merge, and one with a diff from the base
585 579 content to the content on the other side. (experimental)"""
586 580 if not labels:
587 581 labels = _defaultconflictlabels
588 582 if len(labels) < 3:
589 583 labels.append(b'base')
590 return _merge(
591 repo, mynode, fcd, fco, fca, toolconf, backup, labels, b'mergediff'
592 )
584 return _merge(repo, fcd, fco, fca, labels, b'mergediff')
593 585
594 586
595 587 @internaltool(b'merge-local', mergeonly, precheck=_mergecheck)
596 588 def _imergelocal(repo, mynode, fcd, fco, fca, toolconf, backup, labels=None):
597 589 """
598 590 Like :merge, but resolve all conflicts non-interactively in favor
599 591 of the local `p1()` changes."""
600 return _merge(
601 repo, mynode, fcd, fco, fca, toolconf, backup, labels, b'local'
602 )
592 return _merge(repo, fcd, fco, fca, labels, b'local')
603 593
604 594
605 595 @internaltool(b'merge-other', mergeonly, precheck=_mergecheck)
606 596 def _imergeother(repo, mynode, fcd, fco, fca, toolconf, backup, labels=None):
607 597 """
608 598 Like :merge, but resolve all conflicts non-interactively in favor
609 599 of the other `p2()` changes."""
610 return _merge(
611 repo, mynode, fcd, fco, fca, toolconf, backup, labels, b'other'
612 )
600 return _merge(repo, fcd, fco, fca, labels, b'other')
613 601
614 602
615 603 @internaltool(
616 604 b'tagmerge',
617 605 mergeonly,
618 606 _(
619 607 b"automatic tag merging of %s failed! "
620 608 b"(use 'hg resolve --tool :merge' or another merge "
621 609 b"tool of your choice)\n"
622 610 ),
623 611 )
624 612 def _itagmerge(repo, mynode, fcd, fco, fca, toolconf, backup, labels=None):
625 613 """
626 614 Uses the internal tag merge algorithm (experimental).
627 615 """
628 616 success, status = tagmerge.merge(repo, fcd, fco, fca)
629 617 return success, status, False
630 618
631 619
632 620 @internaltool(b'dump', fullmerge, binary=True, symlink=True)
633 621 def _idump(repo, mynode, fcd, fco, fca, toolconf, backup, labels=None):
634 622 """
635 623 Creates three versions of the files to merge, containing the
636 624 contents of local, other and base. These files can then be used to
637 625 perform a merge manually. If the file to be merged is named
638 626 ``a.txt``, these files will accordingly be named ``a.txt.local``,
639 627 ``a.txt.other`` and ``a.txt.base`` and they will be placed in the
640 628 same directory as ``a.txt``.
641 629
642 630 This implies premerge. Therefore, files aren't dumped, if premerge
643 631 runs successfully. Use :forcedump to forcibly write files out.
644 632 """
645 633 a = _workingpath(repo, fcd)
646 634 fd = fcd.path()
647 635
648 636 from . import context
649 637
650 638 if isinstance(fcd, context.overlayworkingfilectx):
651 639 raise error.InMemoryMergeConflictsError(
652 640 b'in-memory merge does not support the :dump tool.'
653 641 )
654 642
655 643 util.writefile(a + b".local", fcd.decodeddata())
656 644 repo.wwrite(fd + b".other", fco.data(), fco.flags())
657 645 repo.wwrite(fd + b".base", fca.data(), fca.flags())
658 646 return False, 1, False
659 647
660 648
661 649 @internaltool(b'forcedump', mergeonly, binary=True, symlink=True)
662 650 def _forcedump(repo, mynode, fcd, fco, fca, toolconf, backup, labels=None):
663 651 """
664 652 Creates three versions of the files as same as :dump, but omits premerge.
665 653 """
666 654 return _idump(repo, mynode, fcd, fco, fca, toolconf, backup, labels=labels)
667 655
668 656
669 657 def _xmergeimm(repo, mynode, fcd, fco, fca, toolconf, backup, labels=None):
670 658 # In-memory merge simply raises an exception on all external merge tools,
671 659 # for now.
672 660 #
673 661 # It would be possible to run most tools with temporary files, but this
674 662 # raises the question of what to do if the user only partially resolves the
675 663 # file -- we can't leave a merge state. (Copy to somewhere in the .hg/
676 664 # directory and tell the user how to get it is my best idea, but it's
677 665 # clunky.)
678 666 raise error.InMemoryMergeConflictsError(
679 667 b'in-memory merge does not support external merge tools'
680 668 )
681 669
682 670
683 671 def _describemerge(ui, repo, mynode, fcl, fcb, fco, env, toolpath, args):
684 672 tmpl = ui.config(b'command-templates', b'pre-merge-tool-output')
685 673 if not tmpl:
686 674 return
687 675
688 676 mappingdict = templateutil.mappingdict
689 677 props = {
690 678 b'ctx': fcl.changectx(),
691 679 b'node': hex(mynode),
692 680 b'path': fcl.path(),
693 681 b'local': mappingdict(
694 682 {
695 683 b'ctx': fcl.changectx(),
696 684 b'fctx': fcl,
697 685 b'node': hex(mynode),
698 686 b'name': _(b'local'),
699 687 b'islink': b'l' in fcl.flags(),
700 688 b'label': env[b'HG_MY_LABEL'],
701 689 }
702 690 ),
703 691 b'base': mappingdict(
704 692 {
705 693 b'ctx': fcb.changectx(),
706 694 b'fctx': fcb,
707 695 b'name': _(b'base'),
708 696 b'islink': b'l' in fcb.flags(),
709 697 b'label': env[b'HG_BASE_LABEL'],
710 698 }
711 699 ),
712 700 b'other': mappingdict(
713 701 {
714 702 b'ctx': fco.changectx(),
715 703 b'fctx': fco,
716 704 b'name': _(b'other'),
717 705 b'islink': b'l' in fco.flags(),
718 706 b'label': env[b'HG_OTHER_LABEL'],
719 707 }
720 708 ),
721 709 b'toolpath': toolpath,
722 710 b'toolargs': args,
723 711 }
724 712
725 713 # TODO: make all of this something that can be specified on a per-tool basis
726 714 tmpl = templater.unquotestring(tmpl)
727 715
728 716 # Not using cmdutil.rendertemplate here since it causes errors importing
729 717 # things for us to import cmdutil.
730 718 tres = formatter.templateresources(ui, repo)
731 719 t = formatter.maketemplater(
732 720 ui, tmpl, defaults=templatekw.keywords, resources=tres
733 721 )
734 722 ui.status(t.renderdefault(props))
735 723
736 724
737 725 def _xmerge(repo, mynode, fcd, fco, fca, toolconf, backup, labels):
738 726 tool, toolpath, binary, symlink, scriptfn = toolconf
739 727 uipathfn = scmutil.getuipathfn(repo)
740 728 if fcd.isabsent() or fco.isabsent():
741 729 repo.ui.warn(
742 730 _(b'warning: %s cannot merge change/delete conflict for %s\n')
743 731 % (tool, uipathfn(fcd.path()))
744 732 )
745 733 return False, 1, None
746 734 localpath = _workingpath(repo, fcd)
747 735 args = _toolstr(repo.ui, tool, b"args")
748 736
749 737 with _maketempfiles(
750 738 repo, fco, fca, repo.wvfs.join(backup.path()), b"$output" in args
751 739 ) as temppaths:
752 740 basepath, otherpath, localoutputpath = temppaths
753 741 outpath = b""
754 742 mylabel, otherlabel = labels[:2]
755 743 if len(labels) >= 3:
756 744 baselabel = labels[2]
757 745 else:
758 746 baselabel = b'base'
759 747 env = {
760 748 b'HG_FILE': fcd.path(),
761 749 b'HG_MY_NODE': short(mynode),
762 750 b'HG_OTHER_NODE': short(fco.changectx().node()),
763 751 b'HG_BASE_NODE': short(fca.changectx().node()),
764 752 b'HG_MY_ISLINK': b'l' in fcd.flags(),
765 753 b'HG_OTHER_ISLINK': b'l' in fco.flags(),
766 754 b'HG_BASE_ISLINK': b'l' in fca.flags(),
767 755 b'HG_MY_LABEL': mylabel,
768 756 b'HG_OTHER_LABEL': otherlabel,
769 757 b'HG_BASE_LABEL': baselabel,
770 758 }
771 759 ui = repo.ui
772 760
773 761 if b"$output" in args:
774 762 # read input from backup, write to original
775 763 outpath = localpath
776 764 localpath = localoutputpath
777 765 replace = {
778 766 b'local': localpath,
779 767 b'base': basepath,
780 768 b'other': otherpath,
781 769 b'output': outpath,
782 770 b'labellocal': mylabel,
783 771 b'labelother': otherlabel,
784 772 b'labelbase': baselabel,
785 773 }
786 774 args = util.interpolate(
787 775 br'\$',
788 776 replace,
789 777 args,
790 778 lambda s: procutil.shellquote(util.localpath(s)),
791 779 )
792 780 if _toolbool(ui, tool, b"gui"):
793 781 repo.ui.status(
794 782 _(b'running merge tool %s for file %s\n')
795 783 % (tool, uipathfn(fcd.path()))
796 784 )
797 785 if scriptfn is None:
798 786 cmd = toolpath + b' ' + args
799 787 repo.ui.debug(b'launching merge tool: %s\n' % cmd)
800 788 _describemerge(ui, repo, mynode, fcd, fca, fco, env, toolpath, args)
801 789 r = ui.system(
802 790 cmd, cwd=repo.root, environ=env, blockedtag=b'mergetool'
803 791 )
804 792 else:
805 793 repo.ui.debug(
806 794 b'launching python merge script: %s:%s\n' % (toolpath, scriptfn)
807 795 )
808 796 r = 0
809 797 try:
810 798 # avoid cycle cmdutil->merge->filemerge->extensions->cmdutil
811 799 from . import extensions
812 800
813 801 mod = extensions.loadpath(toolpath, b'hgmerge.%s' % tool)
814 802 except Exception:
815 803 raise error.Abort(
816 804 _(b"loading python merge script failed: %s") % toolpath
817 805 )
818 806 mergefn = getattr(mod, scriptfn, None)
819 807 if mergefn is None:
820 808 raise error.Abort(
821 809 _(b"%s does not have function: %s") % (toolpath, scriptfn)
822 810 )
823 811 argslist = procutil.shellsplit(args)
824 812 # avoid cycle cmdutil->merge->filemerge->hook->extensions->cmdutil
825 813 from . import hook
826 814
827 815 ret, raised = hook.pythonhook(
828 816 ui, repo, b"merge", toolpath, mergefn, {b'args': argslist}, True
829 817 )
830 818 if raised:
831 819 r = 1
832 820 repo.ui.debug(b'merge tool returned: %d\n' % r)
833 821 return True, r, False
834 822
835 823
836 824 def _formatlabel(ctx, template, label, pad):
837 825 """Applies the given template to the ctx, prefixed by the label.
838 826
839 827 Pad is the minimum width of the label prefix, so that multiple markers
840 828 can have aligned templated parts.
841 829 """
842 830 if ctx.node() is None:
843 831 ctx = ctx.p1()
844 832
845 833 props = {b'ctx': ctx}
846 834 templateresult = template.renderdefault(props)
847 835
848 836 label = (b'%s:' % label).ljust(pad + 1)
849 837 mark = b'%s %s' % (label, templateresult)
850 838 mark = mark.splitlines()[0] # split for safety
851 839
852 840 # 8 for the prefix of conflict marker lines (e.g. '<<<<<<< ')
853 841 return stringutil.ellipsis(mark, 80 - 8)
854 842
855 843
856 844 _defaultconflictlabels = [b'local', b'other']
857 845
858 846
859 847 def _formatlabels(repo, fcd, fco, fca, labels, tool=None):
860 848 """Formats the given labels using the conflict marker template.
861 849
862 850 Returns a list of formatted labels.
863 851 """
864 852 cd = fcd.changectx()
865 853 co = fco.changectx()
866 854 ca = fca.changectx()
867 855
868 856 ui = repo.ui
869 857 template = ui.config(b'command-templates', b'mergemarker')
870 858 if tool is not None:
871 859 template = _toolstr(ui, tool, b'mergemarkertemplate', template)
872 860 template = templater.unquotestring(template)
873 861 tres = formatter.templateresources(ui, repo)
874 862 tmpl = formatter.maketemplater(
875 863 ui, template, defaults=templatekw.keywords, resources=tres
876 864 )
877 865
878 866 pad = max(len(l) for l in labels)
879 867
880 868 newlabels = [
881 869 _formatlabel(cd, tmpl, labels[0], pad),
882 870 _formatlabel(co, tmpl, labels[1], pad),
883 871 ]
884 872 if len(labels) > 2:
885 873 newlabels.append(_formatlabel(ca, tmpl, labels[2], pad))
886 874 return newlabels
887 875
888 876
889 877 def partextras(labels):
890 878 """Return a dictionary of extra labels for use in prompts to the user
891 879
892 880 Intended use is in strings of the form "(l)ocal%(l)s".
893 881 """
894 882 if labels is None:
895 883 return {
896 884 b"l": b"",
897 885 b"o": b"",
898 886 }
899 887
900 888 return {
901 889 b"l": b" [%s]" % labels[0],
902 890 b"o": b" [%s]" % labels[1],
903 891 }
904 892
905 893
906 894 def _restorebackup(fcd, backup):
907 895 # TODO: Add a workingfilectx.write(otherfilectx) path so we can use
908 896 # util.copy here instead.
909 897 fcd.write(backup.data(), fcd.flags())
910 898
911 899
912 900 def _makebackup(repo, ui, wctx, fcd):
913 901 """Makes and returns a filectx-like object for ``fcd``'s backup file.
914 902
915 903 In addition to preserving the user's pre-existing modifications to `fcd`
916 904 (if any), the backup is used to undo certain premerges, confirm whether a
917 905 merge changed anything, and determine what line endings the new file should
918 906 have.
919 907
920 908 Backups only need to be written once since their content doesn't change
921 909 afterwards.
922 910 """
923 911 if fcd.isabsent():
924 912 return None
925 913 # TODO: Break this import cycle somehow. (filectx -> ctx -> fileset ->
926 914 # merge -> filemerge). (I suspect the fileset import is the weakest link)
927 915 from . import context
928 916
929 917 backup = scmutil.backuppath(ui, repo, fcd.path())
930 918 inworkingdir = backup.startswith(repo.wvfs.base) and not backup.startswith(
931 919 repo.vfs.base
932 920 )
933 921 if isinstance(fcd, context.overlayworkingfilectx) and inworkingdir:
934 922 # If the backup file is to be in the working directory, and we're
935 923 # merging in-memory, we must redirect the backup to the memory context
936 924 # so we don't disturb the working directory.
937 925 relpath = backup[len(repo.wvfs.base) + 1 :]
938 926 wctx[relpath].write(fcd.data(), fcd.flags())
939 927 return wctx[relpath]
940 928 else:
941 929 # Otherwise, write to wherever path the user specified the backups
942 930 # should go. We still need to switch based on whether the source is
943 931 # in-memory so we can use the fast path of ``util.copy`` if both are
944 932 # on disk.
945 933 if isinstance(fcd, context.overlayworkingfilectx):
946 934 util.writefile(backup, fcd.data())
947 935 else:
948 936 a = _workingpath(repo, fcd)
949 937 util.copyfile(a, backup)
950 938 # A arbitraryfilectx is returned, so we can run the same functions on
951 939 # the backup context regardless of where it lives.
952 940 return context.arbitraryfilectx(backup, repo=repo)
953 941
954 942
955 943 @contextlib.contextmanager
956 944 def _maketempfiles(repo, fco, fca, localpath, uselocalpath):
957 945 """Writes out `fco` and `fca` as temporary files, and (if uselocalpath)
958 946 copies `localpath` to another temporary file, so an external merge tool may
959 947 use them.
960 948 """
961 949 tmproot = None
962 950 tmprootprefix = repo.ui.config(b'experimental', b'mergetempdirprefix')
963 951 if tmprootprefix:
964 952 tmproot = pycompat.mkdtemp(prefix=tmprootprefix)
965 953
966 954 def maketempfrompath(prefix, path):
967 955 fullbase, ext = os.path.splitext(path)
968 956 pre = b"%s~%s" % (os.path.basename(fullbase), prefix)
969 957 if tmproot:
970 958 name = os.path.join(tmproot, pre)
971 959 if ext:
972 960 name += ext
973 961 f = open(name, "wb")
974 962 else:
975 963 fd, name = pycompat.mkstemp(prefix=pre + b'.', suffix=ext)
976 964 f = os.fdopen(fd, "wb")
977 965 return f, name
978 966
979 967 def tempfromcontext(prefix, ctx):
980 968 f, name = maketempfrompath(prefix, ctx.path())
981 969 data = ctx.decodeddata()
982 970 f.write(data)
983 971 f.close()
984 972 return name
985 973
986 974 b = tempfromcontext(b"base", fca)
987 975 c = tempfromcontext(b"other", fco)
988 976 d = localpath
989 977 if uselocalpath:
990 978 # We start off with this being the backup filename, so remove the .orig
991 979 # to make syntax-highlighting more likely.
992 980 if d.endswith(b'.orig'):
993 981 d, _ = os.path.splitext(d)
994 982 f, d = maketempfrompath(b"local", d)
995 983 with open(localpath, b'rb') as src:
996 984 f.write(src.read())
997 985 f.close()
998 986
999 987 try:
1000 988 yield b, c, d
1001 989 finally:
1002 990 if tmproot:
1003 991 shutil.rmtree(tmproot)
1004 992 else:
1005 993 util.unlink(b)
1006 994 util.unlink(c)
1007 995 # if not uselocalpath, d is the 'orig'/backup file which we
1008 996 # shouldn't delete.
1009 997 if d and uselocalpath:
1010 998 util.unlink(d)
1011 999
1012 1000
1013 1001 def filemerge(repo, wctx, mynode, orig, fcd, fco, fca, labels=None):
1014 1002 """perform a 3-way merge in the working directory
1015 1003
1016 1004 mynode = parent node before merge
1017 1005 orig = original local filename before merge
1018 1006 fco = other file context
1019 1007 fca = ancestor file context
1020 1008 fcd = local file context for current/destination file
1021 1009
1022 1010 Returns whether the merge is complete, the return value of the merge, and
1023 1011 a boolean indicating whether the file was deleted from disk."""
1024 1012
1025 1013 if not fco.cmp(fcd): # files identical?
1026 1014 return None, False
1027 1015
1028 1016 ui = repo.ui
1029 1017 fd = fcd.path()
1030 1018 uipathfn = scmutil.getuipathfn(repo)
1031 1019 fduipath = uipathfn(fd)
1032 1020 binary = fcd.isbinary() or fco.isbinary() or fca.isbinary()
1033 1021 symlink = b'l' in fcd.flags() + fco.flags()
1034 1022 changedelete = fcd.isabsent() or fco.isabsent()
1035 1023 tool, toolpath = _picktool(repo, ui, fd, binary, symlink, changedelete)
1036 1024 scriptfn = None
1037 1025 if tool in internals and tool.startswith(b'internal:'):
1038 1026 # normalize to new-style names (':merge' etc)
1039 1027 tool = tool[len(b'internal') :]
1040 1028 if toolpath and toolpath.startswith(b'python:'):
1041 1029 invalidsyntax = False
1042 1030 if toolpath.count(b':') >= 2:
1043 1031 script, scriptfn = toolpath[7:].rsplit(b':', 1)
1044 1032 if not scriptfn:
1045 1033 invalidsyntax = True
1046 1034 # missing :callable can lead to spliting on windows drive letter
1047 1035 if b'\\' in scriptfn or b'/' in scriptfn:
1048 1036 invalidsyntax = True
1049 1037 else:
1050 1038 invalidsyntax = True
1051 1039 if invalidsyntax:
1052 1040 raise error.Abort(_(b"invalid 'python:' syntax: %s") % toolpath)
1053 1041 toolpath = script
1054 1042 ui.debug(
1055 1043 b"picked tool '%s' for %s (binary %s symlink %s changedelete %s)\n"
1056 1044 % (
1057 1045 tool,
1058 1046 fduipath,
1059 1047 pycompat.bytestr(binary),
1060 1048 pycompat.bytestr(symlink),
1061 1049 pycompat.bytestr(changedelete),
1062 1050 )
1063 1051 )
1064 1052
1065 1053 if tool in internals:
1066 1054 func = internals[tool]
1067 1055 mergetype = func.mergetype
1068 1056 onfailure = func.onfailure
1069 1057 precheck = func.precheck
1070 1058 isexternal = False
1071 1059 else:
1072 1060 if wctx.isinmemory():
1073 1061 func = _xmergeimm
1074 1062 else:
1075 1063 func = _xmerge
1076 1064 mergetype = fullmerge
1077 1065 onfailure = _(b"merging %s failed!\n")
1078 1066 precheck = None
1079 1067 isexternal = True
1080 1068
1081 1069 toolconf = tool, toolpath, binary, symlink, scriptfn
1082 1070
1083 1071 if mergetype == nomerge:
1084 1072 return func(repo, mynode, fcd, fco, fca, toolconf, labels)
1085 1073
1086 1074 if orig != fco.path():
1087 1075 ui.status(
1088 1076 _(b"merging %s and %s to %s\n")
1089 1077 % (uipathfn(orig), uipathfn(fco.path()), fduipath)
1090 1078 )
1091 1079 else:
1092 1080 ui.status(_(b"merging %s\n") % fduipath)
1093 1081
1094 1082 ui.debug(b"my %s other %s ancestor %s\n" % (fcd, fco, fca))
1095 1083
1096 1084 if precheck and not precheck(repo, mynode, fcd, fco, fca, toolconf):
1097 1085 if onfailure:
1098 1086 if wctx.isinmemory():
1099 1087 raise error.InMemoryMergeConflictsError(
1100 1088 b'in-memory merge does not support merge conflicts'
1101 1089 )
1102 1090 ui.warn(onfailure % fduipath)
1103 1091 return 1, False
1104 1092
1105 1093 backup = _makebackup(repo, ui, wctx, fcd)
1106 1094 r = 1
1107 1095 try:
1108 1096 internalmarkerstyle = ui.config(b'ui', b'mergemarkers')
1109 1097 if isexternal:
1110 1098 markerstyle = _toolstr(ui, tool, b'mergemarkers')
1111 1099 else:
1112 1100 markerstyle = internalmarkerstyle
1113 1101
1114 1102 if not labels:
1115 1103 labels = _defaultconflictlabels
1116 1104 formattedlabels = labels
1117 1105 if markerstyle != b'basic':
1118 1106 formattedlabels = _formatlabels(
1119 1107 repo, fcd, fco, fca, labels, tool=tool
1120 1108 )
1121 1109
1122 1110 if mergetype == fullmerge:
1123 1111 # conflict markers generated by premerge will use 'detailed'
1124 1112 # settings if either ui.mergemarkers or the tool's mergemarkers
1125 1113 # setting is 'detailed'. This way tools can have basic labels in
1126 1114 # space-constrained areas of the UI, but still get full information
1127 1115 # in conflict markers if premerge is 'keep' or 'keep-merge3'.
1128 1116 premergelabels = labels
1129 1117 labeltool = None
1130 1118 if markerstyle != b'basic':
1131 1119 # respect 'tool's mergemarkertemplate (which defaults to
1132 1120 # command-templates.mergemarker)
1133 1121 labeltool = tool
1134 1122 if internalmarkerstyle != b'basic' or markerstyle != b'basic':
1135 1123 premergelabels = _formatlabels(
1136 1124 repo, fcd, fco, fca, premergelabels, tool=labeltool
1137 1125 )
1138 1126
1139 1127 r = _premerge(
1140 1128 repo, fcd, fco, fca, toolconf, backup, labels=premergelabels
1141 1129 )
1142 1130 # we're done if premerge was successful (r is 0)
1143 1131 if not r:
1144 1132 return r, False
1145 1133
1146 1134 needcheck, r, deleted = func(
1147 1135 repo,
1148 1136 mynode,
1149 1137 fcd,
1150 1138 fco,
1151 1139 fca,
1152 1140 toolconf,
1153 1141 backup,
1154 1142 labels=formattedlabels,
1155 1143 )
1156 1144
1157 1145 if needcheck:
1158 1146 r = _check(repo, r, ui, tool, fcd, backup)
1159 1147
1160 1148 if r:
1161 1149 if onfailure:
1162 1150 if wctx.isinmemory():
1163 1151 raise error.InMemoryMergeConflictsError(
1164 1152 b'in-memory merge '
1165 1153 b'does not support '
1166 1154 b'merge conflicts'
1167 1155 )
1168 1156 ui.warn(onfailure % fduipath)
1169 1157 _onfilemergefailure(ui)
1170 1158
1171 1159 return r, deleted
1172 1160 finally:
1173 1161 if not r and backup is not None:
1174 1162 backup.remove()
1175 1163
1176 1164
1177 1165 def _haltmerge():
1178 1166 msg = _(b'merge halted after failed merge (see hg resolve)')
1179 1167 raise error.InterventionRequired(msg)
1180 1168
1181 1169
1182 1170 def _onfilemergefailure(ui):
1183 1171 action = ui.config(b'merge', b'on-failure')
1184 1172 if action == b'prompt':
1185 1173 msg = _(b'continue merge operation (yn)?$$ &Yes $$ &No')
1186 1174 if ui.promptchoice(msg, 0) == 1:
1187 1175 _haltmerge()
1188 1176 if action == b'halt':
1189 1177 _haltmerge()
1190 1178 # default action is 'continue', in which case we neither prompt nor halt
1191 1179
1192 1180
1193 1181 def hasconflictmarkers(data):
1194 1182 # Detect lines starting with a string of 7 identical characters from the
1195 1183 # subset Mercurial uses for conflict markers, followed by either the end of
1196 1184 # line or a space and some text. Note that using [<>=+|-]{7} would detect
1197 1185 # `<><><><><` as a conflict marker, which we don't want.
1198 1186 return bool(
1199 1187 re.search(
1200 1188 br"^([<>=+|-])\1{6}( .*)$",
1201 1189 data,
1202 1190 re.MULTILINE,
1203 1191 )
1204 1192 )
1205 1193
1206 1194
1207 1195 def _check(repo, r, ui, tool, fcd, backup):
1208 1196 fd = fcd.path()
1209 1197 uipathfn = scmutil.getuipathfn(repo)
1210 1198
1211 1199 if not r and (
1212 1200 _toolbool(ui, tool, b"checkconflicts")
1213 1201 or b'conflicts' in _toollist(ui, tool, b"check")
1214 1202 ):
1215 1203 if hasconflictmarkers(fcd.data()):
1216 1204 r = 1
1217 1205
1218 1206 checked = False
1219 1207 if b'prompt' in _toollist(ui, tool, b"check"):
1220 1208 checked = True
1221 1209 if ui.promptchoice(
1222 1210 _(b"was merge of '%s' successful (yn)?$$ &Yes $$ &No")
1223 1211 % uipathfn(fd),
1224 1212 1,
1225 1213 ):
1226 1214 r = 1
1227 1215
1228 1216 if (
1229 1217 not r
1230 1218 and not checked
1231 1219 and (
1232 1220 _toolbool(ui, tool, b"checkchanged")
1233 1221 or b'changed' in _toollist(ui, tool, b"check")
1234 1222 )
1235 1223 ):
1236 1224 if backup is not None and not fcd.cmp(backup):
1237 1225 if ui.promptchoice(
1238 1226 _(
1239 1227 b" output file %s appears unchanged\n"
1240 1228 b"was merge successful (yn)?"
1241 1229 b"$$ &Yes $$ &No"
1242 1230 )
1243 1231 % uipathfn(fd),
1244 1232 1,
1245 1233 ):
1246 1234 r = 1
1247 1235
1248 1236 if backup is not None and _toolbool(ui, tool, b"fixeol"):
1249 1237 _matcheol(_workingpath(repo, fcd), backup)
1250 1238
1251 1239 return r
1252 1240
1253 1241
1254 1242 def _workingpath(repo, ctx):
1255 1243 return repo.wjoin(ctx.path())
1256 1244
1257 1245
1258 1246 def loadinternalmerge(ui, extname, registrarobj):
1259 1247 """Load internal merge tool from specified registrarobj"""
1260 1248 for name, func in pycompat.iteritems(registrarobj._table):
1261 1249 fullname = b':' + name
1262 1250 internals[fullname] = func
1263 1251 internals[b'internal:' + name] = func
1264 1252 internalsdoc[fullname] = func
1265 1253
1266 1254 capabilities = sorted([k for k, v in func.capabilities.items() if v])
1267 1255 if capabilities:
1268 1256 capdesc = b" (actual capabilities: %s)" % b', '.join(
1269 1257 capabilities
1270 1258 )
1271 1259 func.__doc__ = func.__doc__ + pycompat.sysstr(b"\n\n%s" % capdesc)
1272 1260
1273 1261 # to put i18n comments into hg.pot for automatically generated texts
1274 1262
1275 1263 # i18n: "binary" and "symlink" are keywords
1276 1264 # i18n: this text is added automatically
1277 1265 _(b" (actual capabilities: binary, symlink)")
1278 1266 # i18n: "binary" is keyword
1279 1267 # i18n: this text is added automatically
1280 1268 _(b" (actual capabilities: binary)")
1281 1269 # i18n: "symlink" is keyword
1282 1270 # i18n: this text is added automatically
1283 1271 _(b" (actual capabilities: symlink)")
1284 1272
1285 1273
1286 1274 # load built-in merge tools explicitly to setup internalsdoc
1287 1275 loadinternalmerge(None, None, internaltool)
1288 1276
1289 1277 # tell hggettext to extract docstrings from these functions:
1290 1278 i18nfunctions = internals.values()
General Comments 0
You need to be logged in to leave comments. Login now