##// END OF EJS Templates
filemerge: only raise InMemoryMergeConflictsError when running _xmerge...
Phil Cohen -
r35479:ef7e667a default
parent child Browse files
Show More
@@ -1,829 +1,839 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 os
10 import os
11 import re
11 import re
12 import tempfile
12 import tempfile
13
13
14 from .i18n import _
14 from .i18n import _
15 from .node import nullid, short
15 from .node import nullid, short
16
16
17 from . import (
17 from . import (
18 encoding,
18 encoding,
19 error,
19 error,
20 formatter,
20 formatter,
21 match,
21 match,
22 pycompat,
22 pycompat,
23 registrar,
23 registrar,
24 scmutil,
24 scmutil,
25 simplemerge,
25 simplemerge,
26 tagmerge,
26 tagmerge,
27 templatekw,
27 templatekw,
28 templater,
28 templater,
29 util,
29 util,
30 )
30 )
31
31
32 def _toolstr(ui, tool, part, *args):
32 def _toolstr(ui, tool, part, *args):
33 return ui.config("merge-tools", tool + "." + part, *args)
33 return ui.config("merge-tools", tool + "." + part, *args)
34
34
35 def _toolbool(ui, tool, part,*args):
35 def _toolbool(ui, tool, part,*args):
36 return ui.configbool("merge-tools", tool + "." + part, *args)
36 return ui.configbool("merge-tools", tool + "." + part, *args)
37
37
38 def _toollist(ui, tool, part):
38 def _toollist(ui, tool, part):
39 return ui.configlist("merge-tools", tool + "." + part)
39 return ui.configlist("merge-tools", tool + "." + part)
40
40
41 internals = {}
41 internals = {}
42 # Merge tools to document.
42 # Merge tools to document.
43 internalsdoc = {}
43 internalsdoc = {}
44
44
45 internaltool = registrar.internalmerge()
45 internaltool = registrar.internalmerge()
46
46
47 # internal tool merge types
47 # internal tool merge types
48 nomerge = internaltool.nomerge
48 nomerge = internaltool.nomerge
49 mergeonly = internaltool.mergeonly # just the full merge, no premerge
49 mergeonly = internaltool.mergeonly # just the full merge, no premerge
50 fullmerge = internaltool.fullmerge # both premerge and merge
50 fullmerge = internaltool.fullmerge # both premerge and merge
51
51
52 _localchangedotherdeletedmsg = _(
52 _localchangedotherdeletedmsg = _(
53 "local%(l)s changed %(fd)s which other%(o)s deleted\n"
53 "local%(l)s changed %(fd)s which other%(o)s deleted\n"
54 "use (c)hanged version, (d)elete, or leave (u)nresolved?"
54 "use (c)hanged version, (d)elete, or leave (u)nresolved?"
55 "$$ &Changed $$ &Delete $$ &Unresolved")
55 "$$ &Changed $$ &Delete $$ &Unresolved")
56
56
57 _otherchangedlocaldeletedmsg = _(
57 _otherchangedlocaldeletedmsg = _(
58 "other%(o)s changed %(fd)s which local%(l)s deleted\n"
58 "other%(o)s changed %(fd)s which local%(l)s deleted\n"
59 "use (c)hanged version, leave (d)eleted, or "
59 "use (c)hanged version, leave (d)eleted, or "
60 "leave (u)nresolved?"
60 "leave (u)nresolved?"
61 "$$ &Changed $$ &Deleted $$ &Unresolved")
61 "$$ &Changed $$ &Deleted $$ &Unresolved")
62
62
63 class absentfilectx(object):
63 class absentfilectx(object):
64 """Represents a file that's ostensibly in a context but is actually not
64 """Represents a file that's ostensibly in a context but is actually not
65 present in it.
65 present in it.
66
66
67 This is here because it's very specific to the filemerge code for now --
67 This is here because it's very specific to the filemerge code for now --
68 other code is likely going to break with the values this returns."""
68 other code is likely going to break with the values this returns."""
69 def __init__(self, ctx, f):
69 def __init__(self, ctx, f):
70 self._ctx = ctx
70 self._ctx = ctx
71 self._f = f
71 self._f = f
72
72
73 def path(self):
73 def path(self):
74 return self._f
74 return self._f
75
75
76 def size(self):
76 def size(self):
77 return None
77 return None
78
78
79 def data(self):
79 def data(self):
80 return None
80 return None
81
81
82 def filenode(self):
82 def filenode(self):
83 return nullid
83 return nullid
84
84
85 _customcmp = True
85 _customcmp = True
86 def cmp(self, fctx):
86 def cmp(self, fctx):
87 """compare with other file context
87 """compare with other file context
88
88
89 returns True if different from fctx.
89 returns True if different from fctx.
90 """
90 """
91 return not (fctx.isabsent() and
91 return not (fctx.isabsent() and
92 fctx.ctx() == self.ctx() and
92 fctx.ctx() == self.ctx() and
93 fctx.path() == self.path())
93 fctx.path() == self.path())
94
94
95 def flags(self):
95 def flags(self):
96 return ''
96 return ''
97
97
98 def changectx(self):
98 def changectx(self):
99 return self._ctx
99 return self._ctx
100
100
101 def isbinary(self):
101 def isbinary(self):
102 return False
102 return False
103
103
104 def isabsent(self):
104 def isabsent(self):
105 return True
105 return True
106
106
107 def _findtool(ui, tool):
107 def _findtool(ui, tool):
108 if tool in internals:
108 if tool in internals:
109 return tool
109 return tool
110 return findexternaltool(ui, tool)
110 return findexternaltool(ui, tool)
111
111
112 def findexternaltool(ui, tool):
112 def findexternaltool(ui, tool):
113 for kn in ("regkey", "regkeyalt"):
113 for kn in ("regkey", "regkeyalt"):
114 k = _toolstr(ui, tool, kn)
114 k = _toolstr(ui, tool, kn)
115 if not k:
115 if not k:
116 continue
116 continue
117 p = util.lookupreg(k, _toolstr(ui, tool, "regname"))
117 p = util.lookupreg(k, _toolstr(ui, tool, "regname"))
118 if p:
118 if p:
119 p = util.findexe(p + _toolstr(ui, tool, "regappend", ""))
119 p = util.findexe(p + _toolstr(ui, tool, "regappend", ""))
120 if p:
120 if p:
121 return p
121 return p
122 exe = _toolstr(ui, tool, "executable", tool)
122 exe = _toolstr(ui, tool, "executable", tool)
123 return util.findexe(util.expandpath(exe))
123 return util.findexe(util.expandpath(exe))
124
124
125 def _picktool(repo, ui, path, binary, symlink, changedelete):
125 def _picktool(repo, ui, path, binary, symlink, changedelete):
126 def supportscd(tool):
126 def supportscd(tool):
127 return tool in internals and internals[tool].mergetype == nomerge
127 return tool in internals and internals[tool].mergetype == nomerge
128
128
129 def check(tool, pat, symlink, binary, changedelete):
129 def check(tool, pat, symlink, binary, changedelete):
130 tmsg = tool
130 tmsg = tool
131 if pat:
131 if pat:
132 tmsg = _("%s (for pattern %s)") % (tool, pat)
132 tmsg = _("%s (for pattern %s)") % (tool, pat)
133 if not _findtool(ui, tool):
133 if not _findtool(ui, tool):
134 if pat: # explicitly requested tool deserves a warning
134 if pat: # explicitly requested tool deserves a warning
135 ui.warn(_("couldn't find merge tool %s\n") % tmsg)
135 ui.warn(_("couldn't find merge tool %s\n") % tmsg)
136 else: # configured but non-existing tools are more silent
136 else: # configured but non-existing tools are more silent
137 ui.note(_("couldn't find merge tool %s\n") % tmsg)
137 ui.note(_("couldn't find merge tool %s\n") % tmsg)
138 elif symlink and not _toolbool(ui, tool, "symlink"):
138 elif symlink and not _toolbool(ui, tool, "symlink"):
139 ui.warn(_("tool %s can't handle symlinks\n") % tmsg)
139 ui.warn(_("tool %s can't handle symlinks\n") % tmsg)
140 elif binary and not _toolbool(ui, tool, "binary"):
140 elif binary and not _toolbool(ui, tool, "binary"):
141 ui.warn(_("tool %s can't handle binary\n") % tmsg)
141 ui.warn(_("tool %s can't handle binary\n") % tmsg)
142 elif changedelete and not supportscd(tool):
142 elif changedelete and not supportscd(tool):
143 # the nomerge tools are the only tools that support change/delete
143 # the nomerge tools are the only tools that support change/delete
144 # conflicts
144 # conflicts
145 pass
145 pass
146 elif not util.gui() and _toolbool(ui, tool, "gui"):
146 elif not util.gui() and _toolbool(ui, tool, "gui"):
147 ui.warn(_("tool %s requires a GUI\n") % tmsg)
147 ui.warn(_("tool %s requires a GUI\n") % tmsg)
148 else:
148 else:
149 return True
149 return True
150 return False
150 return False
151
151
152 # internal config: ui.forcemerge
152 # internal config: ui.forcemerge
153 # forcemerge comes from command line arguments, highest priority
153 # forcemerge comes from command line arguments, highest priority
154 force = ui.config('ui', 'forcemerge')
154 force = ui.config('ui', 'forcemerge')
155 if force:
155 if force:
156 toolpath = _findtool(ui, force)
156 toolpath = _findtool(ui, force)
157 if changedelete and not supportscd(toolpath):
157 if changedelete and not supportscd(toolpath):
158 return ":prompt", None
158 return ":prompt", None
159 else:
159 else:
160 if toolpath:
160 if toolpath:
161 return (force, util.shellquote(toolpath))
161 return (force, util.shellquote(toolpath))
162 else:
162 else:
163 # mimic HGMERGE if given tool not found
163 # mimic HGMERGE if given tool not found
164 return (force, force)
164 return (force, force)
165
165
166 # HGMERGE takes next precedence
166 # HGMERGE takes next precedence
167 hgmerge = encoding.environ.get("HGMERGE")
167 hgmerge = encoding.environ.get("HGMERGE")
168 if hgmerge:
168 if hgmerge:
169 if changedelete and not supportscd(hgmerge):
169 if changedelete and not supportscd(hgmerge):
170 return ":prompt", None
170 return ":prompt", None
171 else:
171 else:
172 return (hgmerge, hgmerge)
172 return (hgmerge, hgmerge)
173
173
174 # then patterns
174 # then patterns
175 for pat, tool in ui.configitems("merge-patterns"):
175 for pat, tool in ui.configitems("merge-patterns"):
176 mf = match.match(repo.root, '', [pat])
176 mf = match.match(repo.root, '', [pat])
177 if mf(path) and check(tool, pat, symlink, False, changedelete):
177 if mf(path) and check(tool, pat, symlink, False, changedelete):
178 toolpath = _findtool(ui, tool)
178 toolpath = _findtool(ui, tool)
179 return (tool, util.shellquote(toolpath))
179 return (tool, util.shellquote(toolpath))
180
180
181 # then merge tools
181 # then merge tools
182 tools = {}
182 tools = {}
183 disabled = set()
183 disabled = set()
184 for k, v in ui.configitems("merge-tools"):
184 for k, v in ui.configitems("merge-tools"):
185 t = k.split('.')[0]
185 t = k.split('.')[0]
186 if t not in tools:
186 if t not in tools:
187 tools[t] = int(_toolstr(ui, t, "priority"))
187 tools[t] = int(_toolstr(ui, t, "priority"))
188 if _toolbool(ui, t, "disabled"):
188 if _toolbool(ui, t, "disabled"):
189 disabled.add(t)
189 disabled.add(t)
190 names = tools.keys()
190 names = tools.keys()
191 tools = sorted([(-p, tool) for tool, p in tools.items()
191 tools = sorted([(-p, tool) for tool, p in tools.items()
192 if tool not in disabled])
192 if tool not in disabled])
193 uimerge = ui.config("ui", "merge")
193 uimerge = ui.config("ui", "merge")
194 if uimerge:
194 if uimerge:
195 # external tools defined in uimerge won't be able to handle
195 # external tools defined in uimerge won't be able to handle
196 # change/delete conflicts
196 # change/delete conflicts
197 if uimerge not in names and not changedelete:
197 if uimerge not in names and not changedelete:
198 return (uimerge, uimerge)
198 return (uimerge, uimerge)
199 tools.insert(0, (None, uimerge)) # highest priority
199 tools.insert(0, (None, uimerge)) # highest priority
200 tools.append((None, "hgmerge")) # the old default, if found
200 tools.append((None, "hgmerge")) # the old default, if found
201 for p, t in tools:
201 for p, t in tools:
202 if check(t, None, symlink, binary, changedelete):
202 if check(t, None, symlink, binary, changedelete):
203 toolpath = _findtool(ui, t)
203 toolpath = _findtool(ui, t)
204 return (t, util.shellquote(toolpath))
204 return (t, util.shellquote(toolpath))
205
205
206 # internal merge or prompt as last resort
206 # internal merge or prompt as last resort
207 if symlink or binary or changedelete:
207 if symlink or binary or changedelete:
208 if not changedelete and len(tools):
208 if not changedelete and len(tools):
209 # any tool is rejected by capability for symlink or binary
209 # any tool is rejected by capability for symlink or binary
210 ui.warn(_("no tool found to merge %s\n") % path)
210 ui.warn(_("no tool found to merge %s\n") % path)
211 return ":prompt", None
211 return ":prompt", None
212 return ":merge", None
212 return ":merge", None
213
213
214 def _eoltype(data):
214 def _eoltype(data):
215 "Guess the EOL type of a file"
215 "Guess the EOL type of a file"
216 if '\0' in data: # binary
216 if '\0' in data: # binary
217 return None
217 return None
218 if '\r\n' in data: # Windows
218 if '\r\n' in data: # Windows
219 return '\r\n'
219 return '\r\n'
220 if '\r' in data: # Old Mac
220 if '\r' in data: # Old Mac
221 return '\r'
221 return '\r'
222 if '\n' in data: # UNIX
222 if '\n' in data: # UNIX
223 return '\n'
223 return '\n'
224 return None # unknown
224 return None # unknown
225
225
226 def _matcheol(file, back):
226 def _matcheol(file, back):
227 "Convert EOL markers in a file to match origfile"
227 "Convert EOL markers in a file to match origfile"
228 tostyle = _eoltype(back.data()) # No repo.wread filters?
228 tostyle = _eoltype(back.data()) # No repo.wread filters?
229 if tostyle:
229 if tostyle:
230 data = util.readfile(file)
230 data = util.readfile(file)
231 style = _eoltype(data)
231 style = _eoltype(data)
232 if style:
232 if style:
233 newdata = data.replace(style, tostyle)
233 newdata = data.replace(style, tostyle)
234 if newdata != data:
234 if newdata != data:
235 util.writefile(file, newdata)
235 util.writefile(file, newdata)
236
236
237 @internaltool('prompt', nomerge)
237 @internaltool('prompt', nomerge)
238 def _iprompt(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
238 def _iprompt(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
239 """Asks the user which of the local `p1()` or the other `p2()` version to
239 """Asks the user which of the local `p1()` or the other `p2()` version to
240 keep as the merged version."""
240 keep as the merged version."""
241 ui = repo.ui
241 ui = repo.ui
242 fd = fcd.path()
242 fd = fcd.path()
243
243
244 # Avoid prompting during an in-memory merge since it doesn't support merge
244 # Avoid prompting during an in-memory merge since it doesn't support merge
245 # conflicts.
245 # conflicts.
246 if fcd.changectx().isinmemory():
246 if fcd.changectx().isinmemory():
247 raise error.InMemoryMergeConflictsError('in-memory merge does not '
247 raise error.InMemoryMergeConflictsError('in-memory merge does not '
248 'support file conflicts')
248 'support file conflicts')
249
249
250 prompts = partextras(labels)
250 prompts = partextras(labels)
251 prompts['fd'] = fd
251 prompts['fd'] = fd
252 try:
252 try:
253 if fco.isabsent():
253 if fco.isabsent():
254 index = ui.promptchoice(
254 index = ui.promptchoice(
255 _localchangedotherdeletedmsg % prompts, 2)
255 _localchangedotherdeletedmsg % prompts, 2)
256 choice = ['local', 'other', 'unresolved'][index]
256 choice = ['local', 'other', 'unresolved'][index]
257 elif fcd.isabsent():
257 elif fcd.isabsent():
258 index = ui.promptchoice(
258 index = ui.promptchoice(
259 _otherchangedlocaldeletedmsg % prompts, 2)
259 _otherchangedlocaldeletedmsg % prompts, 2)
260 choice = ['other', 'local', 'unresolved'][index]
260 choice = ['other', 'local', 'unresolved'][index]
261 else:
261 else:
262 index = ui.promptchoice(
262 index = ui.promptchoice(
263 _("keep (l)ocal%(l)s, take (o)ther%(o)s, or leave (u)nresolved"
263 _("keep (l)ocal%(l)s, take (o)ther%(o)s, or leave (u)nresolved"
264 " for %(fd)s?"
264 " for %(fd)s?"
265 "$$ &Local $$ &Other $$ &Unresolved") % prompts, 2)
265 "$$ &Local $$ &Other $$ &Unresolved") % prompts, 2)
266 choice = ['local', 'other', 'unresolved'][index]
266 choice = ['local', 'other', 'unresolved'][index]
267
267
268 if choice == 'other':
268 if choice == 'other':
269 return _iother(repo, mynode, orig, fcd, fco, fca, toolconf,
269 return _iother(repo, mynode, orig, fcd, fco, fca, toolconf,
270 labels)
270 labels)
271 elif choice == 'local':
271 elif choice == 'local':
272 return _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf,
272 return _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf,
273 labels)
273 labels)
274 elif choice == 'unresolved':
274 elif choice == 'unresolved':
275 return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf,
275 return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf,
276 labels)
276 labels)
277 except error.ResponseExpected:
277 except error.ResponseExpected:
278 ui.write("\n")
278 ui.write("\n")
279 return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf,
279 return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf,
280 labels)
280 labels)
281
281
282 @internaltool('local', nomerge)
282 @internaltool('local', nomerge)
283 def _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
283 def _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
284 """Uses the local `p1()` version of files as the merged version."""
284 """Uses the local `p1()` version of files as the merged version."""
285 return 0, fcd.isabsent()
285 return 0, fcd.isabsent()
286
286
287 @internaltool('other', nomerge)
287 @internaltool('other', nomerge)
288 def _iother(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
288 def _iother(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
289 """Uses the other `p2()` version of files as the merged version."""
289 """Uses the other `p2()` version of files as the merged version."""
290 if fco.isabsent():
290 if fco.isabsent():
291 # local changed, remote deleted -- 'deleted' picked
291 # local changed, remote deleted -- 'deleted' picked
292 _underlyingfctxifabsent(fcd).remove()
292 _underlyingfctxifabsent(fcd).remove()
293 deleted = True
293 deleted = True
294 else:
294 else:
295 _underlyingfctxifabsent(fcd).write(fco.data(), fco.flags())
295 _underlyingfctxifabsent(fcd).write(fco.data(), fco.flags())
296 deleted = False
296 deleted = False
297 return 0, deleted
297 return 0, deleted
298
298
299 @internaltool('fail', nomerge)
299 @internaltool('fail', nomerge)
300 def _ifail(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
300 def _ifail(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
301 """
301 """
302 Rather than attempting to merge files that were modified on both
302 Rather than attempting to merge files that were modified on both
303 branches, it marks them as unresolved. The resolve command must be
303 branches, it marks them as unresolved. The resolve command must be
304 used to resolve these conflicts."""
304 used to resolve these conflicts."""
305 # for change/delete conflicts write out the changed version, then fail
305 # for change/delete conflicts write out the changed version, then fail
306 if fcd.isabsent():
306 if fcd.isabsent():
307 _underlyingfctxifabsent(fcd).write(fco.data(), fco.flags())
307 _underlyingfctxifabsent(fcd).write(fco.data(), fco.flags())
308 return 1, False
308 return 1, False
309
309
310 def _underlyingfctxifabsent(filectx):
310 def _underlyingfctxifabsent(filectx):
311 """Sometimes when resolving, our fcd is actually an absentfilectx, but
311 """Sometimes when resolving, our fcd is actually an absentfilectx, but
312 we want to write to it (to do the resolve). This helper returns the
312 we want to write to it (to do the resolve). This helper returns the
313 underyling workingfilectx in that case.
313 underyling workingfilectx in that case.
314 """
314 """
315 if filectx.isabsent():
315 if filectx.isabsent():
316 return filectx.changectx()[filectx.path()]
316 return filectx.changectx()[filectx.path()]
317 else:
317 else:
318 return filectx
318 return filectx
319
319
320 def _premerge(repo, fcd, fco, fca, toolconf, files, labels=None):
320 def _premerge(repo, fcd, fco, fca, toolconf, files, labels=None):
321 tool, toolpath, binary, symlink = toolconf
321 tool, toolpath, binary, symlink = toolconf
322 if symlink or fcd.isabsent() or fco.isabsent():
322 if symlink or fcd.isabsent() or fco.isabsent():
323 return 1
323 return 1
324 unused, unused, unused, back = files
324 unused, unused, unused, back = files
325
325
326 ui = repo.ui
326 ui = repo.ui
327
327
328 validkeep = ['keep', 'keep-merge3']
328 validkeep = ['keep', 'keep-merge3']
329
329
330 # do we attempt to simplemerge first?
330 # do we attempt to simplemerge first?
331 try:
331 try:
332 premerge = _toolbool(ui, tool, "premerge", not binary)
332 premerge = _toolbool(ui, tool, "premerge", not binary)
333 except error.ConfigError:
333 except error.ConfigError:
334 premerge = _toolstr(ui, tool, "premerge", "").lower()
334 premerge = _toolstr(ui, tool, "premerge", "").lower()
335 if premerge not in validkeep:
335 if premerge not in validkeep:
336 _valid = ', '.join(["'" + v + "'" for v in validkeep])
336 _valid = ', '.join(["'" + v + "'" for v in validkeep])
337 raise error.ConfigError(_("%s.premerge not valid "
337 raise error.ConfigError(_("%s.premerge not valid "
338 "('%s' is neither boolean nor %s)") %
338 "('%s' is neither boolean nor %s)") %
339 (tool, premerge, _valid))
339 (tool, premerge, _valid))
340
340
341 if premerge:
341 if premerge:
342 if premerge == 'keep-merge3':
342 if premerge == 'keep-merge3':
343 if not labels:
343 if not labels:
344 labels = _defaultconflictlabels
344 labels = _defaultconflictlabels
345 if len(labels) < 3:
345 if len(labels) < 3:
346 labels.append('base')
346 labels.append('base')
347 r = simplemerge.simplemerge(ui, fcd, fca, fco, quiet=True, label=labels)
347 r = simplemerge.simplemerge(ui, fcd, fca, fco, quiet=True, label=labels)
348 if not r:
348 if not r:
349 ui.debug(" premerge successful\n")
349 ui.debug(" premerge successful\n")
350 return 0
350 return 0
351 if premerge not in validkeep:
351 if premerge not in validkeep:
352 # restore from backup and try again
352 # restore from backup and try again
353 _restorebackup(fcd, back)
353 _restorebackup(fcd, back)
354 return 1 # continue merging
354 return 1 # continue merging
355
355
356 def _mergecheck(repo, mynode, orig, fcd, fco, fca, toolconf):
356 def _mergecheck(repo, mynode, orig, fcd, fco, fca, toolconf):
357 tool, toolpath, binary, symlink = toolconf
357 tool, toolpath, binary, symlink = toolconf
358 if symlink:
358 if symlink:
359 repo.ui.warn(_('warning: internal %s cannot merge symlinks '
359 repo.ui.warn(_('warning: internal %s cannot merge symlinks '
360 'for %s\n') % (tool, fcd.path()))
360 'for %s\n') % (tool, fcd.path()))
361 return False
361 return False
362 if fcd.isabsent() or fco.isabsent():
362 if fcd.isabsent() or fco.isabsent():
363 repo.ui.warn(_('warning: internal %s cannot merge change/delete '
363 repo.ui.warn(_('warning: internal %s cannot merge change/delete '
364 'conflict for %s\n') % (tool, fcd.path()))
364 'conflict for %s\n') % (tool, fcd.path()))
365 return False
365 return False
366 return True
366 return True
367
367
368 def _merge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels, mode):
368 def _merge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels, mode):
369 """
369 """
370 Uses the internal non-interactive simple merge algorithm for merging
370 Uses the internal non-interactive simple merge algorithm for merging
371 files. It will fail if there are any conflicts and leave markers in
371 files. It will fail if there are any conflicts and leave markers in
372 the partially merged file. Markers will have two sections, one for each side
372 the partially merged file. Markers will have two sections, one for each side
373 of merge, unless mode equals 'union' which suppresses the markers."""
373 of merge, unless mode equals 'union' which suppresses the markers."""
374 ui = repo.ui
374 ui = repo.ui
375
375
376 r = simplemerge.simplemerge(ui, fcd, fca, fco, label=labels, mode=mode)
376 r = simplemerge.simplemerge(ui, fcd, fca, fco, label=labels, mode=mode)
377 return True, r, False
377 return True, r, False
378
378
379 @internaltool('union', fullmerge,
379 @internaltool('union', fullmerge,
380 _("warning: conflicts while merging %s! "
380 _("warning: conflicts while merging %s! "
381 "(edit, then use 'hg resolve --mark')\n"),
381 "(edit, then use 'hg resolve --mark')\n"),
382 precheck=_mergecheck)
382 precheck=_mergecheck)
383 def _iunion(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
383 def _iunion(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
384 """
384 """
385 Uses the internal non-interactive simple merge algorithm for merging
385 Uses the internal non-interactive simple merge algorithm for merging
386 files. It will use both left and right sides for conflict regions.
386 files. It will use both left and right sides for conflict regions.
387 No markers are inserted."""
387 No markers are inserted."""
388 return _merge(repo, mynode, orig, fcd, fco, fca, toolconf,
388 return _merge(repo, mynode, orig, fcd, fco, fca, toolconf,
389 files, labels, 'union')
389 files, labels, 'union')
390
390
391 @internaltool('merge', fullmerge,
391 @internaltool('merge', fullmerge,
392 _("warning: conflicts while merging %s! "
392 _("warning: conflicts while merging %s! "
393 "(edit, then use 'hg resolve --mark')\n"),
393 "(edit, then use 'hg resolve --mark')\n"),
394 precheck=_mergecheck)
394 precheck=_mergecheck)
395 def _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
395 def _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
396 """
396 """
397 Uses the internal non-interactive simple merge algorithm for merging
397 Uses the internal non-interactive simple merge algorithm for merging
398 files. It will fail if there are any conflicts and leave markers in
398 files. It will fail if there are any conflicts and leave markers in
399 the partially merged file. Markers will have two sections, one for each side
399 the partially merged file. Markers will have two sections, one for each side
400 of merge."""
400 of merge."""
401 return _merge(repo, mynode, orig, fcd, fco, fca, toolconf,
401 return _merge(repo, mynode, orig, fcd, fco, fca, toolconf,
402 files, labels, 'merge')
402 files, labels, 'merge')
403
403
404 @internaltool('merge3', fullmerge,
404 @internaltool('merge3', fullmerge,
405 _("warning: conflicts while merging %s! "
405 _("warning: conflicts while merging %s! "
406 "(edit, then use 'hg resolve --mark')\n"),
406 "(edit, then use 'hg resolve --mark')\n"),
407 precheck=_mergecheck)
407 precheck=_mergecheck)
408 def _imerge3(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
408 def _imerge3(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
409 """
409 """
410 Uses the internal non-interactive simple merge algorithm for merging
410 Uses the internal non-interactive simple merge algorithm for merging
411 files. It will fail if there are any conflicts and leave markers in
411 files. It will fail if there are any conflicts and leave markers in
412 the partially merged file. Marker will have three sections, one from each
412 the partially merged file. Marker will have three sections, one from each
413 side of the merge and one for the base content."""
413 side of the merge and one for the base content."""
414 if not labels:
414 if not labels:
415 labels = _defaultconflictlabels
415 labels = _defaultconflictlabels
416 if len(labels) < 3:
416 if len(labels) < 3:
417 labels.append('base')
417 labels.append('base')
418 return _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels)
418 return _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels)
419
419
420 def _imergeauto(repo, mynode, orig, fcd, fco, fca, toolconf, files,
420 def _imergeauto(repo, mynode, orig, fcd, fco, fca, toolconf, files,
421 labels=None, localorother=None):
421 labels=None, localorother=None):
422 """
422 """
423 Generic driver for _imergelocal and _imergeother
423 Generic driver for _imergelocal and _imergeother
424 """
424 """
425 assert localorother is not None
425 assert localorother is not None
426 tool, toolpath, binary, symlink = toolconf
426 tool, toolpath, binary, symlink = toolconf
427 r = simplemerge.simplemerge(repo.ui, fcd, fca, fco, label=labels,
427 r = simplemerge.simplemerge(repo.ui, fcd, fca, fco, label=labels,
428 localorother=localorother)
428 localorother=localorother)
429 return True, r
429 return True, r
430
430
431 @internaltool('merge-local', mergeonly, precheck=_mergecheck)
431 @internaltool('merge-local', mergeonly, precheck=_mergecheck)
432 def _imergelocal(*args, **kwargs):
432 def _imergelocal(*args, **kwargs):
433 """
433 """
434 Like :merge, but resolve all conflicts non-interactively in favor
434 Like :merge, but resolve all conflicts non-interactively in favor
435 of the local `p1()` changes."""
435 of the local `p1()` changes."""
436 success, status = _imergeauto(localorother='local', *args, **kwargs)
436 success, status = _imergeauto(localorother='local', *args, **kwargs)
437 return success, status, False
437 return success, status, False
438
438
439 @internaltool('merge-other', mergeonly, precheck=_mergecheck)
439 @internaltool('merge-other', mergeonly, precheck=_mergecheck)
440 def _imergeother(*args, **kwargs):
440 def _imergeother(*args, **kwargs):
441 """
441 """
442 Like :merge, but resolve all conflicts non-interactively in favor
442 Like :merge, but resolve all conflicts non-interactively in favor
443 of the other `p2()` changes."""
443 of the other `p2()` changes."""
444 success, status = _imergeauto(localorother='other', *args, **kwargs)
444 success, status = _imergeauto(localorother='other', *args, **kwargs)
445 return success, status, False
445 return success, status, False
446
446
447 @internaltool('tagmerge', mergeonly,
447 @internaltool('tagmerge', mergeonly,
448 _("automatic tag merging of %s failed! "
448 _("automatic tag merging of %s failed! "
449 "(use 'hg resolve --tool :merge' or another merge "
449 "(use 'hg resolve --tool :merge' or another merge "
450 "tool of your choice)\n"))
450 "tool of your choice)\n"))
451 def _itagmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
451 def _itagmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
452 """
452 """
453 Uses the internal tag merge algorithm (experimental).
453 Uses the internal tag merge algorithm (experimental).
454 """
454 """
455 success, status = tagmerge.merge(repo, fcd, fco, fca)
455 success, status = tagmerge.merge(repo, fcd, fco, fca)
456 return success, status, False
456 return success, status, False
457
457
458 @internaltool('dump', fullmerge)
458 @internaltool('dump', fullmerge)
459 def _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
459 def _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
460 """
460 """
461 Creates three versions of the files to merge, containing the
461 Creates three versions of the files to merge, containing the
462 contents of local, other and base. These files can then be used to
462 contents of local, other and base. These files can then be used to
463 perform a merge manually. If the file to be merged is named
463 perform a merge manually. If the file to be merged is named
464 ``a.txt``, these files will accordingly be named ``a.txt.local``,
464 ``a.txt``, these files will accordingly be named ``a.txt.local``,
465 ``a.txt.other`` and ``a.txt.base`` and they will be placed in the
465 ``a.txt.other`` and ``a.txt.base`` and they will be placed in the
466 same directory as ``a.txt``.
466 same directory as ``a.txt``.
467
467
468 This implies premerge. Therefore, files aren't dumped, if premerge
468 This implies premerge. Therefore, files aren't dumped, if premerge
469 runs successfully. Use :forcedump to forcibly write files out.
469 runs successfully. Use :forcedump to forcibly write files out.
470 """
470 """
471 a = _workingpath(repo, fcd)
471 a = _workingpath(repo, fcd)
472 fd = fcd.path()
472 fd = fcd.path()
473
473
474 from . import context
474 from . import context
475 if isinstance(fcd, context.overlayworkingfilectx):
475 if isinstance(fcd, context.overlayworkingfilectx):
476 raise error.InMemoryMergeConflictsError('in-memory merge does not '
476 raise error.InMemoryMergeConflictsError('in-memory merge does not '
477 'support the :dump tool.')
477 'support the :dump tool.')
478
478
479 util.writefile(a + ".local", fcd.decodeddata())
479 util.writefile(a + ".local", fcd.decodeddata())
480 repo.wwrite(fd + ".other", fco.data(), fco.flags())
480 repo.wwrite(fd + ".other", fco.data(), fco.flags())
481 repo.wwrite(fd + ".base", fca.data(), fca.flags())
481 repo.wwrite(fd + ".base", fca.data(), fca.flags())
482 return False, 1, False
482 return False, 1, False
483
483
484 @internaltool('forcedump', mergeonly)
484 @internaltool('forcedump', mergeonly)
485 def _forcedump(repo, mynode, orig, fcd, fco, fca, toolconf, files,
485 def _forcedump(repo, mynode, orig, fcd, fco, fca, toolconf, files,
486 labels=None):
486 labels=None):
487 """
487 """
488 Creates three versions of the files as same as :dump, but omits premerge.
488 Creates three versions of the files as same as :dump, but omits premerge.
489 """
489 """
490 return _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files,
490 return _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files,
491 labels=labels)
491 labels=labels)
492
492
493 def _xmergeimm(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
494 # In-memory merge simply raises an exception on all external merge tools,
495 # for now.
496 #
497 # It would be possible to run most tools with temporary files, but this
498 # raises the question of what to do if the user only partially resolves the
499 # file -- we can't leave a merge state. (Copy to somewhere in the .hg/
500 # directory and tell the user how to get it is my best idea, but it's
501 # clunky.)
502 raise error.InMemoryMergeConflictsError('in-memory merge does not support '
503 'external merge tools')
504
493 def _xmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
505 def _xmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
494 tool, toolpath, binary, symlink = toolconf
506 tool, toolpath, binary, symlink = toolconf
495 if fcd.isabsent() or fco.isabsent():
507 if fcd.isabsent() or fco.isabsent():
496 repo.ui.warn(_('warning: %s cannot merge change/delete conflict '
508 repo.ui.warn(_('warning: %s cannot merge change/delete conflict '
497 'for %s\n') % (tool, fcd.path()))
509 'for %s\n') % (tool, fcd.path()))
498 return False, 1, None
510 return False, 1, None
499 unused, unused, unused, back = files
511 unused, unused, unused, back = files
500 a = _workingpath(repo, fcd)
512 a = _workingpath(repo, fcd)
501 b, c = _maketempfiles(repo, fco, fca)
513 b, c = _maketempfiles(repo, fco, fca)
502 try:
514 try:
503 out = ""
515 out = ""
504 env = {'HG_FILE': fcd.path(),
516 env = {'HG_FILE': fcd.path(),
505 'HG_MY_NODE': short(mynode),
517 'HG_MY_NODE': short(mynode),
506 'HG_OTHER_NODE': str(fco.changectx()),
518 'HG_OTHER_NODE': str(fco.changectx()),
507 'HG_BASE_NODE': str(fca.changectx()),
519 'HG_BASE_NODE': str(fca.changectx()),
508 'HG_MY_ISLINK': 'l' in fcd.flags(),
520 'HG_MY_ISLINK': 'l' in fcd.flags(),
509 'HG_OTHER_ISLINK': 'l' in fco.flags(),
521 'HG_OTHER_ISLINK': 'l' in fco.flags(),
510 'HG_BASE_ISLINK': 'l' in fca.flags(),
522 'HG_BASE_ISLINK': 'l' in fca.flags(),
511 }
523 }
512 ui = repo.ui
524 ui = repo.ui
513
525
514 args = _toolstr(ui, tool, "args")
526 args = _toolstr(ui, tool, "args")
515 if "$output" in args:
527 if "$output" in args:
516 # read input from backup, write to original
528 # read input from backup, write to original
517 out = a
529 out = a
518 a = repo.wvfs.join(back.path())
530 a = repo.wvfs.join(back.path())
519 replace = {'local': a, 'base': b, 'other': c, 'output': out}
531 replace = {'local': a, 'base': b, 'other': c, 'output': out}
520 args = util.interpolate(r'\$', replace, args,
532 args = util.interpolate(r'\$', replace, args,
521 lambda s: util.shellquote(util.localpath(s)))
533 lambda s: util.shellquote(util.localpath(s)))
522 cmd = toolpath + ' ' + args
534 cmd = toolpath + ' ' + args
523 if _toolbool(ui, tool, "gui"):
535 if _toolbool(ui, tool, "gui"):
524 repo.ui.status(_('running merge tool %s for file %s\n') %
536 repo.ui.status(_('running merge tool %s for file %s\n') %
525 (tool, fcd.path()))
537 (tool, fcd.path()))
526 repo.ui.debug('launching merge tool: %s\n' % cmd)
538 repo.ui.debug('launching merge tool: %s\n' % cmd)
527 r = ui.system(cmd, cwd=repo.root, environ=env, blockedtag='mergetool')
539 r = ui.system(cmd, cwd=repo.root, environ=env, blockedtag='mergetool')
528 repo.ui.debug('merge tool returned: %d\n' % r)
540 repo.ui.debug('merge tool returned: %d\n' % r)
529 return True, r, False
541 return True, r, False
530 finally:
542 finally:
531 util.unlink(b)
543 util.unlink(b)
532 util.unlink(c)
544 util.unlink(c)
533
545
534 def _formatconflictmarker(repo, ctx, template, label, pad):
546 def _formatconflictmarker(repo, ctx, template, label, pad):
535 """Applies the given template to the ctx, prefixed by the label.
547 """Applies the given template to the ctx, prefixed by the label.
536
548
537 Pad is the minimum width of the label prefix, so that multiple markers
549 Pad is the minimum width of the label prefix, so that multiple markers
538 can have aligned templated parts.
550 can have aligned templated parts.
539 """
551 """
540 if ctx.node() is None:
552 if ctx.node() is None:
541 ctx = ctx.p1()
553 ctx = ctx.p1()
542
554
543 props = templatekw.keywords.copy()
555 props = templatekw.keywords.copy()
544 props['templ'] = template
556 props['templ'] = template
545 props['ctx'] = ctx
557 props['ctx'] = ctx
546 props['repo'] = repo
558 props['repo'] = repo
547 templateresult = template.render(props)
559 templateresult = template.render(props)
548
560
549 label = ('%s:' % label).ljust(pad + 1)
561 label = ('%s:' % label).ljust(pad + 1)
550 mark = '%s %s' % (label, templateresult)
562 mark = '%s %s' % (label, templateresult)
551
563
552 if mark:
564 if mark:
553 mark = mark.splitlines()[0] # split for safety
565 mark = mark.splitlines()[0] # split for safety
554
566
555 # 8 for the prefix of conflict marker lines (e.g. '<<<<<<< ')
567 # 8 for the prefix of conflict marker lines (e.g. '<<<<<<< ')
556 return util.ellipsis(mark, 80 - 8)
568 return util.ellipsis(mark, 80 - 8)
557
569
558 _defaultconflictlabels = ['local', 'other']
570 _defaultconflictlabels = ['local', 'other']
559
571
560 def _formatlabels(repo, fcd, fco, fca, labels):
572 def _formatlabels(repo, fcd, fco, fca, labels):
561 """Formats the given labels using the conflict marker template.
573 """Formats the given labels using the conflict marker template.
562
574
563 Returns a list of formatted labels.
575 Returns a list of formatted labels.
564 """
576 """
565 cd = fcd.changectx()
577 cd = fcd.changectx()
566 co = fco.changectx()
578 co = fco.changectx()
567 ca = fca.changectx()
579 ca = fca.changectx()
568
580
569 ui = repo.ui
581 ui = repo.ui
570 template = ui.config('ui', 'mergemarkertemplate')
582 template = ui.config('ui', 'mergemarkertemplate')
571 template = templater.unquotestring(template)
583 template = templater.unquotestring(template)
572 tmpl = formatter.maketemplater(ui, template)
584 tmpl = formatter.maketemplater(ui, template)
573
585
574 pad = max(len(l) for l in labels)
586 pad = max(len(l) for l in labels)
575
587
576 newlabels = [_formatconflictmarker(repo, cd, tmpl, labels[0], pad),
588 newlabels = [_formatconflictmarker(repo, cd, tmpl, labels[0], pad),
577 _formatconflictmarker(repo, co, tmpl, labels[1], pad)]
589 _formatconflictmarker(repo, co, tmpl, labels[1], pad)]
578 if len(labels) > 2:
590 if len(labels) > 2:
579 newlabels.append(_formatconflictmarker(repo, ca, tmpl, labels[2], pad))
591 newlabels.append(_formatconflictmarker(repo, ca, tmpl, labels[2], pad))
580 return newlabels
592 return newlabels
581
593
582 def partextras(labels):
594 def partextras(labels):
583 """Return a dictionary of extra labels for use in prompts to the user
595 """Return a dictionary of extra labels for use in prompts to the user
584
596
585 Intended use is in strings of the form "(l)ocal%(l)s".
597 Intended use is in strings of the form "(l)ocal%(l)s".
586 """
598 """
587 if labels is None:
599 if labels is None:
588 return {
600 return {
589 "l": "",
601 "l": "",
590 "o": "",
602 "o": "",
591 }
603 }
592
604
593 return {
605 return {
594 "l": " [%s]" % labels[0],
606 "l": " [%s]" % labels[0],
595 "o": " [%s]" % labels[1],
607 "o": " [%s]" % labels[1],
596 }
608 }
597
609
598 def _restorebackup(fcd, back):
610 def _restorebackup(fcd, back):
599 # TODO: Add a workingfilectx.write(otherfilectx) path so we can use
611 # TODO: Add a workingfilectx.write(otherfilectx) path so we can use
600 # util.copy here instead.
612 # util.copy here instead.
601 fcd.write(back.data(), fcd.flags())
613 fcd.write(back.data(), fcd.flags())
602
614
603 def _makebackup(repo, ui, wctx, fcd, premerge):
615 def _makebackup(repo, ui, wctx, fcd, premerge):
604 """Makes and returns a filectx-like object for ``fcd``'s backup file.
616 """Makes and returns a filectx-like object for ``fcd``'s backup file.
605
617
606 In addition to preserving the user's pre-existing modifications to `fcd`
618 In addition to preserving the user's pre-existing modifications to `fcd`
607 (if any), the backup is used to undo certain premerges, confirm whether a
619 (if any), the backup is used to undo certain premerges, confirm whether a
608 merge changed anything, and determine what line endings the new file should
620 merge changed anything, and determine what line endings the new file should
609 have.
621 have.
610 """
622 """
611 if fcd.isabsent():
623 if fcd.isabsent():
612 return None
624 return None
613 # TODO: Break this import cycle somehow. (filectx -> ctx -> fileset ->
625 # TODO: Break this import cycle somehow. (filectx -> ctx -> fileset ->
614 # merge -> filemerge). (I suspect the fileset import is the weakest link)
626 # merge -> filemerge). (I suspect the fileset import is the weakest link)
615 from . import context
627 from . import context
616 a = _workingpath(repo, fcd)
628 a = _workingpath(repo, fcd)
617 back = scmutil.origpath(ui, repo, a)
629 back = scmutil.origpath(ui, repo, a)
618 inworkingdir = (back.startswith(repo.wvfs.base) and not
630 inworkingdir = (back.startswith(repo.wvfs.base) and not
619 back.startswith(repo.vfs.base))
631 back.startswith(repo.vfs.base))
620
632
621 if isinstance(fcd, context.overlayworkingfilectx) and inworkingdir:
633 if isinstance(fcd, context.overlayworkingfilectx) and inworkingdir:
622 # If the backup file is to be in the working directory, and we're
634 # If the backup file is to be in the working directory, and we're
623 # merging in-memory, we must redirect the backup to the memory context
635 # merging in-memory, we must redirect the backup to the memory context
624 # so we don't disturb the working directory.
636 # so we don't disturb the working directory.
625 relpath = back[len(repo.wvfs.base) + 1:]
637 relpath = back[len(repo.wvfs.base) + 1:]
626 wctx[relpath].write(fcd.data(), fcd.flags())
638 wctx[relpath].write(fcd.data(), fcd.flags())
627 return wctx[relpath]
639 return wctx[relpath]
628 else:
640 else:
629 # Otherwise, write to wherever the user specified the backups should go.
641 # Otherwise, write to wherever the user specified the backups should go.
630 #
642 #
631 # A arbitraryfilectx is returned, so we can run the same functions on
643 # A arbitraryfilectx is returned, so we can run the same functions on
632 # the backup context regardless of where it lives.
644 # the backup context regardless of where it lives.
633 if premerge:
645 if premerge:
634 util.copyfile(a, back)
646 util.copyfile(a, back)
635 return context.arbitraryfilectx(back, repo=repo)
647 return context.arbitraryfilectx(back, repo=repo)
636
648
637 def _maketempfiles(repo, fco, fca):
649 def _maketempfiles(repo, fco, fca):
638 """Writes out `fco` and `fca` as temporary files, so an external merge
650 """Writes out `fco` and `fca` as temporary files, so an external merge
639 tool may use them.
651 tool may use them.
640 """
652 """
641 def temp(prefix, ctx):
653 def temp(prefix, ctx):
642 fullbase, ext = os.path.splitext(ctx.path())
654 fullbase, ext = os.path.splitext(ctx.path())
643 pre = "%s~%s." % (os.path.basename(fullbase), prefix)
655 pre = "%s~%s." % (os.path.basename(fullbase), prefix)
644 (fd, name) = tempfile.mkstemp(prefix=pre, suffix=ext)
656 (fd, name) = tempfile.mkstemp(prefix=pre, suffix=ext)
645 data = repo.wwritedata(ctx.path(), ctx.data())
657 data = repo.wwritedata(ctx.path(), ctx.data())
646 f = os.fdopen(fd, pycompat.sysstr("wb"))
658 f = os.fdopen(fd, pycompat.sysstr("wb"))
647 f.write(data)
659 f.write(data)
648 f.close()
660 f.close()
649 return name
661 return name
650
662
651 b = temp("base", fca)
663 b = temp("base", fca)
652 c = temp("other", fco)
664 c = temp("other", fco)
653
665
654 return b, c
666 return b, c
655
667
656 def _filemerge(premerge, repo, wctx, mynode, orig, fcd, fco, fca, labels=None):
668 def _filemerge(premerge, repo, wctx, mynode, orig, fcd, fco, fca, labels=None):
657 """perform a 3-way merge in the working directory
669 """perform a 3-way merge in the working directory
658
670
659 premerge = whether this is a premerge
671 premerge = whether this is a premerge
660 mynode = parent node before merge
672 mynode = parent node before merge
661 orig = original local filename before merge
673 orig = original local filename before merge
662 fco = other file context
674 fco = other file context
663 fca = ancestor file context
675 fca = ancestor file context
664 fcd = local file context for current/destination file
676 fcd = local file context for current/destination file
665
677
666 Returns whether the merge is complete, the return value of the merge, and
678 Returns whether the merge is complete, the return value of the merge, and
667 a boolean indicating whether the file was deleted from disk."""
679 a boolean indicating whether the file was deleted from disk."""
668
680
669 if not fco.cmp(fcd): # files identical?
681 if not fco.cmp(fcd): # files identical?
670 return True, None, False
682 return True, None, False
671
683
672 ui = repo.ui
684 ui = repo.ui
673 fd = fcd.path()
685 fd = fcd.path()
674 binary = fcd.isbinary() or fco.isbinary() or fca.isbinary()
686 binary = fcd.isbinary() or fco.isbinary() or fca.isbinary()
675 symlink = 'l' in fcd.flags() + fco.flags()
687 symlink = 'l' in fcd.flags() + fco.flags()
676 changedelete = fcd.isabsent() or fco.isabsent()
688 changedelete = fcd.isabsent() or fco.isabsent()
677 tool, toolpath = _picktool(repo, ui, fd, binary, symlink, changedelete)
689 tool, toolpath = _picktool(repo, ui, fd, binary, symlink, changedelete)
678 if tool in internals and tool.startswith('internal:'):
690 if tool in internals and tool.startswith('internal:'):
679 # normalize to new-style names (':merge' etc)
691 # normalize to new-style names (':merge' etc)
680 tool = tool[len('internal'):]
692 tool = tool[len('internal'):]
681 ui.debug("picked tool '%s' for %s (binary %s symlink %s changedelete %s)\n"
693 ui.debug("picked tool '%s' for %s (binary %s symlink %s changedelete %s)\n"
682 % (tool, fd, pycompat.bytestr(binary), pycompat.bytestr(symlink),
694 % (tool, fd, pycompat.bytestr(binary), pycompat.bytestr(symlink),
683 pycompat.bytestr(changedelete)))
695 pycompat.bytestr(changedelete)))
684
696
685 if tool in internals:
697 if tool in internals:
686 func = internals[tool]
698 func = internals[tool]
687 mergetype = func.mergetype
699 mergetype = func.mergetype
688 onfailure = func.onfailure
700 onfailure = func.onfailure
689 precheck = func.precheck
701 precheck = func.precheck
690 else:
702 else:
691 func = _xmerge
703 if wctx.isinmemory():
704 func = _xmergeimm
705 else:
706 func = _xmerge
692 mergetype = fullmerge
707 mergetype = fullmerge
693 onfailure = _("merging %s failed!\n")
708 onfailure = _("merging %s failed!\n")
694 precheck = None
709 precheck = None
695
710
696 if wctx.isinmemory():
697 raise error.InMemoryMergeConflictsError('in-memory merge does not '
698 'support external merge '
699 'tools')
700
701 toolconf = tool, toolpath, binary, symlink
711 toolconf = tool, toolpath, binary, symlink
702
712
703 if mergetype == nomerge:
713 if mergetype == nomerge:
704 r, deleted = func(repo, mynode, orig, fcd, fco, fca, toolconf, labels)
714 r, deleted = func(repo, mynode, orig, fcd, fco, fca, toolconf, labels)
705 return True, r, deleted
715 return True, r, deleted
706
716
707 if premerge:
717 if premerge:
708 if orig != fco.path():
718 if orig != fco.path():
709 ui.status(_("merging %s and %s to %s\n") % (orig, fco.path(), fd))
719 ui.status(_("merging %s and %s to %s\n") % (orig, fco.path(), fd))
710 else:
720 else:
711 ui.status(_("merging %s\n") % fd)
721 ui.status(_("merging %s\n") % fd)
712
722
713 ui.debug("my %s other %s ancestor %s\n" % (fcd, fco, fca))
723 ui.debug("my %s other %s ancestor %s\n" % (fcd, fco, fca))
714
724
715 if precheck and not precheck(repo, mynode, orig, fcd, fco, fca,
725 if precheck and not precheck(repo, mynode, orig, fcd, fco, fca,
716 toolconf):
726 toolconf):
717 if onfailure:
727 if onfailure:
718 if wctx.isinmemory():
728 if wctx.isinmemory():
719 raise error.InMemoryMergeConflictsError('in-memory merge does '
729 raise error.InMemoryMergeConflictsError('in-memory merge does '
720 'not support merge '
730 'not support merge '
721 'conflicts')
731 'conflicts')
722 ui.warn(onfailure % fd)
732 ui.warn(onfailure % fd)
723 return True, 1, False
733 return True, 1, False
724
734
725 back = _makebackup(repo, ui, wctx, fcd, premerge)
735 back = _makebackup(repo, ui, wctx, fcd, premerge)
726 files = (None, None, None, back)
736 files = (None, None, None, back)
727 r = 1
737 r = 1
728 try:
738 try:
729 markerstyle = ui.config('ui', 'mergemarkers')
739 markerstyle = ui.config('ui', 'mergemarkers')
730 if not labels:
740 if not labels:
731 labels = _defaultconflictlabels
741 labels = _defaultconflictlabels
732 if markerstyle != 'basic':
742 if markerstyle != 'basic':
733 labels = _formatlabels(repo, fcd, fco, fca, labels)
743 labels = _formatlabels(repo, fcd, fco, fca, labels)
734
744
735 if premerge and mergetype == fullmerge:
745 if premerge and mergetype == fullmerge:
736 r = _premerge(repo, fcd, fco, fca, toolconf, files, labels=labels)
746 r = _premerge(repo, fcd, fco, fca, toolconf, files, labels=labels)
737 # complete if premerge successful (r is 0)
747 # complete if premerge successful (r is 0)
738 return not r, r, False
748 return not r, r, False
739
749
740 needcheck, r, deleted = func(repo, mynode, orig, fcd, fco, fca,
750 needcheck, r, deleted = func(repo, mynode, orig, fcd, fco, fca,
741 toolconf, files, labels=labels)
751 toolconf, files, labels=labels)
742
752
743 if needcheck:
753 if needcheck:
744 r = _check(repo, r, ui, tool, fcd, files)
754 r = _check(repo, r, ui, tool, fcd, files)
745
755
746 if r:
756 if r:
747 if onfailure:
757 if onfailure:
748 if wctx.isinmemory():
758 if wctx.isinmemory():
749 raise error.InMemoryMergeConflictsError('in-memory merge '
759 raise error.InMemoryMergeConflictsError('in-memory merge '
750 'does not support '
760 'does not support '
751 'merge conflicts')
761 'merge conflicts')
752 ui.warn(onfailure % fd)
762 ui.warn(onfailure % fd)
753 _onfilemergefailure(ui)
763 _onfilemergefailure(ui)
754
764
755 return True, r, deleted
765 return True, r, deleted
756 finally:
766 finally:
757 if not r and back is not None:
767 if not r and back is not None:
758 back.remove()
768 back.remove()
759
769
760 def _haltmerge():
770 def _haltmerge():
761 msg = _('merge halted after failed merge (see hg resolve)')
771 msg = _('merge halted after failed merge (see hg resolve)')
762 raise error.InterventionRequired(msg)
772 raise error.InterventionRequired(msg)
763
773
764 def _onfilemergefailure(ui):
774 def _onfilemergefailure(ui):
765 action = ui.config('merge', 'on-failure')
775 action = ui.config('merge', 'on-failure')
766 if action == 'prompt':
776 if action == 'prompt':
767 msg = _('continue merge operation (yn)?' '$$ &Yes $$ &No')
777 msg = _('continue merge operation (yn)?' '$$ &Yes $$ &No')
768 if ui.promptchoice(msg, 0) == 1:
778 if ui.promptchoice(msg, 0) == 1:
769 _haltmerge()
779 _haltmerge()
770 if action == 'halt':
780 if action == 'halt':
771 _haltmerge()
781 _haltmerge()
772 # default action is 'continue', in which case we neither prompt nor halt
782 # default action is 'continue', in which case we neither prompt nor halt
773
783
774 def _check(repo, r, ui, tool, fcd, files):
784 def _check(repo, r, ui, tool, fcd, files):
775 fd = fcd.path()
785 fd = fcd.path()
776 unused, unused, unused, back = files
786 unused, unused, unused, back = files
777
787
778 if not r and (_toolbool(ui, tool, "checkconflicts") or
788 if not r and (_toolbool(ui, tool, "checkconflicts") or
779 'conflicts' in _toollist(ui, tool, "check")):
789 'conflicts' in _toollist(ui, tool, "check")):
780 if re.search("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcd.data(),
790 if re.search("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcd.data(),
781 re.MULTILINE):
791 re.MULTILINE):
782 r = 1
792 r = 1
783
793
784 checked = False
794 checked = False
785 if 'prompt' in _toollist(ui, tool, "check"):
795 if 'prompt' in _toollist(ui, tool, "check"):
786 checked = True
796 checked = True
787 if ui.promptchoice(_("was merge of '%s' successful (yn)?"
797 if ui.promptchoice(_("was merge of '%s' successful (yn)?"
788 "$$ &Yes $$ &No") % fd, 1):
798 "$$ &Yes $$ &No") % fd, 1):
789 r = 1
799 r = 1
790
800
791 if not r and not checked and (_toolbool(ui, tool, "checkchanged") or
801 if not r and not checked and (_toolbool(ui, tool, "checkchanged") or
792 'changed' in
802 'changed' in
793 _toollist(ui, tool, "check")):
803 _toollist(ui, tool, "check")):
794 if back is not None and not fcd.cmp(back):
804 if back is not None and not fcd.cmp(back):
795 if ui.promptchoice(_(" output file %s appears unchanged\n"
805 if ui.promptchoice(_(" output file %s appears unchanged\n"
796 "was merge successful (yn)?"
806 "was merge successful (yn)?"
797 "$$ &Yes $$ &No") % fd, 1):
807 "$$ &Yes $$ &No") % fd, 1):
798 r = 1
808 r = 1
799
809
800 if back is not None and _toolbool(ui, tool, "fixeol"):
810 if back is not None and _toolbool(ui, tool, "fixeol"):
801 _matcheol(_workingpath(repo, fcd), back)
811 _matcheol(_workingpath(repo, fcd), back)
802
812
803 return r
813 return r
804
814
805 def _workingpath(repo, ctx):
815 def _workingpath(repo, ctx):
806 return repo.wjoin(ctx.path())
816 return repo.wjoin(ctx.path())
807
817
808 def premerge(repo, wctx, mynode, orig, fcd, fco, fca, labels=None):
818 def premerge(repo, wctx, mynode, orig, fcd, fco, fca, labels=None):
809 return _filemerge(True, repo, wctx, mynode, orig, fcd, fco, fca,
819 return _filemerge(True, repo, wctx, mynode, orig, fcd, fco, fca,
810 labels=labels)
820 labels=labels)
811
821
812 def filemerge(repo, wctx, mynode, orig, fcd, fco, fca, labels=None):
822 def filemerge(repo, wctx, mynode, orig, fcd, fco, fca, labels=None):
813 return _filemerge(False, repo, wctx, mynode, orig, fcd, fco, fca,
823 return _filemerge(False, repo, wctx, mynode, orig, fcd, fco, fca,
814 labels=labels)
824 labels=labels)
815
825
816 def loadinternalmerge(ui, extname, registrarobj):
826 def loadinternalmerge(ui, extname, registrarobj):
817 """Load internal merge tool from specified registrarobj
827 """Load internal merge tool from specified registrarobj
818 """
828 """
819 for name, func in registrarobj._table.iteritems():
829 for name, func in registrarobj._table.iteritems():
820 fullname = ':' + name
830 fullname = ':' + name
821 internals[fullname] = func
831 internals[fullname] = func
822 internals['internal:' + name] = func
832 internals['internal:' + name] = func
823 internalsdoc[fullname] = func
833 internalsdoc[fullname] = func
824
834
825 # load built-in merge tools explicitly to setup internalsdoc
835 # load built-in merge tools explicitly to setup internalsdoc
826 loadinternalmerge(None, None, internaltool)
836 loadinternalmerge(None, None, internaltool)
827
837
828 # tell hggettext to extract docstrings from these functions:
838 # tell hggettext to extract docstrings from these functions:
829 i18nfunctions = internals.values()
839 i18nfunctions = internals.values()
General Comments 0
You need to be logged in to leave comments. Login now