##// END OF EJS Templates
templater: drop unneeded resources from conflict-marker data...
Yuya Nishihara -
r35498:c240657f default
parent child Browse files
Show More
@@ -1,840 +1,838 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):
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,
494 # In-memory merge simply raises an exception on all external merge tools,
495 # for now.
495 # for now.
496 #
496 #
497 # It would be possible to run most tools with temporary files, but this
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
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/
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
500 # directory and tell the user how to get it is my best idea, but it's
501 # clunky.)
501 # clunky.)
502 raise error.InMemoryMergeConflictsError('in-memory merge does not support '
502 raise error.InMemoryMergeConflictsError('in-memory merge does not support '
503 'external merge tools')
503 'external merge tools')
504
504
505 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):
506 tool, toolpath, binary, symlink = toolconf
506 tool, toolpath, binary, symlink = toolconf
507 if fcd.isabsent() or fco.isabsent():
507 if fcd.isabsent() or fco.isabsent():
508 repo.ui.warn(_('warning: %s cannot merge change/delete conflict '
508 repo.ui.warn(_('warning: %s cannot merge change/delete conflict '
509 'for %s\n') % (tool, fcd.path()))
509 'for %s\n') % (tool, fcd.path()))
510 return False, 1, None
510 return False, 1, None
511 unused, unused, unused, back = files
511 unused, unused, unused, back = files
512 a = _workingpath(repo, fcd)
512 a = _workingpath(repo, fcd)
513 b, c = _maketempfiles(repo, fco, fca)
513 b, c = _maketempfiles(repo, fco, fca)
514 try:
514 try:
515 out = ""
515 out = ""
516 env = {'HG_FILE': fcd.path(),
516 env = {'HG_FILE': fcd.path(),
517 'HG_MY_NODE': short(mynode),
517 'HG_MY_NODE': short(mynode),
518 'HG_OTHER_NODE': str(fco.changectx()),
518 'HG_OTHER_NODE': str(fco.changectx()),
519 'HG_BASE_NODE': str(fca.changectx()),
519 'HG_BASE_NODE': str(fca.changectx()),
520 'HG_MY_ISLINK': 'l' in fcd.flags(),
520 'HG_MY_ISLINK': 'l' in fcd.flags(),
521 'HG_OTHER_ISLINK': 'l' in fco.flags(),
521 'HG_OTHER_ISLINK': 'l' in fco.flags(),
522 'HG_BASE_ISLINK': 'l' in fca.flags(),
522 'HG_BASE_ISLINK': 'l' in fca.flags(),
523 }
523 }
524 ui = repo.ui
524 ui = repo.ui
525
525
526 args = _toolstr(ui, tool, "args")
526 args = _toolstr(ui, tool, "args")
527 if "$output" in args:
527 if "$output" in args:
528 # read input from backup, write to original
528 # read input from backup, write to original
529 out = a
529 out = a
530 a = repo.wvfs.join(back.path())
530 a = repo.wvfs.join(back.path())
531 replace = {'local': a, 'base': b, 'other': c, 'output': out}
531 replace = {'local': a, 'base': b, 'other': c, 'output': out}
532 args = util.interpolate(r'\$', replace, args,
532 args = util.interpolate(r'\$', replace, args,
533 lambda s: util.shellquote(util.localpath(s)))
533 lambda s: util.shellquote(util.localpath(s)))
534 cmd = toolpath + ' ' + args
534 cmd = toolpath + ' ' + args
535 if _toolbool(ui, tool, "gui"):
535 if _toolbool(ui, tool, "gui"):
536 repo.ui.status(_('running merge tool %s for file %s\n') %
536 repo.ui.status(_('running merge tool %s for file %s\n') %
537 (tool, fcd.path()))
537 (tool, fcd.path()))
538 repo.ui.debug('launching merge tool: %s\n' % cmd)
538 repo.ui.debug('launching merge tool: %s\n' % cmd)
539 r = ui.system(cmd, cwd=repo.root, environ=env, blockedtag='mergetool')
539 r = ui.system(cmd, cwd=repo.root, environ=env, blockedtag='mergetool')
540 repo.ui.debug('merge tool returned: %d\n' % r)
540 repo.ui.debug('merge tool returned: %d\n' % r)
541 return True, r, False
541 return True, r, False
542 finally:
542 finally:
543 util.unlink(b)
543 util.unlink(b)
544 util.unlink(c)
544 util.unlink(c)
545
545
546 def _formatconflictmarker(repo, ctx, template, label, pad):
546 def _formatconflictmarker(ctx, template, label, pad):
547 """Applies the given template to the ctx, prefixed by the label.
547 """Applies the given template to the ctx, prefixed by the label.
548
548
549 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
550 can have aligned templated parts.
550 can have aligned templated parts.
551 """
551 """
552 if ctx.node() is None:
552 if ctx.node() is None:
553 ctx = ctx.p1()
553 ctx = ctx.p1()
554
554
555 props = templatekw.keywords.copy()
555 props = templatekw.keywords.copy()
556 props['templ'] = template
557 props['ctx'] = ctx
556 props['ctx'] = ctx
558 props['repo'] = repo
559 templateresult = template.render(props)
557 templateresult = template.render(props)
560
558
561 label = ('%s:' % label).ljust(pad + 1)
559 label = ('%s:' % label).ljust(pad + 1)
562 mark = '%s %s' % (label, templateresult)
560 mark = '%s %s' % (label, templateresult)
563
561
564 if mark:
562 if mark:
565 mark = mark.splitlines()[0] # split for safety
563 mark = mark.splitlines()[0] # split for safety
566
564
567 # 8 for the prefix of conflict marker lines (e.g. '<<<<<<< ')
565 # 8 for the prefix of conflict marker lines (e.g. '<<<<<<< ')
568 return util.ellipsis(mark, 80 - 8)
566 return util.ellipsis(mark, 80 - 8)
569
567
570 _defaultconflictlabels = ['local', 'other']
568 _defaultconflictlabels = ['local', 'other']
571
569
572 def _formatlabels(repo, fcd, fco, fca, labels):
570 def _formatlabels(repo, fcd, fco, fca, labels):
573 """Formats the given labels using the conflict marker template.
571 """Formats the given labels using the conflict marker template.
574
572
575 Returns a list of formatted labels.
573 Returns a list of formatted labels.
576 """
574 """
577 cd = fcd.changectx()
575 cd = fcd.changectx()
578 co = fco.changectx()
576 co = fco.changectx()
579 ca = fca.changectx()
577 ca = fca.changectx()
580
578
581 ui = repo.ui
579 ui = repo.ui
582 template = ui.config('ui', 'mergemarkertemplate')
580 template = ui.config('ui', 'mergemarkertemplate')
583 template = templater.unquotestring(template)
581 template = templater.unquotestring(template)
584 tres = formatter.templateresources(ui, repo)
582 tres = formatter.templateresources(ui, repo)
585 tmpl = formatter.maketemplater(ui, template, resources=tres)
583 tmpl = formatter.maketemplater(ui, template, resources=tres)
586
584
587 pad = max(len(l) for l in labels)
585 pad = max(len(l) for l in labels)
588
586
589 newlabels = [_formatconflictmarker(repo, cd, tmpl, labels[0], pad),
587 newlabels = [_formatconflictmarker(cd, tmpl, labels[0], pad),
590 _formatconflictmarker(repo, co, tmpl, labels[1], pad)]
588 _formatconflictmarker(co, tmpl, labels[1], pad)]
591 if len(labels) > 2:
589 if len(labels) > 2:
592 newlabels.append(_formatconflictmarker(repo, ca, tmpl, labels[2], pad))
590 newlabels.append(_formatconflictmarker(ca, tmpl, labels[2], pad))
593 return newlabels
591 return newlabels
594
592
595 def partextras(labels):
593 def partextras(labels):
596 """Return a dictionary of extra labels for use in prompts to the user
594 """Return a dictionary of extra labels for use in prompts to the user
597
595
598 Intended use is in strings of the form "(l)ocal%(l)s".
596 Intended use is in strings of the form "(l)ocal%(l)s".
599 """
597 """
600 if labels is None:
598 if labels is None:
601 return {
599 return {
602 "l": "",
600 "l": "",
603 "o": "",
601 "o": "",
604 }
602 }
605
603
606 return {
604 return {
607 "l": " [%s]" % labels[0],
605 "l": " [%s]" % labels[0],
608 "o": " [%s]" % labels[1],
606 "o": " [%s]" % labels[1],
609 }
607 }
610
608
611 def _restorebackup(fcd, back):
609 def _restorebackup(fcd, back):
612 # TODO: Add a workingfilectx.write(otherfilectx) path so we can use
610 # TODO: Add a workingfilectx.write(otherfilectx) path so we can use
613 # util.copy here instead.
611 # util.copy here instead.
614 fcd.write(back.data(), fcd.flags())
612 fcd.write(back.data(), fcd.flags())
615
613
616 def _makebackup(repo, ui, wctx, fcd, premerge):
614 def _makebackup(repo, ui, wctx, fcd, premerge):
617 """Makes and returns a filectx-like object for ``fcd``'s backup file.
615 """Makes and returns a filectx-like object for ``fcd``'s backup file.
618
616
619 In addition to preserving the user's pre-existing modifications to `fcd`
617 In addition to preserving the user's pre-existing modifications to `fcd`
620 (if any), the backup is used to undo certain premerges, confirm whether a
618 (if any), the backup is used to undo certain premerges, confirm whether a
621 merge changed anything, and determine what line endings the new file should
619 merge changed anything, and determine what line endings the new file should
622 have.
620 have.
623 """
621 """
624 if fcd.isabsent():
622 if fcd.isabsent():
625 return None
623 return None
626 # TODO: Break this import cycle somehow. (filectx -> ctx -> fileset ->
624 # TODO: Break this import cycle somehow. (filectx -> ctx -> fileset ->
627 # merge -> filemerge). (I suspect the fileset import is the weakest link)
625 # merge -> filemerge). (I suspect the fileset import is the weakest link)
628 from . import context
626 from . import context
629 a = _workingpath(repo, fcd)
627 a = _workingpath(repo, fcd)
630 back = scmutil.origpath(ui, repo, a)
628 back = scmutil.origpath(ui, repo, a)
631 inworkingdir = (back.startswith(repo.wvfs.base) and not
629 inworkingdir = (back.startswith(repo.wvfs.base) and not
632 back.startswith(repo.vfs.base))
630 back.startswith(repo.vfs.base))
633
631
634 if isinstance(fcd, context.overlayworkingfilectx) and inworkingdir:
632 if isinstance(fcd, context.overlayworkingfilectx) and inworkingdir:
635 # If the backup file is to be in the working directory, and we're
633 # If the backup file is to be in the working directory, and we're
636 # merging in-memory, we must redirect the backup to the memory context
634 # merging in-memory, we must redirect the backup to the memory context
637 # so we don't disturb the working directory.
635 # so we don't disturb the working directory.
638 relpath = back[len(repo.wvfs.base) + 1:]
636 relpath = back[len(repo.wvfs.base) + 1:]
639 wctx[relpath].write(fcd.data(), fcd.flags())
637 wctx[relpath].write(fcd.data(), fcd.flags())
640 return wctx[relpath]
638 return wctx[relpath]
641 else:
639 else:
642 # Otherwise, write to wherever the user specified the backups should go.
640 # Otherwise, write to wherever the user specified the backups should go.
643 #
641 #
644 # A arbitraryfilectx is returned, so we can run the same functions on
642 # A arbitraryfilectx is returned, so we can run the same functions on
645 # the backup context regardless of where it lives.
643 # the backup context regardless of where it lives.
646 if premerge:
644 if premerge:
647 util.copyfile(a, back)
645 util.copyfile(a, back)
648 return context.arbitraryfilectx(back, repo=repo)
646 return context.arbitraryfilectx(back, repo=repo)
649
647
650 def _maketempfiles(repo, fco, fca):
648 def _maketempfiles(repo, fco, fca):
651 """Writes out `fco` and `fca` as temporary files, so an external merge
649 """Writes out `fco` and `fca` as temporary files, so an external merge
652 tool may use them.
650 tool may use them.
653 """
651 """
654 def temp(prefix, ctx):
652 def temp(prefix, ctx):
655 fullbase, ext = os.path.splitext(ctx.path())
653 fullbase, ext = os.path.splitext(ctx.path())
656 pre = "%s~%s." % (os.path.basename(fullbase), prefix)
654 pre = "%s~%s." % (os.path.basename(fullbase), prefix)
657 (fd, name) = tempfile.mkstemp(prefix=pre, suffix=ext)
655 (fd, name) = tempfile.mkstemp(prefix=pre, suffix=ext)
658 data = repo.wwritedata(ctx.path(), ctx.data())
656 data = repo.wwritedata(ctx.path(), ctx.data())
659 f = os.fdopen(fd, pycompat.sysstr("wb"))
657 f = os.fdopen(fd, pycompat.sysstr("wb"))
660 f.write(data)
658 f.write(data)
661 f.close()
659 f.close()
662 return name
660 return name
663
661
664 b = temp("base", fca)
662 b = temp("base", fca)
665 c = temp("other", fco)
663 c = temp("other", fco)
666
664
667 return b, c
665 return b, c
668
666
669 def _filemerge(premerge, repo, wctx, mynode, orig, fcd, fco, fca, labels=None):
667 def _filemerge(premerge, repo, wctx, mynode, orig, fcd, fco, fca, labels=None):
670 """perform a 3-way merge in the working directory
668 """perform a 3-way merge in the working directory
671
669
672 premerge = whether this is a premerge
670 premerge = whether this is a premerge
673 mynode = parent node before merge
671 mynode = parent node before merge
674 orig = original local filename before merge
672 orig = original local filename before merge
675 fco = other file context
673 fco = other file context
676 fca = ancestor file context
674 fca = ancestor file context
677 fcd = local file context for current/destination file
675 fcd = local file context for current/destination file
678
676
679 Returns whether the merge is complete, the return value of the merge, and
677 Returns whether the merge is complete, the return value of the merge, and
680 a boolean indicating whether the file was deleted from disk."""
678 a boolean indicating whether the file was deleted from disk."""
681
679
682 if not fco.cmp(fcd): # files identical?
680 if not fco.cmp(fcd): # files identical?
683 return True, None, False
681 return True, None, False
684
682
685 ui = repo.ui
683 ui = repo.ui
686 fd = fcd.path()
684 fd = fcd.path()
687 binary = fcd.isbinary() or fco.isbinary() or fca.isbinary()
685 binary = fcd.isbinary() or fco.isbinary() or fca.isbinary()
688 symlink = 'l' in fcd.flags() + fco.flags()
686 symlink = 'l' in fcd.flags() + fco.flags()
689 changedelete = fcd.isabsent() or fco.isabsent()
687 changedelete = fcd.isabsent() or fco.isabsent()
690 tool, toolpath = _picktool(repo, ui, fd, binary, symlink, changedelete)
688 tool, toolpath = _picktool(repo, ui, fd, binary, symlink, changedelete)
691 if tool in internals and tool.startswith('internal:'):
689 if tool in internals and tool.startswith('internal:'):
692 # normalize to new-style names (':merge' etc)
690 # normalize to new-style names (':merge' etc)
693 tool = tool[len('internal'):]
691 tool = tool[len('internal'):]
694 ui.debug("picked tool '%s' for %s (binary %s symlink %s changedelete %s)\n"
692 ui.debug("picked tool '%s' for %s (binary %s symlink %s changedelete %s)\n"
695 % (tool, fd, pycompat.bytestr(binary), pycompat.bytestr(symlink),
693 % (tool, fd, pycompat.bytestr(binary), pycompat.bytestr(symlink),
696 pycompat.bytestr(changedelete)))
694 pycompat.bytestr(changedelete)))
697
695
698 if tool in internals:
696 if tool in internals:
699 func = internals[tool]
697 func = internals[tool]
700 mergetype = func.mergetype
698 mergetype = func.mergetype
701 onfailure = func.onfailure
699 onfailure = func.onfailure
702 precheck = func.precheck
700 precheck = func.precheck
703 else:
701 else:
704 if wctx.isinmemory():
702 if wctx.isinmemory():
705 func = _xmergeimm
703 func = _xmergeimm
706 else:
704 else:
707 func = _xmerge
705 func = _xmerge
708 mergetype = fullmerge
706 mergetype = fullmerge
709 onfailure = _("merging %s failed!\n")
707 onfailure = _("merging %s failed!\n")
710 precheck = None
708 precheck = None
711
709
712 toolconf = tool, toolpath, binary, symlink
710 toolconf = tool, toolpath, binary, symlink
713
711
714 if mergetype == nomerge:
712 if mergetype == nomerge:
715 r, deleted = func(repo, mynode, orig, fcd, fco, fca, toolconf, labels)
713 r, deleted = func(repo, mynode, orig, fcd, fco, fca, toolconf, labels)
716 return True, r, deleted
714 return True, r, deleted
717
715
718 if premerge:
716 if premerge:
719 if orig != fco.path():
717 if orig != fco.path():
720 ui.status(_("merging %s and %s to %s\n") % (orig, fco.path(), fd))
718 ui.status(_("merging %s and %s to %s\n") % (orig, fco.path(), fd))
721 else:
719 else:
722 ui.status(_("merging %s\n") % fd)
720 ui.status(_("merging %s\n") % fd)
723
721
724 ui.debug("my %s other %s ancestor %s\n" % (fcd, fco, fca))
722 ui.debug("my %s other %s ancestor %s\n" % (fcd, fco, fca))
725
723
726 if precheck and not precheck(repo, mynode, orig, fcd, fco, fca,
724 if precheck and not precheck(repo, mynode, orig, fcd, fco, fca,
727 toolconf):
725 toolconf):
728 if onfailure:
726 if onfailure:
729 if wctx.isinmemory():
727 if wctx.isinmemory():
730 raise error.InMemoryMergeConflictsError('in-memory merge does '
728 raise error.InMemoryMergeConflictsError('in-memory merge does '
731 'not support merge '
729 'not support merge '
732 'conflicts')
730 'conflicts')
733 ui.warn(onfailure % fd)
731 ui.warn(onfailure % fd)
734 return True, 1, False
732 return True, 1, False
735
733
736 back = _makebackup(repo, ui, wctx, fcd, premerge)
734 back = _makebackup(repo, ui, wctx, fcd, premerge)
737 files = (None, None, None, back)
735 files = (None, None, None, back)
738 r = 1
736 r = 1
739 try:
737 try:
740 markerstyle = ui.config('ui', 'mergemarkers')
738 markerstyle = ui.config('ui', 'mergemarkers')
741 if not labels:
739 if not labels:
742 labels = _defaultconflictlabels
740 labels = _defaultconflictlabels
743 if markerstyle != 'basic':
741 if markerstyle != 'basic':
744 labels = _formatlabels(repo, fcd, fco, fca, labels)
742 labels = _formatlabels(repo, fcd, fco, fca, labels)
745
743
746 if premerge and mergetype == fullmerge:
744 if premerge and mergetype == fullmerge:
747 r = _premerge(repo, fcd, fco, fca, toolconf, files, labels=labels)
745 r = _premerge(repo, fcd, fco, fca, toolconf, files, labels=labels)
748 # complete if premerge successful (r is 0)
746 # complete if premerge successful (r is 0)
749 return not r, r, False
747 return not r, r, False
750
748
751 needcheck, r, deleted = func(repo, mynode, orig, fcd, fco, fca,
749 needcheck, r, deleted = func(repo, mynode, orig, fcd, fco, fca,
752 toolconf, files, labels=labels)
750 toolconf, files, labels=labels)
753
751
754 if needcheck:
752 if needcheck:
755 r = _check(repo, r, ui, tool, fcd, files)
753 r = _check(repo, r, ui, tool, fcd, files)
756
754
757 if r:
755 if r:
758 if onfailure:
756 if onfailure:
759 if wctx.isinmemory():
757 if wctx.isinmemory():
760 raise error.InMemoryMergeConflictsError('in-memory merge '
758 raise error.InMemoryMergeConflictsError('in-memory merge '
761 'does not support '
759 'does not support '
762 'merge conflicts')
760 'merge conflicts')
763 ui.warn(onfailure % fd)
761 ui.warn(onfailure % fd)
764 _onfilemergefailure(ui)
762 _onfilemergefailure(ui)
765
763
766 return True, r, deleted
764 return True, r, deleted
767 finally:
765 finally:
768 if not r and back is not None:
766 if not r and back is not None:
769 back.remove()
767 back.remove()
770
768
771 def _haltmerge():
769 def _haltmerge():
772 msg = _('merge halted after failed merge (see hg resolve)')
770 msg = _('merge halted after failed merge (see hg resolve)')
773 raise error.InterventionRequired(msg)
771 raise error.InterventionRequired(msg)
774
772
775 def _onfilemergefailure(ui):
773 def _onfilemergefailure(ui):
776 action = ui.config('merge', 'on-failure')
774 action = ui.config('merge', 'on-failure')
777 if action == 'prompt':
775 if action == 'prompt':
778 msg = _('continue merge operation (yn)?' '$$ &Yes $$ &No')
776 msg = _('continue merge operation (yn)?' '$$ &Yes $$ &No')
779 if ui.promptchoice(msg, 0) == 1:
777 if ui.promptchoice(msg, 0) == 1:
780 _haltmerge()
778 _haltmerge()
781 if action == 'halt':
779 if action == 'halt':
782 _haltmerge()
780 _haltmerge()
783 # default action is 'continue', in which case we neither prompt nor halt
781 # default action is 'continue', in which case we neither prompt nor halt
784
782
785 def _check(repo, r, ui, tool, fcd, files):
783 def _check(repo, r, ui, tool, fcd, files):
786 fd = fcd.path()
784 fd = fcd.path()
787 unused, unused, unused, back = files
785 unused, unused, unused, back = files
788
786
789 if not r and (_toolbool(ui, tool, "checkconflicts") or
787 if not r and (_toolbool(ui, tool, "checkconflicts") or
790 'conflicts' in _toollist(ui, tool, "check")):
788 'conflicts' in _toollist(ui, tool, "check")):
791 if re.search("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcd.data(),
789 if re.search("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcd.data(),
792 re.MULTILINE):
790 re.MULTILINE):
793 r = 1
791 r = 1
794
792
795 checked = False
793 checked = False
796 if 'prompt' in _toollist(ui, tool, "check"):
794 if 'prompt' in _toollist(ui, tool, "check"):
797 checked = True
795 checked = True
798 if ui.promptchoice(_("was merge of '%s' successful (yn)?"
796 if ui.promptchoice(_("was merge of '%s' successful (yn)?"
799 "$$ &Yes $$ &No") % fd, 1):
797 "$$ &Yes $$ &No") % fd, 1):
800 r = 1
798 r = 1
801
799
802 if not r and not checked and (_toolbool(ui, tool, "checkchanged") or
800 if not r and not checked and (_toolbool(ui, tool, "checkchanged") or
803 'changed' in
801 'changed' in
804 _toollist(ui, tool, "check")):
802 _toollist(ui, tool, "check")):
805 if back is not None and not fcd.cmp(back):
803 if back is not None and not fcd.cmp(back):
806 if ui.promptchoice(_(" output file %s appears unchanged\n"
804 if ui.promptchoice(_(" output file %s appears unchanged\n"
807 "was merge successful (yn)?"
805 "was merge successful (yn)?"
808 "$$ &Yes $$ &No") % fd, 1):
806 "$$ &Yes $$ &No") % fd, 1):
809 r = 1
807 r = 1
810
808
811 if back is not None and _toolbool(ui, tool, "fixeol"):
809 if back is not None and _toolbool(ui, tool, "fixeol"):
812 _matcheol(_workingpath(repo, fcd), back)
810 _matcheol(_workingpath(repo, fcd), back)
813
811
814 return r
812 return r
815
813
816 def _workingpath(repo, ctx):
814 def _workingpath(repo, ctx):
817 return repo.wjoin(ctx.path())
815 return repo.wjoin(ctx.path())
818
816
819 def premerge(repo, wctx, mynode, orig, fcd, fco, fca, labels=None):
817 def premerge(repo, wctx, mynode, orig, fcd, fco, fca, labels=None):
820 return _filemerge(True, repo, wctx, mynode, orig, fcd, fco, fca,
818 return _filemerge(True, repo, wctx, mynode, orig, fcd, fco, fca,
821 labels=labels)
819 labels=labels)
822
820
823 def filemerge(repo, wctx, mynode, orig, fcd, fco, fca, labels=None):
821 def filemerge(repo, wctx, mynode, orig, fcd, fco, fca, labels=None):
824 return _filemerge(False, repo, wctx, mynode, orig, fcd, fco, fca,
822 return _filemerge(False, repo, wctx, mynode, orig, fcd, fco, fca,
825 labels=labels)
823 labels=labels)
826
824
827 def loadinternalmerge(ui, extname, registrarobj):
825 def loadinternalmerge(ui, extname, registrarobj):
828 """Load internal merge tool from specified registrarobj
826 """Load internal merge tool from specified registrarobj
829 """
827 """
830 for name, func in registrarobj._table.iteritems():
828 for name, func in registrarobj._table.iteritems():
831 fullname = ':' + name
829 fullname = ':' + name
832 internals[fullname] = func
830 internals[fullname] = func
833 internals['internal:' + name] = func
831 internals['internal:' + name] = func
834 internalsdoc[fullname] = func
832 internalsdoc[fullname] = func
835
833
836 # load built-in merge tools explicitly to setup internalsdoc
834 # load built-in merge tools explicitly to setup internalsdoc
837 loadinternalmerge(None, None, internaltool)
835 loadinternalmerge(None, None, internaltool)
838
836
839 # tell hggettext to extract docstrings from these functions:
837 # tell hggettext to extract docstrings from these functions:
840 i18nfunctions = internals.values()
838 i18nfunctions = internals.values()
General Comments 0
You need to be logged in to leave comments. Login now