comparison mercurial/filemerge.py @ 43077:687b865b95ad

formatting: byteify all mercurial/ and hgext/ string literals Done with python3.7 contrib/byteify-strings.py -i $(hg files 'set:mercurial/**.py - mercurial/thirdparty/** + hgext/**.py - hgext/fsmonitor/pywatchman/** - mercurial/__init__.py') black -l 80 -t py33 -S $(hg files 'set:**.py - mercurial/thirdparty/** - "contrib/python-zstandard/**" - hgext/fsmonitor/pywatchman/**') # skip-blame mass-reformatting only Differential Revision: https://phab.mercurial-scm.org/D6972
author Augie Fackler <augie@google.com>
date Sun, 06 Oct 2019 09:48:39 -0400
parents 2372284d9457
children eef9a2d67051
comparison
equal deleted inserted replaced
43076:2372284d9457 43077:687b865b95ad
40 stringutil, 40 stringutil,
41 ) 41 )
42 42
43 43
44 def _toolstr(ui, tool, part, *args): 44 def _toolstr(ui, tool, part, *args):
45 return ui.config("merge-tools", tool + "." + part, *args) 45 return ui.config(b"merge-tools", tool + b"." + part, *args)
46 46
47 47
48 def _toolbool(ui, tool, part, *args): 48 def _toolbool(ui, tool, part, *args):
49 return ui.configbool("merge-tools", tool + "." + part, *args) 49 return ui.configbool(b"merge-tools", tool + b"." + part, *args)
50 50
51 51
52 def _toollist(ui, tool, part): 52 def _toollist(ui, tool, part):
53 return ui.configlist("merge-tools", tool + "." + part) 53 return ui.configlist(b"merge-tools", tool + b"." + part)
54 54
55 55
56 internals = {} 56 internals = {}
57 # Merge tools to document. 57 # Merge tools to document.
58 internalsdoc = {} 58 internalsdoc = {}
67 # 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
68 # 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
69 # 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
70 # 80-column screen). 70 # 80-column screen).
71 _localchangedotherdeletedmsg = _( 71 _localchangedotherdeletedmsg = _(
72 "file '%(fd)s' was deleted in other%(o)s but was modified in local%(l)s.\n" 72 b"file '%(fd)s' was deleted in other%(o)s but was modified in local%(l)s.\n"
73 "You can use (c)hanged version, (d)elete, or leave (u)nresolved.\n" 73 b"You can use (c)hanged version, (d)elete, or leave (u)nresolved.\n"
74 "What do you want to do?" 74 b"What do you want to do?"
75 "$$ &Changed $$ &Delete $$ &Unresolved" 75 b"$$ &Changed $$ &Delete $$ &Unresolved"
76 ) 76 )
77 77
78 _otherchangedlocaldeletedmsg = _( 78 _otherchangedlocaldeletedmsg = _(
79 "file '%(fd)s' was deleted in local%(l)s but was modified in other%(o)s.\n" 79 b"file '%(fd)s' was deleted in local%(l)s but was modified in other%(o)s.\n"
80 "You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved.\n" 80 b"You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved.\n"
81 "What do you want to do?" 81 b"What do you want to do?"
82 "$$ &Changed $$ &Deleted $$ &Unresolved" 82 b"$$ &Changed $$ &Deleted $$ &Unresolved"
83 ) 83 )
84 84
85 85
86 class absentfilectx(object): 86 class absentfilectx(object):
87 """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
118 and fctx.ctx() == self.ctx() 118 and fctx.ctx() == self.ctx()
119 and fctx.path() == self.path() 119 and fctx.path() == self.path()
120 ) 120 )
121 121
122 def flags(self): 122 def flags(self):
123 return '' 123 return b''
124 124
125 def changectx(self): 125 def changectx(self):
126 return self._ctx 126 return self._ctx
127 127
128 def isbinary(self): 128 def isbinary(self):
133 133
134 134
135 def _findtool(ui, tool): 135 def _findtool(ui, tool):
136 if tool in internals: 136 if tool in internals:
137 return tool 137 return tool
138 cmd = _toolstr(ui, tool, "executable", tool) 138 cmd = _toolstr(ui, tool, b"executable", tool)
139 if cmd.startswith('python:'): 139 if cmd.startswith(b'python:'):
140 return cmd 140 return cmd
141 return findexternaltool(ui, tool) 141 return findexternaltool(ui, tool)
142 142
143 143
144 def _quotetoolpath(cmd): 144 def _quotetoolpath(cmd):
145 if cmd.startswith('python:'): 145 if cmd.startswith(b'python:'):
146 return cmd 146 return cmd
147 return procutil.shellquote(cmd) 147 return procutil.shellquote(cmd)
148 148
149 149
150 def findexternaltool(ui, tool): 150 def findexternaltool(ui, tool):
151 for kn in ("regkey", "regkeyalt"): 151 for kn in (b"regkey", b"regkeyalt"):
152 k = _toolstr(ui, tool, kn) 152 k = _toolstr(ui, tool, kn)
153 if not k: 153 if not k:
154 continue 154 continue
155 p = util.lookupreg(k, _toolstr(ui, tool, "regname")) 155 p = util.lookupreg(k, _toolstr(ui, tool, b"regname"))
156 if p: 156 if p:
157 p = procutil.findexe(p + _toolstr(ui, tool, "regappend", "")) 157 p = procutil.findexe(p + _toolstr(ui, tool, b"regappend", b""))
158 if p: 158 if p:
159 return p 159 return p
160 exe = _toolstr(ui, tool, "executable", tool) 160 exe = _toolstr(ui, tool, b"executable", tool)
161 return procutil.findexe(util.expandpath(exe)) 161 return procutil.findexe(util.expandpath(exe))
162 162
163 163
164 def _picktool(repo, ui, path, binary, symlink, changedelete): 164 def _picktool(repo, ui, path, binary, symlink, changedelete):
165 strictcheck = ui.configbool('merge', 'strict-capability-check') 165 strictcheck = ui.configbool(b'merge', b'strict-capability-check')
166 166
167 def hascapability(tool, capability, strict=False): 167 def hascapability(tool, capability, strict=False):
168 if tool in internals: 168 if tool in internals:
169 return strict and internals[tool].capabilities.get(capability) 169 return strict and internals[tool].capabilities.get(capability)
170 return _toolbool(ui, tool, capability) 170 return _toolbool(ui, tool, capability)
173 return tool in internals and internals[tool].mergetype == nomerge 173 return tool in internals and internals[tool].mergetype == nomerge
174 174
175 def check(tool, pat, symlink, binary, changedelete): 175 def check(tool, pat, symlink, binary, changedelete):
176 tmsg = tool 176 tmsg = tool
177 if pat: 177 if pat:
178 tmsg = _("%s (for pattern %s)") % (tool, pat) 178 tmsg = _(b"%s (for pattern %s)") % (tool, pat)
179 if not _findtool(ui, tool): 179 if not _findtool(ui, tool):
180 if pat: # explicitly requested tool deserves a warning 180 if pat: # explicitly requested tool deserves a warning
181 ui.warn(_("couldn't find merge tool %s\n") % tmsg) 181 ui.warn(_(b"couldn't find merge tool %s\n") % tmsg)
182 else: # configured but non-existing tools are more silent 182 else: # configured but non-existing tools are more silent
183 ui.note(_("couldn't find merge tool %s\n") % tmsg) 183 ui.note(_(b"couldn't find merge tool %s\n") % tmsg)
184 elif symlink and not hascapability(tool, "symlink", strictcheck): 184 elif symlink and not hascapability(tool, b"symlink", strictcheck):
185 ui.warn(_("tool %s can't handle symlinks\n") % tmsg) 185 ui.warn(_(b"tool %s can't handle symlinks\n") % tmsg)
186 elif binary and not hascapability(tool, "binary", strictcheck): 186 elif binary and not hascapability(tool, b"binary", strictcheck):
187 ui.warn(_("tool %s can't handle binary\n") % tmsg) 187 ui.warn(_(b"tool %s can't handle binary\n") % tmsg)
188 elif changedelete and not supportscd(tool): 188 elif changedelete and not supportscd(tool):
189 # the nomerge tools are the only tools that support change/delete 189 # the nomerge tools are the only tools that support change/delete
190 # conflicts 190 # conflicts
191 pass 191 pass
192 elif not procutil.gui() and _toolbool(ui, tool, "gui"): 192 elif not procutil.gui() and _toolbool(ui, tool, b"gui"):
193 ui.warn(_("tool %s requires a GUI\n") % tmsg) 193 ui.warn(_(b"tool %s requires a GUI\n") % tmsg)
194 else: 194 else:
195 return True 195 return True
196 return False 196 return False
197 197
198 # internal config: ui.forcemerge 198 # internal config: ui.forcemerge
199 # forcemerge comes from command line arguments, highest priority 199 # forcemerge comes from command line arguments, highest priority
200 force = ui.config('ui', 'forcemerge') 200 force = ui.config(b'ui', b'forcemerge')
201 if force: 201 if force:
202 toolpath = _findtool(ui, force) 202 toolpath = _findtool(ui, force)
203 if changedelete and not supportscd(toolpath): 203 if changedelete and not supportscd(toolpath):
204 return ":prompt", None 204 return b":prompt", None
205 else: 205 else:
206 if toolpath: 206 if toolpath:
207 return (force, _quotetoolpath(toolpath)) 207 return (force, _quotetoolpath(toolpath))
208 else: 208 else:
209 # mimic HGMERGE if given tool not found 209 # mimic HGMERGE if given tool not found
210 return (force, force) 210 return (force, force)
211 211
212 # HGMERGE takes next precedence 212 # HGMERGE takes next precedence
213 hgmerge = encoding.environ.get("HGMERGE") 213 hgmerge = encoding.environ.get(b"HGMERGE")
214 if hgmerge: 214 if hgmerge:
215 if changedelete and not supportscd(hgmerge): 215 if changedelete and not supportscd(hgmerge):
216 return ":prompt", None 216 return b":prompt", None
217 else: 217 else:
218 return (hgmerge, hgmerge) 218 return (hgmerge, hgmerge)
219 219
220 # then patterns 220 # then patterns
221 221
222 # whether binary capability should be checked strictly 222 # whether binary capability should be checked strictly
223 binarycap = binary and strictcheck 223 binarycap = binary and strictcheck
224 224
225 for pat, tool in ui.configitems("merge-patterns"): 225 for pat, tool in ui.configitems(b"merge-patterns"):
226 mf = match.match(repo.root, '', [pat]) 226 mf = match.match(repo.root, b'', [pat])
227 if mf(path) and check(tool, pat, symlink, binarycap, changedelete): 227 if mf(path) and check(tool, pat, symlink, binarycap, changedelete):
228 if binary and not hascapability(tool, "binary", strict=True): 228 if binary and not hascapability(tool, b"binary", strict=True):
229 ui.warn( 229 ui.warn(
230 _( 230 _(
231 "warning: check merge-patterns configurations," 231 b"warning: check merge-patterns configurations,"
232 " if %r for binary file %r is unintentional\n" 232 b" if %r for binary file %r is unintentional\n"
233 "(see 'hg help merge-tools'" 233 b"(see 'hg help merge-tools'"
234 " for binary files capability)\n" 234 b" for binary files capability)\n"
235 ) 235 )
236 % (pycompat.bytestr(tool), pycompat.bytestr(path)) 236 % (pycompat.bytestr(tool), pycompat.bytestr(path))
237 ) 237 )
238 toolpath = _findtool(ui, tool) 238 toolpath = _findtool(ui, tool)
239 return (tool, _quotetoolpath(toolpath)) 239 return (tool, _quotetoolpath(toolpath))
240 240
241 # then merge tools 241 # then merge tools
242 tools = {} 242 tools = {}
243 disabled = set() 243 disabled = set()
244 for k, v in ui.configitems("merge-tools"): 244 for k, v in ui.configitems(b"merge-tools"):
245 t = k.split('.')[0] 245 t = k.split(b'.')[0]
246 if t not in tools: 246 if t not in tools:
247 tools[t] = int(_toolstr(ui, t, "priority")) 247 tools[t] = int(_toolstr(ui, t, b"priority"))
248 if _toolbool(ui, t, "disabled"): 248 if _toolbool(ui, t, b"disabled"):
249 disabled.add(t) 249 disabled.add(t)
250 names = tools.keys() 250 names = tools.keys()
251 tools = sorted( 251 tools = sorted(
252 [(-p, tool) for tool, p in tools.items() if tool not in disabled] 252 [(-p, tool) for tool, p in tools.items() if tool not in disabled]
253 ) 253 )
254 uimerge = ui.config("ui", "merge") 254 uimerge = ui.config(b"ui", b"merge")
255 if uimerge: 255 if uimerge:
256 # external tools defined in uimerge won't be able to handle 256 # external tools defined in uimerge won't be able to handle
257 # change/delete conflicts 257 # change/delete conflicts
258 if check(uimerge, path, symlink, binary, changedelete): 258 if check(uimerge, path, symlink, binary, changedelete):
259 if uimerge not in names and not changedelete: 259 if uimerge not in names and not changedelete:
260 return (uimerge, uimerge) 260 return (uimerge, uimerge)
261 tools.insert(0, (None, uimerge)) # highest priority 261 tools.insert(0, (None, uimerge)) # highest priority
262 tools.append((None, "hgmerge")) # the old default, if found 262 tools.append((None, b"hgmerge")) # the old default, if found
263 for p, t in tools: 263 for p, t in tools:
264 if check(t, None, symlink, binary, changedelete): 264 if check(t, None, symlink, binary, changedelete):
265 toolpath = _findtool(ui, t) 265 toolpath = _findtool(ui, t)
266 return (t, _quotetoolpath(toolpath)) 266 return (t, _quotetoolpath(toolpath))
267 267
268 # internal merge or prompt as last resort 268 # internal merge or prompt as last resort
269 if symlink or binary or changedelete: 269 if symlink or binary or changedelete:
270 if not changedelete and len(tools): 270 if not changedelete and len(tools):
271 # any tool is rejected by capability for symlink or binary 271 # any tool is rejected by capability for symlink or binary
272 ui.warn(_("no tool found to merge %s\n") % path) 272 ui.warn(_(b"no tool found to merge %s\n") % path)
273 return ":prompt", None 273 return b":prompt", None
274 return ":merge", None 274 return b":merge", None
275 275
276 276
277 def _eoltype(data): 277 def _eoltype(data):
278 "Guess the EOL type of a file" 278 b"Guess the EOL type of a file"
279 if '\0' in data: # binary 279 if b'\0' in data: # binary
280 return None 280 return None
281 if '\r\n' in data: # Windows 281 if b'\r\n' in data: # Windows
282 return '\r\n' 282 return b'\r\n'
283 if '\r' in data: # Old Mac 283 if b'\r' in data: # Old Mac
284 return '\r' 284 return b'\r'
285 if '\n' in data: # UNIX 285 if b'\n' in data: # UNIX
286 return '\n' 286 return b'\n'
287 return None # unknown 287 return None # unknown
288 288
289 289
290 def _matcheol(file, back): 290 def _matcheol(file, back):
291 "Convert EOL markers in a file to match origfile" 291 b"Convert EOL markers in a file to match origfile"
292 tostyle = _eoltype(back.data()) # No repo.wread filters? 292 tostyle = _eoltype(back.data()) # No repo.wread filters?
293 if tostyle: 293 if tostyle:
294 data = util.readfile(file) 294 data = util.readfile(file)
295 style = _eoltype(data) 295 style = _eoltype(data)
296 if style: 296 if style:
297 newdata = data.replace(style, tostyle) 297 newdata = data.replace(style, tostyle)
298 if newdata != data: 298 if newdata != data:
299 util.writefile(file, newdata) 299 util.writefile(file, newdata)
300 300
301 301
302 @internaltool('prompt', nomerge) 302 @internaltool(b'prompt', nomerge)
303 def _iprompt(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None): 303 def _iprompt(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
304 """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
305 keep as the merged version.""" 305 keep as the merged version."""
306 ui = repo.ui 306 ui = repo.ui
307 fd = fcd.path() 307 fd = fcd.path()
309 309
310 # 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
311 # conflicts. 311 # conflicts.
312 if fcd.changectx().isinmemory(): 312 if fcd.changectx().isinmemory():
313 raise error.InMemoryMergeConflictsError( 313 raise error.InMemoryMergeConflictsError(
314 'in-memory merge does not ' 'support file conflicts' 314 b'in-memory merge does not ' b'support file conflicts'
315 ) 315 )
316 316
317 prompts = partextras(labels) 317 prompts = partextras(labels)
318 prompts['fd'] = uipathfn(fd) 318 prompts[b'fd'] = uipathfn(fd)
319 try: 319 try:
320 if fco.isabsent(): 320 if fco.isabsent():
321 index = ui.promptchoice(_localchangedotherdeletedmsg % prompts, 2) 321 index = ui.promptchoice(_localchangedotherdeletedmsg % prompts, 2)
322 choice = ['local', 'other', 'unresolved'][index] 322 choice = [b'local', b'other', b'unresolved'][index]
323 elif fcd.isabsent(): 323 elif fcd.isabsent():
324 index = ui.promptchoice(_otherchangedlocaldeletedmsg % prompts, 2) 324 index = ui.promptchoice(_otherchangedlocaldeletedmsg % prompts, 2)
325 choice = ['other', 'local', 'unresolved'][index] 325 choice = [b'other', b'local', b'unresolved'][index]
326 else: 326 else:
327 # 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
328 # do?") very short, see comment next to _localchangedotherdeletedmsg 328 # do?") very short, see comment next to _localchangedotherdeletedmsg
329 # at the top of the file for details. 329 # at the top of the file for details.
330 index = ui.promptchoice( 330 index = ui.promptchoice(
331 _( 331 _(
332 "file '%(fd)s' needs to be resolved.\n" 332 b"file '%(fd)s' needs to be resolved.\n"
333 "You can keep (l)ocal%(l)s, take (o)ther%(o)s, or leave " 333 b"You can keep (l)ocal%(l)s, take (o)ther%(o)s, or leave "
334 "(u)nresolved.\n" 334 b"(u)nresolved.\n"
335 "What do you want to do?" 335 b"What do you want to do?"
336 "$$ &Local $$ &Other $$ &Unresolved" 336 b"$$ &Local $$ &Other $$ &Unresolved"
337 ) 337 )
338 % prompts, 338 % prompts,
339 2, 339 2,
340 ) 340 )
341 choice = ['local', 'other', 'unresolved'][index] 341 choice = [b'local', b'other', b'unresolved'][index]
342 342
343 if choice == 'other': 343 if choice == b'other':
344 return _iother(repo, mynode, orig, fcd, fco, fca, toolconf, labels) 344 return _iother(repo, mynode, orig, fcd, fco, fca, toolconf, labels)
345 elif choice == 'local': 345 elif choice == b'local':
346 return _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf, labels) 346 return _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf, labels)
347 elif choice == 'unresolved': 347 elif choice == b'unresolved':
348 return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf, labels) 348 return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf, labels)
349 except error.ResponseExpected: 349 except error.ResponseExpected:
350 ui.write("\n") 350 ui.write(b"\n")
351 return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf, labels) 351 return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf, labels)
352 352
353 353
354 @internaltool('local', nomerge) 354 @internaltool(b'local', nomerge)
355 def _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None): 355 def _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
356 """Uses the local `p1()` version of files as the merged version.""" 356 """Uses the local `p1()` version of files as the merged version."""
357 return 0, fcd.isabsent() 357 return 0, fcd.isabsent()
358 358
359 359
360 @internaltool('other', nomerge) 360 @internaltool(b'other', nomerge)
361 def _iother(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None): 361 def _iother(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
362 """Uses the other `p2()` version of files as the merged version.""" 362 """Uses the other `p2()` version of files as the merged version."""
363 if fco.isabsent(): 363 if fco.isabsent():
364 # local changed, remote deleted -- 'deleted' picked 364 # local changed, remote deleted -- 'deleted' picked
365 _underlyingfctxifabsent(fcd).remove() 365 _underlyingfctxifabsent(fcd).remove()
368 _underlyingfctxifabsent(fcd).write(fco.data(), fco.flags()) 368 _underlyingfctxifabsent(fcd).write(fco.data(), fco.flags())
369 deleted = False 369 deleted = False
370 return 0, deleted 370 return 0, deleted
371 371
372 372
373 @internaltool('fail', nomerge) 373 @internaltool(b'fail', nomerge)
374 def _ifail(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None): 374 def _ifail(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
375 """ 375 """
376 Rather than attempting to merge files that were modified on both 376 Rather than attempting to merge files that were modified on both
377 branches, it marks them as unresolved. The resolve command must be 377 branches, it marks them as unresolved. The resolve command must be
378 used to resolve these conflicts.""" 378 used to resolve these conflicts."""
399 return 1 399 return 1
400 unused, unused, unused, back = files 400 unused, unused, unused, back = files
401 401
402 ui = repo.ui 402 ui = repo.ui
403 403
404 validkeep = ['keep', 'keep-merge3'] 404 validkeep = [b'keep', b'keep-merge3']
405 405
406 # do we attempt to simplemerge first? 406 # do we attempt to simplemerge first?
407 try: 407 try:
408 premerge = _toolbool(ui, tool, "premerge", not binary) 408 premerge = _toolbool(ui, tool, b"premerge", not binary)
409 except error.ConfigError: 409 except error.ConfigError:
410 premerge = _toolstr(ui, tool, "premerge", "").lower() 410 premerge = _toolstr(ui, tool, b"premerge", b"").lower()
411 if premerge not in validkeep: 411 if premerge not in validkeep:
412 _valid = ', '.join(["'" + v + "'" for v in validkeep]) 412 _valid = b', '.join([b"'" + v + b"'" for v in validkeep])
413 raise error.ConfigError( 413 raise error.ConfigError(
414 _("%s.premerge not valid " "('%s' is neither boolean nor %s)") 414 _(b"%s.premerge not valid " b"('%s' is neither boolean nor %s)")
415 % (tool, premerge, _valid) 415 % (tool, premerge, _valid)
416 ) 416 )
417 417
418 if premerge: 418 if premerge:
419 if premerge == 'keep-merge3': 419 if premerge == b'keep-merge3':
420 if not labels: 420 if not labels:
421 labels = _defaultconflictlabels 421 labels = _defaultconflictlabels
422 if len(labels) < 3: 422 if len(labels) < 3:
423 labels.append('base') 423 labels.append(b'base')
424 r = simplemerge.simplemerge(ui, fcd, fca, fco, quiet=True, label=labels) 424 r = simplemerge.simplemerge(ui, fcd, fca, fco, quiet=True, label=labels)
425 if not r: 425 if not r:
426 ui.debug(" premerge successful\n") 426 ui.debug(b" premerge successful\n")
427 return 0 427 return 0
428 if premerge not in validkeep: 428 if premerge not in validkeep:
429 # restore from backup and try again 429 # restore from backup and try again
430 _restorebackup(fcd, back) 430 _restorebackup(fcd, back)
431 return 1 # continue merging 431 return 1 # continue merging
434 def _mergecheck(repo, mynode, orig, fcd, fco, fca, toolconf): 434 def _mergecheck(repo, mynode, orig, fcd, fco, fca, toolconf):
435 tool, toolpath, binary, symlink, scriptfn = toolconf 435 tool, toolpath, binary, symlink, scriptfn = toolconf
436 uipathfn = scmutil.getuipathfn(repo) 436 uipathfn = scmutil.getuipathfn(repo)
437 if symlink: 437 if symlink:
438 repo.ui.warn( 438 repo.ui.warn(
439 _('warning: internal %s cannot merge symlinks ' 'for %s\n') 439 _(b'warning: internal %s cannot merge symlinks ' b'for %s\n')
440 % (tool, uipathfn(fcd.path())) 440 % (tool, uipathfn(fcd.path()))
441 ) 441 )
442 return False 442 return False
443 if fcd.isabsent() or fco.isabsent(): 443 if fcd.isabsent() or fco.isabsent():
444 repo.ui.warn( 444 repo.ui.warn(
445 _( 445 _(
446 'warning: internal %s cannot merge change/delete ' 446 b'warning: internal %s cannot merge change/delete '
447 'conflict for %s\n' 447 b'conflict for %s\n'
448 ) 448 )
449 % (tool, uipathfn(fcd.path())) 449 % (tool, uipathfn(fcd.path()))
450 ) 450 )
451 return False 451 return False
452 return True 452 return True
463 r = simplemerge.simplemerge(ui, fcd, fca, fco, label=labels, mode=mode) 463 r = simplemerge.simplemerge(ui, fcd, fca, fco, label=labels, mode=mode)
464 return True, r, False 464 return True, r, False
465 465
466 466
467 @internaltool( 467 @internaltool(
468 'union', 468 b'union',
469 fullmerge, 469 fullmerge,
470 _( 470 _(
471 "warning: conflicts while merging %s! " 471 b"warning: conflicts while merging %s! "
472 "(edit, then use 'hg resolve --mark')\n" 472 b"(edit, then use 'hg resolve --mark')\n"
473 ), 473 ),
474 precheck=_mergecheck, 474 precheck=_mergecheck,
475 ) 475 )
476 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):
477 """ 477 """
478 Uses the internal non-interactive simple merge algorithm for merging 478 Uses the internal non-interactive simple merge algorithm for merging
479 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.
480 No markers are inserted.""" 480 No markers are inserted."""
481 return _merge( 481 return _merge(
482 repo, mynode, orig, fcd, fco, fca, toolconf, files, labels, 'union' 482 repo, mynode, orig, fcd, fco, fca, toolconf, files, labels, b'union'
483 ) 483 )
484 484
485 485
486 @internaltool( 486 @internaltool(
487 'merge', 487 b'merge',
488 fullmerge, 488 fullmerge,
489 _( 489 _(
490 "warning: conflicts while merging %s! " 490 b"warning: conflicts while merging %s! "
491 "(edit, then use 'hg resolve --mark')\n" 491 b"(edit, then use 'hg resolve --mark')\n"
492 ), 492 ),
493 precheck=_mergecheck, 493 precheck=_mergecheck,
494 ) 494 )
495 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):
496 """ 496 """
497 Uses the internal non-interactive simple merge algorithm for merging 497 Uses the internal non-interactive simple merge algorithm for merging
498 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
499 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
500 of merge.""" 500 of merge."""
501 return _merge( 501 return _merge(
502 repo, mynode, orig, fcd, fco, fca, toolconf, files, labels, 'merge' 502 repo, mynode, orig, fcd, fco, fca, toolconf, files, labels, b'merge'
503 ) 503 )
504 504
505 505
506 @internaltool( 506 @internaltool(
507 'merge3', 507 b'merge3',
508 fullmerge, 508 fullmerge,
509 _( 509 _(
510 "warning: conflicts while merging %s! " 510 b"warning: conflicts while merging %s! "
511 "(edit, then use 'hg resolve --mark')\n" 511 b"(edit, then use 'hg resolve --mark')\n"
512 ), 512 ),
513 precheck=_mergecheck, 513 precheck=_mergecheck,
514 ) 514 )
515 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):
516 """ 516 """
519 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
520 side of the merge and one for the base content.""" 520 side of the merge and one for the base content."""
521 if not labels: 521 if not labels:
522 labels = _defaultconflictlabels 522 labels = _defaultconflictlabels
523 if len(labels) < 3: 523 if len(labels) < 3:
524 labels.append('base') 524 labels.append(b'base')
525 return _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels) 525 return _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels)
526 526
527 527
528 def _imergeauto( 528 def _imergeauto(
529 repo, 529 repo,
545 repo.ui, fcd, fca, fco, label=labels, localorother=localorother 545 repo.ui, fcd, fca, fco, label=labels, localorother=localorother
546 ) 546 )
547 return True, r 547 return True, r
548 548
549 549
550 @internaltool('merge-local', mergeonly, precheck=_mergecheck) 550 @internaltool(b'merge-local', mergeonly, precheck=_mergecheck)
551 def _imergelocal(*args, **kwargs): 551 def _imergelocal(*args, **kwargs):
552 """ 552 """
553 Like :merge, but resolve all conflicts non-interactively in favor 553 Like :merge, but resolve all conflicts non-interactively in favor
554 of the local `p1()` changes.""" 554 of the local `p1()` changes."""
555 success, status = _imergeauto(localorother='local', *args, **kwargs) 555 success, status = _imergeauto(localorother=b'local', *args, **kwargs)
556 return success, status, False 556 return success, status, False
557 557
558 558
559 @internaltool('merge-other', mergeonly, precheck=_mergecheck) 559 @internaltool(b'merge-other', mergeonly, precheck=_mergecheck)
560 def _imergeother(*args, **kwargs): 560 def _imergeother(*args, **kwargs):
561 """ 561 """
562 Like :merge, but resolve all conflicts non-interactively in favor 562 Like :merge, but resolve all conflicts non-interactively in favor
563 of the other `p2()` changes.""" 563 of the other `p2()` changes."""
564 success, status = _imergeauto(localorother='other', *args, **kwargs) 564 success, status = _imergeauto(localorother=b'other', *args, **kwargs)
565 return success, status, False 565 return success, status, False
566 566
567 567
568 @internaltool( 568 @internaltool(
569 'tagmerge', 569 b'tagmerge',
570 mergeonly, 570 mergeonly,
571 _( 571 _(
572 "automatic tag merging of %s failed! " 572 b"automatic tag merging of %s failed! "
573 "(use 'hg resolve --tool :merge' or another merge " 573 b"(use 'hg resolve --tool :merge' or another merge "
574 "tool of your choice)\n" 574 b"tool of your choice)\n"
575 ), 575 ),
576 ) 576 )
577 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):
578 """ 578 """
579 Uses the internal tag merge algorithm (experimental). 579 Uses the internal tag merge algorithm (experimental).
580 """ 580 """
581 success, status = tagmerge.merge(repo, fcd, fco, fca) 581 success, status = tagmerge.merge(repo, fcd, fco, fca)
582 return success, status, False 582 return success, status, False
583 583
584 584
585 @internaltool('dump', fullmerge, binary=True, symlink=True) 585 @internaltool(b'dump', fullmerge, binary=True, symlink=True)
586 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):
587 """ 587 """
588 Creates three versions of the files to merge, containing the 588 Creates three versions of the files to merge, containing the
589 contents of local, other and base. These files can then be used to 589 contents of local, other and base. These files can then be used to
590 perform a merge manually. If the file to be merged is named 590 perform a merge manually. If the file to be merged is named
600 600
601 from . import context 601 from . import context
602 602
603 if isinstance(fcd, context.overlayworkingfilectx): 603 if isinstance(fcd, context.overlayworkingfilectx):
604 raise error.InMemoryMergeConflictsError( 604 raise error.InMemoryMergeConflictsError(
605 'in-memory merge does not ' 'support the :dump tool.' 605 b'in-memory merge does not ' b'support the :dump tool.'
606 ) 606 )
607 607
608 util.writefile(a + ".local", fcd.decodeddata()) 608 util.writefile(a + b".local", fcd.decodeddata())
609 repo.wwrite(fd + ".other", fco.data(), fco.flags()) 609 repo.wwrite(fd + b".other", fco.data(), fco.flags())
610 repo.wwrite(fd + ".base", fca.data(), fca.flags()) 610 repo.wwrite(fd + b".base", fca.data(), fca.flags())
611 return False, 1, False 611 return False, 1, False
612 612
613 613
614 @internaltool('forcedump', mergeonly, binary=True, symlink=True) 614 @internaltool(b'forcedump', mergeonly, binary=True, symlink=True)
615 def _forcedump(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None): 615 def _forcedump(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
616 """ 616 """
617 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.
618 """ 618 """
619 return _idump( 619 return _idump(
629 # 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
630 # 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/
631 # 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
632 # clunky.) 632 # clunky.)
633 raise error.InMemoryMergeConflictsError( 633 raise error.InMemoryMergeConflictsError(
634 'in-memory merge does not support ' 'external merge tools' 634 b'in-memory merge does not support ' b'external merge tools'
635 ) 635 )
636 636
637 637
638 def _describemerge(ui, repo, mynode, fcl, fcb, fco, env, toolpath, args): 638 def _describemerge(ui, repo, mynode, fcl, fcb, fco, env, toolpath, args):
639 tmpl = ui.config('ui', 'pre-merge-tool-output-template') 639 tmpl = ui.config(b'ui', b'pre-merge-tool-output-template')
640 if not tmpl: 640 if not tmpl:
641 return 641 return
642 642
643 mappingdict = templateutil.mappingdict 643 mappingdict = templateutil.mappingdict
644 props = { 644 props = {
645 'ctx': fcl.changectx(), 645 b'ctx': fcl.changectx(),
646 'node': hex(mynode), 646 b'node': hex(mynode),
647 'path': fcl.path(), 647 b'path': fcl.path(),
648 'local': mappingdict( 648 b'local': mappingdict(
649 { 649 {
650 'ctx': fcl.changectx(), 650 b'ctx': fcl.changectx(),
651 'fctx': fcl, 651 b'fctx': fcl,
652 'node': hex(mynode), 652 b'node': hex(mynode),
653 'name': _('local'), 653 b'name': _(b'local'),
654 'islink': 'l' in fcl.flags(), 654 b'islink': b'l' in fcl.flags(),
655 'label': env['HG_MY_LABEL'], 655 b'label': env[b'HG_MY_LABEL'],
656 } 656 }
657 ), 657 ),
658 'base': mappingdict( 658 b'base': mappingdict(
659 { 659 {
660 'ctx': fcb.changectx(), 660 b'ctx': fcb.changectx(),
661 'fctx': fcb, 661 b'fctx': fcb,
662 'name': _('base'), 662 b'name': _(b'base'),
663 'islink': 'l' in fcb.flags(), 663 b'islink': b'l' in fcb.flags(),
664 'label': env['HG_BASE_LABEL'], 664 b'label': env[b'HG_BASE_LABEL'],
665 } 665 }
666 ), 666 ),
667 'other': mappingdict( 667 b'other': mappingdict(
668 { 668 {
669 'ctx': fco.changectx(), 669 b'ctx': fco.changectx(),
670 'fctx': fco, 670 b'fctx': fco,
671 'name': _('other'), 671 b'name': _(b'other'),
672 'islink': 'l' in fco.flags(), 672 b'islink': b'l' in fco.flags(),
673 'label': env['HG_OTHER_LABEL'], 673 b'label': env[b'HG_OTHER_LABEL'],
674 } 674 }
675 ), 675 ),
676 'toolpath': toolpath, 676 b'toolpath': toolpath,
677 'toolargs': args, 677 b'toolargs': args,
678 } 678 }
679 679
680 # 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
681 tmpl = templater.unquotestring(tmpl) 681 tmpl = templater.unquotestring(tmpl)
682 682
692 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):
693 tool, toolpath, binary, symlink, scriptfn = toolconf 693 tool, toolpath, binary, symlink, scriptfn = toolconf
694 uipathfn = scmutil.getuipathfn(repo) 694 uipathfn = scmutil.getuipathfn(repo)
695 if fcd.isabsent() or fco.isabsent(): 695 if fcd.isabsent() or fco.isabsent():
696 repo.ui.warn( 696 repo.ui.warn(
697 _('warning: %s cannot merge change/delete conflict ' 'for %s\n') 697 _(b'warning: %s cannot merge change/delete conflict ' b'for %s\n')
698 % (tool, uipathfn(fcd.path())) 698 % (tool, uipathfn(fcd.path()))
699 ) 699 )
700 return False, 1, None 700 return False, 1, None
701 unused, unused, unused, back = files 701 unused, unused, unused, back = files
702 localpath = _workingpath(repo, fcd) 702 localpath = _workingpath(repo, fcd)
703 args = _toolstr(repo.ui, tool, "args") 703 args = _toolstr(repo.ui, tool, b"args")
704 704
705 with _maketempfiles( 705 with _maketempfiles(
706 repo, fco, fca, repo.wvfs.join(back.path()), "$output" in args 706 repo, fco, fca, repo.wvfs.join(back.path()), b"$output" in args
707 ) as temppaths: 707 ) as temppaths:
708 basepath, otherpath, localoutputpath = temppaths 708 basepath, otherpath, localoutputpath = temppaths
709 outpath = "" 709 outpath = b""
710 mylabel, otherlabel = labels[:2] 710 mylabel, otherlabel = labels[:2]
711 if len(labels) >= 3: 711 if len(labels) >= 3:
712 baselabel = labels[2] 712 baselabel = labels[2]
713 else: 713 else:
714 baselabel = 'base' 714 baselabel = b'base'
715 env = { 715 env = {
716 'HG_FILE': fcd.path(), 716 b'HG_FILE': fcd.path(),
717 'HG_MY_NODE': short(mynode), 717 b'HG_MY_NODE': short(mynode),
718 'HG_OTHER_NODE': short(fco.changectx().node()), 718 b'HG_OTHER_NODE': short(fco.changectx().node()),
719 'HG_BASE_NODE': short(fca.changectx().node()), 719 b'HG_BASE_NODE': short(fca.changectx().node()),
720 'HG_MY_ISLINK': 'l' in fcd.flags(), 720 b'HG_MY_ISLINK': b'l' in fcd.flags(),
721 'HG_OTHER_ISLINK': 'l' in fco.flags(), 721 b'HG_OTHER_ISLINK': b'l' in fco.flags(),
722 'HG_BASE_ISLINK': 'l' in fca.flags(), 722 b'HG_BASE_ISLINK': b'l' in fca.flags(),
723 'HG_MY_LABEL': mylabel, 723 b'HG_MY_LABEL': mylabel,
724 'HG_OTHER_LABEL': otherlabel, 724 b'HG_OTHER_LABEL': otherlabel,
725 'HG_BASE_LABEL': baselabel, 725 b'HG_BASE_LABEL': baselabel,
726 } 726 }
727 ui = repo.ui 727 ui = repo.ui
728 728
729 if "$output" in args: 729 if b"$output" in args:
730 # read input from backup, write to original 730 # read input from backup, write to original
731 outpath = localpath 731 outpath = localpath
732 localpath = localoutputpath 732 localpath = localoutputpath
733 replace = { 733 replace = {
734 'local': localpath, 734 b'local': localpath,
735 'base': basepath, 735 b'base': basepath,
736 'other': otherpath, 736 b'other': otherpath,
737 'output': outpath, 737 b'output': outpath,
738 'labellocal': mylabel, 738 b'labellocal': mylabel,
739 'labelother': otherlabel, 739 b'labelother': otherlabel,
740 'labelbase': baselabel, 740 b'labelbase': baselabel,
741 } 741 }
742 args = util.interpolate( 742 args = util.interpolate(
743 br'\$', 743 br'\$',
744 replace, 744 replace,
745 args, 745 args,
746 lambda s: procutil.shellquote(util.localpath(s)), 746 lambda s: procutil.shellquote(util.localpath(s)),
747 ) 747 )
748 if _toolbool(ui, tool, "gui"): 748 if _toolbool(ui, tool, b"gui"):
749 repo.ui.status( 749 repo.ui.status(
750 _('running merge tool %s for file %s\n') 750 _(b'running merge tool %s for file %s\n')
751 % (tool, uipathfn(fcd.path())) 751 % (tool, uipathfn(fcd.path()))
752 ) 752 )
753 if scriptfn is None: 753 if scriptfn is None:
754 cmd = toolpath + ' ' + args 754 cmd = toolpath + b' ' + args
755 repo.ui.debug('launching merge tool: %s\n' % cmd) 755 repo.ui.debug(b'launching merge tool: %s\n' % cmd)
756 _describemerge(ui, repo, mynode, fcd, fca, fco, env, toolpath, args) 756 _describemerge(ui, repo, mynode, fcd, fca, fco, env, toolpath, args)
757 r = ui.system( 757 r = ui.system(
758 cmd, cwd=repo.root, environ=env, blockedtag='mergetool' 758 cmd, cwd=repo.root, environ=env, blockedtag=b'mergetool'
759 ) 759 )
760 else: 760 else:
761 repo.ui.debug( 761 repo.ui.debug(
762 'launching python merge script: %s:%s\n' % (toolpath, scriptfn) 762 b'launching python merge script: %s:%s\n' % (toolpath, scriptfn)
763 ) 763 )
764 r = 0 764 r = 0
765 try: 765 try:
766 # avoid cycle cmdutil->merge->filemerge->extensions->cmdutil 766 # avoid cycle cmdutil->merge->filemerge->extensions->cmdutil
767 from . import extensions 767 from . import extensions
768 768
769 mod = extensions.loadpath(toolpath, 'hgmerge.%s' % tool) 769 mod = extensions.loadpath(toolpath, b'hgmerge.%s' % tool)
770 except Exception: 770 except Exception:
771 raise error.Abort( 771 raise error.Abort(
772 _("loading python merge script failed: %s") % toolpath 772 _(b"loading python merge script failed: %s") % toolpath
773 ) 773 )
774 mergefn = getattr(mod, scriptfn, None) 774 mergefn = getattr(mod, scriptfn, None)
775 if mergefn is None: 775 if mergefn is None:
776 raise error.Abort( 776 raise error.Abort(
777 _("%s does not have function: %s") % (toolpath, scriptfn) 777 _(b"%s does not have function: %s") % (toolpath, scriptfn)
778 ) 778 )
779 argslist = procutil.shellsplit(args) 779 argslist = procutil.shellsplit(args)
780 # avoid cycle cmdutil->merge->filemerge->hook->extensions->cmdutil 780 # avoid cycle cmdutil->merge->filemerge->hook->extensions->cmdutil
781 from . import hook 781 from . import hook
782 782
783 ret, raised = hook.pythonhook( 783 ret, raised = hook.pythonhook(
784 ui, repo, "merge", toolpath, mergefn, {'args': argslist}, True 784 ui, repo, b"merge", toolpath, mergefn, {b'args': argslist}, True
785 ) 785 )
786 if raised: 786 if raised:
787 r = 1 787 r = 1
788 repo.ui.debug('merge tool returned: %d\n' % r) 788 repo.ui.debug(b'merge tool returned: %d\n' % r)
789 return True, r, False 789 return True, r, False
790 790
791 791
792 def _formatconflictmarker(ctx, template, label, pad): 792 def _formatconflictmarker(ctx, template, label, pad):
793 """Applies the given template to the ctx, prefixed by the label. 793 """Applies the given template to the ctx, prefixed by the label.
796 can have aligned templated parts. 796 can have aligned templated parts.
797 """ 797 """
798 if ctx.node() is None: 798 if ctx.node() is None:
799 ctx = ctx.p1() 799 ctx = ctx.p1()
800 800
801 props = {'ctx': ctx} 801 props = {b'ctx': ctx}
802 templateresult = template.renderdefault(props) 802 templateresult = template.renderdefault(props)
803 803
804 label = ('%s:' % label).ljust(pad + 1) 804 label = (b'%s:' % label).ljust(pad + 1)
805 mark = '%s %s' % (label, templateresult) 805 mark = b'%s %s' % (label, templateresult)
806 806
807 if mark: 807 if mark:
808 mark = mark.splitlines()[0] # split for safety 808 mark = mark.splitlines()[0] # split for safety
809 809
810 # 8 for the prefix of conflict marker lines (e.g. '<<<<<<< ') 810 # 8 for the prefix of conflict marker lines (e.g. '<<<<<<< ')
811 return stringutil.ellipsis(mark, 80 - 8) 811 return stringutil.ellipsis(mark, 80 - 8)
812 812
813 813
814 _defaultconflictlabels = ['local', 'other'] 814 _defaultconflictlabels = [b'local', b'other']
815 815
816 816
817 def _formatlabels(repo, fcd, fco, fca, labels, tool=None): 817 def _formatlabels(repo, fcd, fco, fca, labels, tool=None):
818 """Formats the given labels using the conflict marker template. 818 """Formats the given labels using the conflict marker template.
819 819
822 cd = fcd.changectx() 822 cd = fcd.changectx()
823 co = fco.changectx() 823 co = fco.changectx()
824 ca = fca.changectx() 824 ca = fca.changectx()
825 825
826 ui = repo.ui 826 ui = repo.ui
827 template = ui.config('ui', 'mergemarkertemplate') 827 template = ui.config(b'ui', b'mergemarkertemplate')
828 if tool is not None: 828 if tool is not None:
829 template = _toolstr(ui, tool, 'mergemarkertemplate', template) 829 template = _toolstr(ui, tool, b'mergemarkertemplate', template)
830 template = templater.unquotestring(template) 830 template = templater.unquotestring(template)
831 tres = formatter.templateresources(ui, repo) 831 tres = formatter.templateresources(ui, repo)
832 tmpl = formatter.maketemplater( 832 tmpl = formatter.maketemplater(
833 ui, template, defaults=templatekw.keywords, resources=tres 833 ui, template, defaults=templatekw.keywords, resources=tres
834 ) 834 )
849 849
850 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".
851 """ 851 """
852 if labels is None: 852 if labels is None:
853 return { 853 return {
854 "l": "", 854 b"l": b"",
855 "o": "", 855 b"o": b"",
856 } 856 }
857 857
858 return { 858 return {
859 "l": " [%s]" % labels[0], 859 b"l": b" [%s]" % labels[0],
860 "o": " [%s]" % labels[1], 860 b"o": b" [%s]" % labels[1],
861 } 861 }
862 862
863 863
864 def _restorebackup(fcd, back): 864 def _restorebackup(fcd, back):
865 # TODO: Add a workingfilectx.write(otherfilectx) path so we can use 865 # TODO: Add a workingfilectx.write(otherfilectx) path so we can use
917 """Writes out `fco` and `fca` as temporary files, and (if uselocalpath) 917 """Writes out `fco` and `fca` as temporary files, and (if uselocalpath)
918 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
919 use them. 919 use them.
920 """ 920 """
921 tmproot = None 921 tmproot = None
922 tmprootprefix = repo.ui.config('experimental', 'mergetempdirprefix') 922 tmprootprefix = repo.ui.config(b'experimental', b'mergetempdirprefix')
923 if tmprootprefix: 923 if tmprootprefix:
924 tmproot = pycompat.mkdtemp(prefix=tmprootprefix) 924 tmproot = pycompat.mkdtemp(prefix=tmprootprefix)
925 925
926 def maketempfrompath(prefix, path): 926 def maketempfrompath(prefix, path):
927 fullbase, ext = os.path.splitext(path) 927 fullbase, ext = os.path.splitext(path)
928 pre = "%s~%s" % (os.path.basename(fullbase), prefix) 928 pre = b"%s~%s" % (os.path.basename(fullbase), prefix)
929 if tmproot: 929 if tmproot:
930 name = os.path.join(tmproot, pre) 930 name = os.path.join(tmproot, pre)
931 if ext: 931 if ext:
932 name += ext 932 name += ext
933 f = open(name, r"wb") 933 f = open(name, r"wb")
934 else: 934 else:
935 fd, name = pycompat.mkstemp(prefix=pre + '.', suffix=ext) 935 fd, name = pycompat.mkstemp(prefix=pre + b'.', suffix=ext)
936 f = os.fdopen(fd, r"wb") 936 f = os.fdopen(fd, r"wb")
937 return f, name 937 return f, name
938 938
939 def tempfromcontext(prefix, ctx): 939 def tempfromcontext(prefix, ctx):
940 f, name = maketempfrompath(prefix, ctx.path()) 940 f, name = maketempfrompath(prefix, ctx.path())
941 data = repo.wwritedata(ctx.path(), ctx.data()) 941 data = repo.wwritedata(ctx.path(), ctx.data())
942 f.write(data) 942 f.write(data)
943 f.close() 943 f.close()
944 return name 944 return name
945 945
946 b = tempfromcontext("base", fca) 946 b = tempfromcontext(b"base", fca)
947 c = tempfromcontext("other", fco) 947 c = tempfromcontext(b"other", fco)
948 d = localpath 948 d = localpath
949 if uselocalpath: 949 if uselocalpath:
950 # We start off with this being the backup filename, so remove the .orig 950 # We start off with this being the backup filename, so remove the .orig
951 # to make syntax-highlighting more likely. 951 # to make syntax-highlighting more likely.
952 if d.endswith('.orig'): 952 if d.endswith(b'.orig'):
953 d, _ = os.path.splitext(d) 953 d, _ = os.path.splitext(d)
954 f, d = maketempfrompath("local", d) 954 f, d = maketempfrompath(b"local", d)
955 with open(localpath, 'rb') as src: 955 with open(localpath, b'rb') as src:
956 f.write(src.read()) 956 f.write(src.read())
957 f.close() 957 f.close()
958 958
959 try: 959 try:
960 yield b, c, d 960 yield b, c, d
989 ui = repo.ui 989 ui = repo.ui
990 fd = fcd.path() 990 fd = fcd.path()
991 uipathfn = scmutil.getuipathfn(repo) 991 uipathfn = scmutil.getuipathfn(repo)
992 fduipath = uipathfn(fd) 992 fduipath = uipathfn(fd)
993 binary = fcd.isbinary() or fco.isbinary() or fca.isbinary() 993 binary = fcd.isbinary() or fco.isbinary() or fca.isbinary()
994 symlink = 'l' in fcd.flags() + fco.flags() 994 symlink = b'l' in fcd.flags() + fco.flags()
995 changedelete = fcd.isabsent() or fco.isabsent() 995 changedelete = fcd.isabsent() or fco.isabsent()
996 tool, toolpath = _picktool(repo, ui, fd, binary, symlink, changedelete) 996 tool, toolpath = _picktool(repo, ui, fd, binary, symlink, changedelete)
997 scriptfn = None 997 scriptfn = None
998 if tool in internals and tool.startswith('internal:'): 998 if tool in internals and tool.startswith(b'internal:'):
999 # normalize to new-style names (':merge' etc) 999 # normalize to new-style names (':merge' etc)
1000 tool = tool[len('internal') :] 1000 tool = tool[len(b'internal') :]
1001 if toolpath and toolpath.startswith('python:'): 1001 if toolpath and toolpath.startswith(b'python:'):
1002 invalidsyntax = False 1002 invalidsyntax = False
1003 if toolpath.count(':') >= 2: 1003 if toolpath.count(b':') >= 2:
1004 script, scriptfn = toolpath[7:].rsplit(':', 1) 1004 script, scriptfn = toolpath[7:].rsplit(b':', 1)
1005 if not scriptfn: 1005 if not scriptfn:
1006 invalidsyntax = True 1006 invalidsyntax = True
1007 # missing :callable can lead to spliting on windows drive letter 1007 # missing :callable can lead to spliting on windows drive letter
1008 if '\\' in scriptfn or '/' in scriptfn: 1008 if b'\\' in scriptfn or b'/' in scriptfn:
1009 invalidsyntax = True 1009 invalidsyntax = True
1010 else: 1010 else:
1011 invalidsyntax = True 1011 invalidsyntax = True
1012 if invalidsyntax: 1012 if invalidsyntax:
1013 raise error.Abort(_("invalid 'python:' syntax: %s") % toolpath) 1013 raise error.Abort(_(b"invalid 'python:' syntax: %s") % toolpath)
1014 toolpath = script 1014 toolpath = script
1015 ui.debug( 1015 ui.debug(
1016 "picked tool '%s' for %s (binary %s symlink %s changedelete %s)\n" 1016 b"picked tool '%s' for %s (binary %s symlink %s changedelete %s)\n"
1017 % ( 1017 % (
1018 tool, 1018 tool,
1019 fduipath, 1019 fduipath,
1020 pycompat.bytestr(binary), 1020 pycompat.bytestr(binary),
1021 pycompat.bytestr(symlink), 1021 pycompat.bytestr(symlink),
1033 if wctx.isinmemory(): 1033 if wctx.isinmemory():
1034 func = _xmergeimm 1034 func = _xmergeimm
1035 else: 1035 else:
1036 func = _xmerge 1036 func = _xmerge
1037 mergetype = fullmerge 1037 mergetype = fullmerge
1038 onfailure = _("merging %s failed!\n") 1038 onfailure = _(b"merging %s failed!\n")
1039 precheck = None 1039 precheck = None
1040 isexternal = True 1040 isexternal = True
1041 1041
1042 toolconf = tool, toolpath, binary, symlink, scriptfn 1042 toolconf = tool, toolpath, binary, symlink, scriptfn
1043 1043
1046 return True, r, deleted 1046 return True, r, deleted
1047 1047
1048 if premerge: 1048 if premerge:
1049 if orig != fco.path(): 1049 if orig != fco.path():
1050 ui.status( 1050 ui.status(
1051 _("merging %s and %s to %s\n") 1051 _(b"merging %s and %s to %s\n")
1052 % (uipathfn(orig), uipathfn(fco.path()), fduipath) 1052 % (uipathfn(orig), uipathfn(fco.path()), fduipath)
1053 ) 1053 )
1054 else: 1054 else:
1055 ui.status(_("merging %s\n") % fduipath) 1055 ui.status(_(b"merging %s\n") % fduipath)
1056 1056
1057 ui.debug("my %s other %s ancestor %s\n" % (fcd, fco, fca)) 1057 ui.debug(b"my %s other %s ancestor %s\n" % (fcd, fco, fca))
1058 1058
1059 if precheck and not precheck(repo, mynode, orig, fcd, fco, fca, toolconf): 1059 if precheck and not precheck(repo, mynode, orig, fcd, fco, fca, toolconf):
1060 if onfailure: 1060 if onfailure:
1061 if wctx.isinmemory(): 1061 if wctx.isinmemory():
1062 raise error.InMemoryMergeConflictsError( 1062 raise error.InMemoryMergeConflictsError(
1063 'in-memory merge does ' 'not support merge ' 'conflicts' 1063 b'in-memory merge does ' b'not support merge ' b'conflicts'
1064 ) 1064 )
1065 ui.warn(onfailure % fduipath) 1065 ui.warn(onfailure % fduipath)
1066 return True, 1, False 1066 return True, 1, False
1067 1067
1068 back = _makebackup(repo, ui, wctx, fcd, premerge) 1068 back = _makebackup(repo, ui, wctx, fcd, premerge)
1069 files = (None, None, None, back) 1069 files = (None, None, None, back)
1070 r = 1 1070 r = 1
1071 try: 1071 try:
1072 internalmarkerstyle = ui.config('ui', 'mergemarkers') 1072 internalmarkerstyle = ui.config(b'ui', b'mergemarkers')
1073 if isexternal: 1073 if isexternal:
1074 markerstyle = _toolstr(ui, tool, 'mergemarkers') 1074 markerstyle = _toolstr(ui, tool, b'mergemarkers')
1075 else: 1075 else:
1076 markerstyle = internalmarkerstyle 1076 markerstyle = internalmarkerstyle
1077 1077
1078 if not labels: 1078 if not labels:
1079 labels = _defaultconflictlabels 1079 labels = _defaultconflictlabels
1080 formattedlabels = labels 1080 formattedlabels = labels
1081 if markerstyle != 'basic': 1081 if markerstyle != b'basic':
1082 formattedlabels = _formatlabels( 1082 formattedlabels = _formatlabels(
1083 repo, fcd, fco, fca, labels, tool=tool 1083 repo, fcd, fco, fca, labels, tool=tool
1084 ) 1084 )
1085 1085
1086 if premerge and mergetype == fullmerge: 1086 if premerge and mergetype == fullmerge:
1089 # setting is 'detailed'. This way tools can have basic labels in 1089 # setting is 'detailed'. This way tools can have basic labels in
1090 # space-constrained areas of the UI, but still get full information 1090 # space-constrained areas of the UI, but still get full information
1091 # in conflict markers if premerge is 'keep' or 'keep-merge3'. 1091 # in conflict markers if premerge is 'keep' or 'keep-merge3'.
1092 premergelabels = labels 1092 premergelabels = labels
1093 labeltool = None 1093 labeltool = None
1094 if markerstyle != 'basic': 1094 if markerstyle != b'basic':
1095 # respect 'tool's mergemarkertemplate (which defaults to 1095 # respect 'tool's mergemarkertemplate (which defaults to
1096 # ui.mergemarkertemplate) 1096 # ui.mergemarkertemplate)
1097 labeltool = tool 1097 labeltool = tool
1098 if internalmarkerstyle != 'basic' or markerstyle != 'basic': 1098 if internalmarkerstyle != b'basic' or markerstyle != b'basic':
1099 premergelabels = _formatlabels( 1099 premergelabels = _formatlabels(
1100 repo, fcd, fco, fca, premergelabels, tool=labeltool 1100 repo, fcd, fco, fca, premergelabels, tool=labeltool
1101 ) 1101 )
1102 1102
1103 r = _premerge( 1103 r = _premerge(
1123 1123
1124 if r: 1124 if r:
1125 if onfailure: 1125 if onfailure:
1126 if wctx.isinmemory(): 1126 if wctx.isinmemory():
1127 raise error.InMemoryMergeConflictsError( 1127 raise error.InMemoryMergeConflictsError(
1128 'in-memory merge ' 'does not support ' 'merge conflicts' 1128 b'in-memory merge '
1129 b'does not support '
1130 b'merge conflicts'
1129 ) 1131 )
1130 ui.warn(onfailure % fduipath) 1132 ui.warn(onfailure % fduipath)
1131 _onfilemergefailure(ui) 1133 _onfilemergefailure(ui)
1132 1134
1133 return True, r, deleted 1135 return True, r, deleted
1135 if not r and back is not None: 1137 if not r and back is not None:
1136 back.remove() 1138 back.remove()
1137 1139
1138 1140
1139 def _haltmerge(): 1141 def _haltmerge():
1140 msg = _('merge halted after failed merge (see hg resolve)') 1142 msg = _(b'merge halted after failed merge (see hg resolve)')
1141 raise error.InterventionRequired(msg) 1143 raise error.InterventionRequired(msg)
1142 1144
1143 1145
1144 def _onfilemergefailure(ui): 1146 def _onfilemergefailure(ui):
1145 action = ui.config('merge', 'on-failure') 1147 action = ui.config(b'merge', b'on-failure')
1146 if action == 'prompt': 1148 if action == b'prompt':
1147 msg = _('continue merge operation (yn)?' '$$ &Yes $$ &No') 1149 msg = _(b'continue merge operation (yn)?' b'$$ &Yes $$ &No')
1148 if ui.promptchoice(msg, 0) == 1: 1150 if ui.promptchoice(msg, 0) == 1:
1149 _haltmerge() 1151 _haltmerge()
1150 if action == 'halt': 1152 if action == b'halt':
1151 _haltmerge() 1153 _haltmerge()
1152 # default action is 'continue', in which case we neither prompt nor halt 1154 # default action is 'continue', in which case we neither prompt nor halt
1153 1155
1154 1156
1155 def hasconflictmarkers(data): 1157 def hasconflictmarkers(data):
1156 return bool( 1158 return bool(
1157 re.search("^(<<<<<<< .*|=======|>>>>>>> .*)$", data, re.MULTILINE) 1159 re.search(b"^(<<<<<<< .*|=======|>>>>>>> .*)$", data, re.MULTILINE)
1158 ) 1160 )
1159 1161
1160 1162
1161 def _check(repo, r, ui, tool, fcd, files): 1163 def _check(repo, r, ui, tool, fcd, files):
1162 fd = fcd.path() 1164 fd = fcd.path()
1163 uipathfn = scmutil.getuipathfn(repo) 1165 uipathfn = scmutil.getuipathfn(repo)
1164 unused, unused, unused, back = files 1166 unused, unused, unused, back = files
1165 1167
1166 if not r and ( 1168 if not r and (
1167 _toolbool(ui, tool, "checkconflicts") 1169 _toolbool(ui, tool, b"checkconflicts")
1168 or 'conflicts' in _toollist(ui, tool, "check") 1170 or b'conflicts' in _toollist(ui, tool, b"check")
1169 ): 1171 ):
1170 if hasconflictmarkers(fcd.data()): 1172 if hasconflictmarkers(fcd.data()):
1171 r = 1 1173 r = 1
1172 1174
1173 checked = False 1175 checked = False
1174 if 'prompt' in _toollist(ui, tool, "check"): 1176 if b'prompt' in _toollist(ui, tool, b"check"):
1175 checked = True 1177 checked = True
1176 if ui.promptchoice( 1178 if ui.promptchoice(
1177 _("was merge of '%s' successful (yn)?" "$$ &Yes $$ &No") 1179 _(b"was merge of '%s' successful (yn)?" b"$$ &Yes $$ &No")
1178 % uipathfn(fd), 1180 % uipathfn(fd),
1179 1, 1181 1,
1180 ): 1182 ):
1181 r = 1 1183 r = 1
1182 1184
1183 if ( 1185 if (
1184 not r 1186 not r
1185 and not checked 1187 and not checked
1186 and ( 1188 and (
1187 _toolbool(ui, tool, "checkchanged") 1189 _toolbool(ui, tool, b"checkchanged")
1188 or 'changed' in _toollist(ui, tool, "check") 1190 or b'changed' in _toollist(ui, tool, b"check")
1189 ) 1191 )
1190 ): 1192 ):
1191 if back is not None and not fcd.cmp(back): 1193 if back is not None and not fcd.cmp(back):
1192 if ui.promptchoice( 1194 if ui.promptchoice(
1193 _( 1195 _(
1194 " output file %s appears unchanged\n" 1196 b" output file %s appears unchanged\n"
1195 "was merge successful (yn)?" 1197 b"was merge successful (yn)?"
1196 "$$ &Yes $$ &No" 1198 b"$$ &Yes $$ &No"
1197 ) 1199 )
1198 % uipathfn(fd), 1200 % uipathfn(fd),
1199 1, 1201 1,
1200 ): 1202 ):
1201 r = 1 1203 r = 1
1202 1204
1203 if back is not None and _toolbool(ui, tool, "fixeol"): 1205 if back is not None and _toolbool(ui, tool, b"fixeol"):
1204 _matcheol(_workingpath(repo, fcd), back) 1206 _matcheol(_workingpath(repo, fcd), back)
1205 1207
1206 return r 1208 return r
1207 1209
1208 1210
1224 1226
1225 def loadinternalmerge(ui, extname, registrarobj): 1227 def loadinternalmerge(ui, extname, registrarobj):
1226 """Load internal merge tool from specified registrarobj 1228 """Load internal merge tool from specified registrarobj
1227 """ 1229 """
1228 for name, func in registrarobj._table.iteritems(): 1230 for name, func in registrarobj._table.iteritems():
1229 fullname = ':' + name 1231 fullname = b':' + name
1230 internals[fullname] = func 1232 internals[fullname] = func
1231 internals['internal:' + name] = func 1233 internals[b'internal:' + name] = func
1232 internalsdoc[fullname] = func 1234 internalsdoc[fullname] = func
1233 1235
1234 capabilities = sorted([k for k, v in func.capabilities.items() if v]) 1236 capabilities = sorted([k for k, v in func.capabilities.items() if v])
1235 if capabilities: 1237 if capabilities:
1236 capdesc = " (actual capabilities: %s)" % ', '.join(capabilities) 1238 capdesc = b" (actual capabilities: %s)" % b', '.join(
1237 func.__doc__ = func.__doc__ + pycompat.sysstr("\n\n%s" % capdesc) 1239 capabilities
1240 )
1241 func.__doc__ = func.__doc__ + pycompat.sysstr(b"\n\n%s" % capdesc)
1238 1242
1239 # to put i18n comments into hg.pot for automatically generated texts 1243 # to put i18n comments into hg.pot for automatically generated texts
1240 1244
1241 # i18n: "binary" and "symlink" are keywords 1245 # i18n: "binary" and "symlink" are keywords
1242 # i18n: this text is added automatically 1246 # i18n: this text is added automatically
1243 _(" (actual capabilities: binary, symlink)") 1247 _(b" (actual capabilities: binary, symlink)")
1244 # i18n: "binary" is keyword 1248 # i18n: "binary" is keyword
1245 # i18n: this text is added automatically 1249 # i18n: this text is added automatically
1246 _(" (actual capabilities: binary)") 1250 _(b" (actual capabilities: binary)")
1247 # i18n: "symlink" is keyword 1251 # i18n: "symlink" is keyword
1248 # i18n: this text is added automatically 1252 # i18n: this text is added automatically
1249 _(" (actual capabilities: symlink)") 1253 _(b" (actual capabilities: symlink)")
1250 1254
1251 1255
1252 # load built-in merge tools explicitly to setup internalsdoc 1256 # load built-in merge tools explicitly to setup internalsdoc
1253 loadinternalmerge(None, None, internaltool) 1257 loadinternalmerge(None, None, internaltool)
1254 1258