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