diff options
Diffstat (limited to 'mercurial/filemerge.py')
-rw-r--r-- | mercurial/filemerge.py | 255 |
1 files changed, 78 insertions, 177 deletions
diff --git a/mercurial/filemerge.py b/mercurial/filemerge.py index d51f076..b13afe3 100644 --- a/mercurial/filemerge.py +++ b/mercurial/filemerge.py @@ -19,21 +19,11 @@ def _toolbool(ui, tool, part, default=False): def _toollist(ui, tool, part, default=[]): return ui.configlist("merge-tools", tool + "." + part, default) -internals = {} - -def internaltool(name, trymerge, onfailure=None): - '''return a decorator for populating internal merge tool table''' - def decorator(func): - fullname = 'internal:' + name - func.__doc__ = "``%s``\n" % fullname + func.__doc__.strip() - internals[fullname] = func - func.trymerge = trymerge - func.onfailure = onfailure - return func - return decorator +_internal = ['internal:' + s + for s in 'fail local other merge prompt dump'.split()] def _findtool(ui, tool): - if tool in internals: + if tool in _internal: return tool for kn in ("regkey", "regkeyalt"): k = _toolstr(ui, tool, kn) @@ -44,8 +34,7 @@ def _findtool(ui, tool): p = util.findexe(p + _toolstr(ui, tool, "regappend")) if p: return p - exe = _toolstr(ui, tool, "executable", tool) - return util.findexe(util.expandpath(exe)) + return util.findexe(_toolstr(ui, tool, "executable", tool)) def _picktool(repo, ui, path, binary, symlink): def check(tool, pat, symlink, binary): @@ -107,11 +96,8 @@ def _picktool(repo, ui, path, binary, symlink): if check(t, None, symlink, binary): toolpath = _findtool(ui, t) return (t, '"' + toolpath + '"') - - # internal merge or prompt as last resort - if symlink or binary: - return "internal:prompt", None - return "internal:merge", None + # internal merge as last resort + return (not (symlink or binary) and "internal:merge" or None, None) def _eoltype(data): "Guess the EOL type of a file" @@ -136,131 +122,6 @@ def _matcheol(file, origfile): if newdata != data: util.writefile(file, newdata) -@internaltool('prompt', False) -def _iprompt(repo, mynode, orig, fcd, fco, fca, toolconf): - """Asks the user which of the local or the other version to keep as - the merged version.""" - ui = repo.ui - fd = fcd.path() - - if ui.promptchoice(_(" no tool found to merge %s\n" - "keep (l)ocal or take (o)ther?") % fd, - (_("&Local"), _("&Other")), 0): - return _iother(repo, mynode, orig, fcd, fco, fca, toolconf) - else: - return _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf) - -@internaltool('local', False) -def _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf): - """Uses the local version of files as the merged version.""" - return 0 - -@internaltool('other', False) -def _iother(repo, mynode, orig, fcd, fco, fca, toolconf): - """Uses the other version of files as the merged version.""" - repo.wwrite(fcd.path(), fco.data(), fco.flags()) - return 0 - -@internaltool('fail', False) -def _ifail(repo, mynode, orig, fcd, fco, fca, toolconf): - """ - Rather than attempting to merge files that were modified on both - branches, it marks them as unresolved. The resolve command must be - used to resolve these conflicts.""" - return 1 - -def _premerge(repo, toolconf, files): - tool, toolpath, binary, symlink = toolconf - a, b, c, back = files - - ui = repo.ui - - # do we attempt to simplemerge first? - try: - premerge = _toolbool(ui, tool, "premerge", not (binary or symlink)) - except error.ConfigError: - premerge = _toolstr(ui, tool, "premerge").lower() - valid = 'keep'.split() - if premerge not in valid: - _valid = ', '.join(["'" + v + "'" for v in valid]) - raise error.ConfigError(_("%s.premerge not valid " - "('%s' is neither boolean nor %s)") % - (tool, premerge, _valid)) - - if premerge: - r = simplemerge.simplemerge(ui, a, b, c, quiet=True) - if not r: - ui.debug(" premerge successful\n") - return 0 - if premerge != 'keep': - util.copyfile(back, a) # restore from backup and try again - return 1 # continue merging - -@internaltool('merge', True, - _("merging %s incomplete! " - "(edit conflicts, then use 'hg resolve --mark')\n")) -def _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files): - """ - Uses the internal non-interactive simple merge algorithm for merging - files. It will fail if there are any conflicts and leave markers in - the partially merged file.""" - r = _premerge(repo, toolconf, files) - if r: - a, b, c, back = files - - ui = repo.ui - - r = simplemerge.simplemerge(ui, a, b, c, label=['local', 'other']) - return True, r - return False, 0 - -@internaltool('dump', True) -def _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files): - """ - Creates three versions of the files to merge, containing the - contents of local, other and base. These files can then be used to - perform a merge manually. If the file to be merged is named - ``a.txt``, these files will accordingly be named ``a.txt.local``, - ``a.txt.other`` and ``a.txt.base`` and they will be placed in the - same directory as ``a.txt``.""" - r = _premerge(repo, toolconf, files) - if r: - a, b, c, back = files - - fd = fcd.path() - - util.copyfile(a, a + ".local") - repo.wwrite(fd + ".other", fco.data(), fco.flags()) - repo.wwrite(fd + ".base", fca.data(), fca.flags()) - return False, r - -def _xmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files): - r = _premerge(repo, toolconf, files) - if r: - tool, toolpath, binary, symlink = toolconf - a, b, c, back = files - out = "" - env = dict(HG_FILE=fcd.path(), - HG_MY_NODE=short(mynode), - HG_OTHER_NODE=str(fco.changectx()), - HG_BASE_NODE=str(fca.changectx()), - HG_MY_ISLINK='l' in fcd.flags(), - HG_OTHER_ISLINK='l' in fco.flags(), - HG_BASE_ISLINK='l' in fca.flags()) - - ui = repo.ui - - args = _toolstr(ui, tool, "args", '$local $base $other') - if "$output" in args: - out, a = a, back # read input from backup, write to original - replace = dict(local=a, base=b, other=c, output=out) - args = util.interpolate(r'\$', replace, args, - lambda s: '"%s"' % util.localpath(s)) - r = util.system(toolpath + ' ' + args, cwd=repo.root, environ=env, - out=ui.fout) - return True, r - return False, 0 - def filemerge(repo, mynode, orig, fcd, fco, fca): """perform a 3-way merge in the working directory @@ -280,34 +141,42 @@ def filemerge(repo, mynode, orig, fcd, fco, fca): f.close() return name + def isbin(ctx): + try: + return util.binary(ctx.data()) + except IOError: + return False + if not fco.cmp(fcd): # files identical? return None ui = repo.ui fd = fcd.path() - binary = fcd.isbinary() or fco.isbinary() or fca.isbinary() + binary = isbin(fcd) or isbin(fco) or isbin(fca) symlink = 'l' in fcd.flags() + fco.flags() tool, toolpath = _picktool(repo, ui, fd, binary, symlink) ui.debug("picked tool '%s' for %s (binary %s symlink %s)\n" % (tool, fd, binary, symlink)) - if tool in internals: - func = internals[tool] - trymerge = func.trymerge - onfailure = func.onfailure - else: - func = _xmerge - trymerge = True - onfailure = _("merging %s failed!\n") - - toolconf = tool, toolpath, binary, symlink - - if not trymerge: - return func(repo, mynode, orig, fcd, fco, fca, toolconf) - + if not tool or tool == 'internal:prompt': + tool = "internal:local" + if ui.promptchoice(_(" no tool found to merge %s\n" + "keep (l)ocal or take (o)ther?") % fd, + (_("&Local"), _("&Other")), 0): + tool = "internal:other" + if tool == "internal:local": + return 0 + if tool == "internal:other": + repo.wwrite(fd, fco.data(), fco.flags()) + return 0 + if tool == "internal:fail": + return 1 + + # do the actual merge a = repo.wjoin(fd) b = temp("base", fca) c = temp("other", fco) + out = "" back = a + ".orig" util.copyfile(a, back) @@ -318,18 +187,54 @@ def filemerge(repo, mynode, orig, fcd, fco, fca): ui.debug("my %s other %s ancestor %s\n" % (fcd, fco, fca)) - needcheck, r = func(repo, mynode, orig, fcd, fco, fca, toolconf, - (a, b, c, back)) - if not needcheck: - if r: - if onfailure: - ui.warn(onfailure % fd) - else: + # do we attempt to simplemerge first? + try: + premerge = _toolbool(ui, tool, "premerge", not (binary or symlink)) + except error.ConfigError: + premerge = _toolstr(ui, tool, "premerge").lower() + valid = 'keep'.split() + if premerge not in valid: + _valid = ', '.join(["'" + v + "'" for v in valid]) + raise error.ConfigError(_("%s.premerge not valid " + "('%s' is neither boolean nor %s)") % + (tool, premerge, _valid)) + + if premerge: + r = simplemerge.simplemerge(ui, a, b, c, quiet=True) + if not r: + ui.debug(" premerge successful\n") os.unlink(back) + os.unlink(b) + os.unlink(c) + return 0 + if premerge != 'keep': + util.copyfile(back, a) # restore from backup and try again - os.unlink(b) - os.unlink(c) - return r + env = dict(HG_FILE=fd, + HG_MY_NODE=short(mynode), + HG_OTHER_NODE=str(fco.changectx()), + HG_BASE_NODE=str(fca.changectx()), + HG_MY_ISLINK='l' in fcd.flags(), + HG_OTHER_ISLINK='l' in fco.flags(), + HG_BASE_ISLINK='l' in fca.flags()) + + if tool == "internal:merge": + r = simplemerge.simplemerge(ui, a, b, c, label=['local', 'other']) + elif tool == 'internal:dump': + a = repo.wjoin(fd) + util.copyfile(a, a + ".local") + repo.wwrite(fd + ".other", fco.data(), fco.flags()) + repo.wwrite(fd + ".base", fca.data(), fca.flags()) + return 1 # unresolved + else: + args = _toolstr(ui, tool, "args", '$local $base $other') + if "$output" in args: + out, a = a, back # read input from backup, write to original + replace = dict(local=a, base=b, other=c, output=out) + args = util.interpolate(r'\$', replace, args, + lambda s: '"%s"' % util.localpath(s)) + r = util.system(toolpath + ' ' + args, cwd=repo.root, environ=env, + out=ui.fout) if not r and (_toolbool(ui, tool, "checkconflicts") or 'conflicts' in _toollist(ui, tool, "check")): @@ -346,24 +251,20 @@ def filemerge(repo, mynode, orig, fcd, fco, fca): if not r and not checked and (_toolbool(ui, tool, "checkchanged") or 'changed' in _toollist(ui, tool, "check")): - if filecmp.cmp(a, back): + if filecmp.cmp(repo.wjoin(fd), back): if ui.promptchoice(_(" output file %s appears unchanged\n" "was merge successful (yn)?") % fd, (_("&Yes"), _("&No")), 1): r = 1 if _toolbool(ui, tool, "fixeol"): - _matcheol(a, back) + _matcheol(repo.wjoin(fd), back) if r: - if onfailure: - ui.warn(onfailure % fd) + ui.warn(_("merging %s failed!\n") % fd) else: os.unlink(back) os.unlink(b) os.unlink(c) return r - -# tell hggettext to extract docstrings from these functions: -i18nfunctions = internals.values() |