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