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