##// END OF EJS Templates
filemerge: eliminate most uses of tempfiles...
Phil Cohen -
r34034:67cfffbf default
parent child Browse files
Show More
@@ -1,771 +1,769 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 nullid, short
16 from .node import nullid, short
17
17
18 from . import (
18 from . import (
19 encoding,
19 encoding,
20 error,
20 error,
21 formatter,
21 formatter,
22 match,
22 match,
23 pycompat,
23 pycompat,
24 registrar,
24 registrar,
25 scmutil,
25 scmutil,
26 simplemerge,
26 simplemerge,
27 tagmerge,
27 tagmerge,
28 templatekw,
28 templatekw,
29 templater,
29 templater,
30 util,
30 util,
31 )
31 )
32
32
33 def _toolstr(ui, tool, part, default=""):
33 def _toolstr(ui, tool, part, default=""):
34 return ui.config("merge-tools", tool + "." + part, default)
34 return ui.config("merge-tools", tool + "." + part, default)
35
35
36 def _toolbool(ui, tool, part, default=False):
36 def _toolbool(ui, tool, part, default=False):
37 return ui.configbool("merge-tools", tool + "." + part, default)
37 return ui.configbool("merge-tools", tool + "." + part, default)
38
38
39 def _toollist(ui, tool, part, default=None):
39 def _toollist(ui, tool, part, default=None):
40 if default is None:
40 if default is None:
41 default = []
41 default = []
42 return ui.configlist("merge-tools", tool + "." + part, default)
42 return ui.configlist("merge-tools", tool + "." + part, default)
43
43
44 internals = {}
44 internals = {}
45 # Merge tools to document.
45 # Merge tools to document.
46 internalsdoc = {}
46 internalsdoc = {}
47
47
48 internaltool = registrar.internalmerge()
48 internaltool = registrar.internalmerge()
49
49
50 # internal tool merge types
50 # internal tool merge types
51 nomerge = internaltool.nomerge
51 nomerge = internaltool.nomerge
52 mergeonly = internaltool.mergeonly # just the full merge, no premerge
52 mergeonly = internaltool.mergeonly # just the full merge, no premerge
53 fullmerge = internaltool.fullmerge # both premerge and merge
53 fullmerge = internaltool.fullmerge # both premerge and merge
54
54
55 _localchangedotherdeletedmsg = _(
55 _localchangedotherdeletedmsg = _(
56 "local%(l)s changed %(fd)s which other%(o)s deleted\n"
56 "local%(l)s changed %(fd)s which other%(o)s deleted\n"
57 "use (c)hanged version, (d)elete, or leave (u)nresolved?"
57 "use (c)hanged version, (d)elete, or leave (u)nresolved?"
58 "$$ &Changed $$ &Delete $$ &Unresolved")
58 "$$ &Changed $$ &Delete $$ &Unresolved")
59
59
60 _otherchangedlocaldeletedmsg = _(
60 _otherchangedlocaldeletedmsg = _(
61 "other%(o)s changed %(fd)s which local%(l)s deleted\n"
61 "other%(o)s changed %(fd)s which local%(l)s deleted\n"
62 "use (c)hanged version, leave (d)eleted, or "
62 "use (c)hanged version, leave (d)eleted, or "
63 "leave (u)nresolved?"
63 "leave (u)nresolved?"
64 "$$ &Changed $$ &Deleted $$ &Unresolved")
64 "$$ &Changed $$ &Deleted $$ &Unresolved")
65
65
66 class absentfilectx(object):
66 class absentfilectx(object):
67 """Represents a file that's ostensibly in a context but is actually not
67 """Represents a file that's ostensibly in a context but is actually not
68 present in it.
68 present in it.
69
69
70 This is here because it's very specific to the filemerge code for now --
70 This is here because it's very specific to the filemerge code for now --
71 other code is likely going to break with the values this returns."""
71 other code is likely going to break with the values this returns."""
72 def __init__(self, ctx, f):
72 def __init__(self, ctx, f):
73 self._ctx = ctx
73 self._ctx = ctx
74 self._f = f
74 self._f = f
75
75
76 def path(self):
76 def path(self):
77 return self._f
77 return self._f
78
78
79 def size(self):
79 def size(self):
80 return None
80 return None
81
81
82 def data(self):
82 def data(self):
83 return None
83 return None
84
84
85 def filenode(self):
85 def filenode(self):
86 return nullid
86 return nullid
87
87
88 _customcmp = True
88 _customcmp = True
89 def cmp(self, fctx):
89 def cmp(self, fctx):
90 """compare with other file context
90 """compare with other file context
91
91
92 returns True if different from fctx.
92 returns True if different from fctx.
93 """
93 """
94 return not (fctx.isabsent() and
94 return not (fctx.isabsent() and
95 fctx.ctx() == self.ctx() and
95 fctx.ctx() == self.ctx() and
96 fctx.path() == self.path())
96 fctx.path() == self.path())
97
97
98 def flags(self):
98 def flags(self):
99 return ''
99 return ''
100
100
101 def changectx(self):
101 def changectx(self):
102 return self._ctx
102 return self._ctx
103
103
104 def isbinary(self):
104 def isbinary(self):
105 return False
105 return False
106
106
107 def isabsent(self):
107 def isabsent(self):
108 return True
108 return True
109
109
110 def _findtool(ui, tool):
110 def _findtool(ui, tool):
111 if tool in internals:
111 if tool in internals:
112 return tool
112 return tool
113 return findexternaltool(ui, tool)
113 return findexternaltool(ui, tool)
114
114
115 def findexternaltool(ui, tool):
115 def findexternaltool(ui, tool):
116 for kn in ("regkey", "regkeyalt"):
116 for kn in ("regkey", "regkeyalt"):
117 k = _toolstr(ui, tool, kn)
117 k = _toolstr(ui, tool, kn)
118 if not k:
118 if not k:
119 continue
119 continue
120 p = util.lookupreg(k, _toolstr(ui, tool, "regname"))
120 p = util.lookupreg(k, _toolstr(ui, tool, "regname"))
121 if p:
121 if p:
122 p = util.findexe(p + _toolstr(ui, tool, "regappend"))
122 p = util.findexe(p + _toolstr(ui, tool, "regappend"))
123 if p:
123 if p:
124 return p
124 return p
125 exe = _toolstr(ui, tool, "executable", tool)
125 exe = _toolstr(ui, tool, "executable", tool)
126 return util.findexe(util.expandpath(exe))
126 return util.findexe(util.expandpath(exe))
127
127
128 def _picktool(repo, ui, path, binary, symlink, changedelete):
128 def _picktool(repo, ui, path, binary, symlink, changedelete):
129 def supportscd(tool):
129 def supportscd(tool):
130 return tool in internals and internals[tool].mergetype == nomerge
130 return tool in internals and internals[tool].mergetype == nomerge
131
131
132 def check(tool, pat, symlink, binary, changedelete):
132 def check(tool, pat, symlink, binary, changedelete):
133 tmsg = tool
133 tmsg = tool
134 if pat:
134 if pat:
135 tmsg = _("%s (for pattern %s)") % (tool, pat)
135 tmsg = _("%s (for pattern %s)") % (tool, pat)
136 if not _findtool(ui, tool):
136 if not _findtool(ui, tool):
137 if pat: # explicitly requested tool deserves a warning
137 if pat: # explicitly requested tool deserves a warning
138 ui.warn(_("couldn't find merge tool %s\n") % tmsg)
138 ui.warn(_("couldn't find merge tool %s\n") % tmsg)
139 else: # configured but non-existing tools are more silent
139 else: # configured but non-existing tools are more silent
140 ui.note(_("couldn't find merge tool %s\n") % tmsg)
140 ui.note(_("couldn't find merge tool %s\n") % tmsg)
141 elif symlink and not _toolbool(ui, tool, "symlink"):
141 elif symlink and not _toolbool(ui, tool, "symlink"):
142 ui.warn(_("tool %s can't handle symlinks\n") % tmsg)
142 ui.warn(_("tool %s can't handle symlinks\n") % tmsg)
143 elif binary and not _toolbool(ui, tool, "binary"):
143 elif binary and not _toolbool(ui, tool, "binary"):
144 ui.warn(_("tool %s can't handle binary\n") % tmsg)
144 ui.warn(_("tool %s can't handle binary\n") % tmsg)
145 elif changedelete and not supportscd(tool):
145 elif changedelete and not supportscd(tool):
146 # the nomerge tools are the only tools that support change/delete
146 # the nomerge tools are the only tools that support change/delete
147 # conflicts
147 # conflicts
148 pass
148 pass
149 elif not util.gui() and _toolbool(ui, tool, "gui"):
149 elif not util.gui() and _toolbool(ui, tool, "gui"):
150 ui.warn(_("tool %s requires a GUI\n") % tmsg)
150 ui.warn(_("tool %s requires a GUI\n") % tmsg)
151 else:
151 else:
152 return True
152 return True
153 return False
153 return False
154
154
155 # internal config: ui.forcemerge
155 # internal config: ui.forcemerge
156 # forcemerge comes from command line arguments, highest priority
156 # forcemerge comes from command line arguments, highest priority
157 force = ui.config('ui', 'forcemerge')
157 force = ui.config('ui', 'forcemerge')
158 if force:
158 if force:
159 toolpath = _findtool(ui, force)
159 toolpath = _findtool(ui, force)
160 if changedelete and not supportscd(toolpath):
160 if changedelete and not supportscd(toolpath):
161 return ":prompt", None
161 return ":prompt", None
162 else:
162 else:
163 if toolpath:
163 if toolpath:
164 return (force, util.shellquote(toolpath))
164 return (force, util.shellquote(toolpath))
165 else:
165 else:
166 # mimic HGMERGE if given tool not found
166 # mimic HGMERGE if given tool not found
167 return (force, force)
167 return (force, force)
168
168
169 # HGMERGE takes next precedence
169 # HGMERGE takes next precedence
170 hgmerge = encoding.environ.get("HGMERGE")
170 hgmerge = encoding.environ.get("HGMERGE")
171 if hgmerge:
171 if hgmerge:
172 if changedelete and not supportscd(hgmerge):
172 if changedelete and not supportscd(hgmerge):
173 return ":prompt", None
173 return ":prompt", None
174 else:
174 else:
175 return (hgmerge, hgmerge)
175 return (hgmerge, hgmerge)
176
176
177 # then patterns
177 # then patterns
178 for pat, tool in ui.configitems("merge-patterns"):
178 for pat, tool in ui.configitems("merge-patterns"):
179 mf = match.match(repo.root, '', [pat])
179 mf = match.match(repo.root, '', [pat])
180 if mf(path) and check(tool, pat, symlink, False, changedelete):
180 if mf(path) and check(tool, pat, symlink, False, changedelete):
181 toolpath = _findtool(ui, tool)
181 toolpath = _findtool(ui, tool)
182 return (tool, util.shellquote(toolpath))
182 return (tool, util.shellquote(toolpath))
183
183
184 # then merge tools
184 # then merge tools
185 tools = {}
185 tools = {}
186 disabled = set()
186 disabled = set()
187 for k, v in ui.configitems("merge-tools"):
187 for k, v in ui.configitems("merge-tools"):
188 t = k.split('.')[0]
188 t = k.split('.')[0]
189 if t not in tools:
189 if t not in tools:
190 tools[t] = int(_toolstr(ui, t, "priority", "0"))
190 tools[t] = int(_toolstr(ui, t, "priority", "0"))
191 if _toolbool(ui, t, "disabled", False):
191 if _toolbool(ui, t, "disabled", False):
192 disabled.add(t)
192 disabled.add(t)
193 names = tools.keys()
193 names = tools.keys()
194 tools = sorted([(-p, tool) for tool, p in tools.items()
194 tools = sorted([(-p, tool) for tool, p in tools.items()
195 if tool not in disabled])
195 if tool not in disabled])
196 uimerge = ui.config("ui", "merge")
196 uimerge = ui.config("ui", "merge")
197 if uimerge:
197 if uimerge:
198 # external tools defined in uimerge won't be able to handle
198 # external tools defined in uimerge won't be able to handle
199 # change/delete conflicts
199 # change/delete conflicts
200 if uimerge not in names and not changedelete:
200 if uimerge not in names and not changedelete:
201 return (uimerge, uimerge)
201 return (uimerge, uimerge)
202 tools.insert(0, (None, uimerge)) # highest priority
202 tools.insert(0, (None, uimerge)) # highest priority
203 tools.append((None, "hgmerge")) # the old default, if found
203 tools.append((None, "hgmerge")) # the old default, if found
204 for p, t in tools:
204 for p, t in tools:
205 if check(t, None, symlink, binary, changedelete):
205 if check(t, None, symlink, binary, changedelete):
206 toolpath = _findtool(ui, t)
206 toolpath = _findtool(ui, t)
207 return (t, util.shellquote(toolpath))
207 return (t, util.shellquote(toolpath))
208
208
209 # internal merge or prompt as last resort
209 # internal merge or prompt as last resort
210 if symlink or binary or changedelete:
210 if symlink or binary or changedelete:
211 if not changedelete and len(tools):
211 if not changedelete and len(tools):
212 # any tool is rejected by capability for symlink or binary
212 # any tool is rejected by capability for symlink or binary
213 ui.warn(_("no tool found to merge %s\n") % path)
213 ui.warn(_("no tool found to merge %s\n") % path)
214 return ":prompt", None
214 return ":prompt", None
215 return ":merge", None
215 return ":merge", None
216
216
217 def _eoltype(data):
217 def _eoltype(data):
218 "Guess the EOL type of a file"
218 "Guess the EOL type of a file"
219 if '\0' in data: # binary
219 if '\0' in data: # binary
220 return None
220 return None
221 if '\r\n' in data: # Windows
221 if '\r\n' in data: # Windows
222 return '\r\n'
222 return '\r\n'
223 if '\r' in data: # Old Mac
223 if '\r' in data: # Old Mac
224 return '\r'
224 return '\r'
225 if '\n' in data: # UNIX
225 if '\n' in data: # UNIX
226 return '\n'
226 return '\n'
227 return None # unknown
227 return None # unknown
228
228
229 def _matcheol(file, origfile):
229 def _matcheol(file, origfile):
230 "Convert EOL markers in a file to match origfile"
230 "Convert EOL markers in a file to match origfile"
231 tostyle = _eoltype(util.readfile(origfile))
231 tostyle = _eoltype(util.readfile(origfile))
232 if tostyle:
232 if tostyle:
233 data = util.readfile(file)
233 data = util.readfile(file)
234 style = _eoltype(data)
234 style = _eoltype(data)
235 if style:
235 if style:
236 newdata = data.replace(style, tostyle)
236 newdata = data.replace(style, tostyle)
237 if newdata != data:
237 if newdata != data:
238 util.writefile(file, newdata)
238 util.writefile(file, newdata)
239
239
240 @internaltool('prompt', nomerge)
240 @internaltool('prompt', nomerge)
241 def _iprompt(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
241 def _iprompt(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
242 """Asks the user which of the local `p1()` or the other `p2()` version to
242 """Asks the user which of the local `p1()` or the other `p2()` version to
243 keep as the merged version."""
243 keep as the merged version."""
244 ui = repo.ui
244 ui = repo.ui
245 fd = fcd.path()
245 fd = fcd.path()
246
246
247 prompts = partextras(labels)
247 prompts = partextras(labels)
248 prompts['fd'] = fd
248 prompts['fd'] = fd
249 try:
249 try:
250 if fco.isabsent():
250 if fco.isabsent():
251 index = ui.promptchoice(
251 index = ui.promptchoice(
252 _localchangedotherdeletedmsg % prompts, 2)
252 _localchangedotherdeletedmsg % prompts, 2)
253 choice = ['local', 'other', 'unresolved'][index]
253 choice = ['local', 'other', 'unresolved'][index]
254 elif fcd.isabsent():
254 elif fcd.isabsent():
255 index = ui.promptchoice(
255 index = ui.promptchoice(
256 _otherchangedlocaldeletedmsg % prompts, 2)
256 _otherchangedlocaldeletedmsg % prompts, 2)
257 choice = ['other', 'local', 'unresolved'][index]
257 choice = ['other', 'local', 'unresolved'][index]
258 else:
258 else:
259 index = ui.promptchoice(
259 index = ui.promptchoice(
260 _("keep (l)ocal%(l)s, take (o)ther%(o)s, or leave (u)nresolved"
260 _("keep (l)ocal%(l)s, take (o)ther%(o)s, or leave (u)nresolved"
261 " for %(fd)s?"
261 " for %(fd)s?"
262 "$$ &Local $$ &Other $$ &Unresolved") % prompts, 2)
262 "$$ &Local $$ &Other $$ &Unresolved") % prompts, 2)
263 choice = ['local', 'other', 'unresolved'][index]
263 choice = ['local', 'other', 'unresolved'][index]
264
264
265 if choice == 'other':
265 if choice == 'other':
266 return _iother(repo, mynode, orig, fcd, fco, fca, toolconf,
266 return _iother(repo, mynode, orig, fcd, fco, fca, toolconf,
267 labels)
267 labels)
268 elif choice == 'local':
268 elif choice == 'local':
269 return _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf,
269 return _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf,
270 labels)
270 labels)
271 elif choice == 'unresolved':
271 elif choice == 'unresolved':
272 return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf,
272 return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf,
273 labels)
273 labels)
274 except error.ResponseExpected:
274 except error.ResponseExpected:
275 ui.write("\n")
275 ui.write("\n")
276 return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf,
276 return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf,
277 labels)
277 labels)
278
278
279 @internaltool('local', nomerge)
279 @internaltool('local', nomerge)
280 def _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
280 def _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
281 """Uses the local `p1()` version of files as the merged version."""
281 """Uses the local `p1()` version of files as the merged version."""
282 return 0, fcd.isabsent()
282 return 0, fcd.isabsent()
283
283
284 @internaltool('other', nomerge)
284 @internaltool('other', nomerge)
285 def _iother(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
285 def _iother(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
286 """Uses the other `p2()` version of files as the merged version."""
286 """Uses the other `p2()` version of files as the merged version."""
287 if fco.isabsent():
287 if fco.isabsent():
288 # local changed, remote deleted -- 'deleted' picked
288 # local changed, remote deleted -- 'deleted' picked
289 _underlyingfctxifabsent(fcd).remove()
289 _underlyingfctxifabsent(fcd).remove()
290 deleted = True
290 deleted = True
291 else:
291 else:
292 _underlyingfctxifabsent(fcd).write(fco.data(), fco.flags())
292 _underlyingfctxifabsent(fcd).write(fco.data(), fco.flags())
293 deleted = False
293 deleted = False
294 return 0, deleted
294 return 0, deleted
295
295
296 @internaltool('fail', nomerge)
296 @internaltool('fail', nomerge)
297 def _ifail(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
297 def _ifail(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
298 """
298 """
299 Rather than attempting to merge files that were modified on both
299 Rather than attempting to merge files that were modified on both
300 branches, it marks them as unresolved. The resolve command must be
300 branches, it marks them as unresolved. The resolve command must be
301 used to resolve these conflicts."""
301 used to resolve these conflicts."""
302 # for change/delete conflicts write out the changed version, then fail
302 # for change/delete conflicts write out the changed version, then fail
303 if fcd.isabsent():
303 if fcd.isabsent():
304 _underlyingfctxifabsent(fcd).write(fco.data(), fco.flags())
304 _underlyingfctxifabsent(fcd).write(fco.data(), fco.flags())
305 return 1, False
305 return 1, False
306
306
307 def _underlyingfctxifabsent(filectx):
307 def _underlyingfctxifabsent(filectx):
308 """Sometimes when resolving, our fcd is actually an absentfilectx, but
308 """Sometimes when resolving, our fcd is actually an absentfilectx, but
309 we want to write to it (to do the resolve). This helper returns the
309 we want to write to it (to do the resolve). This helper returns the
310 underyling workingfilectx in that case.
310 underyling workingfilectx in that case.
311 """
311 """
312 if filectx.isabsent():
312 if filectx.isabsent():
313 return filectx.changectx()[filectx.path()]
313 return filectx.changectx()[filectx.path()]
314 else:
314 else:
315 return filectx
315 return filectx
316
316
317 def _premerge(repo, fcd, fco, fca, toolconf, files, labels=None):
317 def _premerge(repo, fcd, fco, fca, toolconf, files, labels=None):
318 tool, toolpath, binary, symlink = toolconf
318 tool, toolpath, binary, symlink = toolconf
319 if symlink or fcd.isabsent() or fco.isabsent():
319 if symlink or fcd.isabsent() or fco.isabsent():
320 return 1
320 return 1
321 a, b, c, back = files
321 unused, unused, unused, back = files
322
322
323 ui = repo.ui
323 ui = repo.ui
324
324
325 validkeep = ['keep', 'keep-merge3']
325 validkeep = ['keep', 'keep-merge3']
326
326
327 # do we attempt to simplemerge first?
327 # do we attempt to simplemerge first?
328 try:
328 try:
329 premerge = _toolbool(ui, tool, "premerge", not binary)
329 premerge = _toolbool(ui, tool, "premerge", not binary)
330 except error.ConfigError:
330 except error.ConfigError:
331 premerge = _toolstr(ui, tool, "premerge").lower()
331 premerge = _toolstr(ui, tool, "premerge").lower()
332 if premerge not in validkeep:
332 if premerge not in validkeep:
333 _valid = ', '.join(["'" + v + "'" for v in validkeep])
333 _valid = ', '.join(["'" + v + "'" for v in validkeep])
334 raise error.ConfigError(_("%s.premerge not valid "
334 raise error.ConfigError(_("%s.premerge not valid "
335 "('%s' is neither boolean nor %s)") %
335 "('%s' is neither boolean nor %s)") %
336 (tool, premerge, _valid))
336 (tool, premerge, _valid))
337
337
338 if premerge:
338 if premerge:
339 if premerge == 'keep-merge3':
339 if premerge == 'keep-merge3':
340 if not labels:
340 if not labels:
341 labels = _defaultconflictlabels
341 labels = _defaultconflictlabels
342 if len(labels) < 3:
342 if len(labels) < 3:
343 labels.append('base')
343 labels.append('base')
344 r = simplemerge.simplemerge(ui, fcd, fca, fco,
344 r = simplemerge.simplemerge(ui, fcd, fca, fco,
345 quiet=True, label=labels, repo=repo)
345 quiet=True, label=labels, repo=repo)
346 if not r:
346 if not r:
347 ui.debug(" premerge successful\n")
347 ui.debug(" premerge successful\n")
348 return 0
348 return 0
349 if premerge not in validkeep:
349 if premerge not in validkeep:
350 util.copyfile(back, a) # restore from backup and try again
350 # restore from backup and try again
351 util.copyfile(back, repo.wjoin(fcd.path()))
351 return 1 # continue merging
352 return 1 # continue merging
352
353
353 def _mergecheck(repo, mynode, orig, fcd, fco, fca, toolconf):
354 def _mergecheck(repo, mynode, orig, fcd, fco, fca, toolconf):
354 tool, toolpath, binary, symlink = toolconf
355 tool, toolpath, binary, symlink = toolconf
355 if symlink:
356 if symlink:
356 repo.ui.warn(_('warning: internal %s cannot merge symlinks '
357 repo.ui.warn(_('warning: internal %s cannot merge symlinks '
357 'for %s\n') % (tool, fcd.path()))
358 'for %s\n') % (tool, fcd.path()))
358 return False
359 return False
359 if fcd.isabsent() or fco.isabsent():
360 if fcd.isabsent() or fco.isabsent():
360 repo.ui.warn(_('warning: internal %s cannot merge change/delete '
361 repo.ui.warn(_('warning: internal %s cannot merge change/delete '
361 'conflict for %s\n') % (tool, fcd.path()))
362 'conflict for %s\n') % (tool, fcd.path()))
362 return False
363 return False
363 return True
364 return True
364
365
365 def _merge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels, mode):
366 def _merge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels, mode):
366 """
367 """
367 Uses the internal non-interactive simple merge algorithm for merging
368 Uses the internal non-interactive simple merge algorithm for merging
368 files. It will fail if there are any conflicts and leave markers in
369 files. It will fail if there are any conflicts and leave markers in
369 the partially merged file. Markers will have two sections, one for each side
370 the partially merged file. Markers will have two sections, one for each side
370 of merge, unless mode equals 'union' which suppresses the markers."""
371 of merge, unless mode equals 'union' which suppresses the markers."""
371 a, b, c, back = files
372
373 ui = repo.ui
372 ui = repo.ui
374
373
375 r = simplemerge.simplemerge(ui, fcd, fca, fco,
374 r = simplemerge.simplemerge(ui, fcd, fca, fco,
376 label=labels, mode=mode, repo=repo)
375 label=labels, mode=mode, repo=repo)
377 return True, r, False
376 return True, r, False
378
377
379 @internaltool('union', fullmerge,
378 @internaltool('union', fullmerge,
380 _("warning: conflicts while merging %s! "
379 _("warning: conflicts while merging %s! "
381 "(edit, then use 'hg resolve --mark')\n"),
380 "(edit, then use 'hg resolve --mark')\n"),
382 precheck=_mergecheck)
381 precheck=_mergecheck)
383 def _iunion(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
382 def _iunion(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
384 """
383 """
385 Uses the internal non-interactive simple merge algorithm for merging
384 Uses the internal non-interactive simple merge algorithm for merging
386 files. It will use both left and right sides for conflict regions.
385 files. It will use both left and right sides for conflict regions.
387 No markers are inserted."""
386 No markers are inserted."""
388 return _merge(repo, mynode, orig, fcd, fco, fca, toolconf,
387 return _merge(repo, mynode, orig, fcd, fco, fca, toolconf,
389 files, labels, 'union')
388 files, labels, 'union')
390
389
391 @internaltool('merge', fullmerge,
390 @internaltool('merge', fullmerge,
392 _("warning: conflicts while merging %s! "
391 _("warning: conflicts while merging %s! "
393 "(edit, then use 'hg resolve --mark')\n"),
392 "(edit, then use 'hg resolve --mark')\n"),
394 precheck=_mergecheck)
393 precheck=_mergecheck)
395 def _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
394 def _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
396 """
395 """
397 Uses the internal non-interactive simple merge algorithm for merging
396 Uses the internal non-interactive simple merge algorithm for merging
398 files. It will fail if there are any conflicts and leave markers in
397 files. It will fail if there are any conflicts and leave markers in
399 the partially merged file. Markers will have two sections, one for each side
398 the partially merged file. Markers will have two sections, one for each side
400 of merge."""
399 of merge."""
401 return _merge(repo, mynode, orig, fcd, fco, fca, toolconf,
400 return _merge(repo, mynode, orig, fcd, fco, fca, toolconf,
402 files, labels, 'merge')
401 files, labels, 'merge')
403
402
404 @internaltool('merge3', fullmerge,
403 @internaltool('merge3', fullmerge,
405 _("warning: conflicts while merging %s! "
404 _("warning: conflicts while merging %s! "
406 "(edit, then use 'hg resolve --mark')\n"),
405 "(edit, then use 'hg resolve --mark')\n"),
407 precheck=_mergecheck)
406 precheck=_mergecheck)
408 def _imerge3(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
407 def _imerge3(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
409 """
408 """
410 Uses the internal non-interactive simple merge algorithm for merging
409 Uses the internal non-interactive simple merge algorithm for merging
411 files. It will fail if there are any conflicts and leave markers in
410 files. It will fail if there are any conflicts and leave markers in
412 the partially merged file. Marker will have three sections, one from each
411 the partially merged file. Marker will have three sections, one from each
413 side of the merge and one for the base content."""
412 side of the merge and one for the base content."""
414 if not labels:
413 if not labels:
415 labels = _defaultconflictlabels
414 labels = _defaultconflictlabels
416 if len(labels) < 3:
415 if len(labels) < 3:
417 labels.append('base')
416 labels.append('base')
418 return _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels)
417 return _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels)
419
418
420 def _imergeauto(repo, mynode, orig, fcd, fco, fca, toolconf, files,
419 def _imergeauto(repo, mynode, orig, fcd, fco, fca, toolconf, files,
421 labels=None, localorother=None):
420 labels=None, localorother=None):
422 """
421 """
423 Generic driver for _imergelocal and _imergeother
422 Generic driver for _imergelocal and _imergeother
424 """
423 """
425 assert localorother is not None
424 assert localorother is not None
426 tool, toolpath, binary, symlink = toolconf
425 tool, toolpath, binary, symlink = toolconf
427 a, b, c, back = files
428 r = simplemerge.simplemerge(repo.ui, fcd, fca, fco,
426 r = simplemerge.simplemerge(repo.ui, fcd, fca, fco,
429 label=labels, localorother=localorother,
427 label=labels, localorother=localorother,
430 repo=repo)
428 repo=repo)
431 return True, r
429 return True, r
432
430
433 @internaltool('merge-local', mergeonly, precheck=_mergecheck)
431 @internaltool('merge-local', mergeonly, precheck=_mergecheck)
434 def _imergelocal(*args, **kwargs):
432 def _imergelocal(*args, **kwargs):
435 """
433 """
436 Like :merge, but resolve all conflicts non-interactively in favor
434 Like :merge, but resolve all conflicts non-interactively in favor
437 of the local `p1()` changes."""
435 of the local `p1()` changes."""
438 success, status = _imergeauto(localorother='local', *args, **kwargs)
436 success, status = _imergeauto(localorother='local', *args, **kwargs)
439 return success, status, False
437 return success, status, False
440
438
441 @internaltool('merge-other', mergeonly, precheck=_mergecheck)
439 @internaltool('merge-other', mergeonly, precheck=_mergecheck)
442 def _imergeother(*args, **kwargs):
440 def _imergeother(*args, **kwargs):
443 """
441 """
444 Like :merge, but resolve all conflicts non-interactively in favor
442 Like :merge, but resolve all conflicts non-interactively in favor
445 of the other `p2()` changes."""
443 of the other `p2()` changes."""
446 success, status = _imergeauto(localorother='other', *args, **kwargs)
444 success, status = _imergeauto(localorother='other', *args, **kwargs)
447 return success, status, False
445 return success, status, False
448
446
449 @internaltool('tagmerge', mergeonly,
447 @internaltool('tagmerge', mergeonly,
450 _("automatic tag merging of %s failed! "
448 _("automatic tag merging of %s failed! "
451 "(use 'hg resolve --tool :merge' or another merge "
449 "(use 'hg resolve --tool :merge' or another merge "
452 "tool of your choice)\n"))
450 "tool of your choice)\n"))
453 def _itagmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
451 def _itagmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
454 """
452 """
455 Uses the internal tag merge algorithm (experimental).
453 Uses the internal tag merge algorithm (experimental).
456 """
454 """
457 success, status = tagmerge.merge(repo, fcd, fco, fca)
455 success, status = tagmerge.merge(repo, fcd, fco, fca)
458 return success, status, False
456 return success, status, False
459
457
460 @internaltool('dump', fullmerge)
458 @internaltool('dump', fullmerge)
461 def _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
459 def _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
462 """
460 """
463 Creates three versions of the files to merge, containing the
461 Creates three versions of the files to merge, containing the
464 contents of local, other and base. These files can then be used to
462 contents of local, other and base. These files can then be used to
465 perform a merge manually. If the file to be merged is named
463 perform a merge manually. If the file to be merged is named
466 ``a.txt``, these files will accordingly be named ``a.txt.local``,
464 ``a.txt``, these files will accordingly be named ``a.txt.local``,
467 ``a.txt.other`` and ``a.txt.base`` and they will be placed in the
465 ``a.txt.other`` and ``a.txt.base`` and they will be placed in the
468 same directory as ``a.txt``.
466 same directory as ``a.txt``.
469
467
470 This implies permerge. Therefore, files aren't dumped, if premerge
468 This implies permerge. Therefore, files aren't dumped, if premerge
471 runs successfully. Use :forcedump to forcibly write files out.
469 runs successfully. Use :forcedump to forcibly write files out.
472 """
470 """
473 a, b, c, back = files
471 a, unused, unused, unused = files
474
472
475 fd = fcd.path()
473 fd = fcd.path()
476
474
477 util.copyfile(a, a + ".local")
475 util.copyfile(a, a + ".local")
478 repo.wwrite(fd + ".other", fco.data(), fco.flags())
476 repo.wwrite(fd + ".other", fco.data(), fco.flags())
479 repo.wwrite(fd + ".base", fca.data(), fca.flags())
477 repo.wwrite(fd + ".base", fca.data(), fca.flags())
480 return False, 1, False
478 return False, 1, False
481
479
482 @internaltool('forcedump', mergeonly)
480 @internaltool('forcedump', mergeonly)
483 def _forcedump(repo, mynode, orig, fcd, fco, fca, toolconf, files,
481 def _forcedump(repo, mynode, orig, fcd, fco, fca, toolconf, files,
484 labels=None):
482 labels=None):
485 """
483 """
486 Creates three versions of the files as same as :dump, but omits premerge.
484 Creates three versions of the files as same as :dump, but omits premerge.
487 """
485 """
488 return _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files,
486 return _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files,
489 labels=labels)
487 labels=labels)
490
488
491 def _xmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
489 def _xmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
492 tool, toolpath, binary, symlink = toolconf
490 tool, toolpath, binary, symlink = toolconf
493 if fcd.isabsent() or fco.isabsent():
491 if fcd.isabsent() or fco.isabsent():
494 repo.ui.warn(_('warning: %s cannot merge change/delete conflict '
492 repo.ui.warn(_('warning: %s cannot merge change/delete conflict '
495 'for %s\n') % (tool, fcd.path()))
493 'for %s\n') % (tool, fcd.path()))
496 return False, 1, None
494 return False, 1, None
497 a, b, c, back = files
495 a, b, c, back = files
498 out = ""
496 out = ""
499 env = {'HG_FILE': fcd.path(),
497 env = {'HG_FILE': fcd.path(),
500 'HG_MY_NODE': short(mynode),
498 'HG_MY_NODE': short(mynode),
501 'HG_OTHER_NODE': str(fco.changectx()),
499 'HG_OTHER_NODE': str(fco.changectx()),
502 'HG_BASE_NODE': str(fca.changectx()),
500 'HG_BASE_NODE': str(fca.changectx()),
503 'HG_MY_ISLINK': 'l' in fcd.flags(),
501 'HG_MY_ISLINK': 'l' in fcd.flags(),
504 'HG_OTHER_ISLINK': 'l' in fco.flags(),
502 'HG_OTHER_ISLINK': 'l' in fco.flags(),
505 'HG_BASE_ISLINK': 'l' in fca.flags(),
503 'HG_BASE_ISLINK': 'l' in fca.flags(),
506 }
504 }
507
505
508 ui = repo.ui
506 ui = repo.ui
509
507
510 args = _toolstr(ui, tool, "args", '$local $base $other')
508 args = _toolstr(ui, tool, "args", '$local $base $other')
511 if "$output" in args:
509 if "$output" in args:
512 out, a = a, back # read input from backup, write to original
510 out, a = a, back # read input from backup, write to original
513 replace = {'local': a, 'base': b, 'other': c, 'output': out}
511 replace = {'local': a, 'base': b, 'other': c, 'output': out}
514 args = util.interpolate(r'\$', replace, args,
512 args = util.interpolate(r'\$', replace, args,
515 lambda s: util.shellquote(util.localpath(s)))
513 lambda s: util.shellquote(util.localpath(s)))
516 cmd = toolpath + ' ' + args
514 cmd = toolpath + ' ' + args
517 if _toolbool(ui, tool, "gui"):
515 if _toolbool(ui, tool, "gui"):
518 repo.ui.status(_('running merge tool %s for file %s\n') %
516 repo.ui.status(_('running merge tool %s for file %s\n') %
519 (tool, fcd.path()))
517 (tool, fcd.path()))
520 repo.ui.debug('launching merge tool: %s\n' % cmd)
518 repo.ui.debug('launching merge tool: %s\n' % cmd)
521 r = ui.system(cmd, cwd=repo.root, environ=env, blockedtag='mergetool')
519 r = ui.system(cmd, cwd=repo.root, environ=env, blockedtag='mergetool')
522 repo.ui.debug('merge tool returned: %s\n' % r)
520 repo.ui.debug('merge tool returned: %s\n' % r)
523 return True, r, False
521 return True, r, False
524
522
525 def _formatconflictmarker(repo, ctx, template, label, pad):
523 def _formatconflictmarker(repo, ctx, template, label, pad):
526 """Applies the given template to the ctx, prefixed by the label.
524 """Applies the given template to the ctx, prefixed by the label.
527
525
528 Pad is the minimum width of the label prefix, so that multiple markers
526 Pad is the minimum width of the label prefix, so that multiple markers
529 can have aligned templated parts.
527 can have aligned templated parts.
530 """
528 """
531 if ctx.node() is None:
529 if ctx.node() is None:
532 ctx = ctx.p1()
530 ctx = ctx.p1()
533
531
534 props = templatekw.keywords.copy()
532 props = templatekw.keywords.copy()
535 props['templ'] = template
533 props['templ'] = template
536 props['ctx'] = ctx
534 props['ctx'] = ctx
537 props['repo'] = repo
535 props['repo'] = repo
538 templateresult = template.render(props)
536 templateresult = template.render(props)
539
537
540 label = ('%s:' % label).ljust(pad + 1)
538 label = ('%s:' % label).ljust(pad + 1)
541 mark = '%s %s' % (label, templateresult)
539 mark = '%s %s' % (label, templateresult)
542
540
543 if mark:
541 if mark:
544 mark = mark.splitlines()[0] # split for safety
542 mark = mark.splitlines()[0] # split for safety
545
543
546 # 8 for the prefix of conflict marker lines (e.g. '<<<<<<< ')
544 # 8 for the prefix of conflict marker lines (e.g. '<<<<<<< ')
547 return util.ellipsis(mark, 80 - 8)
545 return util.ellipsis(mark, 80 - 8)
548
546
549 _defaultconflictlabels = ['local', 'other']
547 _defaultconflictlabels = ['local', 'other']
550
548
551 def _formatlabels(repo, fcd, fco, fca, labels):
549 def _formatlabels(repo, fcd, fco, fca, labels):
552 """Formats the given labels using the conflict marker template.
550 """Formats the given labels using the conflict marker template.
553
551
554 Returns a list of formatted labels.
552 Returns a list of formatted labels.
555 """
553 """
556 cd = fcd.changectx()
554 cd = fcd.changectx()
557 co = fco.changectx()
555 co = fco.changectx()
558 ca = fca.changectx()
556 ca = fca.changectx()
559
557
560 ui = repo.ui
558 ui = repo.ui
561 template = ui.config('ui', 'mergemarkertemplate')
559 template = ui.config('ui', 'mergemarkertemplate')
562 template = templater.unquotestring(template)
560 template = templater.unquotestring(template)
563 tmpl = formatter.maketemplater(ui, template)
561 tmpl = formatter.maketemplater(ui, template)
564
562
565 pad = max(len(l) for l in labels)
563 pad = max(len(l) for l in labels)
566
564
567 newlabels = [_formatconflictmarker(repo, cd, tmpl, labels[0], pad),
565 newlabels = [_formatconflictmarker(repo, cd, tmpl, labels[0], pad),
568 _formatconflictmarker(repo, co, tmpl, labels[1], pad)]
566 _formatconflictmarker(repo, co, tmpl, labels[1], pad)]
569 if len(labels) > 2:
567 if len(labels) > 2:
570 newlabels.append(_formatconflictmarker(repo, ca, tmpl, labels[2], pad))
568 newlabels.append(_formatconflictmarker(repo, ca, tmpl, labels[2], pad))
571 return newlabels
569 return newlabels
572
570
573 def partextras(labels):
571 def partextras(labels):
574 """Return a dictionary of extra labels for use in prompts to the user
572 """Return a dictionary of extra labels for use in prompts to the user
575
573
576 Intended use is in strings of the form "(l)ocal%(l)s".
574 Intended use is in strings of the form "(l)ocal%(l)s".
577 """
575 """
578 if labels is None:
576 if labels is None:
579 return {
577 return {
580 "l": "",
578 "l": "",
581 "o": "",
579 "o": "",
582 }
580 }
583
581
584 return {
582 return {
585 "l": " [%s]" % labels[0],
583 "l": " [%s]" % labels[0],
586 "o": " [%s]" % labels[1],
584 "o": " [%s]" % labels[1],
587 }
585 }
588
586
589 def _makebackup(repo, ui, fcd, premerge):
587 def _makebackup(repo, ui, fcd, premerge):
590 """Makes a backup of the local `fcd` file prior to merging.
588 """Makes a backup of the local `fcd` file prior to merging.
591
589
592 In addition to preserving the user's pre-existing modifications to `fcd`
590 In addition to preserving the user's pre-existing modifications to `fcd`
593 (if any), the backup is used to undo certain premerges, confirm whether a
591 (if any), the backup is used to undo certain premerges, confirm whether a
594 merge changed anything, and determine what line endings the new file should
592 merge changed anything, and determine what line endings the new file should
595 have.
593 have.
596 """
594 """
597 if fcd.isabsent():
595 if fcd.isabsent():
598 return None
596 return None
599
597
600 a = repo.wjoin(fcd.path())
598 a = repo.wjoin(fcd.path())
601 back = scmutil.origpath(ui, repo, a)
599 back = scmutil.origpath(ui, repo, a)
602 if premerge:
600 if premerge:
603 util.copyfile(a, back)
601 util.copyfile(a, back)
604 return back
602 return back
605
603
606 def _maketempfiles(repo, fcd, fco, fca):
604 def _maketempfiles(repo, fcd, fco, fca):
607 """Writes out `fco` and `fca` as temporary files, so an external merge
605 """Writes out `fco` and `fca` as temporary files, so an external merge
608 tool may use them.
606 tool may use them.
609
607
610 `fcd` is returned as-is, by convention, because it currently doubles as both
608 `fcd` is returned as-is, by convention, because it currently doubles as both
611 the local version and merge destination.
609 the local version and merge destination.
612 """
610 """
613 def temp(prefix, ctx):
611 def temp(prefix, ctx):
614 fullbase, ext = os.path.splitext(ctx.path())
612 fullbase, ext = os.path.splitext(ctx.path())
615 pre = "%s~%s." % (os.path.basename(fullbase), prefix)
613 pre = "%s~%s." % (os.path.basename(fullbase), prefix)
616 (fd, name) = tempfile.mkstemp(prefix=pre, suffix=ext)
614 (fd, name) = tempfile.mkstemp(prefix=pre, suffix=ext)
617 data = repo.wwritedata(ctx.path(), ctx.data())
615 data = repo.wwritedata(ctx.path(), ctx.data())
618 f = os.fdopen(fd, pycompat.sysstr("wb"))
616 f = os.fdopen(fd, pycompat.sysstr("wb"))
619 f.write(data)
617 f.write(data)
620 f.close()
618 f.close()
621 return name
619 return name
622
620
623 a = repo.wjoin(fcd.path())
621 a = repo.wjoin(fcd.path())
624 b = temp("base", fca)
622 b = temp("base", fca)
625 c = temp("other", fco)
623 c = temp("other", fco)
626
624
627 return a, b, c
625 return a, b, c
628
626
629 def _filemerge(premerge, repo, mynode, orig, fcd, fco, fca, labels=None):
627 def _filemerge(premerge, repo, mynode, orig, fcd, fco, fca, labels=None):
630 """perform a 3-way merge in the working directory
628 """perform a 3-way merge in the working directory
631
629
632 premerge = whether this is a premerge
630 premerge = whether this is a premerge
633 mynode = parent node before merge
631 mynode = parent node before merge
634 orig = original local filename before merge
632 orig = original local filename before merge
635 fco = other file context
633 fco = other file context
636 fca = ancestor file context
634 fca = ancestor file context
637 fcd = local file context for current/destination file
635 fcd = local file context for current/destination file
638
636
639 Returns whether the merge is complete, the return value of the merge, and
637 Returns whether the merge is complete, the return value of the merge, and
640 a boolean indicating whether the file was deleted from disk."""
638 a boolean indicating whether the file was deleted from disk."""
641
639
642 if not fco.cmp(fcd): # files identical?
640 if not fco.cmp(fcd): # files identical?
643 return True, None, False
641 return True, None, False
644
642
645 ui = repo.ui
643 ui = repo.ui
646 fd = fcd.path()
644 fd = fcd.path()
647 binary = fcd.isbinary() or fco.isbinary() or fca.isbinary()
645 binary = fcd.isbinary() or fco.isbinary() or fca.isbinary()
648 symlink = 'l' in fcd.flags() + fco.flags()
646 symlink = 'l' in fcd.flags() + fco.flags()
649 changedelete = fcd.isabsent() or fco.isabsent()
647 changedelete = fcd.isabsent() or fco.isabsent()
650 tool, toolpath = _picktool(repo, ui, fd, binary, symlink, changedelete)
648 tool, toolpath = _picktool(repo, ui, fd, binary, symlink, changedelete)
651 if tool in internals and tool.startswith('internal:'):
649 if tool in internals and tool.startswith('internal:'):
652 # normalize to new-style names (':merge' etc)
650 # normalize to new-style names (':merge' etc)
653 tool = tool[len('internal'):]
651 tool = tool[len('internal'):]
654 ui.debug("picked tool '%s' for %s (binary %s symlink %s changedelete %s)\n"
652 ui.debug("picked tool '%s' for %s (binary %s symlink %s changedelete %s)\n"
655 % (tool, fd, pycompat.bytestr(binary), pycompat.bytestr(symlink),
653 % (tool, fd, pycompat.bytestr(binary), pycompat.bytestr(symlink),
656 pycompat.bytestr(changedelete)))
654 pycompat.bytestr(changedelete)))
657
655
658 if tool in internals:
656 if tool in internals:
659 func = internals[tool]
657 func = internals[tool]
660 mergetype = func.mergetype
658 mergetype = func.mergetype
661 onfailure = func.onfailure
659 onfailure = func.onfailure
662 precheck = func.precheck
660 precheck = func.precheck
663 else:
661 else:
664 func = _xmerge
662 func = _xmerge
665 mergetype = fullmerge
663 mergetype = fullmerge
666 onfailure = _("merging %s failed!\n")
664 onfailure = _("merging %s failed!\n")
667 precheck = None
665 precheck = None
668
666
669 toolconf = tool, toolpath, binary, symlink
667 toolconf = tool, toolpath, binary, symlink
670
668
671 if mergetype == nomerge:
669 if mergetype == nomerge:
672 r, deleted = func(repo, mynode, orig, fcd, fco, fca, toolconf, labels)
670 r, deleted = func(repo, mynode, orig, fcd, fco, fca, toolconf, labels)
673 return True, r, deleted
671 return True, r, deleted
674
672
675 if premerge:
673 if premerge:
676 if orig != fco.path():
674 if orig != fco.path():
677 ui.status(_("merging %s and %s to %s\n") % (orig, fco.path(), fd))
675 ui.status(_("merging %s and %s to %s\n") % (orig, fco.path(), fd))
678 else:
676 else:
679 ui.status(_("merging %s\n") % fd)
677 ui.status(_("merging %s\n") % fd)
680
678
681 ui.debug("my %s other %s ancestor %s\n" % (fcd, fco, fca))
679 ui.debug("my %s other %s ancestor %s\n" % (fcd, fco, fca))
682
680
683 if precheck and not precheck(repo, mynode, orig, fcd, fco, fca,
681 if precheck and not precheck(repo, mynode, orig, fcd, fco, fca,
684 toolconf):
682 toolconf):
685 if onfailure:
683 if onfailure:
686 ui.warn(onfailure % fd)
684 ui.warn(onfailure % fd)
687 return True, 1, False
685 return True, 1, False
688
686
689 back = _makebackup(repo, ui, fcd, premerge)
687 back = _makebackup(repo, ui, fcd, premerge)
690 files = _maketempfiles(repo, fcd, fco, fca) + (back,)
688 files = _maketempfiles(repo, fcd, fco, fca) + (back,)
691 r = 1
689 r = 1
692 try:
690 try:
693 markerstyle = ui.config('ui', 'mergemarkers')
691 markerstyle = ui.config('ui', 'mergemarkers')
694 if not labels:
692 if not labels:
695 labels = _defaultconflictlabels
693 labels = _defaultconflictlabels
696 if markerstyle != 'basic':
694 if markerstyle != 'basic':
697 labels = _formatlabels(repo, fcd, fco, fca, labels)
695 labels = _formatlabels(repo, fcd, fco, fca, labels)
698
696
699 if premerge and mergetype == fullmerge:
697 if premerge and mergetype == fullmerge:
700 r = _premerge(repo, fcd, fco, fca, toolconf, files, labels=labels)
698 r = _premerge(repo, fcd, fco, fca, toolconf, files, labels=labels)
701 # complete if premerge successful (r is 0)
699 # complete if premerge successful (r is 0)
702 return not r, r, False
700 return not r, r, False
703
701
704 needcheck, r, deleted = func(repo, mynode, orig, fcd, fco, fca,
702 needcheck, r, deleted = func(repo, mynode, orig, fcd, fco, fca,
705 toolconf, files, labels=labels)
703 toolconf, files, labels=labels)
706
704
707 if needcheck:
705 if needcheck:
708 r = _check(r, ui, tool, fcd, files)
706 r = _check(r, ui, tool, fcd, files)
709
707
710 if r:
708 if r:
711 if onfailure:
709 if onfailure:
712 ui.warn(onfailure % fd)
710 ui.warn(onfailure % fd)
713
711
714 return True, r, deleted
712 return True, r, deleted
715 finally:
713 finally:
716 if not r and back is not None:
714 if not r and back is not None:
717 util.unlink(back)
715 util.unlink(back)
718 util.unlink(files[1])
716 util.unlink(files[1])
719 util.unlink(files[2])
717 util.unlink(files[2])
720
718
721 def _check(r, ui, tool, fcd, files):
719 def _check(r, ui, tool, fcd, files):
722 fd = fcd.path()
720 fd = fcd.path()
723 a, b, c, back = files
721 a, unused, unused, back = files
724
722
725 if not r and (_toolbool(ui, tool, "checkconflicts") or
723 if not r and (_toolbool(ui, tool, "checkconflicts") or
726 'conflicts' in _toollist(ui, tool, "check")):
724 'conflicts' in _toollist(ui, tool, "check")):
727 if re.search("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcd.data(),
725 if re.search("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcd.data(),
728 re.MULTILINE):
726 re.MULTILINE):
729 r = 1
727 r = 1
730
728
731 checked = False
729 checked = False
732 if 'prompt' in _toollist(ui, tool, "check"):
730 if 'prompt' in _toollist(ui, tool, "check"):
733 checked = True
731 checked = True
734 if ui.promptchoice(_("was merge of '%s' successful (yn)?"
732 if ui.promptchoice(_("was merge of '%s' successful (yn)?"
735 "$$ &Yes $$ &No") % fd, 1):
733 "$$ &Yes $$ &No") % fd, 1):
736 r = 1
734 r = 1
737
735
738 if not r and not checked and (_toolbool(ui, tool, "checkchanged") or
736 if not r and not checked and (_toolbool(ui, tool, "checkchanged") or
739 'changed' in
737 'changed' in
740 _toollist(ui, tool, "check")):
738 _toollist(ui, tool, "check")):
741 if back is not None and filecmp.cmp(a, back):
739 if back is not None and filecmp.cmp(a, back):
742 if ui.promptchoice(_(" output file %s appears unchanged\n"
740 if ui.promptchoice(_(" output file %s appears unchanged\n"
743 "was merge successful (yn)?"
741 "was merge successful (yn)?"
744 "$$ &Yes $$ &No") % fd, 1):
742 "$$ &Yes $$ &No") % fd, 1):
745 r = 1
743 r = 1
746
744
747 if back is not None and _toolbool(ui, tool, "fixeol"):
745 if back is not None and _toolbool(ui, tool, "fixeol"):
748 _matcheol(a, back)
746 _matcheol(a, back)
749
747
750 return r
748 return r
751
749
752 def premerge(repo, mynode, orig, fcd, fco, fca, labels=None):
750 def premerge(repo, mynode, orig, fcd, fco, fca, labels=None):
753 return _filemerge(True, repo, mynode, orig, fcd, fco, fca, labels=labels)
751 return _filemerge(True, repo, mynode, orig, fcd, fco, fca, labels=labels)
754
752
755 def filemerge(repo, mynode, orig, fcd, fco, fca, labels=None):
753 def filemerge(repo, mynode, orig, fcd, fco, fca, labels=None):
756 return _filemerge(False, repo, mynode, orig, fcd, fco, fca, labels=labels)
754 return _filemerge(False, repo, mynode, orig, fcd, fco, fca, labels=labels)
757
755
758 def loadinternalmerge(ui, extname, registrarobj):
756 def loadinternalmerge(ui, extname, registrarobj):
759 """Load internal merge tool from specified registrarobj
757 """Load internal merge tool from specified registrarobj
760 """
758 """
761 for name, func in registrarobj._table.iteritems():
759 for name, func in registrarobj._table.iteritems():
762 fullname = ':' + name
760 fullname = ':' + name
763 internals[fullname] = func
761 internals[fullname] = func
764 internals['internal:' + name] = func
762 internals['internal:' + name] = func
765 internalsdoc[fullname] = func
763 internalsdoc[fullname] = func
766
764
767 # load built-in merge tools explicitly to setup internalsdoc
765 # load built-in merge tools explicitly to setup internalsdoc
768 loadinternalmerge(None, None, internaltool)
766 loadinternalmerge(None, None, internaltool)
769
767
770 # tell hggettext to extract docstrings from these functions:
768 # tell hggettext to extract docstrings from these functions:
771 i18nfunctions = internals.values()
769 i18nfunctions = internals.values()
General Comments 0
You need to be logged in to leave comments. Login now