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