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