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