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