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