##// END OF EJS Templates
simplemerge: use ui.warn() for warnings
Steve Borho -
r8269:bb9f1397 default
parent child Browse files
Show More
@@ -1,67 +1,67 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2
2
3 from mercurial import demandimport
3 from mercurial import demandimport
4 demandimport.enable()
4 demandimport.enable()
5
5
6 import os, sys
6 import os, sys
7 from mercurial.i18n import _
7 from mercurial.i18n import _
8 from mercurial import simplemerge, fancyopts, util
8 from mercurial import simplemerge, fancyopts, util, ui
9
9
10 options = [('L', 'label', [], _('labels to use on conflict markers')),
10 options = [('L', 'label', [], _('labels to use on conflict markers')),
11 ('a', 'text', None, _('treat all files as text')),
11 ('a', 'text', None, _('treat all files as text')),
12 ('p', 'print', None,
12 ('p', 'print', None,
13 _('print results instead of overwriting LOCAL')),
13 _('print results instead of overwriting LOCAL')),
14 ('', 'no-minimal', None,
14 ('', 'no-minimal', None,
15 _('do not try to minimize conflict regions')),
15 _('do not try to minimize conflict regions')),
16 ('h', 'help', None, _('display help and exit')),
16 ('h', 'help', None, _('display help and exit')),
17 ('q', 'quiet', None, _('suppress output'))]
17 ('q', 'quiet', None, _('suppress output'))]
18
18
19 usage = _('''simplemerge [OPTS] LOCAL BASE OTHER
19 usage = _('''simplemerge [OPTS] LOCAL BASE OTHER
20
20
21 Simple three-way file merge utility with a minimal feature set.
21 Simple three-way file merge utility with a minimal feature set.
22
22
23 Apply to LOCAL the changes necessary to go from BASE to OTHER.
23 Apply to LOCAL the changes necessary to go from BASE to OTHER.
24
24
25 By default, LOCAL is overwritten with the results of this operation.
25 By default, LOCAL is overwritten with the results of this operation.
26 ''')
26 ''')
27
27
28 class ParseError(Exception):
28 class ParseError(Exception):
29 """Exception raised on errors in parsing the command line."""
29 """Exception raised on errors in parsing the command line."""
30
30
31 def showhelp():
31 def showhelp():
32 sys.stdout.write(usage)
32 sys.stdout.write(usage)
33 sys.stdout.write('\noptions:\n')
33 sys.stdout.write('\noptions:\n')
34
34
35 out_opts = []
35 out_opts = []
36 for shortopt, longopt, default, desc in options:
36 for shortopt, longopt, default, desc in options:
37 out_opts.append(('%2s%s' % (shortopt and '-%s' % shortopt,
37 out_opts.append(('%2s%s' % (shortopt and '-%s' % shortopt,
38 longopt and ' --%s' % longopt),
38 longopt and ' --%s' % longopt),
39 '%s' % desc))
39 '%s' % desc))
40 opts_len = max([len(opt[0]) for opt in out_opts])
40 opts_len = max([len(opt[0]) for opt in out_opts])
41 for first, second in out_opts:
41 for first, second in out_opts:
42 sys.stdout.write(' %-*s %s\n' % (opts_len, first, second))
42 sys.stdout.write(' %-*s %s\n' % (opts_len, first, second))
43
43
44 try:
44 try:
45 for fp in (sys.stdin, sys.stdout, sys.stderr):
45 for fp in (sys.stdin, sys.stdout, sys.stderr):
46 util.set_binary(fp)
46 util.set_binary(fp)
47
47
48 opts = {}
48 opts = {}
49 try:
49 try:
50 args = fancyopts.fancyopts(sys.argv[1:], options, opts)
50 args = fancyopts.fancyopts(sys.argv[1:], options, opts)
51 except fancyopts.getopt.GetoptError, e:
51 except fancyopts.getopt.GetoptError, e:
52 raise ParseError(e)
52 raise ParseError(e)
53 if opts['help']:
53 if opts['help']:
54 showhelp()
54 showhelp()
55 sys.exit(0)
55 sys.exit(0)
56 if len(args) != 3:
56 if len(args) != 3:
57 raise ParseError(_('wrong number of arguments'))
57 raise ParseError(_('wrong number of arguments'))
58 sys.exit(simplemerge.simplemerge(*args, **opts))
58 sys.exit(simplemerge.simplemerge(ui.ui(), *args, **opts))
59 except ParseError, e:
59 except ParseError, e:
60 sys.stdout.write("%s: %s\n" % (sys.argv[0], e))
60 sys.stdout.write("%s: %s\n" % (sys.argv[0], e))
61 showhelp()
61 showhelp()
62 sys.exit(1)
62 sys.exit(1)
63 except util.Abort, e:
63 except util.Abort, e:
64 sys.stderr.write("abort: %s\n" % e)
64 sys.stderr.write("abort: %s\n" % e)
65 sys.exit(255)
65 sys.exit(255)
66 except KeyboardInterrupt:
66 except KeyboardInterrupt:
67 sys.exit(255)
67 sys.exit(255)
@@ -1,221 +1,221 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, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 from node import short
8 from node import short
9 from i18n import _
9 from i18n import _
10 import util, os, tempfile, simplemerge, re, filecmp
10 import util, os, tempfile, simplemerge, re, filecmp
11
11
12 def _toolstr(ui, tool, part, default=""):
12 def _toolstr(ui, tool, part, default=""):
13 return ui.config("merge-tools", tool + "." + part, default)
13 return ui.config("merge-tools", tool + "." + part, default)
14
14
15 def _toolbool(ui, tool, part, default=False):
15 def _toolbool(ui, tool, part, default=False):
16 return ui.configbool("merge-tools", tool + "." + part, default)
16 return ui.configbool("merge-tools", tool + "." + part, default)
17
17
18 def _findtool(ui, tool):
18 def _findtool(ui, tool):
19 if tool in ("internal:fail", "internal:local", "internal:other"):
19 if tool in ("internal:fail", "internal:local", "internal:other"):
20 return tool
20 return tool
21 k = _toolstr(ui, tool, "regkey")
21 k = _toolstr(ui, tool, "regkey")
22 if k:
22 if k:
23 p = util.lookup_reg(k, _toolstr(ui, tool, "regname"))
23 p = util.lookup_reg(k, _toolstr(ui, tool, "regname"))
24 if p:
24 if p:
25 p = util.find_exe(p + _toolstr(ui, tool, "regappend"))
25 p = util.find_exe(p + _toolstr(ui, tool, "regappend"))
26 if p:
26 if p:
27 return p
27 return p
28 return util.find_exe(_toolstr(ui, tool, "executable", tool))
28 return util.find_exe(_toolstr(ui, tool, "executable", tool))
29
29
30 def _picktool(repo, ui, path, binary, symlink):
30 def _picktool(repo, ui, path, binary, symlink):
31 def check(tool, pat, symlink, binary):
31 def check(tool, pat, symlink, binary):
32 tmsg = tool
32 tmsg = tool
33 if pat:
33 if pat:
34 tmsg += " specified for " + pat
34 tmsg += " specified for " + pat
35 if not _findtool(ui, tool):
35 if not _findtool(ui, tool):
36 if pat: # explicitly requested tool deserves a warning
36 if pat: # explicitly requested tool deserves a warning
37 ui.warn(_("couldn't find merge tool %s\n") % tmsg)
37 ui.warn(_("couldn't find merge tool %s\n") % tmsg)
38 else: # configured but non-existing tools are more silent
38 else: # configured but non-existing tools are more silent
39 ui.note(_("couldn't find merge tool %s\n") % tmsg)
39 ui.note(_("couldn't find merge tool %s\n") % tmsg)
40 elif symlink and not _toolbool(ui, tool, "symlink"):
40 elif symlink and not _toolbool(ui, tool, "symlink"):
41 ui.warn(_("tool %s can't handle symlinks\n") % tmsg)
41 ui.warn(_("tool %s can't handle symlinks\n") % tmsg)
42 elif binary and not _toolbool(ui, tool, "binary"):
42 elif binary and not _toolbool(ui, tool, "binary"):
43 ui.warn(_("tool %s can't handle binary\n") % tmsg)
43 ui.warn(_("tool %s can't handle binary\n") % tmsg)
44 elif not util.gui() and _toolbool(ui, tool, "gui"):
44 elif not util.gui() and _toolbool(ui, tool, "gui"):
45 ui.warn(_("tool %s requires a GUI\n") % tmsg)
45 ui.warn(_("tool %s requires a GUI\n") % tmsg)
46 else:
46 else:
47 return True
47 return True
48 return False
48 return False
49
49
50 # HGMERGE takes precedence
50 # HGMERGE takes precedence
51 hgmerge = os.environ.get("HGMERGE")
51 hgmerge = os.environ.get("HGMERGE")
52 if hgmerge:
52 if hgmerge:
53 return (hgmerge, hgmerge)
53 return (hgmerge, hgmerge)
54
54
55 # then patterns
55 # then patterns
56 for pat, tool in ui.configitems("merge-patterns"):
56 for pat, tool in ui.configitems("merge-patterns"):
57 mf = util.matcher(repo.root, "", [pat], [], [])[1]
57 mf = util.matcher(repo.root, "", [pat], [], [])[1]
58 if mf(path) and check(tool, pat, symlink, False):
58 if mf(path) and check(tool, pat, symlink, False):
59 toolpath = _findtool(ui, tool)
59 toolpath = _findtool(ui, tool)
60 return (tool, '"' + toolpath + '"')
60 return (tool, '"' + toolpath + '"')
61
61
62 # then merge tools
62 # then merge tools
63 tools = {}
63 tools = {}
64 for k,v in ui.configitems("merge-tools"):
64 for k,v in ui.configitems("merge-tools"):
65 t = k.split('.')[0]
65 t = k.split('.')[0]
66 if t not in tools:
66 if t not in tools:
67 tools[t] = int(_toolstr(ui, t, "priority", "0"))
67 tools[t] = int(_toolstr(ui, t, "priority", "0"))
68 names = tools.keys()
68 names = tools.keys()
69 tools = sorted([(-p,t) for t,p in tools.items()])
69 tools = sorted([(-p,t) for t,p in tools.items()])
70 uimerge = ui.config("ui", "merge")
70 uimerge = ui.config("ui", "merge")
71 if uimerge:
71 if uimerge:
72 if uimerge not in names:
72 if uimerge not in names:
73 return (uimerge, uimerge)
73 return (uimerge, uimerge)
74 tools.insert(0, (None, uimerge)) # highest priority
74 tools.insert(0, (None, uimerge)) # highest priority
75 tools.append((None, "hgmerge")) # the old default, if found
75 tools.append((None, "hgmerge")) # the old default, if found
76 for p,t in tools:
76 for p,t in tools:
77 if check(t, None, symlink, binary):
77 if check(t, None, symlink, binary):
78 toolpath = _findtool(ui, t)
78 toolpath = _findtool(ui, t)
79 return (t, '"' + toolpath + '"')
79 return (t, '"' + toolpath + '"')
80 # internal merge as last resort
80 # internal merge as last resort
81 return (not (symlink or binary) and "internal:merge" or None, None)
81 return (not (symlink or binary) and "internal:merge" or None, None)
82
82
83 def _eoltype(data):
83 def _eoltype(data):
84 "Guess the EOL type of a file"
84 "Guess the EOL type of a file"
85 if '\0' in data: # binary
85 if '\0' in data: # binary
86 return None
86 return None
87 if '\r\n' in data: # Windows
87 if '\r\n' in data: # Windows
88 return '\r\n'
88 return '\r\n'
89 if '\r' in data: # Old Mac
89 if '\r' in data: # Old Mac
90 return '\r'
90 return '\r'
91 if '\n' in data: # UNIX
91 if '\n' in data: # UNIX
92 return '\n'
92 return '\n'
93 return None # unknown
93 return None # unknown
94
94
95 def _matcheol(file, origfile):
95 def _matcheol(file, origfile):
96 "Convert EOL markers in a file to match origfile"
96 "Convert EOL markers in a file to match origfile"
97 tostyle = _eoltype(open(origfile, "rb").read())
97 tostyle = _eoltype(open(origfile, "rb").read())
98 if tostyle:
98 if tostyle:
99 data = open(file, "rb").read()
99 data = open(file, "rb").read()
100 style = _eoltype(data)
100 style = _eoltype(data)
101 if style:
101 if style:
102 newdata = data.replace(style, tostyle)
102 newdata = data.replace(style, tostyle)
103 if newdata != data:
103 if newdata != data:
104 open(file, "wb").write(newdata)
104 open(file, "wb").write(newdata)
105
105
106 def filemerge(repo, mynode, orig, fcd, fco, fca):
106 def filemerge(repo, mynode, orig, fcd, fco, fca):
107 """perform a 3-way merge in the working directory
107 """perform a 3-way merge in the working directory
108
108
109 mynode = parent node before merge
109 mynode = parent node before merge
110 orig = original local filename before merge
110 orig = original local filename before merge
111 fco = other file context
111 fco = other file context
112 fca = ancestor file context
112 fca = ancestor file context
113 fcd = local file context for current/destination file
113 fcd = local file context for current/destination file
114 """
114 """
115
115
116 def temp(prefix, ctx):
116 def temp(prefix, ctx):
117 pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
117 pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
118 (fd, name) = tempfile.mkstemp(prefix=pre)
118 (fd, name) = tempfile.mkstemp(prefix=pre)
119 data = repo.wwritedata(ctx.path(), ctx.data())
119 data = repo.wwritedata(ctx.path(), ctx.data())
120 f = os.fdopen(fd, "wb")
120 f = os.fdopen(fd, "wb")
121 f.write(data)
121 f.write(data)
122 f.close()
122 f.close()
123 return name
123 return name
124
124
125 def isbin(ctx):
125 def isbin(ctx):
126 try:
126 try:
127 return util.binary(ctx.data())
127 return util.binary(ctx.data())
128 except IOError:
128 except IOError:
129 return False
129 return False
130
130
131 if not fco.cmp(fcd.data()): # files identical?
131 if not fco.cmp(fcd.data()): # files identical?
132 return None
132 return None
133
133
134 ui = repo.ui
134 ui = repo.ui
135 fd = fcd.path()
135 fd = fcd.path()
136 binary = isbin(fcd) or isbin(fco) or isbin(fca)
136 binary = isbin(fcd) or isbin(fco) or isbin(fca)
137 symlink = 'l' in fcd.flags() + fco.flags()
137 symlink = 'l' in fcd.flags() + fco.flags()
138 tool, toolpath = _picktool(repo, ui, fd, binary, symlink)
138 tool, toolpath = _picktool(repo, ui, fd, binary, symlink)
139 ui.debug(_("picked tool '%s' for %s (binary %s symlink %s)\n") %
139 ui.debug(_("picked tool '%s' for %s (binary %s symlink %s)\n") %
140 (tool, fd, binary, symlink))
140 (tool, fd, binary, symlink))
141
141
142 if not tool:
142 if not tool:
143 tool = "internal:local"
143 tool = "internal:local"
144 if ui.prompt(_(" no tool found to merge %s\n"
144 if ui.prompt(_(" no tool found to merge %s\n"
145 "keep (l)ocal or take (o)ther?") % fd,
145 "keep (l)ocal or take (o)ther?") % fd,
146 (_("&Local"), _("&Other")), _("l")) != _("l"):
146 (_("&Local"), _("&Other")), _("l")) != _("l"):
147 tool = "internal:other"
147 tool = "internal:other"
148 if tool == "internal:local":
148 if tool == "internal:local":
149 return 0
149 return 0
150 if tool == "internal:other":
150 if tool == "internal:other":
151 repo.wwrite(fd, fco.data(), fco.flags())
151 repo.wwrite(fd, fco.data(), fco.flags())
152 return 0
152 return 0
153 if tool == "internal:fail":
153 if tool == "internal:fail":
154 return 1
154 return 1
155
155
156 # do the actual merge
156 # do the actual merge
157 a = repo.wjoin(fd)
157 a = repo.wjoin(fd)
158 b = temp("base", fca)
158 b = temp("base", fca)
159 c = temp("other", fco)
159 c = temp("other", fco)
160 out = ""
160 out = ""
161 back = a + ".orig"
161 back = a + ".orig"
162 util.copyfile(a, back)
162 util.copyfile(a, back)
163
163
164 if orig != fco.path():
164 if orig != fco.path():
165 repo.ui.status(_("merging %s and %s to %s\n") % (orig, fco.path(), fd))
165 repo.ui.status(_("merging %s and %s to %s\n") % (orig, fco.path(), fd))
166 else:
166 else:
167 repo.ui.status(_("merging %s\n") % fd)
167 repo.ui.status(_("merging %s\n") % fd)
168
168
169 repo.ui.debug(_("my %s other %s ancestor %s\n") % (fcd, fco, fca))
169 repo.ui.debug(_("my %s other %s ancestor %s\n") % (fcd, fco, fca))
170
170
171 # do we attempt to simplemerge first?
171 # do we attempt to simplemerge first?
172 if _toolbool(ui, tool, "premerge", not (binary or symlink)):
172 if _toolbool(ui, tool, "premerge", not (binary or symlink)):
173 r = simplemerge.simplemerge(a, b, c, quiet=True)
173 r = simplemerge.simplemerge(ui, a, b, c, quiet=True)
174 if not r:
174 if not r:
175 ui.debug(_(" premerge successful\n"))
175 ui.debug(_(" premerge successful\n"))
176 os.unlink(back)
176 os.unlink(back)
177 os.unlink(b)
177 os.unlink(b)
178 os.unlink(c)
178 os.unlink(c)
179 return 0
179 return 0
180 util.copyfile(back, a) # restore from backup and try again
180 util.copyfile(back, a) # restore from backup and try again
181
181
182 env = dict(HG_FILE=fd,
182 env = dict(HG_FILE=fd,
183 HG_MY_NODE=short(mynode),
183 HG_MY_NODE=short(mynode),
184 HG_OTHER_NODE=str(fco.changectx()),
184 HG_OTHER_NODE=str(fco.changectx()),
185 HG_MY_ISLINK='l' in fcd.flags(),
185 HG_MY_ISLINK='l' in fcd.flags(),
186 HG_OTHER_ISLINK='l' in fco.flags(),
186 HG_OTHER_ISLINK='l' in fco.flags(),
187 HG_BASE_ISLINK='l' in fca.flags())
187 HG_BASE_ISLINK='l' in fca.flags())
188
188
189 if tool == "internal:merge":
189 if tool == "internal:merge":
190 r = simplemerge.simplemerge(a, b, c, label=['local', 'other'])
190 r = simplemerge.simplemerge(ui, a, b, c, label=['local', 'other'])
191 else:
191 else:
192 args = _toolstr(ui, tool, "args", '$local $base $other')
192 args = _toolstr(ui, tool, "args", '$local $base $other')
193 if "$output" in args:
193 if "$output" in args:
194 out, a = a, back # read input from backup, write to original
194 out, a = a, back # read input from backup, write to original
195 replace = dict(local=a, base=b, other=c, output=out)
195 replace = dict(local=a, base=b, other=c, output=out)
196 args = re.sub("\$(local|base|other|output)",
196 args = re.sub("\$(local|base|other|output)",
197 lambda x: '"%s"' % replace[x.group()[1:]], args)
197 lambda x: '"%s"' % replace[x.group()[1:]], args)
198 r = util.system(toolpath + ' ' + args, cwd=repo.root, environ=env)
198 r = util.system(toolpath + ' ' + args, cwd=repo.root, environ=env)
199
199
200 if not r and _toolbool(ui, tool, "checkconflicts"):
200 if not r and _toolbool(ui, tool, "checkconflicts"):
201 if re.match("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcd.data()):
201 if re.match("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcd.data()):
202 r = 1
202 r = 1
203
203
204 if not r and _toolbool(ui, tool, "checkchanged"):
204 if not r and _toolbool(ui, tool, "checkchanged"):
205 if filecmp.cmp(repo.wjoin(fd), back):
205 if filecmp.cmp(repo.wjoin(fd), back):
206 if ui.prompt(_(" output file %s appears unchanged\n"
206 if ui.prompt(_(" output file %s appears unchanged\n"
207 "was merge successful (yn)?") % fd,
207 "was merge successful (yn)?") % fd,
208 (_("&Yes"), _("&No")), _("n")) != _("y"):
208 (_("&Yes"), _("&No")), _("n")) != _("y"):
209 r = 1
209 r = 1
210
210
211 if _toolbool(ui, tool, "fixeol"):
211 if _toolbool(ui, tool, "fixeol"):
212 _matcheol(repo.wjoin(fd), back)
212 _matcheol(repo.wjoin(fd), back)
213
213
214 if r:
214 if r:
215 repo.ui.warn(_("merging %s failed!\n") % fd)
215 repo.ui.warn(_("merging %s failed!\n") % fd)
216 else:
216 else:
217 os.unlink(back)
217 os.unlink(back)
218
218
219 os.unlink(b)
219 os.unlink(b)
220 os.unlink(c)
220 os.unlink(c)
221 return r
221 return r
@@ -1,455 +1,450 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # Copyright (C) 2004, 2005 Canonical Ltd
2 # Copyright (C) 2004, 2005 Canonical Ltd
3 #
3 #
4 # This program is free software; you can redistribute it and/or modify
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 2 of the License, or
6 # the Free Software Foundation; either version 2 of the License, or
7 # (at your option) any later version.
7 # (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
17
18 # mbp: "you know that thing where cvs gives you conflict markers?"
18 # mbp: "you know that thing where cvs gives you conflict markers?"
19 # s: "i hate that."
19 # s: "i hate that."
20
20
21 from i18n import _
21 from i18n import _
22 import util, mdiff, sys, os
22 import util, mdiff, sys, os
23
23
24 class CantReprocessAndShowBase(Exception):
24 class CantReprocessAndShowBase(Exception):
25 pass
25 pass
26
26
27 def warn(message):
28 sys.stdout.flush()
29 sys.stderr.write(message)
30 sys.stderr.flush()
31
32 def intersect(ra, rb):
27 def intersect(ra, rb):
33 """Given two ranges return the range where they intersect or None.
28 """Given two ranges return the range where they intersect or None.
34
29
35 >>> intersect((0, 10), (0, 6))
30 >>> intersect((0, 10), (0, 6))
36 (0, 6)
31 (0, 6)
37 >>> intersect((0, 10), (5, 15))
32 >>> intersect((0, 10), (5, 15))
38 (5, 10)
33 (5, 10)
39 >>> intersect((0, 10), (10, 15))
34 >>> intersect((0, 10), (10, 15))
40 >>> intersect((0, 9), (10, 15))
35 >>> intersect((0, 9), (10, 15))
41 >>> intersect((0, 9), (7, 15))
36 >>> intersect((0, 9), (7, 15))
42 (7, 9)
37 (7, 9)
43 """
38 """
44 assert ra[0] <= ra[1]
39 assert ra[0] <= ra[1]
45 assert rb[0] <= rb[1]
40 assert rb[0] <= rb[1]
46
41
47 sa = max(ra[0], rb[0])
42 sa = max(ra[0], rb[0])
48 sb = min(ra[1], rb[1])
43 sb = min(ra[1], rb[1])
49 if sa < sb:
44 if sa < sb:
50 return sa, sb
45 return sa, sb
51 else:
46 else:
52 return None
47 return None
53
48
54 def compare_range(a, astart, aend, b, bstart, bend):
49 def compare_range(a, astart, aend, b, bstart, bend):
55 """Compare a[astart:aend] == b[bstart:bend], without slicing.
50 """Compare a[astart:aend] == b[bstart:bend], without slicing.
56 """
51 """
57 if (aend-astart) != (bend-bstart):
52 if (aend-astart) != (bend-bstart):
58 return False
53 return False
59 for ia, ib in zip(xrange(astart, aend), xrange(bstart, bend)):
54 for ia, ib in zip(xrange(astart, aend), xrange(bstart, bend)):
60 if a[ia] != b[ib]:
55 if a[ia] != b[ib]:
61 return False
56 return False
62 else:
57 else:
63 return True
58 return True
64
59
65 class Merge3Text(object):
60 class Merge3Text(object):
66 """3-way merge of texts.
61 """3-way merge of texts.
67
62
68 Given strings BASE, OTHER, THIS, tries to produce a combined text
63 Given strings BASE, OTHER, THIS, tries to produce a combined text
69 incorporating the changes from both BASE->OTHER and BASE->THIS."""
64 incorporating the changes from both BASE->OTHER and BASE->THIS."""
70 def __init__(self, basetext, atext, btext, base=None, a=None, b=None):
65 def __init__(self, basetext, atext, btext, base=None, a=None, b=None):
71 self.basetext = basetext
66 self.basetext = basetext
72 self.atext = atext
67 self.atext = atext
73 self.btext = btext
68 self.btext = btext
74 if base is None:
69 if base is None:
75 base = mdiff.splitnewlines(basetext)
70 base = mdiff.splitnewlines(basetext)
76 if a is None:
71 if a is None:
77 a = mdiff.splitnewlines(atext)
72 a = mdiff.splitnewlines(atext)
78 if b is None:
73 if b is None:
79 b = mdiff.splitnewlines(btext)
74 b = mdiff.splitnewlines(btext)
80 self.base = base
75 self.base = base
81 self.a = a
76 self.a = a
82 self.b = b
77 self.b = b
83
78
84 def merge_lines(self,
79 def merge_lines(self,
85 name_a=None,
80 name_a=None,
86 name_b=None,
81 name_b=None,
87 name_base=None,
82 name_base=None,
88 start_marker='<<<<<<<',
83 start_marker='<<<<<<<',
89 mid_marker='=======',
84 mid_marker='=======',
90 end_marker='>>>>>>>',
85 end_marker='>>>>>>>',
91 base_marker=None,
86 base_marker=None,
92 reprocess=False):
87 reprocess=False):
93 """Return merge in cvs-like form.
88 """Return merge in cvs-like form.
94 """
89 """
95 self.conflicts = False
90 self.conflicts = False
96 newline = '\n'
91 newline = '\n'
97 if len(self.a) > 0:
92 if len(self.a) > 0:
98 if self.a[0].endswith('\r\n'):
93 if self.a[0].endswith('\r\n'):
99 newline = '\r\n'
94 newline = '\r\n'
100 elif self.a[0].endswith('\r'):
95 elif self.a[0].endswith('\r'):
101 newline = '\r'
96 newline = '\r'
102 if base_marker and reprocess:
97 if base_marker and reprocess:
103 raise CantReprocessAndShowBase()
98 raise CantReprocessAndShowBase()
104 if name_a:
99 if name_a:
105 start_marker = start_marker + ' ' + name_a
100 start_marker = start_marker + ' ' + name_a
106 if name_b:
101 if name_b:
107 end_marker = end_marker + ' ' + name_b
102 end_marker = end_marker + ' ' + name_b
108 if name_base and base_marker:
103 if name_base and base_marker:
109 base_marker = base_marker + ' ' + name_base
104 base_marker = base_marker + ' ' + name_base
110 merge_regions = self.merge_regions()
105 merge_regions = self.merge_regions()
111 if reprocess is True:
106 if reprocess is True:
112 merge_regions = self.reprocess_merge_regions(merge_regions)
107 merge_regions = self.reprocess_merge_regions(merge_regions)
113 for t in merge_regions:
108 for t in merge_regions:
114 what = t[0]
109 what = t[0]
115 if what == 'unchanged':
110 if what == 'unchanged':
116 for i in range(t[1], t[2]):
111 for i in range(t[1], t[2]):
117 yield self.base[i]
112 yield self.base[i]
118 elif what == 'a' or what == 'same':
113 elif what == 'a' or what == 'same':
119 for i in range(t[1], t[2]):
114 for i in range(t[1], t[2]):
120 yield self.a[i]
115 yield self.a[i]
121 elif what == 'b':
116 elif what == 'b':
122 for i in range(t[1], t[2]):
117 for i in range(t[1], t[2]):
123 yield self.b[i]
118 yield self.b[i]
124 elif what == 'conflict':
119 elif what == 'conflict':
125 self.conflicts = True
120 self.conflicts = True
126 yield start_marker + newline
121 yield start_marker + newline
127 for i in range(t[3], t[4]):
122 for i in range(t[3], t[4]):
128 yield self.a[i]
123 yield self.a[i]
129 if base_marker is not None:
124 if base_marker is not None:
130 yield base_marker + newline
125 yield base_marker + newline
131 for i in range(t[1], t[2]):
126 for i in range(t[1], t[2]):
132 yield self.base[i]
127 yield self.base[i]
133 yield mid_marker + newline
128 yield mid_marker + newline
134 for i in range(t[5], t[6]):
129 for i in range(t[5], t[6]):
135 yield self.b[i]
130 yield self.b[i]
136 yield end_marker + newline
131 yield end_marker + newline
137 else:
132 else:
138 raise ValueError(what)
133 raise ValueError(what)
139
134
140 def merge_annotated(self):
135 def merge_annotated(self):
141 """Return merge with conflicts, showing origin of lines.
136 """Return merge with conflicts, showing origin of lines.
142
137
143 Most useful for debugging merge.
138 Most useful for debugging merge.
144 """
139 """
145 for t in self.merge_regions():
140 for t in self.merge_regions():
146 what = t[0]
141 what = t[0]
147 if what == 'unchanged':
142 if what == 'unchanged':
148 for i in range(t[1], t[2]):
143 for i in range(t[1], t[2]):
149 yield 'u | ' + self.base[i]
144 yield 'u | ' + self.base[i]
150 elif what == 'a' or what == 'same':
145 elif what == 'a' or what == 'same':
151 for i in range(t[1], t[2]):
146 for i in range(t[1], t[2]):
152 yield what[0] + ' | ' + self.a[i]
147 yield what[0] + ' | ' + self.a[i]
153 elif what == 'b':
148 elif what == 'b':
154 for i in range(t[1], t[2]):
149 for i in range(t[1], t[2]):
155 yield 'b | ' + self.b[i]
150 yield 'b | ' + self.b[i]
156 elif what == 'conflict':
151 elif what == 'conflict':
157 yield '<<<<\n'
152 yield '<<<<\n'
158 for i in range(t[3], t[4]):
153 for i in range(t[3], t[4]):
159 yield 'A | ' + self.a[i]
154 yield 'A | ' + self.a[i]
160 yield '----\n'
155 yield '----\n'
161 for i in range(t[5], t[6]):
156 for i in range(t[5], t[6]):
162 yield 'B | ' + self.b[i]
157 yield 'B | ' + self.b[i]
163 yield '>>>>\n'
158 yield '>>>>\n'
164 else:
159 else:
165 raise ValueError(what)
160 raise ValueError(what)
166
161
167 def merge_groups(self):
162 def merge_groups(self):
168 """Yield sequence of line groups. Each one is a tuple:
163 """Yield sequence of line groups. Each one is a tuple:
169
164
170 'unchanged', lines
165 'unchanged', lines
171 Lines unchanged from base
166 Lines unchanged from base
172
167
173 'a', lines
168 'a', lines
174 Lines taken from a
169 Lines taken from a
175
170
176 'same', lines
171 'same', lines
177 Lines taken from a (and equal to b)
172 Lines taken from a (and equal to b)
178
173
179 'b', lines
174 'b', lines
180 Lines taken from b
175 Lines taken from b
181
176
182 'conflict', base_lines, a_lines, b_lines
177 'conflict', base_lines, a_lines, b_lines
183 Lines from base were changed to either a or b and conflict.
178 Lines from base were changed to either a or b and conflict.
184 """
179 """
185 for t in self.merge_regions():
180 for t in self.merge_regions():
186 what = t[0]
181 what = t[0]
187 if what == 'unchanged':
182 if what == 'unchanged':
188 yield what, self.base[t[1]:t[2]]
183 yield what, self.base[t[1]:t[2]]
189 elif what == 'a' or what == 'same':
184 elif what == 'a' or what == 'same':
190 yield what, self.a[t[1]:t[2]]
185 yield what, self.a[t[1]:t[2]]
191 elif what == 'b':
186 elif what == 'b':
192 yield what, self.b[t[1]:t[2]]
187 yield what, self.b[t[1]:t[2]]
193 elif what == 'conflict':
188 elif what == 'conflict':
194 yield (what,
189 yield (what,
195 self.base[t[1]:t[2]],
190 self.base[t[1]:t[2]],
196 self.a[t[3]:t[4]],
191 self.a[t[3]:t[4]],
197 self.b[t[5]:t[6]])
192 self.b[t[5]:t[6]])
198 else:
193 else:
199 raise ValueError(what)
194 raise ValueError(what)
200
195
201 def merge_regions(self):
196 def merge_regions(self):
202 """Return sequences of matching and conflicting regions.
197 """Return sequences of matching and conflicting regions.
203
198
204 This returns tuples, where the first value says what kind we
199 This returns tuples, where the first value says what kind we
205 have:
200 have:
206
201
207 'unchanged', start, end
202 'unchanged', start, end
208 Take a region of base[start:end]
203 Take a region of base[start:end]
209
204
210 'same', astart, aend
205 'same', astart, aend
211 b and a are different from base but give the same result
206 b and a are different from base but give the same result
212
207
213 'a', start, end
208 'a', start, end
214 Non-clashing insertion from a[start:end]
209 Non-clashing insertion from a[start:end]
215
210
216 Method is as follows:
211 Method is as follows:
217
212
218 The two sequences align only on regions which match the base
213 The two sequences align only on regions which match the base
219 and both descendents. These are found by doing a two-way diff
214 and both descendents. These are found by doing a two-way diff
220 of each one against the base, and then finding the
215 of each one against the base, and then finding the
221 intersections between those regions. These "sync regions"
216 intersections between those regions. These "sync regions"
222 are by definition unchanged in both and easily dealt with.
217 are by definition unchanged in both and easily dealt with.
223
218
224 The regions in between can be in any of three cases:
219 The regions in between can be in any of three cases:
225 conflicted, or changed on only one side.
220 conflicted, or changed on only one side.
226 """
221 """
227
222
228 # section a[0:ia] has been disposed of, etc
223 # section a[0:ia] has been disposed of, etc
229 iz = ia = ib = 0
224 iz = ia = ib = 0
230
225
231 for zmatch, zend, amatch, aend, bmatch, bend in self.find_sync_regions():
226 for zmatch, zend, amatch, aend, bmatch, bend in self.find_sync_regions():
232 #print 'match base [%d:%d]' % (zmatch, zend)
227 #print 'match base [%d:%d]' % (zmatch, zend)
233
228
234 matchlen = zend - zmatch
229 matchlen = zend - zmatch
235 assert matchlen >= 0
230 assert matchlen >= 0
236 assert matchlen == (aend - amatch)
231 assert matchlen == (aend - amatch)
237 assert matchlen == (bend - bmatch)
232 assert matchlen == (bend - bmatch)
238
233
239 len_a = amatch - ia
234 len_a = amatch - ia
240 len_b = bmatch - ib
235 len_b = bmatch - ib
241 len_base = zmatch - iz
236 len_base = zmatch - iz
242 assert len_a >= 0
237 assert len_a >= 0
243 assert len_b >= 0
238 assert len_b >= 0
244 assert len_base >= 0
239 assert len_base >= 0
245
240
246 #print 'unmatched a=%d, b=%d' % (len_a, len_b)
241 #print 'unmatched a=%d, b=%d' % (len_a, len_b)
247
242
248 if len_a or len_b:
243 if len_a or len_b:
249 # try to avoid actually slicing the lists
244 # try to avoid actually slicing the lists
250 equal_a = compare_range(self.a, ia, amatch,
245 equal_a = compare_range(self.a, ia, amatch,
251 self.base, iz, zmatch)
246 self.base, iz, zmatch)
252 equal_b = compare_range(self.b, ib, bmatch,
247 equal_b = compare_range(self.b, ib, bmatch,
253 self.base, iz, zmatch)
248 self.base, iz, zmatch)
254 same = compare_range(self.a, ia, amatch,
249 same = compare_range(self.a, ia, amatch,
255 self.b, ib, bmatch)
250 self.b, ib, bmatch)
256
251
257 if same:
252 if same:
258 yield 'same', ia, amatch
253 yield 'same', ia, amatch
259 elif equal_a and not equal_b:
254 elif equal_a and not equal_b:
260 yield 'b', ib, bmatch
255 yield 'b', ib, bmatch
261 elif equal_b and not equal_a:
256 elif equal_b and not equal_a:
262 yield 'a', ia, amatch
257 yield 'a', ia, amatch
263 elif not equal_a and not equal_b:
258 elif not equal_a and not equal_b:
264 yield 'conflict', iz, zmatch, ia, amatch, ib, bmatch
259 yield 'conflict', iz, zmatch, ia, amatch, ib, bmatch
265 else:
260 else:
266 raise AssertionError("can't handle a=b=base but unmatched")
261 raise AssertionError("can't handle a=b=base but unmatched")
267
262
268 ia = amatch
263 ia = amatch
269 ib = bmatch
264 ib = bmatch
270 iz = zmatch
265 iz = zmatch
271
266
272 # if the same part of the base was deleted on both sides
267 # if the same part of the base was deleted on both sides
273 # that's OK, we can just skip it.
268 # that's OK, we can just skip it.
274
269
275
270
276 if matchlen > 0:
271 if matchlen > 0:
277 assert ia == amatch
272 assert ia == amatch
278 assert ib == bmatch
273 assert ib == bmatch
279 assert iz == zmatch
274 assert iz == zmatch
280
275
281 yield 'unchanged', zmatch, zend
276 yield 'unchanged', zmatch, zend
282 iz = zend
277 iz = zend
283 ia = aend
278 ia = aend
284 ib = bend
279 ib = bend
285
280
286 def reprocess_merge_regions(self, merge_regions):
281 def reprocess_merge_regions(self, merge_regions):
287 """Where there are conflict regions, remove the agreed lines.
282 """Where there are conflict regions, remove the agreed lines.
288
283
289 Lines where both A and B have made the same changes are
284 Lines where both A and B have made the same changes are
290 eliminated.
285 eliminated.
291 """
286 """
292 for region in merge_regions:
287 for region in merge_regions:
293 if region[0] != "conflict":
288 if region[0] != "conflict":
294 yield region
289 yield region
295 continue
290 continue
296 type, iz, zmatch, ia, amatch, ib, bmatch = region
291 type, iz, zmatch, ia, amatch, ib, bmatch = region
297 a_region = self.a[ia:amatch]
292 a_region = self.a[ia:amatch]
298 b_region = self.b[ib:bmatch]
293 b_region = self.b[ib:bmatch]
299 matches = mdiff.get_matching_blocks(''.join(a_region),
294 matches = mdiff.get_matching_blocks(''.join(a_region),
300 ''.join(b_region))
295 ''.join(b_region))
301 next_a = ia
296 next_a = ia
302 next_b = ib
297 next_b = ib
303 for region_ia, region_ib, region_len in matches[:-1]:
298 for region_ia, region_ib, region_len in matches[:-1]:
304 region_ia += ia
299 region_ia += ia
305 region_ib += ib
300 region_ib += ib
306 reg = self.mismatch_region(next_a, region_ia, next_b,
301 reg = self.mismatch_region(next_a, region_ia, next_b,
307 region_ib)
302 region_ib)
308 if reg is not None:
303 if reg is not None:
309 yield reg
304 yield reg
310 yield 'same', region_ia, region_len+region_ia
305 yield 'same', region_ia, region_len+region_ia
311 next_a = region_ia + region_len
306 next_a = region_ia + region_len
312 next_b = region_ib + region_len
307 next_b = region_ib + region_len
313 reg = self.mismatch_region(next_a, amatch, next_b, bmatch)
308 reg = self.mismatch_region(next_a, amatch, next_b, bmatch)
314 if reg is not None:
309 if reg is not None:
315 yield reg
310 yield reg
316
311
317 def mismatch_region(next_a, region_ia, next_b, region_ib):
312 def mismatch_region(next_a, region_ia, next_b, region_ib):
318 if next_a < region_ia or next_b < region_ib:
313 if next_a < region_ia or next_b < region_ib:
319 return 'conflict', None, None, next_a, region_ia, next_b, region_ib
314 return 'conflict', None, None, next_a, region_ia, next_b, region_ib
320 mismatch_region = staticmethod(mismatch_region)
315 mismatch_region = staticmethod(mismatch_region)
321
316
322 def find_sync_regions(self):
317 def find_sync_regions(self):
323 """Return a list of sync regions, where both descendents match the base.
318 """Return a list of sync regions, where both descendents match the base.
324
319
325 Generates a list of (base1, base2, a1, a2, b1, b2). There is
320 Generates a list of (base1, base2, a1, a2, b1, b2). There is
326 always a zero-length sync region at the end of all the files.
321 always a zero-length sync region at the end of all the files.
327 """
322 """
328
323
329 ia = ib = 0
324 ia = ib = 0
330 amatches = mdiff.get_matching_blocks(self.basetext, self.atext)
325 amatches = mdiff.get_matching_blocks(self.basetext, self.atext)
331 bmatches = mdiff.get_matching_blocks(self.basetext, self.btext)
326 bmatches = mdiff.get_matching_blocks(self.basetext, self.btext)
332 len_a = len(amatches)
327 len_a = len(amatches)
333 len_b = len(bmatches)
328 len_b = len(bmatches)
334
329
335 sl = []
330 sl = []
336
331
337 while ia < len_a and ib < len_b:
332 while ia < len_a and ib < len_b:
338 abase, amatch, alen = amatches[ia]
333 abase, amatch, alen = amatches[ia]
339 bbase, bmatch, blen = bmatches[ib]
334 bbase, bmatch, blen = bmatches[ib]
340
335
341 # there is an unconflicted block at i; how long does it
336 # there is an unconflicted block at i; how long does it
342 # extend? until whichever one ends earlier.
337 # extend? until whichever one ends earlier.
343 i = intersect((abase, abase+alen), (bbase, bbase+blen))
338 i = intersect((abase, abase+alen), (bbase, bbase+blen))
344 if i:
339 if i:
345 intbase = i[0]
340 intbase = i[0]
346 intend = i[1]
341 intend = i[1]
347 intlen = intend - intbase
342 intlen = intend - intbase
348
343
349 # found a match of base[i[0], i[1]]; this may be less than
344 # found a match of base[i[0], i[1]]; this may be less than
350 # the region that matches in either one
345 # the region that matches in either one
351 assert intlen <= alen
346 assert intlen <= alen
352 assert intlen <= blen
347 assert intlen <= blen
353 assert abase <= intbase
348 assert abase <= intbase
354 assert bbase <= intbase
349 assert bbase <= intbase
355
350
356 asub = amatch + (intbase - abase)
351 asub = amatch + (intbase - abase)
357 bsub = bmatch + (intbase - bbase)
352 bsub = bmatch + (intbase - bbase)
358 aend = asub + intlen
353 aend = asub + intlen
359 bend = bsub + intlen
354 bend = bsub + intlen
360
355
361 assert self.base[intbase:intend] == self.a[asub:aend], \
356 assert self.base[intbase:intend] == self.a[asub:aend], \
362 (self.base[intbase:intend], self.a[asub:aend])
357 (self.base[intbase:intend], self.a[asub:aend])
363
358
364 assert self.base[intbase:intend] == self.b[bsub:bend]
359 assert self.base[intbase:intend] == self.b[bsub:bend]
365
360
366 sl.append((intbase, intend,
361 sl.append((intbase, intend,
367 asub, aend,
362 asub, aend,
368 bsub, bend))
363 bsub, bend))
369
364
370 # advance whichever one ends first in the base text
365 # advance whichever one ends first in the base text
371 if (abase + alen) < (bbase + blen):
366 if (abase + alen) < (bbase + blen):
372 ia += 1
367 ia += 1
373 else:
368 else:
374 ib += 1
369 ib += 1
375
370
376 intbase = len(self.base)
371 intbase = len(self.base)
377 abase = len(self.a)
372 abase = len(self.a)
378 bbase = len(self.b)
373 bbase = len(self.b)
379 sl.append((intbase, intbase, abase, abase, bbase, bbase))
374 sl.append((intbase, intbase, abase, abase, bbase, bbase))
380
375
381 return sl
376 return sl
382
377
383 def find_unconflicted(self):
378 def find_unconflicted(self):
384 """Return a list of ranges in base that are not conflicted."""
379 """Return a list of ranges in base that are not conflicted."""
385 am = mdiff.get_matching_blocks(self.basetext, self.atext)
380 am = mdiff.get_matching_blocks(self.basetext, self.atext)
386 bm = mdiff.get_matching_blocks(self.basetext, self.btext)
381 bm = mdiff.get_matching_blocks(self.basetext, self.btext)
387
382
388 unc = []
383 unc = []
389
384
390 while am and bm:
385 while am and bm:
391 # there is an unconflicted block at i; how long does it
386 # there is an unconflicted block at i; how long does it
392 # extend? until whichever one ends earlier.
387 # extend? until whichever one ends earlier.
393 a1 = am[0][0]
388 a1 = am[0][0]
394 a2 = a1 + am[0][2]
389 a2 = a1 + am[0][2]
395 b1 = bm[0][0]
390 b1 = bm[0][0]
396 b2 = b1 + bm[0][2]
391 b2 = b1 + bm[0][2]
397 i = intersect((a1, a2), (b1, b2))
392 i = intersect((a1, a2), (b1, b2))
398 if i:
393 if i:
399 unc.append(i)
394 unc.append(i)
400
395
401 if a2 < b2:
396 if a2 < b2:
402 del am[0]
397 del am[0]
403 else:
398 else:
404 del bm[0]
399 del bm[0]
405
400
406 return unc
401 return unc
407
402
408 def simplemerge(local, base, other, **opts):
403 def simplemerge(ui, local, base, other, **opts):
409 def readfile(filename):
404 def readfile(filename):
410 f = open(filename, "rb")
405 f = open(filename, "rb")
411 text = f.read()
406 text = f.read()
412 f.close()
407 f.close()
413 if util.binary(text):
408 if util.binary(text):
414 msg = _("%s looks like a binary file.") % filename
409 msg = _("%s looks like a binary file.") % filename
415 if not opts.get('text'):
410 if not opts.get('text'):
416 raise util.Abort(msg)
411 raise util.Abort(msg)
417 elif not opts.get('quiet'):
412 elif not opts.get('quiet'):
418 warn(_('warning: %s\n') % msg)
413 ui.warn(_('warning: %s\n') % msg)
419 return text
414 return text
420
415
421 name_a = local
416 name_a = local
422 name_b = other
417 name_b = other
423 labels = opts.get('label', [])
418 labels = opts.get('label', [])
424 if labels:
419 if labels:
425 name_a = labels.pop(0)
420 name_a = labels.pop(0)
426 if labels:
421 if labels:
427 name_b = labels.pop(0)
422 name_b = labels.pop(0)
428 if labels:
423 if labels:
429 raise util.Abort(_("can only specify two labels."))
424 raise util.Abort(_("can only specify two labels."))
430
425
431 localtext = readfile(local)
426 localtext = readfile(local)
432 basetext = readfile(base)
427 basetext = readfile(base)
433 othertext = readfile(other)
428 othertext = readfile(other)
434
429
435 local = os.path.realpath(local)
430 local = os.path.realpath(local)
436 if not opts.get('print'):
431 if not opts.get('print'):
437 opener = util.opener(os.path.dirname(local))
432 opener = util.opener(os.path.dirname(local))
438 out = opener(os.path.basename(local), "w", atomictemp=True)
433 out = opener(os.path.basename(local), "w", atomictemp=True)
439 else:
434 else:
440 out = sys.stdout
435 out = sys.stdout
441
436
442 reprocess = not opts.get('no_minimal')
437 reprocess = not opts.get('no_minimal')
443
438
444 m3 = Merge3Text(basetext, localtext, othertext)
439 m3 = Merge3Text(basetext, localtext, othertext)
445 for line in m3.merge_lines(name_a=name_a, name_b=name_b,
440 for line in m3.merge_lines(name_a=name_a, name_b=name_b,
446 reprocess=reprocess):
441 reprocess=reprocess):
447 out.write(line)
442 out.write(line)
448
443
449 if not opts.get('print'):
444 if not opts.get('print'):
450 out.rename()
445 out.rename()
451
446
452 if m3.conflicts:
447 if m3.conflicts:
453 if not opts.get('quiet'):
448 if not opts.get('quiet'):
454 warn(_("warning: conflicts during merge.\n"))
449 ui.warn(_("warning: conflicts during merge.\n"))
455 return 1
450 return 1
General Comments 0
You need to be logged in to leave comments. Login now