##// END OF EJS Templates
filemerge: more backwards compatible behavior for ui.merge...
Steve Borho -
r6076:0ee885fe default
parent child Browse files
Show More
@@ -1,213 +1,217 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
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from node import *
8 from node import *
9 from i18n import _
9 from i18n import _
10 import util, os, tempfile, context, simplemerge, re, filecmp
10 import util, os, tempfile, context, 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 k = _toolstr(ui, tool, "regkey")
19 k = _toolstr(ui, tool, "regkey")
20 if k:
20 if k:
21 p = util.lookup_reg(k, _toolstr(ui, tool, "regname"))
21 p = util.lookup_reg(k, _toolstr(ui, tool, "regname"))
22 if p:
22 if p:
23 p = util.find_exe(p + _toolstr(ui, tool, "regappend"))
23 p = util.find_exe(p + _toolstr(ui, tool, "regappend"))
24 if p:
24 if p:
25 return p
25 return p
26 return util.find_exe(_toolstr(ui, tool, "executable", tool))
26 return util.find_exe(_toolstr(ui, tool, "executable", tool))
27
27
28 def _picktool(repo, ui, path, binary, symlink):
28 def _picktool(repo, ui, path, binary, symlink):
29 def check(tool, pat, symlink, binary):
29 def check(tool, pat, symlink, binary):
30 tmsg = tool
30 tmsg = tool
31 if pat:
31 if pat:
32 tmsg += " specified for " + pat
32 tmsg += " specified for " + pat
33 if pat and not _findtool(ui, tool): # skip search if not matching
33 if pat and not _findtool(ui, tool): # skip search if not matching
34 ui.warn(_("couldn't find merge tool %s\n") % tmsg)
34 ui.warn(_("couldn't find merge tool %s\n") % tmsg)
35 elif symlink and not _toolbool(ui, tool, "symlink"):
35 elif symlink and not _toolbool(ui, tool, "symlink"):
36 ui.warn(_("tool %s can't handle symlinks\n") % tmsg)
36 ui.warn(_("tool %s can't handle symlinks\n") % tmsg)
37 elif binary and not _toolbool(ui, tool, "binary"):
37 elif binary and not _toolbool(ui, tool, "binary"):
38 ui.warn(_("tool %s can't handle binary\n") % tmsg)
38 ui.warn(_("tool %s can't handle binary\n") % tmsg)
39 elif not util.gui() and _toolbool(ui, tool, "gui"):
39 elif not util.gui() and _toolbool(ui, tool, "gui"):
40 ui.warn(_("tool %s requires a GUI\n") % tmsg)
40 ui.warn(_("tool %s requires a GUI\n") % tmsg)
41 else:
41 else:
42 return True
42 return True
43 return False
43 return False
44
44
45 # HGMERGE takes precedence
45 # HGMERGE takes precedence
46 hgmerge = os.environ.get("HGMERGE")
46 hgmerge = os.environ.get("HGMERGE")
47 if hgmerge:
47 if hgmerge:
48 return (hgmerge, hgmerge)
48 return (hgmerge, hgmerge)
49
49
50 # then patterns
50 # then patterns
51 for pat, tool in ui.configitems("merge-patterns"):
51 for pat, tool in ui.configitems("merge-patterns"):
52 mf = util.matcher(repo.root, "", [pat], [], [])[1]
52 mf = util.matcher(repo.root, "", [pat], [], [])[1]
53 if mf(path) and check(tool, pat, symlink, False):
53 if mf(path) and check(tool, pat, symlink, False):
54 toolpath = _findtool(ui, tool)
54 toolpath = _findtool(ui, tool)
55 return (tool, '"' + toolpath + '"')
55 return (tool, '"' + toolpath + '"')
56
56
57 # then merge tools
57 # then merge tools
58 tools = {}
58 tools = {}
59 for k,v in ui.configitems("merge-tools"):
59 for k,v in ui.configitems("merge-tools"):
60 t = k.split('.')[0]
60 t = k.split('.')[0]
61 if t not in tools:
61 if t not in tools:
62 tools[t] = int(_toolstr(ui, t, "priority", "0"))
62 tools[t] = int(_toolstr(ui, t, "priority", "0"))
63 names = tools.keys()
63 tools = [(-p,t) for t,p in tools.items()]
64 tools = [(-p,t) for t,p in tools.items()]
64 tools.sort()
65 tools.sort()
65 if ui.config("ui", "merge"):
66 uimerge = ui.config("ui", "merge")
66 tools.insert(0, (None, ui.config("ui", "merge"))) # highest priority
67 if uimerge:
68 if uimerge not in names:
69 return (uimerge, uimerge)
70 tools.insert(0, (None, uimerge)) # highest priority
67 tools.append((None, "hgmerge")) # the old default, if found
71 tools.append((None, "hgmerge")) # the old default, if found
68 for p,t in tools:
72 for p,t in tools:
69 toolpath = _findtool(ui, t)
73 toolpath = _findtool(ui, t)
70 if toolpath and check(t, None, symlink, binary):
74 if toolpath and check(t, None, symlink, binary):
71 return (t, '"' + toolpath + '"')
75 return (t, '"' + toolpath + '"')
72 # internal merge as last resort
76 # internal merge as last resort
73 return (not (symlink or binary) and "internal:merge" or None, None)
77 return (not (symlink or binary) and "internal:merge" or None, None)
74
78
75 def _eoltype(data):
79 def _eoltype(data):
76 "Guess the EOL type of a file"
80 "Guess the EOL type of a file"
77 if '\0' in data: # binary
81 if '\0' in data: # binary
78 return None
82 return None
79 if '\r\n' in data: # Windows
83 if '\r\n' in data: # Windows
80 return '\r\n'
84 return '\r\n'
81 if '\r' in data: # Old Mac
85 if '\r' in data: # Old Mac
82 return '\r'
86 return '\r'
83 if '\n' in data: # UNIX
87 if '\n' in data: # UNIX
84 return '\n'
88 return '\n'
85 return None # unknown
89 return None # unknown
86
90
87 def _matcheol(file, origfile):
91 def _matcheol(file, origfile):
88 "Convert EOL markers in a file to match origfile"
92 "Convert EOL markers in a file to match origfile"
89 tostyle = _eoltype(open(origfile, "rb").read())
93 tostyle = _eoltype(open(origfile, "rb").read())
90 if tostyle:
94 if tostyle:
91 data = open(file, "rb").read()
95 data = open(file, "rb").read()
92 style = _eoltype(data)
96 style = _eoltype(data)
93 if style:
97 if style:
94 newdata = data.replace(style, tostyle)
98 newdata = data.replace(style, tostyle)
95 if newdata != data:
99 if newdata != data:
96 open(file, "wb").write(newdata)
100 open(file, "wb").write(newdata)
97
101
98 def filemerge(repo, fw, fd, fo, wctx, mctx):
102 def filemerge(repo, fw, fd, fo, wctx, mctx):
99 """perform a 3-way merge in the working directory
103 """perform a 3-way merge in the working directory
100
104
101 fw = original filename in the working directory
105 fw = original filename in the working directory
102 fd = destination filename in the working directory
106 fd = destination filename in the working directory
103 fo = filename in other parent
107 fo = filename in other parent
104 wctx, mctx = working and merge changecontexts
108 wctx, mctx = working and merge changecontexts
105 """
109 """
106
110
107 def temp(prefix, ctx):
111 def temp(prefix, ctx):
108 pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
112 pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
109 (fd, name) = tempfile.mkstemp(prefix=pre)
113 (fd, name) = tempfile.mkstemp(prefix=pre)
110 data = repo.wwritedata(ctx.path(), ctx.data())
114 data = repo.wwritedata(ctx.path(), ctx.data())
111 f = os.fdopen(fd, "wb")
115 f = os.fdopen(fd, "wb")
112 f.write(data)
116 f.write(data)
113 f.close()
117 f.close()
114 return name
118 return name
115
119
116 def isbin(ctx):
120 def isbin(ctx):
117 try:
121 try:
118 return util.binary(ctx.data())
122 return util.binary(ctx.data())
119 except IOError:
123 except IOError:
120 return False
124 return False
121
125
122 fco = mctx.filectx(fo)
126 fco = mctx.filectx(fo)
123 if not fco.cmp(wctx.filectx(fd).data()): # files identical?
127 if not fco.cmp(wctx.filectx(fd).data()): # files identical?
124 return None
128 return None
125
129
126 ui = repo.ui
130 ui = repo.ui
127 fcm = wctx.filectx(fw)
131 fcm = wctx.filectx(fw)
128 fca = fcm.ancestor(fco) or repo.filectx(fw, fileid=nullrev)
132 fca = fcm.ancestor(fco) or repo.filectx(fw, fileid=nullrev)
129 binary = isbin(fcm) or isbin(fco) or isbin(fca)
133 binary = isbin(fcm) or isbin(fco) or isbin(fca)
130 symlink = fcm.islink() or fco.islink()
134 symlink = fcm.islink() or fco.islink()
131 tool, toolpath = _picktool(repo, ui, fw, binary, symlink)
135 tool, toolpath = _picktool(repo, ui, fw, binary, symlink)
132 ui.debug(_("picked tool '%s' for %s (binary %s symlink %s)\n") %
136 ui.debug(_("picked tool '%s' for %s (binary %s symlink %s)\n") %
133 (tool, fw, binary, symlink))
137 (tool, fw, binary, symlink))
134
138
135 if not tool:
139 if not tool:
136 tool = "internal:local"
140 tool = "internal:local"
137 if ui.prompt(_(" no tool found to merge %s\n"
141 if ui.prompt(_(" no tool found to merge %s\n"
138 "keep (l)ocal or take (o)ther?") % fw,
142 "keep (l)ocal or take (o)ther?") % fw,
139 _("[lo]"), _("l")) != _("l"):
143 _("[lo]"), _("l")) != _("l"):
140 tool = "internal:other"
144 tool = "internal:other"
141 if tool == "internal:local":
145 if tool == "internal:local":
142 return 0
146 return 0
143 if tool == "internal:other":
147 if tool == "internal:other":
144 repo.wwrite(fd, fco.data(), fco.fileflags())
148 repo.wwrite(fd, fco.data(), fco.fileflags())
145 return 0
149 return 0
146 if tool == "internal:fail":
150 if tool == "internal:fail":
147 return 1
151 return 1
148
152
149 # do the actual merge
153 # do the actual merge
150 a = repo.wjoin(fd)
154 a = repo.wjoin(fd)
151 b = temp("base", fca)
155 b = temp("base", fca)
152 c = temp("other", fco)
156 c = temp("other", fco)
153 out = ""
157 out = ""
154 back = a + ".orig"
158 back = a + ".orig"
155 util.copyfile(a, back)
159 util.copyfile(a, back)
156
160
157 if fw != fo:
161 if fw != fo:
158 repo.ui.status(_("merging %s and %s\n") % (fw, fo))
162 repo.ui.status(_("merging %s and %s\n") % (fw, fo))
159 else:
163 else:
160 repo.ui.status(_("merging %s\n") % fw)
164 repo.ui.status(_("merging %s\n") % fw)
161 repo.ui.debug(_("my %s other %s ancestor %s\n") % (fcm, fco, fca))
165 repo.ui.debug(_("my %s other %s ancestor %s\n") % (fcm, fco, fca))
162
166
163 # do we attempt to simplemerge first?
167 # do we attempt to simplemerge first?
164 if _toolbool(ui, tool, "premerge", not (binary or symlink)):
168 if _toolbool(ui, tool, "premerge", not (binary or symlink)):
165 r = simplemerge.simplemerge(a, b, c, quiet=True)
169 r = simplemerge.simplemerge(a, b, c, quiet=True)
166 if not r:
170 if not r:
167 ui.debug(_(" premerge successful\n"))
171 ui.debug(_(" premerge successful\n"))
168 os.unlink(back)
172 os.unlink(back)
169 os.unlink(b)
173 os.unlink(b)
170 os.unlink(c)
174 os.unlink(c)
171 return 0
175 return 0
172 util.copyfile(back, a) # restore from backup and try again
176 util.copyfile(back, a) # restore from backup and try again
173
177
174 env = dict(HG_FILE=fd,
178 env = dict(HG_FILE=fd,
175 HG_MY_NODE=str(wctx.parents()[0]),
179 HG_MY_NODE=str(wctx.parents()[0]),
176 HG_OTHER_NODE=str(mctx),
180 HG_OTHER_NODE=str(mctx),
177 HG_MY_ISLINK=fcm.islink(),
181 HG_MY_ISLINK=fcm.islink(),
178 HG_OTHER_ISLINK=fco.islink(),
182 HG_OTHER_ISLINK=fco.islink(),
179 HG_BASE_ISLINK=fca.islink())
183 HG_BASE_ISLINK=fca.islink())
180
184
181 if tool == "internal:merge":
185 if tool == "internal:merge":
182 r = simplemerge.simplemerge(a, b, c, label=['local', 'other'])
186 r = simplemerge.simplemerge(a, b, c, label=['local', 'other'])
183 else:
187 else:
184 args = _toolstr(ui, tool, "args", '$local $base $other')
188 args = _toolstr(ui, tool, "args", '$local $base $other')
185 if "$output" in args:
189 if "$output" in args:
186 out, a = a, back # read input from backup, write to original
190 out, a = a, back # read input from backup, write to original
187 replace = dict(local=a, base=b, other=c, output=out)
191 replace = dict(local=a, base=b, other=c, output=out)
188 args = re.sub("\$(local|base|other|output)",
192 args = re.sub("\$(local|base|other|output)",
189 lambda x: '"%s"' % replace[x.group()[1:]], args)
193 lambda x: '"%s"' % replace[x.group()[1:]], args)
190 r = util.system(toolpath + ' ' + args, cwd=repo.root, environ=env)
194 r = util.system(toolpath + ' ' + args, cwd=repo.root, environ=env)
191
195
192 if not r and _toolbool(ui, tool, "checkconflicts"):
196 if not r and _toolbool(ui, tool, "checkconflicts"):
193 if re.match("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcm.data()):
197 if re.match("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcm.data()):
194 r = 1
198 r = 1
195
199
196 if not r and _toolbool(ui, tool, "checkchanged"):
200 if not r and _toolbool(ui, tool, "checkchanged"):
197 if filecmp.cmp(repo.wjoin(fd), back):
201 if filecmp.cmp(repo.wjoin(fd), back):
198 if ui.prompt(_(" output file %s appears unchanged\n"
202 if ui.prompt(_(" output file %s appears unchanged\n"
199 "was merge successful (yn)?") % fd,
203 "was merge successful (yn)?") % fd,
200 _("[yn]"), _("n")) != _("y"):
204 _("[yn]"), _("n")) != _("y"):
201 r = 1
205 r = 1
202
206
203 if _toolbool(ui, tool, "fixeol"):
207 if _toolbool(ui, tool, "fixeol"):
204 _matcheol(repo.wjoin(fd), back)
208 _matcheol(repo.wjoin(fd), back)
205
209
206 if r:
210 if r:
207 repo.ui.warn(_("merging %s failed!\n") % fd)
211 repo.ui.warn(_("merging %s failed!\n") % fd)
208 else:
212 else:
209 os.unlink(back)
213 os.unlink(back)
210
214
211 os.unlink(b)
215 os.unlink(b)
212 os.unlink(c)
216 os.unlink(c)
213 return r
217 return r
General Comments 0
You need to be logged in to leave comments. Login now