##// END OF EJS Templates
filemerge: use only the first line of the generated conflict marker for safety...
FUJIWARA Katsunori -
r21864:755bf1bb default
parent child Browse files
Show More
@@ -1,437 +1,440
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)
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:
291 mark = mark.splitlines()[0] # split for safety
292
290 # The <<< marks add 8 to the length, and '...' adds three, so max
293 # The <<< marks add 8 to the length, and '...' adds three, so max
291 # length of the actual marker is 69.
294 # length of the actual marker is 69.
292 maxlength = 80 - 8 - 3
295 maxlength = 80 - 8 - 3
293 if len(mark) > maxlength:
296 if len(mark) > maxlength:
294 mark = mark[:maxlength] + '...'
297 mark = mark[:maxlength] + '...'
295 return mark
298 return mark
296
299
297 _defaultconflictmarker = ('{node|short} ' +
300 _defaultconflictmarker = ('{node|short} ' +
298 '{ifeq(tags, "tip", "", "{tags} ")}' +
301 '{ifeq(tags, "tip", "", "{tags} ")}' +
299 '{if(bookmarks, "{bookmarks} ")}' +
302 '{if(bookmarks, "{bookmarks} ")}' +
300 '{ifeq(branch, "default", "", "{branch} ")}' +
303 '{ifeq(branch, "default", "", "{branch} ")}' +
301 '- {author|user}: {desc|firstline}')
304 '- {author|user}: {desc|firstline}')
302
305
303 _defaultconflictlabels = ['local', 'other']
306 _defaultconflictlabels = ['local', 'other']
304
307
305 def _formatlabels(repo, fcd, fco, labels):
308 def _formatlabels(repo, fcd, fco, labels):
306 """Formats the given labels using the conflict marker template.
309 """Formats the given labels using the conflict marker template.
307
310
308 Returns a list of formatted labels.
311 Returns a list of formatted labels.
309 """
312 """
310 cd = fcd.changectx()
313 cd = fcd.changectx()
311 co = fco.changectx()
314 co = fco.changectx()
312
315
313 ui = repo.ui
316 ui = repo.ui
314 template = ui.config('ui', 'mergemarkertemplate', _defaultconflictmarker)
317 template = ui.config('ui', 'mergemarkertemplate', _defaultconflictmarker)
315 template = templater.parsestring(template, quoted=False)
318 template = templater.parsestring(template, quoted=False)
316 tmpl = templater.templater(None, cache={ 'conflictmarker' : template })
319 tmpl = templater.templater(None, cache={ 'conflictmarker' : template })
317
320
318 pad = max(len(labels[0]), len(labels[1]))
321 pad = max(len(labels[0]), len(labels[1]))
319
322
320 return [_formatconflictmarker(repo, cd, tmpl, labels[0], pad),
323 return [_formatconflictmarker(repo, cd, tmpl, labels[0], pad),
321 _formatconflictmarker(repo, co, tmpl, labels[1], pad)]
324 _formatconflictmarker(repo, co, tmpl, labels[1], pad)]
322
325
323 def filemerge(repo, mynode, orig, fcd, fco, fca, labels=None):
326 def filemerge(repo, mynode, orig, fcd, fco, fca, labels=None):
324 """perform a 3-way merge in the working directory
327 """perform a 3-way merge in the working directory
325
328
326 mynode = parent node before merge
329 mynode = parent node before merge
327 orig = original local filename before merge
330 orig = original local filename before merge
328 fco = other file context
331 fco = other file context
329 fca = ancestor file context
332 fca = ancestor file context
330 fcd = local file context for current/destination file
333 fcd = local file context for current/destination file
331 """
334 """
332
335
333 def temp(prefix, ctx):
336 def temp(prefix, ctx):
334 pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
337 pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
335 (fd, name) = tempfile.mkstemp(prefix=pre)
338 (fd, name) = tempfile.mkstemp(prefix=pre)
336 data = repo.wwritedata(ctx.path(), ctx.data())
339 data = repo.wwritedata(ctx.path(), ctx.data())
337 f = os.fdopen(fd, "wb")
340 f = os.fdopen(fd, "wb")
338 f.write(data)
341 f.write(data)
339 f.close()
342 f.close()
340 return name
343 return name
341
344
342 if not fco.cmp(fcd): # files identical?
345 if not fco.cmp(fcd): # files identical?
343 return None
346 return None
344
347
345 ui = repo.ui
348 ui = repo.ui
346 fd = fcd.path()
349 fd = fcd.path()
347 binary = fcd.isbinary() or fco.isbinary() or fca.isbinary()
350 binary = fcd.isbinary() or fco.isbinary() or fca.isbinary()
348 symlink = 'l' in fcd.flags() + fco.flags()
351 symlink = 'l' in fcd.flags() + fco.flags()
349 tool, toolpath = _picktool(repo, ui, fd, binary, symlink)
352 tool, toolpath = _picktool(repo, ui, fd, binary, symlink)
350 ui.debug("picked tool '%s' for %s (binary %s symlink %s)\n" %
353 ui.debug("picked tool '%s' for %s (binary %s symlink %s)\n" %
351 (tool, fd, binary, symlink))
354 (tool, fd, binary, symlink))
352
355
353 if tool in internals:
356 if tool in internals:
354 func = internals[tool]
357 func = internals[tool]
355 trymerge = func.trymerge
358 trymerge = func.trymerge
356 onfailure = func.onfailure
359 onfailure = func.onfailure
357 else:
360 else:
358 func = _xmerge
361 func = _xmerge
359 trymerge = True
362 trymerge = True
360 onfailure = _("merging %s failed!\n")
363 onfailure = _("merging %s failed!\n")
361
364
362 toolconf = tool, toolpath, binary, symlink
365 toolconf = tool, toolpath, binary, symlink
363
366
364 if not trymerge:
367 if not trymerge:
365 return func(repo, mynode, orig, fcd, fco, fca, toolconf)
368 return func(repo, mynode, orig, fcd, fco, fca, toolconf)
366
369
367 a = repo.wjoin(fd)
370 a = repo.wjoin(fd)
368 b = temp("base", fca)
371 b = temp("base", fca)
369 c = temp("other", fco)
372 c = temp("other", fco)
370 back = a + ".orig"
373 back = a + ".orig"
371 util.copyfile(a, back)
374 util.copyfile(a, back)
372
375
373 if orig != fco.path():
376 if orig != fco.path():
374 ui.status(_("merging %s and %s to %s\n") % (orig, fco.path(), fd))
377 ui.status(_("merging %s and %s to %s\n") % (orig, fco.path(), fd))
375 else:
378 else:
376 ui.status(_("merging %s\n") % fd)
379 ui.status(_("merging %s\n") % fd)
377
380
378 ui.debug("my %s other %s ancestor %s\n" % (fcd, fco, fca))
381 ui.debug("my %s other %s ancestor %s\n" % (fcd, fco, fca))
379
382
380 markerstyle = ui.config('ui', 'mergemarkers', 'detailed')
383 markerstyle = ui.config('ui', 'mergemarkers', 'detailed')
381 if markerstyle == 'basic':
384 if markerstyle == 'basic':
382 formattedlabels = _defaultconflictlabels
385 formattedlabels = _defaultconflictlabels
383 else:
386 else:
384 if not labels:
387 if not labels:
385 labels = _defaultconflictlabels
388 labels = _defaultconflictlabels
386
389
387 formattedlabels = _formatlabels(repo, fcd, fco, labels)
390 formattedlabels = _formatlabels(repo, fcd, fco, labels)
388
391
389 needcheck, r = func(repo, mynode, orig, fcd, fco, fca, toolconf,
392 needcheck, r = func(repo, mynode, orig, fcd, fco, fca, toolconf,
390 (a, b, c, back), labels=formattedlabels)
393 (a, b, c, back), labels=formattedlabels)
391 if not needcheck:
394 if not needcheck:
392 if r:
395 if r:
393 if onfailure:
396 if onfailure:
394 ui.warn(onfailure % fd)
397 ui.warn(onfailure % fd)
395 else:
398 else:
396 util.unlink(back)
399 util.unlink(back)
397
400
398 util.unlink(b)
401 util.unlink(b)
399 util.unlink(c)
402 util.unlink(c)
400 return r
403 return r
401
404
402 if not r and (_toolbool(ui, tool, "checkconflicts") or
405 if not r and (_toolbool(ui, tool, "checkconflicts") or
403 'conflicts' in _toollist(ui, tool, "check")):
406 'conflicts' in _toollist(ui, tool, "check")):
404 if re.search("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcd.data(),
407 if re.search("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcd.data(),
405 re.MULTILINE):
408 re.MULTILINE):
406 r = 1
409 r = 1
407
410
408 checked = False
411 checked = False
409 if 'prompt' in _toollist(ui, tool, "check"):
412 if 'prompt' in _toollist(ui, tool, "check"):
410 checked = True
413 checked = True
411 if ui.promptchoice(_("was merge of '%s' successful (yn)?"
414 if ui.promptchoice(_("was merge of '%s' successful (yn)?"
412 "$$ &Yes $$ &No") % fd, 1):
415 "$$ &Yes $$ &No") % fd, 1):
413 r = 1
416 r = 1
414
417
415 if not r and not checked and (_toolbool(ui, tool, "checkchanged") or
418 if not r and not checked and (_toolbool(ui, tool, "checkchanged") or
416 'changed' in _toollist(ui, tool, "check")):
419 'changed' in _toollist(ui, tool, "check")):
417 if filecmp.cmp(a, back):
420 if filecmp.cmp(a, back):
418 if ui.promptchoice(_(" output file %s appears unchanged\n"
421 if ui.promptchoice(_(" output file %s appears unchanged\n"
419 "was merge successful (yn)?"
422 "was merge successful (yn)?"
420 "$$ &Yes $$ &No") % fd, 1):
423 "$$ &Yes $$ &No") % fd, 1):
421 r = 1
424 r = 1
422
425
423 if _toolbool(ui, tool, "fixeol"):
426 if _toolbool(ui, tool, "fixeol"):
424 _matcheol(a, back)
427 _matcheol(a, back)
425
428
426 if r:
429 if r:
427 if onfailure:
430 if onfailure:
428 ui.warn(onfailure % fd)
431 ui.warn(onfailure % fd)
429 else:
432 else:
430 util.unlink(back)
433 util.unlink(back)
431
434
432 util.unlink(b)
435 util.unlink(b)
433 util.unlink(c)
436 util.unlink(c)
434 return r
437 return r
435
438
436 # tell hggettext to extract docstrings from these functions:
439 # tell hggettext to extract docstrings from these functions:
437 i18nfunctions = internals.values()
440 i18nfunctions = internals.values()
@@ -1,73 +1,93
1 $ hg init
1 $ hg init
2 $ echo "nothing" > a
2 $ echo "nothing" > a
3 $ hg add a
3 $ hg add a
4 $ hg commit -m ancestor
4 $ hg commit -m ancestor
5 $ echo "something" > a
5 $ echo "something" > a
6 $ hg commit -m branch1
6 $ hg commit -m branch1
7 $ hg co 0
7 $ hg co 0
8 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
8 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
9 $ echo "something else" > a
9 $ echo "something else" > a
10 $ hg commit -m branch2
10 $ hg commit -m branch2
11 created new head
11 created new head
12
12
13 $ hg merge 1
13 $ hg merge 1
14 merging a
14 merging a
15 warning: conflicts during merge.
15 warning: conflicts during merge.
16 merging a incomplete! (edit conflicts, then use 'hg resolve --mark')
16 merging a incomplete! (edit conflicts, then use 'hg resolve --mark')
17 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
17 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
18 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
18 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
19 [1]
19 [1]
20
20
21 $ hg id
21 $ hg id
22 32e80765d7fe+75234512624c+ tip
22 32e80765d7fe+75234512624c+ tip
23
23
24 $ cat a
24 $ cat a
25 <<<<<<< local: 32e80765d7fe - test: branch2
25 <<<<<<< local: 32e80765d7fe - test: branch2
26 something else
26 something else
27 =======
27 =======
28 something
28 something
29 >>>>>>> other: 75234512624c - test: branch1
29 >>>>>>> other: 75234512624c - test: branch1
30
30
31 $ hg status
31 $ hg status
32 M a
32 M a
33 ? a.orig
33 ? a.orig
34
34
35 Verify custom conflict markers
35 Verify custom conflict markers
36
36
37 $ hg up -q --clean .
37 $ hg up -q --clean .
38 $ printf "\n[ui]\nmergemarkertemplate={author} {rev}\n" >> .hg/hgrc
38 $ printf "\n[ui]\nmergemarkertemplate={author} {rev}\n" >> .hg/hgrc
39
39
40 $ hg merge 1
40 $ hg merge 1
41 merging a
41 merging a
42 warning: conflicts during merge.
42 warning: conflicts during merge.
43 merging a incomplete! (edit conflicts, then use 'hg resolve --mark')
43 merging a incomplete! (edit conflicts, then use 'hg resolve --mark')
44 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
44 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
45 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
45 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
46 [1]
46 [1]
47
47
48 $ cat a
48 $ cat a
49 <<<<<<< local: test 2
49 <<<<<<< local: test 2
50 something else
50 something else
51 =======
51 =======
52 something
52 something
53 >>>>>>> other: test 1
53 >>>>>>> other: test 1
54
54
55 Verify line splitting of custom conflict marker which causes multiple lines
56
57 $ hg up -q --clean .
58 $ cat >> .hg/hgrc <<EOF
59 > [ui]
60 > mergemarkertemplate={author} {rev}\nfoo\nbar\nbaz
61 > EOF
62
63 $ hg -q merge 1
64 warning: conflicts during merge.
65 merging a incomplete! (edit conflicts, then use 'hg resolve --mark')
66 [1]
67
68 $ cat a
69 <<<<<<< local: test 2
70 something else
71 =======
72 something
73 >>>>>>> other: test 1
74
55 Verify basic conflict markers
75 Verify basic conflict markers
56
76
57 $ hg up -q --clean .
77 $ hg up -q --clean .
58 $ printf "\n[ui]\nmergemarkers=basic\n" >> .hg/hgrc
78 $ printf "\n[ui]\nmergemarkers=basic\n" >> .hg/hgrc
59
79
60 $ hg merge 1
80 $ hg merge 1
61 merging a
81 merging a
62 warning: conflicts during merge.
82 warning: conflicts during merge.
63 merging a incomplete! (edit conflicts, then use 'hg resolve --mark')
83 merging a incomplete! (edit conflicts, then use 'hg resolve --mark')
64 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
84 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
65 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
85 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
66 [1]
86 [1]
67
87
68 $ cat a
88 $ cat a
69 <<<<<<< local
89 <<<<<<< local
70 something else
90 something else
71 =======
91 =======
72 something
92 something
73 >>>>>>> other
93 >>>>>>> other
General Comments 0
You need to be logged in to leave comments. Login now