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