##// END OF EJS Templates
filemerge: add a wrapper around the filemerge function...
Siddharth Agarwal -
r26605:ef21a2c4 default
parent child Browse files
Show More
@@ -1,172 +1,172 b''
1 1 # Copyright 2009-2010 Gregory P. Ward
2 2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
3 3 # Copyright 2010-2011 Fog Creek Software
4 4 # Copyright 2010-2011 Unity Technologies
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 '''setup for largefiles extension: uisetup'''
10 10
11 11 from mercurial import archival, cmdutil, commands, extensions, filemerge, hg, \
12 12 httppeer, merge, scmutil, sshpeer, wireproto, revset, subrepo, copies
13 13 from mercurial.i18n import _
14 14 from mercurial.hgweb import hgweb_mod, webcommands
15 15
16 16 import overrides
17 17 import proto
18 18
19 19 def uisetup(ui):
20 20 # Disable auto-status for some commands which assume that all
21 21 # files in the result are under Mercurial's control
22 22
23 23 entry = extensions.wrapcommand(commands.table, 'add',
24 24 overrides.overrideadd)
25 25 addopt = [('', 'large', None, _('add as largefile')),
26 26 ('', 'normal', None, _('add as normal file')),
27 27 ('', 'lfsize', '', _('add all files above this size '
28 28 '(in megabytes) as largefiles '
29 29 '(default: 10)'))]
30 30 entry[1].extend(addopt)
31 31
32 32 # The scmutil function is called both by the (trivial) addremove command,
33 33 # and in the process of handling commit -A (issue3542)
34 34 entry = extensions.wrapfunction(scmutil, 'addremove',
35 35 overrides.scmutiladdremove)
36 36 extensions.wrapfunction(cmdutil, 'add', overrides.cmdutiladd)
37 37 extensions.wrapfunction(cmdutil, 'remove', overrides.cmdutilremove)
38 38 extensions.wrapfunction(cmdutil, 'forget', overrides.cmdutilforget)
39 39
40 40 extensions.wrapfunction(copies, 'pathcopies', overrides.copiespathcopies)
41 41
42 42 # Subrepos call status function
43 43 entry = extensions.wrapcommand(commands.table, 'status',
44 44 overrides.overridestatus)
45 45 entry = extensions.wrapfunction(subrepo.hgsubrepo, 'status',
46 46 overrides.overridestatusfn)
47 47
48 48 entry = extensions.wrapcommand(commands.table, 'log',
49 49 overrides.overridelog)
50 50 entry = extensions.wrapcommand(commands.table, 'rollback',
51 51 overrides.overriderollback)
52 52 entry = extensions.wrapcommand(commands.table, 'verify',
53 53 overrides.overrideverify)
54 54
55 55 verifyopt = [('', 'large', None,
56 56 _('verify that all largefiles in current revision exists')),
57 57 ('', 'lfa', None,
58 58 _('verify largefiles in all revisions, not just current')),
59 59 ('', 'lfc', None,
60 60 _('verify local largefile contents, not just existence'))]
61 61 entry[1].extend(verifyopt)
62 62
63 63 entry = extensions.wrapcommand(commands.table, 'debugstate',
64 64 overrides.overridedebugstate)
65 65 debugstateopt = [('', 'large', None, _('display largefiles dirstate'))]
66 66 entry[1].extend(debugstateopt)
67 67
68 68 outgoing = lambda orgfunc, *arg, **kwargs: orgfunc(*arg, **kwargs)
69 69 entry = extensions.wrapcommand(commands.table, 'outgoing', outgoing)
70 70 outgoingopt = [('', 'large', None, _('display outgoing largefiles'))]
71 71 entry[1].extend(outgoingopt)
72 72 cmdutil.outgoinghooks.add('largefiles', overrides.outgoinghook)
73 73 entry = extensions.wrapcommand(commands.table, 'summary',
74 74 overrides.overridesummary)
75 75 summaryopt = [('', 'large', None, _('display outgoing largefiles'))]
76 76 entry[1].extend(summaryopt)
77 77 cmdutil.summaryremotehooks.add('largefiles', overrides.summaryremotehook)
78 78
79 79 entry = extensions.wrapcommand(commands.table, 'pull',
80 80 overrides.overridepull)
81 81 pullopt = [('', 'all-largefiles', None,
82 82 _('download all pulled versions of largefiles (DEPRECATED)')),
83 83 ('', 'lfrev', [],
84 84 _('download largefiles for these revisions'), _('REV'))]
85 85 entry[1].extend(pullopt)
86 86 revset.symbols['pulled'] = overrides.pulledrevsetsymbol
87 87
88 88 entry = extensions.wrapcommand(commands.table, 'clone',
89 89 overrides.overrideclone)
90 90 cloneopt = [('', 'all-largefiles', None,
91 91 _('download all versions of all largefiles'))]
92 92 entry[1].extend(cloneopt)
93 93 entry = extensions.wrapfunction(hg, 'clone', overrides.hgclone)
94 94
95 95 entry = extensions.wrapcommand(commands.table, 'cat',
96 96 overrides.overridecat)
97 97 entry = extensions.wrapfunction(merge, '_checkunknownfile',
98 98 overrides.overridecheckunknownfile)
99 99 entry = extensions.wrapfunction(merge, 'calculateupdates',
100 100 overrides.overridecalculateupdates)
101 101 entry = extensions.wrapfunction(merge, 'recordupdates',
102 102 overrides.mergerecordupdates)
103 103 entry = extensions.wrapfunction(merge, 'update',
104 104 overrides.mergeupdate)
105 entry = extensions.wrapfunction(filemerge, 'filemerge',
105 entry = extensions.wrapfunction(filemerge, '_filemerge',
106 106 overrides.overridefilemerge)
107 107 entry = extensions.wrapfunction(cmdutil, 'copy',
108 108 overrides.overridecopy)
109 109
110 110 # Summary calls dirty on the subrepos
111 111 entry = extensions.wrapfunction(subrepo.hgsubrepo, 'dirty',
112 112 overrides.overridedirty)
113 113
114 114 entry = extensions.wrapfunction(cmdutil, 'revert',
115 115 overrides.overriderevert)
116 116
117 117 extensions.wrapcommand(commands.table, 'archive',
118 118 overrides.overridearchivecmd)
119 119 extensions.wrapfunction(archival, 'archive', overrides.overridearchive)
120 120 extensions.wrapfunction(subrepo.hgsubrepo, 'archive',
121 121 overrides.hgsubrepoarchive)
122 122 extensions.wrapfunction(webcommands, 'archive',
123 123 overrides.hgwebarchive)
124 124 extensions.wrapfunction(cmdutil, 'bailifchanged',
125 125 overrides.overridebailifchanged)
126 126
127 127 extensions.wrapfunction(scmutil, 'marktouched',
128 128 overrides.scmutilmarktouched)
129 129
130 130 # create the new wireproto commands ...
131 131 wireproto.commands['putlfile'] = (proto.putlfile, 'sha')
132 132 wireproto.commands['getlfile'] = (proto.getlfile, 'sha')
133 133 wireproto.commands['statlfile'] = (proto.statlfile, 'sha')
134 134
135 135 # ... and wrap some existing ones
136 136 wireproto.commands['capabilities'] = (proto.capabilities, '')
137 137 wireproto.commands['heads'] = (proto.heads, '')
138 138 wireproto.commands['lheads'] = (wireproto.heads, '')
139 139
140 140 # make putlfile behave the same as push and {get,stat}lfile behave
141 141 # the same as pull w.r.t. permissions checks
142 142 hgweb_mod.perms['putlfile'] = 'push'
143 143 hgweb_mod.perms['getlfile'] = 'pull'
144 144 hgweb_mod.perms['statlfile'] = 'pull'
145 145
146 146 extensions.wrapfunction(webcommands, 'decodepath', overrides.decodepath)
147 147
148 148 # the hello wireproto command uses wireproto.capabilities, so it won't see
149 149 # our largefiles capability unless we replace the actual function as well.
150 150 proto.capabilitiesorig = wireproto.capabilities
151 151 wireproto.capabilities = proto.capabilities
152 152
153 153 # can't do this in reposetup because it needs to have happened before
154 154 # wirerepo.__init__ is called
155 155 proto.ssholdcallstream = sshpeer.sshpeer._callstream
156 156 proto.httpoldcallstream = httppeer.httppeer._callstream
157 157 sshpeer.sshpeer._callstream = proto.sshrepocallstream
158 158 httppeer.httppeer._callstream = proto.httprepocallstream
159 159
160 160 # override some extensions' stuff as well
161 161 for name, module in extensions.extensions():
162 162 if name == 'purge':
163 163 extensions.wrapcommand(getattr(module, 'cmdtable'), 'purge',
164 164 overrides.overridepurge)
165 165 if name == 'rebase':
166 166 extensions.wrapcommand(getattr(module, 'cmdtable'), 'rebase',
167 167 overrides.overriderebase)
168 168 extensions.wrapfunction(module, 'rebase',
169 169 overrides.overriderebase)
170 170 if name == 'transplant':
171 171 extensions.wrapcommand(getattr(module, 'cmdtable'), 'transplant',
172 172 overrides.overridetransplant)
@@ -1,571 +1,574 b''
1 1 # filemerge.py - file-level merge handling for Mercurial
2 2 #
3 3 # Copyright 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import filecmp
11 11 import os
12 12 import re
13 13 import tempfile
14 14
15 15 from .i18n import _
16 16 from .node import short
17 17
18 18 from . import (
19 19 error,
20 20 match,
21 21 simplemerge,
22 22 tagmerge,
23 23 templatekw,
24 24 templater,
25 25 util,
26 26 )
27 27
28 28 def _toolstr(ui, tool, part, default=""):
29 29 return ui.config("merge-tools", tool + "." + part, default)
30 30
31 31 def _toolbool(ui, tool, part, default=False):
32 32 return ui.configbool("merge-tools", tool + "." + part, default)
33 33
34 34 def _toollist(ui, tool, part, default=[]):
35 35 return ui.configlist("merge-tools", tool + "." + part, default)
36 36
37 37 internals = {}
38 38 # Merge tools to document.
39 39 internalsdoc = {}
40 40
41 41 # internal tool merge types
42 42 nomerge = None
43 43 mergeonly = 'mergeonly' # just the full merge, no premerge
44 44 fullmerge = 'fullmerge' # both premerge and merge
45 45
46 46 def internaltool(name, mergetype, onfailure=None, precheck=None):
47 47 '''return a decorator for populating internal merge tool table'''
48 48 def decorator(func):
49 49 fullname = ':' + name
50 50 func.__doc__ = "``%s``\n" % fullname + func.__doc__.strip()
51 51 internals[fullname] = func
52 52 internals['internal:' + name] = func
53 53 internalsdoc[fullname] = func
54 54 func.mergetype = mergetype
55 55 func.onfailure = onfailure
56 56 func.precheck = precheck
57 57 return func
58 58 return decorator
59 59
60 60 def _findtool(ui, tool):
61 61 if tool in internals:
62 62 return tool
63 63 return findexternaltool(ui, tool)
64 64
65 65 def findexternaltool(ui, tool):
66 66 for kn in ("regkey", "regkeyalt"):
67 67 k = _toolstr(ui, tool, kn)
68 68 if not k:
69 69 continue
70 70 p = util.lookupreg(k, _toolstr(ui, tool, "regname"))
71 71 if p:
72 72 p = util.findexe(p + _toolstr(ui, tool, "regappend"))
73 73 if p:
74 74 return p
75 75 exe = _toolstr(ui, tool, "executable", tool)
76 76 return util.findexe(util.expandpath(exe))
77 77
78 78 def _picktool(repo, ui, path, binary, symlink):
79 79 def check(tool, pat, symlink, binary):
80 80 tmsg = tool
81 81 if pat:
82 82 tmsg += " specified for " + pat
83 83 if not _findtool(ui, tool):
84 84 if pat: # explicitly requested tool deserves a warning
85 85 ui.warn(_("couldn't find merge tool %s\n") % tmsg)
86 86 else: # configured but non-existing tools are more silent
87 87 ui.note(_("couldn't find merge tool %s\n") % tmsg)
88 88 elif symlink and not _toolbool(ui, tool, "symlink"):
89 89 ui.warn(_("tool %s can't handle symlinks\n") % tmsg)
90 90 elif binary and not _toolbool(ui, tool, "binary"):
91 91 ui.warn(_("tool %s can't handle binary\n") % tmsg)
92 92 elif not util.gui() and _toolbool(ui, tool, "gui"):
93 93 ui.warn(_("tool %s requires a GUI\n") % tmsg)
94 94 else:
95 95 return True
96 96 return False
97 97
98 98 # internal config: ui.forcemerge
99 99 # forcemerge comes from command line arguments, highest priority
100 100 force = ui.config('ui', 'forcemerge')
101 101 if force:
102 102 toolpath = _findtool(ui, force)
103 103 if toolpath:
104 104 return (force, util.shellquote(toolpath))
105 105 else:
106 106 # mimic HGMERGE if given tool not found
107 107 return (force, force)
108 108
109 109 # HGMERGE takes next precedence
110 110 hgmerge = os.environ.get("HGMERGE")
111 111 if hgmerge:
112 112 return (hgmerge, hgmerge)
113 113
114 114 # then patterns
115 115 for pat, tool in ui.configitems("merge-patterns"):
116 116 mf = match.match(repo.root, '', [pat])
117 117 if mf(path) and check(tool, pat, symlink, False):
118 118 toolpath = _findtool(ui, tool)
119 119 return (tool, util.shellquote(toolpath))
120 120
121 121 # then merge tools
122 122 tools = {}
123 123 for k, v in ui.configitems("merge-tools"):
124 124 t = k.split('.')[0]
125 125 if t not in tools:
126 126 tools[t] = int(_toolstr(ui, t, "priority", "0"))
127 127 names = tools.keys()
128 128 tools = sorted([(-p, t) for t, p in tools.items()])
129 129 uimerge = ui.config("ui", "merge")
130 130 if uimerge:
131 131 if uimerge not in names:
132 132 return (uimerge, uimerge)
133 133 tools.insert(0, (None, uimerge)) # highest priority
134 134 tools.append((None, "hgmerge")) # the old default, if found
135 135 for p, t in tools:
136 136 if check(t, None, symlink, binary):
137 137 toolpath = _findtool(ui, t)
138 138 return (t, util.shellquote(toolpath))
139 139
140 140 # internal merge or prompt as last resort
141 141 if symlink or binary:
142 142 return ":prompt", None
143 143 return ":merge", None
144 144
145 145 def _eoltype(data):
146 146 "Guess the EOL type of a file"
147 147 if '\0' in data: # binary
148 148 return None
149 149 if '\r\n' in data: # Windows
150 150 return '\r\n'
151 151 if '\r' in data: # Old Mac
152 152 return '\r'
153 153 if '\n' in data: # UNIX
154 154 return '\n'
155 155 return None # unknown
156 156
157 157 def _matcheol(file, origfile):
158 158 "Convert EOL markers in a file to match origfile"
159 159 tostyle = _eoltype(util.readfile(origfile))
160 160 if tostyle:
161 161 data = util.readfile(file)
162 162 style = _eoltype(data)
163 163 if style:
164 164 newdata = data.replace(style, tostyle)
165 165 if newdata != data:
166 166 util.writefile(file, newdata)
167 167
168 168 @internaltool('prompt', nomerge)
169 169 def _iprompt(repo, mynode, orig, fcd, fco, fca, toolconf):
170 170 """Asks the user which of the local or the other version to keep as
171 171 the merged version."""
172 172 ui = repo.ui
173 173 fd = fcd.path()
174 174
175 175 if ui.promptchoice(_(" no tool found to merge %s\n"
176 176 "keep (l)ocal or take (o)ther?"
177 177 "$$ &Local $$ &Other") % fd, 0):
178 178 return _iother(repo, mynode, orig, fcd, fco, fca, toolconf)
179 179 else:
180 180 return _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf)
181 181
182 182 @internaltool('local', nomerge)
183 183 def _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf):
184 184 """Uses the local version of files as the merged version."""
185 185 return 0
186 186
187 187 @internaltool('other', nomerge)
188 188 def _iother(repo, mynode, orig, fcd, fco, fca, toolconf):
189 189 """Uses the other version of files as the merged version."""
190 190 repo.wwrite(fcd.path(), fco.data(), fco.flags())
191 191 return 0
192 192
193 193 @internaltool('fail', nomerge)
194 194 def _ifail(repo, mynode, orig, fcd, fco, fca, toolconf):
195 195 """
196 196 Rather than attempting to merge files that were modified on both
197 197 branches, it marks them as unresolved. The resolve command must be
198 198 used to resolve these conflicts."""
199 199 return 1
200 200
201 201 def _premerge(repo, toolconf, files, labels=None):
202 202 tool, toolpath, binary, symlink = toolconf
203 203 if symlink:
204 204 return 1
205 205 a, b, c, back = files
206 206
207 207 ui = repo.ui
208 208
209 209 validkeep = ['keep', 'keep-merge3']
210 210
211 211 # do we attempt to simplemerge first?
212 212 try:
213 213 premerge = _toolbool(ui, tool, "premerge", not binary)
214 214 except error.ConfigError:
215 215 premerge = _toolstr(ui, tool, "premerge").lower()
216 216 if premerge not in validkeep:
217 217 _valid = ', '.join(["'" + v + "'" for v in validkeep])
218 218 raise error.ConfigError(_("%s.premerge not valid "
219 219 "('%s' is neither boolean nor %s)") %
220 220 (tool, premerge, _valid))
221 221
222 222 if premerge:
223 223 if premerge == 'keep-merge3':
224 224 if not labels:
225 225 labels = _defaultconflictlabels
226 226 if len(labels) < 3:
227 227 labels.append('base')
228 228 r = simplemerge.simplemerge(ui, a, b, c, quiet=True, label=labels)
229 229 if not r:
230 230 ui.debug(" premerge successful\n")
231 231 return 0
232 232 if premerge not in validkeep:
233 233 util.copyfile(back, a) # restore from backup and try again
234 234 return 1 # continue merging
235 235
236 236 def _symlinkcheck(repo, mynode, orig, fcd, fco, fca, toolconf):
237 237 tool, toolpath, binary, symlink = toolconf
238 238 if symlink:
239 239 repo.ui.warn(_('warning: internal %s cannot merge symlinks '
240 240 'for %s\n') % (tool, fcd.path()))
241 241 return False
242 242 return True
243 243
244 244 def _merge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels, mode):
245 245 """
246 246 Uses the internal non-interactive simple merge algorithm for merging
247 247 files. It will fail if there are any conflicts and leave markers in
248 248 the partially merged file. Markers will have two sections, one for each side
249 249 of merge, unless mode equals 'union' which suppresses the markers."""
250 250 a, b, c, back = files
251 251
252 252 ui = repo.ui
253 253
254 254 r = simplemerge.simplemerge(ui, a, b, c, label=labels, mode=mode)
255 255 return True, r
256 256
257 257 @internaltool('union', fullmerge,
258 258 _("merging %s incomplete! "
259 259 "(edit conflicts, then use 'hg resolve --mark')\n"),
260 260 precheck=_symlinkcheck)
261 261 def _iunion(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
262 262 """
263 263 Uses the internal non-interactive simple merge algorithm for merging
264 264 files. It will use both left and right sides for conflict regions.
265 265 No markers are inserted."""
266 266 return _merge(repo, mynode, orig, fcd, fco, fca, toolconf,
267 267 files, labels, 'union')
268 268
269 269 @internaltool('merge', fullmerge,
270 270 _("merging %s incomplete! "
271 271 "(edit conflicts, then use 'hg resolve --mark')\n"),
272 272 precheck=_symlinkcheck)
273 273 def _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
274 274 """
275 275 Uses the internal non-interactive simple merge algorithm for merging
276 276 files. It will fail if there are any conflicts and leave markers in
277 277 the partially merged file. Markers will have two sections, one for each side
278 278 of merge."""
279 279 return _merge(repo, mynode, orig, fcd, fco, fca, toolconf,
280 280 files, labels, 'merge')
281 281
282 282 @internaltool('merge3', fullmerge,
283 283 _("merging %s incomplete! "
284 284 "(edit conflicts, then use 'hg resolve --mark')\n"),
285 285 precheck=_symlinkcheck)
286 286 def _imerge3(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
287 287 """
288 288 Uses the internal non-interactive simple merge algorithm for merging
289 289 files. It will fail if there are any conflicts and leave markers in
290 290 the partially merged file. Marker will have three sections, one from each
291 291 side of the merge and one for the base content."""
292 292 if not labels:
293 293 labels = _defaultconflictlabels
294 294 if len(labels) < 3:
295 295 labels.append('base')
296 296 return _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels)
297 297
298 298 def _imergeauto(repo, mynode, orig, fcd, fco, fca, toolconf, files,
299 299 labels=None, localorother=None):
300 300 """
301 301 Generic driver for _imergelocal and _imergeother
302 302 """
303 303 assert localorother is not None
304 304 tool, toolpath, binary, symlink = toolconf
305 305 if symlink:
306 306 repo.ui.warn(_('warning: :merge-%s cannot merge symlinks '
307 307 'for %s\n') % (localorother, fcd.path()))
308 308 return False, 1
309 309 a, b, c, back = files
310 310 r = simplemerge.simplemerge(repo.ui, a, b, c, label=labels,
311 311 localorother=localorother)
312 312 return True, r
313 313
314 314 @internaltool('merge-local', mergeonly)
315 315 def _imergelocal(*args, **kwargs):
316 316 """
317 317 Like :merge, but resolve all conflicts non-interactively in favor
318 318 of the local changes."""
319 319 success, status = _imergeauto(localorother='local', *args, **kwargs)
320 320 return success, status
321 321
322 322 @internaltool('merge-other', mergeonly)
323 323 def _imergeother(*args, **kwargs):
324 324 """
325 325 Like :merge, but resolve all conflicts non-interactively in favor
326 326 of the other changes."""
327 327 success, status = _imergeauto(localorother='other', *args, **kwargs)
328 328 return success, status
329 329
330 330 @internaltool('tagmerge', mergeonly,
331 331 _("automatic tag merging of %s failed! "
332 332 "(use 'hg resolve --tool :merge' or another merge "
333 333 "tool of your choice)\n"))
334 334 def _itagmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
335 335 """
336 336 Uses the internal tag merge algorithm (experimental).
337 337 """
338 338 return tagmerge.merge(repo, fcd, fco, fca)
339 339
340 340 @internaltool('dump', fullmerge)
341 341 def _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
342 342 """
343 343 Creates three versions of the files to merge, containing the
344 344 contents of local, other and base. These files can then be used to
345 345 perform a merge manually. If the file to be merged is named
346 346 ``a.txt``, these files will accordingly be named ``a.txt.local``,
347 347 ``a.txt.other`` and ``a.txt.base`` and they will be placed in the
348 348 same directory as ``a.txt``."""
349 349 a, b, c, back = files
350 350
351 351 fd = fcd.path()
352 352
353 353 util.copyfile(a, a + ".local")
354 354 repo.wwrite(fd + ".other", fco.data(), fco.flags())
355 355 repo.wwrite(fd + ".base", fca.data(), fca.flags())
356 356 return False, 1
357 357
358 358 def _xmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
359 359 tool, toolpath, binary, symlink = toolconf
360 360 a, b, c, back = files
361 361 out = ""
362 362 env = {'HG_FILE': fcd.path(),
363 363 'HG_MY_NODE': short(mynode),
364 364 'HG_OTHER_NODE': str(fco.changectx()),
365 365 'HG_BASE_NODE': str(fca.changectx()),
366 366 'HG_MY_ISLINK': 'l' in fcd.flags(),
367 367 'HG_OTHER_ISLINK': 'l' in fco.flags(),
368 368 'HG_BASE_ISLINK': 'l' in fca.flags(),
369 369 }
370 370
371 371 ui = repo.ui
372 372
373 373 args = _toolstr(ui, tool, "args", '$local $base $other')
374 374 if "$output" in args:
375 375 out, a = a, back # read input from backup, write to original
376 376 replace = {'local': a, 'base': b, 'other': c, 'output': out}
377 377 args = util.interpolate(r'\$', replace, args,
378 378 lambda s: util.shellquote(util.localpath(s)))
379 379 cmd = toolpath + ' ' + args
380 380 repo.ui.debug('launching merge tool: %s\n' % cmd)
381 381 r = ui.system(cmd, cwd=repo.root, environ=env)
382 382 repo.ui.debug('merge tool returned: %s\n' % r)
383 383 return True, r
384 384
385 385 def _formatconflictmarker(repo, ctx, template, label, pad):
386 386 """Applies the given template to the ctx, prefixed by the label.
387 387
388 388 Pad is the minimum width of the label prefix, so that multiple markers
389 389 can have aligned templated parts.
390 390 """
391 391 if ctx.node() is None:
392 392 ctx = ctx.p1()
393 393
394 394 props = templatekw.keywords.copy()
395 395 props['templ'] = template
396 396 props['ctx'] = ctx
397 397 props['repo'] = repo
398 398 templateresult = template('conflictmarker', **props)
399 399
400 400 label = ('%s:' % label).ljust(pad + 1)
401 401 mark = '%s %s' % (label, templater.stringify(templateresult))
402 402
403 403 if mark:
404 404 mark = mark.splitlines()[0] # split for safety
405 405
406 406 # 8 for the prefix of conflict marker lines (e.g. '<<<<<<< ')
407 407 return util.ellipsis(mark, 80 - 8)
408 408
409 409 _defaultconflictmarker = ('{node|short} ' +
410 410 '{ifeq(tags, "tip", "", "{tags} ")}' +
411 411 '{if(bookmarks, "{bookmarks} ")}' +
412 412 '{ifeq(branch, "default", "", "{branch} ")}' +
413 413 '- {author|user}: {desc|firstline}')
414 414
415 415 _defaultconflictlabels = ['local', 'other']
416 416
417 417 def _formatlabels(repo, fcd, fco, fca, labels):
418 418 """Formats the given labels using the conflict marker template.
419 419
420 420 Returns a list of formatted labels.
421 421 """
422 422 cd = fcd.changectx()
423 423 co = fco.changectx()
424 424 ca = fca.changectx()
425 425
426 426 ui = repo.ui
427 427 template = ui.config('ui', 'mergemarkertemplate', _defaultconflictmarker)
428 428 tmpl = templater.templater(None, cache={'conflictmarker': template})
429 429
430 430 pad = max(len(l) for l in labels)
431 431
432 432 newlabels = [_formatconflictmarker(repo, cd, tmpl, labels[0], pad),
433 433 _formatconflictmarker(repo, co, tmpl, labels[1], pad)]
434 434 if len(labels) > 2:
435 435 newlabels.append(_formatconflictmarker(repo, ca, tmpl, labels[2], pad))
436 436 return newlabels
437 437
438 def filemerge(repo, mynode, orig, fcd, fco, fca, labels=None):
438 def _filemerge(repo, mynode, orig, fcd, fco, fca, labels=None):
439 439 """perform a 3-way merge in the working directory
440 440
441 441 mynode = parent node before merge
442 442 orig = original local filename before merge
443 443 fco = other file context
444 444 fca = ancestor file context
445 445 fcd = local file context for current/destination file
446 446 """
447 447
448 448 if True:
449 449 def temp(prefix, ctx):
450 450 pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
451 451 (fd, name) = tempfile.mkstemp(prefix=pre)
452 452 data = repo.wwritedata(ctx.path(), ctx.data())
453 453 f = os.fdopen(fd, "wb")
454 454 f.write(data)
455 455 f.close()
456 456 return name
457 457
458 458 if not fco.cmp(fcd): # files identical?
459 459 return None
460 460
461 461 ui = repo.ui
462 462 fd = fcd.path()
463 463 binary = fcd.isbinary() or fco.isbinary() or fca.isbinary()
464 464 symlink = 'l' in fcd.flags() + fco.flags()
465 465 tool, toolpath = _picktool(repo, ui, fd, binary, symlink)
466 466 if tool in internals and tool.startswith('internal:'):
467 467 # normalize to new-style names (':merge' etc)
468 468 tool = tool[len('internal'):]
469 469 ui.debug("picked tool '%s' for %s (binary %s symlink %s)\n" %
470 470 (tool, fd, binary, symlink))
471 471
472 472 if tool in internals:
473 473 func = internals[tool]
474 474 mergetype = func.mergetype
475 475 onfailure = func.onfailure
476 476 precheck = func.precheck
477 477 else:
478 478 func = _xmerge
479 479 mergetype = fullmerge
480 480 onfailure = _("merging %s failed!\n")
481 481 precheck = None
482 482
483 483 toolconf = tool, toolpath, binary, symlink
484 484
485 485 if mergetype == nomerge:
486 486 return func(repo, mynode, orig, fcd, fco, fca, toolconf)
487 487
488 488 if orig != fco.path():
489 489 ui.status(_("merging %s and %s to %s\n") % (orig, fco.path(), fd))
490 490 else:
491 491 ui.status(_("merging %s\n") % fd)
492 492
493 493 ui.debug("my %s other %s ancestor %s\n" % (fcd, fco, fca))
494 494
495 495 if precheck and not precheck(repo, mynode, orig, fcd, fco, fca,
496 496 toolconf):
497 497 if onfailure:
498 498 ui.warn(onfailure % fd)
499 499 return 1
500 500
501 501 a = repo.wjoin(fd)
502 502 b = temp("base", fca)
503 503 c = temp("other", fco)
504 504 back = a + ".orig"
505 505 util.copyfile(a, back)
506 506 files = (a, b, c, back)
507 507
508 508 r = 1
509 509 try:
510 510 markerstyle = ui.config('ui', 'mergemarkers', 'basic')
511 511 if not labels:
512 512 labels = _defaultconflictlabels
513 513 if markerstyle != 'basic':
514 514 labels = _formatlabels(repo, fcd, fco, fca, labels)
515 515
516 516 if mergetype == fullmerge:
517 517 r = _premerge(repo, toolconf, files, labels=labels)
518 518
519 519 if not r: # premerge successfully merged the file
520 520 needcheck = False
521 521 else:
522 522 needcheck, r = func(repo, mynode, orig, fcd, fco, fca, toolconf,
523 523 files, labels=labels)
524 524
525 525 if needcheck:
526 526 r = _check(r, ui, tool, fcd, files)
527 527
528 528 if r:
529 529 if onfailure:
530 530 ui.warn(onfailure % fd)
531 531
532 532 return r
533 533 finally:
534 534 if not r:
535 535 util.unlink(back)
536 536 util.unlink(b)
537 537 util.unlink(c)
538 538
539 539 def _check(r, ui, tool, fcd, files):
540 540 fd = fcd.path()
541 541 a, b, c, back = files
542 542
543 543 if not r and (_toolbool(ui, tool, "checkconflicts") or
544 544 'conflicts' in _toollist(ui, tool, "check")):
545 545 if re.search("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcd.data(),
546 546 re.MULTILINE):
547 547 r = 1
548 548
549 549 checked = False
550 550 if 'prompt' in _toollist(ui, tool, "check"):
551 551 checked = True
552 552 if ui.promptchoice(_("was merge of '%s' successful (yn)?"
553 553 "$$ &Yes $$ &No") % fd, 1):
554 554 r = 1
555 555
556 556 if not r and not checked and (_toolbool(ui, tool, "checkchanged") or
557 557 'changed' in
558 558 _toollist(ui, tool, "check")):
559 559 if filecmp.cmp(a, back):
560 560 if ui.promptchoice(_(" output file %s appears unchanged\n"
561 561 "was merge successful (yn)?"
562 562 "$$ &Yes $$ &No") % fd, 1):
563 563 r = 1
564 564
565 565 if _toolbool(ui, tool, "fixeol"):
566 566 _matcheol(a, back)
567 567
568 568 return r
569 569
570 def filemerge(repo, mynode, orig, fcd, fco, fca, labels=None):
571 return _filemerge(repo, mynode, orig, fcd, fco, fca, labels=labels)
572
570 573 # tell hggettext to extract docstrings from these functions:
571 574 i18nfunctions = internals.values()
General Comments 0
You need to be logged in to leave comments. Login now