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