##// END OF EJS Templates
filemerge: raise InMemoryMergeConflictsError if we hit merge conflicts in IMM...
Phil Cohen -
r35283:46d7f071 default
parent child Browse files
Show More
@@ -1,816 +1,829 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
245 # conflicts.
246 if fcd.changectx().isinmemory():
247 raise error.InMemoryMergeConflictsError('in-memory merge does not '
248 'support file conflicts')
249
244 prompts = partextras(labels)
250 prompts = partextras(labels)
245 prompts['fd'] = fd
251 prompts['fd'] = fd
246 try:
252 try:
247 if fco.isabsent():
253 if fco.isabsent():
248 index = ui.promptchoice(
254 index = ui.promptchoice(
249 _localchangedotherdeletedmsg % prompts, 2)
255 _localchangedotherdeletedmsg % prompts, 2)
250 choice = ['local', 'other', 'unresolved'][index]
256 choice = ['local', 'other', 'unresolved'][index]
251 elif fcd.isabsent():
257 elif fcd.isabsent():
252 index = ui.promptchoice(
258 index = ui.promptchoice(
253 _otherchangedlocaldeletedmsg % prompts, 2)
259 _otherchangedlocaldeletedmsg % prompts, 2)
254 choice = ['other', 'local', 'unresolved'][index]
260 choice = ['other', 'local', 'unresolved'][index]
255 else:
261 else:
256 index = ui.promptchoice(
262 index = ui.promptchoice(
257 _("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"
258 " for %(fd)s?"
264 " for %(fd)s?"
259 "$$ &Local $$ &Other $$ &Unresolved") % prompts, 2)
265 "$$ &Local $$ &Other $$ &Unresolved") % prompts, 2)
260 choice = ['local', 'other', 'unresolved'][index]
266 choice = ['local', 'other', 'unresolved'][index]
261
267
262 if choice == 'other':
268 if choice == 'other':
263 return _iother(repo, mynode, orig, fcd, fco, fca, toolconf,
269 return _iother(repo, mynode, orig, fcd, fco, fca, toolconf,
264 labels)
270 labels)
265 elif choice == 'local':
271 elif choice == 'local':
266 return _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf,
272 return _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf,
267 labels)
273 labels)
268 elif choice == 'unresolved':
274 elif choice == 'unresolved':
269 return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf,
275 return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf,
270 labels)
276 labels)
271 except error.ResponseExpected:
277 except error.ResponseExpected:
272 ui.write("\n")
278 ui.write("\n")
273 return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf,
279 return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf,
274 labels)
280 labels)
275
281
276 @internaltool('local', nomerge)
282 @internaltool('local', nomerge)
277 def _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
283 def _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
278 """Uses the local `p1()` version of files as the merged version."""
284 """Uses the local `p1()` version of files as the merged version."""
279 return 0, fcd.isabsent()
285 return 0, fcd.isabsent()
280
286
281 @internaltool('other', nomerge)
287 @internaltool('other', nomerge)
282 def _iother(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
288 def _iother(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
283 """Uses the other `p2()` version of files as the merged version."""
289 """Uses the other `p2()` version of files as the merged version."""
284 if fco.isabsent():
290 if fco.isabsent():
285 # local changed, remote deleted -- 'deleted' picked
291 # local changed, remote deleted -- 'deleted' picked
286 _underlyingfctxifabsent(fcd).remove()
292 _underlyingfctxifabsent(fcd).remove()
287 deleted = True
293 deleted = True
288 else:
294 else:
289 _underlyingfctxifabsent(fcd).write(fco.data(), fco.flags())
295 _underlyingfctxifabsent(fcd).write(fco.data(), fco.flags())
290 deleted = False
296 deleted = False
291 return 0, deleted
297 return 0, deleted
292
298
293 @internaltool('fail', nomerge)
299 @internaltool('fail', nomerge)
294 def _ifail(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
300 def _ifail(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
295 """
301 """
296 Rather than attempting to merge files that were modified on both
302 Rather than attempting to merge files that were modified on both
297 branches, it marks them as unresolved. The resolve command must be
303 branches, it marks them as unresolved. The resolve command must be
298 used to resolve these conflicts."""
304 used to resolve these conflicts."""
299 # for change/delete conflicts write out the changed version, then fail
305 # for change/delete conflicts write out the changed version, then fail
300 if fcd.isabsent():
306 if fcd.isabsent():
301 _underlyingfctxifabsent(fcd).write(fco.data(), fco.flags())
307 _underlyingfctxifabsent(fcd).write(fco.data(), fco.flags())
302 return 1, False
308 return 1, False
303
309
304 def _underlyingfctxifabsent(filectx):
310 def _underlyingfctxifabsent(filectx):
305 """Sometimes when resolving, our fcd is actually an absentfilectx, but
311 """Sometimes when resolving, our fcd is actually an absentfilectx, but
306 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
307 underyling workingfilectx in that case.
313 underyling workingfilectx in that case.
308 """
314 """
309 if filectx.isabsent():
315 if filectx.isabsent():
310 return filectx.changectx()[filectx.path()]
316 return filectx.changectx()[filectx.path()]
311 else:
317 else:
312 return filectx
318 return filectx
313
319
314 def _premerge(repo, fcd, fco, fca, toolconf, files, labels=None):
320 def _premerge(repo, fcd, fco, fca, toolconf, files, labels=None):
315 tool, toolpath, binary, symlink = toolconf
321 tool, toolpath, binary, symlink = toolconf
316 if symlink or fcd.isabsent() or fco.isabsent():
322 if symlink or fcd.isabsent() or fco.isabsent():
317 return 1
323 return 1
318 unused, unused, unused, back = files
324 unused, unused, unused, back = files
319
325
320 ui = repo.ui
326 ui = repo.ui
321
327
322 validkeep = ['keep', 'keep-merge3']
328 validkeep = ['keep', 'keep-merge3']
323
329
324 # do we attempt to simplemerge first?
330 # do we attempt to simplemerge first?
325 try:
331 try:
326 premerge = _toolbool(ui, tool, "premerge", not binary)
332 premerge = _toolbool(ui, tool, "premerge", not binary)
327 except error.ConfigError:
333 except error.ConfigError:
328 premerge = _toolstr(ui, tool, "premerge", "").lower()
334 premerge = _toolstr(ui, tool, "premerge", "").lower()
329 if premerge not in validkeep:
335 if premerge not in validkeep:
330 _valid = ', '.join(["'" + v + "'" for v in validkeep])
336 _valid = ', '.join(["'" + v + "'" for v in validkeep])
331 raise error.ConfigError(_("%s.premerge not valid "
337 raise error.ConfigError(_("%s.premerge not valid "
332 "('%s' is neither boolean nor %s)") %
338 "('%s' is neither boolean nor %s)") %
333 (tool, premerge, _valid))
339 (tool, premerge, _valid))
334
340
335 if premerge:
341 if premerge:
336 if premerge == 'keep-merge3':
342 if premerge == 'keep-merge3':
337 if not labels:
343 if not labels:
338 labels = _defaultconflictlabels
344 labels = _defaultconflictlabels
339 if len(labels) < 3:
345 if len(labels) < 3:
340 labels.append('base')
346 labels.append('base')
341 r = simplemerge.simplemerge(ui, fcd, fca, fco, quiet=True, label=labels)
347 r = simplemerge.simplemerge(ui, fcd, fca, fco, quiet=True, label=labels)
342 if not r:
348 if not r:
343 ui.debug(" premerge successful\n")
349 ui.debug(" premerge successful\n")
344 return 0
350 return 0
345 if premerge not in validkeep:
351 if premerge not in validkeep:
346 # restore from backup and try again
352 # restore from backup and try again
347 _restorebackup(fcd, back)
353 _restorebackup(fcd, back)
348 return 1 # continue merging
354 return 1 # continue merging
349
355
350 def _mergecheck(repo, mynode, orig, fcd, fco, fca, toolconf):
356 def _mergecheck(repo, mynode, orig, fcd, fco, fca, toolconf):
351 tool, toolpath, binary, symlink = toolconf
357 tool, toolpath, binary, symlink = toolconf
352 if symlink:
358 if symlink:
353 repo.ui.warn(_('warning: internal %s cannot merge symlinks '
359 repo.ui.warn(_('warning: internal %s cannot merge symlinks '
354 'for %s\n') % (tool, fcd.path()))
360 'for %s\n') % (tool, fcd.path()))
355 return False
361 return False
356 if fcd.isabsent() or fco.isabsent():
362 if fcd.isabsent() or fco.isabsent():
357 repo.ui.warn(_('warning: internal %s cannot merge change/delete '
363 repo.ui.warn(_('warning: internal %s cannot merge change/delete '
358 'conflict for %s\n') % (tool, fcd.path()))
364 'conflict for %s\n') % (tool, fcd.path()))
359 return False
365 return False
360 return True
366 return True
361
367
362 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):
363 """
369 """
364 Uses the internal non-interactive simple merge algorithm for merging
370 Uses the internal non-interactive simple merge algorithm for merging
365 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
366 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
367 of merge, unless mode equals 'union' which suppresses the markers."""
373 of merge, unless mode equals 'union' which suppresses the markers."""
368 ui = repo.ui
374 ui = repo.ui
369
375
370 r = simplemerge.simplemerge(ui, fcd, fca, fco, label=labels, mode=mode)
376 r = simplemerge.simplemerge(ui, fcd, fca, fco, label=labels, mode=mode)
371 return True, r, False
377 return True, r, False
372
378
373 @internaltool('union', fullmerge,
379 @internaltool('union', fullmerge,
374 _("warning: conflicts while merging %s! "
380 _("warning: conflicts while merging %s! "
375 "(edit, then use 'hg resolve --mark')\n"),
381 "(edit, then use 'hg resolve --mark')\n"),
376 precheck=_mergecheck)
382 precheck=_mergecheck)
377 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):
378 """
384 """
379 Uses the internal non-interactive simple merge algorithm for merging
385 Uses the internal non-interactive simple merge algorithm for merging
380 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.
381 No markers are inserted."""
387 No markers are inserted."""
382 return _merge(repo, mynode, orig, fcd, fco, fca, toolconf,
388 return _merge(repo, mynode, orig, fcd, fco, fca, toolconf,
383 files, labels, 'union')
389 files, labels, 'union')
384
390
385 @internaltool('merge', fullmerge,
391 @internaltool('merge', fullmerge,
386 _("warning: conflicts while merging %s! "
392 _("warning: conflicts while merging %s! "
387 "(edit, then use 'hg resolve --mark')\n"),
393 "(edit, then use 'hg resolve --mark')\n"),
388 precheck=_mergecheck)
394 precheck=_mergecheck)
389 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):
390 """
396 """
391 Uses the internal non-interactive simple merge algorithm for merging
397 Uses the internal non-interactive simple merge algorithm for merging
392 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
393 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
394 of merge."""
400 of merge."""
395 return _merge(repo, mynode, orig, fcd, fco, fca, toolconf,
401 return _merge(repo, mynode, orig, fcd, fco, fca, toolconf,
396 files, labels, 'merge')
402 files, labels, 'merge')
397
403
398 @internaltool('merge3', fullmerge,
404 @internaltool('merge3', fullmerge,
399 _("warning: conflicts while merging %s! "
405 _("warning: conflicts while merging %s! "
400 "(edit, then use 'hg resolve --mark')\n"),
406 "(edit, then use 'hg resolve --mark')\n"),
401 precheck=_mergecheck)
407 precheck=_mergecheck)
402 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):
403 """
409 """
404 Uses the internal non-interactive simple merge algorithm for merging
410 Uses the internal non-interactive simple merge algorithm for merging
405 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
406 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
407 side of the merge and one for the base content."""
413 side of the merge and one for the base content."""
408 if not labels:
414 if not labels:
409 labels = _defaultconflictlabels
415 labels = _defaultconflictlabels
410 if len(labels) < 3:
416 if len(labels) < 3:
411 labels.append('base')
417 labels.append('base')
412 return _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels)
418 return _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels)
413
419
414 def _imergeauto(repo, mynode, orig, fcd, fco, fca, toolconf, files,
420 def _imergeauto(repo, mynode, orig, fcd, fco, fca, toolconf, files,
415 labels=None, localorother=None):
421 labels=None, localorother=None):
416 """
422 """
417 Generic driver for _imergelocal and _imergeother
423 Generic driver for _imergelocal and _imergeother
418 """
424 """
419 assert localorother is not None
425 assert localorother is not None
420 tool, toolpath, binary, symlink = toolconf
426 tool, toolpath, binary, symlink = toolconf
421 r = simplemerge.simplemerge(repo.ui, fcd, fca, fco, label=labels,
427 r = simplemerge.simplemerge(repo.ui, fcd, fca, fco, label=labels,
422 localorother=localorother)
428 localorother=localorother)
423 return True, r
429 return True, r
424
430
425 @internaltool('merge-local', mergeonly, precheck=_mergecheck)
431 @internaltool('merge-local', mergeonly, precheck=_mergecheck)
426 def _imergelocal(*args, **kwargs):
432 def _imergelocal(*args, **kwargs):
427 """
433 """
428 Like :merge, but resolve all conflicts non-interactively in favor
434 Like :merge, but resolve all conflicts non-interactively in favor
429 of the local `p1()` changes."""
435 of the local `p1()` changes."""
430 success, status = _imergeauto(localorother='local', *args, **kwargs)
436 success, status = _imergeauto(localorother='local', *args, **kwargs)
431 return success, status, False
437 return success, status, False
432
438
433 @internaltool('merge-other', mergeonly, precheck=_mergecheck)
439 @internaltool('merge-other', mergeonly, precheck=_mergecheck)
434 def _imergeother(*args, **kwargs):
440 def _imergeother(*args, **kwargs):
435 """
441 """
436 Like :merge, but resolve all conflicts non-interactively in favor
442 Like :merge, but resolve all conflicts non-interactively in favor
437 of the other `p2()` changes."""
443 of the other `p2()` changes."""
438 success, status = _imergeauto(localorother='other', *args, **kwargs)
444 success, status = _imergeauto(localorother='other', *args, **kwargs)
439 return success, status, False
445 return success, status, False
440
446
441 @internaltool('tagmerge', mergeonly,
447 @internaltool('tagmerge', mergeonly,
442 _("automatic tag merging of %s failed! "
448 _("automatic tag merging of %s failed! "
443 "(use 'hg resolve --tool :merge' or another merge "
449 "(use 'hg resolve --tool :merge' or another merge "
444 "tool of your choice)\n"))
450 "tool of your choice)\n"))
445 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):
446 """
452 """
447 Uses the internal tag merge algorithm (experimental).
453 Uses the internal tag merge algorithm (experimental).
448 """
454 """
449 success, status = tagmerge.merge(repo, fcd, fco, fca)
455 success, status = tagmerge.merge(repo, fcd, fco, fca)
450 return success, status, False
456 return success, status, False
451
457
452 @internaltool('dump', fullmerge)
458 @internaltool('dump', fullmerge)
453 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):
454 """
460 """
455 Creates three versions of the files to merge, containing the
461 Creates three versions of the files to merge, containing the
456 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
457 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
458 ``a.txt``, these files will accordingly be named ``a.txt.local``,
464 ``a.txt``, these files will accordingly be named ``a.txt.local``,
459 ``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
460 same directory as ``a.txt``.
466 same directory as ``a.txt``.
461
467
462 This implies premerge. Therefore, files aren't dumped, if premerge
468 This implies premerge. Therefore, files aren't dumped, if premerge
463 runs successfully. Use :forcedump to forcibly write files out.
469 runs successfully. Use :forcedump to forcibly write files out.
464 """
470 """
465 a = _workingpath(repo, fcd)
471 a = _workingpath(repo, fcd)
466 fd = fcd.path()
472 fd = fcd.path()
467
473
468 # Run ``flushall()`` to make any missing folders the following wwrite
469 # calls might be depending on.
470 from . import context
474 from . import context
471 if isinstance(fcd, context.overlayworkingfilectx):
475 if isinstance(fcd, context.overlayworkingfilectx):
472 fcd.changectx().flushall()
476 raise error.InMemoryMergeConflictsError('in-memory merge does not '
477 'support the :dump tool.')
473
478
474 util.writefile(a + ".local", fcd.decodeddata())
479 util.writefile(a + ".local", fcd.decodeddata())
475 repo.wwrite(fd + ".other", fco.data(), fco.flags())
480 repo.wwrite(fd + ".other", fco.data(), fco.flags())
476 repo.wwrite(fd + ".base", fca.data(), fca.flags())
481 repo.wwrite(fd + ".base", fca.data(), fca.flags())
477 return False, 1, False
482 return False, 1, False
478
483
479 @internaltool('forcedump', mergeonly)
484 @internaltool('forcedump', mergeonly)
480 def _forcedump(repo, mynode, orig, fcd, fco, fca, toolconf, files,
485 def _forcedump(repo, mynode, orig, fcd, fco, fca, toolconf, files,
481 labels=None):
486 labels=None):
482 """
487 """
483 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.
484 """
489 """
485 return _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files,
490 return _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files,
486 labels=labels)
491 labels=labels)
487
492
488 def _xmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
493 def _xmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
489 tool, toolpath, binary, symlink = toolconf
494 tool, toolpath, binary, symlink = toolconf
490 if fcd.isabsent() or fco.isabsent():
495 if fcd.isabsent() or fco.isabsent():
491 repo.ui.warn(_('warning: %s cannot merge change/delete conflict '
496 repo.ui.warn(_('warning: %s cannot merge change/delete conflict '
492 'for %s\n') % (tool, fcd.path()))
497 'for %s\n') % (tool, fcd.path()))
493 return False, 1, None
498 return False, 1, None
494 unused, unused, unused, back = files
499 unused, unused, unused, back = files
495 a = _workingpath(repo, fcd)
500 a = _workingpath(repo, fcd)
496 b, c = _maketempfiles(repo, fco, fca)
501 b, c = _maketempfiles(repo, fco, fca)
497 try:
502 try:
498 out = ""
503 out = ""
499 env = {'HG_FILE': fcd.path(),
504 env = {'HG_FILE': fcd.path(),
500 'HG_MY_NODE': short(mynode),
505 'HG_MY_NODE': short(mynode),
501 'HG_OTHER_NODE': str(fco.changectx()),
506 'HG_OTHER_NODE': str(fco.changectx()),
502 'HG_BASE_NODE': str(fca.changectx()),
507 'HG_BASE_NODE': str(fca.changectx()),
503 'HG_MY_ISLINK': 'l' in fcd.flags(),
508 'HG_MY_ISLINK': 'l' in fcd.flags(),
504 'HG_OTHER_ISLINK': 'l' in fco.flags(),
509 'HG_OTHER_ISLINK': 'l' in fco.flags(),
505 'HG_BASE_ISLINK': 'l' in fca.flags(),
510 'HG_BASE_ISLINK': 'l' in fca.flags(),
506 }
511 }
507 ui = repo.ui
512 ui = repo.ui
508
513
509 args = _toolstr(ui, tool, "args")
514 args = _toolstr(ui, tool, "args")
510 if "$output" in args:
515 if "$output" in args:
511 # read input from backup, write to original
516 # read input from backup, write to original
512 out = a
517 out = a
513 a = repo.wvfs.join(back.path())
518 a = repo.wvfs.join(back.path())
514 replace = {'local': a, 'base': b, 'other': c, 'output': out}
519 replace = {'local': a, 'base': b, 'other': c, 'output': out}
515 args = util.interpolate(r'\$', replace, args,
520 args = util.interpolate(r'\$', replace, args,
516 lambda s: util.shellquote(util.localpath(s)))
521 lambda s: util.shellquote(util.localpath(s)))
517 cmd = toolpath + ' ' + args
522 cmd = toolpath + ' ' + args
518 if _toolbool(ui, tool, "gui"):
523 if _toolbool(ui, tool, "gui"):
519 repo.ui.status(_('running merge tool %s for file %s\n') %
524 repo.ui.status(_('running merge tool %s for file %s\n') %
520 (tool, fcd.path()))
525 (tool, fcd.path()))
521 repo.ui.debug('launching merge tool: %s\n' % cmd)
526 repo.ui.debug('launching merge tool: %s\n' % cmd)
522 r = ui.system(cmd, cwd=repo.root, environ=env, blockedtag='mergetool')
527 r = ui.system(cmd, cwd=repo.root, environ=env, blockedtag='mergetool')
523 repo.ui.debug('merge tool returned: %d\n' % r)
528 repo.ui.debug('merge tool returned: %d\n' % r)
524 return True, r, False
529 return True, r, False
525 finally:
530 finally:
526 util.unlink(b)
531 util.unlink(b)
527 util.unlink(c)
532 util.unlink(c)
528
533
529 def _formatconflictmarker(repo, ctx, template, label, pad):
534 def _formatconflictmarker(repo, ctx, template, label, pad):
530 """Applies the given template to the ctx, prefixed by the label.
535 """Applies the given template to the ctx, prefixed by the label.
531
536
532 Pad is the minimum width of the label prefix, so that multiple markers
537 Pad is the minimum width of the label prefix, so that multiple markers
533 can have aligned templated parts.
538 can have aligned templated parts.
534 """
539 """
535 if ctx.node() is None:
540 if ctx.node() is None:
536 ctx = ctx.p1()
541 ctx = ctx.p1()
537
542
538 props = templatekw.keywords.copy()
543 props = templatekw.keywords.copy()
539 props['templ'] = template
544 props['templ'] = template
540 props['ctx'] = ctx
545 props['ctx'] = ctx
541 props['repo'] = repo
546 props['repo'] = repo
542 templateresult = template.render(props)
547 templateresult = template.render(props)
543
548
544 label = ('%s:' % label).ljust(pad + 1)
549 label = ('%s:' % label).ljust(pad + 1)
545 mark = '%s %s' % (label, templateresult)
550 mark = '%s %s' % (label, templateresult)
546
551
547 if mark:
552 if mark:
548 mark = mark.splitlines()[0] # split for safety
553 mark = mark.splitlines()[0] # split for safety
549
554
550 # 8 for the prefix of conflict marker lines (e.g. '<<<<<<< ')
555 # 8 for the prefix of conflict marker lines (e.g. '<<<<<<< ')
551 return util.ellipsis(mark, 80 - 8)
556 return util.ellipsis(mark, 80 - 8)
552
557
553 _defaultconflictlabels = ['local', 'other']
558 _defaultconflictlabels = ['local', 'other']
554
559
555 def _formatlabels(repo, fcd, fco, fca, labels):
560 def _formatlabels(repo, fcd, fco, fca, labels):
556 """Formats the given labels using the conflict marker template.
561 """Formats the given labels using the conflict marker template.
557
562
558 Returns a list of formatted labels.
563 Returns a list of formatted labels.
559 """
564 """
560 cd = fcd.changectx()
565 cd = fcd.changectx()
561 co = fco.changectx()
566 co = fco.changectx()
562 ca = fca.changectx()
567 ca = fca.changectx()
563
568
564 ui = repo.ui
569 ui = repo.ui
565 template = ui.config('ui', 'mergemarkertemplate')
570 template = ui.config('ui', 'mergemarkertemplate')
566 template = templater.unquotestring(template)
571 template = templater.unquotestring(template)
567 tmpl = formatter.maketemplater(ui, template)
572 tmpl = formatter.maketemplater(ui, template)
568
573
569 pad = max(len(l) for l in labels)
574 pad = max(len(l) for l in labels)
570
575
571 newlabels = [_formatconflictmarker(repo, cd, tmpl, labels[0], pad),
576 newlabels = [_formatconflictmarker(repo, cd, tmpl, labels[0], pad),
572 _formatconflictmarker(repo, co, tmpl, labels[1], pad)]
577 _formatconflictmarker(repo, co, tmpl, labels[1], pad)]
573 if len(labels) > 2:
578 if len(labels) > 2:
574 newlabels.append(_formatconflictmarker(repo, ca, tmpl, labels[2], pad))
579 newlabels.append(_formatconflictmarker(repo, ca, tmpl, labels[2], pad))
575 return newlabels
580 return newlabels
576
581
577 def partextras(labels):
582 def partextras(labels):
578 """Return a dictionary of extra labels for use in prompts to the user
583 """Return a dictionary of extra labels for use in prompts to the user
579
584
580 Intended use is in strings of the form "(l)ocal%(l)s".
585 Intended use is in strings of the form "(l)ocal%(l)s".
581 """
586 """
582 if labels is None:
587 if labels is None:
583 return {
588 return {
584 "l": "",
589 "l": "",
585 "o": "",
590 "o": "",
586 }
591 }
587
592
588 return {
593 return {
589 "l": " [%s]" % labels[0],
594 "l": " [%s]" % labels[0],
590 "o": " [%s]" % labels[1],
595 "o": " [%s]" % labels[1],
591 }
596 }
592
597
593 def _restorebackup(fcd, back):
598 def _restorebackup(fcd, back):
594 # TODO: Add a workingfilectx.write(otherfilectx) path so we can use
599 # TODO: Add a workingfilectx.write(otherfilectx) path so we can use
595 # util.copy here instead.
600 # util.copy here instead.
596 fcd.write(back.data(), fcd.flags())
601 fcd.write(back.data(), fcd.flags())
597
602
598 def _makebackup(repo, ui, wctx, fcd, premerge):
603 def _makebackup(repo, ui, wctx, fcd, premerge):
599 """Makes and returns a filectx-like object for ``fcd``'s backup file.
604 """Makes and returns a filectx-like object for ``fcd``'s backup file.
600
605
601 In addition to preserving the user's pre-existing modifications to `fcd`
606 In addition to preserving the user's pre-existing modifications to `fcd`
602 (if any), the backup is used to undo certain premerges, confirm whether a
607 (if any), the backup is used to undo certain premerges, confirm whether a
603 merge changed anything, and determine what line endings the new file should
608 merge changed anything, and determine what line endings the new file should
604 have.
609 have.
605 """
610 """
606 if fcd.isabsent():
611 if fcd.isabsent():
607 return None
612 return None
608 # TODO: Break this import cycle somehow. (filectx -> ctx -> fileset ->
613 # TODO: Break this import cycle somehow. (filectx -> ctx -> fileset ->
609 # merge -> filemerge). (I suspect the fileset import is the weakest link)
614 # merge -> filemerge). (I suspect the fileset import is the weakest link)
610 from . import context
615 from . import context
611 a = _workingpath(repo, fcd)
616 a = _workingpath(repo, fcd)
612 back = scmutil.origpath(ui, repo, a)
617 back = scmutil.origpath(ui, repo, a)
613 inworkingdir = (back.startswith(repo.wvfs.base) and not
618 inworkingdir = (back.startswith(repo.wvfs.base) and not
614 back.startswith(repo.vfs.base))
619 back.startswith(repo.vfs.base))
615
620
616 if isinstance(fcd, context.overlayworkingfilectx) and inworkingdir:
621 if isinstance(fcd, context.overlayworkingfilectx) and inworkingdir:
617 # If the backup file is to be in the working directory, and we're
622 # If the backup file is to be in the working directory, and we're
618 # merging in-memory, we must redirect the backup to the memory context
623 # merging in-memory, we must redirect the backup to the memory context
619 # so we don't disturb the working directory.
624 # so we don't disturb the working directory.
620 relpath = back[len(repo.wvfs.base) + 1:]
625 relpath = back[len(repo.wvfs.base) + 1:]
621 wctx[relpath].write(fcd.data(), fcd.flags())
626 wctx[relpath].write(fcd.data(), fcd.flags())
622 return wctx[relpath]
627 return wctx[relpath]
623 else:
628 else:
624 # Otherwise, write to wherever the user specified the backups should go.
629 # Otherwise, write to wherever the user specified the backups should go.
625 #
630 #
626 # A arbitraryfilectx is returned, so we can run the same functions on
631 # A arbitraryfilectx is returned, so we can run the same functions on
627 # the backup context regardless of where it lives.
632 # the backup context regardless of where it lives.
628 if premerge:
633 if premerge:
629 util.copyfile(a, back)
634 util.copyfile(a, back)
630 return context.arbitraryfilectx(back, repo=repo)
635 return context.arbitraryfilectx(back, repo=repo)
631
636
632 def _maketempfiles(repo, fco, fca):
637 def _maketempfiles(repo, fco, fca):
633 """Writes out `fco` and `fca` as temporary files, so an external merge
638 """Writes out `fco` and `fca` as temporary files, so an external merge
634 tool may use them.
639 tool may use them.
635 """
640 """
636 def temp(prefix, ctx):
641 def temp(prefix, ctx):
637 fullbase, ext = os.path.splitext(ctx.path())
642 fullbase, ext = os.path.splitext(ctx.path())
638 pre = "%s~%s." % (os.path.basename(fullbase), prefix)
643 pre = "%s~%s." % (os.path.basename(fullbase), prefix)
639 (fd, name) = tempfile.mkstemp(prefix=pre, suffix=ext)
644 (fd, name) = tempfile.mkstemp(prefix=pre, suffix=ext)
640 data = repo.wwritedata(ctx.path(), ctx.data())
645 data = repo.wwritedata(ctx.path(), ctx.data())
641 f = os.fdopen(fd, pycompat.sysstr("wb"))
646 f = os.fdopen(fd, pycompat.sysstr("wb"))
642 f.write(data)
647 f.write(data)
643 f.close()
648 f.close()
644 return name
649 return name
645
650
646 b = temp("base", fca)
651 b = temp("base", fca)
647 c = temp("other", fco)
652 c = temp("other", fco)
648
653
649 return b, c
654 return b, c
650
655
651 def _filemerge(premerge, repo, wctx, mynode, orig, fcd, fco, fca, labels=None):
656 def _filemerge(premerge, repo, wctx, mynode, orig, fcd, fco, fca, labels=None):
652 """perform a 3-way merge in the working directory
657 """perform a 3-way merge in the working directory
653
658
654 premerge = whether this is a premerge
659 premerge = whether this is a premerge
655 mynode = parent node before merge
660 mynode = parent node before merge
656 orig = original local filename before merge
661 orig = original local filename before merge
657 fco = other file context
662 fco = other file context
658 fca = ancestor file context
663 fca = ancestor file context
659 fcd = local file context for current/destination file
664 fcd = local file context for current/destination file
660
665
661 Returns whether the merge is complete, the return value of the merge, and
666 Returns whether the merge is complete, the return value of the merge, and
662 a boolean indicating whether the file was deleted from disk."""
667 a boolean indicating whether the file was deleted from disk."""
663
668
664 if not fco.cmp(fcd): # files identical?
669 if not fco.cmp(fcd): # files identical?
665 return True, None, False
670 return True, None, False
666
671
667 ui = repo.ui
672 ui = repo.ui
668 fd = fcd.path()
673 fd = fcd.path()
669 binary = fcd.isbinary() or fco.isbinary() or fca.isbinary()
674 binary = fcd.isbinary() or fco.isbinary() or fca.isbinary()
670 symlink = 'l' in fcd.flags() + fco.flags()
675 symlink = 'l' in fcd.flags() + fco.flags()
671 changedelete = fcd.isabsent() or fco.isabsent()
676 changedelete = fcd.isabsent() or fco.isabsent()
672 tool, toolpath = _picktool(repo, ui, fd, binary, symlink, changedelete)
677 tool, toolpath = _picktool(repo, ui, fd, binary, symlink, changedelete)
673 if tool in internals and tool.startswith('internal:'):
678 if tool in internals and tool.startswith('internal:'):
674 # normalize to new-style names (':merge' etc)
679 # normalize to new-style names (':merge' etc)
675 tool = tool[len('internal'):]
680 tool = tool[len('internal'):]
676 ui.debug("picked tool '%s' for %s (binary %s symlink %s changedelete %s)\n"
681 ui.debug("picked tool '%s' for %s (binary %s symlink %s changedelete %s)\n"
677 % (tool, fd, pycompat.bytestr(binary), pycompat.bytestr(symlink),
682 % (tool, fd, pycompat.bytestr(binary), pycompat.bytestr(symlink),
678 pycompat.bytestr(changedelete)))
683 pycompat.bytestr(changedelete)))
679
684
680 if tool in internals:
685 if tool in internals:
681 func = internals[tool]
686 func = internals[tool]
682 mergetype = func.mergetype
687 mergetype = func.mergetype
683 onfailure = func.onfailure
688 onfailure = func.onfailure
684 precheck = func.precheck
689 precheck = func.precheck
685 else:
690 else:
686 func = _xmerge
691 func = _xmerge
687 mergetype = fullmerge
692 mergetype = fullmerge
688 onfailure = _("merging %s failed!\n")
693 onfailure = _("merging %s failed!\n")
689 precheck = None
694 precheck = None
690
695
691 # If using deferred writes, must flush any deferred contents if running
696 if wctx.isinmemory():
692 # an external merge tool since it has arbitrary access to the working
697 raise error.InMemoryMergeConflictsError('in-memory merge does not '
693 # copy.
698 'support external merge '
694 wctx.flushall()
699 'tools')
695
700
696 toolconf = tool, toolpath, binary, symlink
701 toolconf = tool, toolpath, binary, symlink
697
702
698 if mergetype == nomerge:
703 if mergetype == nomerge:
699 r, deleted = func(repo, mynode, orig, fcd, fco, fca, toolconf, labels)
704 r, deleted = func(repo, mynode, orig, fcd, fco, fca, toolconf, labels)
700 return True, r, deleted
705 return True, r, deleted
701
706
702 if premerge:
707 if premerge:
703 if orig != fco.path():
708 if orig != fco.path():
704 ui.status(_("merging %s and %s to %s\n") % (orig, fco.path(), fd))
709 ui.status(_("merging %s and %s to %s\n") % (orig, fco.path(), fd))
705 else:
710 else:
706 ui.status(_("merging %s\n") % fd)
711 ui.status(_("merging %s\n") % fd)
707
712
708 ui.debug("my %s other %s ancestor %s\n" % (fcd, fco, fca))
713 ui.debug("my %s other %s ancestor %s\n" % (fcd, fco, fca))
709
714
710 if precheck and not precheck(repo, mynode, orig, fcd, fco, fca,
715 if precheck and not precheck(repo, mynode, orig, fcd, fco, fca,
711 toolconf):
716 toolconf):
712 if onfailure:
717 if onfailure:
718 if wctx.isinmemory():
719 raise error.InMemoryMergeConflictsError('in-memory merge does '
720 'not support merge '
721 'conflicts')
713 ui.warn(onfailure % fd)
722 ui.warn(onfailure % fd)
714 return True, 1, False
723 return True, 1, False
715
724
716 back = _makebackup(repo, ui, wctx, fcd, premerge)
725 back = _makebackup(repo, ui, wctx, fcd, premerge)
717 files = (None, None, None, back)
726 files = (None, None, None, back)
718 r = 1
727 r = 1
719 try:
728 try:
720 markerstyle = ui.config('ui', 'mergemarkers')
729 markerstyle = ui.config('ui', 'mergemarkers')
721 if not labels:
730 if not labels:
722 labels = _defaultconflictlabels
731 labels = _defaultconflictlabels
723 if markerstyle != 'basic':
732 if markerstyle != 'basic':
724 labels = _formatlabels(repo, fcd, fco, fca, labels)
733 labels = _formatlabels(repo, fcd, fco, fca, labels)
725
734
726 if premerge and mergetype == fullmerge:
735 if premerge and mergetype == fullmerge:
727 r = _premerge(repo, fcd, fco, fca, toolconf, files, labels=labels)
736 r = _premerge(repo, fcd, fco, fca, toolconf, files, labels=labels)
728 # complete if premerge successful (r is 0)
737 # complete if premerge successful (r is 0)
729 return not r, r, False
738 return not r, r, False
730
739
731 needcheck, r, deleted = func(repo, mynode, orig, fcd, fco, fca,
740 needcheck, r, deleted = func(repo, mynode, orig, fcd, fco, fca,
732 toolconf, files, labels=labels)
741 toolconf, files, labels=labels)
733
742
734 if needcheck:
743 if needcheck:
735 r = _check(repo, r, ui, tool, fcd, files)
744 r = _check(repo, r, ui, tool, fcd, files)
736
745
737 if r:
746 if r:
738 if onfailure:
747 if onfailure:
748 if wctx.isinmemory():
749 raise error.InMemoryMergeConflictsError('in-memory merge '
750 'does not support '
751 'merge conflicts')
739 ui.warn(onfailure % fd)
752 ui.warn(onfailure % fd)
740 _onfilemergefailure(ui)
753 _onfilemergefailure(ui)
741
754
742 return True, r, deleted
755 return True, r, deleted
743 finally:
756 finally:
744 if not r and back is not None:
757 if not r and back is not None:
745 back.remove()
758 back.remove()
746
759
747 def _haltmerge():
760 def _haltmerge():
748 msg = _('merge halted after failed merge (see hg resolve)')
761 msg = _('merge halted after failed merge (see hg resolve)')
749 raise error.InterventionRequired(msg)
762 raise error.InterventionRequired(msg)
750
763
751 def _onfilemergefailure(ui):
764 def _onfilemergefailure(ui):
752 action = ui.config('merge', 'on-failure')
765 action = ui.config('merge', 'on-failure')
753 if action == 'prompt':
766 if action == 'prompt':
754 msg = _('continue merge operation (yn)?' '$$ &Yes $$ &No')
767 msg = _('continue merge operation (yn)?' '$$ &Yes $$ &No')
755 if ui.promptchoice(msg, 0) == 1:
768 if ui.promptchoice(msg, 0) == 1:
756 _haltmerge()
769 _haltmerge()
757 if action == 'halt':
770 if action == 'halt':
758 _haltmerge()
771 _haltmerge()
759 # default action is 'continue', in which case we neither prompt nor halt
772 # default action is 'continue', in which case we neither prompt nor halt
760
773
761 def _check(repo, r, ui, tool, fcd, files):
774 def _check(repo, r, ui, tool, fcd, files):
762 fd = fcd.path()
775 fd = fcd.path()
763 unused, unused, unused, back = files
776 unused, unused, unused, back = files
764
777
765 if not r and (_toolbool(ui, tool, "checkconflicts") or
778 if not r and (_toolbool(ui, tool, "checkconflicts") or
766 'conflicts' in _toollist(ui, tool, "check")):
779 'conflicts' in _toollist(ui, tool, "check")):
767 if re.search("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcd.data(),
780 if re.search("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcd.data(),
768 re.MULTILINE):
781 re.MULTILINE):
769 r = 1
782 r = 1
770
783
771 checked = False
784 checked = False
772 if 'prompt' in _toollist(ui, tool, "check"):
785 if 'prompt' in _toollist(ui, tool, "check"):
773 checked = True
786 checked = True
774 if ui.promptchoice(_("was merge of '%s' successful (yn)?"
787 if ui.promptchoice(_("was merge of '%s' successful (yn)?"
775 "$$ &Yes $$ &No") % fd, 1):
788 "$$ &Yes $$ &No") % fd, 1):
776 r = 1
789 r = 1
777
790
778 if not r and not checked and (_toolbool(ui, tool, "checkchanged") or
791 if not r and not checked and (_toolbool(ui, tool, "checkchanged") or
779 'changed' in
792 'changed' in
780 _toollist(ui, tool, "check")):
793 _toollist(ui, tool, "check")):
781 if back is not None and not fcd.cmp(back):
794 if back is not None and not fcd.cmp(back):
782 if ui.promptchoice(_(" output file %s appears unchanged\n"
795 if ui.promptchoice(_(" output file %s appears unchanged\n"
783 "was merge successful (yn)?"
796 "was merge successful (yn)?"
784 "$$ &Yes $$ &No") % fd, 1):
797 "$$ &Yes $$ &No") % fd, 1):
785 r = 1
798 r = 1
786
799
787 if back is not None and _toolbool(ui, tool, "fixeol"):
800 if back is not None and _toolbool(ui, tool, "fixeol"):
788 _matcheol(_workingpath(repo, fcd), back)
801 _matcheol(_workingpath(repo, fcd), back)
789
802
790 return r
803 return r
791
804
792 def _workingpath(repo, ctx):
805 def _workingpath(repo, ctx):
793 return repo.wjoin(ctx.path())
806 return repo.wjoin(ctx.path())
794
807
795 def premerge(repo, wctx, mynode, orig, fcd, fco, fca, labels=None):
808 def premerge(repo, wctx, mynode, orig, fcd, fco, fca, labels=None):
796 return _filemerge(True, repo, wctx, mynode, orig, fcd, fco, fca,
809 return _filemerge(True, repo, wctx, mynode, orig, fcd, fco, fca,
797 labels=labels)
810 labels=labels)
798
811
799 def filemerge(repo, wctx, mynode, orig, fcd, fco, fca, labels=None):
812 def filemerge(repo, wctx, mynode, orig, fcd, fco, fca, labels=None):
800 return _filemerge(False, repo, wctx, mynode, orig, fcd, fco, fca,
813 return _filemerge(False, repo, wctx, mynode, orig, fcd, fco, fca,
801 labels=labels)
814 labels=labels)
802
815
803 def loadinternalmerge(ui, extname, registrarobj):
816 def loadinternalmerge(ui, extname, registrarobj):
804 """Load internal merge tool from specified registrarobj
817 """Load internal merge tool from specified registrarobj
805 """
818 """
806 for name, func in registrarobj._table.iteritems():
819 for name, func in registrarobj._table.iteritems():
807 fullname = ':' + name
820 fullname = ':' + name
808 internals[fullname] = func
821 internals[fullname] = func
809 internals['internal:' + name] = func
822 internals['internal:' + name] = func
810 internalsdoc[fullname] = func
823 internalsdoc[fullname] = func
811
824
812 # load built-in merge tools explicitly to setup internalsdoc
825 # load built-in merge tools explicitly to setup internalsdoc
813 loadinternalmerge(None, None, internaltool)
826 loadinternalmerge(None, None, internaltool)
814
827
815 # tell hggettext to extract docstrings from these functions:
828 # tell hggettext to extract docstrings from these functions:
816 i18nfunctions = internals.values()
829 i18nfunctions = internals.values()
General Comments 0
You need to be logged in to leave comments. Login now