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