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