##// END OF EJS Templates
filemerge: optionally strip quotes from merge marker template (BC)...
Yuya Nishihara -
r32047:458f7294 default
parent child Browse files
Show More
@@ -1,720 +1,721
1 1 # filemerge.py - file-level merge handling for Mercurial
2 2 #
3 3 # Copyright 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import filecmp
11 11 import os
12 12 import re
13 13 import tempfile
14 14
15 15 from .i18n import _
16 16 from .node import nullid, short
17 17
18 18 from . import (
19 19 encoding,
20 20 error,
21 21 formatter,
22 22 match,
23 23 pycompat,
24 24 scmutil,
25 25 simplemerge,
26 26 tagmerge,
27 27 templatekw,
28 28 templater,
29 29 util,
30 30 )
31 31
32 32 def _toolstr(ui, tool, part, default=""):
33 33 return ui.config("merge-tools", tool + "." + part, default)
34 34
35 35 def _toolbool(ui, tool, part, default=False):
36 36 return ui.configbool("merge-tools", tool + "." + part, default)
37 37
38 38 def _toollist(ui, tool, part, default=None):
39 39 if default is None:
40 40 default = []
41 41 return ui.configlist("merge-tools", tool + "." + part, default)
42 42
43 43 internals = {}
44 44 # Merge tools to document.
45 45 internalsdoc = {}
46 46
47 47 # internal tool merge types
48 48 nomerge = None
49 49 mergeonly = 'mergeonly' # just the full merge, no premerge
50 50 fullmerge = 'fullmerge' # both premerge and merge
51 51
52 52 class absentfilectx(object):
53 53 """Represents a file that's ostensibly in a context but is actually not
54 54 present in it.
55 55
56 56 This is here because it's very specific to the filemerge code for now --
57 57 other code is likely going to break with the values this returns."""
58 58 def __init__(self, ctx, f):
59 59 self._ctx = ctx
60 60 self._f = f
61 61
62 62 def path(self):
63 63 return self._f
64 64
65 65 def size(self):
66 66 return None
67 67
68 68 def data(self):
69 69 return None
70 70
71 71 def filenode(self):
72 72 return nullid
73 73
74 74 _customcmp = True
75 75 def cmp(self, fctx):
76 76 """compare with other file context
77 77
78 78 returns True if different from fctx.
79 79 """
80 80 return not (fctx.isabsent() and
81 81 fctx.ctx() == self.ctx() and
82 82 fctx.path() == self.path())
83 83
84 84 def flags(self):
85 85 return ''
86 86
87 87 def changectx(self):
88 88 return self._ctx
89 89
90 90 def isbinary(self):
91 91 return False
92 92
93 93 def isabsent(self):
94 94 return True
95 95
96 96 def internaltool(name, mergetype, onfailure=None, precheck=None):
97 97 '''return a decorator for populating internal merge tool table'''
98 98 def decorator(func):
99 99 fullname = ':' + name
100 100 func.__doc__ = (pycompat.sysstr("``%s``\n" % fullname)
101 101 + func.__doc__.strip())
102 102 internals[fullname] = func
103 103 internals['internal:' + name] = func
104 104 internalsdoc[fullname] = func
105 105 func.mergetype = mergetype
106 106 func.onfailure = onfailure
107 107 func.precheck = precheck
108 108 return func
109 109 return decorator
110 110
111 111 def _findtool(ui, tool):
112 112 if tool in internals:
113 113 return tool
114 114 return findexternaltool(ui, tool)
115 115
116 116 def findexternaltool(ui, tool):
117 117 for kn in ("regkey", "regkeyalt"):
118 118 k = _toolstr(ui, tool, kn)
119 119 if not k:
120 120 continue
121 121 p = util.lookupreg(k, _toolstr(ui, tool, "regname"))
122 122 if p:
123 123 p = util.findexe(p + _toolstr(ui, tool, "regappend"))
124 124 if p:
125 125 return p
126 126 exe = _toolstr(ui, tool, "executable", tool)
127 127 return util.findexe(util.expandpath(exe))
128 128
129 129 def _picktool(repo, ui, path, binary, symlink, changedelete):
130 130 def supportscd(tool):
131 131 return tool in internals and internals[tool].mergetype == nomerge
132 132
133 133 def check(tool, pat, symlink, binary, changedelete):
134 134 tmsg = tool
135 135 if pat:
136 136 tmsg += " specified for " + pat
137 137 if not _findtool(ui, tool):
138 138 if pat: # explicitly requested tool deserves a warning
139 139 ui.warn(_("couldn't find merge tool %s\n") % tmsg)
140 140 else: # configured but non-existing tools are more silent
141 141 ui.note(_("couldn't find merge tool %s\n") % tmsg)
142 142 elif symlink and not _toolbool(ui, tool, "symlink"):
143 143 ui.warn(_("tool %s can't handle symlinks\n") % tmsg)
144 144 elif binary and not _toolbool(ui, tool, "binary"):
145 145 ui.warn(_("tool %s can't handle binary\n") % tmsg)
146 146 elif changedelete and not supportscd(tool):
147 147 # the nomerge tools are the only tools that support change/delete
148 148 # conflicts
149 149 pass
150 150 elif not util.gui() and _toolbool(ui, tool, "gui"):
151 151 ui.warn(_("tool %s requires a GUI\n") % tmsg)
152 152 else:
153 153 return True
154 154 return False
155 155
156 156 # internal config: ui.forcemerge
157 157 # forcemerge comes from command line arguments, highest priority
158 158 force = ui.config('ui', 'forcemerge')
159 159 if force:
160 160 toolpath = _findtool(ui, force)
161 161 if changedelete and not supportscd(toolpath):
162 162 return ":prompt", None
163 163 else:
164 164 if toolpath:
165 165 return (force, util.shellquote(toolpath))
166 166 else:
167 167 # mimic HGMERGE if given tool not found
168 168 return (force, force)
169 169
170 170 # HGMERGE takes next precedence
171 171 hgmerge = encoding.environ.get("HGMERGE")
172 172 if hgmerge:
173 173 if changedelete and not supportscd(hgmerge):
174 174 return ":prompt", None
175 175 else:
176 176 return (hgmerge, hgmerge)
177 177
178 178 # then patterns
179 179 for pat, tool in ui.configitems("merge-patterns"):
180 180 mf = match.match(repo.root, '', [pat])
181 181 if mf(path) and check(tool, pat, symlink, False, changedelete):
182 182 toolpath = _findtool(ui, tool)
183 183 return (tool, util.shellquote(toolpath))
184 184
185 185 # then merge tools
186 186 tools = {}
187 187 disabled = set()
188 188 for k, v in ui.configitems("merge-tools"):
189 189 t = k.split('.')[0]
190 190 if t not in tools:
191 191 tools[t] = int(_toolstr(ui, t, "priority", "0"))
192 192 if _toolbool(ui, t, "disabled", False):
193 193 disabled.add(t)
194 194 names = tools.keys()
195 195 tools = sorted([(-p, tool) for tool, p in tools.items()
196 196 if tool not in disabled])
197 197 uimerge = ui.config("ui", "merge")
198 198 if uimerge:
199 199 # external tools defined in uimerge won't be able to handle
200 200 # change/delete conflicts
201 201 if uimerge not in names and not changedelete:
202 202 return (uimerge, uimerge)
203 203 tools.insert(0, (None, uimerge)) # highest priority
204 204 tools.append((None, "hgmerge")) # the old default, if found
205 205 for p, t in tools:
206 206 if check(t, None, symlink, binary, changedelete):
207 207 toolpath = _findtool(ui, t)
208 208 return (t, util.shellquote(toolpath))
209 209
210 210 # internal merge or prompt as last resort
211 211 if symlink or binary or changedelete:
212 212 return ":prompt", None
213 213 return ":merge", None
214 214
215 215 def _eoltype(data):
216 216 "Guess the EOL type of a file"
217 217 if '\0' in data: # binary
218 218 return None
219 219 if '\r\n' in data: # Windows
220 220 return '\r\n'
221 221 if '\r' in data: # Old Mac
222 222 return '\r'
223 223 if '\n' in data: # UNIX
224 224 return '\n'
225 225 return None # unknown
226 226
227 227 def _matcheol(file, origfile):
228 228 "Convert EOL markers in a file to match origfile"
229 229 tostyle = _eoltype(util.readfile(origfile))
230 230 if tostyle:
231 231 data = util.readfile(file)
232 232 style = _eoltype(data)
233 233 if style:
234 234 newdata = data.replace(style, tostyle)
235 235 if newdata != data:
236 236 util.writefile(file, newdata)
237 237
238 238 @internaltool('prompt', nomerge)
239 239 def _iprompt(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
240 240 """Asks the user which of the local `p1()` or the other `p2()` version to
241 241 keep as the merged version."""
242 242 ui = repo.ui
243 243 fd = fcd.path()
244 244
245 245 prompts = partextras(labels)
246 246 prompts['fd'] = fd
247 247 try:
248 248 if fco.isabsent():
249 249 index = ui.promptchoice(
250 250 _("local%(l)s changed %(fd)s which other%(o)s deleted\n"
251 251 "use (c)hanged version, (d)elete, or leave (u)nresolved?"
252 252 "$$ &Changed $$ &Delete $$ &Unresolved") % prompts, 2)
253 253 choice = ['local', 'other', 'unresolved'][index]
254 254 elif fcd.isabsent():
255 255 index = ui.promptchoice(
256 256 _("other%(o)s changed %(fd)s which local%(l)s deleted\n"
257 257 "use (c)hanged version, leave (d)eleted, or "
258 258 "leave (u)nresolved?"
259 259 "$$ &Changed $$ &Deleted $$ &Unresolved") % prompts, 2)
260 260 choice = ['other', 'local', 'unresolved'][index]
261 261 else:
262 262 index = ui.promptchoice(
263 263 _("no tool found to merge %(fd)s\n"
264 264 "keep (l)ocal%(l)s, take (o)ther%(o)s, or leave (u)nresolved?"
265 265 "$$ &Local $$ &Other $$ &Unresolved") % prompts, 2)
266 266 choice = ['local', 'other', 'unresolved'][index]
267 267
268 268 if choice == 'other':
269 269 return _iother(repo, mynode, orig, fcd, fco, fca, toolconf,
270 270 labels)
271 271 elif choice == 'local':
272 272 return _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf,
273 273 labels)
274 274 elif choice == 'unresolved':
275 275 return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf,
276 276 labels)
277 277 except error.ResponseExpected:
278 278 ui.write("\n")
279 279 return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf,
280 280 labels)
281 281
282 282 @internaltool('local', nomerge)
283 283 def _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
284 284 """Uses the local `p1()` version of files as the merged version."""
285 285 return 0, fcd.isabsent()
286 286
287 287 @internaltool('other', nomerge)
288 288 def _iother(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
289 289 """Uses the other `p2()` version of files as the merged version."""
290 290 if fco.isabsent():
291 291 # local changed, remote deleted -- 'deleted' picked
292 292 repo.wvfs.unlinkpath(fcd.path())
293 293 deleted = True
294 294 else:
295 295 repo.wwrite(fcd.path(), fco.data(), fco.flags())
296 296 deleted = False
297 297 return 0, deleted
298 298
299 299 @internaltool('fail', nomerge)
300 300 def _ifail(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
301 301 """
302 302 Rather than attempting to merge files that were modified on both
303 303 branches, it marks them as unresolved. The resolve command must be
304 304 used to resolve these conflicts."""
305 305 # for change/delete conflicts write out the changed version, then fail
306 306 if fcd.isabsent():
307 307 repo.wwrite(fcd.path(), fco.data(), fco.flags())
308 308 return 1, False
309 309
310 310 def _premerge(repo, fcd, fco, fca, toolconf, files, labels=None):
311 311 tool, toolpath, binary, symlink = toolconf
312 312 if symlink or fcd.isabsent() or fco.isabsent():
313 313 return 1
314 314 a, b, c, back = files
315 315
316 316 ui = repo.ui
317 317
318 318 validkeep = ['keep', 'keep-merge3']
319 319
320 320 # do we attempt to simplemerge first?
321 321 try:
322 322 premerge = _toolbool(ui, tool, "premerge", not binary)
323 323 except error.ConfigError:
324 324 premerge = _toolstr(ui, tool, "premerge").lower()
325 325 if premerge not in validkeep:
326 326 _valid = ', '.join(["'" + v + "'" for v in validkeep])
327 327 raise error.ConfigError(_("%s.premerge not valid "
328 328 "('%s' is neither boolean nor %s)") %
329 329 (tool, premerge, _valid))
330 330
331 331 if premerge:
332 332 if premerge == 'keep-merge3':
333 333 if not labels:
334 334 labels = _defaultconflictlabels
335 335 if len(labels) < 3:
336 336 labels.append('base')
337 337 r = simplemerge.simplemerge(ui, a, b, c, quiet=True, label=labels)
338 338 if not r:
339 339 ui.debug(" premerge successful\n")
340 340 return 0
341 341 if premerge not in validkeep:
342 342 util.copyfile(back, a) # restore from backup and try again
343 343 return 1 # continue merging
344 344
345 345 def _mergecheck(repo, mynode, orig, fcd, fco, fca, toolconf):
346 346 tool, toolpath, binary, symlink = toolconf
347 347 if symlink:
348 348 repo.ui.warn(_('warning: internal %s cannot merge symlinks '
349 349 'for %s\n') % (tool, fcd.path()))
350 350 return False
351 351 if fcd.isabsent() or fco.isabsent():
352 352 repo.ui.warn(_('warning: internal %s cannot merge change/delete '
353 353 'conflict for %s\n') % (tool, fcd.path()))
354 354 return False
355 355 return True
356 356
357 357 def _merge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels, mode):
358 358 """
359 359 Uses the internal non-interactive simple merge algorithm for merging
360 360 files. It will fail if there are any conflicts and leave markers in
361 361 the partially merged file. Markers will have two sections, one for each side
362 362 of merge, unless mode equals 'union' which suppresses the markers."""
363 363 a, b, c, back = files
364 364
365 365 ui = repo.ui
366 366
367 367 r = simplemerge.simplemerge(ui, a, b, c, label=labels, mode=mode)
368 368 return True, r, False
369 369
370 370 @internaltool('union', fullmerge,
371 371 _("warning: conflicts while merging %s! "
372 372 "(edit, then use 'hg resolve --mark')\n"),
373 373 precheck=_mergecheck)
374 374 def _iunion(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
375 375 """
376 376 Uses the internal non-interactive simple merge algorithm for merging
377 377 files. It will use both left and right sides for conflict regions.
378 378 No markers are inserted."""
379 379 return _merge(repo, mynode, orig, fcd, fco, fca, toolconf,
380 380 files, labels, 'union')
381 381
382 382 @internaltool('merge', fullmerge,
383 383 _("warning: conflicts while merging %s! "
384 384 "(edit, then use 'hg resolve --mark')\n"),
385 385 precheck=_mergecheck)
386 386 def _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
387 387 """
388 388 Uses the internal non-interactive simple merge algorithm for merging
389 389 files. It will fail if there are any conflicts and leave markers in
390 390 the partially merged file. Markers will have two sections, one for each side
391 391 of merge."""
392 392 return _merge(repo, mynode, orig, fcd, fco, fca, toolconf,
393 393 files, labels, 'merge')
394 394
395 395 @internaltool('merge3', fullmerge,
396 396 _("warning: conflicts while merging %s! "
397 397 "(edit, then use 'hg resolve --mark')\n"),
398 398 precheck=_mergecheck)
399 399 def _imerge3(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
400 400 """
401 401 Uses the internal non-interactive simple merge algorithm for merging
402 402 files. It will fail if there are any conflicts and leave markers in
403 403 the partially merged file. Marker will have three sections, one from each
404 404 side of the merge and one for the base content."""
405 405 if not labels:
406 406 labels = _defaultconflictlabels
407 407 if len(labels) < 3:
408 408 labels.append('base')
409 409 return _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels)
410 410
411 411 def _imergeauto(repo, mynode, orig, fcd, fco, fca, toolconf, files,
412 412 labels=None, localorother=None):
413 413 """
414 414 Generic driver for _imergelocal and _imergeother
415 415 """
416 416 assert localorother is not None
417 417 tool, toolpath, binary, symlink = toolconf
418 418 a, b, c, back = files
419 419 r = simplemerge.simplemerge(repo.ui, a, b, c, label=labels,
420 420 localorother=localorother)
421 421 return True, r
422 422
423 423 @internaltool('merge-local', mergeonly, precheck=_mergecheck)
424 424 def _imergelocal(*args, **kwargs):
425 425 """
426 426 Like :merge, but resolve all conflicts non-interactively in favor
427 427 of the local `p1()` changes."""
428 428 success, status = _imergeauto(localorother='local', *args, **kwargs)
429 429 return success, status, False
430 430
431 431 @internaltool('merge-other', mergeonly, precheck=_mergecheck)
432 432 def _imergeother(*args, **kwargs):
433 433 """
434 434 Like :merge, but resolve all conflicts non-interactively in favor
435 435 of the other `p2()` changes."""
436 436 success, status = _imergeauto(localorother='other', *args, **kwargs)
437 437 return success, status, False
438 438
439 439 @internaltool('tagmerge', mergeonly,
440 440 _("automatic tag merging of %s failed! "
441 441 "(use 'hg resolve --tool :merge' or another merge "
442 442 "tool of your choice)\n"))
443 443 def _itagmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
444 444 """
445 445 Uses the internal tag merge algorithm (experimental).
446 446 """
447 447 success, status = tagmerge.merge(repo, fcd, fco, fca)
448 448 return success, status, False
449 449
450 450 @internaltool('dump', fullmerge)
451 451 def _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
452 452 """
453 453 Creates three versions of the files to merge, containing the
454 454 contents of local, other and base. These files can then be used to
455 455 perform a merge manually. If the file to be merged is named
456 456 ``a.txt``, these files will accordingly be named ``a.txt.local``,
457 457 ``a.txt.other`` and ``a.txt.base`` and they will be placed in the
458 458 same directory as ``a.txt``."""
459 459 a, b, c, back = files
460 460
461 461 fd = fcd.path()
462 462
463 463 util.copyfile(a, a + ".local")
464 464 repo.wwrite(fd + ".other", fco.data(), fco.flags())
465 465 repo.wwrite(fd + ".base", fca.data(), fca.flags())
466 466 return False, 1, False
467 467
468 468 def _xmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
469 469 tool, toolpath, binary, symlink = toolconf
470 470 if fcd.isabsent() or fco.isabsent():
471 471 repo.ui.warn(_('warning: %s cannot merge change/delete conflict '
472 472 'for %s\n') % (tool, fcd.path()))
473 473 return False, 1, None
474 474 a, b, c, back = files
475 475 out = ""
476 476 env = {'HG_FILE': fcd.path(),
477 477 'HG_MY_NODE': short(mynode),
478 478 'HG_OTHER_NODE': str(fco.changectx()),
479 479 'HG_BASE_NODE': str(fca.changectx()),
480 480 'HG_MY_ISLINK': 'l' in fcd.flags(),
481 481 'HG_OTHER_ISLINK': 'l' in fco.flags(),
482 482 'HG_BASE_ISLINK': 'l' in fca.flags(),
483 483 }
484 484
485 485 ui = repo.ui
486 486
487 487 args = _toolstr(ui, tool, "args", '$local $base $other')
488 488 if "$output" in args:
489 489 out, a = a, back # read input from backup, write to original
490 490 replace = {'local': a, 'base': b, 'other': c, 'output': out}
491 491 args = util.interpolate(r'\$', replace, args,
492 492 lambda s: util.shellquote(util.localpath(s)))
493 493 cmd = toolpath + ' ' + args
494 494 if _toolbool(ui, tool, "gui"):
495 495 repo.ui.status(_('running merge tool %s for file %s\n') %
496 496 (tool, fcd.path()))
497 497 repo.ui.debug('launching merge tool: %s\n' % cmd)
498 498 r = ui.system(cmd, cwd=repo.root, environ=env, blockedtag='mergetool')
499 499 repo.ui.debug('merge tool returned: %s\n' % r)
500 500 return True, r, False
501 501
502 502 def _formatconflictmarker(repo, ctx, template, label, pad):
503 503 """Applies the given template to the ctx, prefixed by the label.
504 504
505 505 Pad is the minimum width of the label prefix, so that multiple markers
506 506 can have aligned templated parts.
507 507 """
508 508 if ctx.node() is None:
509 509 ctx = ctx.p1()
510 510
511 511 props = templatekw.keywords.copy()
512 512 props['templ'] = template
513 513 props['ctx'] = ctx
514 514 props['repo'] = repo
515 515 templateresult = template('conflictmarker', **props)
516 516
517 517 label = ('%s:' % label).ljust(pad + 1)
518 518 mark = '%s %s' % (label, templater.stringify(templateresult))
519 519
520 520 if mark:
521 521 mark = mark.splitlines()[0] # split for safety
522 522
523 523 # 8 for the prefix of conflict marker lines (e.g. '<<<<<<< ')
524 524 return util.ellipsis(mark, 80 - 8)
525 525
526 526 _defaultconflictmarker = ('{node|short} '
527 527 '{ifeq(tags, "tip", "", '
528 528 'ifeq(tags, "", "", "{tags} "))}'
529 529 '{if(bookmarks, "{bookmarks} ")}'
530 530 '{ifeq(branch, "default", "", "{branch} ")}'
531 531 '- {author|user}: {desc|firstline}')
532 532
533 533 _defaultconflictlabels = ['local', 'other']
534 534
535 535 def _formatlabels(repo, fcd, fco, fca, labels):
536 536 """Formats the given labels using the conflict marker template.
537 537
538 538 Returns a list of formatted labels.
539 539 """
540 540 cd = fcd.changectx()
541 541 co = fco.changectx()
542 542 ca = fca.changectx()
543 543
544 544 ui = repo.ui
545 545 template = ui.config('ui', 'mergemarkertemplate', _defaultconflictmarker)
546 template = templater.unquotestring(template)
546 547 tmpl = formatter.maketemplater(ui, 'conflictmarker', template)
547 548
548 549 pad = max(len(l) for l in labels)
549 550
550 551 newlabels = [_formatconflictmarker(repo, cd, tmpl, labels[0], pad),
551 552 _formatconflictmarker(repo, co, tmpl, labels[1], pad)]
552 553 if len(labels) > 2:
553 554 newlabels.append(_formatconflictmarker(repo, ca, tmpl, labels[2], pad))
554 555 return newlabels
555 556
556 557 def partextras(labels):
557 558 """Return a dictionary of extra labels for use in prompts to the user
558 559
559 560 Intended use is in strings of the form "(l)ocal%(l)s".
560 561 """
561 562 if labels is None:
562 563 return {
563 564 "l": "",
564 565 "o": "",
565 566 }
566 567
567 568 return {
568 569 "l": " [%s]" % labels[0],
569 570 "o": " [%s]" % labels[1],
570 571 }
571 572
572 573 def _filemerge(premerge, repo, mynode, orig, fcd, fco, fca, labels=None):
573 574 """perform a 3-way merge in the working directory
574 575
575 576 premerge = whether this is a premerge
576 577 mynode = parent node before merge
577 578 orig = original local filename before merge
578 579 fco = other file context
579 580 fca = ancestor file context
580 581 fcd = local file context for current/destination file
581 582
582 583 Returns whether the merge is complete, the return value of the merge, and
583 584 a boolean indicating whether the file was deleted from disk."""
584 585
585 586 def temp(prefix, ctx):
586 587 fullbase, ext = os.path.splitext(ctx.path())
587 588 pre = "%s~%s." % (os.path.basename(fullbase), prefix)
588 589 (fd, name) = tempfile.mkstemp(prefix=pre, suffix=ext)
589 590 data = repo.wwritedata(ctx.path(), ctx.data())
590 591 f = os.fdopen(fd, pycompat.sysstr("wb"))
591 592 f.write(data)
592 593 f.close()
593 594 return name
594 595
595 596 if not fco.cmp(fcd): # files identical?
596 597 return True, None, False
597 598
598 599 ui = repo.ui
599 600 fd = fcd.path()
600 601 binary = fcd.isbinary() or fco.isbinary() or fca.isbinary()
601 602 symlink = 'l' in fcd.flags() + fco.flags()
602 603 changedelete = fcd.isabsent() or fco.isabsent()
603 604 tool, toolpath = _picktool(repo, ui, fd, binary, symlink, changedelete)
604 605 if tool in internals and tool.startswith('internal:'):
605 606 # normalize to new-style names (':merge' etc)
606 607 tool = tool[len('internal'):]
607 608 ui.debug("picked tool '%s' for %s (binary %s symlink %s changedelete %s)\n"
608 609 % (tool, fd, binary, symlink, changedelete))
609 610
610 611 if tool in internals:
611 612 func = internals[tool]
612 613 mergetype = func.mergetype
613 614 onfailure = func.onfailure
614 615 precheck = func.precheck
615 616 else:
616 617 func = _xmerge
617 618 mergetype = fullmerge
618 619 onfailure = _("merging %s failed!\n")
619 620 precheck = None
620 621
621 622 toolconf = tool, toolpath, binary, symlink
622 623
623 624 if mergetype == nomerge:
624 625 r, deleted = func(repo, mynode, orig, fcd, fco, fca, toolconf, labels)
625 626 return True, r, deleted
626 627
627 628 if premerge:
628 629 if orig != fco.path():
629 630 ui.status(_("merging %s and %s to %s\n") % (orig, fco.path(), fd))
630 631 else:
631 632 ui.status(_("merging %s\n") % fd)
632 633
633 634 ui.debug("my %s other %s ancestor %s\n" % (fcd, fco, fca))
634 635
635 636 if precheck and not precheck(repo, mynode, orig, fcd, fco, fca,
636 637 toolconf):
637 638 if onfailure:
638 639 ui.warn(onfailure % fd)
639 640 return True, 1, False
640 641
641 642 a = repo.wjoin(fd)
642 643 b = temp("base", fca)
643 644 c = temp("other", fco)
644 645 if not fcd.isabsent():
645 646 back = scmutil.origpath(ui, repo, a)
646 647 if premerge:
647 648 util.copyfile(a, back)
648 649 else:
649 650 back = None
650 651 files = (a, b, c, back)
651 652
652 653 r = 1
653 654 try:
654 655 markerstyle = ui.config('ui', 'mergemarkers', 'basic')
655 656 if not labels:
656 657 labels = _defaultconflictlabels
657 658 if markerstyle != 'basic':
658 659 labels = _formatlabels(repo, fcd, fco, fca, labels)
659 660
660 661 if premerge and mergetype == fullmerge:
661 662 r = _premerge(repo, fcd, fco, fca, toolconf, files, labels=labels)
662 663 # complete if premerge successful (r is 0)
663 664 return not r, r, False
664 665
665 666 needcheck, r, deleted = func(repo, mynode, orig, fcd, fco, fca,
666 667 toolconf, files, labels=labels)
667 668
668 669 if needcheck:
669 670 r = _check(r, ui, tool, fcd, files)
670 671
671 672 if r:
672 673 if onfailure:
673 674 ui.warn(onfailure % fd)
674 675
675 676 return True, r, deleted
676 677 finally:
677 678 if not r and back is not None:
678 679 util.unlink(back)
679 680 util.unlink(b)
680 681 util.unlink(c)
681 682
682 683 def _check(r, ui, tool, fcd, files):
683 684 fd = fcd.path()
684 685 a, b, c, back = files
685 686
686 687 if not r and (_toolbool(ui, tool, "checkconflicts") or
687 688 'conflicts' in _toollist(ui, tool, "check")):
688 689 if re.search("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcd.data(),
689 690 re.MULTILINE):
690 691 r = 1
691 692
692 693 checked = False
693 694 if 'prompt' in _toollist(ui, tool, "check"):
694 695 checked = True
695 696 if ui.promptchoice(_("was merge of '%s' successful (yn)?"
696 697 "$$ &Yes $$ &No") % fd, 1):
697 698 r = 1
698 699
699 700 if not r and not checked and (_toolbool(ui, tool, "checkchanged") or
700 701 'changed' in
701 702 _toollist(ui, tool, "check")):
702 703 if back is not None and filecmp.cmp(a, back):
703 704 if ui.promptchoice(_(" output file %s appears unchanged\n"
704 705 "was merge successful (yn)?"
705 706 "$$ &Yes $$ &No") % fd, 1):
706 707 r = 1
707 708
708 709 if back is not None and _toolbool(ui, tool, "fixeol"):
709 710 _matcheol(a, back)
710 711
711 712 return r
712 713
713 714 def premerge(repo, mynode, orig, fcd, fco, fca, labels=None):
714 715 return _filemerge(True, repo, mynode, orig, fcd, fco, fca, labels=labels)
715 716
716 717 def filemerge(repo, mynode, orig, fcd, fco, fca, labels=None):
717 718 return _filemerge(False, repo, mynode, orig, fcd, fco, fca, labels=labels)
718 719
719 720 # tell hggettext to extract docstrings from these functions:
720 721 i18nfunctions = internals.values()
@@ -1,276 +1,279
1 1 $ hg init
2 2 $ cat << EOF > a
3 3 > Small Mathematical Series.
4 4 > One
5 5 > Two
6 6 > Three
7 7 > Four
8 8 > Five
9 9 > Hop we are done.
10 10 > EOF
11 11 $ hg add a
12 12 $ hg commit -m ancestor
13 13 $ cat << EOF > a
14 14 > Small Mathematical Series.
15 15 > 1
16 16 > 2
17 17 > 3
18 18 > 4
19 19 > 5
20 20 > Hop we are done.
21 21 > EOF
22 22 $ hg commit -m branch1
23 23 $ hg co 0
24 24 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
25 25 $ cat << EOF > a
26 26 > Small Mathematical Series.
27 27 > 1
28 28 > 2
29 29 > 3
30 30 > 6
31 31 > 8
32 32 > Hop we are done.
33 33 > EOF
34 34 $ hg commit -m branch2
35 35 created new head
36 36
37 37 $ hg merge 1
38 38 merging a
39 39 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
40 40 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
41 41 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
42 42 [1]
43 43
44 44 $ hg id
45 45 618808747361+c0c68e4fe667+ tip
46 46
47 47 $ cat a
48 48 Small Mathematical Series.
49 49 1
50 50 2
51 51 3
52 52 <<<<<<< working copy: 618808747361 - test: branch2
53 53 6
54 54 8
55 55 =======
56 56 4
57 57 5
58 58 >>>>>>> merge rev: c0c68e4fe667 - test: branch1
59 59 Hop we are done.
60 60
61 61 $ hg status
62 62 M a
63 63 ? a.orig
64 64
65 65 Verify custom conflict markers
66 66
67 67 $ hg up -q --clean .
68 $ printf "\n[ui]\nmergemarkertemplate={author} {rev}\n" >> .hg/hgrc
68 $ cat <<EOF >> .hg/hgrc
69 > [ui]
70 > mergemarkertemplate = '{author} {rev}'
71 > EOF
69 72
70 73 $ hg merge 1
71 74 merging a
72 75 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
73 76 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
74 77 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
75 78 [1]
76 79
77 80 $ cat a
78 81 Small Mathematical Series.
79 82 1
80 83 2
81 84 3
82 85 <<<<<<< working copy: test 2
83 86 6
84 87 8
85 88 =======
86 89 4
87 90 5
88 91 >>>>>>> merge rev: test 1
89 92 Hop we are done.
90 93
91 94 Verify line splitting of custom conflict marker which causes multiple lines
92 95
93 96 $ hg up -q --clean .
94 97 $ cat >> .hg/hgrc <<EOF
95 98 > [ui]
96 99 > mergemarkertemplate={author} {rev}\nfoo\nbar\nbaz
97 100 > EOF
98 101
99 102 $ hg -q merge 1
100 103 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
101 104 [1]
102 105
103 106 $ cat a
104 107 Small Mathematical Series.
105 108 1
106 109 2
107 110 3
108 111 <<<<<<< working copy: test 2
109 112 6
110 113 8
111 114 =======
112 115 4
113 116 5
114 117 >>>>>>> merge rev: test 1
115 118 Hop we are done.
116 119
117 120 Verify line trimming of custom conflict marker using multi-byte characters
118 121
119 122 $ hg up -q --clean .
120 123 $ python <<EOF
121 124 > fp = open('logfile', 'w')
122 125 > fp.write('12345678901234567890123456789012345678901234567890' +
123 126 > '1234567890') # there are 5 more columns for 80 columns
124 127 >
125 128 > # 2 x 4 = 8 columns, but 3 x 4 = 12 bytes
126 129 > fp.write(u'\u3042\u3044\u3046\u3048'.encode('utf-8'))
127 130 >
128 131 > fp.close()
129 132 > EOF
130 133 $ hg add logfile
131 134 $ hg --encoding utf-8 commit --logfile logfile
132 135
133 136 $ cat >> .hg/hgrc <<EOF
134 137 > [ui]
135 138 > mergemarkertemplate={desc|firstline}
136 139 > EOF
137 140
138 141 $ hg -q --encoding utf-8 merge 1
139 142 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
140 143 [1]
141 144
142 145 $ cat a
143 146 Small Mathematical Series.
144 147 1
145 148 2
146 149 3
147 150 <<<<<<< working copy: 1234567890123456789012345678901234567890123456789012345...
148 151 6
149 152 8
150 153 =======
151 154 4
152 155 5
153 156 >>>>>>> merge rev: branch1
154 157 Hop we are done.
155 158
156 159 Verify basic conflict markers
157 160
158 161 $ hg up -q --clean 2
159 162 $ printf "\n[ui]\nmergemarkers=basic\n" >> .hg/hgrc
160 163
161 164 $ hg merge 1
162 165 merging a
163 166 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
164 167 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
165 168 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
166 169 [1]
167 170
168 171 $ cat a
169 172 Small Mathematical Series.
170 173 1
171 174 2
172 175 3
173 176 <<<<<<< working copy
174 177 6
175 178 8
176 179 =======
177 180 4
178 181 5
179 182 >>>>>>> merge rev
180 183 Hop we are done.
181 184
182 185 internal:merge3
183 186
184 187 $ hg up -q --clean .
185 188
186 189 $ hg merge 1 --tool internal:merge3
187 190 merging a
188 191 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
189 192 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
190 193 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
191 194 [1]
192 195 $ cat a
193 196 Small Mathematical Series.
194 197 <<<<<<< working copy
195 198 1
196 199 2
197 200 3
198 201 6
199 202 8
200 203 ||||||| base
201 204 One
202 205 Two
203 206 Three
204 207 Four
205 208 Five
206 209 =======
207 210 1
208 211 2
209 212 3
210 213 4
211 214 5
212 215 >>>>>>> merge rev
213 216 Hop we are done.
214 217
215 218 Add some unconflicting changes on each head, to make sure we really
216 219 are merging, unlike :local and :other
217 220
218 221 $ hg up -C
219 222 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
220 223 1 other heads for branch "default"
221 224 $ printf "\n\nEnd of file\n" >> a
222 225 $ hg ci -m "Add some stuff at the end"
223 226 $ hg up -r 1
224 227 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
225 228 $ printf "Start of file\n\n\n" > tmp
226 229 $ cat a >> tmp
227 230 $ mv tmp a
228 231 $ hg ci -m "Add some stuff at the beginning"
229 232
230 233 Now test :merge-other and :merge-local
231 234
232 235 $ hg merge
233 236 merging a
234 237 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
235 238 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
236 239 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
237 240 [1]
238 241 $ hg resolve --tool :merge-other a
239 242 merging a
240 243 (no more unresolved files)
241 244 $ cat a
242 245 Start of file
243 246
244 247
245 248 Small Mathematical Series.
246 249 1
247 250 2
248 251 3
249 252 6
250 253 8
251 254 Hop we are done.
252 255
253 256
254 257 End of file
255 258
256 259 $ hg up -C
257 260 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
258 261 1 other heads for branch "default"
259 262 $ hg merge --tool :merge-local
260 263 merging a
261 264 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
262 265 (branch merge, don't forget to commit)
263 266 $ cat a
264 267 Start of file
265 268
266 269
267 270 Small Mathematical Series.
268 271 1
269 272 2
270 273 3
271 274 4
272 275 5
273 276 Hop we are done.
274 277
275 278
276 279 End of file
General Comments 0
You need to be logged in to leave comments. Login now