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