##// END OF EJS Templates
filemerge: add `_workingpath`...
Phil Cohen -
r34036:7558917f default
parent child Browse files
Show More
@@ -1,771 +1,774
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 unused, unused, unused, 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 # restore from backup and try again
350 # restore from backup and try again
351 # TODO: Add a workingfilectx.write(otherfilectx) path so we can use
351 # TODO: Add a workingfilectx.write(otherfilectx) path so we can use
352 # util.copy here instead.
352 # util.copy here instead.
353 fcd.write(util.readfile(back), fcd.flags())
353 fcd.write(util.readfile(back), fcd.flags())
354 return 1 # continue merging
354 return 1 # continue merging
355
355
356 def _mergecheck(repo, mynode, orig, fcd, fco, fca, toolconf):
356 def _mergecheck(repo, mynode, orig, fcd, fco, fca, toolconf):
357 tool, toolpath, binary, symlink = toolconf
357 tool, toolpath, binary, symlink = toolconf
358 if symlink:
358 if symlink:
359 repo.ui.warn(_('warning: internal %s cannot merge symlinks '
359 repo.ui.warn(_('warning: internal %s cannot merge symlinks '
360 'for %s\n') % (tool, fcd.path()))
360 'for %s\n') % (tool, fcd.path()))
361 return False
361 return False
362 if fcd.isabsent() or fco.isabsent():
362 if fcd.isabsent() or fco.isabsent():
363 repo.ui.warn(_('warning: internal %s cannot merge change/delete '
363 repo.ui.warn(_('warning: internal %s cannot merge change/delete '
364 'conflict for %s\n') % (tool, fcd.path()))
364 'conflict for %s\n') % (tool, fcd.path()))
365 return False
365 return False
366 return True
366 return True
367
367
368 def _merge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels, mode):
368 def _merge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels, mode):
369 """
369 """
370 Uses the internal non-interactive simple merge algorithm for merging
370 Uses the internal non-interactive simple merge algorithm for merging
371 files. It will fail if there are any conflicts and leave markers in
371 files. It will fail if there are any conflicts and leave markers in
372 the partially merged file. Markers will have two sections, one for each side
372 the partially merged file. Markers will have two sections, one for each side
373 of merge, unless mode equals 'union' which suppresses the markers."""
373 of merge, unless mode equals 'union' which suppresses the markers."""
374 ui = repo.ui
374 ui = repo.ui
375
375
376 r = simplemerge.simplemerge(ui, fcd, fca, fco,
376 r = simplemerge.simplemerge(ui, fcd, fca, fco,
377 label=labels, mode=mode, repo=repo)
377 label=labels, mode=mode, repo=repo)
378 return True, r, False
378 return True, r, False
379
379
380 @internaltool('union', fullmerge,
380 @internaltool('union', fullmerge,
381 _("warning: conflicts while merging %s! "
381 _("warning: conflicts while merging %s! "
382 "(edit, then use 'hg resolve --mark')\n"),
382 "(edit, then use 'hg resolve --mark')\n"),
383 precheck=_mergecheck)
383 precheck=_mergecheck)
384 def _iunion(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
384 def _iunion(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
385 """
385 """
386 Uses the internal non-interactive simple merge algorithm for merging
386 Uses the internal non-interactive simple merge algorithm for merging
387 files. It will use both left and right sides for conflict regions.
387 files. It will use both left and right sides for conflict regions.
388 No markers are inserted."""
388 No markers are inserted."""
389 return _merge(repo, mynode, orig, fcd, fco, fca, toolconf,
389 return _merge(repo, mynode, orig, fcd, fco, fca, toolconf,
390 files, labels, 'union')
390 files, labels, 'union')
391
391
392 @internaltool('merge', fullmerge,
392 @internaltool('merge', fullmerge,
393 _("warning: conflicts while merging %s! "
393 _("warning: conflicts while merging %s! "
394 "(edit, then use 'hg resolve --mark')\n"),
394 "(edit, then use 'hg resolve --mark')\n"),
395 precheck=_mergecheck)
395 precheck=_mergecheck)
396 def _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
396 def _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
397 """
397 """
398 Uses the internal non-interactive simple merge algorithm for merging
398 Uses the internal non-interactive simple merge algorithm for merging
399 files. It will fail if there are any conflicts and leave markers in
399 files. It will fail if there are any conflicts and leave markers in
400 the partially merged file. Markers will have two sections, one for each side
400 the partially merged file. Markers will have two sections, one for each side
401 of merge."""
401 of merge."""
402 return _merge(repo, mynode, orig, fcd, fco, fca, toolconf,
402 return _merge(repo, mynode, orig, fcd, fco, fca, toolconf,
403 files, labels, 'merge')
403 files, labels, 'merge')
404
404
405 @internaltool('merge3', fullmerge,
405 @internaltool('merge3', fullmerge,
406 _("warning: conflicts while merging %s! "
406 _("warning: conflicts while merging %s! "
407 "(edit, then use 'hg resolve --mark')\n"),
407 "(edit, then use 'hg resolve --mark')\n"),
408 precheck=_mergecheck)
408 precheck=_mergecheck)
409 def _imerge3(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
409 def _imerge3(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
410 """
410 """
411 Uses the internal non-interactive simple merge algorithm for merging
411 Uses the internal non-interactive simple merge algorithm for merging
412 files. It will fail if there are any conflicts and leave markers in
412 files. It will fail if there are any conflicts and leave markers in
413 the partially merged file. Marker will have three sections, one from each
413 the partially merged file. Marker will have three sections, one from each
414 side of the merge and one for the base content."""
414 side of the merge and one for the base content."""
415 if not labels:
415 if not labels:
416 labels = _defaultconflictlabels
416 labels = _defaultconflictlabels
417 if len(labels) < 3:
417 if len(labels) < 3:
418 labels.append('base')
418 labels.append('base')
419 return _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels)
419 return _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels)
420
420
421 def _imergeauto(repo, mynode, orig, fcd, fco, fca, toolconf, files,
421 def _imergeauto(repo, mynode, orig, fcd, fco, fca, toolconf, files,
422 labels=None, localorother=None):
422 labels=None, localorother=None):
423 """
423 """
424 Generic driver for _imergelocal and _imergeother
424 Generic driver for _imergelocal and _imergeother
425 """
425 """
426 assert localorother is not None
426 assert localorother is not None
427 tool, toolpath, binary, symlink = toolconf
427 tool, toolpath, binary, symlink = toolconf
428 r = simplemerge.simplemerge(repo.ui, fcd, fca, fco,
428 r = simplemerge.simplemerge(repo.ui, fcd, fca, fco,
429 label=labels, localorother=localorother,
429 label=labels, localorother=localorother,
430 repo=repo)
430 repo=repo)
431 return True, r
431 return True, r
432
432
433 @internaltool('merge-local', mergeonly, precheck=_mergecheck)
433 @internaltool('merge-local', mergeonly, precheck=_mergecheck)
434 def _imergelocal(*args, **kwargs):
434 def _imergelocal(*args, **kwargs):
435 """
435 """
436 Like :merge, but resolve all conflicts non-interactively in favor
436 Like :merge, but resolve all conflicts non-interactively in favor
437 of the local `p1()` changes."""
437 of the local `p1()` changes."""
438 success, status = _imergeauto(localorother='local', *args, **kwargs)
438 success, status = _imergeauto(localorother='local', *args, **kwargs)
439 return success, status, False
439 return success, status, False
440
440
441 @internaltool('merge-other', mergeonly, precheck=_mergecheck)
441 @internaltool('merge-other', mergeonly, precheck=_mergecheck)
442 def _imergeother(*args, **kwargs):
442 def _imergeother(*args, **kwargs):
443 """
443 """
444 Like :merge, but resolve all conflicts non-interactively in favor
444 Like :merge, but resolve all conflicts non-interactively in favor
445 of the other `p2()` changes."""
445 of the other `p2()` changes."""
446 success, status = _imergeauto(localorother='other', *args, **kwargs)
446 success, status = _imergeauto(localorother='other', *args, **kwargs)
447 return success, status, False
447 return success, status, False
448
448
449 @internaltool('tagmerge', mergeonly,
449 @internaltool('tagmerge', mergeonly,
450 _("automatic tag merging of %s failed! "
450 _("automatic tag merging of %s failed! "
451 "(use 'hg resolve --tool :merge' or another merge "
451 "(use 'hg resolve --tool :merge' or another merge "
452 "tool of your choice)\n"))
452 "tool of your choice)\n"))
453 def _itagmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
453 def _itagmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
454 """
454 """
455 Uses the internal tag merge algorithm (experimental).
455 Uses the internal tag merge algorithm (experimental).
456 """
456 """
457 success, status = tagmerge.merge(repo, fcd, fco, fca)
457 success, status = tagmerge.merge(repo, fcd, fco, fca)
458 return success, status, False
458 return success, status, False
459
459
460 @internaltool('dump', fullmerge)
460 @internaltool('dump', fullmerge)
461 def _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
461 def _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
462 """
462 """
463 Creates three versions of the files to merge, containing the
463 Creates three versions of the files to merge, containing the
464 contents of local, other and base. These files can then be used to
464 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
465 perform a merge manually. If the file to be merged is named
466 ``a.txt``, these files will accordingly be named ``a.txt.local``,
466 ``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
467 ``a.txt.other`` and ``a.txt.base`` and they will be placed in the
468 same directory as ``a.txt``.
468 same directory as ``a.txt``.
469
469
470 This implies permerge. Therefore, files aren't dumped, if premerge
470 This implies permerge. Therefore, files aren't dumped, if premerge
471 runs successfully. Use :forcedump to forcibly write files out.
471 runs successfully. Use :forcedump to forcibly write files out.
472 """
472 """
473 a, unused, unused, unused = files
473 a = _workingpath(repo, fcd)
474
475 fd = fcd.path()
474 fd = fcd.path()
476
475
477 util.copyfile(a, a + ".local")
476 util.copyfile(a, a + ".local")
478 repo.wwrite(fd + ".other", fco.data(), fco.flags())
477 repo.wwrite(fd + ".other", fco.data(), fco.flags())
479 repo.wwrite(fd + ".base", fca.data(), fca.flags())
478 repo.wwrite(fd + ".base", fca.data(), fca.flags())
480 return False, 1, False
479 return False, 1, False
481
480
482 @internaltool('forcedump', mergeonly)
481 @internaltool('forcedump', mergeonly)
483 def _forcedump(repo, mynode, orig, fcd, fco, fca, toolconf, files,
482 def _forcedump(repo, mynode, orig, fcd, fco, fca, toolconf, files,
484 labels=None):
483 labels=None):
485 """
484 """
486 Creates three versions of the files as same as :dump, but omits premerge.
485 Creates three versions of the files as same as :dump, but omits premerge.
487 """
486 """
488 return _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files,
487 return _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files,
489 labels=labels)
488 labels=labels)
490
489
491 def _xmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
490 def _xmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
492 tool, toolpath, binary, symlink = toolconf
491 tool, toolpath, binary, symlink = toolconf
493 if fcd.isabsent() or fco.isabsent():
492 if fcd.isabsent() or fco.isabsent():
494 repo.ui.warn(_('warning: %s cannot merge change/delete conflict '
493 repo.ui.warn(_('warning: %s cannot merge change/delete conflict '
495 'for %s\n') % (tool, fcd.path()))
494 'for %s\n') % (tool, fcd.path()))
496 return False, 1, None
495 return False, 1, None
497 a, b, c, back = files
496 unused, b, c, back = files
497 a = _workingpath(repo, fcd)
498 out = ""
498 out = ""
499 env = {'HG_FILE': fcd.path(),
499 env = {'HG_FILE': fcd.path(),
500 'HG_MY_NODE': short(mynode),
500 'HG_MY_NODE': short(mynode),
501 'HG_OTHER_NODE': str(fco.changectx()),
501 'HG_OTHER_NODE': str(fco.changectx()),
502 'HG_BASE_NODE': str(fca.changectx()),
502 'HG_BASE_NODE': str(fca.changectx()),
503 'HG_MY_ISLINK': 'l' in fcd.flags(),
503 'HG_MY_ISLINK': 'l' in fcd.flags(),
504 'HG_OTHER_ISLINK': 'l' in fco.flags(),
504 'HG_OTHER_ISLINK': 'l' in fco.flags(),
505 'HG_BASE_ISLINK': 'l' in fca.flags(),
505 'HG_BASE_ISLINK': 'l' in fca.flags(),
506 }
506 }
507
507
508 ui = repo.ui
508 ui = repo.ui
509
509
510 args = _toolstr(ui, tool, "args", '$local $base $other')
510 args = _toolstr(ui, tool, "args", '$local $base $other')
511 if "$output" in args:
511 if "$output" in args:
512 out, a = a, back # read input from backup, write to original
512 out, a = a, back # read input from backup, write to original
513 replace = {'local': a, 'base': b, 'other': c, 'output': out}
513 replace = {'local': a, 'base': b, 'other': c, 'output': out}
514 args = util.interpolate(r'\$', replace, args,
514 args = util.interpolate(r'\$', replace, args,
515 lambda s: util.shellquote(util.localpath(s)))
515 lambda s: util.shellquote(util.localpath(s)))
516 cmd = toolpath + ' ' + args
516 cmd = toolpath + ' ' + args
517 if _toolbool(ui, tool, "gui"):
517 if _toolbool(ui, tool, "gui"):
518 repo.ui.status(_('running merge tool %s for file %s\n') %
518 repo.ui.status(_('running merge tool %s for file %s\n') %
519 (tool, fcd.path()))
519 (tool, fcd.path()))
520 repo.ui.debug('launching merge tool: %s\n' % cmd)
520 repo.ui.debug('launching merge tool: %s\n' % cmd)
521 r = ui.system(cmd, cwd=repo.root, environ=env, blockedtag='mergetool')
521 r = ui.system(cmd, cwd=repo.root, environ=env, blockedtag='mergetool')
522 repo.ui.debug('merge tool returned: %s\n' % r)
522 repo.ui.debug('merge tool returned: %s\n' % r)
523 return True, r, False
523 return True, r, False
524
524
525 def _formatconflictmarker(repo, ctx, template, label, pad):
525 def _formatconflictmarker(repo, ctx, template, label, pad):
526 """Applies the given template to the ctx, prefixed by the label.
526 """Applies the given template to the ctx, prefixed by the label.
527
527
528 Pad is the minimum width of the label prefix, so that multiple markers
528 Pad is the minimum width of the label prefix, so that multiple markers
529 can have aligned templated parts.
529 can have aligned templated parts.
530 """
530 """
531 if ctx.node() is None:
531 if ctx.node() is None:
532 ctx = ctx.p1()
532 ctx = ctx.p1()
533
533
534 props = templatekw.keywords.copy()
534 props = templatekw.keywords.copy()
535 props['templ'] = template
535 props['templ'] = template
536 props['ctx'] = ctx
536 props['ctx'] = ctx
537 props['repo'] = repo
537 props['repo'] = repo
538 templateresult = template.render(props)
538 templateresult = template.render(props)
539
539
540 label = ('%s:' % label).ljust(pad + 1)
540 label = ('%s:' % label).ljust(pad + 1)
541 mark = '%s %s' % (label, templateresult)
541 mark = '%s %s' % (label, templateresult)
542
542
543 if mark:
543 if mark:
544 mark = mark.splitlines()[0] # split for safety
544 mark = mark.splitlines()[0] # split for safety
545
545
546 # 8 for the prefix of conflict marker lines (e.g. '<<<<<<< ')
546 # 8 for the prefix of conflict marker lines (e.g. '<<<<<<< ')
547 return util.ellipsis(mark, 80 - 8)
547 return util.ellipsis(mark, 80 - 8)
548
548
549 _defaultconflictlabels = ['local', 'other']
549 _defaultconflictlabels = ['local', 'other']
550
550
551 def _formatlabels(repo, fcd, fco, fca, labels):
551 def _formatlabels(repo, fcd, fco, fca, labels):
552 """Formats the given labels using the conflict marker template.
552 """Formats the given labels using the conflict marker template.
553
553
554 Returns a list of formatted labels.
554 Returns a list of formatted labels.
555 """
555 """
556 cd = fcd.changectx()
556 cd = fcd.changectx()
557 co = fco.changectx()
557 co = fco.changectx()
558 ca = fca.changectx()
558 ca = fca.changectx()
559
559
560 ui = repo.ui
560 ui = repo.ui
561 template = ui.config('ui', 'mergemarkertemplate')
561 template = ui.config('ui', 'mergemarkertemplate')
562 template = templater.unquotestring(template)
562 template = templater.unquotestring(template)
563 tmpl = formatter.maketemplater(ui, template)
563 tmpl = formatter.maketemplater(ui, template)
564
564
565 pad = max(len(l) for l in labels)
565 pad = max(len(l) for l in labels)
566
566
567 newlabels = [_formatconflictmarker(repo, cd, tmpl, labels[0], pad),
567 newlabels = [_formatconflictmarker(repo, cd, tmpl, labels[0], pad),
568 _formatconflictmarker(repo, co, tmpl, labels[1], pad)]
568 _formatconflictmarker(repo, co, tmpl, labels[1], pad)]
569 if len(labels) > 2:
569 if len(labels) > 2:
570 newlabels.append(_formatconflictmarker(repo, ca, tmpl, labels[2], pad))
570 newlabels.append(_formatconflictmarker(repo, ca, tmpl, labels[2], pad))
571 return newlabels
571 return newlabels
572
572
573 def partextras(labels):
573 def partextras(labels):
574 """Return a dictionary of extra labels for use in prompts to the user
574 """Return a dictionary of extra labels for use in prompts to the user
575
575
576 Intended use is in strings of the form "(l)ocal%(l)s".
576 Intended use is in strings of the form "(l)ocal%(l)s".
577 """
577 """
578 if labels is None:
578 if labels is None:
579 return {
579 return {
580 "l": "",
580 "l": "",
581 "o": "",
581 "o": "",
582 }
582 }
583
583
584 return {
584 return {
585 "l": " [%s]" % labels[0],
585 "l": " [%s]" % labels[0],
586 "o": " [%s]" % labels[1],
586 "o": " [%s]" % labels[1],
587 }
587 }
588
588
589 def _makebackup(repo, ui, fcd, premerge):
589 def _makebackup(repo, ui, fcd, premerge):
590 """Makes a backup of the local `fcd` file prior to merging.
590 """Makes a backup of the local `fcd` file prior to merging.
591
591
592 In addition to preserving the user's pre-existing modifications to `fcd`
592 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
593 (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
594 merge changed anything, and determine what line endings the new file should
595 have.
595 have.
596 """
596 """
597 if fcd.isabsent():
597 if fcd.isabsent():
598 return None
598 return None
599
599
600 a = repo.wjoin(fcd.path())
600 a = _workingpath(repo, fcd)
601 back = scmutil.origpath(ui, repo, a)
601 back = scmutil.origpath(ui, repo, a)
602 if premerge:
602 if premerge:
603 util.copyfile(a, back)
603 util.copyfile(a, back)
604 return back
604 return back
605
605
606 def _maketempfiles(repo, fcd, fco, fca):
606 def _maketempfiles(repo, fcd, fco, fca):
607 """Writes out `fco` and `fca` as temporary files, so an external merge
607 """Writes out `fco` and `fca` as temporary files, so an external merge
608 tool may use them.
608 tool may use them.
609
609
610 `fcd` is returned as-is, by convention, because it currently doubles as both
610 `fcd` is returned as-is, by convention, because it currently doubles as both
611 the local version and merge destination.
611 the local version and merge destination.
612 """
612 """
613 def temp(prefix, ctx):
613 def temp(prefix, ctx):
614 fullbase, ext = os.path.splitext(ctx.path())
614 fullbase, ext = os.path.splitext(ctx.path())
615 pre = "%s~%s." % (os.path.basename(fullbase), prefix)
615 pre = "%s~%s." % (os.path.basename(fullbase), prefix)
616 (fd, name) = tempfile.mkstemp(prefix=pre, suffix=ext)
616 (fd, name) = tempfile.mkstemp(prefix=pre, suffix=ext)
617 data = repo.wwritedata(ctx.path(), ctx.data())
617 data = repo.wwritedata(ctx.path(), ctx.data())
618 f = os.fdopen(fd, pycompat.sysstr("wb"))
618 f = os.fdopen(fd, pycompat.sysstr("wb"))
619 f.write(data)
619 f.write(data)
620 f.close()
620 f.close()
621 return name
621 return name
622
622
623 a = repo.wjoin(fcd.path())
623 a = repo.wjoin(fcd.path())
624 b = temp("base", fca)
624 b = temp("base", fca)
625 c = temp("other", fco)
625 c = temp("other", fco)
626
626
627 return a, b, c
627 return a, b, c
628
628
629 def _filemerge(premerge, repo, mynode, orig, fcd, fco, fca, labels=None):
629 def _filemerge(premerge, repo, mynode, orig, fcd, fco, fca, labels=None):
630 """perform a 3-way merge in the working directory
630 """perform a 3-way merge in the working directory
631
631
632 premerge = whether this is a premerge
632 premerge = whether this is a premerge
633 mynode = parent node before merge
633 mynode = parent node before merge
634 orig = original local filename before merge
634 orig = original local filename before merge
635 fco = other file context
635 fco = other file context
636 fca = ancestor file context
636 fca = ancestor file context
637 fcd = local file context for current/destination file
637 fcd = local file context for current/destination file
638
638
639 Returns whether the merge is complete, the return value of the merge, and
639 Returns whether the merge is complete, the return value of the merge, and
640 a boolean indicating whether the file was deleted from disk."""
640 a boolean indicating whether the file was deleted from disk."""
641
641
642 if not fco.cmp(fcd): # files identical?
642 if not fco.cmp(fcd): # files identical?
643 return True, None, False
643 return True, None, False
644
644
645 ui = repo.ui
645 ui = repo.ui
646 fd = fcd.path()
646 fd = fcd.path()
647 binary = fcd.isbinary() or fco.isbinary() or fca.isbinary()
647 binary = fcd.isbinary() or fco.isbinary() or fca.isbinary()
648 symlink = 'l' in fcd.flags() + fco.flags()
648 symlink = 'l' in fcd.flags() + fco.flags()
649 changedelete = fcd.isabsent() or fco.isabsent()
649 changedelete = fcd.isabsent() or fco.isabsent()
650 tool, toolpath = _picktool(repo, ui, fd, binary, symlink, changedelete)
650 tool, toolpath = _picktool(repo, ui, fd, binary, symlink, changedelete)
651 if tool in internals and tool.startswith('internal:'):
651 if tool in internals and tool.startswith('internal:'):
652 # normalize to new-style names (':merge' etc)
652 # normalize to new-style names (':merge' etc)
653 tool = tool[len('internal'):]
653 tool = tool[len('internal'):]
654 ui.debug("picked tool '%s' for %s (binary %s symlink %s changedelete %s)\n"
654 ui.debug("picked tool '%s' for %s (binary %s symlink %s changedelete %s)\n"
655 % (tool, fd, pycompat.bytestr(binary), pycompat.bytestr(symlink),
655 % (tool, fd, pycompat.bytestr(binary), pycompat.bytestr(symlink),
656 pycompat.bytestr(changedelete)))
656 pycompat.bytestr(changedelete)))
657
657
658 if tool in internals:
658 if tool in internals:
659 func = internals[tool]
659 func = internals[tool]
660 mergetype = func.mergetype
660 mergetype = func.mergetype
661 onfailure = func.onfailure
661 onfailure = func.onfailure
662 precheck = func.precheck
662 precheck = func.precheck
663 else:
663 else:
664 func = _xmerge
664 func = _xmerge
665 mergetype = fullmerge
665 mergetype = fullmerge
666 onfailure = _("merging %s failed!\n")
666 onfailure = _("merging %s failed!\n")
667 precheck = None
667 precheck = None
668
668
669 toolconf = tool, toolpath, binary, symlink
669 toolconf = tool, toolpath, binary, symlink
670
670
671 if mergetype == nomerge:
671 if mergetype == nomerge:
672 r, deleted = func(repo, mynode, orig, fcd, fco, fca, toolconf, labels)
672 r, deleted = func(repo, mynode, orig, fcd, fco, fca, toolconf, labels)
673 return True, r, deleted
673 return True, r, deleted
674
674
675 if premerge:
675 if premerge:
676 if orig != fco.path():
676 if orig != fco.path():
677 ui.status(_("merging %s and %s to %s\n") % (orig, fco.path(), fd))
677 ui.status(_("merging %s and %s to %s\n") % (orig, fco.path(), fd))
678 else:
678 else:
679 ui.status(_("merging %s\n") % fd)
679 ui.status(_("merging %s\n") % fd)
680
680
681 ui.debug("my %s other %s ancestor %s\n" % (fcd, fco, fca))
681 ui.debug("my %s other %s ancestor %s\n" % (fcd, fco, fca))
682
682
683 if precheck and not precheck(repo, mynode, orig, fcd, fco, fca,
683 if precheck and not precheck(repo, mynode, orig, fcd, fco, fca,
684 toolconf):
684 toolconf):
685 if onfailure:
685 if onfailure:
686 ui.warn(onfailure % fd)
686 ui.warn(onfailure % fd)
687 return True, 1, False
687 return True, 1, False
688
688
689 back = _makebackup(repo, ui, fcd, premerge)
689 back = _makebackup(repo, ui, fcd, premerge)
690 files = _maketempfiles(repo, fcd, fco, fca) + (back,)
690 files = _maketempfiles(repo, fcd, fco, fca) + (back,)
691 r = 1
691 r = 1
692 try:
692 try:
693 markerstyle = ui.config('ui', 'mergemarkers')
693 markerstyle = ui.config('ui', 'mergemarkers')
694 if not labels:
694 if not labels:
695 labels = _defaultconflictlabels
695 labels = _defaultconflictlabels
696 if markerstyle != 'basic':
696 if markerstyle != 'basic':
697 labels = _formatlabels(repo, fcd, fco, fca, labels)
697 labels = _formatlabels(repo, fcd, fco, fca, labels)
698
698
699 if premerge and mergetype == fullmerge:
699 if premerge and mergetype == fullmerge:
700 r = _premerge(repo, fcd, fco, fca, toolconf, files, labels=labels)
700 r = _premerge(repo, fcd, fco, fca, toolconf, files, labels=labels)
701 # complete if premerge successful (r is 0)
701 # complete if premerge successful (r is 0)
702 return not r, r, False
702 return not r, r, False
703
703
704 needcheck, r, deleted = func(repo, mynode, orig, fcd, fco, fca,
704 needcheck, r, deleted = func(repo, mynode, orig, fcd, fco, fca,
705 toolconf, files, labels=labels)
705 toolconf, files, labels=labels)
706
706
707 if needcheck:
707 if needcheck:
708 r = _check(r, ui, tool, fcd, files)
708 r = _check(repo, r, ui, tool, fcd, files)
709
709
710 if r:
710 if r:
711 if onfailure:
711 if onfailure:
712 ui.warn(onfailure % fd)
712 ui.warn(onfailure % fd)
713
713
714 return True, r, deleted
714 return True, r, deleted
715 finally:
715 finally:
716 if not r and back is not None:
716 if not r and back is not None:
717 util.unlink(back)
717 util.unlink(back)
718 util.unlink(files[1])
718 util.unlink(files[1])
719 util.unlink(files[2])
719 util.unlink(files[2])
720
720
721 def _check(r, ui, tool, fcd, files):
721 def _check(repo, r, ui, tool, fcd, files):
722 fd = fcd.path()
722 fd = fcd.path()
723 a, unused, unused, back = files
723 unused, unused, unused, back = files
724
724
725 if not r and (_toolbool(ui, tool, "checkconflicts") or
725 if not r and (_toolbool(ui, tool, "checkconflicts") or
726 'conflicts' in _toollist(ui, tool, "check")):
726 'conflicts' in _toollist(ui, tool, "check")):
727 if re.search("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcd.data(),
727 if re.search("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcd.data(),
728 re.MULTILINE):
728 re.MULTILINE):
729 r = 1
729 r = 1
730
730
731 checked = False
731 checked = False
732 if 'prompt' in _toollist(ui, tool, "check"):
732 if 'prompt' in _toollist(ui, tool, "check"):
733 checked = True
733 checked = True
734 if ui.promptchoice(_("was merge of '%s' successful (yn)?"
734 if ui.promptchoice(_("was merge of '%s' successful (yn)?"
735 "$$ &Yes $$ &No") % fd, 1):
735 "$$ &Yes $$ &No") % fd, 1):
736 r = 1
736 r = 1
737
737
738 if not r and not checked and (_toolbool(ui, tool, "checkchanged") or
738 if not r and not checked and (_toolbool(ui, tool, "checkchanged") or
739 'changed' in
739 'changed' in
740 _toollist(ui, tool, "check")):
740 _toollist(ui, tool, "check")):
741 if back is not None and filecmp.cmp(a, back):
741 if back is not None and filecmp.cmp(_workingpath(repo, fcd), back):
742 if ui.promptchoice(_(" output file %s appears unchanged\n"
742 if ui.promptchoice(_(" output file %s appears unchanged\n"
743 "was merge successful (yn)?"
743 "was merge successful (yn)?"
744 "$$ &Yes $$ &No") % fd, 1):
744 "$$ &Yes $$ &No") % fd, 1):
745 r = 1
745 r = 1
746
746
747 if back is not None and _toolbool(ui, tool, "fixeol"):
747 if back is not None and _toolbool(ui, tool, "fixeol"):
748 _matcheol(a, back)
748 _matcheol(_workingpath(repo, fcd), back)
749
749
750 return r
750 return r
751
751
752 def _workingpath(repo, ctx):
753 return repo.wjoin(ctx.path())
754
752 def premerge(repo, mynode, orig, fcd, fco, fca, labels=None):
755 def premerge(repo, mynode, orig, fcd, fco, fca, labels=None):
753 return _filemerge(True, repo, mynode, orig, fcd, fco, fca, labels=labels)
756 return _filemerge(True, repo, mynode, orig, fcd, fco, fca, labels=labels)
754
757
755 def filemerge(repo, mynode, orig, fcd, fco, fca, labels=None):
758 def filemerge(repo, mynode, orig, fcd, fco, fca, labels=None):
756 return _filemerge(False, repo, mynode, orig, fcd, fco, fca, labels=labels)
759 return _filemerge(False, repo, mynode, orig, fcd, fco, fca, labels=labels)
757
760
758 def loadinternalmerge(ui, extname, registrarobj):
761 def loadinternalmerge(ui, extname, registrarobj):
759 """Load internal merge tool from specified registrarobj
762 """Load internal merge tool from specified registrarobj
760 """
763 """
761 for name, func in registrarobj._table.iteritems():
764 for name, func in registrarobj._table.iteritems():
762 fullname = ':' + name
765 fullname = ':' + name
763 internals[fullname] = func
766 internals[fullname] = func
764 internals['internal:' + name] = func
767 internals['internal:' + name] = func
765 internalsdoc[fullname] = func
768 internalsdoc[fullname] = func
766
769
767 # load built-in merge tools explicitly to setup internalsdoc
770 # load built-in merge tools explicitly to setup internalsdoc
768 loadinternalmerge(None, None, internaltool)
771 loadinternalmerge(None, None, internaltool)
769
772
770 # tell hggettext to extract docstrings from these functions:
773 # tell hggettext to extract docstrings from these functions:
771 i18nfunctions = internals.values()
774 i18nfunctions = internals.values()
General Comments 0
You need to be logged in to leave comments. Login now