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