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