##// END OF EJS Templates
merge: remove redundant if
Matt Mackall -
r2976:c67920d7 default
parent child Browse files
Show More
@@ -1,334 +1,333 b''
1 # merge.py - directory-level update/merge handling for Mercurial
1 # merge.py - directory-level update/merge handling for Mercurial
2 #
2 #
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006 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 gettext as _
9 from i18n import gettext as _
10 from demandload import *
10 from demandload import *
11 demandload(globals(), "util os tempfile")
11 demandload(globals(), "util os tempfile")
12
12
13 def fmerge(f, local, other, ancestor):
13 def fmerge(f, local, other, ancestor):
14 """merge executable flags"""
14 """merge executable flags"""
15 a, b, c = ancestor.execf(f), local.execf(f), other.execf(f)
15 a, b, c = ancestor.execf(f), local.execf(f), other.execf(f)
16 return ((a^b) | (a^c)) ^ a
16 return ((a^b) | (a^c)) ^ a
17
17
18 def merge3(repo, fn, my, other, p1, p2):
18 def merge3(repo, fn, my, other, p1, p2):
19 """perform a 3-way merge in the working directory"""
19 """perform a 3-way merge in the working directory"""
20
20
21 def temp(prefix, node):
21 def temp(prefix, node):
22 pre = "%s~%s." % (os.path.basename(fn), prefix)
22 pre = "%s~%s." % (os.path.basename(fn), prefix)
23 (fd, name) = tempfile.mkstemp(prefix=pre)
23 (fd, name) = tempfile.mkstemp(prefix=pre)
24 f = os.fdopen(fd, "wb")
24 f = os.fdopen(fd, "wb")
25 repo.wwrite(fn, fl.read(node), f)
25 repo.wwrite(fn, fl.read(node), f)
26 f.close()
26 f.close()
27 return name
27 return name
28
28
29 fl = repo.file(fn)
29 fl = repo.file(fn)
30 base = fl.ancestor(my, other)
30 base = fl.ancestor(my, other)
31 a = repo.wjoin(fn)
31 a = repo.wjoin(fn)
32 b = temp("base", base)
32 b = temp("base", base)
33 c = temp("other", other)
33 c = temp("other", other)
34
34
35 repo.ui.note(_("resolving %s\n") % fn)
35 repo.ui.note(_("resolving %s\n") % fn)
36 repo.ui.debug(_("file %s: my %s other %s ancestor %s\n") %
36 repo.ui.debug(_("file %s: my %s other %s ancestor %s\n") %
37 (fn, short(my), short(other), short(base)))
37 (fn, short(my), short(other), short(base)))
38
38
39 cmd = (os.environ.get("HGMERGE") or repo.ui.config("ui", "merge")
39 cmd = (os.environ.get("HGMERGE") or repo.ui.config("ui", "merge")
40 or "hgmerge")
40 or "hgmerge")
41 r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=repo.root,
41 r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=repo.root,
42 environ={'HG_FILE': fn,
42 environ={'HG_FILE': fn,
43 'HG_MY_NODE': p1,
43 'HG_MY_NODE': p1,
44 'HG_OTHER_NODE': p2,
44 'HG_OTHER_NODE': p2,
45 'HG_FILE_MY_NODE': hex(my),
45 'HG_FILE_MY_NODE': hex(my),
46 'HG_FILE_OTHER_NODE': hex(other),
46 'HG_FILE_OTHER_NODE': hex(other),
47 'HG_FILE_BASE_NODE': hex(base)})
47 'HG_FILE_BASE_NODE': hex(base)})
48 if r:
48 if r:
49 repo.ui.warn(_("merging %s failed!\n") % fn)
49 repo.ui.warn(_("merging %s failed!\n") % fn)
50
50
51 os.unlink(b)
51 os.unlink(b)
52 os.unlink(c)
52 os.unlink(c)
53 return r
53 return r
54
54
55 def update(repo, node, branchmerge=False, force=False, partial=None,
55 def update(repo, node, branchmerge=False, force=False, partial=None,
56 wlock=None, show_stats=True, remind=True):
56 wlock=None, show_stats=True, remind=True):
57
57
58 overwrite = force and not branchmerge
58 overwrite = force and not branchmerge
59 forcemerge = force and branchmerge
59 forcemerge = force and branchmerge
60
60
61 if not wlock:
61 if not wlock:
62 wlock = repo.wlock()
62 wlock = repo.wlock()
63
63
64 ### check phase
64 ### check phase
65
65
66 pl = repo.dirstate.parents()
66 pl = repo.dirstate.parents()
67 if not overwrite and pl[1] != nullid:
67 if not overwrite and pl[1] != nullid:
68 raise util.Abort(_("outstanding uncommitted merges"))
68 raise util.Abort(_("outstanding uncommitted merges"))
69
69
70 p1, p2 = pl[0], node
70 p1, p2 = pl[0], node
71 pa = repo.changelog.ancestor(p1, p2)
71 pa = repo.changelog.ancestor(p1, p2)
72
72
73 # are we going backwards?
73 # are we going backwards?
74 backwards = (pa == p2)
74 backwards = (pa == p2)
75
75
76 # is there a linear path from p1 to p2?
76 # is there a linear path from p1 to p2?
77 linear_path = (pa == p1 or pa == p2)
77 linear_path = (pa == p1 or pa == p2)
78 if branchmerge and linear_path:
78 if branchmerge and linear_path:
79 raise util.Abort(_("there is nothing to merge, just use "
79 raise util.Abort(_("there is nothing to merge, just use "
80 "'hg update' or look at 'hg heads'"))
80 "'hg update' or look at 'hg heads'"))
81
81
82 if not linear_path and not (overwrite or branchmerge):
82 if not linear_path and not (overwrite or branchmerge):
83 raise util.Abort(_("update spans branches, use 'hg merge' "
83 raise util.Abort(_("update spans branches, use 'hg merge' "
84 "or 'hg update -C' to lose changes"))
84 "or 'hg update -C' to lose changes"))
85
85
86 modified, added, removed, deleted, unknown = repo.status()[:5]
86 modified, added, removed, deleted, unknown = repo.status()[:5]
87 if branchmerge and not forcemerge:
87 if branchmerge and not forcemerge:
88 if modified or added or removed:
88 if modified or added or removed:
89 raise util.Abort(_("outstanding uncommitted changes"))
89 raise util.Abort(_("outstanding uncommitted changes"))
90
90
91 m1n = repo.changelog.read(p1)[0]
91 m1n = repo.changelog.read(p1)[0]
92 m2n = repo.changelog.read(p2)[0]
92 m2n = repo.changelog.read(p2)[0]
93 man = repo.manifest.ancestor(m1n, m2n)
93 man = repo.manifest.ancestor(m1n, m2n)
94 m1 = repo.manifest.read(m1n).copy()
94 m1 = repo.manifest.read(m1n).copy()
95 m2 = repo.manifest.read(m2n).copy()
95 m2 = repo.manifest.read(m2n).copy()
96 ma = repo.manifest.read(man)
96 ma = repo.manifest.read(man)
97
97
98 if not force:
98 if not force:
99 for f in unknown:
99 for f in unknown:
100 if f in m2:
100 if f in m2:
101 if repo.file(f).cmp(m2[f], repo.wread(f)):
101 if repo.file(f).cmp(m2[f], repo.wread(f)):
102 raise util.Abort(_("'%s' already exists in the working"
102 raise util.Abort(_("'%s' already exists in the working"
103 " dir and differs from remote") % f)
103 " dir and differs from remote") % f)
104
104
105 # resolve the manifest to determine which files
105 # resolve the manifest to determine which files
106 # we care about merging
106 # we care about merging
107 repo.ui.note(_("resolving manifests\n"))
107 repo.ui.note(_("resolving manifests\n"))
108 repo.ui.debug(_(" overwrite %s branchmerge %s partial %s linear %s\n") %
108 repo.ui.debug(_(" overwrite %s branchmerge %s partial %s linear %s\n") %
109 (overwrite, branchmerge, bool(partial), linear_path))
109 (overwrite, branchmerge, bool(partial), linear_path))
110 repo.ui.debug(_(" ancestor %s local %s remote %s\n") %
110 repo.ui.debug(_(" ancestor %s local %s remote %s\n") %
111 (short(man), short(m1n), short(m2n)))
111 (short(man), short(m1n), short(m2n)))
112
112
113 merge = {}
113 merge = {}
114 get = {}
114 get = {}
115 remove = []
115 remove = []
116 forget = []
116 forget = []
117
117
118 # update m1 from working dir
118 # update m1 from working dir
119 umap = dict.fromkeys(unknown)
119 umap = dict.fromkeys(unknown)
120
120
121 for f in added + modified + unknown:
121 for f in added + modified + unknown:
122 m1[f] = m1.get(f, nullid) + "+"
122 m1[f] = m1.get(f, nullid) + "+"
123 m1.set(f, util.is_exec(repo.wjoin(f), m1.execf(f)))
123 m1.set(f, util.is_exec(repo.wjoin(f), m1.execf(f)))
124
124
125 for f in deleted + removed:
125 for f in deleted + removed:
126 if f in m1:
126 del m1[f]
127 del m1[f]
128
127
129 # If we're jumping between revisions (as opposed to merging),
128 # If we're jumping between revisions (as opposed to merging),
130 # and if neither the working directory nor the target rev has
129 # and if neither the working directory nor the target rev has
131 # the file, then we need to remove it from the dirstate, to
130 # the file, then we need to remove it from the dirstate, to
132 # prevent the dirstate from listing the file when it is no
131 # prevent the dirstate from listing the file when it is no
133 # longer in the manifest.
132 # longer in the manifest.
134 if linear_path and f not in m2:
133 if linear_path and f not in m2:
135 forget.append(f)
134 forget.append(f)
136
135
137 if partial:
136 if partial:
138 for f in m1.keys():
137 for f in m1.keys():
139 if not partial(f): del m1[f]
138 if not partial(f): del m1[f]
140 for f in m2.keys():
139 for f in m2.keys():
141 if not partial(f): del m2[f]
140 if not partial(f): del m2[f]
142
141
143 # Compare manifests
142 # Compare manifests
144 for f, n in m1.iteritems():
143 for f, n in m1.iteritems():
145 if f in m2:
144 if f in m2:
146 queued = 0
145 queued = 0
147
146
148 # are files different?
147 # are files different?
149 if n != m2[f]:
148 if n != m2[f]:
150 a = ma.get(f, nullid)
149 a = ma.get(f, nullid)
151 # are both different from the ancestor?
150 # are both different from the ancestor?
152 if not overwrite and n != a and m2[f] != a:
151 if not overwrite and n != a and m2[f] != a:
153 repo.ui.debug(_(" %s versions differ, resolve\n") % f)
152 repo.ui.debug(_(" %s versions differ, resolve\n") % f)
154 merge[f] = (fmerge(f, m1, m2, ma), n[:20], m2[f])
153 merge[f] = (fmerge(f, m1, m2, ma), n[:20], m2[f])
155 queued = 1
154 queued = 1
156 # are we clobbering?
155 # are we clobbering?
157 # is remote's version newer?
156 # is remote's version newer?
158 # or are we going back in time and clean?
157 # or are we going back in time and clean?
159 elif overwrite or m2[f] != a or (backwards and not n[20:]):
158 elif overwrite or m2[f] != a or (backwards and not n[20:]):
160 repo.ui.debug(_(" remote %s is newer, get\n") % f)
159 repo.ui.debug(_(" remote %s is newer, get\n") % f)
161 get[f] = (m2.execf(f), m2[f])
160 get[f] = (m2.execf(f), m2[f])
162 queued = 1
161 queued = 1
163 elif f in umap or f in added:
162 elif f in umap or f in added:
164 # this unknown file is the same as the checkout
163 # this unknown file is the same as the checkout
165 # we need to reset the dirstate if the file was added
164 # we need to reset the dirstate if the file was added
166 get[f] = (m2.execf(f), m2[f])
165 get[f] = (m2.execf(f), m2[f])
167
166
168 # do we still need to look at mode bits?
167 # do we still need to look at mode bits?
169 if not queued and m1.execf(f) != m2.execf(f):
168 if not queued and m1.execf(f) != m2.execf(f):
170 if overwrite:
169 if overwrite:
171 repo.ui.debug(_(" updating permissions for %s\n") % f)
170 repo.ui.debug(_(" updating permissions for %s\n") % f)
172 util.set_exec(repo.wjoin(f), m2.execf(f))
171 util.set_exec(repo.wjoin(f), m2.execf(f))
173 else:
172 else:
174 if fmerge(f, m1, m2, ma) != m1.execf(f):
173 if fmerge(f, m1, m2, ma) != m1.execf(f):
175 repo.ui.debug(_(" updating permissions for %s\n")
174 repo.ui.debug(_(" updating permissions for %s\n")
176 % f)
175 % f)
177 util.set_exec(repo.wjoin(f), mode)
176 util.set_exec(repo.wjoin(f), mode)
178 del m2[f]
177 del m2[f]
179 elif f in ma:
178 elif f in ma:
180 if n != ma[f]:
179 if n != ma[f]:
181 r = _("d")
180 r = _("d")
182 if not overwrite:
181 if not overwrite:
183 r = repo.ui.prompt(
182 r = repo.ui.prompt(
184 (_(" local changed %s which remote deleted\n") % f) +
183 (_(" local changed %s which remote deleted\n") % f) +
185 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
184 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
186 if r == _("d"):
185 if r == _("d"):
187 remove.append(f)
186 remove.append(f)
188 else:
187 else:
189 repo.ui.debug(_("other deleted %s\n") % f)
188 repo.ui.debug(_("other deleted %s\n") % f)
190 remove.append(f) # other deleted it
189 remove.append(f) # other deleted it
191 else:
190 else:
192 # file is created on branch or in working directory
191 # file is created on branch or in working directory
193 if overwrite and f not in umap:
192 if overwrite and f not in umap:
194 repo.ui.debug(_("remote deleted %s, clobbering\n") % f)
193 repo.ui.debug(_("remote deleted %s, clobbering\n") % f)
195 remove.append(f)
194 remove.append(f)
196 elif not n[20:]: # same as parent
195 elif not n[20:]: # same as parent
197 if backwards:
196 if backwards:
198 repo.ui.debug(_("remote deleted %s\n") % f)
197 repo.ui.debug(_("remote deleted %s\n") % f)
199 remove.append(f)
198 remove.append(f)
200 else:
199 else:
201 repo.ui.debug(_("local modified %s, keeping\n") % f)
200 repo.ui.debug(_("local modified %s, keeping\n") % f)
202 else:
201 else:
203 repo.ui.debug(_("working dir created %s, keeping\n") % f)
202 repo.ui.debug(_("working dir created %s, keeping\n") % f)
204
203
205 for f, n in m2.iteritems():
204 for f, n in m2.iteritems():
206 if f[0] == "/":
205 if f[0] == "/":
207 continue
206 continue
208 if f in ma and n != ma[f]:
207 if f in ma and n != ma[f]:
209 r = _("k")
208 r = _("k")
210 if not overwrite:
209 if not overwrite:
211 r = repo.ui.prompt(
210 r = repo.ui.prompt(
212 (_("remote changed %s which local deleted\n") % f) +
211 (_("remote changed %s which local deleted\n") % f) +
213 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
212 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
214 if r == _("k"):
213 if r == _("k"):
215 get[f] = (m2.execf(f), n)
214 get[f] = (m2.execf(f), n)
216 elif f not in ma:
215 elif f not in ma:
217 repo.ui.debug(_("remote created %s\n") % f)
216 repo.ui.debug(_("remote created %s\n") % f)
218 get[f] = (m2.execf(f), n)
217 get[f] = (m2.execf(f), n)
219 else:
218 else:
220 if overwrite or backwards:
219 if overwrite or backwards:
221 repo.ui.debug(_("local deleted %s, recreating\n") % f)
220 repo.ui.debug(_("local deleted %s, recreating\n") % f)
222 get[f] = (m2.execf(f), n)
221 get[f] = (m2.execf(f), n)
223 else:
222 else:
224 repo.ui.debug(_("local deleted %s\n") % f)
223 repo.ui.debug(_("local deleted %s\n") % f)
225
224
226 del m1, m2, ma
225 del m1, m2, ma
227
226
228 ### apply phase
227 ### apply phase
229
228
230 if linear_path or overwrite:
229 if linear_path or overwrite:
231 # we don't need to do any magic, just jump to the new rev
230 # we don't need to do any magic, just jump to the new rev
232 p1, p2 = p2, nullid
231 p1, p2 = p2, nullid
233
232
234 xp1 = hex(p1)
233 xp1 = hex(p1)
235 xp2 = hex(p2)
234 xp2 = hex(p2)
236 if p2 == nullid: xxp2 = ''
235 if p2 == nullid: xxp2 = ''
237 else: xxp2 = xp2
236 else: xxp2 = xp2
238
237
239 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xxp2)
238 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xxp2)
240
239
241 # get the files we don't need to change
240 # get the files we don't need to change
242 files = get.keys()
241 files = get.keys()
243 files.sort()
242 files.sort()
244 for f in files:
243 for f in files:
245 flag, node = get[f]
244 flag, node = get[f]
246 if f[0] == "/":
245 if f[0] == "/":
247 continue
246 continue
248 repo.ui.note(_("getting %s\n") % f)
247 repo.ui.note(_("getting %s\n") % f)
249 t = repo.file(f).read(node)
248 t = repo.file(f).read(node)
250 repo.wwrite(f, t)
249 repo.wwrite(f, t)
251 util.set_exec(repo.wjoin(f), flag)
250 util.set_exec(repo.wjoin(f), flag)
252
251
253 # merge the tricky bits
252 # merge the tricky bits
254 unresolved = []
253 unresolved = []
255 files = merge.keys()
254 files = merge.keys()
256 files.sort()
255 files.sort()
257 for f in files:
256 for f in files:
258 repo.ui.status(_("merging %s\n") % f)
257 repo.ui.status(_("merging %s\n") % f)
259 flag, my, other = merge[f]
258 flag, my, other = merge[f]
260 ret = merge3(repo, f, my, other, xp1, xp2)
259 ret = merge3(repo, f, my, other, xp1, xp2)
261 if ret:
260 if ret:
262 unresolved.append(f)
261 unresolved.append(f)
263 util.set_exec(repo.wjoin(f), flag)
262 util.set_exec(repo.wjoin(f), flag)
264
263
265 remove.sort()
264 remove.sort()
266 for f in remove:
265 for f in remove:
267 repo.ui.note(_("removing %s\n") % f)
266 repo.ui.note(_("removing %s\n") % f)
268 util.audit_path(f)
267 util.audit_path(f)
269 try:
268 try:
270 util.unlink(repo.wjoin(f))
269 util.unlink(repo.wjoin(f))
271 except OSError, inst:
270 except OSError, inst:
272 if inst.errno != errno.ENOENT:
271 if inst.errno != errno.ENOENT:
273 repo.ui.warn(_("update failed to remove %s: %s!\n") %
272 repo.ui.warn(_("update failed to remove %s: %s!\n") %
274 (f, inst.strerror))
273 (f, inst.strerror))
275
274
276 # update dirstate
275 # update dirstate
277 if not partial:
276 if not partial:
278 repo.dirstate.setparents(p1, p2)
277 repo.dirstate.setparents(p1, p2)
279 repo.dirstate.forget(forget)
278 repo.dirstate.forget(forget)
280 if branchmerge:
279 if branchmerge:
281 repo.dirstate.update(remove, 'r')
280 repo.dirstate.update(remove, 'r')
282 else:
281 else:
283 repo.dirstate.forget(remove)
282 repo.dirstate.forget(remove)
284
283
285 files = get.keys()
284 files = get.keys()
286 files.sort()
285 files.sort()
287 for f in files:
286 for f in files:
288 if branchmerge:
287 if branchmerge:
289 repo.dirstate.update([f], 'n', st_mtime=-1)
288 repo.dirstate.update([f], 'n', st_mtime=-1)
290 else:
289 else:
291 repo.dirstate.update([f], 'n')
290 repo.dirstate.update([f], 'n')
292
291
293 files = merge.keys()
292 files = merge.keys()
294 files.sort()
293 files.sort()
295 for f in files:
294 for f in files:
296 if branchmerge:
295 if branchmerge:
297 # We've done a branch merge, mark this file as merged
296 # We've done a branch merge, mark this file as merged
298 # so that we properly record the merger later
297 # so that we properly record the merger later
299 repo.dirstate.update([f], 'm')
298 repo.dirstate.update([f], 'm')
300 else:
299 else:
301 # We've update-merged a locally modified file, so
300 # We've update-merged a locally modified file, so
302 # we set the dirstate to emulate a normal checkout
301 # we set the dirstate to emulate a normal checkout
303 # of that file some time in the past. Thus our
302 # of that file some time in the past. Thus our
304 # merge will appear as a normal local file
303 # merge will appear as a normal local file
305 # modification.
304 # modification.
306 fl = repo.file(f)
305 fl = repo.file(f)
307 f_len = fl.size(fl.rev(other))
306 f_len = fl.size(fl.rev(other))
308 repo.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
307 repo.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
309
308
310 if show_stats:
309 if show_stats:
311 stats = ((len(get), _("updated")),
310 stats = ((len(get), _("updated")),
312 (len(merge) - len(unresolved), _("merged")),
311 (len(merge) - len(unresolved), _("merged")),
313 (len(remove), _("removed")),
312 (len(remove), _("removed")),
314 (len(unresolved), _("unresolved")))
313 (len(unresolved), _("unresolved")))
315 note = ", ".join([_("%d files %s") % s for s in stats])
314 note = ", ".join([_("%d files %s") % s for s in stats])
316 repo.ui.status("%s\n" % note)
315 repo.ui.status("%s\n" % note)
317 if not partial:
316 if not partial:
318 if branchmerge:
317 if branchmerge:
319 if unresolved:
318 if unresolved:
320 repo.ui.status(_("There are unresolved merges,"
319 repo.ui.status(_("There are unresolved merges,"
321 " you can redo the full merge using:\n"
320 " you can redo the full merge using:\n"
322 " hg update -C %s\n"
321 " hg update -C %s\n"
323 " hg merge %s\n"
322 " hg merge %s\n"
324 % (repo.changelog.rev(p1),
323 % (repo.changelog.rev(p1),
325 repo.changelog.rev(p2))))
324 repo.changelog.rev(p2))))
326 elif remind:
325 elif remind:
327 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
326 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
328 elif unresolved:
327 elif unresolved:
329 repo.ui.status(_("There are unresolved merges with"
328 repo.ui.status(_("There are unresolved merges with"
330 " locally modified files.\n"))
329 " locally modified files.\n"))
331
330
332 repo.hook('update', parent1=xp1, parent2=xxp2, error=len(unresolved))
331 repo.hook('update', parent1=xp1, parent2=xxp2, error=len(unresolved))
333 return len(unresolved)
332 return len(unresolved)
334
333
General Comments 0
You need to be logged in to leave comments. Login now