Mercurial > hg
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() |