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