##// END OF EJS Templates
python-2.6: use sha wrapper from util for new merge code
Dirkjan Ochtman -
r6517:fcfb6a0a default
parent child Browse files
Show More
@@ -1,460 +1,460
1 1 # merge.py - directory-level update/merge handling for Mercurial
2 2 #
3 3 # Copyright 2006, 2007 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 nullid, nullrev, hex
9 9 from i18n import _
10 import errno, util, os, filemerge, copies, sha, shutil
10 import errno, util, os, filemerge, copies, shutil
11 11
12 12 class mergestate(object):
13 13 '''track 3-way merge state of individual files'''
14 14 def __init__(self, repo):
15 15 self._repo = repo
16 16 self._state = {}
17 17 self._data = {}
18 18 def reset(self, node):
19 19 self._local = node
20 20 shutil.rmtree(self._repo.join("merge"), True)
21 21 def add(self, fcl, fco, fca, fd, flags):
22 hash = sha.sha(fcl.path()).hexdigest()
22 hash = util.sha1(fcl.path()).hexdigest()
23 23 self._repo.opener("merge/" + hash, "w").write(fcl.data())
24 24 self._state[fd] = 'u'
25 25 self._data[fd] = (hash, fcl.path(), fca.path(), hex(fca.filenode()),
26 26 fco.path(), flags)
27 27 def __contains__(self, dfile):
28 28 return dfile in self._state
29 29 def __getitem__(self, dfile):
30 30 return self._state[dfile]
31 31 def mark(self, dfile, state):
32 32 self._state[dfile] = state
33 33 def resolve(self, dfile, wctx, octx):
34 34 if self[dfile] == 'r':
35 35 return 0
36 36 hash, lfile, afile, anode, ofile, flags = self._data[dfile]
37 37 f = self._repo.opener("merge/" + hash)
38 38 self._repo.wwrite(dfile, f.read(), flags)
39 39 fcd = wctx[dfile]
40 40 fco = octx[ofile]
41 41 fca = self._repo.filectx(afile, fileid=anode)
42 42 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
43 43 if not r:
44 44 util.set_flags(self._repo.wjoin(dfile), flags)
45 45 self.mark(dfile, 'r')
46 46 return r
47 47
48 48 def _checkunknown(wctx, mctx):
49 49 "check for collisions between unknown files and files in mctx"
50 50 for f in wctx.unknown():
51 51 if f in mctx and mctx[f].cmp(wctx[f].data()):
52 52 raise util.Abort(_("untracked file in working directory differs"
53 53 " from file in requested revision: '%s'") % f)
54 54
55 55 def _checkcollision(mctx):
56 56 "check for case folding collisions in the destination context"
57 57 folded = {}
58 58 for fn in mctx:
59 59 fold = fn.lower()
60 60 if fold in folded:
61 61 raise util.Abort(_("case-folding collision between %s and %s")
62 62 % (fn, folded[fold]))
63 63 folded[fold] = fn
64 64
65 65 def _forgetremoved(wctx, mctx, branchmerge):
66 66 """
67 67 Forget removed files
68 68
69 69 If we're jumping between revisions (as opposed to merging), and if
70 70 neither the working directory nor the target rev has the file,
71 71 then we need to remove it from the dirstate, to prevent the
72 72 dirstate from listing the file when it is no longer in the
73 73 manifest.
74 74
75 75 If we're merging, and the other revision has removed a file
76 76 that is not present in the working directory, we need to mark it
77 77 as removed.
78 78 """
79 79
80 80 action = []
81 81 state = branchmerge and 'r' or 'f'
82 82 for f in wctx.deleted():
83 83 if f not in mctx:
84 84 action.append((f, state))
85 85
86 86 if not branchmerge:
87 87 for f in wctx.removed():
88 88 if f not in mctx:
89 89 action.append((f, "f"))
90 90
91 91 return action
92 92
93 93 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
94 94 """
95 95 Merge p1 and p2 with ancestor ma and generate merge action list
96 96
97 97 overwrite = whether we clobber working files
98 98 partial = function to filter file lists
99 99 """
100 100
101 101 repo.ui.note(_("resolving manifests\n"))
102 102 repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial)))
103 103 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2))
104 104
105 105 m1 = p1.manifest()
106 106 m2 = p2.manifest()
107 107 ma = pa.manifest()
108 108 backwards = (pa == p2)
109 109 action = []
110 110 copy, copied, diverge = {}, {}, {}
111 111
112 112 def fmerge(f, f2=None, fa=None):
113 113 """merge flags"""
114 114 if not f2:
115 115 f2 = f
116 116 fa = f
117 117 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
118 118 if m == n: # flags agree
119 119 return m # unchanged
120 120 if m and n: # flags are set but don't agree
121 121 if not a: # both differ from parent
122 122 r = repo.ui.prompt(
123 123 _(" conflicting flags for %s\n"
124 124 "(n)one, e(x)ec or sym(l)ink?") % f, "[nxl]", "n")
125 125 return r != "n" and r or ''
126 126 if m == a:
127 127 return n # changed from m to n
128 128 return m # changed from n to m
129 129 if m and m != a: # changed from a to m
130 130 return m
131 131 if n and n != a: # changed from a to n
132 132 return n
133 133 return '' # flag was cleared
134 134
135 135 def act(msg, m, f, *args):
136 136 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
137 137 action.append((f, m) + args)
138 138
139 139 if pa and not (backwards or overwrite):
140 140 if repo.ui.configbool("merge", "followcopies", True):
141 141 dirs = repo.ui.configbool("merge", "followdirs", True)
142 142 copy, diverge = copies.copies(repo, p1, p2, pa, dirs)
143 143 copied = dict.fromkeys(copy.values())
144 144 for of, fl in diverge.items():
145 145 act("divergent renames", "dr", of, fl)
146 146
147 147 # Compare manifests
148 148 for f, n in m1.iteritems():
149 149 if partial and not partial(f):
150 150 continue
151 151 if f in m2:
152 152 if overwrite or backwards:
153 153 rflags = m2.flags(f)
154 154 else:
155 155 rflags = fmerge(f)
156 156 # are files different?
157 157 if n != m2[f]:
158 158 a = ma.get(f, nullid)
159 159 # are we clobbering?
160 160 if overwrite:
161 161 act("clobbering", "g", f, rflags)
162 162 # or are we going back in time and clean?
163 163 elif backwards and not n[20:]:
164 164 act("reverting", "g", f, rflags)
165 165 # are both different from the ancestor?
166 166 elif n != a and m2[f] != a:
167 167 act("versions differ", "m", f, f, f, rflags, False)
168 168 # is remote's version newer?
169 169 elif m2[f] != a:
170 170 act("remote is newer", "g", f, rflags)
171 171 # local is newer, not overwrite, check mode bits
172 172 elif m1.flags(f) != rflags:
173 173 act("update permissions", "e", f, rflags)
174 174 # contents same, check mode bits
175 175 elif m1.flags(f) != rflags:
176 176 act("update permissions", "e", f, rflags)
177 177 elif f in copied:
178 178 continue
179 179 elif f in copy:
180 180 f2 = copy[f]
181 181 if f2 not in m2: # directory rename
182 182 act("remote renamed directory to " + f2, "d",
183 183 f, None, f2, m1.flags(f))
184 184 elif f2 in m1: # case 2 A,B/B/B
185 185 act("local copied to " + f2, "m",
186 186 f, f2, f, fmerge(f, f2, f2), False)
187 187 else: # case 4,21 A/B/B
188 188 act("local moved to " + f2, "m",
189 189 f, f2, f, fmerge(f, f2, f2), False)
190 190 elif f in ma:
191 191 if n != ma[f] and not overwrite:
192 192 if repo.ui.prompt(
193 193 _(" local changed %s which remote deleted\n"
194 194 "use (c)hanged version or (d)elete?") % f,
195 195 _("[cd]"), _("c")) == _("d"):
196 196 act("prompt delete", "r", f)
197 197 else:
198 198 act("other deleted", "r", f)
199 199 else:
200 200 # file is created on branch or in working directory
201 201 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
202 202 act("remote deleted", "r", f)
203 203
204 204 for f, n in m2.iteritems():
205 205 if partial and not partial(f):
206 206 continue
207 207 if f in m1:
208 208 continue
209 209 if f in copied:
210 210 continue
211 211 if f in copy:
212 212 f2 = copy[f]
213 213 if f2 not in m1: # directory rename
214 214 act("local renamed directory to " + f2, "d",
215 215 None, f, f2, m2.flags(f))
216 216 elif f2 in m2: # rename case 1, A/A,B/A
217 217 act("remote copied to " + f, "m",
218 218 f2, f, f, fmerge(f2, f, f2), False)
219 219 else: # case 3,20 A/B/A
220 220 act("remote moved to " + f, "m",
221 221 f2, f, f, fmerge(f2, f, f2), True)
222 222 elif f in ma:
223 223 if overwrite or backwards:
224 224 act("recreating", "g", f, m2.flags(f))
225 225 elif n != ma[f]:
226 226 if repo.ui.prompt(
227 227 _("remote changed %s which local deleted\n"
228 228 "use (c)hanged version or leave (d)eleted?") % f,
229 229 _("[cd]"), _("c")) == _("c"):
230 230 act("prompt recreating", "g", f, m2.flags(f))
231 231 else:
232 232 act("remote created", "g", f, m2.flags(f))
233 233
234 234 return action
235 235
236 236 def applyupdates(repo, action, wctx, mctx):
237 237 "apply the merge action list to the working directory"
238 238
239 239 updated, merged, removed, unresolved = 0, 0, 0, 0
240 240 action.sort()
241 241
242 242 ms = mergestate(repo)
243 243 ms.reset(wctx.parents()[0].node())
244 244 moves = []
245 245
246 246 # prescan for merges
247 247 for a in action:
248 248 f, m = a[:2]
249 249 if m == 'm': # merge
250 250 f2, fd, flags, move = a[2:]
251 251 repo.ui.debug(_("preserving %s for resolve of %s\n") % (f, fd))
252 252 fcl = wctx[f]
253 253 fco = mctx[f2]
254 254 fca = fcl.ancestor(fco) or repo.filectx(f, fileid=nullrev)
255 255 ms.add(fcl, fco, fca, fd, flags)
256 256 if f != fd and move:
257 257 moves.append(f)
258 258
259 259 # remove renamed files after safely stored
260 260 for f in moves:
261 261 if util.lexists(repo.wjoin(f)):
262 262 repo.ui.debug(_("removing %s\n") % f)
263 263 os.unlink(repo.wjoin(f))
264 264
265 265 audit_path = util.path_auditor(repo.root)
266 266
267 267 for a in action:
268 268 f, m = a[:2]
269 269 if f and f[0] == "/":
270 270 continue
271 271 if m == "r": # remove
272 272 repo.ui.note(_("removing %s\n") % f)
273 273 audit_path(f)
274 274 try:
275 275 util.unlink(repo.wjoin(f))
276 276 except OSError, inst:
277 277 if inst.errno != errno.ENOENT:
278 278 repo.ui.warn(_("update failed to remove %s: %s!\n") %
279 279 (f, inst.strerror))
280 280 removed += 1
281 281 elif m == "m": # merge
282 282 f2, fd, flags, move = a[2:]
283 283 r = ms.resolve(fd, wctx, mctx)
284 284 if r > 0:
285 285 unresolved += 1
286 286 else:
287 287 if r is None:
288 288 updated += 1
289 289 else:
290 290 merged += 1
291 291 elif m == "g": # get
292 292 flags = a[2]
293 293 repo.ui.note(_("getting %s\n") % f)
294 294 t = mctx.filectx(f).data()
295 295 repo.wwrite(f, t, flags)
296 296 updated += 1
297 297 elif m == "d": # directory rename
298 298 f2, fd, flags = a[2:]
299 299 if f:
300 300 repo.ui.note(_("moving %s to %s\n") % (f, fd))
301 301 t = wctx.filectx(f).data()
302 302 repo.wwrite(fd, t, flags)
303 303 util.unlink(repo.wjoin(f))
304 304 if f2:
305 305 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
306 306 t = mctx.filectx(f2).data()
307 307 repo.wwrite(fd, t, flags)
308 308 updated += 1
309 309 elif m == "dr": # divergent renames
310 310 fl = a[2]
311 311 repo.ui.warn("warning: detected divergent renames of %s to:\n" % f)
312 312 for nf in fl:
313 313 repo.ui.warn(" %s\n" % nf)
314 314 elif m == "e": # exec
315 315 flags = a[2]
316 316 util.set_flags(repo.wjoin(f), flags)
317 317
318 318 return updated, merged, removed, unresolved
319 319
320 320 def recordupdates(repo, action, branchmerge):
321 321 "record merge actions to the dirstate"
322 322
323 323 for a in action:
324 324 f, m = a[:2]
325 325 if m == "r": # remove
326 326 if branchmerge:
327 327 repo.dirstate.remove(f)
328 328 else:
329 329 repo.dirstate.forget(f)
330 330 elif m == "f": # forget
331 331 repo.dirstate.forget(f)
332 332 elif m in "ge": # get or exec change
333 333 if branchmerge:
334 334 repo.dirstate.normaldirty(f)
335 335 else:
336 336 repo.dirstate.normal(f)
337 337 elif m == "m": # merge
338 338 f2, fd, flag, move = a[2:]
339 339 if branchmerge:
340 340 # We've done a branch merge, mark this file as merged
341 341 # so that we properly record the merger later
342 342 repo.dirstate.merge(fd)
343 343 if f != f2: # copy/rename
344 344 if move:
345 345 repo.dirstate.remove(f)
346 346 if f != fd:
347 347 repo.dirstate.copy(f, fd)
348 348 else:
349 349 repo.dirstate.copy(f2, fd)
350 350 else:
351 351 # We've update-merged a locally modified file, so
352 352 # we set the dirstate to emulate a normal checkout
353 353 # of that file some time in the past. Thus our
354 354 # merge will appear as a normal local file
355 355 # modification.
356 356 repo.dirstate.normallookup(fd)
357 357 if move:
358 358 repo.dirstate.forget(f)
359 359 elif m == "d": # directory rename
360 360 f2, fd, flag = a[2:]
361 361 if not f2 and f not in repo.dirstate:
362 362 # untracked file moved
363 363 continue
364 364 if branchmerge:
365 365 repo.dirstate.add(fd)
366 366 if f:
367 367 repo.dirstate.remove(f)
368 368 repo.dirstate.copy(f, fd)
369 369 if f2:
370 370 repo.dirstate.copy(f2, fd)
371 371 else:
372 372 repo.dirstate.normal(fd)
373 373 if f:
374 374 repo.dirstate.forget(f)
375 375
376 376 def update(repo, node, branchmerge, force, partial):
377 377 """
378 378 Perform a merge between the working directory and the given node
379 379
380 380 branchmerge = whether to merge between branches
381 381 force = whether to force branch merging or file overwriting
382 382 partial = a function to filter file lists (dirstate not updated)
383 383 """
384 384
385 385 wlock = repo.wlock()
386 386 try:
387 387 wc = repo.workingctx()
388 388 if node is None:
389 389 # tip of current branch
390 390 try:
391 391 node = repo.branchtags()[wc.branch()]
392 392 except KeyError:
393 393 if wc.branch() == "default": # no default branch!
394 394 node = repo.lookup("tip") # update to tip
395 395 else:
396 396 raise util.Abort(_("branch %s not found") % wc.branch())
397 397 overwrite = force and not branchmerge
398 398 pl = wc.parents()
399 399 p1, p2 = pl[0], repo.changectx(node)
400 400 pa = p1.ancestor(p2)
401 401 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
402 402 fastforward = False
403 403
404 404 ### check phase
405 405 if not overwrite and len(pl) > 1:
406 406 raise util.Abort(_("outstanding uncommitted merges"))
407 407 if branchmerge:
408 408 if pa == p2:
409 409 raise util.Abort(_("can't merge with ancestor"))
410 410 elif pa == p1:
411 411 if p1.branch() != p2.branch():
412 412 fastforward = True
413 413 else:
414 414 raise util.Abort(_("nothing to merge (use 'hg update'"
415 415 " or check 'hg heads')"))
416 416 if not force and (wc.files() or wc.deleted()):
417 417 raise util.Abort(_("outstanding uncommitted changes"))
418 418 elif not overwrite:
419 419 if pa == p1 or pa == p2: # linear
420 420 pass # all good
421 421 elif p1.branch() == p2.branch():
422 422 if wc.files() or wc.deleted():
423 423 raise util.Abort(_("crosses branches (use 'hg merge' or "
424 424 "'hg update -C' to discard changes)"))
425 425 raise util.Abort(_("crosses branches (use 'hg merge' "
426 426 "or 'hg update -C')"))
427 427 elif wc.files() or wc.deleted():
428 428 raise util.Abort(_("crosses named branches (use "
429 429 "'hg update -C' to discard changes)"))
430 430 else:
431 431 # Allow jumping branches if there are no changes
432 432 overwrite = True
433 433
434 434 ### calculate phase
435 435 action = []
436 436 if not force:
437 437 _checkunknown(wc, p2)
438 438 if not util.checkfolding(repo.path):
439 439 _checkcollision(p2)
440 440 action += _forgetremoved(wc, p2, branchmerge)
441 441 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
442 442
443 443 ### apply phase
444 444 if not branchmerge: # just jump to the new rev
445 445 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
446 446 if not partial:
447 447 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
448 448
449 449 stats = applyupdates(repo, action, wc, p2)
450 450
451 451 if not partial:
452 452 recordupdates(repo, action, branchmerge)
453 453 repo.dirstate.setparents(fp1, fp2)
454 454 if not branchmerge and not fastforward:
455 455 repo.dirstate.setbranch(p2.branch())
456 456 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
457 457
458 458 return stats
459 459 finally:
460 460 del wlock
General Comments 0
You need to be logged in to leave comments. Login now