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