##// END OF EJS Templates
simplemerge: take over formatting of label from `filemerge`...
Martin von Zweigbergk -
r49433:3c8cc987 default
parent child Browse files
Show More
@@ -1,1264 +1,1255 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 stringutil,
44 43 )
45 44
46 45
47 46 def _toolstr(ui, tool, part, *args):
48 47 return ui.config(b"merge-tools", tool + b"." + part, *args)
49 48
50 49
51 50 def _toolbool(ui, tool, part, *args):
52 51 return ui.configbool(b"merge-tools", tool + b"." + part, *args)
53 52
54 53
55 54 def _toollist(ui, tool, part):
56 55 return ui.configlist(b"merge-tools", tool + b"." + part)
57 56
58 57
59 58 internals = {}
60 59 # Merge tools to document.
61 60 internalsdoc = {}
62 61
63 62 internaltool = registrar.internalmerge()
64 63
65 64 # internal tool merge types
66 65 nomerge = internaltool.nomerge
67 66 mergeonly = internaltool.mergeonly # just the full merge, no premerge
68 67 fullmerge = internaltool.fullmerge # both premerge and merge
69 68
70 69 # IMPORTANT: keep the last line of this prompt very short ("What do you want to
71 70 # do?") because of issue6158, ideally to <40 English characters (to allow other
72 71 # languages that may take more columns to still have a chance to fit in an
73 72 # 80-column screen).
74 73 _localchangedotherdeletedmsg = _(
75 74 b"file '%(fd)s' was deleted in other%(o)s but was modified in local%(l)s.\n"
76 75 b"You can use (c)hanged version, (d)elete, or leave (u)nresolved.\n"
77 76 b"What do you want to do?"
78 77 b"$$ &Changed $$ &Delete $$ &Unresolved"
79 78 )
80 79
81 80 _otherchangedlocaldeletedmsg = _(
82 81 b"file '%(fd)s' was deleted in local%(l)s but was modified in other%(o)s.\n"
83 82 b"You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved.\n"
84 83 b"What do you want to do?"
85 84 b"$$ &Changed $$ &Deleted $$ &Unresolved"
86 85 )
87 86
88 87
89 88 class absentfilectx(object):
90 89 """Represents a file that's ostensibly in a context but is actually not
91 90 present in it.
92 91
93 92 This is here because it's very specific to the filemerge code for now --
94 93 other code is likely going to break with the values this returns."""
95 94
96 95 def __init__(self, ctx, f):
97 96 self._ctx = ctx
98 97 self._f = f
99 98
100 99 def __bytes__(self):
101 100 return b'absent file %s@%s' % (self._f, self._ctx)
102 101
103 102 def path(self):
104 103 return self._f
105 104
106 105 def size(self):
107 106 return None
108 107
109 108 def data(self):
110 109 return None
111 110
112 111 def filenode(self):
113 112 return self._ctx.repo().nullid
114 113
115 114 _customcmp = True
116 115
117 116 def cmp(self, fctx):
118 117 """compare with other file context
119 118
120 119 returns True if different from fctx.
121 120 """
122 121 return not (
123 122 fctx.isabsent()
124 123 and fctx.changectx() == self.changectx()
125 124 and fctx.path() == self.path()
126 125 )
127 126
128 127 def flags(self):
129 128 return b''
130 129
131 130 def changectx(self):
132 131 return self._ctx
133 132
134 133 def isbinary(self):
135 134 return False
136 135
137 136 def isabsent(self):
138 137 return True
139 138
140 139
141 140 def _findtool(ui, tool):
142 141 if tool in internals:
143 142 return tool
144 143 cmd = _toolstr(ui, tool, b"executable", tool)
145 144 if cmd.startswith(b'python:'):
146 145 return cmd
147 146 return findexternaltool(ui, tool)
148 147
149 148
150 149 def _quotetoolpath(cmd):
151 150 if cmd.startswith(b'python:'):
152 151 return cmd
153 152 return procutil.shellquote(cmd)
154 153
155 154
156 155 def findexternaltool(ui, tool):
157 156 for kn in (b"regkey", b"regkeyalt"):
158 157 k = _toolstr(ui, tool, kn)
159 158 if not k:
160 159 continue
161 160 p = util.lookupreg(k, _toolstr(ui, tool, b"regname"))
162 161 if p:
163 162 p = procutil.findexe(p + _toolstr(ui, tool, b"regappend", b""))
164 163 if p:
165 164 return p
166 165 exe = _toolstr(ui, tool, b"executable", tool)
167 166 return procutil.findexe(util.expandpath(exe))
168 167
169 168
170 169 def _picktool(repo, ui, path, binary, symlink, changedelete):
171 170 strictcheck = ui.configbool(b'merge', b'strict-capability-check')
172 171
173 172 def hascapability(tool, capability, strict=False):
174 173 if tool in internals:
175 174 return strict and internals[tool].capabilities.get(capability)
176 175 return _toolbool(ui, tool, capability)
177 176
178 177 def supportscd(tool):
179 178 return tool in internals and internals[tool].mergetype == nomerge
180 179
181 180 def check(tool, pat, symlink, binary, changedelete):
182 181 tmsg = tool
183 182 if pat:
184 183 tmsg = _(b"%s (for pattern %s)") % (tool, pat)
185 184 if not _findtool(ui, tool):
186 185 if pat: # explicitly requested tool deserves a warning
187 186 ui.warn(_(b"couldn't find merge tool %s\n") % tmsg)
188 187 else: # configured but non-existing tools are more silent
189 188 ui.note(_(b"couldn't find merge tool %s\n") % tmsg)
190 189 elif symlink and not hascapability(tool, b"symlink", strictcheck):
191 190 ui.warn(_(b"tool %s can't handle symlinks\n") % tmsg)
192 191 elif binary and not hascapability(tool, b"binary", strictcheck):
193 192 ui.warn(_(b"tool %s can't handle binary\n") % tmsg)
194 193 elif changedelete and not supportscd(tool):
195 194 # the nomerge tools are the only tools that support change/delete
196 195 # conflicts
197 196 pass
198 197 elif not procutil.gui() and _toolbool(ui, tool, b"gui"):
199 198 ui.warn(_(b"tool %s requires a GUI\n") % tmsg)
200 199 else:
201 200 return True
202 201 return False
203 202
204 203 # internal config: ui.forcemerge
205 204 # forcemerge comes from command line arguments, highest priority
206 205 force = ui.config(b'ui', b'forcemerge')
207 206 if force:
208 207 toolpath = _findtool(ui, force)
209 208 if changedelete and not supportscd(toolpath):
210 209 return b":prompt", None
211 210 else:
212 211 if toolpath:
213 212 return (force, _quotetoolpath(toolpath))
214 213 else:
215 214 # mimic HGMERGE if given tool not found
216 215 return (force, force)
217 216
218 217 # HGMERGE takes next precedence
219 218 hgmerge = encoding.environ.get(b"HGMERGE")
220 219 if hgmerge:
221 220 if changedelete and not supportscd(hgmerge):
222 221 return b":prompt", None
223 222 else:
224 223 return (hgmerge, hgmerge)
225 224
226 225 # then patterns
227 226
228 227 # whether binary capability should be checked strictly
229 228 binarycap = binary and strictcheck
230 229
231 230 for pat, tool in ui.configitems(b"merge-patterns"):
232 231 mf = match.match(repo.root, b'', [pat])
233 232 if mf(path) and check(tool, pat, symlink, binarycap, changedelete):
234 233 if binary and not hascapability(tool, b"binary", strict=True):
235 234 ui.warn(
236 235 _(
237 236 b"warning: check merge-patterns configurations,"
238 237 b" if %r for binary file %r is unintentional\n"
239 238 b"(see 'hg help merge-tools'"
240 239 b" for binary files capability)\n"
241 240 )
242 241 % (pycompat.bytestr(tool), pycompat.bytestr(path))
243 242 )
244 243 toolpath = _findtool(ui, tool)
245 244 return (tool, _quotetoolpath(toolpath))
246 245
247 246 # then merge tools
248 247 tools = {}
249 248 disabled = set()
250 249 for k, v in ui.configitems(b"merge-tools"):
251 250 t = k.split(b'.')[0]
252 251 if t not in tools:
253 252 tools[t] = int(_toolstr(ui, t, b"priority"))
254 253 if _toolbool(ui, t, b"disabled"):
255 254 disabled.add(t)
256 255 names = tools.keys()
257 256 tools = sorted(
258 257 [(-p, tool) for tool, p in tools.items() if tool not in disabled]
259 258 )
260 259 uimerge = ui.config(b"ui", b"merge")
261 260 if uimerge:
262 261 # external tools defined in uimerge won't be able to handle
263 262 # change/delete conflicts
264 263 if check(uimerge, path, symlink, binary, changedelete):
265 264 if uimerge not in names and not changedelete:
266 265 return (uimerge, uimerge)
267 266 tools.insert(0, (None, uimerge)) # highest priority
268 267 tools.append((None, b"hgmerge")) # the old default, if found
269 268 for p, t in tools:
270 269 if check(t, None, symlink, binary, changedelete):
271 270 toolpath = _findtool(ui, t)
272 271 return (t, _quotetoolpath(toolpath))
273 272
274 273 # internal merge or prompt as last resort
275 274 if symlink or binary or changedelete:
276 275 if not changedelete and len(tools):
277 276 # any tool is rejected by capability for symlink or binary
278 277 ui.warn(_(b"no tool found to merge %s\n") % path)
279 278 return b":prompt", None
280 279 return b":merge", None
281 280
282 281
283 282 def _eoltype(data):
284 283 """Guess the EOL type of a file"""
285 284 if b'\0' in data: # binary
286 285 return None
287 286 if b'\r\n' in data: # Windows
288 287 return b'\r\n'
289 288 if b'\r' in data: # Old Mac
290 289 return b'\r'
291 290 if b'\n' in data: # UNIX
292 291 return b'\n'
293 292 return None # unknown
294 293
295 294
296 295 def _matcheol(file, backup):
297 296 """Convert EOL markers in a file to match origfile"""
298 297 tostyle = _eoltype(backup.data()) # No repo.wread filters?
299 298 if tostyle:
300 299 data = util.readfile(file)
301 300 style = _eoltype(data)
302 301 if style:
303 302 newdata = data.replace(style, tostyle)
304 303 if newdata != data:
305 304 util.writefile(file, newdata)
306 305
307 306
308 307 @internaltool(b'prompt', nomerge)
309 308 def _iprompt(repo, mynode, local, other, base, toolconf):
310 309 """Asks the user which of the local `p1()` or the other `p2()` version to
311 310 keep as the merged version."""
312 311 ui = repo.ui
313 312 fd = local.fctx.path()
314 313 uipathfn = scmutil.getuipathfn(repo)
315 314
316 315 # Avoid prompting during an in-memory merge since it doesn't support merge
317 316 # conflicts.
318 317 if local.fctx.changectx().isinmemory():
319 318 raise error.InMemoryMergeConflictsError(
320 319 b'in-memory merge does not support file conflicts'
321 320 )
322 321
323 322 prompts = partextras([local.label, other.label])
324 323 prompts[b'fd'] = uipathfn(fd)
325 324 try:
326 325 if other.fctx.isabsent():
327 326 index = ui.promptchoice(_localchangedotherdeletedmsg % prompts, 2)
328 327 choice = [b'local', b'other', b'unresolved'][index]
329 328 elif local.fctx.isabsent():
330 329 index = ui.promptchoice(_otherchangedlocaldeletedmsg % prompts, 2)
331 330 choice = [b'other', b'local', b'unresolved'][index]
332 331 else:
333 332 # IMPORTANT: keep the last line of this prompt ("What do you want to
334 333 # do?") very short, see comment next to _localchangedotherdeletedmsg
335 334 # at the top of the file for details.
336 335 index = ui.promptchoice(
337 336 _(
338 337 b"file '%(fd)s' needs to be resolved.\n"
339 338 b"You can keep (l)ocal%(l)s, take (o)ther%(o)s, or leave "
340 339 b"(u)nresolved.\n"
341 340 b"What do you want to do?"
342 341 b"$$ &Local $$ &Other $$ &Unresolved"
343 342 )
344 343 % prompts,
345 344 2,
346 345 )
347 346 choice = [b'local', b'other', b'unresolved'][index]
348 347
349 348 if choice == b'other':
350 349 return _iother(repo, mynode, local, other, base, toolconf)
351 350 elif choice == b'local':
352 351 return _ilocal(repo, mynode, local, other, base, toolconf)
353 352 elif choice == b'unresolved':
354 353 return _ifail(repo, mynode, local, other, base, toolconf)
355 354 except error.ResponseExpected:
356 355 ui.write(b"\n")
357 356 return _ifail(repo, mynode, local, other, base, toolconf)
358 357
359 358
360 359 @internaltool(b'local', nomerge)
361 360 def _ilocal(repo, mynode, local, other, base, toolconf):
362 361 """Uses the local `p1()` version of files as the merged version."""
363 362 return 0, local.fctx.isabsent()
364 363
365 364
366 365 @internaltool(b'other', nomerge)
367 366 def _iother(repo, mynode, local, other, base, toolconf):
368 367 """Uses the other `p2()` version of files as the merged version."""
369 368 if other.fctx.isabsent():
370 369 # local changed, remote deleted -- 'deleted' picked
371 370 _underlyingfctxifabsent(local.fctx).remove()
372 371 deleted = True
373 372 else:
374 373 _underlyingfctxifabsent(local.fctx).write(
375 374 other.fctx.data(), other.fctx.flags()
376 375 )
377 376 deleted = False
378 377 return 0, deleted
379 378
380 379
381 380 @internaltool(b'fail', nomerge)
382 381 def _ifail(repo, mynode, local, other, base, toolconf):
383 382 """
384 383 Rather than attempting to merge files that were modified on both
385 384 branches, it marks them as unresolved. The resolve command must be
386 385 used to resolve these conflicts."""
387 386 # for change/delete conflicts write out the changed version, then fail
388 387 if local.fctx.isabsent():
389 388 _underlyingfctxifabsent(local.fctx).write(
390 389 other.fctx.data(), other.fctx.flags()
391 390 )
392 391 return 1, False
393 392
394 393
395 394 def _underlyingfctxifabsent(filectx):
396 395 """Sometimes when resolving, our fcd is actually an absentfilectx, but
397 396 we want to write to it (to do the resolve). This helper returns the
398 397 underyling workingfilectx in that case.
399 398 """
400 399 if filectx.isabsent():
401 400 return filectx.changectx()[filectx.path()]
402 401 else:
403 402 return filectx
404 403
405 404
406 405 def _premerge(repo, local, other, base, toolconf, backup):
407 406 tool, toolpath, binary, symlink, scriptfn = toolconf
408 407 if symlink or local.fctx.isabsent() or other.fctx.isabsent():
409 408 return 1
410 409
411 410 ui = repo.ui
412 411
413 412 validkeep = [b'keep', b'keep-merge3', b'keep-mergediff']
414 413
415 414 # do we attempt to simplemerge first?
416 415 try:
417 416 premerge = _toolbool(ui, tool, b"premerge", not binary)
418 417 except error.ConfigError:
419 418 premerge = _toolstr(ui, tool, b"premerge", b"").lower()
420 419 if premerge not in validkeep:
421 420 _valid = b', '.join([b"'" + v + b"'" for v in validkeep])
422 421 raise error.ConfigError(
423 422 _(b"%s.premerge not valid ('%s' is neither boolean nor %s)")
424 423 % (tool, premerge, _valid)
425 424 )
426 425
427 426 if premerge:
428 427 mode = b'merge'
429 428 if premerge == b'keep-mergediff':
430 429 mode = b'mergediff'
431 430 elif premerge == b'keep-merge3':
432 431 mode = b'merge3'
433 432 r = simplemerge.simplemerge(
434 433 ui, local, base, other, quiet=True, mode=mode
435 434 )
436 435 if not r:
437 436 ui.debug(b" premerge successful\n")
438 437 return 0
439 438 if premerge not in validkeep:
440 439 # restore from backup and try again
441 440 _restorebackup(local.fctx, backup)
442 441 return 1 # continue merging
443 442
444 443
445 444 def _mergecheck(repo, mynode, fcd, fco, fca, toolconf):
446 445 tool, toolpath, binary, symlink, scriptfn = toolconf
447 446 uipathfn = scmutil.getuipathfn(repo)
448 447 if symlink:
449 448 repo.ui.warn(
450 449 _(b'warning: internal %s cannot merge symlinks for %s\n')
451 450 % (tool, uipathfn(fcd.path()))
452 451 )
453 452 return False
454 453 if fcd.isabsent() or fco.isabsent():
455 454 repo.ui.warn(
456 455 _(
457 456 b'warning: internal %s cannot merge change/delete '
458 457 b'conflict for %s\n'
459 458 )
460 459 % (tool, uipathfn(fcd.path()))
461 460 )
462 461 return False
463 462 return True
464 463
465 464
466 465 def _merge(repo, local, other, base, mode):
467 466 """
468 467 Uses the internal non-interactive simple merge algorithm for merging
469 468 files. It will fail if there are any conflicts and leave markers in
470 469 the partially merged file. Markers will have two sections, one for each side
471 470 of merge, unless mode equals 'union' which suppresses the markers."""
472 471 ui = repo.ui
473 472
474 473 r = simplemerge.simplemerge(ui, local, base, other, mode=mode)
475 474 return True, r, False
476 475
477 476
478 477 @internaltool(
479 478 b'union',
480 479 fullmerge,
481 480 _(
482 481 b"warning: conflicts while merging %s! "
483 482 b"(edit, then use 'hg resolve --mark')\n"
484 483 ),
485 484 precheck=_mergecheck,
486 485 )
487 486 def _iunion(repo, mynode, local, other, base, toolconf, backup):
488 487 """
489 488 Uses the internal non-interactive simple merge algorithm for merging
490 489 files. It will use both left and right sides for conflict regions.
491 490 No markers are inserted."""
492 491 return _merge(repo, local, other, base, b'union')
493 492
494 493
495 494 @internaltool(
496 495 b'merge',
497 496 fullmerge,
498 497 _(
499 498 b"warning: conflicts while merging %s! "
500 499 b"(edit, then use 'hg resolve --mark')\n"
501 500 ),
502 501 precheck=_mergecheck,
503 502 )
504 503 def _imerge(repo, mynode, local, other, base, toolconf, backup):
505 504 """
506 505 Uses the internal non-interactive simple merge algorithm for merging
507 506 files. It will fail if there are any conflicts and leave markers in
508 507 the partially merged file. Markers will have two sections, one for each side
509 508 of merge."""
510 509 return _merge(repo, local, other, base, b'merge')
511 510
512 511
513 512 @internaltool(
514 513 b'merge3',
515 514 fullmerge,
516 515 _(
517 516 b"warning: conflicts while merging %s! "
518 517 b"(edit, then use 'hg resolve --mark')\n"
519 518 ),
520 519 precheck=_mergecheck,
521 520 )
522 521 def _imerge3(repo, mynode, local, other, base, toolconf, backup):
523 522 """
524 523 Uses the internal non-interactive simple merge algorithm for merging
525 524 files. It will fail if there are any conflicts and leave markers in
526 525 the partially merged file. Marker will have three sections, one from each
527 526 side of the merge and one for the base content."""
528 527 return _merge(repo, local, other, base, b'merge3')
529 528
530 529
531 530 @internaltool(
532 531 b'merge3-lie-about-conflicts',
533 532 fullmerge,
534 533 b'',
535 534 precheck=_mergecheck,
536 535 )
537 536 def _imerge3alwaysgood(*args, **kwargs):
538 537 # Like merge3, but record conflicts as resolved with markers in place.
539 538 #
540 539 # This is used for `diff.merge` to show the differences between
541 540 # the auto-merge state and the committed merge state. It may be
542 541 # useful for other things.
543 542 b1, junk, b2 = _imerge3(*args, **kwargs)
544 543 # TODO is this right? I'm not sure what these return values mean,
545 544 # but as far as I can tell this will indicate to callers tha the
546 545 # merge succeeded.
547 546 return b1, False, b2
548 547
549 548
550 549 @internaltool(
551 550 b'mergediff',
552 551 fullmerge,
553 552 _(
554 553 b"warning: conflicts while merging %s! "
555 554 b"(edit, then use 'hg resolve --mark')\n"
556 555 ),
557 556 precheck=_mergecheck,
558 557 )
559 558 def _imerge_diff(repo, mynode, local, other, base, toolconf, backup):
560 559 """
561 560 Uses the internal non-interactive simple merge algorithm for merging
562 561 files. It will fail if there are any conflicts and leave markers in
563 562 the partially merged file. The marker will have two sections, one with the
564 563 content from one side of the merge, and one with a diff from the base
565 564 content to the content on the other side. (experimental)"""
566 565 return _merge(repo, local, other, base, b'mergediff')
567 566
568 567
569 568 @internaltool(b'merge-local', mergeonly, precheck=_mergecheck)
570 569 def _imergelocal(repo, mynode, local, other, base, toolconf, backup):
571 570 """
572 571 Like :merge, but resolve all conflicts non-interactively in favor
573 572 of the local `p1()` changes."""
574 573 return _merge(repo, local, other, base, b'local')
575 574
576 575
577 576 @internaltool(b'merge-other', mergeonly, precheck=_mergecheck)
578 577 def _imergeother(repo, mynode, local, other, base, toolconf, backup):
579 578 """
580 579 Like :merge, but resolve all conflicts non-interactively in favor
581 580 of the other `p2()` changes."""
582 581 return _merge(repo, local, other, base, b'other')
583 582
584 583
585 584 @internaltool(
586 585 b'tagmerge',
587 586 mergeonly,
588 587 _(
589 588 b"automatic tag merging of %s failed! "
590 589 b"(use 'hg resolve --tool :merge' or another merge "
591 590 b"tool of your choice)\n"
592 591 ),
593 592 )
594 593 def _itagmerge(repo, mynode, local, other, base, toolconf, backup):
595 594 """
596 595 Uses the internal tag merge algorithm (experimental).
597 596 """
598 597 success, status = tagmerge.merge(repo, local.fctx, other.fctx, base.fctx)
599 598 return success, status, False
600 599
601 600
602 601 @internaltool(b'dump', fullmerge, binary=True, symlink=True)
603 602 def _idump(repo, mynode, local, other, base, toolconf, backup):
604 603 """
605 604 Creates three versions of the files to merge, containing the
606 605 contents of local, other and base. These files can then be used to
607 606 perform a merge manually. If the file to be merged is named
608 607 ``a.txt``, these files will accordingly be named ``a.txt.local``,
609 608 ``a.txt.other`` and ``a.txt.base`` and they will be placed in the
610 609 same directory as ``a.txt``.
611 610
612 611 This implies premerge. Therefore, files aren't dumped, if premerge
613 612 runs successfully. Use :forcedump to forcibly write files out.
614 613 """
615 614 a = _workingpath(repo, local.fctx)
616 615 fd = local.fctx.path()
617 616
618 617 from . import context
619 618
620 619 if isinstance(local.fctx, context.overlayworkingfilectx):
621 620 raise error.InMemoryMergeConflictsError(
622 621 b'in-memory merge does not support the :dump tool.'
623 622 )
624 623
625 624 util.writefile(a + b".local", local.fctx.decodeddata())
626 625 repo.wwrite(fd + b".other", other.fctx.data(), other.fctx.flags())
627 626 repo.wwrite(fd + b".base", base.fctx.data(), base.fctx.flags())
628 627 return False, 1, False
629 628
630 629
631 630 @internaltool(b'forcedump', mergeonly, binary=True, symlink=True)
632 631 def _forcedump(repo, mynode, local, other, base, toolconf, backup):
633 632 """
634 633 Creates three versions of the files as same as :dump, but omits premerge.
635 634 """
636 635 return _idump(repo, mynode, local, other, base, toolconf, backup)
637 636
638 637
639 638 def _xmergeimm(repo, mynode, local, other, base, toolconf, backup):
640 639 # In-memory merge simply raises an exception on all external merge tools,
641 640 # for now.
642 641 #
643 642 # It would be possible to run most tools with temporary files, but this
644 643 # raises the question of what to do if the user only partially resolves the
645 644 # file -- we can't leave a merge state. (Copy to somewhere in the .hg/
646 645 # directory and tell the user how to get it is my best idea, but it's
647 646 # clunky.)
648 647 raise error.InMemoryMergeConflictsError(
649 648 b'in-memory merge does not support external merge tools'
650 649 )
651 650
652 651
653 652 def _describemerge(ui, repo, mynode, fcl, fcb, fco, env, toolpath, args):
654 653 tmpl = ui.config(b'command-templates', b'pre-merge-tool-output')
655 654 if not tmpl:
656 655 return
657 656
658 657 mappingdict = templateutil.mappingdict
659 658 props = {
660 659 b'ctx': fcl.changectx(),
661 660 b'node': hex(mynode),
662 661 b'path': fcl.path(),
663 662 b'local': mappingdict(
664 663 {
665 664 b'ctx': fcl.changectx(),
666 665 b'fctx': fcl,
667 666 b'node': hex(mynode),
668 667 b'name': _(b'local'),
669 668 b'islink': b'l' in fcl.flags(),
670 669 b'label': env[b'HG_MY_LABEL'],
671 670 }
672 671 ),
673 672 b'base': mappingdict(
674 673 {
675 674 b'ctx': fcb.changectx(),
676 675 b'fctx': fcb,
677 676 b'name': _(b'base'),
678 677 b'islink': b'l' in fcb.flags(),
679 678 b'label': env[b'HG_BASE_LABEL'],
680 679 }
681 680 ),
682 681 b'other': mappingdict(
683 682 {
684 683 b'ctx': fco.changectx(),
685 684 b'fctx': fco,
686 685 b'name': _(b'other'),
687 686 b'islink': b'l' in fco.flags(),
688 687 b'label': env[b'HG_OTHER_LABEL'],
689 688 }
690 689 ),
691 690 b'toolpath': toolpath,
692 691 b'toolargs': args,
693 692 }
694 693
695 694 # TODO: make all of this something that can be specified on a per-tool basis
696 695 tmpl = templater.unquotestring(tmpl)
697 696
698 697 # Not using cmdutil.rendertemplate here since it causes errors importing
699 698 # things for us to import cmdutil.
700 699 tres = formatter.templateresources(ui, repo)
701 700 t = formatter.maketemplater(
702 701 ui, tmpl, defaults=templatekw.keywords, resources=tres
703 702 )
704 703 ui.status(t.renderdefault(props))
705 704
706 705
707 706 def _xmerge(repo, mynode, local, other, base, toolconf, backup):
708 707 fcd = local.fctx
709 708 fco = other.fctx
710 709 fca = base.fctx
711 710 tool, toolpath, binary, symlink, scriptfn = toolconf
712 711 uipathfn = scmutil.getuipathfn(repo)
713 712 if fcd.isabsent() or fco.isabsent():
714 713 repo.ui.warn(
715 714 _(b'warning: %s cannot merge change/delete conflict for %s\n')
716 715 % (tool, uipathfn(fcd.path()))
717 716 )
718 717 return False, 1, None
719 718 localpath = _workingpath(repo, fcd)
720 719 args = _toolstr(repo.ui, tool, b"args")
721 720
722 721 with _maketempfiles(
723 722 repo, fco, fca, repo.wvfs.join(backup.path()), b"$output" in args
724 723 ) as temppaths:
725 724 basepath, otherpath, localoutputpath = temppaths
726 725 outpath = b""
726
727 def format_label(input):
728 if input.label_detail:
729 return b'%s: %s' % (input.label, input.label_detail)
730 else:
731 return input.label
732
727 733 env = {
728 734 b'HG_FILE': fcd.path(),
729 735 b'HG_MY_NODE': short(mynode),
730 736 b'HG_OTHER_NODE': short(fco.changectx().node()),
731 737 b'HG_BASE_NODE': short(fca.changectx().node()),
732 738 b'HG_MY_ISLINK': b'l' in fcd.flags(),
733 739 b'HG_OTHER_ISLINK': b'l' in fco.flags(),
734 740 b'HG_BASE_ISLINK': b'l' in fca.flags(),
735 b'HG_MY_LABEL': local.label,
736 b'HG_OTHER_LABEL': other.label,
737 b'HG_BASE_LABEL': base.label,
741 b'HG_MY_LABEL': format_label(local),
742 b'HG_OTHER_LABEL': format_label(other),
743 b'HG_BASE_LABEL': format_label(base),
738 744 }
739 745 ui = repo.ui
740 746
741 747 if b"$output" in args:
742 748 # read input from backup, write to original
743 749 outpath = localpath
744 750 localpath = localoutputpath
745 751 replace = {
746 752 b'local': localpath,
747 753 b'base': basepath,
748 754 b'other': otherpath,
749 755 b'output': outpath,
750 b'labellocal': local.label,
751 b'labelother': other.label,
752 b'labelbase': base.label,
756 b'labellocal': format_label(local),
757 b'labelother': format_label(other),
758 b'labelbase': format_label(base),
753 759 }
754 760 args = util.interpolate(
755 761 br'\$',
756 762 replace,
757 763 args,
758 764 lambda s: procutil.shellquote(util.localpath(s)),
759 765 )
760 766 if _toolbool(ui, tool, b"gui"):
761 767 repo.ui.status(
762 768 _(b'running merge tool %s for file %s\n')
763 769 % (tool, uipathfn(fcd.path()))
764 770 )
765 771 if scriptfn is None:
766 772 cmd = toolpath + b' ' + args
767 773 repo.ui.debug(b'launching merge tool: %s\n' % cmd)
768 774 _describemerge(ui, repo, mynode, fcd, fca, fco, env, toolpath, args)
769 775 r = ui.system(
770 776 cmd, cwd=repo.root, environ=env, blockedtag=b'mergetool'
771 777 )
772 778 else:
773 779 repo.ui.debug(
774 780 b'launching python merge script: %s:%s\n' % (toolpath, scriptfn)
775 781 )
776 782 r = 0
777 783 try:
778 784 # avoid cycle cmdutil->merge->filemerge->extensions->cmdutil
779 785 from . import extensions
780 786
781 787 mod = extensions.loadpath(toolpath, b'hgmerge.%s' % tool)
782 788 except Exception:
783 789 raise error.Abort(
784 790 _(b"loading python merge script failed: %s") % toolpath
785 791 )
786 792 mergefn = getattr(mod, scriptfn, None)
787 793 if mergefn is None:
788 794 raise error.Abort(
789 795 _(b"%s does not have function: %s") % (toolpath, scriptfn)
790 796 )
791 797 argslist = procutil.shellsplit(args)
792 798 # avoid cycle cmdutil->merge->filemerge->hook->extensions->cmdutil
793 799 from . import hook
794 800
795 801 ret, raised = hook.pythonhook(
796 802 ui, repo, b"merge", toolpath, mergefn, {b'args': argslist}, True
797 803 )
798 804 if raised:
799 805 r = 1
800 806 repo.ui.debug(b'merge tool returned: %d\n' % r)
801 807 return True, r, False
802 808
803 809
804 def _populate_label_detail(input, template, pad):
805 """Applies the given template to the ctx, prefixed by the label.
806
807 Pad is the minimum width of the label prefix, so that multiple markers
808 can have aligned templated parts.
809 """
810 def _populate_label_detail(input, template):
811 """Applies the given template to the ctx and stores it in the input."""
810 812 ctx = input.fctx.changectx()
811 813 if ctx.node() is None:
812 814 ctx = ctx.p1()
813 815
814 816 props = {b'ctx': ctx}
815 817 templateresult = template.renderdefault(props)
816
817 label = (b'%s:' % input.label).ljust(pad + 1)
818 mark = b'%s %s' % (label, templateresult)
819 mark = mark.splitlines()[0] # split for safety
820
821 # 8 for the prefix of conflict marker lines (e.g. '<<<<<<< ')
822 input.label = stringutil.ellipsis(mark, 80 - 8)
818 input.label_detail = templateresult.splitlines()[0] # split for safety
823 819
824 820
825 821 def _populate_label_details(repo, inputs, tool=None):
826 """Formats the given labels using the conflict marker template.
827
828 Returns a list of formatted labels.
829 """
822 """Populates the label details using the conflict marker template."""
830 823 ui = repo.ui
831 824 template = ui.config(b'command-templates', b'mergemarker')
832 825 if tool is not None:
833 826 template = _toolstr(ui, tool, b'mergemarkertemplate', template)
834 827 template = templater.unquotestring(template)
835 828 tres = formatter.templateresources(ui, repo)
836 829 tmpl = formatter.maketemplater(
837 830 ui, template, defaults=templatekw.keywords, resources=tres
838 831 )
839 832
840 pad = max(len(input.label) for input in inputs)
841
842 833 for input in inputs:
843 _populate_label_detail(input, tmpl, pad)
834 _populate_label_detail(input, tmpl)
844 835
845 836
846 837 def partextras(labels):
847 838 """Return a dictionary of extra labels for use in prompts to the user
848 839
849 840 Intended use is in strings of the form "(l)ocal%(l)s".
850 841 """
851 842 if labels is None:
852 843 return {
853 844 b"l": b"",
854 845 b"o": b"",
855 846 }
856 847
857 848 return {
858 849 b"l": b" [%s]" % labels[0],
859 850 b"o": b" [%s]" % labels[1],
860 851 }
861 852
862 853
863 854 def _restorebackup(fcd, backup):
864 855 # TODO: Add a workingfilectx.write(otherfilectx) path so we can use
865 856 # util.copy here instead.
866 857 fcd.write(backup.data(), fcd.flags())
867 858
868 859
869 860 def _makebackup(repo, ui, wctx, fcd):
870 861 """Makes and returns a filectx-like object for ``fcd``'s backup file.
871 862
872 863 In addition to preserving the user's pre-existing modifications to `fcd`
873 864 (if any), the backup is used to undo certain premerges, confirm whether a
874 865 merge changed anything, and determine what line endings the new file should
875 866 have.
876 867
877 868 Backups only need to be written once since their content doesn't change
878 869 afterwards.
879 870 """
880 871 if fcd.isabsent():
881 872 return None
882 873 # TODO: Break this import cycle somehow. (filectx -> ctx -> fileset ->
883 874 # merge -> filemerge). (I suspect the fileset import is the weakest link)
884 875 from . import context
885 876
886 877 backup = scmutil.backuppath(ui, repo, fcd.path())
887 878 inworkingdir = backup.startswith(repo.wvfs.base) and not backup.startswith(
888 879 repo.vfs.base
889 880 )
890 881 if isinstance(fcd, context.overlayworkingfilectx) and inworkingdir:
891 882 # If the backup file is to be in the working directory, and we're
892 883 # merging in-memory, we must redirect the backup to the memory context
893 884 # so we don't disturb the working directory.
894 885 relpath = backup[len(repo.wvfs.base) + 1 :]
895 886 wctx[relpath].write(fcd.data(), fcd.flags())
896 887 return wctx[relpath]
897 888 else:
898 889 # Otherwise, write to wherever path the user specified the backups
899 890 # should go. We still need to switch based on whether the source is
900 891 # in-memory so we can use the fast path of ``util.copy`` if both are
901 892 # on disk.
902 893 if isinstance(fcd, context.overlayworkingfilectx):
903 894 util.writefile(backup, fcd.data())
904 895 else:
905 896 a = _workingpath(repo, fcd)
906 897 util.copyfile(a, backup)
907 898 # A arbitraryfilectx is returned, so we can run the same functions on
908 899 # the backup context regardless of where it lives.
909 900 return context.arbitraryfilectx(backup, repo=repo)
910 901
911 902
912 903 @contextlib.contextmanager
913 904 def _maketempfiles(repo, fco, fca, localpath, uselocalpath):
914 905 """Writes out `fco` and `fca` as temporary files, and (if uselocalpath)
915 906 copies `localpath` to another temporary file, so an external merge tool may
916 907 use them.
917 908 """
918 909 tmproot = None
919 910 tmprootprefix = repo.ui.config(b'experimental', b'mergetempdirprefix')
920 911 if tmprootprefix:
921 912 tmproot = pycompat.mkdtemp(prefix=tmprootprefix)
922 913
923 914 def maketempfrompath(prefix, path):
924 915 fullbase, ext = os.path.splitext(path)
925 916 pre = b"%s~%s" % (os.path.basename(fullbase), prefix)
926 917 if tmproot:
927 918 name = os.path.join(tmproot, pre)
928 919 if ext:
929 920 name += ext
930 921 f = open(name, "wb")
931 922 else:
932 923 fd, name = pycompat.mkstemp(prefix=pre + b'.', suffix=ext)
933 924 f = os.fdopen(fd, "wb")
934 925 return f, name
935 926
936 927 def tempfromcontext(prefix, ctx):
937 928 f, name = maketempfrompath(prefix, ctx.path())
938 929 data = ctx.decodeddata()
939 930 f.write(data)
940 931 f.close()
941 932 return name
942 933
943 934 b = tempfromcontext(b"base", fca)
944 935 c = tempfromcontext(b"other", fco)
945 936 d = localpath
946 937 if uselocalpath:
947 938 # We start off with this being the backup filename, so remove the .orig
948 939 # to make syntax-highlighting more likely.
949 940 if d.endswith(b'.orig'):
950 941 d, _ = os.path.splitext(d)
951 942 f, d = maketempfrompath(b"local", d)
952 943 with open(localpath, b'rb') as src:
953 944 f.write(src.read())
954 945 f.close()
955 946
956 947 try:
957 948 yield b, c, d
958 949 finally:
959 950 if tmproot:
960 951 shutil.rmtree(tmproot)
961 952 else:
962 953 util.unlink(b)
963 954 util.unlink(c)
964 955 # if not uselocalpath, d is the 'orig'/backup file which we
965 956 # shouldn't delete.
966 957 if d and uselocalpath:
967 958 util.unlink(d)
968 959
969 960
970 961 def filemerge(repo, wctx, mynode, orig, fcd, fco, fca, labels=None):
971 962 """perform a 3-way merge in the working directory
972 963
973 964 mynode = parent node before merge
974 965 orig = original local filename before merge
975 966 fco = other file context
976 967 fca = ancestor file context
977 968 fcd = local file context for current/destination file
978 969
979 970 Returns whether the merge is complete, the return value of the merge, and
980 971 a boolean indicating whether the file was deleted from disk."""
981 972
982 973 if not fco.cmp(fcd): # files identical?
983 974 return None, False
984 975
985 976 ui = repo.ui
986 977 fd = fcd.path()
987 978 uipathfn = scmutil.getuipathfn(repo)
988 979 fduipath = uipathfn(fd)
989 980 binary = fcd.isbinary() or fco.isbinary() or fca.isbinary()
990 981 symlink = b'l' in fcd.flags() + fco.flags()
991 982 changedelete = fcd.isabsent() or fco.isabsent()
992 983 tool, toolpath = _picktool(repo, ui, fd, binary, symlink, changedelete)
993 984 scriptfn = None
994 985 if tool in internals and tool.startswith(b'internal:'):
995 986 # normalize to new-style names (':merge' etc)
996 987 tool = tool[len(b'internal') :]
997 988 if toolpath and toolpath.startswith(b'python:'):
998 989 invalidsyntax = False
999 990 if toolpath.count(b':') >= 2:
1000 991 script, scriptfn = toolpath[7:].rsplit(b':', 1)
1001 992 if not scriptfn:
1002 993 invalidsyntax = True
1003 994 # missing :callable can lead to spliting on windows drive letter
1004 995 if b'\\' in scriptfn or b'/' in scriptfn:
1005 996 invalidsyntax = True
1006 997 else:
1007 998 invalidsyntax = True
1008 999 if invalidsyntax:
1009 1000 raise error.Abort(_(b"invalid 'python:' syntax: %s") % toolpath)
1010 1001 toolpath = script
1011 1002 ui.debug(
1012 1003 b"picked tool '%s' for %s (binary %s symlink %s changedelete %s)\n"
1013 1004 % (
1014 1005 tool,
1015 1006 fduipath,
1016 1007 pycompat.bytestr(binary),
1017 1008 pycompat.bytestr(symlink),
1018 1009 pycompat.bytestr(changedelete),
1019 1010 )
1020 1011 )
1021 1012
1022 1013 if tool in internals:
1023 1014 func = internals[tool]
1024 1015 mergetype = func.mergetype
1025 1016 onfailure = func.onfailure
1026 1017 precheck = func.precheck
1027 1018 isexternal = False
1028 1019 else:
1029 1020 if wctx.isinmemory():
1030 1021 func = _xmergeimm
1031 1022 else:
1032 1023 func = _xmerge
1033 1024 mergetype = fullmerge
1034 1025 onfailure = _(b"merging %s failed!\n")
1035 1026 precheck = None
1036 1027 isexternal = True
1037 1028
1038 1029 toolconf = tool, toolpath, binary, symlink, scriptfn
1039 1030
1040 1031 if not labels:
1041 1032 labels = [b'local', b'other']
1042 1033 if len(labels) < 3:
1043 1034 labels.append(b'base')
1044 1035 local = simplemerge.MergeInput(fcd, labels[0])
1045 1036 other = simplemerge.MergeInput(fco, labels[1])
1046 1037 base = simplemerge.MergeInput(fca, labels[2])
1047 1038 if mergetype == nomerge:
1048 1039 return func(
1049 1040 repo,
1050 1041 mynode,
1051 1042 local,
1052 1043 other,
1053 1044 base,
1054 1045 toolconf,
1055 1046 )
1056 1047
1057 1048 if orig != fco.path():
1058 1049 ui.status(
1059 1050 _(b"merging %s and %s to %s\n")
1060 1051 % (uipathfn(orig), uipathfn(fco.path()), fduipath)
1061 1052 )
1062 1053 else:
1063 1054 ui.status(_(b"merging %s\n") % fduipath)
1064 1055
1065 1056 ui.debug(b"my %s other %s ancestor %s\n" % (fcd, fco, fca))
1066 1057
1067 1058 if precheck and not precheck(repo, mynode, fcd, fco, fca, toolconf):
1068 1059 if onfailure:
1069 1060 if wctx.isinmemory():
1070 1061 raise error.InMemoryMergeConflictsError(
1071 1062 b'in-memory merge does not support merge conflicts'
1072 1063 )
1073 1064 ui.warn(onfailure % fduipath)
1074 1065 return 1, False
1075 1066
1076 1067 backup = _makebackup(repo, ui, wctx, fcd)
1077 1068 r = 1
1078 1069 try:
1079 1070 internalmarkerstyle = ui.config(b'ui', b'mergemarkers')
1080 1071 if isexternal:
1081 1072 markerstyle = _toolstr(ui, tool, b'mergemarkers')
1082 1073 else:
1083 1074 markerstyle = internalmarkerstyle
1084 1075
1085 1076 if mergetype == fullmerge:
1086 1077 # conflict markers generated by premerge will use 'detailed'
1087 1078 # settings if either ui.mergemarkers or the tool's mergemarkers
1088 1079 # setting is 'detailed'. This way tools can have basic labels in
1089 1080 # space-constrained areas of the UI, but still get full information
1090 1081 # in conflict markers if premerge is 'keep' or 'keep-merge3'.
1091 1082 labeltool = None
1092 1083 if markerstyle != b'basic':
1093 1084 # respect 'tool's mergemarkertemplate (which defaults to
1094 1085 # command-templates.mergemarker)
1095 1086 labeltool = tool
1096 1087 if internalmarkerstyle != b'basic' or markerstyle != b'basic':
1097 1088 _populate_label_details(
1098 1089 repo, [local, other, base], tool=labeltool
1099 1090 )
1100 1091
1101 1092 r = _premerge(
1102 1093 repo,
1103 1094 local,
1104 1095 other,
1105 1096 base,
1106 1097 toolconf,
1107 1098 backup,
1108 1099 )
1109 1100 # we're done if premerge was successful (r is 0)
1110 1101 if not r:
1111 1102 return r, False
1112 1103
1113 1104 # Reset to basic labels
1114 local.label = labels[0]
1115 other.label = labels[1]
1116 base.label = labels[2]
1105 local.label_detail = None
1106 other.label_detail = None
1107 base.label_detail = None
1117 1108
1118 1109 if markerstyle != b'basic':
1119 1110 _populate_label_details(repo, [local, other, base], tool=tool)
1120 1111
1121 1112 needcheck, r, deleted = func(
1122 1113 repo,
1123 1114 mynode,
1124 1115 local,
1125 1116 other,
1126 1117 base,
1127 1118 toolconf,
1128 1119 backup,
1129 1120 )
1130 1121
1131 1122 if needcheck:
1132 1123 r = _check(repo, r, ui, tool, fcd, backup)
1133 1124
1134 1125 if r:
1135 1126 if onfailure:
1136 1127 if wctx.isinmemory():
1137 1128 raise error.InMemoryMergeConflictsError(
1138 1129 b'in-memory merge '
1139 1130 b'does not support '
1140 1131 b'merge conflicts'
1141 1132 )
1142 1133 ui.warn(onfailure % fduipath)
1143 1134 _onfilemergefailure(ui)
1144 1135
1145 1136 return r, deleted
1146 1137 finally:
1147 1138 if not r and backup is not None:
1148 1139 backup.remove()
1149 1140
1150 1141
1151 1142 def _haltmerge():
1152 1143 msg = _(b'merge halted after failed merge (see hg resolve)')
1153 1144 raise error.InterventionRequired(msg)
1154 1145
1155 1146
1156 1147 def _onfilemergefailure(ui):
1157 1148 action = ui.config(b'merge', b'on-failure')
1158 1149 if action == b'prompt':
1159 1150 msg = _(b'continue merge operation (yn)?$$ &Yes $$ &No')
1160 1151 if ui.promptchoice(msg, 0) == 1:
1161 1152 _haltmerge()
1162 1153 if action == b'halt':
1163 1154 _haltmerge()
1164 1155 # default action is 'continue', in which case we neither prompt nor halt
1165 1156
1166 1157
1167 1158 def hasconflictmarkers(data):
1168 1159 # Detect lines starting with a string of 7 identical characters from the
1169 1160 # subset Mercurial uses for conflict markers, followed by either the end of
1170 1161 # line or a space and some text. Note that using [<>=+|-]{7} would detect
1171 1162 # `<><><><><` as a conflict marker, which we don't want.
1172 1163 return bool(
1173 1164 re.search(
1174 1165 br"^([<>=+|-])\1{6}( .*)$",
1175 1166 data,
1176 1167 re.MULTILINE,
1177 1168 )
1178 1169 )
1179 1170
1180 1171
1181 1172 def _check(repo, r, ui, tool, fcd, backup):
1182 1173 fd = fcd.path()
1183 1174 uipathfn = scmutil.getuipathfn(repo)
1184 1175
1185 1176 if not r and (
1186 1177 _toolbool(ui, tool, b"checkconflicts")
1187 1178 or b'conflicts' in _toollist(ui, tool, b"check")
1188 1179 ):
1189 1180 if hasconflictmarkers(fcd.data()):
1190 1181 r = 1
1191 1182
1192 1183 checked = False
1193 1184 if b'prompt' in _toollist(ui, tool, b"check"):
1194 1185 checked = True
1195 1186 if ui.promptchoice(
1196 1187 _(b"was merge of '%s' successful (yn)?$$ &Yes $$ &No")
1197 1188 % uipathfn(fd),
1198 1189 1,
1199 1190 ):
1200 1191 r = 1
1201 1192
1202 1193 if (
1203 1194 not r
1204 1195 and not checked
1205 1196 and (
1206 1197 _toolbool(ui, tool, b"checkchanged")
1207 1198 or b'changed' in _toollist(ui, tool, b"check")
1208 1199 )
1209 1200 ):
1210 1201 if backup is not None and not fcd.cmp(backup):
1211 1202 if ui.promptchoice(
1212 1203 _(
1213 1204 b" output file %s appears unchanged\n"
1214 1205 b"was merge successful (yn)?"
1215 1206 b"$$ &Yes $$ &No"
1216 1207 )
1217 1208 % uipathfn(fd),
1218 1209 1,
1219 1210 ):
1220 1211 r = 1
1221 1212
1222 1213 if backup is not None and _toolbool(ui, tool, b"fixeol"):
1223 1214 _matcheol(_workingpath(repo, fcd), backup)
1224 1215
1225 1216 return r
1226 1217
1227 1218
1228 1219 def _workingpath(repo, ctx):
1229 1220 return repo.wjoin(ctx.path())
1230 1221
1231 1222
1232 1223 def loadinternalmerge(ui, extname, registrarobj):
1233 1224 """Load internal merge tool from specified registrarobj"""
1234 1225 for name, func in pycompat.iteritems(registrarobj._table):
1235 1226 fullname = b':' + name
1236 1227 internals[fullname] = func
1237 1228 internals[b'internal:' + name] = func
1238 1229 internalsdoc[fullname] = func
1239 1230
1240 1231 capabilities = sorted([k for k, v in func.capabilities.items() if v])
1241 1232 if capabilities:
1242 1233 capdesc = b" (actual capabilities: %s)" % b', '.join(
1243 1234 capabilities
1244 1235 )
1245 1236 func.__doc__ = func.__doc__ + pycompat.sysstr(b"\n\n%s" % capdesc)
1246 1237
1247 1238 # to put i18n comments into hg.pot for automatically generated texts
1248 1239
1249 1240 # i18n: "binary" and "symlink" are keywords
1250 1241 # i18n: this text is added automatically
1251 1242 _(b" (actual capabilities: binary, symlink)")
1252 1243 # i18n: "binary" is keyword
1253 1244 # i18n: this text is added automatically
1254 1245 _(b" (actual capabilities: binary)")
1255 1246 # i18n: "symlink" is keyword
1256 1247 # i18n: this text is added automatically
1257 1248 _(b" (actual capabilities: symlink)")
1258 1249
1259 1250
1260 1251 # load built-in merge tools explicitly to setup internalsdoc
1261 1252 loadinternalmerge(None, None, internaltool)
1262 1253
1263 1254 # tell hggettext to extract docstrings from these functions:
1264 1255 i18nfunctions = internals.values()
@@ -1,524 +1,538 b''
1 1 # Copyright (C) 2004, 2005 Canonical Ltd
2 2 #
3 3 # This program is free software; you can redistribute it and/or modify
4 4 # it under the terms of the GNU General Public License as published by
5 5 # the Free Software Foundation; either version 2 of the License, or
6 6 # (at your option) any later version.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU General Public License
14 14 # along with this program; if not, see <http://www.gnu.org/licenses/>.
15 15
16 16 # mbp: "you know that thing where cvs gives you conflict markers?"
17 17 # s: "i hate that."
18 18
19 19 from __future__ import absolute_import
20 20
21 21 from .i18n import _
22 22 from .thirdparty import attr
23 23 from . import (
24 24 error,
25 25 mdiff,
26 26 pycompat,
27 27 )
28 28 from .utils import stringutil
29 29
30 30
31 31 def intersect(ra, rb):
32 32 """Given two ranges return the range where they intersect or None.
33 33
34 34 >>> intersect((0, 10), (0, 6))
35 35 (0, 6)
36 36 >>> intersect((0, 10), (5, 15))
37 37 (5, 10)
38 38 >>> intersect((0, 10), (10, 15))
39 39 >>> intersect((0, 9), (10, 15))
40 40 >>> intersect((0, 9), (7, 15))
41 41 (7, 9)
42 42 """
43 43 assert ra[0] <= ra[1]
44 44 assert rb[0] <= rb[1]
45 45
46 46 sa = max(ra[0], rb[0])
47 47 sb = min(ra[1], rb[1])
48 48 if sa < sb:
49 49 return sa, sb
50 50 else:
51 51 return None
52 52
53 53
54 54 def compare_range(a, astart, aend, b, bstart, bend):
55 55 """Compare a[astart:aend] == b[bstart:bend], without slicing."""
56 56 if (aend - astart) != (bend - bstart):
57 57 return False
58 58 for ia, ib in zip(
59 59 pycompat.xrange(astart, aend), pycompat.xrange(bstart, bend)
60 60 ):
61 61 if a[ia] != b[ib]:
62 62 return False
63 63 else:
64 64 return True
65 65
66 66
67 67 class Merge3Text(object):
68 68 """3-way merge of texts.
69 69
70 70 Given strings BASE, OTHER, THIS, tries to produce a combined text
71 71 incorporating the changes from both BASE->OTHER and BASE->THIS."""
72 72
73 73 def __init__(self, basetext, atext, btext, base=None, a=None, b=None):
74 74 self.basetext = basetext
75 75 self.atext = atext
76 76 self.btext = btext
77 77 if base is None:
78 78 base = mdiff.splitnewlines(basetext)
79 79 if a is None:
80 80 a = mdiff.splitnewlines(atext)
81 81 if b is None:
82 82 b = mdiff.splitnewlines(btext)
83 83 self.base = base
84 84 self.a = a
85 85 self.b = b
86 86
87 87 def merge_groups(self):
88 88 """Yield sequence of line groups. Each one is a tuple:
89 89
90 90 'unchanged', lines
91 91 Lines unchanged from base
92 92
93 93 'a', lines
94 94 Lines taken from a
95 95
96 96 'same', lines
97 97 Lines taken from a (and equal to b)
98 98
99 99 'b', lines
100 100 Lines taken from b
101 101
102 102 'conflict', (base_lines, a_lines, b_lines)
103 103 Lines from base were changed to either a or b and conflict.
104 104 """
105 105 for t in self.merge_regions():
106 106 what = t[0]
107 107 if what == b'unchanged':
108 108 yield what, self.base[t[1] : t[2]]
109 109 elif what == b'a' or what == b'same':
110 110 yield what, self.a[t[1] : t[2]]
111 111 elif what == b'b':
112 112 yield what, self.b[t[1] : t[2]]
113 113 elif what == b'conflict':
114 114 yield (
115 115 what,
116 116 (
117 117 self.base[t[1] : t[2]],
118 118 self.a[t[3] : t[4]],
119 119 self.b[t[5] : t[6]],
120 120 ),
121 121 )
122 122 else:
123 123 raise ValueError(what)
124 124
125 125 def merge_regions(self):
126 126 """Return sequences of matching and conflicting regions.
127 127
128 128 This returns tuples, where the first value says what kind we
129 129 have:
130 130
131 131 'unchanged', start, end
132 132 Take a region of base[start:end]
133 133
134 134 'same', astart, aend
135 135 b and a are different from base but give the same result
136 136
137 137 'a', start, end
138 138 Non-clashing insertion from a[start:end]
139 139
140 140 'conflict', zstart, zend, astart, aend, bstart, bend
141 141 Conflict between a and b, with z as common ancestor
142 142
143 143 Method is as follows:
144 144
145 145 The two sequences align only on regions which match the base
146 146 and both descendants. These are found by doing a two-way diff
147 147 of each one against the base, and then finding the
148 148 intersections between those regions. These "sync regions"
149 149 are by definition unchanged in both and easily dealt with.
150 150
151 151 The regions in between can be in any of three cases:
152 152 conflicted, or changed on only one side.
153 153 """
154 154
155 155 # section a[0:ia] has been disposed of, etc
156 156 iz = ia = ib = 0
157 157
158 158 for region in self.find_sync_regions():
159 159 zmatch, zend, amatch, aend, bmatch, bend = region
160 160 # print 'match base [%d:%d]' % (zmatch, zend)
161 161
162 162 matchlen = zend - zmatch
163 163 assert matchlen >= 0
164 164 assert matchlen == (aend - amatch)
165 165 assert matchlen == (bend - bmatch)
166 166
167 167 len_a = amatch - ia
168 168 len_b = bmatch - ib
169 169 len_base = zmatch - iz
170 170 assert len_a >= 0
171 171 assert len_b >= 0
172 172 assert len_base >= 0
173 173
174 174 # print 'unmatched a=%d, b=%d' % (len_a, len_b)
175 175
176 176 if len_a or len_b:
177 177 # try to avoid actually slicing the lists
178 178 equal_a = compare_range(
179 179 self.a, ia, amatch, self.base, iz, zmatch
180 180 )
181 181 equal_b = compare_range(
182 182 self.b, ib, bmatch, self.base, iz, zmatch
183 183 )
184 184 same = compare_range(self.a, ia, amatch, self.b, ib, bmatch)
185 185
186 186 if same:
187 187 yield b'same', ia, amatch
188 188 elif equal_a and not equal_b:
189 189 yield b'b', ib, bmatch
190 190 elif equal_b and not equal_a:
191 191 yield b'a', ia, amatch
192 192 elif not equal_a and not equal_b:
193 193 yield b'conflict', iz, zmatch, ia, amatch, ib, bmatch
194 194 else:
195 195 raise AssertionError(b"can't handle a=b=base but unmatched")
196 196
197 197 ia = amatch
198 198 ib = bmatch
199 199 iz = zmatch
200 200
201 201 # if the same part of the base was deleted on both sides
202 202 # that's OK, we can just skip it.
203 203
204 204 if matchlen > 0:
205 205 assert ia == amatch
206 206 assert ib == bmatch
207 207 assert iz == zmatch
208 208
209 209 yield b'unchanged', zmatch, zend
210 210 iz = zend
211 211 ia = aend
212 212 ib = bend
213 213
214 214 def find_sync_regions(self):
215 215 """Return a list of sync regions, where both descendants match the base.
216 216
217 217 Generates a list of (base1, base2, a1, a2, b1, b2). There is
218 218 always a zero-length sync region at the end of all the files.
219 219 """
220 220
221 221 ia = ib = 0
222 222 amatches = mdiff.get_matching_blocks(self.basetext, self.atext)
223 223 bmatches = mdiff.get_matching_blocks(self.basetext, self.btext)
224 224 len_a = len(amatches)
225 225 len_b = len(bmatches)
226 226
227 227 sl = []
228 228
229 229 while ia < len_a and ib < len_b:
230 230 abase, amatch, alen = amatches[ia]
231 231 bbase, bmatch, blen = bmatches[ib]
232 232
233 233 # there is an unconflicted block at i; how long does it
234 234 # extend? until whichever one ends earlier.
235 235 i = intersect((abase, abase + alen), (bbase, bbase + blen))
236 236 if i:
237 237 intbase = i[0]
238 238 intend = i[1]
239 239 intlen = intend - intbase
240 240
241 241 # found a match of base[i[0], i[1]]; this may be less than
242 242 # the region that matches in either one
243 243 assert intlen <= alen
244 244 assert intlen <= blen
245 245 assert abase <= intbase
246 246 assert bbase <= intbase
247 247
248 248 asub = amatch + (intbase - abase)
249 249 bsub = bmatch + (intbase - bbase)
250 250 aend = asub + intlen
251 251 bend = bsub + intlen
252 252
253 253 assert self.base[intbase:intend] == self.a[asub:aend], (
254 254 self.base[intbase:intend],
255 255 self.a[asub:aend],
256 256 )
257 257
258 258 assert self.base[intbase:intend] == self.b[bsub:bend]
259 259
260 260 sl.append((intbase, intend, asub, aend, bsub, bend))
261 261
262 262 # advance whichever one ends first in the base text
263 263 if (abase + alen) < (bbase + blen):
264 264 ia += 1
265 265 else:
266 266 ib += 1
267 267
268 268 intbase = len(self.base)
269 269 abase = len(self.a)
270 270 bbase = len(self.b)
271 271 sl.append((intbase, intbase, abase, abase, bbase, bbase))
272 272
273 273 return sl
274 274
275 275
276 276 def _verifytext(text, path, ui, opts):
277 277 """verifies that text is non-binary (unless opts[text] is passed,
278 278 then we just warn)"""
279 279 if stringutil.binary(text):
280 280 msg = _(b"%s looks like a binary file.") % path
281 281 if not opts.get('quiet'):
282 282 ui.warn(_(b'warning: %s\n') % msg)
283 283 if not opts.get('text'):
284 284 raise error.Abort(msg)
285 285 return text
286 286
287 287
288 288 def _format_labels(*inputs):
289 pad = max(len(input.label) if input.label else 0 for input in inputs)
289 290 labels = []
290 291 for input in inputs:
291 292 if input.label:
292 labels.append(input.label)
293 if input.label_detail:
294 label = (
295 (input.label + b':').ljust(pad + 1)
296 + b' '
297 + input.label_detail
298 )
299 else:
300 label = input.label
301 # 8 for the prefix of conflict marker lines (e.g. '<<<<<<< ')
302 labels.append(stringutil.ellipsis(label, 80 - 8))
293 303 else:
294 304 labels.append(None)
295 305 return labels
296 306
297 307
298 308 def _detect_newline(m3):
299 309 if len(m3.a) > 0:
300 310 if m3.a[0].endswith(b'\r\n'):
301 311 return b'\r\n'
302 312 elif m3.a[0].endswith(b'\r'):
303 313 return b'\r'
304 314 return b'\n'
305 315
306 316
307 317 def _minimize(a_lines, b_lines):
308 318 """Trim conflict regions of lines where A and B sides match.
309 319
310 320 Lines where both A and B have made the same changes at the beginning
311 321 or the end of each merge region are eliminated from the conflict
312 322 region and are instead considered the same.
313 323 """
314 324 alen = len(a_lines)
315 325 blen = len(b_lines)
316 326
317 327 # find matches at the front
318 328 ii = 0
319 329 while ii < alen and ii < blen and a_lines[ii] == b_lines[ii]:
320 330 ii += 1
321 331 startmatches = ii
322 332
323 333 # find matches at the end
324 334 ii = 0
325 335 while ii < alen and ii < blen and a_lines[-ii - 1] == b_lines[-ii - 1]:
326 336 ii += 1
327 337 endmatches = ii
328 338
329 339 lines_before = a_lines[:startmatches]
330 340 new_a_lines = a_lines[startmatches : alen - endmatches]
331 341 new_b_lines = b_lines[startmatches : blen - endmatches]
332 342 lines_after = a_lines[alen - endmatches :]
333 343 return lines_before, new_a_lines, new_b_lines, lines_after
334 344
335 345
336 346 def render_minimized(
337 347 m3,
338 348 name_a=None,
339 349 name_b=None,
340 350 start_marker=b'<<<<<<<',
341 351 mid_marker=b'=======',
342 352 end_marker=b'>>>>>>>',
343 353 ):
344 354 """Return merge in cvs-like form."""
345 355 newline = _detect_newline(m3)
346 356 conflicts = False
347 357 if name_a:
348 358 start_marker = start_marker + b' ' + name_a
349 359 if name_b:
350 360 end_marker = end_marker + b' ' + name_b
351 361 merge_groups = m3.merge_groups()
352 362 lines = []
353 363 for what, group_lines in merge_groups:
354 364 if what == b'conflict':
355 365 conflicts = True
356 366 base_lines, a_lines, b_lines = group_lines
357 367 minimized = _minimize(a_lines, b_lines)
358 368 lines_before, a_lines, b_lines, lines_after = minimized
359 369 lines.extend(lines_before)
360 370 lines.append(start_marker + newline)
361 371 lines.extend(a_lines)
362 372 lines.append(mid_marker + newline)
363 373 lines.extend(b_lines)
364 374 lines.append(end_marker + newline)
365 375 lines.extend(lines_after)
366 376 else:
367 377 lines.extend(group_lines)
368 378 return lines, conflicts
369 379
370 380
371 381 def render_merge3(m3, name_a, name_b, name_base):
372 382 """Render conflicts as 3-way conflict markers."""
373 383 newline = _detect_newline(m3)
374 384 conflicts = False
375 385 lines = []
376 386 for what, group_lines in m3.merge_groups():
377 387 if what == b'conflict':
378 388 base_lines, a_lines, b_lines = group_lines
379 389 conflicts = True
380 390 lines.append(b'<<<<<<< ' + name_a + newline)
381 391 lines.extend(a_lines)
382 392 lines.append(b'||||||| ' + name_base + newline)
383 393 lines.extend(base_lines)
384 394 lines.append(b'=======' + newline)
385 395 lines.extend(b_lines)
386 396 lines.append(b'>>>>>>> ' + name_b + newline)
387 397 else:
388 398 lines.extend(group_lines)
389 399 return lines, conflicts
390 400
391 401
392 402 def render_mergediff(m3, name_a, name_b, name_base):
393 403 """Render conflicts as conflict markers with one snapshot and one diff."""
394 404 newline = _detect_newline(m3)
395 405 lines = []
396 406 conflicts = False
397 407 for what, group_lines in m3.merge_groups():
398 408 if what == b'conflict':
399 409 base_lines, a_lines, b_lines = group_lines
400 410 base_text = b''.join(base_lines)
401 411 b_blocks = list(
402 412 mdiff.allblocks(
403 413 base_text,
404 414 b''.join(b_lines),
405 415 lines1=base_lines,
406 416 lines2=b_lines,
407 417 )
408 418 )
409 419 a_blocks = list(
410 420 mdiff.allblocks(
411 421 base_text,
412 422 b''.join(a_lines),
413 423 lines1=base_lines,
414 424 lines2=b_lines,
415 425 )
416 426 )
417 427
418 428 def matching_lines(blocks):
419 429 return sum(
420 430 block[1] - block[0]
421 431 for block, kind in blocks
422 432 if kind == b'='
423 433 )
424 434
425 435 def diff_lines(blocks, lines1, lines2):
426 436 for block, kind in blocks:
427 437 if kind == b'=':
428 438 for line in lines1[block[0] : block[1]]:
429 439 yield b' ' + line
430 440 else:
431 441 for line in lines1[block[0] : block[1]]:
432 442 yield b'-' + line
433 443 for line in lines2[block[2] : block[3]]:
434 444 yield b'+' + line
435 445
436 446 lines.append(b"<<<<<<<" + newline)
437 447 if matching_lines(a_blocks) < matching_lines(b_blocks):
438 448 lines.append(b"======= " + name_a + newline)
439 449 lines.extend(a_lines)
440 450 lines.append(b"------- " + name_base + newline)
441 451 lines.append(b"+++++++ " + name_b + newline)
442 452 lines.extend(diff_lines(b_blocks, base_lines, b_lines))
443 453 else:
444 454 lines.append(b"------- " + name_base + newline)
445 455 lines.append(b"+++++++ " + name_a + newline)
446 456 lines.extend(diff_lines(a_blocks, base_lines, a_lines))
447 457 lines.append(b"======= " + name_b + newline)
448 458 lines.extend(b_lines)
449 459 lines.append(b">>>>>>>" + newline)
450 460 conflicts = True
451 461 else:
452 462 lines.extend(group_lines)
453 463 return lines, conflicts
454 464
455 465
456 466 def _resolve(m3, sides):
457 467 lines = []
458 468 for what, group_lines in m3.merge_groups():
459 469 if what == b'conflict':
460 470 for side in sides:
461 471 lines.extend(group_lines[side])
462 472 else:
463 473 lines.extend(group_lines)
464 474 return lines
465 475
466 476
467 477 @attr.s
468 478 class MergeInput(object):
469 479 fctx = attr.ib()
470 480 label = attr.ib(default=None)
481 # If the "detail" part is set, then that is rendered after the label and
482 # separated by a ':'. The label is padded to make the ':' aligned among all
483 # merge inputs.
484 label_detail = attr.ib(default=None)
471 485
472 486
473 487 def simplemerge(ui, local, base, other, **opts):
474 488 """Performs the simplemerge algorithm.
475 489
476 490 The merged result is written into `localctx`.
477 491 """
478 492
479 493 def readctx(ctx):
480 494 # Merges were always run in the working copy before, which means
481 495 # they used decoded data, if the user defined any repository
482 496 # filters.
483 497 #
484 498 # Maintain that behavior today for BC, though perhaps in the future
485 499 # it'd be worth considering whether merging encoded data (what the
486 500 # repository usually sees) might be more useful.
487 501 return _verifytext(ctx.decodeddata(), ctx.path(), ui, opts)
488 502
489 503 try:
490 504 localtext = readctx(local.fctx)
491 505 basetext = readctx(base.fctx)
492 506 othertext = readctx(other.fctx)
493 507 except error.Abort:
494 508 return True
495 509
496 510 m3 = Merge3Text(basetext, localtext, othertext)
497 511 conflicts = False
498 512 mode = opts.get('mode', b'merge')
499 513 if mode == b'union':
500 514 lines = _resolve(m3, (1, 2))
501 515 elif mode == b'local':
502 516 lines = _resolve(m3, (1,))
503 517 elif mode == b'other':
504 518 lines = _resolve(m3, (2,))
505 519 else:
506 520 if mode == b'mergediff':
507 521 labels = _format_labels(local, other, base)
508 522 lines, conflicts = render_mergediff(m3, *labels)
509 523 elif mode == b'merge3':
510 524 labels = _format_labels(local, other, base)
511 525 lines, conflicts = render_merge3(m3, *labels)
512 526 else:
513 527 labels = _format_labels(local, other)
514 528 lines, conflicts = render_minimized(m3, *labels)
515 529
516 530 mergedtext = b''.join(lines)
517 531 if opts.get('print'):
518 532 ui.fout.write(mergedtext)
519 533 else:
520 534 # local.fctx.flags() already has the merged flags (done in
521 535 # mergestate.resolve())
522 536 local.fctx.write(mergedtext, local.fctx.flags())
523 537
524 538 return conflicts
@@ -1,25 +1,31 b''
1 1 == New Features ==
2 2
3 3
4 4 == Default Format Change ==
5 5
6 6 These changes affects newly created repositories (or new clone) done with
7 7 Mercurial XXX.
8 8
9 9
10 10 == New Experimental Features ==
11 11
12 12 == Bug Fixes ==
13 13
14 14 The `--no-check` and `--no-merge` now properly overwrite the behavior from `commands.update.check`.
15 15
16 16 == Backwards Compatibility Changes ==
17 17
18 18 The remotefilelog extension now requires an appropiate excludepattern
19 19 for subrepositories.
20 20
21 The labels passed to merge tools have changed slightly. Merge tools can get
22 labels passed to them if you include `$labellocal`, `$labelbase`, and/or
23 `$labelother` in the `merge-tool.<tool name>.args` configuration. These labels
24 used to have some space-padding, and truncation to fit within 72 columns. Both
25 the padding and the truncation has been removed.
26
21 27 == Internal API Changes ==
22 28
23 29 The following functions have been removed:
24 30
25 31 Miscellaneous:
@@ -1,2129 +1,2129 b''
1 1 test merge-tools configuration - mostly exercising filemerge.py
2 2
3 3 $ unset HGMERGE # make sure HGMERGE doesn't interfere with the test
4 4 $ cat >> $HGRCPATH << EOF
5 5 > [ui]
6 6 > merge=
7 7 > [commands]
8 8 > merge.require-rev=True
9 9 > EOF
10 10 $ hg init repo
11 11 $ cd repo
12 12
13 13 revision 0
14 14
15 15 $ echo "revision 0" > f
16 16 $ echo "space" >> f
17 17 $ hg commit -Am "revision 0"
18 18 adding f
19 19
20 20 revision 1
21 21
22 22 $ echo "revision 1" > f
23 23 $ echo "space" >> f
24 24 $ hg commit -Am "revision 1"
25 25 $ hg update 0 > /dev/null
26 26
27 27 revision 2
28 28
29 29 $ echo "revision 2" > f
30 30 $ echo "space" >> f
31 31 $ hg commit -Am "revision 2"
32 32 created new head
33 33 $ hg update 0 > /dev/null
34 34
35 35 revision 3 - simple to merge
36 36
37 37 $ echo "revision 3" >> f
38 38 $ hg commit -Am "revision 3"
39 39 created new head
40 40
41 41 revision 4 - hard to merge
42 42
43 43 $ hg update 0 > /dev/null
44 44 $ echo "revision 4" > f
45 45 $ hg commit -Am "revision 4"
46 46 created new head
47 47
48 48 $ echo "[merge-tools]" > .hg/hgrc
49 49
50 50 $ beforemerge() {
51 51 > cat .hg/hgrc
52 52 > echo "# hg update -C 1"
53 53 > hg update -C 1 > /dev/null
54 54 > }
55 55 $ aftermerge() {
56 56 > echo "# cat f"
57 57 > cat f
58 58 > echo "# hg stat"
59 59 > hg stat
60 60 > echo "# hg resolve --list"
61 61 > hg resolve --list
62 62 > rm -f f.orig
63 63 > }
64 64
65 65 Tool selection
66 66
67 67 default is internal merge:
68 68
69 69 $ beforemerge
70 70 [merge-tools]
71 71 # hg update -C 1
72 72
73 73 hg merge -r 2
74 74 override $PATH to ensure hgmerge not visible; use $PYTHON in case we're
75 75 running from a devel copy, not a temp installation
76 76
77 77 $ PATH="/usr/sbin" "$PYTHON" "$BINDIR"/hg merge -r 2
78 78 merging f
79 79 warning: conflicts while merging f! (edit, then use 'hg resolve --mark')
80 80 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
81 81 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
82 82 [1]
83 83 $ aftermerge
84 84 # cat f
85 85 <<<<<<< working copy: ef83787e2614 - test: revision 1
86 86 revision 1
87 87 =======
88 88 revision 2
89 89 >>>>>>> merge rev: 0185f4e0cf02 - test: revision 2
90 90 space
91 91 # hg stat
92 92 M f
93 93 ? f.orig
94 94 # hg resolve --list
95 95 U f
96 96
97 97 simplest hgrc using false for merge:
98 98
99 99 $ echo "false.whatever=" >> .hg/hgrc
100 100 $ beforemerge
101 101 [merge-tools]
102 102 false.whatever=
103 103 # hg update -C 1
104 104 $ hg merge -r 2
105 105 merging f
106 106 merging f failed!
107 107 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
108 108 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
109 109 [1]
110 110 $ aftermerge
111 111 # cat f
112 112 revision 1
113 113 space
114 114 # hg stat
115 115 M f
116 116 ? f.orig
117 117 # hg resolve --list
118 118 U f
119 119
120 120 #if unix-permissions
121 121
122 122 unexecutable file in $PATH shouldn't be found:
123 123
124 124 $ echo "echo fail" > false
125 125 $ hg up -qC 1
126 126 $ PATH="`pwd`:/usr/sbin" "$PYTHON" "$BINDIR"/hg merge -r 2
127 127 merging f
128 128 warning: conflicts while merging f! (edit, then use 'hg resolve --mark')
129 129 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
130 130 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
131 131 [1]
132 132 $ rm false
133 133
134 134 #endif
135 135
136 136 executable directory in $PATH shouldn't be found:
137 137
138 138 $ mkdir false
139 139 $ hg up -qC 1
140 140 $ PATH="`pwd`:/usr/sbin" "$PYTHON" "$BINDIR"/hg merge -r 2
141 141 merging f
142 142 warning: conflicts while merging f! (edit, then use 'hg resolve --mark')
143 143 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
144 144 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
145 145 [1]
146 146 $ rmdir false
147 147
148 148 true with higher .priority gets precedence:
149 149
150 150 $ echo "true.priority=1" >> .hg/hgrc
151 151 $ beforemerge
152 152 [merge-tools]
153 153 false.whatever=
154 154 true.priority=1
155 155 # hg update -C 1
156 156 $ hg merge -r 2
157 157 merging f
158 158 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
159 159 (branch merge, don't forget to commit)
160 160 $ aftermerge
161 161 # cat f
162 162 revision 1
163 163 space
164 164 # hg stat
165 165 M f
166 166 # hg resolve --list
167 167 R f
168 168
169 169 unless lowered on command line:
170 170
171 171 $ beforemerge
172 172 [merge-tools]
173 173 false.whatever=
174 174 true.priority=1
175 175 # hg update -C 1
176 176 $ hg merge -r 2 --config merge-tools.true.priority=-7
177 177 merging f
178 178 merging f failed!
179 179 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
180 180 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
181 181 [1]
182 182 $ aftermerge
183 183 # cat f
184 184 revision 1
185 185 space
186 186 # hg stat
187 187 M f
188 188 ? f.orig
189 189 # hg resolve --list
190 190 U f
191 191
192 192 or false set higher on command line:
193 193
194 194 $ beforemerge
195 195 [merge-tools]
196 196 false.whatever=
197 197 true.priority=1
198 198 # hg update -C 1
199 199 $ hg merge -r 2 --config merge-tools.false.priority=117
200 200 merging f
201 201 merging f failed!
202 202 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
203 203 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
204 204 [1]
205 205 $ aftermerge
206 206 # cat f
207 207 revision 1
208 208 space
209 209 # hg stat
210 210 M f
211 211 ? f.orig
212 212 # hg resolve --list
213 213 U f
214 214
215 215 or true set to disabled:
216 216 $ beforemerge
217 217 [merge-tools]
218 218 false.whatever=
219 219 true.priority=1
220 220 # hg update -C 1
221 221 $ hg merge -r 2 --config merge-tools.true.disabled=yes
222 222 merging f
223 223 merging f failed!
224 224 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
225 225 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
226 226 [1]
227 227 $ aftermerge
228 228 # cat f
229 229 revision 1
230 230 space
231 231 # hg stat
232 232 M f
233 233 ? f.orig
234 234 # hg resolve --list
235 235 U f
236 236
237 237 or true.executable not found in PATH:
238 238
239 239 $ beforemerge
240 240 [merge-tools]
241 241 false.whatever=
242 242 true.priority=1
243 243 # hg update -C 1
244 244 $ hg merge -r 2 --config merge-tools.true.executable=nonexistentmergetool
245 245 merging f
246 246 merging f failed!
247 247 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
248 248 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
249 249 [1]
250 250 $ aftermerge
251 251 # cat f
252 252 revision 1
253 253 space
254 254 # hg stat
255 255 M f
256 256 ? f.orig
257 257 # hg resolve --list
258 258 U f
259 259
260 260 or true.executable with bogus path:
261 261
262 262 $ beforemerge
263 263 [merge-tools]
264 264 false.whatever=
265 265 true.priority=1
266 266 # hg update -C 1
267 267 $ hg merge -r 2 --config merge-tools.true.executable=/nonexistent/mergetool
268 268 merging f
269 269 merging f failed!
270 270 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
271 271 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
272 272 [1]
273 273 $ aftermerge
274 274 # cat f
275 275 revision 1
276 276 space
277 277 # hg stat
278 278 M f
279 279 ? f.orig
280 280 # hg resolve --list
281 281 U f
282 282
283 283 but true.executable set to cat found in PATH works:
284 284
285 285 $ echo "true.executable=cat" >> .hg/hgrc
286 286 $ beforemerge
287 287 [merge-tools]
288 288 false.whatever=
289 289 true.priority=1
290 290 true.executable=cat
291 291 # hg update -C 1
292 292 $ hg merge -r 2
293 293 merging f
294 294 revision 1
295 295 space
296 296 revision 0
297 297 space
298 298 revision 2
299 299 space
300 300 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
301 301 (branch merge, don't forget to commit)
302 302 $ aftermerge
303 303 # cat f
304 304 revision 1
305 305 space
306 306 # hg stat
307 307 M f
308 308 # hg resolve --list
309 309 R f
310 310
311 311 and true.executable set to cat with path works:
312 312
313 313 $ beforemerge
314 314 [merge-tools]
315 315 false.whatever=
316 316 true.priority=1
317 317 true.executable=cat
318 318 # hg update -C 1
319 319 $ hg merge -r 2 --config merge-tools.true.executable=cat
320 320 merging f
321 321 revision 1
322 322 space
323 323 revision 0
324 324 space
325 325 revision 2
326 326 space
327 327 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
328 328 (branch merge, don't forget to commit)
329 329 $ aftermerge
330 330 # cat f
331 331 revision 1
332 332 space
333 333 # hg stat
334 334 M f
335 335 # hg resolve --list
336 336 R f
337 337
338 338 executable set to python script that succeeds:
339 339
340 340 $ cat > "$TESTTMP/myworkingmerge.py" <<EOF
341 341 > def myworkingmergefn(ui, repo, args, **kwargs):
342 342 > return False
343 343 > EOF
344 344 $ beforemerge
345 345 [merge-tools]
346 346 false.whatever=
347 347 true.priority=1
348 348 true.executable=cat
349 349 # hg update -C 1
350 350 $ hg merge -r 2 --config merge-tools.true.executable="python:$TESTTMP/myworkingmerge.py:myworkingmergefn"
351 351 merging f
352 352 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
353 353 (branch merge, don't forget to commit)
354 354 $ aftermerge
355 355 # cat f
356 356 revision 1
357 357 space
358 358 # hg stat
359 359 M f
360 360 # hg resolve --list
361 361 R f
362 362
363 363 executable set to python script that fails:
364 364
365 365 $ cat > "$TESTTMP/mybrokenmerge.py" <<EOF
366 366 > def mybrokenmergefn(ui, repo, args, **kwargs):
367 367 > ui.write(b"some fail message\n")
368 368 > return True
369 369 > EOF
370 370 $ beforemerge
371 371 [merge-tools]
372 372 false.whatever=
373 373 true.priority=1
374 374 true.executable=cat
375 375 # hg update -C 1
376 376 $ hg merge -r 2 --config merge-tools.true.executable="python:$TESTTMP/mybrokenmerge.py:mybrokenmergefn"
377 377 merging f
378 378 some fail message
379 379 abort: $TESTTMP/mybrokenmerge.py hook failed
380 380 [40]
381 381 $ aftermerge
382 382 # cat f
383 383 revision 1
384 384 space
385 385 # hg stat
386 386 ? f.orig
387 387 # hg resolve --list
388 388 U f
389 389
390 390 executable set to python script that is missing function:
391 391
392 392 $ beforemerge
393 393 [merge-tools]
394 394 false.whatever=
395 395 true.priority=1
396 396 true.executable=cat
397 397 # hg update -C 1
398 398 $ hg merge -r 2 --config merge-tools.true.executable="python:$TESTTMP/myworkingmerge.py:missingFunction"
399 399 merging f
400 400 abort: $TESTTMP/myworkingmerge.py does not have function: missingFunction
401 401 [255]
402 402 $ aftermerge
403 403 # cat f
404 404 revision 1
405 405 space
406 406 # hg stat
407 407 ? f.orig
408 408 # hg resolve --list
409 409 U f
410 410
411 411 executable set to missing python script:
412 412
413 413 $ beforemerge
414 414 [merge-tools]
415 415 false.whatever=
416 416 true.priority=1
417 417 true.executable=cat
418 418 # hg update -C 1
419 419 $ hg merge -r 2 --config merge-tools.true.executable="python:$TESTTMP/missingpythonscript.py:mergefn"
420 420 merging f
421 421 abort: loading python merge script failed: $TESTTMP/missingpythonscript.py
422 422 [255]
423 423 $ aftermerge
424 424 # cat f
425 425 revision 1
426 426 space
427 427 # hg stat
428 428 ? f.orig
429 429 # hg resolve --list
430 430 U f
431 431
432 432 executable set to python script but callable function is missing:
433 433
434 434 $ beforemerge
435 435 [merge-tools]
436 436 false.whatever=
437 437 true.priority=1
438 438 true.executable=cat
439 439 # hg update -C 1
440 440 $ hg merge -r 2 --config merge-tools.true.executable="python:$TESTTMP/myworkingmerge.py"
441 441 abort: invalid 'python:' syntax: python:$TESTTMP/myworkingmerge.py
442 442 [255]
443 443 $ aftermerge
444 444 # cat f
445 445 revision 1
446 446 space
447 447 # hg stat
448 448 # hg resolve --list
449 449 U f
450 450
451 451 executable set to python script but callable function is empty string:
452 452
453 453 $ beforemerge
454 454 [merge-tools]
455 455 false.whatever=
456 456 true.priority=1
457 457 true.executable=cat
458 458 # hg update -C 1
459 459 $ hg merge -r 2 --config merge-tools.true.executable="python:$TESTTMP/myworkingmerge.py:"
460 460 abort: invalid 'python:' syntax: python:$TESTTMP/myworkingmerge.py:
461 461 [255]
462 462 $ aftermerge
463 463 # cat f
464 464 revision 1
465 465 space
466 466 # hg stat
467 467 # hg resolve --list
468 468 U f
469 469
470 470 executable set to python script but callable function is missing and path contains colon:
471 471
472 472 $ beforemerge
473 473 [merge-tools]
474 474 false.whatever=
475 475 true.priority=1
476 476 true.executable=cat
477 477 # hg update -C 1
478 478 $ hg merge -r 2 --config merge-tools.true.executable="python:$TESTTMP/some:dir/myworkingmerge.py"
479 479 abort: invalid 'python:' syntax: python:$TESTTMP/some:dir/myworkingmerge.py
480 480 [255]
481 481 $ aftermerge
482 482 # cat f
483 483 revision 1
484 484 space
485 485 # hg stat
486 486 # hg resolve --list
487 487 U f
488 488
489 489 executable set to python script filename that contains spaces:
490 490
491 491 $ mkdir -p "$TESTTMP/my path"
492 492 $ cat > "$TESTTMP/my path/my working merge with spaces in filename.py" <<EOF
493 493 > def myworkingmergefn(ui, repo, args, **kwargs):
494 494 > return False
495 495 > EOF
496 496 $ beforemerge
497 497 [merge-tools]
498 498 false.whatever=
499 499 true.priority=1
500 500 true.executable=cat
501 501 # hg update -C 1
502 502 $ hg merge -r 2 --config "merge-tools.true.executable=python:$TESTTMP/my path/my working merge with spaces in filename.py:myworkingmergefn"
503 503 merging f
504 504 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
505 505 (branch merge, don't forget to commit)
506 506 $ aftermerge
507 507 # cat f
508 508 revision 1
509 509 space
510 510 # hg stat
511 511 M f
512 512 # hg resolve --list
513 513 R f
514 514
515 515 #if unix-permissions
516 516
517 517 environment variables in true.executable are handled:
518 518
519 519 $ echo 'echo "custom merge tool"' > .hg/merge.sh
520 520 $ beforemerge
521 521 [merge-tools]
522 522 false.whatever=
523 523 true.priority=1
524 524 true.executable=cat
525 525 # hg update -C 1
526 526 $ hg --config merge-tools.true.executable='sh' \
527 527 > --config merge-tools.true.args=.hg/merge.sh \
528 528 > merge -r 2
529 529 merging f
530 530 custom merge tool
531 531 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
532 532 (branch merge, don't forget to commit)
533 533 $ aftermerge
534 534 # cat f
535 535 revision 1
536 536 space
537 537 # hg stat
538 538 M f
539 539 # hg resolve --list
540 540 R f
541 541
542 542 #endif
543 543
544 544 Tool selection and merge-patterns
545 545
546 546 merge-patterns specifies new tool false:
547 547
548 548 $ beforemerge
549 549 [merge-tools]
550 550 false.whatever=
551 551 true.priority=1
552 552 true.executable=cat
553 553 # hg update -C 1
554 554 $ hg merge -r 2 --config merge-patterns.f=false
555 555 merging f
556 556 merging f failed!
557 557 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
558 558 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
559 559 [1]
560 560 $ aftermerge
561 561 # cat f
562 562 revision 1
563 563 space
564 564 # hg stat
565 565 M f
566 566 ? f.orig
567 567 # hg resolve --list
568 568 U f
569 569
570 570 merge-patterns specifies executable not found in PATH and gets warning:
571 571
572 572 $ beforemerge
573 573 [merge-tools]
574 574 false.whatever=
575 575 true.priority=1
576 576 true.executable=cat
577 577 # hg update -C 1
578 578 $ hg merge -r 2 --config merge-patterns.f=true --config merge-tools.true.executable=nonexistentmergetool
579 579 couldn't find merge tool true (for pattern f)
580 580 merging f
581 581 merging f failed!
582 582 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
583 583 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
584 584 [1]
585 585 $ aftermerge
586 586 # cat f
587 587 revision 1
588 588 space
589 589 # hg stat
590 590 M f
591 591 ? f.orig
592 592 # hg resolve --list
593 593 U f
594 594
595 595 merge-patterns specifies executable with bogus path and gets warning:
596 596
597 597 $ beforemerge
598 598 [merge-tools]
599 599 false.whatever=
600 600 true.priority=1
601 601 true.executable=cat
602 602 # hg update -C 1
603 603 $ hg merge -r 2 --config merge-patterns.f=true --config merge-tools.true.executable=/nonexistent/mergetool
604 604 couldn't find merge tool true (for pattern f)
605 605 merging f
606 606 merging f failed!
607 607 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
608 608 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
609 609 [1]
610 610 $ aftermerge
611 611 # cat f
612 612 revision 1
613 613 space
614 614 # hg stat
615 615 M f
616 616 ? f.orig
617 617 # hg resolve --list
618 618 U f
619 619
620 620 ui.merge overrules priority
621 621
622 622 ui.merge specifies false:
623 623
624 624 $ beforemerge
625 625 [merge-tools]
626 626 false.whatever=
627 627 true.priority=1
628 628 true.executable=cat
629 629 # hg update -C 1
630 630 $ hg merge -r 2 --config ui.merge=false
631 631 merging f
632 632 merging f failed!
633 633 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
634 634 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
635 635 [1]
636 636 $ aftermerge
637 637 # cat f
638 638 revision 1
639 639 space
640 640 # hg stat
641 641 M f
642 642 ? f.orig
643 643 # hg resolve --list
644 644 U f
645 645
646 646 ui.merge specifies internal:fail:
647 647
648 648 $ beforemerge
649 649 [merge-tools]
650 650 false.whatever=
651 651 true.priority=1
652 652 true.executable=cat
653 653 # hg update -C 1
654 654 $ hg merge -r 2 --config ui.merge=internal:fail
655 655 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
656 656 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
657 657 [1]
658 658 $ aftermerge
659 659 # cat f
660 660 revision 1
661 661 space
662 662 # hg stat
663 663 M f
664 664 # hg resolve --list
665 665 U f
666 666
667 667 ui.merge specifies :local (without internal prefix):
668 668
669 669 $ beforemerge
670 670 [merge-tools]
671 671 false.whatever=
672 672 true.priority=1
673 673 true.executable=cat
674 674 # hg update -C 1
675 675 $ hg merge -r 2 --config ui.merge=:local
676 676 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
677 677 (branch merge, don't forget to commit)
678 678 $ aftermerge
679 679 # cat f
680 680 revision 1
681 681 space
682 682 # hg stat
683 683 M f
684 684 # hg resolve --list
685 685 R f
686 686
687 687 ui.merge specifies internal:other:
688 688
689 689 $ beforemerge
690 690 [merge-tools]
691 691 false.whatever=
692 692 true.priority=1
693 693 true.executable=cat
694 694 # hg update -C 1
695 695 $ hg merge -r 2 --config ui.merge=internal:other
696 696 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
697 697 (branch merge, don't forget to commit)
698 698 $ aftermerge
699 699 # cat f
700 700 revision 2
701 701 space
702 702 # hg stat
703 703 M f
704 704 # hg resolve --list
705 705 R f
706 706
707 707 ui.merge specifies internal:prompt:
708 708
709 709 $ beforemerge
710 710 [merge-tools]
711 711 false.whatever=
712 712 true.priority=1
713 713 true.executable=cat
714 714 # hg update -C 1
715 715 $ hg merge -r 2 --config ui.merge=internal:prompt
716 716 file 'f' needs to be resolved.
717 717 You can keep (l)ocal [working copy], take (o)ther [merge rev], or leave (u)nresolved.
718 718 What do you want to do? u
719 719 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
720 720 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
721 721 [1]
722 722 $ aftermerge
723 723 # cat f
724 724 revision 1
725 725 space
726 726 # hg stat
727 727 M f
728 728 # hg resolve --list
729 729 U f
730 730
731 731 ui.merge specifies :prompt, with 'leave unresolved' chosen
732 732
733 733 $ beforemerge
734 734 [merge-tools]
735 735 false.whatever=
736 736 true.priority=1
737 737 true.executable=cat
738 738 # hg update -C 1
739 739 $ hg merge -r 2 --config ui.merge=:prompt --config ui.interactive=True << EOF
740 740 > u
741 741 > EOF
742 742 file 'f' needs to be resolved.
743 743 You can keep (l)ocal [working copy], take (o)ther [merge rev], or leave (u)nresolved.
744 744 What do you want to do? u
745 745 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
746 746 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
747 747 [1]
748 748 $ aftermerge
749 749 # cat f
750 750 revision 1
751 751 space
752 752 # hg stat
753 753 M f
754 754 # hg resolve --list
755 755 U f
756 756
757 757 prompt with EOF
758 758
759 759 $ beforemerge
760 760 [merge-tools]
761 761 false.whatever=
762 762 true.priority=1
763 763 true.executable=cat
764 764 # hg update -C 1
765 765 $ hg merge -r 2 --config ui.merge=internal:prompt --config ui.interactive=true
766 766 file 'f' needs to be resolved.
767 767 You can keep (l)ocal [working copy], take (o)ther [merge rev], or leave (u)nresolved.
768 768 What do you want to do?
769 769 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
770 770 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
771 771 [1]
772 772 $ aftermerge
773 773 # cat f
774 774 revision 1
775 775 space
776 776 # hg stat
777 777 M f
778 778 # hg resolve --list
779 779 U f
780 780 $ hg resolve --all --config ui.merge=internal:prompt --config ui.interactive=true
781 781 file 'f' needs to be resolved.
782 782 You can keep (l)ocal [working copy], take (o)ther [merge rev], or leave (u)nresolved.
783 783 What do you want to do?
784 784 [1]
785 785 $ aftermerge
786 786 # cat f
787 787 revision 1
788 788 space
789 789 # hg stat
790 790 M f
791 791 ? f.orig
792 792 # hg resolve --list
793 793 U f
794 794 $ rm f
795 795 $ hg resolve --all --config ui.merge=internal:prompt --config ui.interactive=true
796 796 file 'f' needs to be resolved.
797 797 You can keep (l)ocal [working copy], take (o)ther [merge rev], or leave (u)nresolved.
798 798 What do you want to do?
799 799 [1]
800 800 $ aftermerge
801 801 # cat f
802 802 revision 1
803 803 space
804 804 # hg stat
805 805 M f
806 806 # hg resolve --list
807 807 U f
808 808 $ hg resolve --all --config ui.merge=internal:prompt
809 809 file 'f' needs to be resolved.
810 810 You can keep (l)ocal [working copy], take (o)ther [merge rev], or leave (u)nresolved.
811 811 What do you want to do? u
812 812 [1]
813 813 $ aftermerge
814 814 # cat f
815 815 revision 1
816 816 space
817 817 # hg stat
818 818 M f
819 819 ? f.orig
820 820 # hg resolve --list
821 821 U f
822 822
823 823 ui.merge specifies internal:dump:
824 824
825 825 $ beforemerge
826 826 [merge-tools]
827 827 false.whatever=
828 828 true.priority=1
829 829 true.executable=cat
830 830 # hg update -C 1
831 831 $ hg merge -r 2 --config ui.merge=internal:dump
832 832 merging f
833 833 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
834 834 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
835 835 [1]
836 836 $ aftermerge
837 837 # cat f
838 838 revision 1
839 839 space
840 840 # hg stat
841 841 M f
842 842 ? f.base
843 843 ? f.local
844 844 ? f.orig
845 845 ? f.other
846 846 # hg resolve --list
847 847 U f
848 848
849 849 f.base:
850 850
851 851 $ cat f.base
852 852 revision 0
853 853 space
854 854
855 855 f.local:
856 856
857 857 $ cat f.local
858 858 revision 1
859 859 space
860 860
861 861 f.other:
862 862
863 863 $ cat f.other
864 864 revision 2
865 865 space
866 866 $ rm f.base f.local f.other
867 867
868 868 check that internal:dump doesn't dump files if premerge runs
869 869 successfully
870 870
871 871 $ beforemerge
872 872 [merge-tools]
873 873 false.whatever=
874 874 true.priority=1
875 875 true.executable=cat
876 876 # hg update -C 1
877 877 $ hg merge -r 3 --config ui.merge=internal:dump
878 878 merging f
879 879 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
880 880 (branch merge, don't forget to commit)
881 881
882 882 $ aftermerge
883 883 # cat f
884 884 revision 1
885 885 space
886 886 revision 3
887 887 # hg stat
888 888 M f
889 889 # hg resolve --list
890 890 R f
891 891
892 892 check that internal:forcedump dumps files, even if local and other can
893 893 be merged easily
894 894
895 895 $ beforemerge
896 896 [merge-tools]
897 897 false.whatever=
898 898 true.priority=1
899 899 true.executable=cat
900 900 # hg update -C 1
901 901 $ hg merge -r 3 --config ui.merge=internal:forcedump
902 902 merging f
903 903 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
904 904 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
905 905 [1]
906 906 $ aftermerge
907 907 # cat f
908 908 revision 1
909 909 space
910 910 # hg stat
911 911 M f
912 912 ? f.base
913 913 ? f.local
914 914 ? f.orig
915 915 ? f.other
916 916 # hg resolve --list
917 917 U f
918 918
919 919 $ cat f.base
920 920 revision 0
921 921 space
922 922
923 923 $ cat f.local
924 924 revision 1
925 925 space
926 926
927 927 $ cat f.other
928 928 revision 0
929 929 space
930 930 revision 3
931 931
932 932 $ rm -f f.base f.local f.other
933 933
934 934 ui.merge specifies internal:other but is overruled by pattern for false:
935 935
936 936 $ beforemerge
937 937 [merge-tools]
938 938 false.whatever=
939 939 true.priority=1
940 940 true.executable=cat
941 941 # hg update -C 1
942 942 $ hg merge -r 2 --config ui.merge=internal:other --config merge-patterns.f=false
943 943 merging f
944 944 merging f failed!
945 945 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
946 946 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
947 947 [1]
948 948 $ aftermerge
949 949 # cat f
950 950 revision 1
951 951 space
952 952 # hg stat
953 953 M f
954 954 ? f.orig
955 955 # hg resolve --list
956 956 U f
957 957
958 958 Premerge
959 959
960 960 ui.merge specifies internal:other but is overruled by --tool=false
961 961
962 962 $ beforemerge
963 963 [merge-tools]
964 964 false.whatever=
965 965 true.priority=1
966 966 true.executable=cat
967 967 # hg update -C 1
968 968 $ hg merge -r 2 --config ui.merge=internal:other --tool=false
969 969 merging f
970 970 merging f failed!
971 971 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
972 972 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
973 973 [1]
974 974 $ aftermerge
975 975 # cat f
976 976 revision 1
977 977 space
978 978 # hg stat
979 979 M f
980 980 ? f.orig
981 981 # hg resolve --list
982 982 U f
983 983
984 984 HGMERGE specifies internal:other but is overruled by --tool=false
985 985
986 986 $ HGMERGE=internal:other ; export HGMERGE
987 987 $ beforemerge
988 988 [merge-tools]
989 989 false.whatever=
990 990 true.priority=1
991 991 true.executable=cat
992 992 # hg update -C 1
993 993 $ hg merge -r 2 --tool=false
994 994 merging f
995 995 merging f failed!
996 996 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
997 997 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
998 998 [1]
999 999 $ aftermerge
1000 1000 # cat f
1001 1001 revision 1
1002 1002 space
1003 1003 # hg stat
1004 1004 M f
1005 1005 ? f.orig
1006 1006 # hg resolve --list
1007 1007 U f
1008 1008
1009 1009 $ unset HGMERGE # make sure HGMERGE doesn't interfere with remaining tests
1010 1010
1011 1011 update is a merge ...
1012 1012
1013 1013 (this also tests that files reverted with '--rev REV' are treated as
1014 1014 "modified", even if none of mode, size and timestamp of them isn't
1015 1015 changed on the filesystem (see also issue4583))
1016 1016
1017 1017 $ cat >> $HGRCPATH <<EOF
1018 1018 > [fakedirstatewritetime]
1019 1019 > # emulate invoking dirstate.write() via repo.status()
1020 1020 > # at 2000-01-01 00:00
1021 1021 > fakenow = 200001010000
1022 1022 > EOF
1023 1023
1024 1024 $ beforemerge
1025 1025 [merge-tools]
1026 1026 false.whatever=
1027 1027 true.priority=1
1028 1028 true.executable=cat
1029 1029 # hg update -C 1
1030 1030 $ hg update -q 0
1031 1031 $ f -s f
1032 1032 f: size=17
1033 1033 $ touch -t 200001010000 f
1034 1034 $ hg debugrebuildstate
1035 1035 $ cat >> $HGRCPATH <<EOF
1036 1036 > [extensions]
1037 1037 > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py
1038 1038 > EOF
1039 1039 $ hg revert -q -r 1 .
1040 1040 $ cat >> $HGRCPATH <<EOF
1041 1041 > [extensions]
1042 1042 > fakedirstatewritetime = !
1043 1043 > EOF
1044 1044 $ f -s f
1045 1045 f: size=17
1046 1046 $ touch -t 200001010000 f
1047 1047 $ hg status f
1048 1048 M f
1049 1049 $ hg update -r 2
1050 1050 merging f
1051 1051 revision 1
1052 1052 space
1053 1053 revision 0
1054 1054 space
1055 1055 revision 2
1056 1056 space
1057 1057 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1058 1058 $ aftermerge
1059 1059 # cat f
1060 1060 revision 1
1061 1061 space
1062 1062 # hg stat
1063 1063 M f
1064 1064 # hg resolve --list
1065 1065 R f
1066 1066
1067 1067 update should also have --tool
1068 1068
1069 1069 $ beforemerge
1070 1070 [merge-tools]
1071 1071 false.whatever=
1072 1072 true.priority=1
1073 1073 true.executable=cat
1074 1074 # hg update -C 1
1075 1075 $ hg update -q 0
1076 1076 $ f -s f
1077 1077 f: size=17
1078 1078 $ touch -t 200001010000 f
1079 1079 $ hg debugrebuildstate
1080 1080 $ cat >> $HGRCPATH <<EOF
1081 1081 > [extensions]
1082 1082 > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py
1083 1083 > EOF
1084 1084 $ hg revert -q -r 1 .
1085 1085 $ cat >> $HGRCPATH <<EOF
1086 1086 > [extensions]
1087 1087 > fakedirstatewritetime = !
1088 1088 > EOF
1089 1089 $ f -s f
1090 1090 f: size=17
1091 1091 $ touch -t 200001010000 f
1092 1092 $ hg status f
1093 1093 M f
1094 1094 $ hg update -r 2 --tool false
1095 1095 merging f
1096 1096 merging f failed!
1097 1097 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
1098 1098 use 'hg resolve' to retry unresolved file merges
1099 1099 [1]
1100 1100 $ aftermerge
1101 1101 # cat f
1102 1102 revision 1
1103 1103 space
1104 1104 # hg stat
1105 1105 M f
1106 1106 ? f.orig
1107 1107 # hg resolve --list
1108 1108 U f
1109 1109
1110 1110 Default is silent simplemerge:
1111 1111
1112 1112 $ beforemerge
1113 1113 [merge-tools]
1114 1114 false.whatever=
1115 1115 true.priority=1
1116 1116 true.executable=cat
1117 1117 # hg update -C 1
1118 1118 $ hg merge -r 3
1119 1119 merging f
1120 1120 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1121 1121 (branch merge, don't forget to commit)
1122 1122 $ aftermerge
1123 1123 # cat f
1124 1124 revision 1
1125 1125 space
1126 1126 revision 3
1127 1127 # hg stat
1128 1128 M f
1129 1129 # hg resolve --list
1130 1130 R f
1131 1131
1132 1132 .premerge=True is same:
1133 1133
1134 1134 $ beforemerge
1135 1135 [merge-tools]
1136 1136 false.whatever=
1137 1137 true.priority=1
1138 1138 true.executable=cat
1139 1139 # hg update -C 1
1140 1140 $ hg merge -r 3 --config merge-tools.true.premerge=True
1141 1141 merging f
1142 1142 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1143 1143 (branch merge, don't forget to commit)
1144 1144 $ aftermerge
1145 1145 # cat f
1146 1146 revision 1
1147 1147 space
1148 1148 revision 3
1149 1149 # hg stat
1150 1150 M f
1151 1151 # hg resolve --list
1152 1152 R f
1153 1153
1154 1154 .premerge=False executes merge-tool:
1155 1155
1156 1156 $ beforemerge
1157 1157 [merge-tools]
1158 1158 false.whatever=
1159 1159 true.priority=1
1160 1160 true.executable=cat
1161 1161 # hg update -C 1
1162 1162 $ hg merge -r 3 --config merge-tools.true.premerge=False
1163 1163 merging f
1164 1164 revision 1
1165 1165 space
1166 1166 revision 0
1167 1167 space
1168 1168 revision 0
1169 1169 space
1170 1170 revision 3
1171 1171 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1172 1172 (branch merge, don't forget to commit)
1173 1173 $ aftermerge
1174 1174 # cat f
1175 1175 revision 1
1176 1176 space
1177 1177 # hg stat
1178 1178 M f
1179 1179 # hg resolve --list
1180 1180 R f
1181 1181
1182 1182 premerge=keep keeps conflict markers in:
1183 1183
1184 1184 $ beforemerge
1185 1185 [merge-tools]
1186 1186 false.whatever=
1187 1187 true.priority=1
1188 1188 true.executable=cat
1189 1189 # hg update -C 1
1190 1190 $ hg merge -r 4 --config merge-tools.true.premerge=keep
1191 1191 merging f
1192 1192 <<<<<<< working copy: ef83787e2614 - test: revision 1
1193 1193 revision 1
1194 1194 space
1195 1195 =======
1196 1196 revision 4
1197 1197 >>>>>>> merge rev: 81448d39c9a0 - test: revision 4
1198 1198 revision 0
1199 1199 space
1200 1200 revision 4
1201 1201 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1202 1202 (branch merge, don't forget to commit)
1203 1203 $ aftermerge
1204 1204 # cat f
1205 1205 <<<<<<< working copy: ef83787e2614 - test: revision 1
1206 1206 revision 1
1207 1207 space
1208 1208 =======
1209 1209 revision 4
1210 1210 >>>>>>> merge rev: 81448d39c9a0 - test: revision 4
1211 1211 # hg stat
1212 1212 M f
1213 1213 # hg resolve --list
1214 1214 R f
1215 1215
1216 1216 premerge=keep-merge3 keeps conflict markers with base content:
1217 1217
1218 1218 $ beforemerge
1219 1219 [merge-tools]
1220 1220 false.whatever=
1221 1221 true.priority=1
1222 1222 true.executable=cat
1223 1223 # hg update -C 1
1224 1224 $ hg merge -r 4 --config merge-tools.true.premerge=keep-merge3
1225 1225 merging f
1226 1226 <<<<<<< working copy: ef83787e2614 - test: revision 1
1227 1227 revision 1
1228 1228 space
1229 1229 ||||||| base: ffd2bda21d6e - test: revision 0
1230 1230 revision 0
1231 1231 space
1232 1232 =======
1233 1233 revision 4
1234 1234 >>>>>>> merge rev: 81448d39c9a0 - test: revision 4
1235 1235 revision 0
1236 1236 space
1237 1237 revision 4
1238 1238 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1239 1239 (branch merge, don't forget to commit)
1240 1240 $ aftermerge
1241 1241 # cat f
1242 1242 <<<<<<< working copy: ef83787e2614 - test: revision 1
1243 1243 revision 1
1244 1244 space
1245 1245 ||||||| base: ffd2bda21d6e - test: revision 0
1246 1246 revision 0
1247 1247 space
1248 1248 =======
1249 1249 revision 4
1250 1250 >>>>>>> merge rev: 81448d39c9a0 - test: revision 4
1251 1251 # hg stat
1252 1252 M f
1253 1253 # hg resolve --list
1254 1254 R f
1255 1255
1256 1256 premerge=keep-mergediff keeps conflict markers with base content:
1257 1257
1258 1258 $ beforemerge
1259 1259 [merge-tools]
1260 1260 false.whatever=
1261 1261 true.priority=1
1262 1262 true.executable=cat
1263 1263 # hg update -C 1
1264 1264 $ hg merge -r 4 --config merge-tools.true.premerge=keep-mergediff
1265 1265 merging f
1266 1266 <<<<<<<
1267 1267 ------- base: ffd2bda21d6e - test: revision 0
1268 1268 +++++++ working copy: ef83787e2614 - test: revision 1
1269 1269 -revision 0
1270 1270 +revision 1
1271 1271 space
1272 1272 ======= merge rev: 81448d39c9a0 - test: revision 4
1273 1273 revision 4
1274 1274 >>>>>>>
1275 1275 revision 0
1276 1276 space
1277 1277 revision 4
1278 1278 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1279 1279 (branch merge, don't forget to commit)
1280 1280 $ aftermerge
1281 1281 # cat f
1282 1282 <<<<<<<
1283 1283 ------- base: ffd2bda21d6e - test: revision 0
1284 1284 +++++++ working copy: ef83787e2614 - test: revision 1
1285 1285 -revision 0
1286 1286 +revision 1
1287 1287 space
1288 1288 ======= merge rev: 81448d39c9a0 - test: revision 4
1289 1289 revision 4
1290 1290 >>>>>>>
1291 1291 # hg stat
1292 1292 M f
1293 1293 # hg resolve --list
1294 1294 R f
1295 1295
1296 1296 premerge=keep respects ui.mergemarkers=basic:
1297 1297
1298 1298 $ beforemerge
1299 1299 [merge-tools]
1300 1300 false.whatever=
1301 1301 true.priority=1
1302 1302 true.executable=cat
1303 1303 # hg update -C 1
1304 1304 $ hg merge -r 4 --config merge-tools.true.premerge=keep --config ui.mergemarkers=basic
1305 1305 merging f
1306 1306 <<<<<<< working copy
1307 1307 revision 1
1308 1308 space
1309 1309 =======
1310 1310 revision 4
1311 1311 >>>>>>> merge rev
1312 1312 revision 0
1313 1313 space
1314 1314 revision 4
1315 1315 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1316 1316 (branch merge, don't forget to commit)
1317 1317 $ aftermerge
1318 1318 # cat f
1319 1319 <<<<<<< working copy
1320 1320 revision 1
1321 1321 space
1322 1322 =======
1323 1323 revision 4
1324 1324 >>>>>>> merge rev
1325 1325 # hg stat
1326 1326 M f
1327 1327 # hg resolve --list
1328 1328 R f
1329 1329
1330 1330 premerge=keep ignores ui.mergemarkers=basic if true.mergemarkers=detailed:
1331 1331
1332 1332 $ beforemerge
1333 1333 [merge-tools]
1334 1334 false.whatever=
1335 1335 true.priority=1
1336 1336 true.executable=cat
1337 1337 # hg update -C 1
1338 1338 $ hg merge -r 4 --config merge-tools.true.premerge=keep \
1339 1339 > --config ui.mergemarkers=basic \
1340 1340 > --config merge-tools.true.mergemarkers=detailed
1341 1341 merging f
1342 1342 <<<<<<< working copy: ef83787e2614 - test: revision 1
1343 1343 revision 1
1344 1344 space
1345 1345 =======
1346 1346 revision 4
1347 1347 >>>>>>> merge rev: 81448d39c9a0 - test: revision 4
1348 1348 revision 0
1349 1349 space
1350 1350 revision 4
1351 1351 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1352 1352 (branch merge, don't forget to commit)
1353 1353 $ aftermerge
1354 1354 # cat f
1355 1355 <<<<<<< working copy: ef83787e2614 - test: revision 1
1356 1356 revision 1
1357 1357 space
1358 1358 =======
1359 1359 revision 4
1360 1360 >>>>>>> merge rev: 81448d39c9a0 - test: revision 4
1361 1361 # hg stat
1362 1362 M f
1363 1363 # hg resolve --list
1364 1364 R f
1365 1365
1366 1366 premerge=keep respects ui.mergemarkertemplate instead of
1367 1367 true.mergemarkertemplate if true.mergemarkers=basic:
1368 1368
1369 1369 $ beforemerge
1370 1370 [merge-tools]
1371 1371 false.whatever=
1372 1372 true.priority=1
1373 1373 true.executable=cat
1374 1374 # hg update -C 1
1375 1375 $ hg merge -r 4 --config merge-tools.true.premerge=keep \
1376 1376 > --config ui.mergemarkertemplate='uitmpl {rev}' \
1377 1377 > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}'
1378 1378 merging f
1379 1379 <<<<<<< working copy: uitmpl 1
1380 1380 revision 1
1381 1381 space
1382 1382 =======
1383 1383 revision 4
1384 1384 >>>>>>> merge rev: uitmpl 4
1385 1385 revision 0
1386 1386 space
1387 1387 revision 4
1388 1388 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1389 1389 (branch merge, don't forget to commit)
1390 1390 $ aftermerge
1391 1391 # cat f
1392 1392 <<<<<<< working copy: uitmpl 1
1393 1393 revision 1
1394 1394 space
1395 1395 =======
1396 1396 revision 4
1397 1397 >>>>>>> merge rev: uitmpl 4
1398 1398 # hg stat
1399 1399 M f
1400 1400 # hg resolve --list
1401 1401 R f
1402 1402
1403 1403 premerge=keep respects true.mergemarkertemplate instead of
1404 1404 true.mergemarkertemplate if true.mergemarkers=detailed:
1405 1405
1406 1406 $ beforemerge
1407 1407 [merge-tools]
1408 1408 false.whatever=
1409 1409 true.priority=1
1410 1410 true.executable=cat
1411 1411 # hg update -C 1
1412 1412 $ hg merge -r 4 --config merge-tools.true.premerge=keep \
1413 1413 > --config ui.mergemarkertemplate='uitmpl {rev}' \
1414 1414 > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}' \
1415 1415 > --config merge-tools.true.mergemarkers=detailed
1416 1416 merging f
1417 1417 <<<<<<< working copy: tooltmpl ef83787e2614
1418 1418 revision 1
1419 1419 space
1420 1420 =======
1421 1421 revision 4
1422 1422 >>>>>>> merge rev: tooltmpl 81448d39c9a0
1423 1423 revision 0
1424 1424 space
1425 1425 revision 4
1426 1426 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1427 1427 (branch merge, don't forget to commit)
1428 1428 $ aftermerge
1429 1429 # cat f
1430 1430 <<<<<<< working copy: tooltmpl ef83787e2614
1431 1431 revision 1
1432 1432 space
1433 1433 =======
1434 1434 revision 4
1435 1435 >>>>>>> merge rev: tooltmpl 81448d39c9a0
1436 1436 # hg stat
1437 1437 M f
1438 1438 # hg resolve --list
1439 1439 R f
1440 1440
1441 1441 Tool execution
1442 1442
1443 1443 set tools.args explicit to include $base $local $other $output:
1444 1444
1445 1445 $ beforemerge
1446 1446 [merge-tools]
1447 1447 false.whatever=
1448 1448 true.priority=1
1449 1449 true.executable=cat
1450 1450 # hg update -C 1
1451 1451 $ hg merge -r 2 --config merge-tools.true.executable=head --config merge-tools.true.args='$base $local $other $output' \
1452 1452 > | sed 's,==> .* <==,==> ... <==,g'
1453 1453 merging f
1454 1454 ==> ... <==
1455 1455 revision 0
1456 1456 space
1457 1457
1458 1458 ==> ... <==
1459 1459 revision 1
1460 1460 space
1461 1461
1462 1462 ==> ... <==
1463 1463 revision 2
1464 1464 space
1465 1465
1466 1466 ==> ... <==
1467 1467 revision 1
1468 1468 space
1469 1469 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1470 1470 (branch merge, don't forget to commit)
1471 1471 $ aftermerge
1472 1472 # cat f
1473 1473 revision 1
1474 1474 space
1475 1475 # hg stat
1476 1476 M f
1477 1477 # hg resolve --list
1478 1478 R f
1479 1479
1480 1480 Merge with "echo mergeresult > $local":
1481 1481
1482 1482 $ beforemerge
1483 1483 [merge-tools]
1484 1484 false.whatever=
1485 1485 true.priority=1
1486 1486 true.executable=cat
1487 1487 # hg update -C 1
1488 1488 $ hg merge -r 2 --config merge-tools.true.executable=echo --config merge-tools.true.args='mergeresult > $local'
1489 1489 merging f
1490 1490 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1491 1491 (branch merge, don't forget to commit)
1492 1492 $ aftermerge
1493 1493 # cat f
1494 1494 mergeresult
1495 1495 # hg stat
1496 1496 M f
1497 1497 # hg resolve --list
1498 1498 R f
1499 1499
1500 1500 - and $local is the file f:
1501 1501
1502 1502 $ beforemerge
1503 1503 [merge-tools]
1504 1504 false.whatever=
1505 1505 true.priority=1
1506 1506 true.executable=cat
1507 1507 # hg update -C 1
1508 1508 $ hg merge -r 2 --config merge-tools.true.executable=echo --config merge-tools.true.args='mergeresult > f'
1509 1509 merging f
1510 1510 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1511 1511 (branch merge, don't forget to commit)
1512 1512 $ aftermerge
1513 1513 # cat f
1514 1514 mergeresult
1515 1515 # hg stat
1516 1516 M f
1517 1517 # hg resolve --list
1518 1518 R f
1519 1519
1520 1520 Merge with "echo mergeresult > $output" - the variable is a bit magic:
1521 1521
1522 1522 $ beforemerge
1523 1523 [merge-tools]
1524 1524 false.whatever=
1525 1525 true.priority=1
1526 1526 true.executable=cat
1527 1527 # hg update -C 1
1528 1528 $ hg merge -r 2 --config merge-tools.true.executable=echo --config merge-tools.true.args='mergeresult > $output'
1529 1529 merging f
1530 1530 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1531 1531 (branch merge, don't forget to commit)
1532 1532 $ aftermerge
1533 1533 # cat f
1534 1534 mergeresult
1535 1535 # hg stat
1536 1536 M f
1537 1537 # hg resolve --list
1538 1538 R f
1539 1539
1540 1540 Merge using tool with a path that must be quoted:
1541 1541
1542 1542 $ beforemerge
1543 1543 [merge-tools]
1544 1544 false.whatever=
1545 1545 true.priority=1
1546 1546 true.executable=cat
1547 1547 # hg update -C 1
1548 1548 $ cat <<EOF > 'my merge tool'
1549 1549 > cat "\$1" "\$2" "\$3" > "\$4"
1550 1550 > EOF
1551 1551 $ hg --config merge-tools.true.executable='sh' \
1552 1552 > --config merge-tools.true.args='"./my merge tool" $base $local $other $output' \
1553 1553 > merge -r 2
1554 1554 merging f
1555 1555 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1556 1556 (branch merge, don't forget to commit)
1557 1557 $ rm -f 'my merge tool'
1558 1558 $ aftermerge
1559 1559 # cat f
1560 1560 revision 0
1561 1561 space
1562 1562 revision 1
1563 1563 space
1564 1564 revision 2
1565 1565 space
1566 1566 # hg stat
1567 1567 M f
1568 1568 # hg resolve --list
1569 1569 R f
1570 1570
1571 1571 Merge using a tool that supports labellocal, labelother, and labelbase, checking
1572 1572 that they're quoted properly as well. This is using the default 'basic'
1573 1573 mergemarkers even though ui.mergemarkers is 'detailed', so it's ignoring both
1574 1574 mergemarkertemplate settings:
1575 1575
1576 1576 $ beforemerge
1577 1577 [merge-tools]
1578 1578 false.whatever=
1579 1579 true.priority=1
1580 1580 true.executable=cat
1581 1581 # hg update -C 1
1582 1582 $ cat <<EOF > printargs_merge_tool
1583 1583 > while test \$# -gt 0; do echo arg: \""\$1"\"; shift; done
1584 1584 > EOF
1585 1585 $ hg --config merge-tools.true.executable='sh' \
1586 1586 > --config merge-tools.true.args='./printargs_merge_tool ll:$labellocal lo: $labelother lb:$labelbase": "$base' \
1587 1587 > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}' \
1588 1588 > --config ui.mergemarkertemplate='uitmpl {rev}' \
1589 1589 > --config ui.mergemarkers=detailed \
1590 1590 > merge -r 2
1591 1591 merging f
1592 1592 arg: "ll:working copy"
1593 1593 arg: "lo:"
1594 1594 arg: "merge rev"
1595 1595 arg: "lb:base: */f~base.*" (glob)
1596 1596 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1597 1597 (branch merge, don't forget to commit)
1598 1598 $ rm -f 'printargs_merge_tool'
1599 1599
1600 1600 Same test with experimental.mergetempdirprefix set:
1601 1601
1602 1602 $ beforemerge
1603 1603 [merge-tools]
1604 1604 false.whatever=
1605 1605 true.priority=1
1606 1606 true.executable=cat
1607 1607 # hg update -C 1
1608 1608 $ cat <<EOF > printargs_merge_tool
1609 1609 > while test \$# -gt 0; do echo arg: \""\$1"\"; shift; done
1610 1610 > EOF
1611 1611 $ hg --config experimental.mergetempdirprefix=$TESTTMP/hgmerge. \
1612 1612 > --config merge-tools.true.executable='sh' \
1613 1613 > --config merge-tools.true.args='./printargs_merge_tool ll:$labellocal lo: $labelother lb:$labelbase": "$base' \
1614 1614 > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}' \
1615 1615 > --config ui.mergemarkertemplate='uitmpl {rev}' \
1616 1616 > --config ui.mergemarkers=detailed \
1617 1617 > merge -r 2
1618 1618 merging f
1619 1619 arg: "ll:working copy"
1620 1620 arg: "lo:"
1621 1621 arg: "merge rev"
1622 1622 arg: "lb:base: */hgmerge.*/f~base" (glob)
1623 1623 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1624 1624 (branch merge, don't forget to commit)
1625 1625 $ rm -f 'printargs_merge_tool'
1626 1626
1627 1627 Merge using a tool that supports labellocal, labelother, and labelbase, checking
1628 1628 that they're quoted properly as well. This is using 'detailed' mergemarkers,
1629 1629 even though ui.mergemarkers is 'basic', and using the tool's
1630 1630 mergemarkertemplate:
1631 1631
1632 1632 $ beforemerge
1633 1633 [merge-tools]
1634 1634 false.whatever=
1635 1635 true.priority=1
1636 1636 true.executable=cat
1637 1637 # hg update -C 1
1638 1638 $ cat <<EOF > printargs_merge_tool
1639 1639 > while test \$# -gt 0; do echo arg: \""\$1"\"; shift; done
1640 1640 > EOF
1641 1641 $ hg --config merge-tools.true.executable='sh' \
1642 1642 > --config merge-tools.true.args='./printargs_merge_tool ll:$labellocal lo: $labelother lb:$labelbase": "$base' \
1643 1643 > --config merge-tools.true.mergemarkers=detailed \
1644 1644 > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}' \
1645 1645 > --config ui.mergemarkertemplate='uitmpl {rev}' \
1646 1646 > --config ui.mergemarkers=basic \
1647 1647 > merge -r 2
1648 1648 merging f
1649 1649 arg: "ll:working copy: tooltmpl ef83787e2614"
1650 1650 arg: "lo:"
1651 arg: "merge rev: tooltmpl 0185f4e0cf02"
1651 arg: "merge rev: tooltmpl 0185f4e0cf02"
1652 1652 arg: "lb:base: */f~base.*" (glob)
1653 1653 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1654 1654 (branch merge, don't forget to commit)
1655 1655 $ rm -f 'printargs_merge_tool'
1656 1656
1657 1657 The merge tool still gets labellocal and labelother as 'basic' even when
1658 1658 premerge=keep is used and has 'detailed' markers:
1659 1659
1660 1660 $ beforemerge
1661 1661 [merge-tools]
1662 1662 false.whatever=
1663 1663 true.priority=1
1664 1664 true.executable=cat
1665 1665 # hg update -C 1
1666 1666 $ cat <<EOF > mytool
1667 1667 > echo labellocal: \""\$1"\"
1668 1668 > echo labelother: \""\$2"\"
1669 1669 > echo "output (arg)": \""\$3"\"
1670 1670 > echo "output (contents)":
1671 1671 > cat "\$3"
1672 1672 > EOF
1673 1673 $ hg --config merge-tools.true.executable='sh' \
1674 1674 > --config merge-tools.true.args='mytool $labellocal $labelother $output' \
1675 1675 > --config merge-tools.true.premerge=keep \
1676 1676 > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}' \
1677 1677 > --config ui.mergemarkertemplate='uitmpl {rev}' \
1678 1678 > --config ui.mergemarkers=detailed \
1679 1679 > merge -r 2
1680 1680 merging f
1681 1681 labellocal: "working copy"
1682 1682 labelother: "merge rev"
1683 1683 output (arg): "$TESTTMP/repo/f"
1684 1684 output (contents):
1685 1685 <<<<<<< working copy: uitmpl 1
1686 1686 revision 1
1687 1687 =======
1688 1688 revision 2
1689 1689 >>>>>>> merge rev: uitmpl 2
1690 1690 space
1691 1691 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1692 1692 (branch merge, don't forget to commit)
1693 1693 $ rm -f 'mytool'
1694 1694
1695 1695 premerge=keep uses the *tool's* mergemarkertemplate if tool's
1696 1696 mergemarkers=detailed; labellocal and labelother also use the tool's template
1697 1697
1698 1698 $ beforemerge
1699 1699 [merge-tools]
1700 1700 false.whatever=
1701 1701 true.priority=1
1702 1702 true.executable=cat
1703 1703 # hg update -C 1
1704 1704 $ cat <<EOF > mytool
1705 1705 > echo labellocal: \""\$1"\"
1706 1706 > echo labelother: \""\$2"\"
1707 1707 > echo "output (arg)": \""\$3"\"
1708 1708 > echo "output (contents)":
1709 1709 > cat "\$3"
1710 1710 > EOF
1711 1711 $ hg --config merge-tools.true.executable='sh' \
1712 1712 > --config merge-tools.true.args='mytool $labellocal $labelother $output' \
1713 1713 > --config merge-tools.true.premerge=keep \
1714 1714 > --config merge-tools.true.mergemarkers=detailed \
1715 1715 > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}' \
1716 1716 > --config ui.mergemarkertemplate='uitmpl {rev}' \
1717 1717 > --config ui.mergemarkers=detailed \
1718 1718 > merge -r 2
1719 1719 merging f
1720 1720 labellocal: "working copy: tooltmpl ef83787e2614"
1721 labelother: "merge rev: tooltmpl 0185f4e0cf02"
1721 labelother: "merge rev: tooltmpl 0185f4e0cf02"
1722 1722 output (arg): "$TESTTMP/repo/f"
1723 1723 output (contents):
1724 1724 <<<<<<< working copy: tooltmpl ef83787e2614
1725 1725 revision 1
1726 1726 =======
1727 1727 revision 2
1728 1728 >>>>>>> merge rev: tooltmpl 0185f4e0cf02
1729 1729 space
1730 1730 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1731 1731 (branch merge, don't forget to commit)
1732 1732 $ rm -f 'mytool'
1733 1733
1734 1734 Issue3581: Merging a filename that needs to be quoted
1735 1735 (This test doesn't work on Windows filesystems even on Linux, so check
1736 1736 for Unix-like permission)
1737 1737
1738 1738 #if unix-permissions
1739 1739 $ beforemerge
1740 1740 [merge-tools]
1741 1741 false.whatever=
1742 1742 true.priority=1
1743 1743 true.executable=cat
1744 1744 # hg update -C 1
1745 1745 $ echo "revision 5" > '"; exit 1; echo "'
1746 1746 $ hg commit -Am "revision 5"
1747 1747 adding "; exit 1; echo "
1748 1748 warning: filename contains '"', which is reserved on Windows: '"; exit 1; echo "'
1749 1749 $ hg update -C 1 > /dev/null
1750 1750 $ echo "revision 6" > '"; exit 1; echo "'
1751 1751 $ hg commit -Am "revision 6"
1752 1752 adding "; exit 1; echo "
1753 1753 warning: filename contains '"', which is reserved on Windows: '"; exit 1; echo "'
1754 1754 created new head
1755 1755 $ hg merge --config merge-tools.true.executable="true" -r 5
1756 1756 merging "; exit 1; echo "
1757 1757 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1758 1758 (branch merge, don't forget to commit)
1759 1759 $ hg update -C 1 > /dev/null
1760 1760
1761 1761 #else
1762 1762
1763 1763 Match the non-portable filename commits above for test stability
1764 1764
1765 1765 $ hg import --bypass -q - << EOF
1766 1766 > # HG changeset patch
1767 1767 > revision 5
1768 1768 >
1769 1769 > diff --git a/"; exit 1; echo " b/"; exit 1; echo "
1770 1770 > new file mode 100644
1771 1771 > --- /dev/null
1772 1772 > +++ b/"; exit 1; echo "
1773 1773 > @@ -0,0 +1,1 @@
1774 1774 > +revision 5
1775 1775 > EOF
1776 1776
1777 1777 $ hg import --bypass -q - << EOF
1778 1778 > # HG changeset patch
1779 1779 > revision 6
1780 1780 >
1781 1781 > diff --git a/"; exit 1; echo " b/"; exit 1; echo "
1782 1782 > new file mode 100644
1783 1783 > --- /dev/null
1784 1784 > +++ b/"; exit 1; echo "
1785 1785 > @@ -0,0 +1,1 @@
1786 1786 > +revision 6
1787 1787 > EOF
1788 1788
1789 1789 #endif
1790 1790
1791 1791 Merge post-processing
1792 1792
1793 1793 cat is a bad merge-tool and doesn't change:
1794 1794
1795 1795 $ beforemerge
1796 1796 [merge-tools]
1797 1797 false.whatever=
1798 1798 true.priority=1
1799 1799 true.executable=cat
1800 1800 # hg update -C 1
1801 1801 $ hg merge -y -r 2 --config merge-tools.true.checkchanged=1
1802 1802 merging f
1803 1803 revision 1
1804 1804 space
1805 1805 revision 0
1806 1806 space
1807 1807 revision 2
1808 1808 space
1809 1809 output file f appears unchanged
1810 1810 was merge successful (yn)? n
1811 1811 merging f failed!
1812 1812 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
1813 1813 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
1814 1814 [1]
1815 1815 $ aftermerge
1816 1816 # cat f
1817 1817 revision 1
1818 1818 space
1819 1819 # hg stat
1820 1820 M f
1821 1821 ? f.orig
1822 1822 # hg resolve --list
1823 1823 U f
1824 1824
1825 1825 missingbinary is a merge-tool that doesn't exist:
1826 1826
1827 1827 $ echo "missingbinary.executable=doesnotexist" >> .hg/hgrc
1828 1828 $ beforemerge
1829 1829 [merge-tools]
1830 1830 false.whatever=
1831 1831 true.priority=1
1832 1832 true.executable=cat
1833 1833 missingbinary.executable=doesnotexist
1834 1834 # hg update -C 1
1835 1835 $ hg merge -y -r 2 --config ui.merge=missingbinary
1836 1836 couldn't find merge tool missingbinary (for pattern f)
1837 1837 merging f
1838 1838 revision 1
1839 1839 space
1840 1840 revision 0
1841 1841 space
1842 1842 revision 2
1843 1843 space
1844 1844 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1845 1845 (branch merge, don't forget to commit)
1846 1846
1847 1847 $ hg update -q -C 1
1848 1848 $ rm f
1849 1849
1850 1850 internal merge cannot handle symlinks and shouldn't try:
1851 1851
1852 1852 #if symlink
1853 1853
1854 1854 $ ln -s symlink f
1855 1855 $ hg commit -qm 'f is symlink'
1856 1856
1857 1857 #else
1858 1858
1859 1859 $ hg import --bypass -q - << EOF
1860 1860 > # HG changeset patch
1861 1861 > f is symlink
1862 1862 >
1863 1863 > diff --git a/f b/f
1864 1864 > old mode 100644
1865 1865 > new mode 120000
1866 1866 > --- a/f
1867 1867 > +++ b/f
1868 1868 > @@ -1,2 +1,1 @@
1869 1869 > -revision 1
1870 1870 > -space
1871 1871 > +symlink
1872 1872 > \ No newline at end of file
1873 1873 > EOF
1874 1874
1875 1875 Resolve 'other [destination] changed f which local [working copy] deleted' prompt
1876 1876 $ hg up -q -C --config ui.interactive=True << EOF
1877 1877 > c
1878 1878 > EOF
1879 1879
1880 1880 #endif
1881 1881
1882 1882 $ hg merge -r 2 --tool internal:merge
1883 1883 merging f
1884 1884 warning: internal :merge cannot merge symlinks for f
1885 1885 warning: conflicts while merging f! (edit, then use 'hg resolve --mark')
1886 1886 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
1887 1887 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
1888 1888 [1]
1889 1889
1890 1890 Verify naming of temporary files and that extension is preserved:
1891 1891
1892 1892 $ hg update -q -C 1
1893 1893 $ hg mv f f.txt
1894 1894 $ hg ci -qm "f.txt"
1895 1895 $ hg update -q -C 2
1896 1896 $ hg merge -y -r tip --tool echo --config merge-tools.echo.args='$base $local $other $output'
1897 1897 merging f and f.txt to f.txt
1898 1898 */f~base.* */f~local.*.txt */f~other.*.txt $TESTTMP/repo/f.txt (glob)
1899 1899 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1900 1900 (branch merge, don't forget to commit)
1901 1901
1902 1902 Verify naming of temporary files and that extension is preserved
1903 1903 (experimental.mergetempdirprefix version):
1904 1904
1905 1905 $ hg update -q -C 1
1906 1906 $ hg mv f f.txt
1907 1907 $ hg ci -qm "f.txt"
1908 1908 warning: commit already existed in the repository!
1909 1909 $ hg update -q -C 2
1910 1910 $ hg merge -y -r tip --tool echo \
1911 1911 > --config merge-tools.echo.args='$base $local $other $output' \
1912 1912 > --config experimental.mergetempdirprefix=$TESTTMP/hgmerge.
1913 1913 merging f and f.txt to f.txt
1914 1914 $TESTTMP/hgmerge.*/f~base $TESTTMP/hgmerge.*/f~local.txt $TESTTMP/hgmerge.*/f~other.txt $TESTTMP/repo/f.txt (glob)
1915 1915 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1916 1916 (branch merge, don't forget to commit)
1917 1917
1918 1918 Binary files capability checking
1919 1919
1920 1920 $ hg update -q -C 0
1921 1921 $ "$PYTHON" <<EOF
1922 1922 > with open('b', 'wb') as fp:
1923 1923 > fp.write(b'\x00\x01\x02\x03')
1924 1924 > EOF
1925 1925 $ hg add b
1926 1926 $ hg commit -qm "add binary file (#1)"
1927 1927
1928 1928 $ hg update -q -C 0
1929 1929 $ "$PYTHON" <<EOF
1930 1930 > with open('b', 'wb') as fp:
1931 1931 > fp.write(b'\x03\x02\x01\x00')
1932 1932 > EOF
1933 1933 $ hg add b
1934 1934 $ hg commit -qm "add binary file (#2)"
1935 1935
1936 1936 By default, binary files capability of internal merge tools is not
1937 1937 checked strictly.
1938 1938
1939 1939 (for merge-patterns, chosen unintentionally)
1940 1940
1941 1941 $ hg merge 9 \
1942 1942 > --config merge-patterns.b=:merge-other \
1943 1943 > --config merge-patterns.re:[a-z]=:other
1944 1944 warning: check merge-patterns configurations, if ':merge-other' for binary file 'b' is unintentional
1945 1945 (see 'hg help merge-tools' for binary files capability)
1946 1946 merging b
1947 1947 warning: b looks like a binary file.
1948 1948 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
1949 1949 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
1950 1950 [1]
1951 1951 (Testing that commands.merge.require-rev doesn't break --abort)
1952 1952 $ hg merge --abort -q
1953 1953
1954 1954 (for ui.merge, ignored unintentionally)
1955 1955
1956 1956 $ hg merge 9 \
1957 1957 > --config merge-tools.:other.binary=true \
1958 1958 > --config ui.merge=:other
1959 1959 tool :other (for pattern b) can't handle binary
1960 1960 tool true can't handle binary
1961 1961 tool :other can't handle binary
1962 1962 tool false can't handle binary
1963 1963 no tool found to merge b
1964 1964 file 'b' needs to be resolved.
1965 1965 You can keep (l)ocal [working copy], take (o)ther [merge rev], or leave (u)nresolved.
1966 1966 What do you want to do? u
1967 1967 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
1968 1968 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
1969 1969 [1]
1970 1970 $ hg merge --abort -q
1971 1971
1972 1972 With merge.strict-capability-check=true, binary files capability of
1973 1973 internal merge tools is checked strictly.
1974 1974
1975 1975 $ f --hexdump b
1976 1976 b:
1977 1977 0000: 03 02 01 00 |....|
1978 1978
1979 1979 (for merge-patterns)
1980 1980
1981 1981 $ hg merge 9 --config merge.strict-capability-check=true \
1982 1982 > --config merge-tools.:merge-other.binary=true \
1983 1983 > --config merge-patterns.b=:merge-other \
1984 1984 > --config merge-patterns.re:[a-z]=:other
1985 1985 tool :merge-other (for pattern b) can't handle binary
1986 1986 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1987 1987 (branch merge, don't forget to commit)
1988 1988 $ f --hexdump b
1989 1989 b:
1990 1990 0000: 00 01 02 03 |....|
1991 1991 $ hg merge --abort -q
1992 1992
1993 1993 (for ui.merge)
1994 1994
1995 1995 $ hg merge 9 --config merge.strict-capability-check=true \
1996 1996 > --config ui.merge=:other
1997 1997 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1998 1998 (branch merge, don't forget to commit)
1999 1999 $ f --hexdump b
2000 2000 b:
2001 2001 0000: 00 01 02 03 |....|
2002 2002 $ hg merge --abort -q
2003 2003
2004 2004 Check that the extra information is printed correctly
2005 2005
2006 2006 $ hg merge 9 \
2007 2007 > --config merge-tools.testecho.executable='echo' \
2008 2008 > --config merge-tools.testecho.args='merge runs here ...' \
2009 2009 > --config merge-tools.testecho.binary=True \
2010 2010 > --config ui.merge=testecho \
2011 2011 > --config ui.pre-merge-tool-output-template='\n{label("extmerge.running_merge_tool", "Running merge tool for {path} ({toolpath}):")}\n{separate("\n", extmerge_section(local), extmerge_section(base), extmerge_section(other))}\n' \
2012 2012 > --config 'templatealias.extmerge_section(sect)="- {pad("{sect.name} ({sect.label})", 20, left=True)}: {revset(sect.node)%"{rev}:{shortest(node,8)} {desc|firstline} {separate(" ", tags, bookmarks, branch)}"}"'
2013 2013 merging b
2014 2014
2015 2015 Running merge tool for b ("*/bin/echo.exe"): (glob) (windows !)
2016 2016 Running merge tool for b (*/bin/echo): (glob) (no-windows !)
2017 2017 - local (working copy): 10:2d1f533d add binary file (#2) tip default
2018 2018 - base (base): -1:00000000 default
2019 2019 - other (merge rev): 9:1e7ad7d7 add binary file (#1) default
2020 2020 merge runs here ...
2021 2021 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
2022 2022 (branch merge, don't forget to commit)
2023 2023
2024 2024 Check that debugpicktool examines which merge tool is chosen for
2025 2025 specified file as expected
2026 2026
2027 2027 $ beforemerge
2028 2028 [merge-tools]
2029 2029 false.whatever=
2030 2030 true.priority=1
2031 2031 true.executable=cat
2032 2032 missingbinary.executable=doesnotexist
2033 2033 # hg update -C 1
2034 2034
2035 2035 (default behavior: checking files in the working parent context)
2036 2036
2037 2037 $ hg manifest
2038 2038 f
2039 2039 $ hg debugpickmergetool
2040 2040 f = true
2041 2041
2042 2042 (-X/-I and file patterns limmit examination targets)
2043 2043
2044 2044 $ hg debugpickmergetool -X f
2045 2045 $ hg debugpickmergetool unknown
2046 2046 unknown: no such file in rev ef83787e2614
2047 2047
2048 2048 (--changedelete emulates merging change and delete)
2049 2049
2050 2050 $ hg debugpickmergetool --changedelete
2051 2051 f = :prompt
2052 2052
2053 2053 (-r REV causes checking files in specified revision)
2054 2054
2055 2055 $ hg manifest -r 8
2056 2056 f.txt
2057 2057 $ hg debugpickmergetool -r 8
2058 2058 f.txt = true
2059 2059
2060 2060 #if symlink
2061 2061
2062 2062 (symlink causes chosing :prompt)
2063 2063
2064 2064 $ hg debugpickmergetool -r 6d00b3726f6e
2065 2065 f = :prompt
2066 2066
2067 2067 (by default, it is assumed that no internal merge tools has symlinks
2068 2068 capability)
2069 2069
2070 2070 $ hg debugpickmergetool \
2071 2071 > -r 6d00b3726f6e \
2072 2072 > --config merge-tools.:merge-other.symlink=true \
2073 2073 > --config merge-patterns.f=:merge-other \
2074 2074 > --config merge-patterns.re:[f]=:merge-local \
2075 2075 > --config merge-patterns.re:[a-z]=:other
2076 2076 f = :prompt
2077 2077
2078 2078 $ hg debugpickmergetool \
2079 2079 > -r 6d00b3726f6e \
2080 2080 > --config merge-tools.:other.symlink=true \
2081 2081 > --config ui.merge=:other
2082 2082 f = :prompt
2083 2083
2084 2084 (with strict-capability-check=true, actual symlink capabilities are
2085 2085 checked striclty)
2086 2086
2087 2087 $ hg debugpickmergetool --config merge.strict-capability-check=true \
2088 2088 > -r 6d00b3726f6e \
2089 2089 > --config merge-tools.:merge-other.symlink=true \
2090 2090 > --config merge-patterns.f=:merge-other \
2091 2091 > --config merge-patterns.re:[f]=:merge-local \
2092 2092 > --config merge-patterns.re:[a-z]=:other
2093 2093 f = :other
2094 2094
2095 2095 $ hg debugpickmergetool --config merge.strict-capability-check=true \
2096 2096 > -r 6d00b3726f6e \
2097 2097 > --config ui.merge=:other
2098 2098 f = :other
2099 2099
2100 2100 $ hg debugpickmergetool --config merge.strict-capability-check=true \
2101 2101 > -r 6d00b3726f6e \
2102 2102 > --config merge-tools.:merge-other.symlink=true \
2103 2103 > --config ui.merge=:merge-other
2104 2104 f = :prompt
2105 2105
2106 2106 #endif
2107 2107
2108 2108 (--verbose shows some configurations)
2109 2109
2110 2110 $ hg debugpickmergetool --tool foobar -v
2111 2111 with --tool 'foobar'
2112 2112 f = foobar
2113 2113
2114 2114 $ HGMERGE=false hg debugpickmergetool -v
2115 2115 with HGMERGE='false'
2116 2116 f = false
2117 2117
2118 2118 $ hg debugpickmergetool --config ui.merge=false -v
2119 2119 with ui.merge='false'
2120 2120 f = false
2121 2121
2122 2122 (--debug shows errors detected intermediately)
2123 2123
2124 2124 $ hg debugpickmergetool --config merge-patterns.f=true --config merge-tools.true.executable=nonexistentmergetool --debug f
2125 2125 couldn't find merge tool true (for pattern f)
2126 2126 couldn't find merge tool true
2127 2127 f = false
2128 2128
2129 2129 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now