comparison mercurial/filemerge.py @ 43076:2372284d9457

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