##// END OF EJS Templates
merge: eliminate nodes from action list...
Matt Mackall -
r3303:488d3062 default
parent child Browse files
Show More
@@ -1,466 +1,463 b''
1 1 # merge.py - directory-level update/merge handling for Mercurial
2 2 #
3 3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from node import *
9 9 from i18n import gettext as _
10 10 from demandload import *
11 11 demandload(globals(), "errno util os tempfile")
12 12
13 13 def filemerge(repo, fw, fo, fd, wctx, mctx, move):
14 14 """perform a 3-way merge in the working directory
15 15
16 16 fw = filename in the working directory and first parent
17 17 fo = filename in other parent
18 18 fd = destination filename
19 my = fileid in first parent
20 other = fileid in second parent
21 19 wctx, mctx = working and merge changecontexts
22 20 move = whether to move or copy the file to the destination
23 21
24 22 TODO:
25 23 if fw is copied in the working directory, we get confused
26 24 implement move and fd
27 25 """
28 26
29 27 def temp(prefix, ctx):
30 28 pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
31 29 (fd, name) = tempfile.mkstemp(prefix=pre)
32 30 f = os.fdopen(fd, "wb")
33 31 repo.wwrite(ctx.path(), ctx.data(), f)
34 32 f.close()
35 33 return name
36 34
37 35 fcm = wctx.filectx(fw)
38 36 fco = mctx.filectx(fo)
39 37 fca = fcm.ancestor(fco)
40 38 if not fca:
41 39 fca = repo.filectx(fw, fileid=-1)
42 40 a = repo.wjoin(fw)
43 41 b = temp("base", fca)
44 42 c = temp("other", fco)
45 43
46 44 repo.ui.note(_("resolving %s\n") % fw)
47 45 repo.ui.debug(_("my %s other %s ancestor %s\n") % (fcm, fco, fca))
48 46
49 47 cmd = (os.environ.get("HGMERGE") or repo.ui.config("ui", "merge")
50 48 or "hgmerge")
51 49 r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=repo.root,
52 50 environ={'HG_FILE': fw,
53 51 'HG_MY_NODE': str(wctx.parents()[0]),
54 52 'HG_OTHER_NODE': str(mctx)})
55 53 if r:
56 54 repo.ui.warn(_("merging %s failed!\n") % fw)
57 55 else:
58 56 if fd != fw:
59 57 repo.ui.debug(_("copying %s to %s\n") % (fw, fd))
60 58 repo.wwrite(fd, repo.wread(fw))
61 59 if move:
62 60 repo.ui.debug(_("removing %s\n") % fw)
63 61 os.unlink(a)
64 62
65 63 os.unlink(b)
66 64 os.unlink(c)
67 65 return r
68 66
69 67 def checkunknown(repo, m2, wctx):
70 68 """
71 69 check for collisions between unknown files and files in m2
72 70 """
73 71 for f in wctx.unknown():
74 72 if f in m2:
75 73 if repo.file(f).cmp(m2[f], repo.wread(f)):
76 74 raise util.Abort(_("'%s' already exists in the working"
77 75 " dir and differs from remote") % f)
78 76
79 77 def forgetremoved(m2, wctx):
80 78 """
81 79 Forget removed files
82 80
83 81 If we're jumping between revisions (as opposed to merging), and if
84 82 neither the working directory nor the target rev has the file,
85 83 then we need to remove it from the dirstate, to prevent the
86 84 dirstate from listing the file when it is no longer in the
87 85 manifest.
88 86 """
89 87
90 88 action = []
91 89
92 90 for f in wctx.deleted() + wctx.removed():
93 91 if f not in m2:
94 92 action.append((f, "f"))
95 93
96 94 return action
97 95
98 96 def nonoverlap(d1, d2):
99 97 """
100 98 Return list of elements in d1 not in d2
101 99 """
102 100
103 101 l = []
104 102 for d in d1:
105 103 if d not in d2:
106 104 l.append(d)
107 105
108 106 l.sort()
109 107 return l
110 108
111 109 def findold(fctx, limit):
112 110 """
113 111 find files that path was copied from, back to linkrev limit
114 112 """
115 113
116 114 old = {}
117 115 orig = fctx.path()
118 116 visit = [fctx]
119 117 while visit:
120 118 fc = visit.pop()
121 119 if fc.rev() < limit:
122 120 continue
123 121 if fc.path() != orig and fc.path() not in old:
124 122 old[fc.path()] = 1
125 123 visit += fc.parents()
126 124
127 125 old = old.keys()
128 126 old.sort()
129 127 return old
130 128
131 129 def findcopies(repo, m1, m2, limit):
132 130 """
133 131 Find moves and copies between m1 and m2 back to limit linkrev
134 132 """
135 133
136 134 if not repo.ui.config("merge", "followcopies"):
137 135 return {}
138 136
139 137 # avoid silly behavior for update from empty dir
140 138 if not m1:
141 139 return {}
142 140
143 141 dcopies = repo.dirstate.copies()
144 142 copy = {}
145 143 match = {}
146 144 u1 = nonoverlap(m1, m2)
147 145 u2 = nonoverlap(m2, m1)
148 146 ctx = util.cachefunc(lambda f,n: repo.filectx(f, fileid=n[:20]))
149 147
150 148 def checkpair(c, f2, man):
151 149 ''' check if an apparent pair actually matches '''
152 150 c2 = ctx(f2, man[f2])
153 151 ca = c.ancestor(c2)
154 152 if ca and ca.path() == c.path() or ca.path() == c2.path():
155 153 copy[c.path()] = f2
156 154 copy[f2] = c.path()
157 155
158 156 for f in u1:
159 157 c = ctx(dcopies.get(f, f), m1[f])
160 158 for of in findold(c, limit):
161 159 if of in m2:
162 160 checkpair(c, of, m2)
163 161 else:
164 162 match.setdefault(of, []).append(f)
165 163
166 164 for f in u2:
167 165 c = ctx(f, m2[f])
168 166 for of in findold(c, limit):
169 167 if of in m1:
170 168 checkpair(c, of, m1)
171 169 elif of in match:
172 170 for mf in match[of]:
173 171 checkpair(c, mf, m1)
174 172
175 173 return copy
176 174
177 175 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
178 176 """
179 177 Merge manifest m1 with m2 using ancestor ma and generate merge action list
180 178 """
181 179
182 180 m1 = p1.manifest()
183 181 m2 = p2.manifest()
184 182 ma = pa.manifest()
185 183 backwards = (pa == p2)
186 184
187 185 def fmerge(f, f2=None, fa=None):
188 186 """merge executable flags"""
189 187 if not f2:
190 188 f2 = f
191 189 fa = f
192 190 a, b, c = ma.execf(fa), m1.execf(f), m2.execf(f2)
193 191 return ((a^b) | (a^c)) ^ a
194 192
195 193 action = []
196 194
197 195 def act(msg, f, m, *args):
198 196 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
199 197 action.append((f, m) + args)
200 198
201 199 copy = {}
202 200 if not (backwards or overwrite):
203 201 copy = findcopies(repo, m1, m2, pa.rev())
204 202
205 203 # Compare manifests
206 204 for f, n in m1.iteritems():
207 205 if partial and not partial(f):
208 206 continue
209 207 if f in m2:
210 208 # are files different?
211 209 if n != m2[f]:
212 210 a = ma.get(f, nullid)
213 211 # are both different from the ancestor?
214 212 if not overwrite and n != a and m2[f] != a:
215 act("versions differ", f, "m", fmerge(f), n[:20], m2[f])
213 act("versions differ", f, "m", fmerge(f))
216 214 # are we clobbering?
217 215 # is remote's version newer?
218 216 # or are we going back in time and clean?
219 217 elif overwrite or m2[f] != a or (backwards and not n[20:]):
220 218 act("remote is newer", f, "g", m2.execf(f), m2[f])
221 219 # local is newer, not overwrite, check mode bits
222 220 elif fmerge(f) != m1.execf(f):
223 221 act("update permissions", f, "e", m2.execf(f))
224 222 # contents same, check mode bits
225 223 elif m1.execf(f) != m2.execf(f):
226 224 if overwrite or fmerge(f) != m1.execf(f):
227 225 act("update permissions", f, "e", m2.execf(f))
228 226 elif f in copy:
229 227 f2 = copy[f]
230 228 if f in ma: # case 3,20 A/B/A
231 229 act("remote moved",
232 f, "c", f2, f2, m1[f], m2[f2], fmerge(f, f2, f), True)
230 f, "c", f2, f2, fmerge(f, f2, f), True)
233 231 else:
234 232 if f2 in m1: # case 2 A,B/B/B
235 233 act("local copied",
236 f, "c", f2, f, m1[f], m2[f2], fmerge(f, f2, f2), False)
234 f, "c", f2, f, fmerge(f, f2, f2), False)
237 235 else: # case 4,21 A/B/B
238 236 act("local moved",
239 f, "c", f2, f, m1[f], m2[f2], fmerge(f, f2, f2), False)
237 f, "c", f2, f, fmerge(f, f2, f2), False)
240 238 elif f in ma:
241 239 if n != ma[f] and not overwrite:
242 240 if repo.ui.prompt(
243 241 (_(" local changed %s which remote deleted\n") % f) +
244 242 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("d"):
245 243 act("prompt delete", f, "r")
246 244 else:
247 245 act("other deleted", f, "r")
248 246 else:
249 247 # file is created on branch or in working directory
250 248 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
251 249 act("remote deleted", f, "r")
252 250
253 251 for f, n in m2.iteritems():
254 252 if partial and not partial(f):
255 253 continue
256 254 if f in m1:
257 255 continue
258 256 if f in copy:
259 257 f2 = copy[f]
260 258 if f2 not in m2: # already seen
261 259 continue
262 260 # rename case 1, A/A,B/A
263 act("remote copied",
264 f2, "c", f, f, m1[f2], m2[f], fmerge(f2, f, f2), False)
261 act("remote copied", f2, "c", f, f, fmerge(f2, f, f2), False)
265 262 elif f in ma:
266 263 if overwrite or backwards:
267 264 act("recreating", f, "g", m2.execf(f), n)
268 265 elif n != ma[f]:
269 266 if repo.ui.prompt(
270 267 (_("remote changed %s which local deleted\n") % f) +
271 268 _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("k"):
272 269 act("prompt recreating", f, "g", m2.execf(f), n)
273 270 else:
274 271 act("remote created", f, "g", m2.execf(f), n)
275 272
276 273 return action
277 274
278 275 def applyupdates(repo, action, wctx, mctx):
279 276 updated, merged, removed, unresolved = 0, 0, 0, 0
280 277 action.sort()
281 278 for a in action:
282 279 f, m = a[:2]
283 280 if f[0] == "/":
284 281 continue
285 282 if m == "r": # remove
286 283 repo.ui.note(_("removing %s\n") % f)
287 284 util.audit_path(f)
288 285 try:
289 286 util.unlink(repo.wjoin(f))
290 287 except OSError, inst:
291 288 if inst.errno != errno.ENOENT:
292 289 repo.ui.warn(_("update failed to remove %s: %s!\n") %
293 290 (f, inst.strerror))
294 291 removed +=1
295 292 elif m == "c": # copy
296 f2, fd, my, other, flag, move = a[2:]
293 f2, fd, flag, move = a[2:]
297 294 repo.ui.status(_("merging %s and %s to %s\n") % (f, f2, fd))
298 295 if filemerge(repo, f, f2, fd, wctx, mctx, move):
299 296 unresolved += 1
300 297 util.set_exec(repo.wjoin(fd), flag)
301 298 merged += 1
302 299 elif m == "m": # merge
303 flag, my, other = a[2:]
300 flag = a[2]
304 301 repo.ui.status(_("merging %s\n") % f)
305 302 if filemerge(repo, f, f, f, wctx, mctx, False):
306 303 unresolved += 1
307 304 util.set_exec(repo.wjoin(f), flag)
308 305 merged += 1
309 306 elif m == "g": # get
310 307 flag, node = a[2:]
311 308 repo.ui.note(_("getting %s\n") % f)
312 t = repo.file(f).read(node)
309 t = mctx.filectx(f).data()
313 310 repo.wwrite(f, t)
314 311 util.set_exec(repo.wjoin(f), flag)
315 312 updated += 1
316 313 elif m == "e": # exec
317 flag = a[2:]
314 flag = a[2]
318 315 util.set_exec(repo.wjoin(f), flag)
319 316
320 317 return updated, merged, removed, unresolved
321 318
322 def recordupdates(repo, action, branchmerge):
319 def recordupdates(repo, action, branchmerge, mctx):
323 320 for a in action:
324 321 f, m = a[:2]
325 322 if m == "r": # remove
326 323 if branchmerge:
327 324 repo.dirstate.update([f], 'r')
328 325 else:
329 326 repo.dirstate.forget([f])
330 327 elif m == "f": # forget
331 328 repo.dirstate.forget([f])
332 329 elif m == "g": # get
333 330 if branchmerge:
334 331 repo.dirstate.update([f], 'n', st_mtime=-1)
335 332 else:
336 333 repo.dirstate.update([f], 'n')
337 334 elif m == "m": # merge
338 flag, my, other = a[2:]
335 flag = a[2]
339 336 if branchmerge:
340 337 # We've done a branch merge, mark this file as merged
341 338 # so that we properly record the merger later
342 339 repo.dirstate.update([f], 'm')
343 340 else:
344 341 # We've update-merged a locally modified file, so
345 342 # we set the dirstate to emulate a normal checkout
346 343 # of that file some time in the past. Thus our
347 344 # merge will appear as a normal local file
348 345 # modification.
349 346 fl = repo.file(f)
350 f_len = fl.size(fl.rev(other))
347 f_len = mctx.filectx(f).size()
351 348 repo.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
352 349 elif m == "c": # copy
353 f2, fd, my, other, flag, move = a[2:]
350 f2, fd, flag, move = a[2:]
354 351 if branchmerge:
355 352 # We've done a branch merge, mark this file as merged
356 353 # so that we properly record the merger later
357 354 repo.dirstate.update([fd], 'm')
358 355 else:
359 356 # We've update-merged a locally modified file, so
360 357 # we set the dirstate to emulate a normal checkout
361 358 # of that file some time in the past. Thus our
362 359 # merge will appear as a normal local file
363 360 # modification.
364 361 fl = repo.file(f)
365 f_len = fl.size(fl.rev(other))
362 f_len = mctx.filectx(f).size()
366 363 repo.dirstate.update([fd], 'n', st_size=f_len, st_mtime=-1)
367 364 if move:
368 365 repo.dirstate.update([f], 'r')
369 366 if f != fd:
370 367 repo.dirstate.copy(f, fd)
371 368 else:
372 369 repo.dirstate.copy(f2, fd)
373 370
374 371 def update(repo, node, branchmerge=False, force=False, partial=None,
375 372 wlock=None, show_stats=True, remind=True):
376 373
377 374 overwrite = force and not branchmerge
378 375 forcemerge = force and branchmerge
379 376
380 377 if not wlock:
381 378 wlock = repo.wlock()
382 379
383 380 ### check phase
384 381
385 382 wc = repo.workingctx()
386 383 pl = wc.parents()
387 384 if not overwrite and len(pl) > 1:
388 385 raise util.Abort(_("outstanding uncommitted merges"))
389 386
390 387 p1, p2 = pl[0], repo.changectx(node)
391 388 pa = p1.ancestor(p2)
392 389
393 390 # is there a linear path from p1 to p2?
394 391 if pa == p1 or pa == p2:
395 392 if branchmerge:
396 393 raise util.Abort(_("there is nothing to merge, just use "
397 394 "'hg update' or look at 'hg heads'"))
398 395 elif not (overwrite or branchmerge):
399 396 raise util.Abort(_("update spans branches, use 'hg merge' "
400 397 "or 'hg update -C' to lose changes"))
401 398
402 399 if branchmerge and not forcemerge:
403 400 if wc.modified() or wc.added() or wc.removed():
404 401 raise util.Abort(_("outstanding uncommitted changes"))
405 402
406 403 m1 = wc.manifest()
407 404 m2 = p2.manifest()
408 405
409 406 # resolve the manifest to determine which files
410 407 # we care about merging
411 408 repo.ui.note(_("resolving manifests\n"))
412 409 repo.ui.debug(_(" overwrite %s branchmerge %s partial %s\n") %
413 410 (overwrite, branchmerge, bool(partial)))
414 411 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (p1, p2, pa))
415 412
416 413 action = []
417 414
418 415 if not force:
419 416 checkunknown(repo, m2, wc)
420 417 if not branchmerge:
421 418 action += forgetremoved(m2, wc)
422 419
423 420 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
424 421
425 422 ### apply phase
426 423
427 424 if not branchmerge:
428 425 # just jump to the new rev
429 426 fp1, fp2, xp1, xp2 = p2.node(), nullid, str(p2), ''
430 427 else:
431 428 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
432 429
433 430 if not partial:
434 431 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
435 432
436 433 updated, merged, removed, unresolved = applyupdates(repo, action, wc, p2)
437 434
438 435 # update dirstate
439 436 if not partial:
440 recordupdates(repo, action, branchmerge)
437 recordupdates(repo, action, branchmerge, p2)
441 438 repo.dirstate.setparents(fp1, fp2)
442 439 repo.hook('update', parent1=xp1, parent2=xp2, error=unresolved)
443 440
444 441 if show_stats:
445 442 stats = ((updated, _("updated")),
446 443 (merged - unresolved, _("merged")),
447 444 (removed, _("removed")),
448 445 (unresolved, _("unresolved")))
449 446 note = ", ".join([_("%d files %s") % s for s in stats])
450 447 repo.ui.status("%s\n" % note)
451 448 if not partial:
452 449 if branchmerge:
453 450 if unresolved:
454 451 repo.ui.status(_("There are unresolved merges,"
455 452 " you can redo the full merge using:\n"
456 453 " hg update -C %s\n"
457 454 " hg merge %s\n"
458 455 % (p1.rev(), p2.rev())))
459 456 elif remind:
460 457 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
461 458 elif unresolved:
462 459 repo.ui.status(_("There are unresolved merges with"
463 460 " locally modified files.\n"))
464 461
465 462 return unresolved
466 463
General Comments 0
You need to be logged in to leave comments. Login now