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