##// END OF EJS Templates
filemerge._idump: drop no longer necessary 'if r:' check...
Siddharth Agarwal -
r26573:a875773c default
parent child Browse files
Show More
@@ -1,576 +1,574 b''
1 1 # filemerge.py - file-level merge handling for Mercurial
2 2 #
3 3 # Copyright 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import 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 short
17 17
18 18 from . import (
19 19 error,
20 20 match,
21 21 simplemerge,
22 22 tagmerge,
23 23 templatekw,
24 24 templater,
25 25 util,
26 26 )
27 27
28 28 def _toolstr(ui, tool, part, default=""):
29 29 return ui.config("merge-tools", tool + "." + part, default)
30 30
31 31 def _toolbool(ui, tool, part, default=False):
32 32 return ui.configbool("merge-tools", tool + "." + part, default)
33 33
34 34 def _toollist(ui, tool, part, default=[]):
35 35 return ui.configlist("merge-tools", tool + "." + part, default)
36 36
37 37 internals = {}
38 38 # Merge tools to document.
39 39 internalsdoc = {}
40 40
41 41 # internal tool merge types
42 42 nomerge = None
43 43 mergeonly = 'mergeonly' # just the full merge, no premerge
44 44 fullmerge = 'fullmerge' # both premerge and merge
45 45
46 46 def internaltool(name, mergetype, onfailure=None, precheck=None):
47 47 '''return a decorator for populating internal merge tool table'''
48 48 def decorator(func):
49 49 fullname = ':' + name
50 50 func.__doc__ = "``%s``\n" % fullname + func.__doc__.strip()
51 51 internals[fullname] = func
52 52 internals['internal:' + name] = func
53 53 internalsdoc[fullname] = func
54 54 func.mergetype = mergetype
55 55 func.onfailure = onfailure
56 56 func.precheck = precheck
57 57 return func
58 58 return decorator
59 59
60 60 def _findtool(ui, tool):
61 61 if tool in internals:
62 62 return tool
63 63 return findexternaltool(ui, tool)
64 64
65 65 def findexternaltool(ui, tool):
66 66 for kn in ("regkey", "regkeyalt"):
67 67 k = _toolstr(ui, tool, kn)
68 68 if not k:
69 69 continue
70 70 p = util.lookupreg(k, _toolstr(ui, tool, "regname"))
71 71 if p:
72 72 p = util.findexe(p + _toolstr(ui, tool, "regappend"))
73 73 if p:
74 74 return p
75 75 exe = _toolstr(ui, tool, "executable", tool)
76 76 return util.findexe(util.expandpath(exe))
77 77
78 78 def _picktool(repo, ui, path, binary, symlink):
79 79 def check(tool, pat, symlink, binary):
80 80 tmsg = tool
81 81 if pat:
82 82 tmsg += " specified for " + pat
83 83 if not _findtool(ui, tool):
84 84 if pat: # explicitly requested tool deserves a warning
85 85 ui.warn(_("couldn't find merge tool %s\n") % tmsg)
86 86 else: # configured but non-existing tools are more silent
87 87 ui.note(_("couldn't find merge tool %s\n") % tmsg)
88 88 elif symlink and not _toolbool(ui, tool, "symlink"):
89 89 ui.warn(_("tool %s can't handle symlinks\n") % tmsg)
90 90 elif binary and not _toolbool(ui, tool, "binary"):
91 91 ui.warn(_("tool %s can't handle binary\n") % tmsg)
92 92 elif not util.gui() and _toolbool(ui, tool, "gui"):
93 93 ui.warn(_("tool %s requires a GUI\n") % tmsg)
94 94 else:
95 95 return True
96 96 return False
97 97
98 98 # internal config: ui.forcemerge
99 99 # forcemerge comes from command line arguments, highest priority
100 100 force = ui.config('ui', 'forcemerge')
101 101 if force:
102 102 toolpath = _findtool(ui, force)
103 103 if toolpath:
104 104 return (force, util.shellquote(toolpath))
105 105 else:
106 106 # mimic HGMERGE if given tool not found
107 107 return (force, force)
108 108
109 109 # HGMERGE takes next precedence
110 110 hgmerge = os.environ.get("HGMERGE")
111 111 if hgmerge:
112 112 return (hgmerge, hgmerge)
113 113
114 114 # then patterns
115 115 for pat, tool in ui.configitems("merge-patterns"):
116 116 mf = match.match(repo.root, '', [pat])
117 117 if mf(path) and check(tool, pat, symlink, False):
118 118 toolpath = _findtool(ui, tool)
119 119 return (tool, util.shellquote(toolpath))
120 120
121 121 # then merge tools
122 122 tools = {}
123 123 for k, v in ui.configitems("merge-tools"):
124 124 t = k.split('.')[0]
125 125 if t not in tools:
126 126 tools[t] = int(_toolstr(ui, t, "priority", "0"))
127 127 names = tools.keys()
128 128 tools = sorted([(-p, t) for t, p in tools.items()])
129 129 uimerge = ui.config("ui", "merge")
130 130 if uimerge:
131 131 if uimerge not in names:
132 132 return (uimerge, uimerge)
133 133 tools.insert(0, (None, uimerge)) # highest priority
134 134 tools.append((None, "hgmerge")) # the old default, if found
135 135 for p, t in tools:
136 136 if check(t, None, symlink, binary):
137 137 toolpath = _findtool(ui, t)
138 138 return (t, util.shellquote(toolpath))
139 139
140 140 # internal merge or prompt as last resort
141 141 if symlink or binary:
142 142 return ":prompt", None
143 143 return ":merge", None
144 144
145 145 def _eoltype(data):
146 146 "Guess the EOL type of a file"
147 147 if '\0' in data: # binary
148 148 return None
149 149 if '\r\n' in data: # Windows
150 150 return '\r\n'
151 151 if '\r' in data: # Old Mac
152 152 return '\r'
153 153 if '\n' in data: # UNIX
154 154 return '\n'
155 155 return None # unknown
156 156
157 157 def _matcheol(file, origfile):
158 158 "Convert EOL markers in a file to match origfile"
159 159 tostyle = _eoltype(util.readfile(origfile))
160 160 if tostyle:
161 161 data = util.readfile(file)
162 162 style = _eoltype(data)
163 163 if style:
164 164 newdata = data.replace(style, tostyle)
165 165 if newdata != data:
166 166 util.writefile(file, newdata)
167 167
168 168 @internaltool('prompt', nomerge)
169 169 def _iprompt(repo, mynode, orig, fcd, fco, fca, toolconf):
170 170 """Asks the user which of the local or the other version to keep as
171 171 the merged version."""
172 172 ui = repo.ui
173 173 fd = fcd.path()
174 174
175 175 if ui.promptchoice(_(" no tool found to merge %s\n"
176 176 "keep (l)ocal or take (o)ther?"
177 177 "$$ &Local $$ &Other") % fd, 0):
178 178 return _iother(repo, mynode, orig, fcd, fco, fca, toolconf)
179 179 else:
180 180 return _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf)
181 181
182 182 @internaltool('local', nomerge)
183 183 def _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf):
184 184 """Uses the local version of files as the merged version."""
185 185 return 0
186 186
187 187 @internaltool('other', nomerge)
188 188 def _iother(repo, mynode, orig, fcd, fco, fca, toolconf):
189 189 """Uses the other version of files as the merged version."""
190 190 repo.wwrite(fcd.path(), fco.data(), fco.flags())
191 191 return 0
192 192
193 193 @internaltool('fail', nomerge)
194 194 def _ifail(repo, mynode, orig, fcd, fco, fca, toolconf):
195 195 """
196 196 Rather than attempting to merge files that were modified on both
197 197 branches, it marks them as unresolved. The resolve command must be
198 198 used to resolve these conflicts."""
199 199 return 1
200 200
201 201 def _premerge(repo, toolconf, files, labels=None):
202 202 tool, toolpath, binary, symlink = toolconf
203 203 if symlink:
204 204 return 1
205 205 a, b, c, back = files
206 206
207 207 ui = repo.ui
208 208
209 209 validkeep = ['keep', 'keep-merge3']
210 210
211 211 # do we attempt to simplemerge first?
212 212 try:
213 213 premerge = _toolbool(ui, tool, "premerge", not binary)
214 214 except error.ConfigError:
215 215 premerge = _toolstr(ui, tool, "premerge").lower()
216 216 if premerge not in validkeep:
217 217 _valid = ', '.join(["'" + v + "'" for v in validkeep])
218 218 raise error.ConfigError(_("%s.premerge not valid "
219 219 "('%s' is neither boolean nor %s)") %
220 220 (tool, premerge, _valid))
221 221
222 222 if premerge:
223 223 if premerge == 'keep-merge3':
224 224 if not labels:
225 225 labels = _defaultconflictlabels
226 226 if len(labels) < 3:
227 227 labels.append('base')
228 228 r = simplemerge.simplemerge(ui, a, b, c, quiet=True, label=labels)
229 229 if not r:
230 230 ui.debug(" premerge successful\n")
231 231 return 0
232 232 if premerge not in validkeep:
233 233 util.copyfile(back, a) # restore from backup and try again
234 234 return 1 # continue merging
235 235
236 236 def _symlinkcheck(repo, mynode, orig, fcd, fco, fca, toolconf):
237 237 tool, toolpath, binary, symlink = toolconf
238 238 if symlink:
239 239 repo.ui.warn(_('warning: internal %s cannot merge symlinks '
240 240 'for %s\n') % (tool, fcd.path()))
241 241 return False
242 242 return True
243 243
244 244 def _merge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels, mode):
245 245 """
246 246 Uses the internal non-interactive simple merge algorithm for merging
247 247 files. It will fail if there are any conflicts and leave markers in
248 248 the partially merged file. Markers will have two sections, one for each side
249 249 of merge, unless mode equals 'union' which suppresses the markers."""
250 250 a, b, c, back = files
251 251
252 252 ui = repo.ui
253 253
254 254 r = simplemerge.simplemerge(ui, a, b, c, label=labels, mode=mode)
255 255 return True, r
256 256
257 257 @internaltool('union', fullmerge,
258 258 _("merging %s incomplete! "
259 259 "(edit conflicts, then use 'hg resolve --mark')\n"),
260 260 precheck=_symlinkcheck)
261 261 def _iunion(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
262 262 """
263 263 Uses the internal non-interactive simple merge algorithm for merging
264 264 files. It will use both left and right sides for conflict regions.
265 265 No markers are inserted."""
266 266 return _merge(repo, mynode, orig, fcd, fco, fca, toolconf,
267 267 files, labels, 'union')
268 268
269 269 @internaltool('merge', fullmerge,
270 270 _("merging %s incomplete! "
271 271 "(edit conflicts, then use 'hg resolve --mark')\n"),
272 272 precheck=_symlinkcheck)
273 273 def _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
274 274 """
275 275 Uses the internal non-interactive simple merge algorithm for merging
276 276 files. It will fail if there are any conflicts and leave markers in
277 277 the partially merged file. Markers will have two sections, one for each side
278 278 of merge."""
279 279 return _merge(repo, mynode, orig, fcd, fco, fca, toolconf,
280 280 files, labels, 'merge')
281 281
282 282 @internaltool('merge3', fullmerge,
283 283 _("merging %s incomplete! "
284 284 "(edit conflicts, then use 'hg resolve --mark')\n"),
285 285 precheck=_symlinkcheck)
286 286 def _imerge3(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
287 287 """
288 288 Uses the internal non-interactive simple merge algorithm for merging
289 289 files. It will fail if there are any conflicts and leave markers in
290 290 the partially merged file. Marker will have three sections, one from each
291 291 side of the merge and one for the base content."""
292 292 if not labels:
293 293 labels = _defaultconflictlabels
294 294 if len(labels) < 3:
295 295 labels.append('base')
296 296 return _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels)
297 297
298 298 def _imergeauto(repo, mynode, orig, fcd, fco, fca, toolconf, files,
299 299 labels=None, localorother=None):
300 300 """
301 301 Generic driver for _imergelocal and _imergeother
302 302 """
303 303 assert localorother is not None
304 304 tool, toolpath, binary, symlink = toolconf
305 305 if symlink:
306 306 repo.ui.warn(_('warning: :merge-%s cannot merge symlinks '
307 307 'for %s\n') % (localorother, fcd.path()))
308 308 return False, 1
309 309 a, b, c, back = files
310 310 r = simplemerge.simplemerge(repo.ui, a, b, c, label=labels,
311 311 localorother=localorother)
312 312 return True, r
313 313
314 314 @internaltool('merge-local', mergeonly)
315 315 def _imergelocal(*args, **kwargs):
316 316 """
317 317 Like :merge, but resolve all conflicts non-interactively in favor
318 318 of the local changes."""
319 319 success, status = _imergeauto(localorother='local', *args, **kwargs)
320 320 return success, status
321 321
322 322 @internaltool('merge-other', mergeonly)
323 323 def _imergeother(*args, **kwargs):
324 324 """
325 325 Like :merge, but resolve all conflicts non-interactively in favor
326 326 of the other changes."""
327 327 success, status = _imergeauto(localorother='other', *args, **kwargs)
328 328 return success, status
329 329
330 330 @internaltool('tagmerge', mergeonly,
331 331 _("automatic tag merging of %s failed! "
332 332 "(use 'hg resolve --tool :merge' or another merge "
333 333 "tool of your choice)\n"))
334 334 def _itagmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
335 335 """
336 336 Uses the internal tag merge algorithm (experimental).
337 337 """
338 338 return tagmerge.merge(repo, fcd, fco, fca)
339 339
340 340 @internaltool('dump', fullmerge)
341 341 def _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
342 342 """
343 343 Creates three versions of the files to merge, containing the
344 344 contents of local, other and base. These files can then be used to
345 345 perform a merge manually. If the file to be merged is named
346 346 ``a.txt``, these files will accordingly be named ``a.txt.local``,
347 347 ``a.txt.other`` and ``a.txt.base`` and they will be placed in the
348 348 same directory as ``a.txt``."""
349 r = 1
350 if r:
351 a, b, c, back = files
349 a, b, c, back = files
350
351 fd = fcd.path()
352 352
353 fd = fcd.path()
354
355 util.copyfile(a, a + ".local")
356 repo.wwrite(fd + ".other", fco.data(), fco.flags())
357 repo.wwrite(fd + ".base", fca.data(), fca.flags())
358 return False, r
353 util.copyfile(a, a + ".local")
354 repo.wwrite(fd + ".other", fco.data(), fco.flags())
355 repo.wwrite(fd + ".base", fca.data(), fca.flags())
356 return False, 1
359 357
360 358 def _xmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
361 359 r = 1
362 360 if r:
363 361 tool, toolpath, binary, symlink = toolconf
364 362 a, b, c, back = files
365 363 out = ""
366 364 env = {'HG_FILE': fcd.path(),
367 365 'HG_MY_NODE': short(mynode),
368 366 'HG_OTHER_NODE': str(fco.changectx()),
369 367 'HG_BASE_NODE': str(fca.changectx()),
370 368 'HG_MY_ISLINK': 'l' in fcd.flags(),
371 369 'HG_OTHER_ISLINK': 'l' in fco.flags(),
372 370 'HG_BASE_ISLINK': 'l' in fca.flags(),
373 371 }
374 372
375 373 ui = repo.ui
376 374
377 375 args = _toolstr(ui, tool, "args", '$local $base $other')
378 376 if "$output" in args:
379 377 out, a = a, back # read input from backup, write to original
380 378 replace = {'local': a, 'base': b, 'other': c, 'output': out}
381 379 args = util.interpolate(r'\$', replace, args,
382 380 lambda s: util.shellquote(util.localpath(s)))
383 381 cmd = toolpath + ' ' + args
384 382 repo.ui.debug('launching merge tool: %s\n' % cmd)
385 383 r = ui.system(cmd, cwd=repo.root, environ=env)
386 384 repo.ui.debug('merge tool returned: %s\n' % r)
387 385 return True, r
388 386 return False, 0
389 387
390 388 def _formatconflictmarker(repo, ctx, template, label, pad):
391 389 """Applies the given template to the ctx, prefixed by the label.
392 390
393 391 Pad is the minimum width of the label prefix, so that multiple markers
394 392 can have aligned templated parts.
395 393 """
396 394 if ctx.node() is None:
397 395 ctx = ctx.p1()
398 396
399 397 props = templatekw.keywords.copy()
400 398 props['templ'] = template
401 399 props['ctx'] = ctx
402 400 props['repo'] = repo
403 401 templateresult = template('conflictmarker', **props)
404 402
405 403 label = ('%s:' % label).ljust(pad + 1)
406 404 mark = '%s %s' % (label, templater.stringify(templateresult))
407 405
408 406 if mark:
409 407 mark = mark.splitlines()[0] # split for safety
410 408
411 409 # 8 for the prefix of conflict marker lines (e.g. '<<<<<<< ')
412 410 return util.ellipsis(mark, 80 - 8)
413 411
414 412 _defaultconflictmarker = ('{node|short} ' +
415 413 '{ifeq(tags, "tip", "", "{tags} ")}' +
416 414 '{if(bookmarks, "{bookmarks} ")}' +
417 415 '{ifeq(branch, "default", "", "{branch} ")}' +
418 416 '- {author|user}: {desc|firstline}')
419 417
420 418 _defaultconflictlabels = ['local', 'other']
421 419
422 420 def _formatlabels(repo, fcd, fco, fca, labels):
423 421 """Formats the given labels using the conflict marker template.
424 422
425 423 Returns a list of formatted labels.
426 424 """
427 425 cd = fcd.changectx()
428 426 co = fco.changectx()
429 427 ca = fca.changectx()
430 428
431 429 ui = repo.ui
432 430 template = ui.config('ui', 'mergemarkertemplate', _defaultconflictmarker)
433 431 tmpl = templater.templater(None, cache={'conflictmarker': template})
434 432
435 433 pad = max(len(l) for l in labels)
436 434
437 435 newlabels = [_formatconflictmarker(repo, cd, tmpl, labels[0], pad),
438 436 _formatconflictmarker(repo, co, tmpl, labels[1], pad)]
439 437 if len(labels) > 2:
440 438 newlabels.append(_formatconflictmarker(repo, ca, tmpl, labels[2], pad))
441 439 return newlabels
442 440
443 441 def filemerge(repo, mynode, orig, fcd, fco, fca, labels=None):
444 442 """perform a 3-way merge in the working directory
445 443
446 444 mynode = parent node before merge
447 445 orig = original local filename before merge
448 446 fco = other file context
449 447 fca = ancestor file context
450 448 fcd = local file context for current/destination file
451 449 """
452 450
453 451 if True:
454 452 def temp(prefix, ctx):
455 453 pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
456 454 (fd, name) = tempfile.mkstemp(prefix=pre)
457 455 data = repo.wwritedata(ctx.path(), ctx.data())
458 456 f = os.fdopen(fd, "wb")
459 457 f.write(data)
460 458 f.close()
461 459 return name
462 460
463 461 if not fco.cmp(fcd): # files identical?
464 462 return None
465 463
466 464 ui = repo.ui
467 465 fd = fcd.path()
468 466 binary = fcd.isbinary() or fco.isbinary() or fca.isbinary()
469 467 symlink = 'l' in fcd.flags() + fco.flags()
470 468 tool, toolpath = _picktool(repo, ui, fd, binary, symlink)
471 469 if tool in internals and tool.startswith('internal:'):
472 470 # normalize to new-style names (':merge' etc)
473 471 tool = tool[len('internal'):]
474 472 ui.debug("picked tool '%s' for %s (binary %s symlink %s)\n" %
475 473 (tool, fd, binary, symlink))
476 474
477 475 if tool in internals:
478 476 func = internals[tool]
479 477 mergetype = func.mergetype
480 478 onfailure = func.onfailure
481 479 precheck = func.precheck
482 480 else:
483 481 func = _xmerge
484 482 mergetype = fullmerge
485 483 onfailure = _("merging %s failed!\n")
486 484 precheck = None
487 485
488 486 toolconf = tool, toolpath, binary, symlink
489 487
490 488 if mergetype == nomerge:
491 489 return func(repo, mynode, orig, fcd, fco, fca, toolconf)
492 490
493 491 if orig != fco.path():
494 492 ui.status(_("merging %s and %s to %s\n") % (orig, fco.path(), fd))
495 493 else:
496 494 ui.status(_("merging %s\n") % fd)
497 495
498 496 ui.debug("my %s other %s ancestor %s\n" % (fcd, fco, fca))
499 497
500 498 if precheck and not precheck(repo, mynode, orig, fcd, fco, fca,
501 499 toolconf):
502 500 if onfailure:
503 501 ui.warn(onfailure % fd)
504 502 return 1
505 503
506 504 a = repo.wjoin(fd)
507 505 b = temp("base", fca)
508 506 c = temp("other", fco)
509 507 back = a + ".orig"
510 508 util.copyfile(a, back)
511 509 files = (a, b, c, back)
512 510
513 511 markerstyle = ui.config('ui', 'mergemarkers', 'basic')
514 512 if not labels:
515 513 labels = _defaultconflictlabels
516 514 if markerstyle != 'basic':
517 515 labels = _formatlabels(repo, fcd, fco, fca, labels)
518 516
519 517 r = 1
520 518 if mergetype == fullmerge:
521 519 r = _premerge(repo, toolconf, files, labels=labels)
522 520
523 521 if not r: # premerge successfully merged the file
524 522 needcheck = False
525 523 else:
526 524 needcheck, r = func(repo, mynode, orig, fcd, fco, fca, toolconf,
527 525 files, labels=labels)
528 526
529 527 if not needcheck:
530 528 if r:
531 529 if onfailure:
532 530 ui.warn(onfailure % fd)
533 531 else:
534 532 util.unlink(back)
535 533
536 534 util.unlink(b)
537 535 util.unlink(c)
538 536 return r
539 537
540 538 if not r and (_toolbool(ui, tool, "checkconflicts") or
541 539 'conflicts' in _toollist(ui, tool, "check")):
542 540 if re.search("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcd.data(),
543 541 re.MULTILINE):
544 542 r = 1
545 543
546 544 checked = False
547 545 if 'prompt' in _toollist(ui, tool, "check"):
548 546 checked = True
549 547 if ui.promptchoice(_("was merge of '%s' successful (yn)?"
550 548 "$$ &Yes $$ &No") % fd, 1):
551 549 r = 1
552 550
553 551 if not r and not checked and (_toolbool(ui, tool, "checkchanged") or
554 552 'changed' in
555 553 _toollist(ui, tool, "check")):
556 554 if filecmp.cmp(a, back):
557 555 if ui.promptchoice(_(" output file %s appears unchanged\n"
558 556 "was merge successful (yn)?"
559 557 "$$ &Yes $$ &No") % fd, 1):
560 558 r = 1
561 559
562 560 if _toolbool(ui, tool, "fixeol"):
563 561 _matcheol(a, back)
564 562
565 563 if r:
566 564 if onfailure:
567 565 ui.warn(onfailure % fd)
568 566 else:
569 567 util.unlink(back)
570 568
571 569 util.unlink(b)
572 570 util.unlink(c)
573 571 return r
574 572
575 573 # tell hggettext to extract docstrings from these functions:
576 574 i18nfunctions = internals.values()
General Comments 0
You need to be logged in to leave comments. Login now