##// END OF EJS Templates
filemerge: convert a couple of wvfs calls in internal mergetools to contexts...
Phil Cohen -
r33152:85182521 default
parent child Browse files
Show More
@@ -1,744 +1,754 b''
1 # filemerge.py - file-level merge handling for Mercurial
1 # filemerge.py - file-level merge handling for Mercurial
2 #
2 #
3 # Copyright 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import filecmp
10 import filecmp
11 import os
11 import os
12 import re
12 import re
13 import tempfile
13 import tempfile
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import nullid, short
16 from .node import nullid, short
17
17
18 from . import (
18 from . import (
19 encoding,
19 encoding,
20 error,
20 error,
21 formatter,
21 formatter,
22 match,
22 match,
23 pycompat,
23 pycompat,
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, default=""):
32 def _toolstr(ui, tool, part, default=""):
33 return ui.config("merge-tools", tool + "." + part, default)
33 return ui.config("merge-tools", tool + "." + part, default)
34
34
35 def _toolbool(ui, tool, part, default=False):
35 def _toolbool(ui, tool, part, default=False):
36 return ui.configbool("merge-tools", tool + "." + part, default)
36 return ui.configbool("merge-tools", tool + "." + part, default)
37
37
38 def _toollist(ui, tool, part, default=None):
38 def _toollist(ui, tool, part, default=None):
39 if default is None:
39 if default is None:
40 default = []
40 default = []
41 return ui.configlist("merge-tools", tool + "." + part, default)
41 return ui.configlist("merge-tools", tool + "." + part, default)
42
42
43 internals = {}
43 internals = {}
44 # Merge tools to document.
44 # Merge tools to document.
45 internalsdoc = {}
45 internalsdoc = {}
46
46
47 # internal tool merge types
47 # internal tool merge types
48 nomerge = None
48 nomerge = None
49 mergeonly = 'mergeonly' # just the full merge, no premerge
49 mergeonly = 'mergeonly' # just the full merge, no premerge
50 fullmerge = 'fullmerge' # both premerge and merge
50 fullmerge = '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 internaltool(name, mergetype, onfailure=None, precheck=None):
107 def internaltool(name, mergetype, onfailure=None, precheck=None):
108 '''return a decorator for populating internal merge tool table'''
108 '''return a decorator for populating internal merge tool table'''
109 def decorator(func):
109 def decorator(func):
110 fullname = ':' + name
110 fullname = ':' + name
111 func.__doc__ = (pycompat.sysstr("``%s``\n" % fullname)
111 func.__doc__ = (pycompat.sysstr("``%s``\n" % fullname)
112 + func.__doc__.strip())
112 + func.__doc__.strip())
113 internals[fullname] = func
113 internals[fullname] = func
114 internals['internal:' + name] = func
114 internals['internal:' + name] = func
115 internalsdoc[fullname] = func
115 internalsdoc[fullname] = func
116 func.mergetype = mergetype
116 func.mergetype = mergetype
117 func.onfailure = onfailure
117 func.onfailure = onfailure
118 func.precheck = precheck
118 func.precheck = precheck
119 return func
119 return func
120 return decorator
120 return decorator
121
121
122 def _findtool(ui, tool):
122 def _findtool(ui, tool):
123 if tool in internals:
123 if tool in internals:
124 return tool
124 return tool
125 return findexternaltool(ui, tool)
125 return findexternaltool(ui, tool)
126
126
127 def findexternaltool(ui, tool):
127 def findexternaltool(ui, tool):
128 for kn in ("regkey", "regkeyalt"):
128 for kn in ("regkey", "regkeyalt"):
129 k = _toolstr(ui, tool, kn)
129 k = _toolstr(ui, tool, kn)
130 if not k:
130 if not k:
131 continue
131 continue
132 p = util.lookupreg(k, _toolstr(ui, tool, "regname"))
132 p = util.lookupreg(k, _toolstr(ui, tool, "regname"))
133 if p:
133 if p:
134 p = util.findexe(p + _toolstr(ui, tool, "regappend"))
134 p = util.findexe(p + _toolstr(ui, tool, "regappend"))
135 if p:
135 if p:
136 return p
136 return p
137 exe = _toolstr(ui, tool, "executable", tool)
137 exe = _toolstr(ui, tool, "executable", tool)
138 return util.findexe(util.expandpath(exe))
138 return util.findexe(util.expandpath(exe))
139
139
140 def _picktool(repo, ui, path, binary, symlink, changedelete):
140 def _picktool(repo, ui, path, binary, symlink, changedelete):
141 def supportscd(tool):
141 def supportscd(tool):
142 return tool in internals and internals[tool].mergetype == nomerge
142 return tool in internals and internals[tool].mergetype == nomerge
143
143
144 def check(tool, pat, symlink, binary, changedelete):
144 def check(tool, pat, symlink, binary, changedelete):
145 tmsg = tool
145 tmsg = tool
146 if pat:
146 if pat:
147 tmsg = _("%s (for pattern %s)") % (tool, pat)
147 tmsg = _("%s (for pattern %s)") % (tool, pat)
148 if not _findtool(ui, tool):
148 if not _findtool(ui, tool):
149 if pat: # explicitly requested tool deserves a warning
149 if pat: # explicitly requested tool deserves a warning
150 ui.warn(_("couldn't find merge tool %s\n") % tmsg)
150 ui.warn(_("couldn't find merge tool %s\n") % tmsg)
151 else: # configured but non-existing tools are more silent
151 else: # configured but non-existing tools are more silent
152 ui.note(_("couldn't find merge tool %s\n") % tmsg)
152 ui.note(_("couldn't find merge tool %s\n") % tmsg)
153 elif symlink and not _toolbool(ui, tool, "symlink"):
153 elif symlink and not _toolbool(ui, tool, "symlink"):
154 ui.warn(_("tool %s can't handle symlinks\n") % tmsg)
154 ui.warn(_("tool %s can't handle symlinks\n") % tmsg)
155 elif binary and not _toolbool(ui, tool, "binary"):
155 elif binary and not _toolbool(ui, tool, "binary"):
156 ui.warn(_("tool %s can't handle binary\n") % tmsg)
156 ui.warn(_("tool %s can't handle binary\n") % tmsg)
157 elif changedelete and not supportscd(tool):
157 elif changedelete and not supportscd(tool):
158 # the nomerge tools are the only tools that support change/delete
158 # the nomerge tools are the only tools that support change/delete
159 # conflicts
159 # conflicts
160 pass
160 pass
161 elif not util.gui() and _toolbool(ui, tool, "gui"):
161 elif not util.gui() and _toolbool(ui, tool, "gui"):
162 ui.warn(_("tool %s requires a GUI\n") % tmsg)
162 ui.warn(_("tool %s requires a GUI\n") % tmsg)
163 else:
163 else:
164 return True
164 return True
165 return False
165 return False
166
166
167 # internal config: ui.forcemerge
167 # internal config: ui.forcemerge
168 # forcemerge comes from command line arguments, highest priority
168 # forcemerge comes from command line arguments, highest priority
169 force = ui.config('ui', 'forcemerge')
169 force = ui.config('ui', 'forcemerge')
170 if force:
170 if force:
171 toolpath = _findtool(ui, force)
171 toolpath = _findtool(ui, force)
172 if changedelete and not supportscd(toolpath):
172 if changedelete and not supportscd(toolpath):
173 return ":prompt", None
173 return ":prompt", None
174 else:
174 else:
175 if toolpath:
175 if toolpath:
176 return (force, util.shellquote(toolpath))
176 return (force, util.shellquote(toolpath))
177 else:
177 else:
178 # mimic HGMERGE if given tool not found
178 # mimic HGMERGE if given tool not found
179 return (force, force)
179 return (force, force)
180
180
181 # HGMERGE takes next precedence
181 # HGMERGE takes next precedence
182 hgmerge = encoding.environ.get("HGMERGE")
182 hgmerge = encoding.environ.get("HGMERGE")
183 if hgmerge:
183 if hgmerge:
184 if changedelete and not supportscd(hgmerge):
184 if changedelete and not supportscd(hgmerge):
185 return ":prompt", None
185 return ":prompt", None
186 else:
186 else:
187 return (hgmerge, hgmerge)
187 return (hgmerge, hgmerge)
188
188
189 # then patterns
189 # then patterns
190 for pat, tool in ui.configitems("merge-patterns"):
190 for pat, tool in ui.configitems("merge-patterns"):
191 mf = match.match(repo.root, '', [pat])
191 mf = match.match(repo.root, '', [pat])
192 if mf(path) and check(tool, pat, symlink, False, changedelete):
192 if mf(path) and check(tool, pat, symlink, False, changedelete):
193 toolpath = _findtool(ui, tool)
193 toolpath = _findtool(ui, tool)
194 return (tool, util.shellquote(toolpath))
194 return (tool, util.shellquote(toolpath))
195
195
196 # then merge tools
196 # then merge tools
197 tools = {}
197 tools = {}
198 disabled = set()
198 disabled = set()
199 for k, v in ui.configitems("merge-tools"):
199 for k, v in ui.configitems("merge-tools"):
200 t = k.split('.')[0]
200 t = k.split('.')[0]
201 if t not in tools:
201 if t not in tools:
202 tools[t] = int(_toolstr(ui, t, "priority", "0"))
202 tools[t] = int(_toolstr(ui, t, "priority", "0"))
203 if _toolbool(ui, t, "disabled", False):
203 if _toolbool(ui, t, "disabled", False):
204 disabled.add(t)
204 disabled.add(t)
205 names = tools.keys()
205 names = tools.keys()
206 tools = sorted([(-p, tool) for tool, p in tools.items()
206 tools = sorted([(-p, tool) for tool, p in tools.items()
207 if tool not in disabled])
207 if tool not in disabled])
208 uimerge = ui.config("ui", "merge")
208 uimerge = ui.config("ui", "merge")
209 if uimerge:
209 if uimerge:
210 # external tools defined in uimerge won't be able to handle
210 # external tools defined in uimerge won't be able to handle
211 # change/delete conflicts
211 # change/delete conflicts
212 if uimerge not in names and not changedelete:
212 if uimerge not in names and not changedelete:
213 return (uimerge, uimerge)
213 return (uimerge, uimerge)
214 tools.insert(0, (None, uimerge)) # highest priority
214 tools.insert(0, (None, uimerge)) # highest priority
215 tools.append((None, "hgmerge")) # the old default, if found
215 tools.append((None, "hgmerge")) # the old default, if found
216 for p, t in tools:
216 for p, t in tools:
217 if check(t, None, symlink, binary, changedelete):
217 if check(t, None, symlink, binary, changedelete):
218 toolpath = _findtool(ui, t)
218 toolpath = _findtool(ui, t)
219 return (t, util.shellquote(toolpath))
219 return (t, util.shellquote(toolpath))
220
220
221 # internal merge or prompt as last resort
221 # internal merge or prompt as last resort
222 if symlink or binary or changedelete:
222 if symlink or binary or changedelete:
223 if not changedelete and len(tools):
223 if not changedelete and len(tools):
224 # any tool is rejected by capability for symlink or binary
224 # any tool is rejected by capability for symlink or binary
225 ui.warn(_("no tool found to merge %s\n") % path)
225 ui.warn(_("no tool found to merge %s\n") % path)
226 return ":prompt", None
226 return ":prompt", None
227 return ":merge", None
227 return ":merge", None
228
228
229 def _eoltype(data):
229 def _eoltype(data):
230 "Guess the EOL type of a file"
230 "Guess the EOL type of a file"
231 if '\0' in data: # binary
231 if '\0' in data: # binary
232 return None
232 return None
233 if '\r\n' in data: # Windows
233 if '\r\n' in data: # Windows
234 return '\r\n'
234 return '\r\n'
235 if '\r' in data: # Old Mac
235 if '\r' in data: # Old Mac
236 return '\r'
236 return '\r'
237 if '\n' in data: # UNIX
237 if '\n' in data: # UNIX
238 return '\n'
238 return '\n'
239 return None # unknown
239 return None # unknown
240
240
241 def _matcheol(file, origfile):
241 def _matcheol(file, origfile):
242 "Convert EOL markers in a file to match origfile"
242 "Convert EOL markers in a file to match origfile"
243 tostyle = _eoltype(util.readfile(origfile))
243 tostyle = _eoltype(util.readfile(origfile))
244 if tostyle:
244 if tostyle:
245 data = util.readfile(file)
245 data = util.readfile(file)
246 style = _eoltype(data)
246 style = _eoltype(data)
247 if style:
247 if style:
248 newdata = data.replace(style, tostyle)
248 newdata = data.replace(style, tostyle)
249 if newdata != data:
249 if newdata != data:
250 util.writefile(file, newdata)
250 util.writefile(file, newdata)
251
251
252 @internaltool('prompt', nomerge)
252 @internaltool('prompt', nomerge)
253 def _iprompt(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
253 def _iprompt(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
254 """Asks the user which of the local `p1()` or the other `p2()` version to
254 """Asks the user which of the local `p1()` or the other `p2()` version to
255 keep as the merged version."""
255 keep as the merged version."""
256 ui = repo.ui
256 ui = repo.ui
257 fd = fcd.path()
257 fd = fcd.path()
258
258
259 prompts = partextras(labels)
259 prompts = partextras(labels)
260 prompts['fd'] = fd
260 prompts['fd'] = fd
261 try:
261 try:
262 if fco.isabsent():
262 if fco.isabsent():
263 index = ui.promptchoice(
263 index = ui.promptchoice(
264 _localchangedotherdeletedmsg % prompts, 2)
264 _localchangedotherdeletedmsg % prompts, 2)
265 choice = ['local', 'other', 'unresolved'][index]
265 choice = ['local', 'other', 'unresolved'][index]
266 elif fcd.isabsent():
266 elif fcd.isabsent():
267 index = ui.promptchoice(
267 index = ui.promptchoice(
268 _otherchangedlocaldeletedmsg % prompts, 2)
268 _otherchangedlocaldeletedmsg % prompts, 2)
269 choice = ['other', 'local', 'unresolved'][index]
269 choice = ['other', 'local', 'unresolved'][index]
270 else:
270 else:
271 index = ui.promptchoice(
271 index = ui.promptchoice(
272 _("keep (l)ocal%(l)s, take (o)ther%(o)s, or leave (u)nresolved"
272 _("keep (l)ocal%(l)s, take (o)ther%(o)s, or leave (u)nresolved"
273 " for %(fd)s?"
273 " for %(fd)s?"
274 "$$ &Local $$ &Other $$ &Unresolved") % prompts, 2)
274 "$$ &Local $$ &Other $$ &Unresolved") % prompts, 2)
275 choice = ['local', 'other', 'unresolved'][index]
275 choice = ['local', 'other', 'unresolved'][index]
276
276
277 if choice == 'other':
277 if choice == 'other':
278 return _iother(repo, mynode, orig, fcd, fco, fca, toolconf,
278 return _iother(repo, mynode, orig, fcd, fco, fca, toolconf,
279 labels)
279 labels)
280 elif choice == 'local':
280 elif choice == 'local':
281 return _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf,
281 return _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf,
282 labels)
282 labels)
283 elif choice == 'unresolved':
283 elif choice == 'unresolved':
284 return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf,
284 return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf,
285 labels)
285 labels)
286 except error.ResponseExpected:
286 except error.ResponseExpected:
287 ui.write("\n")
287 ui.write("\n")
288 return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf,
288 return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf,
289 labels)
289 labels)
290
290
291 @internaltool('local', nomerge)
291 @internaltool('local', nomerge)
292 def _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
292 def _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
293 """Uses the local `p1()` version of files as the merged version."""
293 """Uses the local `p1()` version of files as the merged version."""
294 return 0, fcd.isabsent()
294 return 0, fcd.isabsent()
295
295
296 @internaltool('other', nomerge)
296 @internaltool('other', nomerge)
297 def _iother(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
297 def _iother(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
298 """Uses the other `p2()` version of files as the merged version."""
298 """Uses the other `p2()` version of files as the merged version."""
299 if fco.isabsent():
299 if fco.isabsent():
300 # local changed, remote deleted -- 'deleted' picked
300 # local changed, remote deleted -- 'deleted' picked
301 repo.wvfs.unlinkpath(fcd.path())
301 _underlyingfctxifabsent(fcd).remove()
302 deleted = True
302 deleted = True
303 else:
303 else:
304 repo.wwrite(fcd.path(), fco.data(), fco.flags())
304 _underlyingfctxifabsent(fcd).write(fco.data(), fco.flags())
305 deleted = False
305 deleted = False
306 return 0, deleted
306 return 0, deleted
307
307
308 @internaltool('fail', nomerge)
308 @internaltool('fail', nomerge)
309 def _ifail(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
309 def _ifail(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
310 """
310 """
311 Rather than attempting to merge files that were modified on both
311 Rather than attempting to merge files that were modified on both
312 branches, it marks them as unresolved. The resolve command must be
312 branches, it marks them as unresolved. The resolve command must be
313 used to resolve these conflicts."""
313 used to resolve these conflicts."""
314 # for change/delete conflicts write out the changed version, then fail
314 # for change/delete conflicts write out the changed version, then fail
315 if fcd.isabsent():
315 if fcd.isabsent():
316 repo.wwrite(fcd.path(), fco.data(), fco.flags())
316 _underlyingfctxifabsent(fcd).write(fco.data(), fco.flags())
317 return 1, False
317 return 1, False
318
318
319 def _underlyingfctxifabsent(filectx):
320 """Sometimes when resolving, our fcd is actually an absentfilectx, but
321 we want to write to it (to do the resolve). This helper returns the
322 underyling workingfilectx in that case.
323 """
324 if filectx.isabsent():
325 return filectx.changectx()[filectx.path()]
326 else:
327 return filectx
328
319 def _premerge(repo, fcd, fco, fca, toolconf, files, labels=None):
329 def _premerge(repo, fcd, fco, fca, toolconf, files, labels=None):
320 tool, toolpath, binary, symlink = toolconf
330 tool, toolpath, binary, symlink = toolconf
321 if symlink or fcd.isabsent() or fco.isabsent():
331 if symlink or fcd.isabsent() or fco.isabsent():
322 return 1
332 return 1
323 a, b, c, back = files
333 a, b, c, back = files
324
334
325 ui = repo.ui
335 ui = repo.ui
326
336
327 validkeep = ['keep', 'keep-merge3']
337 validkeep = ['keep', 'keep-merge3']
328
338
329 # do we attempt to simplemerge first?
339 # do we attempt to simplemerge first?
330 try:
340 try:
331 premerge = _toolbool(ui, tool, "premerge", not binary)
341 premerge = _toolbool(ui, tool, "premerge", not binary)
332 except error.ConfigError:
342 except error.ConfigError:
333 premerge = _toolstr(ui, tool, "premerge").lower()
343 premerge = _toolstr(ui, tool, "premerge").lower()
334 if premerge not in validkeep:
344 if premerge not in validkeep:
335 _valid = ', '.join(["'" + v + "'" for v in validkeep])
345 _valid = ', '.join(["'" + v + "'" for v in validkeep])
336 raise error.ConfigError(_("%s.premerge not valid "
346 raise error.ConfigError(_("%s.premerge not valid "
337 "('%s' is neither boolean nor %s)") %
347 "('%s' is neither boolean nor %s)") %
338 (tool, premerge, _valid))
348 (tool, premerge, _valid))
339
349
340 if premerge:
350 if premerge:
341 if premerge == 'keep-merge3':
351 if premerge == 'keep-merge3':
342 if not labels:
352 if not labels:
343 labels = _defaultconflictlabels
353 labels = _defaultconflictlabels
344 if len(labels) < 3:
354 if len(labels) < 3:
345 labels.append('base')
355 labels.append('base')
346 r = simplemerge.simplemerge(ui, a, b, c, quiet=True, label=labels)
356 r = simplemerge.simplemerge(ui, a, b, c, quiet=True, label=labels)
347 if not r:
357 if not r:
348 ui.debug(" premerge successful\n")
358 ui.debug(" premerge successful\n")
349 return 0
359 return 0
350 if premerge not in validkeep:
360 if premerge not in validkeep:
351 util.copyfile(back, a) # restore from backup and try again
361 util.copyfile(back, a) # restore from backup and try again
352 return 1 # continue merging
362 return 1 # continue merging
353
363
354 def _mergecheck(repo, mynode, orig, fcd, fco, fca, toolconf):
364 def _mergecheck(repo, mynode, orig, fcd, fco, fca, toolconf):
355 tool, toolpath, binary, symlink = toolconf
365 tool, toolpath, binary, symlink = toolconf
356 if symlink:
366 if symlink:
357 repo.ui.warn(_('warning: internal %s cannot merge symlinks '
367 repo.ui.warn(_('warning: internal %s cannot merge symlinks '
358 'for %s\n') % (tool, fcd.path()))
368 'for %s\n') % (tool, fcd.path()))
359 return False
369 return False
360 if fcd.isabsent() or fco.isabsent():
370 if fcd.isabsent() or fco.isabsent():
361 repo.ui.warn(_('warning: internal %s cannot merge change/delete '
371 repo.ui.warn(_('warning: internal %s cannot merge change/delete '
362 'conflict for %s\n') % (tool, fcd.path()))
372 'conflict for %s\n') % (tool, fcd.path()))
363 return False
373 return False
364 return True
374 return True
365
375
366 def _merge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels, mode):
376 def _merge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels, mode):
367 """
377 """
368 Uses the internal non-interactive simple merge algorithm for merging
378 Uses the internal non-interactive simple merge algorithm for merging
369 files. It will fail if there are any conflicts and leave markers in
379 files. It will fail if there are any conflicts and leave markers in
370 the partially merged file. Markers will have two sections, one for each side
380 the partially merged file. Markers will have two sections, one for each side
371 of merge, unless mode equals 'union' which suppresses the markers."""
381 of merge, unless mode equals 'union' which suppresses the markers."""
372 a, b, c, back = files
382 a, b, c, back = files
373
383
374 ui = repo.ui
384 ui = repo.ui
375
385
376 r = simplemerge.simplemerge(ui, a, b, c, label=labels, mode=mode)
386 r = simplemerge.simplemerge(ui, a, b, c, label=labels, mode=mode)
377 return True, r, False
387 return True, r, False
378
388
379 @internaltool('union', fullmerge,
389 @internaltool('union', fullmerge,
380 _("warning: conflicts while merging %s! "
390 _("warning: conflicts while merging %s! "
381 "(edit, then use 'hg resolve --mark')\n"),
391 "(edit, then use 'hg resolve --mark')\n"),
382 precheck=_mergecheck)
392 precheck=_mergecheck)
383 def _iunion(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
393 def _iunion(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
384 """
394 """
385 Uses the internal non-interactive simple merge algorithm for merging
395 Uses the internal non-interactive simple merge algorithm for merging
386 files. It will use both left and right sides for conflict regions.
396 files. It will use both left and right sides for conflict regions.
387 No markers are inserted."""
397 No markers are inserted."""
388 return _merge(repo, mynode, orig, fcd, fco, fca, toolconf,
398 return _merge(repo, mynode, orig, fcd, fco, fca, toolconf,
389 files, labels, 'union')
399 files, labels, 'union')
390
400
391 @internaltool('merge', fullmerge,
401 @internaltool('merge', fullmerge,
392 _("warning: conflicts while merging %s! "
402 _("warning: conflicts while merging %s! "
393 "(edit, then use 'hg resolve --mark')\n"),
403 "(edit, then use 'hg resolve --mark')\n"),
394 precheck=_mergecheck)
404 precheck=_mergecheck)
395 def _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
405 def _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
396 """
406 """
397 Uses the internal non-interactive simple merge algorithm for merging
407 Uses the internal non-interactive simple merge algorithm for merging
398 files. It will fail if there are any conflicts and leave markers in
408 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
409 the partially merged file. Markers will have two sections, one for each side
400 of merge."""
410 of merge."""
401 return _merge(repo, mynode, orig, fcd, fco, fca, toolconf,
411 return _merge(repo, mynode, orig, fcd, fco, fca, toolconf,
402 files, labels, 'merge')
412 files, labels, 'merge')
403
413
404 @internaltool('merge3', fullmerge,
414 @internaltool('merge3', fullmerge,
405 _("warning: conflicts while merging %s! "
415 _("warning: conflicts while merging %s! "
406 "(edit, then use 'hg resolve --mark')\n"),
416 "(edit, then use 'hg resolve --mark')\n"),
407 precheck=_mergecheck)
417 precheck=_mergecheck)
408 def _imerge3(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
418 def _imerge3(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
409 """
419 """
410 Uses the internal non-interactive simple merge algorithm for merging
420 Uses the internal non-interactive simple merge algorithm for merging
411 files. It will fail if there are any conflicts and leave markers in
421 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
422 the partially merged file. Marker will have three sections, one from each
413 side of the merge and one for the base content."""
423 side of the merge and one for the base content."""
414 if not labels:
424 if not labels:
415 labels = _defaultconflictlabels
425 labels = _defaultconflictlabels
416 if len(labels) < 3:
426 if len(labels) < 3:
417 labels.append('base')
427 labels.append('base')
418 return _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels)
428 return _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels)
419
429
420 def _imergeauto(repo, mynode, orig, fcd, fco, fca, toolconf, files,
430 def _imergeauto(repo, mynode, orig, fcd, fco, fca, toolconf, files,
421 labels=None, localorother=None):
431 labels=None, localorother=None):
422 """
432 """
423 Generic driver for _imergelocal and _imergeother
433 Generic driver for _imergelocal and _imergeother
424 """
434 """
425 assert localorother is not None
435 assert localorother is not None
426 tool, toolpath, binary, symlink = toolconf
436 tool, toolpath, binary, symlink = toolconf
427 a, b, c, back = files
437 a, b, c, back = files
428 r = simplemerge.simplemerge(repo.ui, a, b, c, label=labels,
438 r = simplemerge.simplemerge(repo.ui, a, b, c, label=labels,
429 localorother=localorother)
439 localorother=localorother)
430 return True, r
440 return True, r
431
441
432 @internaltool('merge-local', mergeonly, precheck=_mergecheck)
442 @internaltool('merge-local', mergeonly, precheck=_mergecheck)
433 def _imergelocal(*args, **kwargs):
443 def _imergelocal(*args, **kwargs):
434 """
444 """
435 Like :merge, but resolve all conflicts non-interactively in favor
445 Like :merge, but resolve all conflicts non-interactively in favor
436 of the local `p1()` changes."""
446 of the local `p1()` changes."""
437 success, status = _imergeauto(localorother='local', *args, **kwargs)
447 success, status = _imergeauto(localorother='local', *args, **kwargs)
438 return success, status, False
448 return success, status, False
439
449
440 @internaltool('merge-other', mergeonly, precheck=_mergecheck)
450 @internaltool('merge-other', mergeonly, precheck=_mergecheck)
441 def _imergeother(*args, **kwargs):
451 def _imergeother(*args, **kwargs):
442 """
452 """
443 Like :merge, but resolve all conflicts non-interactively in favor
453 Like :merge, but resolve all conflicts non-interactively in favor
444 of the other `p2()` changes."""
454 of the other `p2()` changes."""
445 success, status = _imergeauto(localorother='other', *args, **kwargs)
455 success, status = _imergeauto(localorother='other', *args, **kwargs)
446 return success, status, False
456 return success, status, False
447
457
448 @internaltool('tagmerge', mergeonly,
458 @internaltool('tagmerge', mergeonly,
449 _("automatic tag merging of %s failed! "
459 _("automatic tag merging of %s failed! "
450 "(use 'hg resolve --tool :merge' or another merge "
460 "(use 'hg resolve --tool :merge' or another merge "
451 "tool of your choice)\n"))
461 "tool of your choice)\n"))
452 def _itagmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
462 def _itagmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
453 """
463 """
454 Uses the internal tag merge algorithm (experimental).
464 Uses the internal tag merge algorithm (experimental).
455 """
465 """
456 success, status = tagmerge.merge(repo, fcd, fco, fca)
466 success, status = tagmerge.merge(repo, fcd, fco, fca)
457 return success, status, False
467 return success, status, False
458
468
459 @internaltool('dump', fullmerge)
469 @internaltool('dump', fullmerge)
460 def _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
470 def _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
461 """
471 """
462 Creates three versions of the files to merge, containing the
472 Creates three versions of the files to merge, containing the
463 contents of local, other and base. These files can then be used to
473 contents of local, other and base. These files can then be used to
464 perform a merge manually. If the file to be merged is named
474 perform a merge manually. If the file to be merged is named
465 ``a.txt``, these files will accordingly be named ``a.txt.local``,
475 ``a.txt``, these files will accordingly be named ``a.txt.local``,
466 ``a.txt.other`` and ``a.txt.base`` and they will be placed in the
476 ``a.txt.other`` and ``a.txt.base`` and they will be placed in the
467 same directory as ``a.txt``.
477 same directory as ``a.txt``.
468
478
469 This implies permerge. Therefore, files aren't dumped, if premerge
479 This implies permerge. Therefore, files aren't dumped, if premerge
470 runs successfully. Use :forcedump to forcibly write files out.
480 runs successfully. Use :forcedump to forcibly write files out.
471 """
481 """
472 a, b, c, back = files
482 a, b, c, back = files
473
483
474 fd = fcd.path()
484 fd = fcd.path()
475
485
476 util.copyfile(a, a + ".local")
486 util.copyfile(a, a + ".local")
477 repo.wwrite(fd + ".other", fco.data(), fco.flags())
487 repo.wwrite(fd + ".other", fco.data(), fco.flags())
478 repo.wwrite(fd + ".base", fca.data(), fca.flags())
488 repo.wwrite(fd + ".base", fca.data(), fca.flags())
479 return False, 1, False
489 return False, 1, False
480
490
481 @internaltool('forcedump', mergeonly)
491 @internaltool('forcedump', mergeonly)
482 def _forcedump(repo, mynode, orig, fcd, fco, fca, toolconf, files,
492 def _forcedump(repo, mynode, orig, fcd, fco, fca, toolconf, files,
483 labels=None):
493 labels=None):
484 """
494 """
485 Creates three versions of the files as same as :dump, but omits premerge.
495 Creates three versions of the files as same as :dump, but omits premerge.
486 """
496 """
487 return _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files,
497 return _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files,
488 labels=labels)
498 labels=labels)
489
499
490 def _xmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
500 def _xmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
491 tool, toolpath, binary, symlink = toolconf
501 tool, toolpath, binary, symlink = toolconf
492 if fcd.isabsent() or fco.isabsent():
502 if fcd.isabsent() or fco.isabsent():
493 repo.ui.warn(_('warning: %s cannot merge change/delete conflict '
503 repo.ui.warn(_('warning: %s cannot merge change/delete conflict '
494 'for %s\n') % (tool, fcd.path()))
504 'for %s\n') % (tool, fcd.path()))
495 return False, 1, None
505 return False, 1, None
496 a, b, c, back = files
506 a, b, c, back = files
497 out = ""
507 out = ""
498 env = {'HG_FILE': fcd.path(),
508 env = {'HG_FILE': fcd.path(),
499 'HG_MY_NODE': short(mynode),
509 'HG_MY_NODE': short(mynode),
500 'HG_OTHER_NODE': str(fco.changectx()),
510 'HG_OTHER_NODE': str(fco.changectx()),
501 'HG_BASE_NODE': str(fca.changectx()),
511 'HG_BASE_NODE': str(fca.changectx()),
502 'HG_MY_ISLINK': 'l' in fcd.flags(),
512 'HG_MY_ISLINK': 'l' in fcd.flags(),
503 'HG_OTHER_ISLINK': 'l' in fco.flags(),
513 'HG_OTHER_ISLINK': 'l' in fco.flags(),
504 'HG_BASE_ISLINK': 'l' in fca.flags(),
514 'HG_BASE_ISLINK': 'l' in fca.flags(),
505 }
515 }
506
516
507 ui = repo.ui
517 ui = repo.ui
508
518
509 args = _toolstr(ui, tool, "args", '$local $base $other')
519 args = _toolstr(ui, tool, "args", '$local $base $other')
510 if "$output" in args:
520 if "$output" in args:
511 out, a = a, back # read input from backup, write to original
521 out, a = a, back # read input from backup, write to original
512 replace = {'local': a, 'base': b, 'other': c, 'output': out}
522 replace = {'local': a, 'base': b, 'other': c, 'output': out}
513 args = util.interpolate(r'\$', replace, args,
523 args = util.interpolate(r'\$', replace, args,
514 lambda s: util.shellquote(util.localpath(s)))
524 lambda s: util.shellquote(util.localpath(s)))
515 cmd = toolpath + ' ' + args
525 cmd = toolpath + ' ' + args
516 if _toolbool(ui, tool, "gui"):
526 if _toolbool(ui, tool, "gui"):
517 repo.ui.status(_('running merge tool %s for file %s\n') %
527 repo.ui.status(_('running merge tool %s for file %s\n') %
518 (tool, fcd.path()))
528 (tool, fcd.path()))
519 repo.ui.debug('launching merge tool: %s\n' % cmd)
529 repo.ui.debug('launching merge tool: %s\n' % cmd)
520 r = ui.system(cmd, cwd=repo.root, environ=env, blockedtag='mergetool')
530 r = ui.system(cmd, cwd=repo.root, environ=env, blockedtag='mergetool')
521 repo.ui.debug('merge tool returned: %s\n' % r)
531 repo.ui.debug('merge tool returned: %s\n' % r)
522 return True, r, False
532 return True, r, False
523
533
524 def _formatconflictmarker(repo, ctx, template, label, pad):
534 def _formatconflictmarker(repo, ctx, template, label, pad):
525 """Applies the given template to the ctx, prefixed by the label.
535 """Applies the given template to the ctx, prefixed by the label.
526
536
527 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
528 can have aligned templated parts.
538 can have aligned templated parts.
529 """
539 """
530 if ctx.node() is None:
540 if ctx.node() is None:
531 ctx = ctx.p1()
541 ctx = ctx.p1()
532
542
533 props = templatekw.keywords.copy()
543 props = templatekw.keywords.copy()
534 props['templ'] = template
544 props['templ'] = template
535 props['ctx'] = ctx
545 props['ctx'] = ctx
536 props['repo'] = repo
546 props['repo'] = repo
537 templateresult = template.render(props)
547 templateresult = template.render(props)
538
548
539 label = ('%s:' % label).ljust(pad + 1)
549 label = ('%s:' % label).ljust(pad + 1)
540 mark = '%s %s' % (label, templateresult)
550 mark = '%s %s' % (label, templateresult)
541
551
542 if mark:
552 if mark:
543 mark = mark.splitlines()[0] # split for safety
553 mark = mark.splitlines()[0] # split for safety
544
554
545 # 8 for the prefix of conflict marker lines (e.g. '<<<<<<< ')
555 # 8 for the prefix of conflict marker lines (e.g. '<<<<<<< ')
546 return util.ellipsis(mark, 80 - 8)
556 return util.ellipsis(mark, 80 - 8)
547
557
548 _defaultconflictmarker = ('{node|short} '
558 _defaultconflictmarker = ('{node|short} '
549 '{ifeq(tags, "tip", "", '
559 '{ifeq(tags, "tip", "", '
550 'ifeq(tags, "", "", "{tags} "))}'
560 'ifeq(tags, "", "", "{tags} "))}'
551 '{if(bookmarks, "{bookmarks} ")}'
561 '{if(bookmarks, "{bookmarks} ")}'
552 '{ifeq(branch, "default", "", "{branch} ")}'
562 '{ifeq(branch, "default", "", "{branch} ")}'
553 '- {author|user}: {desc|firstline}')
563 '- {author|user}: {desc|firstline}')
554
564
555 _defaultconflictlabels = ['local', 'other']
565 _defaultconflictlabels = ['local', 'other']
556
566
557 def _formatlabels(repo, fcd, fco, fca, labels):
567 def _formatlabels(repo, fcd, fco, fca, labels):
558 """Formats the given labels using the conflict marker template.
568 """Formats the given labels using the conflict marker template.
559
569
560 Returns a list of formatted labels.
570 Returns a list of formatted labels.
561 """
571 """
562 cd = fcd.changectx()
572 cd = fcd.changectx()
563 co = fco.changectx()
573 co = fco.changectx()
564 ca = fca.changectx()
574 ca = fca.changectx()
565
575
566 ui = repo.ui
576 ui = repo.ui
567 template = ui.config('ui', 'mergemarkertemplate', _defaultconflictmarker)
577 template = ui.config('ui', 'mergemarkertemplate', _defaultconflictmarker)
568 template = templater.unquotestring(template)
578 template = templater.unquotestring(template)
569 tmpl = formatter.maketemplater(ui, template)
579 tmpl = formatter.maketemplater(ui, template)
570
580
571 pad = max(len(l) for l in labels)
581 pad = max(len(l) for l in labels)
572
582
573 newlabels = [_formatconflictmarker(repo, cd, tmpl, labels[0], pad),
583 newlabels = [_formatconflictmarker(repo, cd, tmpl, labels[0], pad),
574 _formatconflictmarker(repo, co, tmpl, labels[1], pad)]
584 _formatconflictmarker(repo, co, tmpl, labels[1], pad)]
575 if len(labels) > 2:
585 if len(labels) > 2:
576 newlabels.append(_formatconflictmarker(repo, ca, tmpl, labels[2], pad))
586 newlabels.append(_formatconflictmarker(repo, ca, tmpl, labels[2], pad))
577 return newlabels
587 return newlabels
578
588
579 def partextras(labels):
589 def partextras(labels):
580 """Return a dictionary of extra labels for use in prompts to the user
590 """Return a dictionary of extra labels for use in prompts to the user
581
591
582 Intended use is in strings of the form "(l)ocal%(l)s".
592 Intended use is in strings of the form "(l)ocal%(l)s".
583 """
593 """
584 if labels is None:
594 if labels is None:
585 return {
595 return {
586 "l": "",
596 "l": "",
587 "o": "",
597 "o": "",
588 }
598 }
589
599
590 return {
600 return {
591 "l": " [%s]" % labels[0],
601 "l": " [%s]" % labels[0],
592 "o": " [%s]" % labels[1],
602 "o": " [%s]" % labels[1],
593 }
603 }
594
604
595 def _filemerge(premerge, repo, mynode, orig, fcd, fco, fca, labels=None):
605 def _filemerge(premerge, repo, mynode, orig, fcd, fco, fca, labels=None):
596 """perform a 3-way merge in the working directory
606 """perform a 3-way merge in the working directory
597
607
598 premerge = whether this is a premerge
608 premerge = whether this is a premerge
599 mynode = parent node before merge
609 mynode = parent node before merge
600 orig = original local filename before merge
610 orig = original local filename before merge
601 fco = other file context
611 fco = other file context
602 fca = ancestor file context
612 fca = ancestor file context
603 fcd = local file context for current/destination file
613 fcd = local file context for current/destination file
604
614
605 Returns whether the merge is complete, the return value of the merge, and
615 Returns whether the merge is complete, the return value of the merge, and
606 a boolean indicating whether the file was deleted from disk."""
616 a boolean indicating whether the file was deleted from disk."""
607
617
608 def temp(prefix, ctx):
618 def temp(prefix, ctx):
609 fullbase, ext = os.path.splitext(ctx.path())
619 fullbase, ext = os.path.splitext(ctx.path())
610 pre = "%s~%s." % (os.path.basename(fullbase), prefix)
620 pre = "%s~%s." % (os.path.basename(fullbase), prefix)
611 (fd, name) = tempfile.mkstemp(prefix=pre, suffix=ext)
621 (fd, name) = tempfile.mkstemp(prefix=pre, suffix=ext)
612 data = repo.wwritedata(ctx.path(), ctx.data())
622 data = repo.wwritedata(ctx.path(), ctx.data())
613 f = os.fdopen(fd, pycompat.sysstr("wb"))
623 f = os.fdopen(fd, pycompat.sysstr("wb"))
614 f.write(data)
624 f.write(data)
615 f.close()
625 f.close()
616 return name
626 return name
617
627
618 if not fco.cmp(fcd): # files identical?
628 if not fco.cmp(fcd): # files identical?
619 return True, None, False
629 return True, None, False
620
630
621 ui = repo.ui
631 ui = repo.ui
622 fd = fcd.path()
632 fd = fcd.path()
623 binary = fcd.isbinary() or fco.isbinary() or fca.isbinary()
633 binary = fcd.isbinary() or fco.isbinary() or fca.isbinary()
624 symlink = 'l' in fcd.flags() + fco.flags()
634 symlink = 'l' in fcd.flags() + fco.flags()
625 changedelete = fcd.isabsent() or fco.isabsent()
635 changedelete = fcd.isabsent() or fco.isabsent()
626 tool, toolpath = _picktool(repo, ui, fd, binary, symlink, changedelete)
636 tool, toolpath = _picktool(repo, ui, fd, binary, symlink, changedelete)
627 if tool in internals and tool.startswith('internal:'):
637 if tool in internals and tool.startswith('internal:'):
628 # normalize to new-style names (':merge' etc)
638 # normalize to new-style names (':merge' etc)
629 tool = tool[len('internal'):]
639 tool = tool[len('internal'):]
630 ui.debug("picked tool '%s' for %s (binary %s symlink %s changedelete %s)\n"
640 ui.debug("picked tool '%s' for %s (binary %s symlink %s changedelete %s)\n"
631 % (tool, fd, pycompat.bytestr(binary), pycompat.bytestr(symlink),
641 % (tool, fd, pycompat.bytestr(binary), pycompat.bytestr(symlink),
632 pycompat.bytestr(changedelete)))
642 pycompat.bytestr(changedelete)))
633
643
634 if tool in internals:
644 if tool in internals:
635 func = internals[tool]
645 func = internals[tool]
636 mergetype = func.mergetype
646 mergetype = func.mergetype
637 onfailure = func.onfailure
647 onfailure = func.onfailure
638 precheck = func.precheck
648 precheck = func.precheck
639 else:
649 else:
640 func = _xmerge
650 func = _xmerge
641 mergetype = fullmerge
651 mergetype = fullmerge
642 onfailure = _("merging %s failed!\n")
652 onfailure = _("merging %s failed!\n")
643 precheck = None
653 precheck = None
644
654
645 toolconf = tool, toolpath, binary, symlink
655 toolconf = tool, toolpath, binary, symlink
646
656
647 if mergetype == nomerge:
657 if mergetype == nomerge:
648 r, deleted = func(repo, mynode, orig, fcd, fco, fca, toolconf, labels)
658 r, deleted = func(repo, mynode, orig, fcd, fco, fca, toolconf, labels)
649 return True, r, deleted
659 return True, r, deleted
650
660
651 if premerge:
661 if premerge:
652 if orig != fco.path():
662 if orig != fco.path():
653 ui.status(_("merging %s and %s to %s\n") % (orig, fco.path(), fd))
663 ui.status(_("merging %s and %s to %s\n") % (orig, fco.path(), fd))
654 else:
664 else:
655 ui.status(_("merging %s\n") % fd)
665 ui.status(_("merging %s\n") % fd)
656
666
657 ui.debug("my %s other %s ancestor %s\n" % (fcd, fco, fca))
667 ui.debug("my %s other %s ancestor %s\n" % (fcd, fco, fca))
658
668
659 if precheck and not precheck(repo, mynode, orig, fcd, fco, fca,
669 if precheck and not precheck(repo, mynode, orig, fcd, fco, fca,
660 toolconf):
670 toolconf):
661 if onfailure:
671 if onfailure:
662 ui.warn(onfailure % fd)
672 ui.warn(onfailure % fd)
663 return True, 1, False
673 return True, 1, False
664
674
665 a = repo.wjoin(fd)
675 a = repo.wjoin(fd)
666 b = temp("base", fca)
676 b = temp("base", fca)
667 c = temp("other", fco)
677 c = temp("other", fco)
668 if not fcd.isabsent():
678 if not fcd.isabsent():
669 back = scmutil.origpath(ui, repo, a)
679 back = scmutil.origpath(ui, repo, a)
670 if premerge:
680 if premerge:
671 util.copyfile(a, back)
681 util.copyfile(a, back)
672 else:
682 else:
673 back = None
683 back = None
674 files = (a, b, c, back)
684 files = (a, b, c, back)
675
685
676 r = 1
686 r = 1
677 try:
687 try:
678 markerstyle = ui.config('ui', 'mergemarkers', 'basic')
688 markerstyle = ui.config('ui', 'mergemarkers', 'basic')
679 if not labels:
689 if not labels:
680 labels = _defaultconflictlabels
690 labels = _defaultconflictlabels
681 if markerstyle != 'basic':
691 if markerstyle != 'basic':
682 labels = _formatlabels(repo, fcd, fco, fca, labels)
692 labels = _formatlabels(repo, fcd, fco, fca, labels)
683
693
684 if premerge and mergetype == fullmerge:
694 if premerge and mergetype == fullmerge:
685 r = _premerge(repo, fcd, fco, fca, toolconf, files, labels=labels)
695 r = _premerge(repo, fcd, fco, fca, toolconf, files, labels=labels)
686 # complete if premerge successful (r is 0)
696 # complete if premerge successful (r is 0)
687 return not r, r, False
697 return not r, r, False
688
698
689 needcheck, r, deleted = func(repo, mynode, orig, fcd, fco, fca,
699 needcheck, r, deleted = func(repo, mynode, orig, fcd, fco, fca,
690 toolconf, files, labels=labels)
700 toolconf, files, labels=labels)
691
701
692 if needcheck:
702 if needcheck:
693 r = _check(r, ui, tool, fcd, files)
703 r = _check(r, ui, tool, fcd, files)
694
704
695 if r:
705 if r:
696 if onfailure:
706 if onfailure:
697 ui.warn(onfailure % fd)
707 ui.warn(onfailure % fd)
698
708
699 return True, r, deleted
709 return True, r, deleted
700 finally:
710 finally:
701 if not r and back is not None:
711 if not r and back is not None:
702 util.unlink(back)
712 util.unlink(back)
703 util.unlink(b)
713 util.unlink(b)
704 util.unlink(c)
714 util.unlink(c)
705
715
706 def _check(r, ui, tool, fcd, files):
716 def _check(r, ui, tool, fcd, files):
707 fd = fcd.path()
717 fd = fcd.path()
708 a, b, c, back = files
718 a, b, c, back = files
709
719
710 if not r and (_toolbool(ui, tool, "checkconflicts") or
720 if not r and (_toolbool(ui, tool, "checkconflicts") or
711 'conflicts' in _toollist(ui, tool, "check")):
721 'conflicts' in _toollist(ui, tool, "check")):
712 if re.search("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcd.data(),
722 if re.search("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcd.data(),
713 re.MULTILINE):
723 re.MULTILINE):
714 r = 1
724 r = 1
715
725
716 checked = False
726 checked = False
717 if 'prompt' in _toollist(ui, tool, "check"):
727 if 'prompt' in _toollist(ui, tool, "check"):
718 checked = True
728 checked = True
719 if ui.promptchoice(_("was merge of '%s' successful (yn)?"
729 if ui.promptchoice(_("was merge of '%s' successful (yn)?"
720 "$$ &Yes $$ &No") % fd, 1):
730 "$$ &Yes $$ &No") % fd, 1):
721 r = 1
731 r = 1
722
732
723 if not r and not checked and (_toolbool(ui, tool, "checkchanged") or
733 if not r and not checked and (_toolbool(ui, tool, "checkchanged") or
724 'changed' in
734 'changed' in
725 _toollist(ui, tool, "check")):
735 _toollist(ui, tool, "check")):
726 if back is not None and filecmp.cmp(a, back):
736 if back is not None and filecmp.cmp(a, back):
727 if ui.promptchoice(_(" output file %s appears unchanged\n"
737 if ui.promptchoice(_(" output file %s appears unchanged\n"
728 "was merge successful (yn)?"
738 "was merge successful (yn)?"
729 "$$ &Yes $$ &No") % fd, 1):
739 "$$ &Yes $$ &No") % fd, 1):
730 r = 1
740 r = 1
731
741
732 if back is not None and _toolbool(ui, tool, "fixeol"):
742 if back is not None and _toolbool(ui, tool, "fixeol"):
733 _matcheol(a, back)
743 _matcheol(a, back)
734
744
735 return r
745 return r
736
746
737 def premerge(repo, mynode, orig, fcd, fco, fca, labels=None):
747 def premerge(repo, mynode, orig, fcd, fco, fca, labels=None):
738 return _filemerge(True, repo, mynode, orig, fcd, fco, fca, labels=labels)
748 return _filemerge(True, repo, mynode, orig, fcd, fco, fca, labels=labels)
739
749
740 def filemerge(repo, mynode, orig, fcd, fco, fca, labels=None):
750 def filemerge(repo, mynode, orig, fcd, fco, fca, labels=None):
741 return _filemerge(False, repo, mynode, orig, fcd, fco, fca, labels=labels)
751 return _filemerge(False, repo, mynode, orig, fcd, fco, fca, labels=labels)
742
752
743 # tell hggettext to extract docstrings from these functions:
753 # tell hggettext to extract docstrings from these functions:
744 i18nfunctions = internals.values()
754 i18nfunctions = internals.values()
General Comments 0
You need to be logged in to leave comments. Login now