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