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