##// END OF EJS Templates
filemerge: support specifying a python function to custom merge-tools...
hindlemail -
r38052:242eb513 default
parent child Browse files
Show More
@@ -1,928 +1,975 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 import tempfile
15 15
16 16 from .i18n import _
17 17 from .node import nullid, short
18 18
19 19 from . import (
20 20 encoding,
21 21 error,
22 22 formatter,
23 23 match,
24 24 pycompat,
25 25 registrar,
26 26 scmutil,
27 27 simplemerge,
28 28 tagmerge,
29 29 templatekw,
30 30 templater,
31 31 util,
32 32 )
33 33
34 34 from .utils import (
35 35 procutil,
36 36 stringutil,
37 37 )
38 38
39 39 def _toolstr(ui, tool, part, *args):
40 40 return ui.config("merge-tools", tool + "." + part, *args)
41 41
42 42 def _toolbool(ui, tool, part,*args):
43 43 return ui.configbool("merge-tools", tool + "." + part, *args)
44 44
45 45 def _toollist(ui, tool, part):
46 46 return ui.configlist("merge-tools", tool + "." + part)
47 47
48 48 internals = {}
49 49 # Merge tools to document.
50 50 internalsdoc = {}
51 51
52 52 internaltool = registrar.internalmerge()
53 53
54 54 # internal tool merge types
55 55 nomerge = internaltool.nomerge
56 56 mergeonly = internaltool.mergeonly # just the full merge, no premerge
57 57 fullmerge = internaltool.fullmerge # both premerge and merge
58 58
59 59 _localchangedotherdeletedmsg = _(
60 60 "local%(l)s changed %(fd)s which other%(o)s deleted\n"
61 61 "use (c)hanged version, (d)elete, or leave (u)nresolved?"
62 62 "$$ &Changed $$ &Delete $$ &Unresolved")
63 63
64 64 _otherchangedlocaldeletedmsg = _(
65 65 "other%(o)s changed %(fd)s which local%(l)s deleted\n"
66 66 "use (c)hanged version, leave (d)eleted, or "
67 67 "leave (u)nresolved?"
68 68 "$$ &Changed $$ &Deleted $$ &Unresolved")
69 69
70 70 class absentfilectx(object):
71 71 """Represents a file that's ostensibly in a context but is actually not
72 72 present in it.
73 73
74 74 This is here because it's very specific to the filemerge code for now --
75 75 other code is likely going to break with the values this returns."""
76 76 def __init__(self, ctx, f):
77 77 self._ctx = ctx
78 78 self._f = f
79 79
80 80 def path(self):
81 81 return self._f
82 82
83 83 def size(self):
84 84 return None
85 85
86 86 def data(self):
87 87 return None
88 88
89 89 def filenode(self):
90 90 return nullid
91 91
92 92 _customcmp = True
93 93 def cmp(self, fctx):
94 94 """compare with other file context
95 95
96 96 returns True if different from fctx.
97 97 """
98 98 return not (fctx.isabsent() and
99 99 fctx.ctx() == self.ctx() and
100 100 fctx.path() == self.path())
101 101
102 102 def flags(self):
103 103 return ''
104 104
105 105 def changectx(self):
106 106 return self._ctx
107 107
108 108 def isbinary(self):
109 109 return False
110 110
111 111 def isabsent(self):
112 112 return True
113 113
114 114 def _findtool(ui, tool):
115 115 if tool in internals:
116 116 return tool
117 cmd = _toolstr(ui, tool, "executable", tool)
118 if cmd.startswith('python:'):
119 return cmd
117 120 return findexternaltool(ui, tool)
118 121
122 def _quotetoolpath(cmd):
123 if cmd.startswith('python:'):
124 return cmd
125 return procutil.shellquote(cmd)
126
119 127 def findexternaltool(ui, tool):
120 128 for kn in ("regkey", "regkeyalt"):
121 129 k = _toolstr(ui, tool, kn)
122 130 if not k:
123 131 continue
124 132 p = util.lookupreg(k, _toolstr(ui, tool, "regname"))
125 133 if p:
126 134 p = procutil.findexe(p + _toolstr(ui, tool, "regappend", ""))
127 135 if p:
128 136 return p
129 137 exe = _toolstr(ui, tool, "executable", tool)
130 138 return procutil.findexe(util.expandpath(exe))
131 139
132 140 def _picktool(repo, ui, path, binary, symlink, changedelete):
133 141 def supportscd(tool):
134 142 return tool in internals and internals[tool].mergetype == nomerge
135 143
136 144 def check(tool, pat, symlink, binary, changedelete):
137 145 tmsg = tool
138 146 if pat:
139 147 tmsg = _("%s (for pattern %s)") % (tool, pat)
140 148 if not _findtool(ui, tool):
141 149 if pat: # explicitly requested tool deserves a warning
142 150 ui.warn(_("couldn't find merge tool %s\n") % tmsg)
143 151 else: # configured but non-existing tools are more silent
144 152 ui.note(_("couldn't find merge tool %s\n") % tmsg)
145 153 elif symlink and not _toolbool(ui, tool, "symlink"):
146 154 ui.warn(_("tool %s can't handle symlinks\n") % tmsg)
147 155 elif binary and not _toolbool(ui, tool, "binary"):
148 156 ui.warn(_("tool %s can't handle binary\n") % tmsg)
149 157 elif changedelete and not supportscd(tool):
150 158 # the nomerge tools are the only tools that support change/delete
151 159 # conflicts
152 160 pass
153 161 elif not procutil.gui() and _toolbool(ui, tool, "gui"):
154 162 ui.warn(_("tool %s requires a GUI\n") % tmsg)
155 163 else:
156 164 return True
157 165 return False
158 166
159 167 # internal config: ui.forcemerge
160 168 # forcemerge comes from command line arguments, highest priority
161 169 force = ui.config('ui', 'forcemerge')
162 170 if force:
163 171 toolpath = _findtool(ui, force)
164 172 if changedelete and not supportscd(toolpath):
165 173 return ":prompt", None
166 174 else:
167 175 if toolpath:
168 return (force, procutil.shellquote(toolpath))
176 return (force, _quotetoolpath(toolpath))
169 177 else:
170 178 # mimic HGMERGE if given tool not found
171 179 return (force, force)
172 180
173 181 # HGMERGE takes next precedence
174 182 hgmerge = encoding.environ.get("HGMERGE")
175 183 if hgmerge:
176 184 if changedelete and not supportscd(hgmerge):
177 185 return ":prompt", None
178 186 else:
179 187 return (hgmerge, hgmerge)
180 188
181 189 # then patterns
182 190 for pat, tool in ui.configitems("merge-patterns"):
183 191 mf = match.match(repo.root, '', [pat])
184 192 if mf(path) and check(tool, pat, symlink, False, changedelete):
185 193 toolpath = _findtool(ui, tool)
186 return (tool, procutil.shellquote(toolpath))
194 return (tool, _quotetoolpath(toolpath))
187 195
188 196 # then merge tools
189 197 tools = {}
190 198 disabled = set()
191 199 for k, v in ui.configitems("merge-tools"):
192 200 t = k.split('.')[0]
193 201 if t not in tools:
194 202 tools[t] = int(_toolstr(ui, t, "priority"))
195 203 if _toolbool(ui, t, "disabled"):
196 204 disabled.add(t)
197 205 names = tools.keys()
198 206 tools = sorted([(-p, tool) for tool, p in tools.items()
199 207 if tool not in disabled])
200 208 uimerge = ui.config("ui", "merge")
201 209 if uimerge:
202 210 # external tools defined in uimerge won't be able to handle
203 211 # change/delete conflicts
204 212 if uimerge not in names and not changedelete:
205 213 return (uimerge, uimerge)
206 214 tools.insert(0, (None, uimerge)) # highest priority
207 215 tools.append((None, "hgmerge")) # the old default, if found
208 216 for p, t in tools:
209 217 if check(t, None, symlink, binary, changedelete):
210 218 toolpath = _findtool(ui, t)
211 return (t, procutil.shellquote(toolpath))
219 return (t, _quotetoolpath(toolpath))
212 220
213 221 # internal merge or prompt as last resort
214 222 if symlink or binary or changedelete:
215 223 if not changedelete and len(tools):
216 224 # any tool is rejected by capability for symlink or binary
217 225 ui.warn(_("no tool found to merge %s\n") % path)
218 226 return ":prompt", None
219 227 return ":merge", None
220 228
221 229 def _eoltype(data):
222 230 "Guess the EOL type of a file"
223 231 if '\0' in data: # binary
224 232 return None
225 233 if '\r\n' in data: # Windows
226 234 return '\r\n'
227 235 if '\r' in data: # Old Mac
228 236 return '\r'
229 237 if '\n' in data: # UNIX
230 238 return '\n'
231 239 return None # unknown
232 240
233 241 def _matcheol(file, back):
234 242 "Convert EOL markers in a file to match origfile"
235 243 tostyle = _eoltype(back.data()) # No repo.wread filters?
236 244 if tostyle:
237 245 data = util.readfile(file)
238 246 style = _eoltype(data)
239 247 if style:
240 248 newdata = data.replace(style, tostyle)
241 249 if newdata != data:
242 250 util.writefile(file, newdata)
243 251
244 252 @internaltool('prompt', nomerge)
245 253 def _iprompt(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
246 254 """Asks the user which of the local `p1()` or the other `p2()` version to
247 255 keep as the merged version."""
248 256 ui = repo.ui
249 257 fd = fcd.path()
250 258
251 259 # Avoid prompting during an in-memory merge since it doesn't support merge
252 260 # conflicts.
253 261 if fcd.changectx().isinmemory():
254 262 raise error.InMemoryMergeConflictsError('in-memory merge does not '
255 263 'support file conflicts')
256 264
257 265 prompts = partextras(labels)
258 266 prompts['fd'] = fd
259 267 try:
260 268 if fco.isabsent():
261 269 index = ui.promptchoice(
262 270 _localchangedotherdeletedmsg % prompts, 2)
263 271 choice = ['local', 'other', 'unresolved'][index]
264 272 elif fcd.isabsent():
265 273 index = ui.promptchoice(
266 274 _otherchangedlocaldeletedmsg % prompts, 2)
267 275 choice = ['other', 'local', 'unresolved'][index]
268 276 else:
269 277 index = ui.promptchoice(
270 278 _("keep (l)ocal%(l)s, take (o)ther%(o)s, or leave (u)nresolved"
271 279 " for %(fd)s?"
272 280 "$$ &Local $$ &Other $$ &Unresolved") % prompts, 2)
273 281 choice = ['local', 'other', 'unresolved'][index]
274 282
275 283 if choice == 'other':
276 284 return _iother(repo, mynode, orig, fcd, fco, fca, toolconf,
277 285 labels)
278 286 elif choice == 'local':
279 287 return _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf,
280 288 labels)
281 289 elif choice == 'unresolved':
282 290 return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf,
283 291 labels)
284 292 except error.ResponseExpected:
285 293 ui.write("\n")
286 294 return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf,
287 295 labels)
288 296
289 297 @internaltool('local', nomerge)
290 298 def _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
291 299 """Uses the local `p1()` version of files as the merged version."""
292 300 return 0, fcd.isabsent()
293 301
294 302 @internaltool('other', nomerge)
295 303 def _iother(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
296 304 """Uses the other `p2()` version of files as the merged version."""
297 305 if fco.isabsent():
298 306 # local changed, remote deleted -- 'deleted' picked
299 307 _underlyingfctxifabsent(fcd).remove()
300 308 deleted = True
301 309 else:
302 310 _underlyingfctxifabsent(fcd).write(fco.data(), fco.flags())
303 311 deleted = False
304 312 return 0, deleted
305 313
306 314 @internaltool('fail', nomerge)
307 315 def _ifail(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
308 316 """
309 317 Rather than attempting to merge files that were modified on both
310 318 branches, it marks them as unresolved. The resolve command must be
311 319 used to resolve these conflicts."""
312 320 # for change/delete conflicts write out the changed version, then fail
313 321 if fcd.isabsent():
314 322 _underlyingfctxifabsent(fcd).write(fco.data(), fco.flags())
315 323 return 1, False
316 324
317 325 def _underlyingfctxifabsent(filectx):
318 326 """Sometimes when resolving, our fcd is actually an absentfilectx, but
319 327 we want to write to it (to do the resolve). This helper returns the
320 328 underyling workingfilectx in that case.
321 329 """
322 330 if filectx.isabsent():
323 331 return filectx.changectx()[filectx.path()]
324 332 else:
325 333 return filectx
326 334
327 335 def _premerge(repo, fcd, fco, fca, toolconf, files, labels=None):
328 tool, toolpath, binary, symlink = toolconf
336 tool, toolpath, binary, symlink, scriptfn = toolconf
329 337 if symlink or fcd.isabsent() or fco.isabsent():
330 338 return 1
331 339 unused, unused, unused, back = files
332 340
333 341 ui = repo.ui
334 342
335 343 validkeep = ['keep', 'keep-merge3']
336 344
337 345 # do we attempt to simplemerge first?
338 346 try:
339 347 premerge = _toolbool(ui, tool, "premerge", not binary)
340 348 except error.ConfigError:
341 349 premerge = _toolstr(ui, tool, "premerge", "").lower()
342 350 if premerge not in validkeep:
343 351 _valid = ', '.join(["'" + v + "'" for v in validkeep])
344 352 raise error.ConfigError(_("%s.premerge not valid "
345 353 "('%s' is neither boolean nor %s)") %
346 354 (tool, premerge, _valid))
347 355
348 356 if premerge:
349 357 if premerge == 'keep-merge3':
350 358 if not labels:
351 359 labels = _defaultconflictlabels
352 360 if len(labels) < 3:
353 361 labels.append('base')
354 362 r = simplemerge.simplemerge(ui, fcd, fca, fco, quiet=True, label=labels)
355 363 if not r:
356 364 ui.debug(" premerge successful\n")
357 365 return 0
358 366 if premerge not in validkeep:
359 367 # restore from backup and try again
360 368 _restorebackup(fcd, back)
361 369 return 1 # continue merging
362 370
363 371 def _mergecheck(repo, mynode, orig, fcd, fco, fca, toolconf):
364 tool, toolpath, binary, symlink = toolconf
372 tool, toolpath, binary, symlink, scriptfn = toolconf
365 373 if symlink:
366 374 repo.ui.warn(_('warning: internal %s cannot merge symlinks '
367 375 'for %s\n') % (tool, fcd.path()))
368 376 return False
369 377 if fcd.isabsent() or fco.isabsent():
370 378 repo.ui.warn(_('warning: internal %s cannot merge change/delete '
371 379 'conflict for %s\n') % (tool, fcd.path()))
372 380 return False
373 381 return True
374 382
375 383 def _merge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels, mode):
376 384 """
377 385 Uses the internal non-interactive simple merge algorithm for merging
378 386 files. It will fail if there are any conflicts and leave markers in
379 387 the partially merged file. Markers will have two sections, one for each side
380 388 of merge, unless mode equals 'union' which suppresses the markers."""
381 389 ui = repo.ui
382 390
383 391 r = simplemerge.simplemerge(ui, fcd, fca, fco, label=labels, mode=mode)
384 392 return True, r, False
385 393
386 394 @internaltool('union', fullmerge,
387 395 _("warning: conflicts while merging %s! "
388 396 "(edit, then use 'hg resolve --mark')\n"),
389 397 precheck=_mergecheck)
390 398 def _iunion(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
391 399 """
392 400 Uses the internal non-interactive simple merge algorithm for merging
393 401 files. It will use both left and right sides for conflict regions.
394 402 No markers are inserted."""
395 403 return _merge(repo, mynode, orig, fcd, fco, fca, toolconf,
396 404 files, labels, 'union')
397 405
398 406 @internaltool('merge', fullmerge,
399 407 _("warning: conflicts while merging %s! "
400 408 "(edit, then use 'hg resolve --mark')\n"),
401 409 precheck=_mergecheck)
402 410 def _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
403 411 """
404 412 Uses the internal non-interactive simple merge algorithm for merging
405 413 files. It will fail if there are any conflicts and leave markers in
406 414 the partially merged file. Markers will have two sections, one for each side
407 415 of merge."""
408 416 return _merge(repo, mynode, orig, fcd, fco, fca, toolconf,
409 417 files, labels, 'merge')
410 418
411 419 @internaltool('merge3', fullmerge,
412 420 _("warning: conflicts while merging %s! "
413 421 "(edit, then use 'hg resolve --mark')\n"),
414 422 precheck=_mergecheck)
415 423 def _imerge3(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
416 424 """
417 425 Uses the internal non-interactive simple merge algorithm for merging
418 426 files. It will fail if there are any conflicts and leave markers in
419 427 the partially merged file. Marker will have three sections, one from each
420 428 side of the merge and one for the base content."""
421 429 if not labels:
422 430 labels = _defaultconflictlabels
423 431 if len(labels) < 3:
424 432 labels.append('base')
425 433 return _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels)
426 434
427 435 def _imergeauto(repo, mynode, orig, fcd, fco, fca, toolconf, files,
428 436 labels=None, localorother=None):
429 437 """
430 438 Generic driver for _imergelocal and _imergeother
431 439 """
432 440 assert localorother is not None
433 tool, toolpath, binary, symlink = toolconf
441 tool, toolpath, binary, symlink, scriptfn = toolconf
434 442 r = simplemerge.simplemerge(repo.ui, fcd, fca, fco, label=labels,
435 443 localorother=localorother)
436 444 return True, r
437 445
438 446 @internaltool('merge-local', mergeonly, precheck=_mergecheck)
439 447 def _imergelocal(*args, **kwargs):
440 448 """
441 449 Like :merge, but resolve all conflicts non-interactively in favor
442 450 of the local `p1()` changes."""
443 451 success, status = _imergeauto(localorother='local', *args, **kwargs)
444 452 return success, status, False
445 453
446 454 @internaltool('merge-other', mergeonly, precheck=_mergecheck)
447 455 def _imergeother(*args, **kwargs):
448 456 """
449 457 Like :merge, but resolve all conflicts non-interactively in favor
450 458 of the other `p2()` changes."""
451 459 success, status = _imergeauto(localorother='other', *args, **kwargs)
452 460 return success, status, False
453 461
454 462 @internaltool('tagmerge', mergeonly,
455 463 _("automatic tag merging of %s failed! "
456 464 "(use 'hg resolve --tool :merge' or another merge "
457 465 "tool of your choice)\n"))
458 466 def _itagmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
459 467 """
460 468 Uses the internal tag merge algorithm (experimental).
461 469 """
462 470 success, status = tagmerge.merge(repo, fcd, fco, fca)
463 471 return success, status, False
464 472
465 473 @internaltool('dump', fullmerge)
466 474 def _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
467 475 """
468 476 Creates three versions of the files to merge, containing the
469 477 contents of local, other and base. These files can then be used to
470 478 perform a merge manually. If the file to be merged is named
471 479 ``a.txt``, these files will accordingly be named ``a.txt.local``,
472 480 ``a.txt.other`` and ``a.txt.base`` and they will be placed in the
473 481 same directory as ``a.txt``.
474 482
475 483 This implies premerge. Therefore, files aren't dumped, if premerge
476 484 runs successfully. Use :forcedump to forcibly write files out.
477 485 """
478 486 a = _workingpath(repo, fcd)
479 487 fd = fcd.path()
480 488
481 489 from . import context
482 490 if isinstance(fcd, context.overlayworkingfilectx):
483 491 raise error.InMemoryMergeConflictsError('in-memory merge does not '
484 492 'support the :dump tool.')
485 493
486 494 util.writefile(a + ".local", fcd.decodeddata())
487 495 repo.wwrite(fd + ".other", fco.data(), fco.flags())
488 496 repo.wwrite(fd + ".base", fca.data(), fca.flags())
489 497 return False, 1, False
490 498
491 499 @internaltool('forcedump', mergeonly)
492 500 def _forcedump(repo, mynode, orig, fcd, fco, fca, toolconf, files,
493 501 labels=None):
494 502 """
495 503 Creates three versions of the files as same as :dump, but omits premerge.
496 504 """
497 505 return _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files,
498 506 labels=labels)
499 507
500 508 def _xmergeimm(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
501 509 # In-memory merge simply raises an exception on all external merge tools,
502 510 # for now.
503 511 #
504 512 # It would be possible to run most tools with temporary files, but this
505 513 # raises the question of what to do if the user only partially resolves the
506 514 # file -- we can't leave a merge state. (Copy to somewhere in the .hg/
507 515 # directory and tell the user how to get it is my best idea, but it's
508 516 # clunky.)
509 517 raise error.InMemoryMergeConflictsError('in-memory merge does not support '
510 518 'external merge tools')
511 519
512 520 def _xmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
513 tool, toolpath, binary, symlink = toolconf
521 tool, toolpath, binary, symlink, scriptfn = toolconf
514 522 if fcd.isabsent() or fco.isabsent():
515 523 repo.ui.warn(_('warning: %s cannot merge change/delete conflict '
516 524 'for %s\n') % (tool, fcd.path()))
517 525 return False, 1, None
518 526 unused, unused, unused, back = files
519 527 localpath = _workingpath(repo, fcd)
520 528 args = _toolstr(repo.ui, tool, "args")
521 529
522 530 with _maketempfiles(repo, fco, fca, repo.wvfs.join(back.path()),
523 531 "$output" in args) as temppaths:
524 532 basepath, otherpath, localoutputpath = temppaths
525 533 outpath = ""
526 534 mylabel, otherlabel = labels[:2]
527 535 if len(labels) >= 3:
528 536 baselabel = labels[2]
529 537 else:
530 538 baselabel = 'base'
531 539 env = {'HG_FILE': fcd.path(),
532 540 'HG_MY_NODE': short(mynode),
533 541 'HG_OTHER_NODE': short(fco.changectx().node()),
534 542 'HG_BASE_NODE': short(fca.changectx().node()),
535 543 'HG_MY_ISLINK': 'l' in fcd.flags(),
536 544 'HG_OTHER_ISLINK': 'l' in fco.flags(),
537 545 'HG_BASE_ISLINK': 'l' in fca.flags(),
538 546 'HG_MY_LABEL': mylabel,
539 547 'HG_OTHER_LABEL': otherlabel,
540 548 'HG_BASE_LABEL': baselabel,
541 549 }
542 550 ui = repo.ui
543 551
544 552 if "$output" in args:
545 553 # read input from backup, write to original
546 554 outpath = localpath
547 555 localpath = localoutputpath
548 556 replace = {'local': localpath, 'base': basepath, 'other': otherpath,
549 557 'output': outpath, 'labellocal': mylabel,
550 558 'labelother': otherlabel, 'labelbase': baselabel}
551 559 args = util.interpolate(
552 560 br'\$', replace, args,
553 561 lambda s: procutil.shellquote(util.localpath(s)))
554 cmd = toolpath + ' ' + args
555 562 if _toolbool(ui, tool, "gui"):
556 563 repo.ui.status(_('running merge tool %s for file %s\n') %
557 564 (tool, fcd.path()))
565 if scriptfn is None:
566 cmd = toolpath + ' ' + args
558 567 repo.ui.debug('launching merge tool: %s\n' % cmd)
559 r = ui.system(cmd, cwd=repo.root, environ=env, blockedtag='mergetool')
568 r = ui.system(cmd, cwd=repo.root, environ=env,
569 blockedtag='mergetool')
570 else:
571 repo.ui.debug('launching python merge script: %s:%s\n' %
572 (toolpath, scriptfn))
573 r = 0
574 try:
575 # avoid cycle cmdutil->merge->filemerge->extensions->cmdutil
576 from . import extensions
577 mod = extensions.loadpath(toolpath, 'hgmerge.%s' % scriptfn)
578 except Exception:
579 raise error.Abort(_("loading python merge script failed: %s") %
580 toolpath)
581 mergefn = getattr(mod, scriptfn, None)
582 if mergefn is None:
583 raise error.Abort(_("%s does not have function: %s") %
584 (toolpath, scriptfn))
585 argslist = procutil.shellsplit(args)
586 # avoid cycle cmdutil->merge->filemerge->hook->extensions->cmdutil
587 from . import hook
588 ret, raised = hook.pythonhook(ui, repo, "merge", toolpath,
589 mergefn, {'args': argslist}, True)
590 if raised:
591 r = 1
560 592 repo.ui.debug('merge tool returned: %d\n' % r)
561 593 return True, r, False
562 594
563 595 def _formatconflictmarker(ctx, template, label, pad):
564 596 """Applies the given template to the ctx, prefixed by the label.
565 597
566 598 Pad is the minimum width of the label prefix, so that multiple markers
567 599 can have aligned templated parts.
568 600 """
569 601 if ctx.node() is None:
570 602 ctx = ctx.p1()
571 603
572 604 props = {'ctx': ctx}
573 605 templateresult = template.renderdefault(props)
574 606
575 607 label = ('%s:' % label).ljust(pad + 1)
576 608 mark = '%s %s' % (label, templateresult)
577 609
578 610 if mark:
579 611 mark = mark.splitlines()[0] # split for safety
580 612
581 613 # 8 for the prefix of conflict marker lines (e.g. '<<<<<<< ')
582 614 return stringutil.ellipsis(mark, 80 - 8)
583 615
584 616 _defaultconflictlabels = ['local', 'other']
585 617
586 618 def _formatlabels(repo, fcd, fco, fca, labels, tool=None):
587 619 """Formats the given labels using the conflict marker template.
588 620
589 621 Returns a list of formatted labels.
590 622 """
591 623 cd = fcd.changectx()
592 624 co = fco.changectx()
593 625 ca = fca.changectx()
594 626
595 627 ui = repo.ui
596 628 template = ui.config('ui', 'mergemarkertemplate')
597 629 if tool is not None:
598 630 template = _toolstr(ui, tool, 'mergemarkertemplate', template)
599 631 template = templater.unquotestring(template)
600 632 tres = formatter.templateresources(ui, repo)
601 633 tmpl = formatter.maketemplater(ui, template, defaults=templatekw.keywords,
602 634 resources=tres)
603 635
604 636 pad = max(len(l) for l in labels)
605 637
606 638 newlabels = [_formatconflictmarker(cd, tmpl, labels[0], pad),
607 639 _formatconflictmarker(co, tmpl, labels[1], pad)]
608 640 if len(labels) > 2:
609 641 newlabels.append(_formatconflictmarker(ca, tmpl, labels[2], pad))
610 642 return newlabels
611 643
612 644 def partextras(labels):
613 645 """Return a dictionary of extra labels for use in prompts to the user
614 646
615 647 Intended use is in strings of the form "(l)ocal%(l)s".
616 648 """
617 649 if labels is None:
618 650 return {
619 651 "l": "",
620 652 "o": "",
621 653 }
622 654
623 655 return {
624 656 "l": " [%s]" % labels[0],
625 657 "o": " [%s]" % labels[1],
626 658 }
627 659
628 660 def _restorebackup(fcd, back):
629 661 # TODO: Add a workingfilectx.write(otherfilectx) path so we can use
630 662 # util.copy here instead.
631 663 fcd.write(back.data(), fcd.flags())
632 664
633 665 def _makebackup(repo, ui, wctx, fcd, premerge):
634 666 """Makes and returns a filectx-like object for ``fcd``'s backup file.
635 667
636 668 In addition to preserving the user's pre-existing modifications to `fcd`
637 669 (if any), the backup is used to undo certain premerges, confirm whether a
638 670 merge changed anything, and determine what line endings the new file should
639 671 have.
640 672
641 673 Backups only need to be written once (right before the premerge) since their
642 674 content doesn't change afterwards.
643 675 """
644 676 if fcd.isabsent():
645 677 return None
646 678 # TODO: Break this import cycle somehow. (filectx -> ctx -> fileset ->
647 679 # merge -> filemerge). (I suspect the fileset import is the weakest link)
648 680 from . import context
649 681 a = _workingpath(repo, fcd)
650 682 back = scmutil.origpath(ui, repo, a)
651 683 inworkingdir = (back.startswith(repo.wvfs.base) and not
652 684 back.startswith(repo.vfs.base))
653 685 if isinstance(fcd, context.overlayworkingfilectx) and inworkingdir:
654 686 # If the backup file is to be in the working directory, and we're
655 687 # merging in-memory, we must redirect the backup to the memory context
656 688 # so we don't disturb the working directory.
657 689 relpath = back[len(repo.wvfs.base) + 1:]
658 690 if premerge:
659 691 wctx[relpath].write(fcd.data(), fcd.flags())
660 692 return wctx[relpath]
661 693 else:
662 694 if premerge:
663 695 # Otherwise, write to wherever path the user specified the backups
664 696 # should go. We still need to switch based on whether the source is
665 697 # in-memory so we can use the fast path of ``util.copy`` if both are
666 698 # on disk.
667 699 if isinstance(fcd, context.overlayworkingfilectx):
668 700 util.writefile(back, fcd.data())
669 701 else:
670 702 util.copyfile(a, back)
671 703 # A arbitraryfilectx is returned, so we can run the same functions on
672 704 # the backup context regardless of where it lives.
673 705 return context.arbitraryfilectx(back, repo=repo)
674 706
675 707 @contextlib.contextmanager
676 708 def _maketempfiles(repo, fco, fca, localpath, uselocalpath):
677 709 """Writes out `fco` and `fca` as temporary files, and (if uselocalpath)
678 710 copies `localpath` to another temporary file, so an external merge tool may
679 711 use them.
680 712 """
681 713 tmproot = None
682 714 tmprootprefix = repo.ui.config('experimental', 'mergetempdirprefix')
683 715 if tmprootprefix:
684 716 tmproot = tempfile.mkdtemp(prefix=tmprootprefix)
685 717
686 718 def maketempfrompath(prefix, path):
687 719 fullbase, ext = os.path.splitext(path)
688 720 pre = "%s~%s" % (os.path.basename(fullbase), prefix)
689 721 if tmproot:
690 722 name = os.path.join(tmproot, pre)
691 723 if ext:
692 724 name += ext
693 725 f = open(name, r"wb")
694 726 else:
695 727 fd, name = tempfile.mkstemp(prefix=pre + '.', suffix=ext)
696 728 f = os.fdopen(fd, r"wb")
697 729 return f, name
698 730
699 731 def tempfromcontext(prefix, ctx):
700 732 f, name = maketempfrompath(prefix, ctx.path())
701 733 data = repo.wwritedata(ctx.path(), ctx.data())
702 734 f.write(data)
703 735 f.close()
704 736 return name
705 737
706 738 b = tempfromcontext("base", fca)
707 739 c = tempfromcontext("other", fco)
708 740 d = localpath
709 741 if uselocalpath:
710 742 # We start off with this being the backup filename, so remove the .orig
711 743 # to make syntax-highlighting more likely.
712 744 if d.endswith('.orig'):
713 745 d, _ = os.path.splitext(d)
714 746 f, d = maketempfrompath("local", d)
715 747 with open(localpath, 'rb') as src:
716 748 f.write(src.read())
717 749 f.close()
718 750
719 751 try:
720 752 yield b, c, d
721 753 finally:
722 754 if tmproot:
723 755 shutil.rmtree(tmproot)
724 756 else:
725 757 util.unlink(b)
726 758 util.unlink(c)
727 759 # if not uselocalpath, d is the 'orig'/backup file which we
728 760 # shouldn't delete.
729 761 if d and uselocalpath:
730 762 util.unlink(d)
731 763
732 764 def _filemerge(premerge, repo, wctx, mynode, orig, fcd, fco, fca, labels=None):
733 765 """perform a 3-way merge in the working directory
734 766
735 767 premerge = whether this is a premerge
736 768 mynode = parent node before merge
737 769 orig = original local filename before merge
738 770 fco = other file context
739 771 fca = ancestor file context
740 772 fcd = local file context for current/destination file
741 773
742 774 Returns whether the merge is complete, the return value of the merge, and
743 775 a boolean indicating whether the file was deleted from disk."""
744 776
745 777 if not fco.cmp(fcd): # files identical?
746 778 return True, None, False
747 779
748 780 ui = repo.ui
749 781 fd = fcd.path()
750 782 binary = fcd.isbinary() or fco.isbinary() or fca.isbinary()
751 783 symlink = 'l' in fcd.flags() + fco.flags()
752 784 changedelete = fcd.isabsent() or fco.isabsent()
753 785 tool, toolpath = _picktool(repo, ui, fd, binary, symlink, changedelete)
786 scriptfn = None
754 787 if tool in internals and tool.startswith('internal:'):
755 788 # normalize to new-style names (':merge' etc)
756 789 tool = tool[len('internal'):]
790 if toolpath and toolpath.startswith('python:'):
791 invalidsyntax = False
792 if toolpath.count(':') >= 2:
793 script, scriptfn = toolpath[7:].rsplit(':', 1)
794 if not scriptfn:
795 invalidsyntax = True
796 # missing :callable can lead to spliting on windows drive letter
797 if '\\' in scriptfn or '/' in scriptfn:
798 invalidsyntax = True
799 else:
800 invalidsyntax = True
801 if invalidsyntax:
802 raise error.Abort(_("invalid 'python:' syntax: %s") % toolpath)
803 toolpath = script
757 804 ui.debug("picked tool '%s' for %s (binary %s symlink %s changedelete %s)\n"
758 805 % (tool, fd, pycompat.bytestr(binary), pycompat.bytestr(symlink),
759 806 pycompat.bytestr(changedelete)))
760 807
761 808 if tool in internals:
762 809 func = internals[tool]
763 810 mergetype = func.mergetype
764 811 onfailure = func.onfailure
765 812 precheck = func.precheck
766 813 isexternal = False
767 814 else:
768 815 if wctx.isinmemory():
769 816 func = _xmergeimm
770 817 else:
771 818 func = _xmerge
772 819 mergetype = fullmerge
773 820 onfailure = _("merging %s failed!\n")
774 821 precheck = None
775 822 isexternal = True
776 823
777 toolconf = tool, toolpath, binary, symlink
824 toolconf = tool, toolpath, binary, symlink, scriptfn
778 825
779 826 if mergetype == nomerge:
780 827 r, deleted = func(repo, mynode, orig, fcd, fco, fca, toolconf, labels)
781 828 return True, r, deleted
782 829
783 830 if premerge:
784 831 if orig != fco.path():
785 832 ui.status(_("merging %s and %s to %s\n") % (orig, fco.path(), fd))
786 833 else:
787 834 ui.status(_("merging %s\n") % fd)
788 835
789 836 ui.debug("my %s other %s ancestor %s\n" % (fcd, fco, fca))
790 837
791 838 if precheck and not precheck(repo, mynode, orig, fcd, fco, fca,
792 839 toolconf):
793 840 if onfailure:
794 841 if wctx.isinmemory():
795 842 raise error.InMemoryMergeConflictsError('in-memory merge does '
796 843 'not support merge '
797 844 'conflicts')
798 845 ui.warn(onfailure % fd)
799 846 return True, 1, False
800 847
801 848 back = _makebackup(repo, ui, wctx, fcd, premerge)
802 849 files = (None, None, None, back)
803 850 r = 1
804 851 try:
805 852 internalmarkerstyle = ui.config('ui', 'mergemarkers')
806 853 if isexternal:
807 854 markerstyle = _toolstr(ui, tool, 'mergemarkers')
808 855 else:
809 856 markerstyle = internalmarkerstyle
810 857
811 858 if not labels:
812 859 labels = _defaultconflictlabels
813 860 formattedlabels = labels
814 861 if markerstyle != 'basic':
815 862 formattedlabels = _formatlabels(repo, fcd, fco, fca, labels,
816 863 tool=tool)
817 864
818 865 if premerge and mergetype == fullmerge:
819 866 # conflict markers generated by premerge will use 'detailed'
820 867 # settings if either ui.mergemarkers or the tool's mergemarkers
821 868 # setting is 'detailed'. This way tools can have basic labels in
822 869 # space-constrained areas of the UI, but still get full information
823 870 # in conflict markers if premerge is 'keep' or 'keep-merge3'.
824 871 premergelabels = labels
825 872 labeltool = None
826 873 if markerstyle != 'basic':
827 874 # respect 'tool's mergemarkertemplate (which defaults to
828 875 # ui.mergemarkertemplate)
829 876 labeltool = tool
830 877 if internalmarkerstyle != 'basic' or markerstyle != 'basic':
831 878 premergelabels = _formatlabels(repo, fcd, fco, fca,
832 879 premergelabels, tool=labeltool)
833 880
834 881 r = _premerge(repo, fcd, fco, fca, toolconf, files,
835 882 labels=premergelabels)
836 883 # complete if premerge successful (r is 0)
837 884 return not r, r, False
838 885
839 886 needcheck, r, deleted = func(repo, mynode, orig, fcd, fco, fca,
840 887 toolconf, files, labels=formattedlabels)
841 888
842 889 if needcheck:
843 890 r = _check(repo, r, ui, tool, fcd, files)
844 891
845 892 if r:
846 893 if onfailure:
847 894 if wctx.isinmemory():
848 895 raise error.InMemoryMergeConflictsError('in-memory merge '
849 896 'does not support '
850 897 'merge conflicts')
851 898 ui.warn(onfailure % fd)
852 899 _onfilemergefailure(ui)
853 900
854 901 return True, r, deleted
855 902 finally:
856 903 if not r and back is not None:
857 904 back.remove()
858 905
859 906 def _haltmerge():
860 907 msg = _('merge halted after failed merge (see hg resolve)')
861 908 raise error.InterventionRequired(msg)
862 909
863 910 def _onfilemergefailure(ui):
864 911 action = ui.config('merge', 'on-failure')
865 912 if action == 'prompt':
866 913 msg = _('continue merge operation (yn)?' '$$ &Yes $$ &No')
867 914 if ui.promptchoice(msg, 0) == 1:
868 915 _haltmerge()
869 916 if action == 'halt':
870 917 _haltmerge()
871 918 # default action is 'continue', in which case we neither prompt nor halt
872 919
873 920 def _check(repo, r, ui, tool, fcd, files):
874 921 fd = fcd.path()
875 922 unused, unused, unused, back = files
876 923
877 924 if not r and (_toolbool(ui, tool, "checkconflicts") or
878 925 'conflicts' in _toollist(ui, tool, "check")):
879 926 if re.search("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcd.data(),
880 927 re.MULTILINE):
881 928 r = 1
882 929
883 930 checked = False
884 931 if 'prompt' in _toollist(ui, tool, "check"):
885 932 checked = True
886 933 if ui.promptchoice(_("was merge of '%s' successful (yn)?"
887 934 "$$ &Yes $$ &No") % fd, 1):
888 935 r = 1
889 936
890 937 if not r and not checked and (_toolbool(ui, tool, "checkchanged") or
891 938 'changed' in
892 939 _toollist(ui, tool, "check")):
893 940 if back is not None and not fcd.cmp(back):
894 941 if ui.promptchoice(_(" output file %s appears unchanged\n"
895 942 "was merge successful (yn)?"
896 943 "$$ &Yes $$ &No") % fd, 1):
897 944 r = 1
898 945
899 946 if back is not None and _toolbool(ui, tool, "fixeol"):
900 947 _matcheol(_workingpath(repo, fcd), back)
901 948
902 949 return r
903 950
904 951 def _workingpath(repo, ctx):
905 952 return repo.wjoin(ctx.path())
906 953
907 954 def premerge(repo, wctx, mynode, orig, fcd, fco, fca, labels=None):
908 955 return _filemerge(True, repo, wctx, mynode, orig, fcd, fco, fca,
909 956 labels=labels)
910 957
911 958 def filemerge(repo, wctx, mynode, orig, fcd, fco, fca, labels=None):
912 959 return _filemerge(False, repo, wctx, mynode, orig, fcd, fco, fca,
913 960 labels=labels)
914 961
915 962 def loadinternalmerge(ui, extname, registrarobj):
916 963 """Load internal merge tool from specified registrarobj
917 964 """
918 965 for name, func in registrarobj._table.iteritems():
919 966 fullname = ':' + name
920 967 internals[fullname] = func
921 968 internals['internal:' + name] = func
922 969 internalsdoc[fullname] = func
923 970
924 971 # load built-in merge tools explicitly to setup internalsdoc
925 972 loadinternalmerge(None, None, internaltool)
926 973
927 974 # tell hggettext to extract docstrings from these functions:
928 975 i18nfunctions = internals.values()
@@ -1,279 +1,279 b''
1 1 # hook.py - hook support for mercurial
2 2 #
3 3 # Copyright 2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import os
11 11 import sys
12 12
13 13 from .i18n import _
14 14 from . import (
15 15 demandimport,
16 16 encoding,
17 17 error,
18 18 extensions,
19 19 pycompat,
20 20 util,
21 21 )
22 22 from .utils import (
23 23 procutil,
24 24 stringutil,
25 25 )
26 26
27 def _pythonhook(ui, repo, htype, hname, funcname, args, throw):
27 def pythonhook(ui, repo, htype, hname, funcname, args, throw):
28 28 '''call python hook. hook is callable object, looked up as
29 29 name in python module. if callable returns "true", hook
30 30 fails, else passes. if hook raises exception, treated as
31 31 hook failure. exception propagates if throw is "true".
32 32
33 33 reason for "true" meaning "hook failed" is so that
34 34 unmodified commands (e.g. mercurial.commands.update) can
35 35 be run as hooks without wrappers to convert return values.'''
36 36
37 37 if callable(funcname):
38 38 obj = funcname
39 39 funcname = pycompat.sysbytes(obj.__module__ + r"." + obj.__name__)
40 40 else:
41 41 d = funcname.rfind('.')
42 42 if d == -1:
43 43 raise error.HookLoadError(
44 44 _('%s hook is invalid: "%s" not in a module')
45 45 % (hname, funcname))
46 46 modname = funcname[:d]
47 47 oldpaths = sys.path
48 48 if procutil.mainfrozen():
49 49 # binary installs require sys.path manipulation
50 50 modpath, modfile = os.path.split(modname)
51 51 if modpath and modfile:
52 52 sys.path = sys.path[:] + [modpath]
53 53 modname = modfile
54 54 with demandimport.deactivated():
55 55 try:
56 56 obj = __import__(pycompat.sysstr(modname))
57 57 except (ImportError, SyntaxError):
58 58 e1 = sys.exc_info()
59 59 try:
60 60 # extensions are loaded with hgext_ prefix
61 61 obj = __import__(r"hgext_%s" % pycompat.sysstr(modname))
62 62 except (ImportError, SyntaxError):
63 63 e2 = sys.exc_info()
64 64 if ui.tracebackflag:
65 65 ui.warn(_('exception from first failed import '
66 66 'attempt:\n'))
67 67 ui.traceback(e1)
68 68 if ui.tracebackflag:
69 69 ui.warn(_('exception from second failed import '
70 70 'attempt:\n'))
71 71 ui.traceback(e2)
72 72
73 73 if not ui.tracebackflag:
74 74 tracebackhint = _(
75 75 'run with --traceback for stack trace')
76 76 else:
77 77 tracebackhint = None
78 78 raise error.HookLoadError(
79 79 _('%s hook is invalid: import of "%s" failed') %
80 80 (hname, modname), hint=tracebackhint)
81 81 sys.path = oldpaths
82 82 try:
83 83 for p in funcname.split('.')[1:]:
84 84 obj = getattr(obj, p)
85 85 except AttributeError:
86 86 raise error.HookLoadError(
87 87 _('%s hook is invalid: "%s" is not defined')
88 88 % (hname, funcname))
89 89 if not callable(obj):
90 90 raise error.HookLoadError(
91 91 _('%s hook is invalid: "%s" is not callable')
92 92 % (hname, funcname))
93 93
94 94 ui.note(_("calling hook %s: %s\n") % (hname, funcname))
95 95 starttime = util.timer()
96 96
97 97 try:
98 98 r = obj(ui=ui, repo=repo, hooktype=htype, **pycompat.strkwargs(args))
99 99 except Exception as exc:
100 100 if isinstance(exc, error.Abort):
101 101 ui.warn(_('error: %s hook failed: %s\n') %
102 102 (hname, exc.args[0]))
103 103 else:
104 104 ui.warn(_('error: %s hook raised an exception: '
105 105 '%s\n') % (hname, encoding.strtolocal(str(exc))))
106 106 if throw:
107 107 raise
108 108 if not ui.tracebackflag:
109 109 ui.warn(_('(run with --traceback for stack trace)\n'))
110 110 ui.traceback()
111 111 return True, True
112 112 finally:
113 113 duration = util.timer() - starttime
114 114 ui.log('pythonhook', 'pythonhook-%s: %s finished in %0.2f seconds\n',
115 115 htype, funcname, duration)
116 116 if r:
117 117 if throw:
118 118 raise error.HookAbort(_('%s hook failed') % hname)
119 119 ui.warn(_('warning: %s hook failed\n') % hname)
120 120 return r, False
121 121
122 122 def _exthook(ui, repo, htype, name, cmd, args, throw):
123 123 ui.note(_("running hook %s: %s\n") % (name, cmd))
124 124
125 125 starttime = util.timer()
126 126 env = {}
127 127
128 128 # make in-memory changes visible to external process
129 129 if repo is not None:
130 130 tr = repo.currenttransaction()
131 131 repo.dirstate.write(tr)
132 132 if tr and tr.writepending():
133 133 env['HG_PENDING'] = repo.root
134 134 env['HG_HOOKTYPE'] = htype
135 135 env['HG_HOOKNAME'] = name
136 136
137 137 for k, v in args.iteritems():
138 138 if callable(v):
139 139 v = v()
140 140 if isinstance(v, (dict, list)):
141 141 v = stringutil.pprint(v)
142 142 env['HG_' + k.upper()] = v
143 143
144 144 if repo:
145 145 cwd = repo.root
146 146 else:
147 147 cwd = pycompat.getcwd()
148 148 r = ui.system(cmd, environ=env, cwd=cwd, blockedtag='exthook-%s' % (name,))
149 149
150 150 duration = util.timer() - starttime
151 151 ui.log('exthook', 'exthook-%s: %s finished in %0.2f seconds\n',
152 152 name, cmd, duration)
153 153 if r:
154 154 desc = procutil.explainexit(r)
155 155 if throw:
156 156 raise error.HookAbort(_('%s hook %s') % (name, desc))
157 157 ui.warn(_('warning: %s hook %s\n') % (name, desc))
158 158 return r
159 159
160 160 # represent an untrusted hook command
161 161 _fromuntrusted = object()
162 162
163 163 def _allhooks(ui):
164 164 """return a list of (hook-id, cmd) pairs sorted by priority"""
165 165 hooks = _hookitems(ui)
166 166 # Be careful in this section, propagating the real commands from untrusted
167 167 # sources would create a security vulnerability, make sure anything altered
168 168 # in that section uses "_fromuntrusted" as its command.
169 169 untrustedhooks = _hookitems(ui, _untrusted=True)
170 170 for name, value in untrustedhooks.items():
171 171 trustedvalue = hooks.get(name, (None, None, name, _fromuntrusted))
172 172 if value != trustedvalue:
173 173 (lp, lo, lk, lv) = trustedvalue
174 174 hooks[name] = (lp, lo, lk, _fromuntrusted)
175 175 # (end of the security sensitive section)
176 176 return [(k, v) for p, o, k, v in sorted(hooks.values())]
177 177
178 178 def _hookitems(ui, _untrusted=False):
179 179 """return all hooks items ready to be sorted"""
180 180 hooks = {}
181 181 for name, cmd in ui.configitems('hooks', untrusted=_untrusted):
182 182 if not name.startswith('priority'):
183 183 priority = ui.configint('hooks', 'priority.%s' % name, 0)
184 184 hooks[name] = (-priority, len(hooks), name, cmd)
185 185 return hooks
186 186
187 187 _redirect = False
188 188 def redirect(state):
189 189 global _redirect
190 190 _redirect = state
191 191
192 192 def hashook(ui, htype):
193 193 """return True if a hook is configured for 'htype'"""
194 194 if not ui.callhooks:
195 195 return False
196 196 for hname, cmd in _allhooks(ui):
197 197 if hname.split('.')[0] == htype and cmd:
198 198 return True
199 199 return False
200 200
201 201 def hook(ui, repo, htype, throw=False, **args):
202 202 if not ui.callhooks:
203 203 return False
204 204
205 205 hooks = []
206 206 for hname, cmd in _allhooks(ui):
207 207 if hname.split('.')[0] == htype and cmd:
208 208 hooks.append((hname, cmd))
209 209
210 210 res = runhooks(ui, repo, htype, hooks, throw=throw, **args)
211 211 r = False
212 212 for hname, cmd in hooks:
213 213 r = res[hname][0] or r
214 214 return r
215 215
216 216 def runhooks(ui, repo, htype, hooks, throw=False, **args):
217 217 args = pycompat.byteskwargs(args)
218 218 res = {}
219 219 oldstdout = -1
220 220
221 221 try:
222 222 for hname, cmd in hooks:
223 223 if oldstdout == -1 and _redirect:
224 224 try:
225 225 stdoutno = procutil.stdout.fileno()
226 226 stderrno = procutil.stderr.fileno()
227 227 # temporarily redirect stdout to stderr, if possible
228 228 if stdoutno >= 0 and stderrno >= 0:
229 229 procutil.stdout.flush()
230 230 oldstdout = os.dup(stdoutno)
231 231 os.dup2(stderrno, stdoutno)
232 232 except (OSError, AttributeError):
233 233 # files seem to be bogus, give up on redirecting (WSGI, etc)
234 234 pass
235 235
236 236 if cmd is _fromuntrusted:
237 237 if throw:
238 238 raise error.HookAbort(
239 239 _('untrusted hook %s not executed') % hname,
240 240 hint = _("see 'hg help config.trusted'"))
241 241 ui.warn(_('warning: untrusted hook %s not executed\n') % hname)
242 242 r = 1
243 243 raised = False
244 244 elif callable(cmd):
245 r, raised = _pythonhook(ui, repo, htype, hname, cmd, args,
245 r, raised = pythonhook(ui, repo, htype, hname, cmd, args,
246 246 throw)
247 247 elif cmd.startswith('python:'):
248 248 if cmd.count(':') >= 2:
249 249 path, cmd = cmd[7:].rsplit(':', 1)
250 250 path = util.expandpath(path)
251 251 if repo:
252 252 path = os.path.join(repo.root, path)
253 253 try:
254 254 mod = extensions.loadpath(path, 'hghook.%s' % hname)
255 255 except Exception:
256 256 ui.write(_("loading %s hook failed:\n") % hname)
257 257 raise
258 258 hookfn = getattr(mod, cmd)
259 259 else:
260 260 hookfn = cmd[7:].strip()
261 r, raised = _pythonhook(ui, repo, htype, hname, hookfn, args,
261 r, raised = pythonhook(ui, repo, htype, hname, hookfn, args,
262 262 throw)
263 263 else:
264 264 r = _exthook(ui, repo, htype, hname, cmd, args, throw)
265 265 raised = False
266 266
267 267 res[hname] = r, raised
268 268 finally:
269 269 # The stderr is fully buffered on Windows when connected to a pipe.
270 270 # A forcible flush is required to make small stderr data in the
271 271 # remote side available to the client immediately.
272 272 procutil.stderr.flush()
273 273
274 274 if _redirect and oldstdout >= 0:
275 275 procutil.stdout.flush() # write hook output to stderr fd
276 276 os.dup2(oldstdout, stdoutno)
277 277 os.close(oldstdout)
278 278
279 279 return res
@@ -1,1673 +1,1850 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 $ hg init repo
5 5 $ cd repo
6 6
7 7 revision 0
8 8
9 9 $ echo "revision 0" > f
10 10 $ echo "space" >> f
11 11 $ hg commit -Am "revision 0"
12 12 adding f
13 13
14 14 revision 1
15 15
16 16 $ echo "revision 1" > f
17 17 $ echo "space" >> f
18 18 $ hg commit -Am "revision 1"
19 19 $ hg update 0 > /dev/null
20 20
21 21 revision 2
22 22
23 23 $ echo "revision 2" > f
24 24 $ echo "space" >> f
25 25 $ hg commit -Am "revision 2"
26 26 created new head
27 27 $ hg update 0 > /dev/null
28 28
29 29 revision 3 - simple to merge
30 30
31 31 $ echo "revision 3" >> f
32 32 $ hg commit -Am "revision 3"
33 33 created new head
34 34
35 35 revision 4 - hard to merge
36 36
37 37 $ hg update 0 > /dev/null
38 38 $ echo "revision 4" > f
39 39 $ hg commit -Am "revision 4"
40 40 created new head
41 41
42 42 $ echo "[merge-tools]" > .hg/hgrc
43 43
44 44 $ beforemerge() {
45 45 > cat .hg/hgrc
46 46 > echo "# hg update -C 1"
47 47 > hg update -C 1 > /dev/null
48 48 > }
49 49 $ aftermerge() {
50 50 > echo "# cat f"
51 51 > cat f
52 52 > echo "# hg stat"
53 53 > hg stat
54 54 > echo "# hg resolve --list"
55 55 > hg resolve --list
56 56 > rm -f f.orig
57 57 > }
58 58
59 59 Tool selection
60 60
61 61 default is internal merge:
62 62
63 63 $ beforemerge
64 64 [merge-tools]
65 65 # hg update -C 1
66 66
67 67 hg merge -r 2
68 68 override $PATH to ensure hgmerge not visible; use $PYTHON in case we're
69 69 running from a devel copy, not a temp installation
70 70
71 71 $ PATH="$BINDIR:/usr/sbin" $PYTHON "$BINDIR"/hg merge -r 2
72 72 merging f
73 73 warning: conflicts while merging f! (edit, then use 'hg resolve --mark')
74 74 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
75 75 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
76 76 [1]
77 77 $ aftermerge
78 78 # cat f
79 79 <<<<<<< working copy: ef83787e2614 - test: revision 1
80 80 revision 1
81 81 =======
82 82 revision 2
83 83 >>>>>>> merge rev: 0185f4e0cf02 - test: revision 2
84 84 space
85 85 # hg stat
86 86 M f
87 87 ? f.orig
88 88 # hg resolve --list
89 89 U f
90 90
91 91 simplest hgrc using false for merge:
92 92
93 93 $ echo "false.whatever=" >> .hg/hgrc
94 94 $ beforemerge
95 95 [merge-tools]
96 96 false.whatever=
97 97 # hg update -C 1
98 98 $ hg merge -r 2
99 99 merging f
100 100 merging f failed!
101 101 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
102 102 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
103 103 [1]
104 104 $ aftermerge
105 105 # cat f
106 106 revision 1
107 107 space
108 108 # hg stat
109 109 M f
110 110 ? f.orig
111 111 # hg resolve --list
112 112 U f
113 113
114 114 #if unix-permissions
115 115
116 116 unexecutable file in $PATH shouldn't be found:
117 117
118 118 $ echo "echo fail" > false
119 119 $ hg up -qC 1
120 120 $ PATH="`pwd`:$BINDIR:/usr/sbin" $PYTHON "$BINDIR"/hg merge -r 2
121 121 merging f
122 122 warning: conflicts while merging f! (edit, then use 'hg resolve --mark')
123 123 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
124 124 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
125 125 [1]
126 126 $ rm false
127 127
128 128 #endif
129 129
130 130 executable directory in $PATH shouldn't be found:
131 131
132 132 $ mkdir false
133 133 $ hg up -qC 1
134 134 $ PATH="`pwd`:$BINDIR:/usr/sbin" $PYTHON "$BINDIR"/hg merge -r 2
135 135 merging f
136 136 warning: conflicts while merging f! (edit, then use 'hg resolve --mark')
137 137 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
138 138 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
139 139 [1]
140 140 $ rmdir false
141 141
142 142 true with higher .priority gets precedence:
143 143
144 144 $ echo "true.priority=1" >> .hg/hgrc
145 145 $ beforemerge
146 146 [merge-tools]
147 147 false.whatever=
148 148 true.priority=1
149 149 # hg update -C 1
150 150 $ hg merge -r 2
151 151 merging f
152 152 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
153 153 (branch merge, don't forget to commit)
154 154 $ aftermerge
155 155 # cat f
156 156 revision 1
157 157 space
158 158 # hg stat
159 159 M f
160 160 # hg resolve --list
161 161 R f
162 162
163 163 unless lowered on command line:
164 164
165 165 $ beforemerge
166 166 [merge-tools]
167 167 false.whatever=
168 168 true.priority=1
169 169 # hg update -C 1
170 170 $ hg merge -r 2 --config merge-tools.true.priority=-7
171 171 merging f
172 172 merging f failed!
173 173 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
174 174 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
175 175 [1]
176 176 $ aftermerge
177 177 # cat f
178 178 revision 1
179 179 space
180 180 # hg stat
181 181 M f
182 182 ? f.orig
183 183 # hg resolve --list
184 184 U f
185 185
186 186 or false set higher on command line:
187 187
188 188 $ beforemerge
189 189 [merge-tools]
190 190 false.whatever=
191 191 true.priority=1
192 192 # hg update -C 1
193 193 $ hg merge -r 2 --config merge-tools.false.priority=117
194 194 merging f
195 195 merging f failed!
196 196 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
197 197 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
198 198 [1]
199 199 $ aftermerge
200 200 # cat f
201 201 revision 1
202 202 space
203 203 # hg stat
204 204 M f
205 205 ? f.orig
206 206 # hg resolve --list
207 207 U f
208 208
209 209 or true set to disabled:
210 210 $ beforemerge
211 211 [merge-tools]
212 212 false.whatever=
213 213 true.priority=1
214 214 # hg update -C 1
215 215 $ hg merge -r 2 --config merge-tools.true.disabled=yes
216 216 merging f
217 217 merging f failed!
218 218 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
219 219 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
220 220 [1]
221 221 $ aftermerge
222 222 # cat f
223 223 revision 1
224 224 space
225 225 # hg stat
226 226 M f
227 227 ? f.orig
228 228 # hg resolve --list
229 229 U f
230 230
231 231 or true.executable not found in PATH:
232 232
233 233 $ beforemerge
234 234 [merge-tools]
235 235 false.whatever=
236 236 true.priority=1
237 237 # hg update -C 1
238 238 $ hg merge -r 2 --config merge-tools.true.executable=nonexistentmergetool
239 239 merging f
240 240 merging f failed!
241 241 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
242 242 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
243 243 [1]
244 244 $ aftermerge
245 245 # cat f
246 246 revision 1
247 247 space
248 248 # hg stat
249 249 M f
250 250 ? f.orig
251 251 # hg resolve --list
252 252 U f
253 253
254 254 or true.executable with bogus path:
255 255
256 256 $ beforemerge
257 257 [merge-tools]
258 258 false.whatever=
259 259 true.priority=1
260 260 # hg update -C 1
261 261 $ hg merge -r 2 --config merge-tools.true.executable=/nonexistent/mergetool
262 262 merging f
263 263 merging f failed!
264 264 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
265 265 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
266 266 [1]
267 267 $ aftermerge
268 268 # cat f
269 269 revision 1
270 270 space
271 271 # hg stat
272 272 M f
273 273 ? f.orig
274 274 # hg resolve --list
275 275 U f
276 276
277 277 but true.executable set to cat found in PATH works:
278 278
279 279 $ echo "true.executable=cat" >> .hg/hgrc
280 280 $ beforemerge
281 281 [merge-tools]
282 282 false.whatever=
283 283 true.priority=1
284 284 true.executable=cat
285 285 # hg update -C 1
286 286 $ hg merge -r 2
287 287 merging f
288 288 revision 1
289 289 space
290 290 revision 0
291 291 space
292 292 revision 2
293 293 space
294 294 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
295 295 (branch merge, don't forget to commit)
296 296 $ aftermerge
297 297 # cat f
298 298 revision 1
299 299 space
300 300 # hg stat
301 301 M f
302 302 # hg resolve --list
303 303 R f
304 304
305 305 and true.executable set to cat with path works:
306 306
307 307 $ beforemerge
308 308 [merge-tools]
309 309 false.whatever=
310 310 true.priority=1
311 311 true.executable=cat
312 312 # hg update -C 1
313 313 $ hg merge -r 2 --config merge-tools.true.executable=cat
314 314 merging f
315 315 revision 1
316 316 space
317 317 revision 0
318 318 space
319 319 revision 2
320 320 space
321 321 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
322 322 (branch merge, don't forget to commit)
323 323 $ aftermerge
324 324 # cat f
325 325 revision 1
326 326 space
327 327 # hg stat
328 328 M f
329 329 # hg resolve --list
330 330 R f
331 331
332 executable set to python script that succeeds:
333
334 $ cat > "$TESTTMP/myworkingmerge.py" <<EOF
335 > def myworkingmergefn(ui, repo, args, **kwargs):
336 > return False
337 > EOF
338 $ beforemerge
339 [merge-tools]
340 false.whatever=
341 true.priority=1
342 true.executable=cat
343 # hg update -C 1
344 $ hg merge -r 2 --config merge-tools.true.executable="python:$TESTTMP/myworkingmerge.py:myworkingmergefn"
345 merging f
346 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
347 (branch merge, don't forget to commit)
348 $ aftermerge
349 # cat f
350 revision 1
351 space
352 # hg stat
353 M f
354 # hg resolve --list
355 R f
356
357 executable set to python script that fails:
358
359 $ cat > "$TESTTMP/mybrokenmerge.py" <<EOF
360 > def mybrokenmergefn(ui, repo, args, **kwargs):
361 > ui.write(b"some fail message\n")
362 > return True
363 > EOF
364 $ beforemerge
365 [merge-tools]
366 false.whatever=
367 true.priority=1
368 true.executable=cat
369 # hg update -C 1
370 $ hg merge -r 2 --config merge-tools.true.executable="python:$TESTTMP/mybrokenmerge.py:mybrokenmergefn"
371 merging f
372 some fail message
373 abort: $TESTTMP/mybrokenmerge.py hook failed
374 [255]
375 $ aftermerge
376 # cat f
377 revision 1
378 space
379 # hg stat
380 ? f.orig
381 # hg resolve --list
382 U f
383
384 executable set to python script that is missing function:
385
386 $ beforemerge
387 [merge-tools]
388 false.whatever=
389 true.priority=1
390 true.executable=cat
391 # hg update -C 1
392 $ hg merge -r 2 --config merge-tools.true.executable="python:$TESTTMP/myworkingmerge.py:missingFunction"
393 merging f
394 abort: $TESTTMP/myworkingmerge.py does not have function: missingFunction
395 [255]
396 $ aftermerge
397 # cat f
398 revision 1
399 space
400 # hg stat
401 ? f.orig
402 # hg resolve --list
403 U f
404
405 executable set to missing python script:
406
407 $ beforemerge
408 [merge-tools]
409 false.whatever=
410 true.priority=1
411 true.executable=cat
412 # hg update -C 1
413 $ hg merge -r 2 --config merge-tools.true.executable="python:$TESTTMP/missingpythonscript.py:mergefn"
414 merging f
415 abort: loading python merge script failed: $TESTTMP/missingpythonscript.py
416 [255]
417 $ aftermerge
418 # cat f
419 revision 1
420 space
421 # hg stat
422 ? f.orig
423 # hg resolve --list
424 U f
425
426 executable set to python script but callable function is missing:
427
428 $ beforemerge
429 [merge-tools]
430 false.whatever=
431 true.priority=1
432 true.executable=cat
433 # hg update -C 1
434 $ hg merge -r 2 --config merge-tools.true.executable="python:$TESTTMP/myworkingmerge.py"
435 abort: invalid 'python:' syntax: python:$TESTTMP/myworkingmerge.py
436 [255]
437 $ aftermerge
438 # cat f
439 revision 1
440 space
441 # hg stat
442 # hg resolve --list
443 U f
444
445 executable set to python script but callable function is empty string:
446
447 $ beforemerge
448 [merge-tools]
449 false.whatever=
450 true.priority=1
451 true.executable=cat
452 # hg update -C 1
453 $ hg merge -r 2 --config merge-tools.true.executable="python:$TESTTMP/myworkingmerge.py:"
454 abort: invalid 'python:' syntax: python:$TESTTMP/myworkingmerge.py:
455 [255]
456 $ aftermerge
457 # cat f
458 revision 1
459 space
460 # hg stat
461 # hg resolve --list
462 U f
463
464 executable set to python script but callable function is missing and path contains colon:
465
466 $ beforemerge
467 [merge-tools]
468 false.whatever=
469 true.priority=1
470 true.executable=cat
471 # hg update -C 1
472 $ hg merge -r 2 --config merge-tools.true.executable="python:$TESTTMP/some:dir/myworkingmerge.py"
473 abort: invalid 'python:' syntax: python:$TESTTMP/some:dir/myworkingmerge.py
474 [255]
475 $ aftermerge
476 # cat f
477 revision 1
478 space
479 # hg stat
480 # hg resolve --list
481 U f
482
483 executable set to python script filename that contains spaces:
484
485 $ mkdir -p "$TESTTMP/my path"
486 $ cat > "$TESTTMP/my path/my working merge with spaces in filename.py" <<EOF
487 > def myworkingmergefn(ui, repo, args, **kwargs):
488 > return False
489 > EOF
490 $ beforemerge
491 [merge-tools]
492 false.whatever=
493 true.priority=1
494 true.executable=cat
495 # hg update -C 1
496 $ hg merge -r 2 --config "merge-tools.true.executable=python:$TESTTMP/my path/my working merge with spaces in filename.py:myworkingmergefn"
497 merging f
498 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
499 (branch merge, don't forget to commit)
500 $ aftermerge
501 # cat f
502 revision 1
503 space
504 # hg stat
505 M f
506 # hg resolve --list
507 R f
508
332 509 #if unix-permissions
333 510
334 511 environment variables in true.executable are handled:
335 512
336 513 $ echo 'echo "custom merge tool"' > .hg/merge.sh
337 514 $ beforemerge
338 515 [merge-tools]
339 516 false.whatever=
340 517 true.priority=1
341 518 true.executable=cat
342 519 # hg update -C 1
343 520 $ hg --config merge-tools.true.executable='sh' \
344 521 > --config merge-tools.true.args=.hg/merge.sh \
345 522 > merge -r 2
346 523 merging f
347 524 custom merge tool
348 525 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
349 526 (branch merge, don't forget to commit)
350 527 $ aftermerge
351 528 # cat f
352 529 revision 1
353 530 space
354 531 # hg stat
355 532 M f
356 533 # hg resolve --list
357 534 R f
358 535
359 536 #endif
360 537
361 538 Tool selection and merge-patterns
362 539
363 540 merge-patterns specifies new tool false:
364 541
365 542 $ beforemerge
366 543 [merge-tools]
367 544 false.whatever=
368 545 true.priority=1
369 546 true.executable=cat
370 547 # hg update -C 1
371 548 $ hg merge -r 2 --config merge-patterns.f=false
372 549 merging f
373 550 merging f failed!
374 551 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
375 552 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
376 553 [1]
377 554 $ aftermerge
378 555 # cat f
379 556 revision 1
380 557 space
381 558 # hg stat
382 559 M f
383 560 ? f.orig
384 561 # hg resolve --list
385 562 U f
386 563
387 564 merge-patterns specifies executable not found in PATH and gets warning:
388 565
389 566 $ beforemerge
390 567 [merge-tools]
391 568 false.whatever=
392 569 true.priority=1
393 570 true.executable=cat
394 571 # hg update -C 1
395 572 $ hg merge -r 2 --config merge-patterns.f=true --config merge-tools.true.executable=nonexistentmergetool
396 573 couldn't find merge tool true (for pattern f)
397 574 merging f
398 575 couldn't find merge tool true (for pattern f)
399 576 merging f failed!
400 577 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
401 578 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
402 579 [1]
403 580 $ aftermerge
404 581 # cat f
405 582 revision 1
406 583 space
407 584 # hg stat
408 585 M f
409 586 ? f.orig
410 587 # hg resolve --list
411 588 U f
412 589
413 590 merge-patterns specifies executable with bogus path and gets warning:
414 591
415 592 $ beforemerge
416 593 [merge-tools]
417 594 false.whatever=
418 595 true.priority=1
419 596 true.executable=cat
420 597 # hg update -C 1
421 598 $ hg merge -r 2 --config merge-patterns.f=true --config merge-tools.true.executable=/nonexistent/mergetool
422 599 couldn't find merge tool true (for pattern f)
423 600 merging f
424 601 couldn't find merge tool true (for pattern f)
425 602 merging f failed!
426 603 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
427 604 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
428 605 [1]
429 606 $ aftermerge
430 607 # cat f
431 608 revision 1
432 609 space
433 610 # hg stat
434 611 M f
435 612 ? f.orig
436 613 # hg resolve --list
437 614 U f
438 615
439 616 ui.merge overrules priority
440 617
441 618 ui.merge specifies false:
442 619
443 620 $ beforemerge
444 621 [merge-tools]
445 622 false.whatever=
446 623 true.priority=1
447 624 true.executable=cat
448 625 # hg update -C 1
449 626 $ hg merge -r 2 --config ui.merge=false
450 627 merging f
451 628 merging f failed!
452 629 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
453 630 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
454 631 [1]
455 632 $ aftermerge
456 633 # cat f
457 634 revision 1
458 635 space
459 636 # hg stat
460 637 M f
461 638 ? f.orig
462 639 # hg resolve --list
463 640 U f
464 641
465 642 ui.merge specifies internal:fail:
466 643
467 644 $ beforemerge
468 645 [merge-tools]
469 646 false.whatever=
470 647 true.priority=1
471 648 true.executable=cat
472 649 # hg update -C 1
473 650 $ hg merge -r 2 --config ui.merge=internal:fail
474 651 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
475 652 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
476 653 [1]
477 654 $ aftermerge
478 655 # cat f
479 656 revision 1
480 657 space
481 658 # hg stat
482 659 M f
483 660 # hg resolve --list
484 661 U f
485 662
486 663 ui.merge specifies :local (without internal prefix):
487 664
488 665 $ beforemerge
489 666 [merge-tools]
490 667 false.whatever=
491 668 true.priority=1
492 669 true.executable=cat
493 670 # hg update -C 1
494 671 $ hg merge -r 2 --config ui.merge=:local
495 672 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
496 673 (branch merge, don't forget to commit)
497 674 $ aftermerge
498 675 # cat f
499 676 revision 1
500 677 space
501 678 # hg stat
502 679 M f
503 680 # hg resolve --list
504 681 R f
505 682
506 683 ui.merge specifies internal:other:
507 684
508 685 $ beforemerge
509 686 [merge-tools]
510 687 false.whatever=
511 688 true.priority=1
512 689 true.executable=cat
513 690 # hg update -C 1
514 691 $ hg merge -r 2 --config ui.merge=internal:other
515 692 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
516 693 (branch merge, don't forget to commit)
517 694 $ aftermerge
518 695 # cat f
519 696 revision 2
520 697 space
521 698 # hg stat
522 699 M f
523 700 # hg resolve --list
524 701 R f
525 702
526 703 ui.merge specifies internal:prompt:
527 704
528 705 $ beforemerge
529 706 [merge-tools]
530 707 false.whatever=
531 708 true.priority=1
532 709 true.executable=cat
533 710 # hg update -C 1
534 711 $ hg merge -r 2 --config ui.merge=internal:prompt
535 712 keep (l)ocal [working copy], take (o)ther [merge rev], or leave (u)nresolved for f? u
536 713 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
537 714 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
538 715 [1]
539 716 $ aftermerge
540 717 # cat f
541 718 revision 1
542 719 space
543 720 # hg stat
544 721 M f
545 722 # hg resolve --list
546 723 U f
547 724
548 725 ui.merge specifies :prompt, with 'leave unresolved' chosen
549 726
550 727 $ beforemerge
551 728 [merge-tools]
552 729 false.whatever=
553 730 true.priority=1
554 731 true.executable=cat
555 732 # hg update -C 1
556 733 $ hg merge -r 2 --config ui.merge=:prompt --config ui.interactive=True << EOF
557 734 > u
558 735 > EOF
559 736 keep (l)ocal [working copy], take (o)ther [merge rev], or leave (u)nresolved for f? u
560 737 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
561 738 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
562 739 [1]
563 740 $ aftermerge
564 741 # cat f
565 742 revision 1
566 743 space
567 744 # hg stat
568 745 M f
569 746 # hg resolve --list
570 747 U f
571 748
572 749 prompt with EOF
573 750
574 751 $ beforemerge
575 752 [merge-tools]
576 753 false.whatever=
577 754 true.priority=1
578 755 true.executable=cat
579 756 # hg update -C 1
580 757 $ hg merge -r 2 --config ui.merge=internal:prompt --config ui.interactive=true
581 758 keep (l)ocal [working copy], take (o)ther [merge rev], or leave (u)nresolved for f?
582 759 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
583 760 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
584 761 [1]
585 762 $ aftermerge
586 763 # cat f
587 764 revision 1
588 765 space
589 766 # hg stat
590 767 M f
591 768 # hg resolve --list
592 769 U f
593 770 $ hg resolve --all --config ui.merge=internal:prompt --config ui.interactive=true
594 771 keep (l)ocal [working copy], take (o)ther [merge rev], or leave (u)nresolved for f?
595 772 [1]
596 773 $ aftermerge
597 774 # cat f
598 775 revision 1
599 776 space
600 777 # hg stat
601 778 M f
602 779 ? f.orig
603 780 # hg resolve --list
604 781 U f
605 782 $ rm f
606 783 $ hg resolve --all --config ui.merge=internal:prompt --config ui.interactive=true
607 784 keep (l)ocal [working copy], take (o)ther [merge rev], or leave (u)nresolved for f?
608 785 [1]
609 786 $ aftermerge
610 787 # cat f
611 788 revision 1
612 789 space
613 790 # hg stat
614 791 M f
615 792 # hg resolve --list
616 793 U f
617 794 $ hg resolve --all --config ui.merge=internal:prompt
618 795 keep (l)ocal [working copy], take (o)ther [merge rev], or leave (u)nresolved for f? u
619 796 [1]
620 797 $ aftermerge
621 798 # cat f
622 799 revision 1
623 800 space
624 801 # hg stat
625 802 M f
626 803 ? f.orig
627 804 # hg resolve --list
628 805 U f
629 806
630 807 ui.merge specifies internal:dump:
631 808
632 809 $ beforemerge
633 810 [merge-tools]
634 811 false.whatever=
635 812 true.priority=1
636 813 true.executable=cat
637 814 # hg update -C 1
638 815 $ hg merge -r 2 --config ui.merge=internal:dump
639 816 merging f
640 817 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
641 818 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
642 819 [1]
643 820 $ aftermerge
644 821 # cat f
645 822 revision 1
646 823 space
647 824 # hg stat
648 825 M f
649 826 ? f.base
650 827 ? f.local
651 828 ? f.orig
652 829 ? f.other
653 830 # hg resolve --list
654 831 U f
655 832
656 833 f.base:
657 834
658 835 $ cat f.base
659 836 revision 0
660 837 space
661 838
662 839 f.local:
663 840
664 841 $ cat f.local
665 842 revision 1
666 843 space
667 844
668 845 f.other:
669 846
670 847 $ cat f.other
671 848 revision 2
672 849 space
673 850 $ rm f.base f.local f.other
674 851
675 852 check that internal:dump doesn't dump files if premerge runs
676 853 successfully
677 854
678 855 $ beforemerge
679 856 [merge-tools]
680 857 false.whatever=
681 858 true.priority=1
682 859 true.executable=cat
683 860 # hg update -C 1
684 861 $ hg merge -r 3 --config ui.merge=internal:dump
685 862 merging f
686 863 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
687 864 (branch merge, don't forget to commit)
688 865
689 866 $ aftermerge
690 867 # cat f
691 868 revision 1
692 869 space
693 870 revision 3
694 871 # hg stat
695 872 M f
696 873 # hg resolve --list
697 874 R f
698 875
699 876 check that internal:forcedump dumps files, even if local and other can
700 877 be merged easily
701 878
702 879 $ beforemerge
703 880 [merge-tools]
704 881 false.whatever=
705 882 true.priority=1
706 883 true.executable=cat
707 884 # hg update -C 1
708 885 $ hg merge -r 3 --config ui.merge=internal:forcedump
709 886 merging f
710 887 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
711 888 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
712 889 [1]
713 890 $ aftermerge
714 891 # cat f
715 892 revision 1
716 893 space
717 894 # hg stat
718 895 M f
719 896 ? f.base
720 897 ? f.local
721 898 ? f.orig
722 899 ? f.other
723 900 # hg resolve --list
724 901 U f
725 902
726 903 $ cat f.base
727 904 revision 0
728 905 space
729 906
730 907 $ cat f.local
731 908 revision 1
732 909 space
733 910
734 911 $ cat f.other
735 912 revision 0
736 913 space
737 914 revision 3
738 915
739 916 $ rm -f f.base f.local f.other
740 917
741 918 ui.merge specifies internal:other but is overruled by pattern for false:
742 919
743 920 $ beforemerge
744 921 [merge-tools]
745 922 false.whatever=
746 923 true.priority=1
747 924 true.executable=cat
748 925 # hg update -C 1
749 926 $ hg merge -r 2 --config ui.merge=internal:other --config merge-patterns.f=false
750 927 merging f
751 928 merging f failed!
752 929 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
753 930 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
754 931 [1]
755 932 $ aftermerge
756 933 # cat f
757 934 revision 1
758 935 space
759 936 # hg stat
760 937 M f
761 938 ? f.orig
762 939 # hg resolve --list
763 940 U f
764 941
765 942 Premerge
766 943
767 944 ui.merge specifies internal:other but is overruled by --tool=false
768 945
769 946 $ beforemerge
770 947 [merge-tools]
771 948 false.whatever=
772 949 true.priority=1
773 950 true.executable=cat
774 951 # hg update -C 1
775 952 $ hg merge -r 2 --config ui.merge=internal:other --tool=false
776 953 merging f
777 954 merging f failed!
778 955 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
779 956 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
780 957 [1]
781 958 $ aftermerge
782 959 # cat f
783 960 revision 1
784 961 space
785 962 # hg stat
786 963 M f
787 964 ? f.orig
788 965 # hg resolve --list
789 966 U f
790 967
791 968 HGMERGE specifies internal:other but is overruled by --tool=false
792 969
793 970 $ HGMERGE=internal:other ; export HGMERGE
794 971 $ beforemerge
795 972 [merge-tools]
796 973 false.whatever=
797 974 true.priority=1
798 975 true.executable=cat
799 976 # hg update -C 1
800 977 $ hg merge -r 2 --tool=false
801 978 merging f
802 979 merging f failed!
803 980 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
804 981 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
805 982 [1]
806 983 $ aftermerge
807 984 # cat f
808 985 revision 1
809 986 space
810 987 # hg stat
811 988 M f
812 989 ? f.orig
813 990 # hg resolve --list
814 991 U f
815 992
816 993 $ unset HGMERGE # make sure HGMERGE doesn't interfere with remaining tests
817 994
818 995 update is a merge ...
819 996
820 997 (this also tests that files reverted with '--rev REV' are treated as
821 998 "modified", even if none of mode, size and timestamp of them isn't
822 999 changed on the filesystem (see also issue4583))
823 1000
824 1001 $ cat >> $HGRCPATH <<EOF
825 1002 > [fakedirstatewritetime]
826 1003 > # emulate invoking dirstate.write() via repo.status()
827 1004 > # at 2000-01-01 00:00
828 1005 > fakenow = 200001010000
829 1006 > EOF
830 1007
831 1008 $ beforemerge
832 1009 [merge-tools]
833 1010 false.whatever=
834 1011 true.priority=1
835 1012 true.executable=cat
836 1013 # hg update -C 1
837 1014 $ hg update -q 0
838 1015 $ f -s f
839 1016 f: size=17
840 1017 $ touch -t 200001010000 f
841 1018 $ hg debugrebuildstate
842 1019 $ cat >> $HGRCPATH <<EOF
843 1020 > [extensions]
844 1021 > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py
845 1022 > EOF
846 1023 $ hg revert -q -r 1 .
847 1024 $ cat >> $HGRCPATH <<EOF
848 1025 > [extensions]
849 1026 > fakedirstatewritetime = !
850 1027 > EOF
851 1028 $ f -s f
852 1029 f: size=17
853 1030 $ touch -t 200001010000 f
854 1031 $ hg status f
855 1032 M f
856 1033 $ hg update -r 2
857 1034 merging f
858 1035 revision 1
859 1036 space
860 1037 revision 0
861 1038 space
862 1039 revision 2
863 1040 space
864 1041 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
865 1042 $ aftermerge
866 1043 # cat f
867 1044 revision 1
868 1045 space
869 1046 # hg stat
870 1047 M f
871 1048 # hg resolve --list
872 1049 R f
873 1050
874 1051 update should also have --tool
875 1052
876 1053 $ beforemerge
877 1054 [merge-tools]
878 1055 false.whatever=
879 1056 true.priority=1
880 1057 true.executable=cat
881 1058 # hg update -C 1
882 1059 $ hg update -q 0
883 1060 $ f -s f
884 1061 f: size=17
885 1062 $ touch -t 200001010000 f
886 1063 $ hg debugrebuildstate
887 1064 $ cat >> $HGRCPATH <<EOF
888 1065 > [extensions]
889 1066 > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py
890 1067 > EOF
891 1068 $ hg revert -q -r 1 .
892 1069 $ cat >> $HGRCPATH <<EOF
893 1070 > [extensions]
894 1071 > fakedirstatewritetime = !
895 1072 > EOF
896 1073 $ f -s f
897 1074 f: size=17
898 1075 $ touch -t 200001010000 f
899 1076 $ hg status f
900 1077 M f
901 1078 $ hg update -r 2 --tool false
902 1079 merging f
903 1080 merging f failed!
904 1081 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
905 1082 use 'hg resolve' to retry unresolved file merges
906 1083 [1]
907 1084 $ aftermerge
908 1085 # cat f
909 1086 revision 1
910 1087 space
911 1088 # hg stat
912 1089 M f
913 1090 ? f.orig
914 1091 # hg resolve --list
915 1092 U f
916 1093
917 1094 Default is silent simplemerge:
918 1095
919 1096 $ beforemerge
920 1097 [merge-tools]
921 1098 false.whatever=
922 1099 true.priority=1
923 1100 true.executable=cat
924 1101 # hg update -C 1
925 1102 $ hg merge -r 3
926 1103 merging f
927 1104 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
928 1105 (branch merge, don't forget to commit)
929 1106 $ aftermerge
930 1107 # cat f
931 1108 revision 1
932 1109 space
933 1110 revision 3
934 1111 # hg stat
935 1112 M f
936 1113 # hg resolve --list
937 1114 R f
938 1115
939 1116 .premerge=True is same:
940 1117
941 1118 $ beforemerge
942 1119 [merge-tools]
943 1120 false.whatever=
944 1121 true.priority=1
945 1122 true.executable=cat
946 1123 # hg update -C 1
947 1124 $ hg merge -r 3 --config merge-tools.true.premerge=True
948 1125 merging f
949 1126 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
950 1127 (branch merge, don't forget to commit)
951 1128 $ aftermerge
952 1129 # cat f
953 1130 revision 1
954 1131 space
955 1132 revision 3
956 1133 # hg stat
957 1134 M f
958 1135 # hg resolve --list
959 1136 R f
960 1137
961 1138 .premerge=False executes merge-tool:
962 1139
963 1140 $ beforemerge
964 1141 [merge-tools]
965 1142 false.whatever=
966 1143 true.priority=1
967 1144 true.executable=cat
968 1145 # hg update -C 1
969 1146 $ hg merge -r 3 --config merge-tools.true.premerge=False
970 1147 merging f
971 1148 revision 1
972 1149 space
973 1150 revision 0
974 1151 space
975 1152 revision 0
976 1153 space
977 1154 revision 3
978 1155 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
979 1156 (branch merge, don't forget to commit)
980 1157 $ aftermerge
981 1158 # cat f
982 1159 revision 1
983 1160 space
984 1161 # hg stat
985 1162 M f
986 1163 # hg resolve --list
987 1164 R f
988 1165
989 1166 premerge=keep keeps conflict markers in:
990 1167
991 1168 $ beforemerge
992 1169 [merge-tools]
993 1170 false.whatever=
994 1171 true.priority=1
995 1172 true.executable=cat
996 1173 # hg update -C 1
997 1174 $ hg merge -r 4 --config merge-tools.true.premerge=keep
998 1175 merging f
999 1176 <<<<<<< working copy: ef83787e2614 - test: revision 1
1000 1177 revision 1
1001 1178 space
1002 1179 =======
1003 1180 revision 4
1004 1181 >>>>>>> merge rev: 81448d39c9a0 - test: revision 4
1005 1182 revision 0
1006 1183 space
1007 1184 revision 4
1008 1185 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1009 1186 (branch merge, don't forget to commit)
1010 1187 $ aftermerge
1011 1188 # cat f
1012 1189 <<<<<<< working copy: ef83787e2614 - test: revision 1
1013 1190 revision 1
1014 1191 space
1015 1192 =======
1016 1193 revision 4
1017 1194 >>>>>>> merge rev: 81448d39c9a0 - test: revision 4
1018 1195 # hg stat
1019 1196 M f
1020 1197 # hg resolve --list
1021 1198 R f
1022 1199
1023 1200 premerge=keep-merge3 keeps conflict markers with base content:
1024 1201
1025 1202 $ beforemerge
1026 1203 [merge-tools]
1027 1204 false.whatever=
1028 1205 true.priority=1
1029 1206 true.executable=cat
1030 1207 # hg update -C 1
1031 1208 $ hg merge -r 4 --config merge-tools.true.premerge=keep-merge3
1032 1209 merging f
1033 1210 <<<<<<< working copy: ef83787e2614 - test: revision 1
1034 1211 revision 1
1035 1212 space
1036 1213 ||||||| base
1037 1214 revision 0
1038 1215 space
1039 1216 =======
1040 1217 revision 4
1041 1218 >>>>>>> merge rev: 81448d39c9a0 - test: revision 4
1042 1219 revision 0
1043 1220 space
1044 1221 revision 4
1045 1222 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1046 1223 (branch merge, don't forget to commit)
1047 1224 $ aftermerge
1048 1225 # cat f
1049 1226 <<<<<<< working copy: ef83787e2614 - test: revision 1
1050 1227 revision 1
1051 1228 space
1052 1229 ||||||| base
1053 1230 revision 0
1054 1231 space
1055 1232 =======
1056 1233 revision 4
1057 1234 >>>>>>> merge rev: 81448d39c9a0 - test: revision 4
1058 1235 # hg stat
1059 1236 M f
1060 1237 # hg resolve --list
1061 1238 R f
1062 1239
1063 1240 premerge=keep respects ui.mergemarkers=basic:
1064 1241
1065 1242 $ beforemerge
1066 1243 [merge-tools]
1067 1244 false.whatever=
1068 1245 true.priority=1
1069 1246 true.executable=cat
1070 1247 # hg update -C 1
1071 1248 $ hg merge -r 4 --config merge-tools.true.premerge=keep --config ui.mergemarkers=basic
1072 1249 merging f
1073 1250 <<<<<<< working copy
1074 1251 revision 1
1075 1252 space
1076 1253 =======
1077 1254 revision 4
1078 1255 >>>>>>> merge rev
1079 1256 revision 0
1080 1257 space
1081 1258 revision 4
1082 1259 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1083 1260 (branch merge, don't forget to commit)
1084 1261 $ aftermerge
1085 1262 # cat f
1086 1263 <<<<<<< working copy
1087 1264 revision 1
1088 1265 space
1089 1266 =======
1090 1267 revision 4
1091 1268 >>>>>>> merge rev
1092 1269 # hg stat
1093 1270 M f
1094 1271 # hg resolve --list
1095 1272 R f
1096 1273
1097 1274 premerge=keep ignores ui.mergemarkers=basic if true.mergemarkers=detailed:
1098 1275
1099 1276 $ beforemerge
1100 1277 [merge-tools]
1101 1278 false.whatever=
1102 1279 true.priority=1
1103 1280 true.executable=cat
1104 1281 # hg update -C 1
1105 1282 $ hg merge -r 4 --config merge-tools.true.premerge=keep \
1106 1283 > --config ui.mergemarkers=basic \
1107 1284 > --config merge-tools.true.mergemarkers=detailed
1108 1285 merging f
1109 1286 <<<<<<< working copy: ef83787e2614 - test: revision 1
1110 1287 revision 1
1111 1288 space
1112 1289 =======
1113 1290 revision 4
1114 1291 >>>>>>> merge rev: 81448d39c9a0 - test: revision 4
1115 1292 revision 0
1116 1293 space
1117 1294 revision 4
1118 1295 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1119 1296 (branch merge, don't forget to commit)
1120 1297 $ aftermerge
1121 1298 # cat f
1122 1299 <<<<<<< working copy: ef83787e2614 - test: revision 1
1123 1300 revision 1
1124 1301 space
1125 1302 =======
1126 1303 revision 4
1127 1304 >>>>>>> merge rev: 81448d39c9a0 - test: revision 4
1128 1305 # hg stat
1129 1306 M f
1130 1307 # hg resolve --list
1131 1308 R f
1132 1309
1133 1310 premerge=keep respects ui.mergemarkertemplate instead of
1134 1311 true.mergemarkertemplate if true.mergemarkers=basic:
1135 1312
1136 1313 $ beforemerge
1137 1314 [merge-tools]
1138 1315 false.whatever=
1139 1316 true.priority=1
1140 1317 true.executable=cat
1141 1318 # hg update -C 1
1142 1319 $ hg merge -r 4 --config merge-tools.true.premerge=keep \
1143 1320 > --config ui.mergemarkertemplate='uitmpl {rev}' \
1144 1321 > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}'
1145 1322 merging f
1146 1323 <<<<<<< working copy: uitmpl 1
1147 1324 revision 1
1148 1325 space
1149 1326 =======
1150 1327 revision 4
1151 1328 >>>>>>> merge rev: uitmpl 4
1152 1329 revision 0
1153 1330 space
1154 1331 revision 4
1155 1332 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1156 1333 (branch merge, don't forget to commit)
1157 1334 $ aftermerge
1158 1335 # cat f
1159 1336 <<<<<<< working copy: uitmpl 1
1160 1337 revision 1
1161 1338 space
1162 1339 =======
1163 1340 revision 4
1164 1341 >>>>>>> merge rev: uitmpl 4
1165 1342 # hg stat
1166 1343 M f
1167 1344 # hg resolve --list
1168 1345 R f
1169 1346
1170 1347 premerge=keep respects true.mergemarkertemplate instead of
1171 1348 true.mergemarkertemplate if true.mergemarkers=detailed:
1172 1349
1173 1350 $ beforemerge
1174 1351 [merge-tools]
1175 1352 false.whatever=
1176 1353 true.priority=1
1177 1354 true.executable=cat
1178 1355 # hg update -C 1
1179 1356 $ hg merge -r 4 --config merge-tools.true.premerge=keep \
1180 1357 > --config ui.mergemarkertemplate='uitmpl {rev}' \
1181 1358 > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}' \
1182 1359 > --config merge-tools.true.mergemarkers=detailed
1183 1360 merging f
1184 1361 <<<<<<< working copy: tooltmpl ef83787e2614
1185 1362 revision 1
1186 1363 space
1187 1364 =======
1188 1365 revision 4
1189 1366 >>>>>>> merge rev: tooltmpl 81448d39c9a0
1190 1367 revision 0
1191 1368 space
1192 1369 revision 4
1193 1370 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1194 1371 (branch merge, don't forget to commit)
1195 1372 $ aftermerge
1196 1373 # cat f
1197 1374 <<<<<<< working copy: tooltmpl ef83787e2614
1198 1375 revision 1
1199 1376 space
1200 1377 =======
1201 1378 revision 4
1202 1379 >>>>>>> merge rev: tooltmpl 81448d39c9a0
1203 1380 # hg stat
1204 1381 M f
1205 1382 # hg resolve --list
1206 1383 R f
1207 1384
1208 1385 Tool execution
1209 1386
1210 1387 set tools.args explicit to include $base $local $other $output:
1211 1388
1212 1389 $ beforemerge
1213 1390 [merge-tools]
1214 1391 false.whatever=
1215 1392 true.priority=1
1216 1393 true.executable=cat
1217 1394 # hg update -C 1
1218 1395 $ hg merge -r 2 --config merge-tools.true.executable=head --config merge-tools.true.args='$base $local $other $output' \
1219 1396 > | sed 's,==> .* <==,==> ... <==,g'
1220 1397 merging f
1221 1398 ==> ... <==
1222 1399 revision 0
1223 1400 space
1224 1401
1225 1402 ==> ... <==
1226 1403 revision 1
1227 1404 space
1228 1405
1229 1406 ==> ... <==
1230 1407 revision 2
1231 1408 space
1232 1409
1233 1410 ==> ... <==
1234 1411 revision 1
1235 1412 space
1236 1413 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1237 1414 (branch merge, don't forget to commit)
1238 1415 $ aftermerge
1239 1416 # cat f
1240 1417 revision 1
1241 1418 space
1242 1419 # hg stat
1243 1420 M f
1244 1421 # hg resolve --list
1245 1422 R f
1246 1423
1247 1424 Merge with "echo mergeresult > $local":
1248 1425
1249 1426 $ beforemerge
1250 1427 [merge-tools]
1251 1428 false.whatever=
1252 1429 true.priority=1
1253 1430 true.executable=cat
1254 1431 # hg update -C 1
1255 1432 $ hg merge -r 2 --config merge-tools.true.executable=echo --config merge-tools.true.args='mergeresult > $local'
1256 1433 merging f
1257 1434 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1258 1435 (branch merge, don't forget to commit)
1259 1436 $ aftermerge
1260 1437 # cat f
1261 1438 mergeresult
1262 1439 # hg stat
1263 1440 M f
1264 1441 # hg resolve --list
1265 1442 R f
1266 1443
1267 1444 - and $local is the file f:
1268 1445
1269 1446 $ beforemerge
1270 1447 [merge-tools]
1271 1448 false.whatever=
1272 1449 true.priority=1
1273 1450 true.executable=cat
1274 1451 # hg update -C 1
1275 1452 $ hg merge -r 2 --config merge-tools.true.executable=echo --config merge-tools.true.args='mergeresult > f'
1276 1453 merging f
1277 1454 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1278 1455 (branch merge, don't forget to commit)
1279 1456 $ aftermerge
1280 1457 # cat f
1281 1458 mergeresult
1282 1459 # hg stat
1283 1460 M f
1284 1461 # hg resolve --list
1285 1462 R f
1286 1463
1287 1464 Merge with "echo mergeresult > $output" - the variable is a bit magic:
1288 1465
1289 1466 $ beforemerge
1290 1467 [merge-tools]
1291 1468 false.whatever=
1292 1469 true.priority=1
1293 1470 true.executable=cat
1294 1471 # hg update -C 1
1295 1472 $ hg merge -r 2 --config merge-tools.true.executable=echo --config merge-tools.true.args='mergeresult > $output'
1296 1473 merging f
1297 1474 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1298 1475 (branch merge, don't forget to commit)
1299 1476 $ aftermerge
1300 1477 # cat f
1301 1478 mergeresult
1302 1479 # hg stat
1303 1480 M f
1304 1481 # hg resolve --list
1305 1482 R f
1306 1483
1307 1484 Merge using tool with a path that must be quoted:
1308 1485
1309 1486 $ beforemerge
1310 1487 [merge-tools]
1311 1488 false.whatever=
1312 1489 true.priority=1
1313 1490 true.executable=cat
1314 1491 # hg update -C 1
1315 1492 $ cat <<EOF > 'my merge tool'
1316 1493 > cat "\$1" "\$2" "\$3" > "\$4"
1317 1494 > EOF
1318 1495 $ hg --config merge-tools.true.executable='sh' \
1319 1496 > --config merge-tools.true.args='"./my merge tool" $base $local $other $output' \
1320 1497 > merge -r 2
1321 1498 merging f
1322 1499 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1323 1500 (branch merge, don't forget to commit)
1324 1501 $ rm -f 'my merge tool'
1325 1502 $ aftermerge
1326 1503 # cat f
1327 1504 revision 0
1328 1505 space
1329 1506 revision 1
1330 1507 space
1331 1508 revision 2
1332 1509 space
1333 1510 # hg stat
1334 1511 M f
1335 1512 # hg resolve --list
1336 1513 R f
1337 1514
1338 1515 Merge using a tool that supports labellocal, labelother, and labelbase, checking
1339 1516 that they're quoted properly as well. This is using the default 'basic'
1340 1517 mergemarkers even though ui.mergemarkers is 'detailed', so it's ignoring both
1341 1518 mergemarkertemplate settings:
1342 1519
1343 1520 $ beforemerge
1344 1521 [merge-tools]
1345 1522 false.whatever=
1346 1523 true.priority=1
1347 1524 true.executable=cat
1348 1525 # hg update -C 1
1349 1526 $ cat <<EOF > printargs_merge_tool
1350 1527 > while test \$# -gt 0; do echo arg: \"\$1\"; shift; done
1351 1528 > EOF
1352 1529 $ hg --config merge-tools.true.executable='sh' \
1353 1530 > --config merge-tools.true.args='./printargs_merge_tool ll:$labellocal lo: $labelother lb:$labelbase": "$base' \
1354 1531 > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}' \
1355 1532 > --config ui.mergemarkertemplate='uitmpl {rev}' \
1356 1533 > --config ui.mergemarkers=detailed \
1357 1534 > merge -r 2
1358 1535 merging f
1359 1536 arg: "ll:working copy"
1360 1537 arg: "lo:"
1361 1538 arg: "merge rev"
1362 1539 arg: "lb:base: */f~base.*" (glob)
1363 1540 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1364 1541 (branch merge, don't forget to commit)
1365 1542 $ rm -f 'printargs_merge_tool'
1366 1543
1367 1544 Same test with experimental.mergetempdirprefix set:
1368 1545
1369 1546 $ beforemerge
1370 1547 [merge-tools]
1371 1548 false.whatever=
1372 1549 true.priority=1
1373 1550 true.executable=cat
1374 1551 # hg update -C 1
1375 1552 $ cat <<EOF > printargs_merge_tool
1376 1553 > while test \$# -gt 0; do echo arg: \"\$1\"; shift; done
1377 1554 > EOF
1378 1555 $ hg --config experimental.mergetempdirprefix=$TESTTMP/hgmerge. \
1379 1556 > --config merge-tools.true.executable='sh' \
1380 1557 > --config merge-tools.true.args='./printargs_merge_tool ll:$labellocal lo: $labelother lb:$labelbase": "$base' \
1381 1558 > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}' \
1382 1559 > --config ui.mergemarkertemplate='uitmpl {rev}' \
1383 1560 > --config ui.mergemarkers=detailed \
1384 1561 > merge -r 2
1385 1562 merging f
1386 1563 arg: "ll:working copy"
1387 1564 arg: "lo:"
1388 1565 arg: "merge rev"
1389 1566 arg: "lb:base: */hgmerge.*/f~base" (glob)
1390 1567 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1391 1568 (branch merge, don't forget to commit)
1392 1569 $ rm -f 'printargs_merge_tool'
1393 1570
1394 1571 Merge using a tool that supports labellocal, labelother, and labelbase, checking
1395 1572 that they're quoted properly as well. This is using 'detailed' mergemarkers,
1396 1573 even though ui.mergemarkers is 'basic', and using the tool's
1397 1574 mergemarkertemplate:
1398 1575
1399 1576 $ beforemerge
1400 1577 [merge-tools]
1401 1578 false.whatever=
1402 1579 true.priority=1
1403 1580 true.executable=cat
1404 1581 # hg update -C 1
1405 1582 $ cat <<EOF > printargs_merge_tool
1406 1583 > while test \$# -gt 0; do echo arg: \"\$1\"; shift; done
1407 1584 > EOF
1408 1585 $ hg --config merge-tools.true.executable='sh' \
1409 1586 > --config merge-tools.true.args='./printargs_merge_tool ll:$labellocal lo: $labelother lb:$labelbase": "$base' \
1410 1587 > --config merge-tools.true.mergemarkers=detailed \
1411 1588 > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}' \
1412 1589 > --config ui.mergemarkertemplate='uitmpl {rev}' \
1413 1590 > --config ui.mergemarkers=basic \
1414 1591 > merge -r 2
1415 1592 merging f
1416 1593 arg: "ll:working copy: tooltmpl ef83787e2614"
1417 1594 arg: "lo:"
1418 1595 arg: "merge rev: tooltmpl 0185f4e0cf02"
1419 1596 arg: "lb:base: */f~base.*" (glob)
1420 1597 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1421 1598 (branch merge, don't forget to commit)
1422 1599 $ rm -f 'printargs_merge_tool'
1423 1600
1424 1601 The merge tool still gets labellocal and labelother as 'basic' even when
1425 1602 premerge=keep is used and has 'detailed' markers:
1426 1603
1427 1604 $ beforemerge
1428 1605 [merge-tools]
1429 1606 false.whatever=
1430 1607 true.priority=1
1431 1608 true.executable=cat
1432 1609 # hg update -C 1
1433 1610 $ cat <<EOF > mytool
1434 1611 > echo labellocal: \"\$1\"
1435 1612 > echo labelother: \"\$2\"
1436 1613 > echo "output (arg)": \"\$3\"
1437 1614 > echo "output (contents)":
1438 1615 > cat "\$3"
1439 1616 > EOF
1440 1617 $ hg --config merge-tools.true.executable='sh' \
1441 1618 > --config merge-tools.true.args='mytool $labellocal $labelother $output' \
1442 1619 > --config merge-tools.true.premerge=keep \
1443 1620 > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}' \
1444 1621 > --config ui.mergemarkertemplate='uitmpl {rev}' \
1445 1622 > --config ui.mergemarkers=detailed \
1446 1623 > merge -r 2
1447 1624 merging f
1448 1625 labellocal: "working copy"
1449 1626 labelother: "merge rev"
1450 1627 output (arg): "$TESTTMP/repo/f"
1451 1628 output (contents):
1452 1629 <<<<<<< working copy: uitmpl 1
1453 1630 revision 1
1454 1631 =======
1455 1632 revision 2
1456 1633 >>>>>>> merge rev: uitmpl 2
1457 1634 space
1458 1635 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1459 1636 (branch merge, don't forget to commit)
1460 1637 $ rm -f 'mytool'
1461 1638
1462 1639 premerge=keep uses the *tool's* mergemarkertemplate if tool's
1463 1640 mergemarkers=detailed; labellocal and labelother also use the tool's template
1464 1641
1465 1642 $ beforemerge
1466 1643 [merge-tools]
1467 1644 false.whatever=
1468 1645 true.priority=1
1469 1646 true.executable=cat
1470 1647 # hg update -C 1
1471 1648 $ cat <<EOF > mytool
1472 1649 > echo labellocal: \"\$1\"
1473 1650 > echo labelother: \"\$2\"
1474 1651 > echo "output (arg)": \"\$3\"
1475 1652 > echo "output (contents)":
1476 1653 > cat "\$3"
1477 1654 > EOF
1478 1655 $ hg --config merge-tools.true.executable='sh' \
1479 1656 > --config merge-tools.true.args='mytool $labellocal $labelother $output' \
1480 1657 > --config merge-tools.true.premerge=keep \
1481 1658 > --config merge-tools.true.mergemarkers=detailed \
1482 1659 > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}' \
1483 1660 > --config ui.mergemarkertemplate='uitmpl {rev}' \
1484 1661 > --config ui.mergemarkers=detailed \
1485 1662 > merge -r 2
1486 1663 merging f
1487 1664 labellocal: "working copy: tooltmpl ef83787e2614"
1488 1665 labelother: "merge rev: tooltmpl 0185f4e0cf02"
1489 1666 output (arg): "$TESTTMP/repo/f"
1490 1667 output (contents):
1491 1668 <<<<<<< working copy: tooltmpl ef83787e2614
1492 1669 revision 1
1493 1670 =======
1494 1671 revision 2
1495 1672 >>>>>>> merge rev: tooltmpl 0185f4e0cf02
1496 1673 space
1497 1674 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1498 1675 (branch merge, don't forget to commit)
1499 1676 $ rm -f 'mytool'
1500 1677
1501 1678 Issue3581: Merging a filename that needs to be quoted
1502 1679 (This test doesn't work on Windows filesystems even on Linux, so check
1503 1680 for Unix-like permission)
1504 1681
1505 1682 #if unix-permissions
1506 1683 $ beforemerge
1507 1684 [merge-tools]
1508 1685 false.whatever=
1509 1686 true.priority=1
1510 1687 true.executable=cat
1511 1688 # hg update -C 1
1512 1689 $ echo "revision 5" > '"; exit 1; echo "'
1513 1690 $ hg commit -Am "revision 5"
1514 1691 adding "; exit 1; echo "
1515 1692 warning: filename contains '"', which is reserved on Windows: '"; exit 1; echo "'
1516 1693 $ hg update -C 1 > /dev/null
1517 1694 $ echo "revision 6" > '"; exit 1; echo "'
1518 1695 $ hg commit -Am "revision 6"
1519 1696 adding "; exit 1; echo "
1520 1697 warning: filename contains '"', which is reserved on Windows: '"; exit 1; echo "'
1521 1698 created new head
1522 1699 $ hg merge --config merge-tools.true.executable="true" -r 5
1523 1700 merging "; exit 1; echo "
1524 1701 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1525 1702 (branch merge, don't forget to commit)
1526 1703 $ hg update -C 1 > /dev/null
1527 1704 #endif
1528 1705
1529 1706 Merge post-processing
1530 1707
1531 1708 cat is a bad merge-tool and doesn't change:
1532 1709
1533 1710 $ beforemerge
1534 1711 [merge-tools]
1535 1712 false.whatever=
1536 1713 true.priority=1
1537 1714 true.executable=cat
1538 1715 # hg update -C 1
1539 1716 $ hg merge -y -r 2 --config merge-tools.true.checkchanged=1
1540 1717 merging f
1541 1718 revision 1
1542 1719 space
1543 1720 revision 0
1544 1721 space
1545 1722 revision 2
1546 1723 space
1547 1724 output file f appears unchanged
1548 1725 was merge successful (yn)? n
1549 1726 merging f failed!
1550 1727 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
1551 1728 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
1552 1729 [1]
1553 1730 $ aftermerge
1554 1731 # cat f
1555 1732 revision 1
1556 1733 space
1557 1734 # hg stat
1558 1735 M f
1559 1736 ? f.orig
1560 1737 # hg resolve --list
1561 1738 U f
1562 1739
1563 1740 #if symlink
1564 1741
1565 1742 internal merge cannot handle symlinks and shouldn't try:
1566 1743
1567 1744 $ hg update -q -C 1
1568 1745 $ rm f
1569 1746 $ ln -s symlink f
1570 1747 $ hg commit -qm 'f is symlink'
1571 1748 $ hg merge -r 2 --tool internal:merge
1572 1749 merging f
1573 1750 warning: internal :merge cannot merge symlinks for f
1574 1751 warning: conflicts while merging f! (edit, then use 'hg resolve --mark')
1575 1752 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
1576 1753 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
1577 1754 [1]
1578 1755
1579 1756 #endif
1580 1757
1581 1758 Verify naming of temporary files and that extension is preserved:
1582 1759
1583 1760 $ hg update -q -C 1
1584 1761 $ hg mv f f.txt
1585 1762 $ hg ci -qm "f.txt"
1586 1763 $ hg update -q -C 2
1587 1764 $ hg merge -y -r tip --tool echo --config merge-tools.echo.args='$base $local $other $output'
1588 1765 merging f and f.txt to f.txt
1589 1766 */f~base.* */f~local.*.txt */f~other.*.txt $TESTTMP/repo/f.txt (glob)
1590 1767 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1591 1768 (branch merge, don't forget to commit)
1592 1769
1593 1770 Verify naming of temporary files and that extension is preserved
1594 1771 (experimental.mergetempdirprefix version):
1595 1772
1596 1773 $ hg update -q -C 1
1597 1774 $ hg mv f f.txt
1598 1775 $ hg ci -qm "f.txt"
1599 1776 $ hg update -q -C 2
1600 1777 $ hg merge -y -r tip --tool echo \
1601 1778 > --config merge-tools.echo.args='$base $local $other $output' \
1602 1779 > --config experimental.mergetempdirprefix=$TESTTMP/hgmerge.
1603 1780 merging f and f.txt to f.txt
1604 1781 $TESTTMP/hgmerge.*/f~base $TESTTMP/hgmerge.*/f~local.txt $TESTTMP/hgmerge.*/f~other.txt $TESTTMP/repo/f.txt (glob)
1605 1782 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
1606 1783 (branch merge, don't forget to commit)
1607 1784
1608 1785 Check that debugpicktool examines which merge tool is chosen for
1609 1786 specified file as expected
1610 1787
1611 1788 $ beforemerge
1612 1789 [merge-tools]
1613 1790 false.whatever=
1614 1791 true.priority=1
1615 1792 true.executable=cat
1616 1793 # hg update -C 1
1617 1794
1618 1795 (default behavior: checking files in the working parent context)
1619 1796
1620 1797 $ hg manifest
1621 1798 f
1622 1799 $ hg debugpickmergetool
1623 1800 f = true
1624 1801
1625 1802 (-X/-I and file patterns limmit examination targets)
1626 1803
1627 1804 $ hg debugpickmergetool -X f
1628 1805 $ hg debugpickmergetool unknown
1629 1806 unknown: no such file in rev ef83787e2614
1630 1807
1631 1808 (--changedelete emulates merging change and delete)
1632 1809
1633 1810 $ hg debugpickmergetool --changedelete
1634 1811 f = :prompt
1635 1812
1636 1813 (-r REV causes checking files in specified revision)
1637 1814
1638 1815 $ hg manifest -r tip
1639 1816 f.txt
1640 1817 $ hg debugpickmergetool -r tip
1641 1818 f.txt = true
1642 1819
1643 1820 #if symlink
1644 1821
1645 1822 (symlink causes chosing :prompt)
1646 1823
1647 1824 $ hg debugpickmergetool -r 6d00b3726f6e
1648 1825 f = :prompt
1649 1826
1650 1827 #endif
1651 1828
1652 1829 (--verbose shows some configurations)
1653 1830
1654 1831 $ hg debugpickmergetool --tool foobar -v
1655 1832 with --tool 'foobar'
1656 1833 f = foobar
1657 1834
1658 1835 $ HGMERGE=false hg debugpickmergetool -v
1659 1836 with HGMERGE='false'
1660 1837 f = false
1661 1838
1662 1839 $ hg debugpickmergetool --config ui.merge=false -v
1663 1840 with ui.merge='false'
1664 1841 f = false
1665 1842
1666 1843 (--debug shows errors detected intermediately)
1667 1844
1668 1845 $ hg debugpickmergetool --config merge-patterns.f=true --config merge-tools.true.executable=nonexistentmergetool --debug f
1669 1846 couldn't find merge tool true (for pattern f)
1670 1847 couldn't find merge tool true
1671 1848 f = false
1672 1849
1673 1850 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now