##// END OF EJS Templates
icasefs: rewrite case-folding collision detection (issue3452)...
FUJIWARA Katsunori -
r19105:c60a7f5a stable
parent child Browse files
Show More
@@ -1,752 +1,760 b''
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 of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import nullid, nullrev, hex, bin
9 9 from i18n import _
10 10 from mercurial import obsolete
11 11 import error, util, filemerge, copies, subrepo, worker, dicthelpers
12 12 import errno, os, shutil
13 13
14 14 class mergestate(object):
15 15 '''track 3-way merge state of individual files'''
16 16 def __init__(self, repo):
17 17 self._repo = repo
18 18 self._dirty = False
19 19 self._read()
20 20 def reset(self, node=None):
21 21 self._state = {}
22 22 if node:
23 23 self._local = node
24 24 shutil.rmtree(self._repo.join("merge"), True)
25 25 self._dirty = False
26 26 def _read(self):
27 27 self._state = {}
28 28 try:
29 29 f = self._repo.opener("merge/state")
30 30 for i, l in enumerate(f):
31 31 if i == 0:
32 32 self._local = bin(l[:-1])
33 33 else:
34 34 bits = l[:-1].split("\0")
35 35 self._state[bits[0]] = bits[1:]
36 36 f.close()
37 37 except IOError, err:
38 38 if err.errno != errno.ENOENT:
39 39 raise
40 40 self._dirty = False
41 41 def commit(self):
42 42 if self._dirty:
43 43 f = self._repo.opener("merge/state", "w")
44 44 f.write(hex(self._local) + "\n")
45 45 for d, v in self._state.iteritems():
46 46 f.write("\0".join([d] + v) + "\n")
47 47 f.close()
48 48 self._dirty = False
49 49 def add(self, fcl, fco, fca, fd):
50 50 hash = util.sha1(fcl.path()).hexdigest()
51 51 self._repo.opener.write("merge/" + hash, fcl.data())
52 52 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
53 53 hex(fca.filenode()), fco.path(), fcl.flags()]
54 54 self._dirty = True
55 55 def __contains__(self, dfile):
56 56 return dfile in self._state
57 57 def __getitem__(self, dfile):
58 58 return self._state[dfile][0]
59 59 def __iter__(self):
60 60 l = self._state.keys()
61 61 l.sort()
62 62 for f in l:
63 63 yield f
64 64 def mark(self, dfile, state):
65 65 self._state[dfile][0] = state
66 66 self._dirty = True
67 67 def resolve(self, dfile, wctx, octx):
68 68 if self[dfile] == 'r':
69 69 return 0
70 70 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
71 71 fcd = wctx[dfile]
72 72 fco = octx[ofile]
73 73 fca = self._repo.filectx(afile, fileid=anode)
74 74 # "premerge" x flags
75 75 flo = fco.flags()
76 76 fla = fca.flags()
77 77 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
78 78 if fca.node() == nullid:
79 79 self._repo.ui.warn(_('warning: cannot merge flags for %s\n') %
80 80 afile)
81 81 elif flags == fla:
82 82 flags = flo
83 83 # restore local
84 84 f = self._repo.opener("merge/" + hash)
85 85 self._repo.wwrite(dfile, f.read(), flags)
86 86 f.close()
87 87 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
88 88 if r is None:
89 89 # no real conflict
90 90 del self._state[dfile]
91 91 elif not r:
92 92 self.mark(dfile, 'r')
93 93 return r
94 94
95 95 def _checkunknownfile(repo, wctx, mctx, f):
96 96 return (not repo.dirstate._ignore(f)
97 97 and os.path.isfile(repo.wjoin(f))
98 98 and repo.dirstate.normalize(f) not in repo.dirstate
99 99 and mctx[f].cmp(wctx[f]))
100 100
101 101 def _checkunknown(repo, wctx, mctx):
102 102 "check for collisions between unknown files and files in mctx"
103 103
104 104 error = False
105 105 for f in mctx:
106 106 if f not in wctx and _checkunknownfile(repo, wctx, mctx, f):
107 107 error = True
108 108 wctx._repo.ui.warn(_("%s: untracked file differs\n") % f)
109 109 if error:
110 110 raise util.Abort(_("untracked files in working directory differ "
111 111 "from files in requested revision"))
112 112
113 def _remains(f, m, ma, workingctx=False):
114 """check whether specified file remains after merge.
115
116 It is assumed that specified file is not contained in the manifest
117 of the other context.
118 """
119 if f in ma:
120 n = m[f]
121 if n != ma[f]:
122 return True # because it is changed locally
123 # even though it doesn't remain, if "remote deleted" is
124 # chosen in manifestmerge()
125 elif workingctx and n[20:] == "a":
126 return True # because it is added locally (linear merge specific)
127 else:
128 return False # because it is removed remotely
129 else:
130 return True # because it is added locally
131
132 def _checkcollision(mctx, extractxs):
133 "check for case folding collisions in the destination context"
134 folded = {}
135 for fn in mctx:
136 fold = util.normcase(fn)
137 if fold in folded:
138 raise util.Abort(_("case-folding collision between %s and %s")
139 % (fn, folded[fold]))
140 folded[fold] = fn
141
142 if extractxs:
143 wctx, actx = extractxs
144 # class to delay looking up copy mapping
145 class pathcopies(object):
146 @util.propertycache
147 def map(self):
148 # {dst@mctx: src@wctx} copy mapping
149 return copies.pathcopies(wctx, mctx)
150 pc = pathcopies()
151
152 for fn in wctx:
153 fold = util.normcase(fn)
154 mfn = folded.get(fold, None)
155 if (mfn and mfn != fn and pc.map.get(mfn) != fn and
156 _remains(fn, wctx.manifest(), actx.manifest(), True) and
157 _remains(mfn, mctx.manifest(), actx.manifest())):
158 raise util.Abort(_("case-folding collision between %s and %s")
159 % (mfn, fn))
160
161 113 def _forgetremoved(wctx, mctx, branchmerge):
162 114 """
163 115 Forget removed files
164 116
165 117 If we're jumping between revisions (as opposed to merging), and if
166 118 neither the working directory nor the target rev has the file,
167 119 then we need to remove it from the dirstate, to prevent the
168 120 dirstate from listing the file when it is no longer in the
169 121 manifest.
170 122
171 123 If we're merging, and the other revision has removed a file
172 124 that is not present in the working directory, we need to mark it
173 125 as removed.
174 126 """
175 127
176 128 actions = []
177 129 state = branchmerge and 'r' or 'f'
178 130 for f in wctx.deleted():
179 131 if f not in mctx:
180 132 actions.append((f, state, None, "forget deleted"))
181 133
182 134 if not branchmerge:
183 135 for f in wctx.removed():
184 136 if f not in mctx:
185 137 actions.append((f, "f", None, "forget removed"))
186 138
187 139 return actions
188 140
141 def _checkcollision(repo, wmf, actions, prompts):
142 # build provisional merged manifest up
143 pmmf = set(wmf)
144
145 def addop(f, args):
146 pmmf.add(f)
147 def removeop(f, args):
148 pmmf.discard(f)
149 def nop(f, args):
150 pass
151
152 def renameop(f, args):
153 f2, fd, flags = args
154 if f:
155 pmmf.discard(f)
156 pmmf.add(fd)
157 def mergeop(f, args):
158 f2, fd, move = args
159 if move:
160 pmmf.discard(f)
161 pmmf.add(fd)
162
163 opmap = {
164 "a": addop,
165 "d": renameop,
166 "dr": nop,
167 "e": nop,
168 "f": addop, # untracked file should be kept in working directory
169 "g": addop,
170 "m": mergeop,
171 "r": removeop,
172 "rd": nop,
173 }
174 for f, m, args, msg in actions:
175 op = opmap.get(m)
176 assert op, m
177 op(f, args)
178
179 opmap = {
180 "cd": addop,
181 "dc": addop,
182 }
183 for f, m in prompts:
184 op = opmap.get(m)
185 assert op, m
186 op(f, None)
187
188 # check case-folding collision in provisional merged manifest
189 foldmap = {}
190 for f in sorted(pmmf):
191 fold = util.normcase(f)
192 if fold in foldmap:
193 raise util.Abort(_("case-folding collision between %s and %s")
194 % (f, foldmap[fold]))
195 foldmap[fold] = f
196
189 197 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial,
190 198 acceptremote=False):
191 199 """
192 200 Merge p1 and p2 with ancestor pa and generate merge action list
193 201
194 202 branchmerge and force are as passed in to update
195 203 partial = function to filter file lists
196 204 acceptremote = accept the incoming changes without prompting
197 205 """
198 206
199 207 overwrite = force and not branchmerge
200 208 actions, copy, movewithdir = [], {}, {}
201 209
202 210 followcopies = False
203 211 if overwrite:
204 212 pa = wctx
205 213 elif pa == p2: # backwards
206 214 pa = wctx.p1()
207 215 elif not branchmerge and not wctx.dirty(missing=True):
208 216 pass
209 217 elif pa and repo.ui.configbool("merge", "followcopies", True):
210 218 followcopies = True
211 219
212 220 # manifests fetched in order are going to be faster, so prime the caches
213 221 [x.manifest() for x in
214 222 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
215 223
216 224 if followcopies:
217 225 ret = copies.mergecopies(repo, wctx, p2, pa)
218 226 copy, movewithdir, diverge, renamedelete = ret
219 227 for of, fl in diverge.iteritems():
220 228 actions.append((of, "dr", (fl,), "divergent renames"))
221 229 for of, fl in renamedelete.iteritems():
222 230 actions.append((of, "rd", (fl,), "rename and delete"))
223 231
224 232 repo.ui.note(_("resolving manifests\n"))
225 233 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
226 234 % (bool(branchmerge), bool(force), bool(partial)))
227 235 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
228 236
229 237 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
230 238 copied = set(copy.values())
231 239 copied.update(movewithdir.values())
232 240
233 241 if '.hgsubstate' in m1:
234 242 # check whether sub state is modified
235 243 for s in sorted(wctx.substate):
236 244 if wctx.sub(s).dirty():
237 245 m1['.hgsubstate'] += "+"
238 246 break
239 247
240 248 aborts, prompts = [], []
241 249 # Compare manifests
242 250 fdiff = dicthelpers.diff(m1, m2)
243 251 flagsdiff = m1.flagsdiff(m2)
244 252 diff12 = dicthelpers.join(fdiff, flagsdiff)
245 253
246 254 for f, (n12, fl12) in diff12.iteritems():
247 255 if n12:
248 256 n1, n2 = n12
249 257 else: # file contents didn't change, but flags did
250 258 n1 = n2 = m1.get(f, None)
251 259 if n1 is None:
252 260 # Since n1 == n2, the file isn't present in m2 either. This
253 261 # means that the file was removed or deleted locally and
254 262 # removed remotely, but that residual entries remain in flags.
255 263 # This can happen in manifests generated by workingctx.
256 264 continue
257 265 if fl12:
258 266 fl1, fl2 = fl12
259 267 else: # flags didn't change, file contents did
260 268 fl1 = fl2 = m1.flags(f)
261 269
262 270 if partial and not partial(f):
263 271 continue
264 272 if n1 and n2:
265 273 fla = ma.flags(f)
266 274 nol = 'l' not in fl1 + fl2 + fla
267 275 a = ma.get(f, nullid)
268 276 if n2 == a and fl2 == fla:
269 277 pass # remote unchanged - keep local
270 278 elif n1 == a and fl1 == fla: # local unchanged - use remote
271 279 if n1 == n2: # optimization: keep local content
272 280 actions.append((f, "e", (fl2,), "update permissions"))
273 281 else:
274 282 actions.append((f, "g", (fl2,), "remote is newer"))
275 283 elif nol and n2 == a: # remote only changed 'x'
276 284 actions.append((f, "e", (fl2,), "update permissions"))
277 285 elif nol and n1 == a: # local only changed 'x'
278 286 actions.append((f, "g", (fl1,), "remote is newer"))
279 287 else: # both changed something
280 288 actions.append((f, "m", (f, f, False), "versions differ"))
281 289 elif f in copied: # files we'll deal with on m2 side
282 290 pass
283 291 elif n1 and f in movewithdir: # directory rename
284 292 f2 = movewithdir[f]
285 293 actions.append((f, "d", (None, f2, fl1),
286 294 "remote renamed directory to " + f2))
287 295 elif n1 and f in copy:
288 296 f2 = copy[f]
289 297 actions.append((f, "m", (f2, f, False),
290 298 "local copied/moved to " + f2))
291 299 elif n1 and f in ma: # clean, a different, no remote
292 300 if n1 != ma[f]:
293 301 prompts.append((f, "cd")) # prompt changed/deleted
294 302 elif n1[20:] == "a": # added, no remote
295 303 actions.append((f, "f", None, "remote deleted"))
296 304 else:
297 305 actions.append((f, "r", None, "other deleted"))
298 306 elif n2 and f in movewithdir:
299 307 f2 = movewithdir[f]
300 308 actions.append((None, "d", (f, f2, fl2),
301 309 "local renamed directory to " + f2))
302 310 elif n2 and f in copy:
303 311 f2 = copy[f]
304 312 if f2 in m2:
305 313 actions.append((f2, "m", (f, f, False),
306 314 "remote copied to " + f))
307 315 else:
308 316 actions.append((f2, "m", (f, f, True),
309 317 "remote moved to " + f))
310 318 elif n2 and f not in ma:
311 319 # local unknown, remote created: the logic is described by the
312 320 # following table:
313 321 #
314 322 # force branchmerge different | action
315 323 # n * n | get
316 324 # n * y | abort
317 325 # y n * | get
318 326 # y y n | get
319 327 # y y y | merge
320 328 #
321 329 # Checking whether the files are different is expensive, so we
322 330 # don't do that when we can avoid it.
323 331 if force and not branchmerge:
324 332 actions.append((f, "g", (fl2,), "remote created"))
325 333 else:
326 334 different = _checkunknownfile(repo, wctx, p2, f)
327 335 if force and branchmerge and different:
328 336 actions.append((f, "m", (f, f, False),
329 337 "remote differs from untracked local"))
330 338 elif not force and different:
331 339 aborts.append((f, "ud"))
332 340 else:
333 341 actions.append((f, "g", (fl2,), "remote created"))
334 342 elif n2 and n2 != ma[f]:
335 343 prompts.append((f, "dc")) # prompt deleted/changed
336 344
337 345 for f, m in sorted(aborts):
338 346 if m == "ud":
339 347 repo.ui.warn(_("%s: untracked file differs\n") % f)
340 348 else: assert False, m
341 349 if aborts:
342 350 raise util.Abort(_("untracked files in working directory differ "
343 351 "from files in requested revision"))
344 352
353 if not util.checkcase(repo.path):
354 # check collision between files only in p2 for clean update
355 if (not branchmerge and
356 (force or not wctx.dirty(missing=True, branch=False))):
357 _checkcollision(repo, m2, [], [])
358 else:
359 _checkcollision(repo, m1, actions, prompts)
360
345 361 for f, m in sorted(prompts):
346 362 if m == "cd":
347 363 if acceptremote:
348 364 actions.append((f, "r", None, "remote delete"))
349 365 elif repo.ui.promptchoice(
350 366 _("local changed %s which remote deleted\n"
351 367 "use (c)hanged version or (d)elete?") % f,
352 368 (_("&Changed"), _("&Delete")), 0):
353 369 actions.append((f, "r", None, "prompt delete"))
354 370 else:
355 371 actions.append((f, "a", None, "prompt keep"))
356 372 elif m == "dc":
357 373 if acceptremote:
358 374 actions.append((f, "g", (m2.flags(f),), "remote recreating"))
359 375 elif repo.ui.promptchoice(
360 376 _("remote changed %s which local deleted\n"
361 377 "use (c)hanged version or leave (d)eleted?") % f,
362 378 (_("&Changed"), _("&Deleted")), 0) == 0:
363 379 actions.append((f, "g", (m2.flags(f),), "prompt recreating"))
364 380 else: assert False, m
365 381 return actions
366 382
367 383 def actionkey(a):
368 384 return a[1] == "r" and -1 or 0, a
369 385
370 386 def getremove(repo, mctx, overwrite, args):
371 387 """apply usually-non-interactive updates to the working directory
372 388
373 389 mctx is the context to be merged into the working copy
374 390
375 391 yields tuples for progress updates
376 392 """
377 393 verbose = repo.ui.verbose
378 394 unlink = util.unlinkpath
379 395 wjoin = repo.wjoin
380 396 fctx = mctx.filectx
381 397 wwrite = repo.wwrite
382 398 audit = repo.wopener.audit
383 399 i = 0
384 400 for arg in args:
385 401 f = arg[0]
386 402 if arg[1] == 'r':
387 403 if verbose:
388 404 repo.ui.note(_("removing %s\n") % f)
389 405 audit(f)
390 406 try:
391 407 unlink(wjoin(f), ignoremissing=True)
392 408 except OSError, inst:
393 409 repo.ui.warn(_("update failed to remove %s: %s!\n") %
394 410 (f, inst.strerror))
395 411 else:
396 412 if verbose:
397 413 repo.ui.note(_("getting %s\n") % f)
398 414 wwrite(f, fctx(f).data(), arg[2][0])
399 415 if i == 100:
400 416 yield i, f
401 417 i = 0
402 418 i += 1
403 419 if i > 0:
404 420 yield i, f
405 421
406 422 def applyupdates(repo, actions, wctx, mctx, actx, overwrite):
407 423 """apply the merge action list to the working directory
408 424
409 425 wctx is the working copy context
410 426 mctx is the context to be merged into the working copy
411 427 actx is the context of the common ancestor
412 428
413 429 Return a tuple of counts (updated, merged, removed, unresolved) that
414 430 describes how many files were affected by the update.
415 431 """
416 432
417 433 updated, merged, removed, unresolved = 0, 0, 0, 0
418 434 ms = mergestate(repo)
419 435 ms.reset(wctx.p1().node())
420 436 moves = []
421 437 actions.sort(key=actionkey)
422 438
423 439 # prescan for merges
424 440 for a in actions:
425 441 f, m, args, msg = a
426 442 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
427 443 if m == "m": # merge
428 444 f2, fd, move = args
429 445 if fd == '.hgsubstate': # merged internally
430 446 continue
431 447 repo.ui.debug(" preserving %s for resolve of %s\n" % (f, fd))
432 448 fcl = wctx[f]
433 449 fco = mctx[f2]
434 450 if mctx == actx: # backwards, use working dir parent as ancestor
435 451 if fcl.parents():
436 452 fca = fcl.p1()
437 453 else:
438 454 fca = repo.filectx(f, fileid=nullrev)
439 455 else:
440 456 fca = fcl.ancestor(fco, actx)
441 457 if not fca:
442 458 fca = repo.filectx(f, fileid=nullrev)
443 459 ms.add(fcl, fco, fca, fd)
444 460 if f != fd and move:
445 461 moves.append(f)
446 462
447 463 audit = repo.wopener.audit
448 464
449 465 # remove renamed files after safely stored
450 466 for f in moves:
451 467 if os.path.lexists(repo.wjoin(f)):
452 468 repo.ui.debug("removing %s\n" % f)
453 469 audit(f)
454 470 util.unlinkpath(repo.wjoin(f))
455 471
456 472 numupdates = len(actions)
457 473 workeractions = [a for a in actions if a[1] in 'gr']
458 474 updateactions = [a for a in workeractions if a[1] == 'g']
459 475 updated = len(updateactions)
460 476 removeactions = [a for a in workeractions if a[1] == 'r']
461 477 removed = len(removeactions)
462 478 actions = [a for a in actions if a[1] not in 'gr']
463 479
464 480 hgsub = [a[1] for a in workeractions if a[0] == '.hgsubstate']
465 481 if hgsub and hgsub[0] == 'r':
466 482 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
467 483
468 484 z = 0
469 485 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
470 486 removeactions)
471 487 for i, item in prog:
472 488 z += i
473 489 repo.ui.progress(_('updating'), z, item=item, total=numupdates,
474 490 unit=_('files'))
475 491 prog = worker.worker(repo.ui, 0.001, getremove, (repo, mctx, overwrite),
476 492 updateactions)
477 493 for i, item in prog:
478 494 z += i
479 495 repo.ui.progress(_('updating'), z, item=item, total=numupdates,
480 496 unit=_('files'))
481 497
482 498 if hgsub and hgsub[0] == 'g':
483 499 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
484 500
485 501 _updating = _('updating')
486 502 _files = _('files')
487 503 progress = repo.ui.progress
488 504
489 505 for i, a in enumerate(actions):
490 506 f, m, args, msg = a
491 507 progress(_updating, z + i + 1, item=f, total=numupdates, unit=_files)
492 508 if m == "m": # merge
493 509 f2, fd, move = args
494 510 if fd == '.hgsubstate': # subrepo states need updating
495 511 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
496 512 overwrite)
497 513 continue
498 514 audit(fd)
499 515 r = ms.resolve(fd, wctx, mctx)
500 516 if r is not None and r > 0:
501 517 unresolved += 1
502 518 else:
503 519 if r is None:
504 520 updated += 1
505 521 else:
506 522 merged += 1
507 523 elif m == "d": # directory rename
508 524 f2, fd, flags = args
509 525 if f:
510 526 repo.ui.note(_("moving %s to %s\n") % (f, fd))
511 527 audit(f)
512 528 repo.wwrite(fd, wctx.filectx(f).data(), flags)
513 529 util.unlinkpath(repo.wjoin(f))
514 530 if f2:
515 531 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
516 532 repo.wwrite(fd, mctx.filectx(f2).data(), flags)
517 533 updated += 1
518 534 elif m == "dr": # divergent renames
519 535 fl, = args
520 536 repo.ui.warn(_("note: possible conflict - %s was renamed "
521 537 "multiple times to:\n") % f)
522 538 for nf in fl:
523 539 repo.ui.warn(" %s\n" % nf)
524 540 elif m == "rd": # rename and delete
525 541 fl, = args
526 542 repo.ui.warn(_("note: possible conflict - %s was deleted "
527 543 "and renamed to:\n") % f)
528 544 for nf in fl:
529 545 repo.ui.warn(" %s\n" % nf)
530 546 elif m == "e": # exec
531 547 flags, = args
532 548 audit(f)
533 549 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
534 550 updated += 1
535 551 ms.commit()
536 552 progress(_updating, None, total=numupdates, unit=_files)
537 553
538 554 return updated, merged, removed, unresolved
539 555
540 556 def calculateupdates(repo, tctx, mctx, ancestor, branchmerge, force, partial,
541 557 acceptremote=False):
542 558 "Calculate the actions needed to merge mctx into tctx"
543 559 actions = []
544 folding = not util.checkcase(repo.path)
545 if folding:
546 # collision check is not needed for clean update
547 if (not branchmerge and
548 (force or not tctx.dirty(missing=True, branch=False))):
549 _checkcollision(mctx, None)
550 else:
551 _checkcollision(mctx, (tctx, ancestor))
552 560 actions += manifestmerge(repo, tctx, mctx,
553 561 ancestor,
554 562 branchmerge, force,
555 563 partial, acceptremote)
556 564 if tctx.rev() is None:
557 565 actions += _forgetremoved(tctx, mctx, branchmerge)
558 566 return actions
559 567
560 568 def recordupdates(repo, actions, branchmerge):
561 569 "record merge actions to the dirstate"
562 570
563 571 for a in actions:
564 572 f, m, args, msg = a
565 573 if m == "r": # remove
566 574 if branchmerge:
567 575 repo.dirstate.remove(f)
568 576 else:
569 577 repo.dirstate.drop(f)
570 578 elif m == "a": # re-add
571 579 if not branchmerge:
572 580 repo.dirstate.add(f)
573 581 elif m == "f": # forget
574 582 repo.dirstate.drop(f)
575 583 elif m == "e": # exec change
576 584 repo.dirstate.normallookup(f)
577 585 elif m == "g": # get
578 586 if branchmerge:
579 587 repo.dirstate.otherparent(f)
580 588 else:
581 589 repo.dirstate.normal(f)
582 590 elif m == "m": # merge
583 591 f2, fd, move = args
584 592 if branchmerge:
585 593 # We've done a branch merge, mark this file as merged
586 594 # so that we properly record the merger later
587 595 repo.dirstate.merge(fd)
588 596 if f != f2: # copy/rename
589 597 if move:
590 598 repo.dirstate.remove(f)
591 599 if f != fd:
592 600 repo.dirstate.copy(f, fd)
593 601 else:
594 602 repo.dirstate.copy(f2, fd)
595 603 else:
596 604 # We've update-merged a locally modified file, so
597 605 # we set the dirstate to emulate a normal checkout
598 606 # of that file some time in the past. Thus our
599 607 # merge will appear as a normal local file
600 608 # modification.
601 609 if f2 == fd: # file not locally copied/moved
602 610 repo.dirstate.normallookup(fd)
603 611 if move:
604 612 repo.dirstate.drop(f)
605 613 elif m == "d": # directory rename
606 614 f2, fd, flag = args
607 615 if not f2 and f not in repo.dirstate:
608 616 # untracked file moved
609 617 continue
610 618 if branchmerge:
611 619 repo.dirstate.add(fd)
612 620 if f:
613 621 repo.dirstate.remove(f)
614 622 repo.dirstate.copy(f, fd)
615 623 if f2:
616 624 repo.dirstate.copy(f2, fd)
617 625 else:
618 626 repo.dirstate.normal(fd)
619 627 if f:
620 628 repo.dirstate.drop(f)
621 629
622 630 def update(repo, node, branchmerge, force, partial, ancestor=None,
623 631 mergeancestor=False):
624 632 """
625 633 Perform a merge between the working directory and the given node
626 634
627 635 node = the node to update to, or None if unspecified
628 636 branchmerge = whether to merge between branches
629 637 force = whether to force branch merging or file overwriting
630 638 partial = a function to filter file lists (dirstate not updated)
631 639 mergeancestor = whether it is merging with an ancestor. If true,
632 640 we should accept the incoming changes for any prompts that occur.
633 641 If false, merging with an ancestor (fast-forward) is only allowed
634 642 between different named branches. This flag is used by rebase extension
635 643 as a temporary fix and should be avoided in general.
636 644
637 645 The table below shows all the behaviors of the update command
638 646 given the -c and -C or no options, whether the working directory
639 647 is dirty, whether a revision is specified, and the relationship of
640 648 the parent rev to the target rev (linear, on the same named
641 649 branch, or on another named branch).
642 650
643 651 This logic is tested by test-update-branches.t.
644 652
645 653 -c -C dirty rev | linear same cross
646 654 n n n n | ok (1) x
647 655 n n n y | ok ok ok
648 656 n n y * | merge (2) (2)
649 657 n y * * | --- discard ---
650 658 y n y * | --- (3) ---
651 659 y n n * | --- ok ---
652 660 y y * * | --- (4) ---
653 661
654 662 x = can't happen
655 663 * = don't-care
656 664 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
657 665 2 = abort: crosses branches (use 'hg merge' to merge or
658 666 use 'hg update -C' to discard changes)
659 667 3 = abort: uncommitted local changes
660 668 4 = incompatible options (checked in commands.py)
661 669
662 670 Return the same tuple as applyupdates().
663 671 """
664 672
665 673 onode = node
666 674 wlock = repo.wlock()
667 675 try:
668 676 wc = repo[None]
669 677 if node is None:
670 678 # tip of current branch
671 679 try:
672 680 node = repo.branchtip(wc.branch())
673 681 except error.RepoLookupError:
674 682 if wc.branch() == "default": # no default branch!
675 683 node = repo.lookup("tip") # update to tip
676 684 else:
677 685 raise util.Abort(_("branch %s not found") % wc.branch())
678 686 overwrite = force and not branchmerge
679 687 pl = wc.parents()
680 688 p1, p2 = pl[0], repo[node]
681 689 if ancestor:
682 690 pa = repo[ancestor]
683 691 else:
684 692 pa = p1.ancestor(p2)
685 693
686 694 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
687 695
688 696 ### check phase
689 697 if not overwrite and len(pl) > 1:
690 698 raise util.Abort(_("outstanding uncommitted merges"))
691 699 if branchmerge:
692 700 if pa == p2:
693 701 raise util.Abort(_("merging with a working directory ancestor"
694 702 " has no effect"))
695 703 elif pa == p1:
696 704 if not mergeancestor and p1.branch() == p2.branch():
697 705 raise util.Abort(_("nothing to merge"),
698 706 hint=_("use 'hg update' "
699 707 "or check 'hg heads'"))
700 708 if not force and (wc.files() or wc.deleted()):
701 709 raise util.Abort(_("outstanding uncommitted changes"),
702 710 hint=_("use 'hg status' to list changes"))
703 711 for s in sorted(wc.substate):
704 712 if wc.sub(s).dirty():
705 713 raise util.Abort(_("outstanding uncommitted changes in "
706 714 "subrepository '%s'") % s)
707 715
708 716 elif not overwrite:
709 717 if pa not in (p1, p2): # nolinear
710 718 dirty = wc.dirty(missing=True)
711 719 if dirty or onode is None:
712 720 # Branching is a bit strange to ensure we do the minimal
713 721 # amount of call to obsolete.background.
714 722 foreground = obsolete.foreground(repo, [p1.node()])
715 723 # note: the <node> variable contains a random identifier
716 724 if repo[node].node() in foreground:
717 725 pa = p1 # allow updating to successors
718 726 elif dirty:
719 727 msg = _("crosses branches (merge branches or use"
720 728 " --clean to discard changes)")
721 729 raise util.Abort(msg)
722 730 else: # node is none
723 731 msg = _("crosses branches (merge branches or update"
724 732 " --check to force update)")
725 733 raise util.Abort(msg)
726 734 else:
727 735 # Allow jumping branches if clean and specific rev given
728 736 pa = p1
729 737
730 738 ### calculate phase
731 739 actions = calculateupdates(repo, wc, p2, pa,
732 740 branchmerge, force, partial, mergeancestor)
733 741
734 742 ### apply phase
735 743 if not branchmerge: # just jump to the new rev
736 744 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
737 745 if not partial:
738 746 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
739 747
740 748 stats = applyupdates(repo, actions, wc, p2, pa, overwrite)
741 749
742 750 if not partial:
743 751 repo.setparents(fp1, fp2)
744 752 recordupdates(repo, actions, branchmerge)
745 753 if not branchmerge:
746 754 repo.dirstate.setbranch(p2.branch())
747 755 finally:
748 756 wlock.release()
749 757
750 758 if not partial:
751 759 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
752 760 return stats
@@ -1,225 +1,268 b''
1 1 run only on case-insensitive filesystems
2 2
3 3 $ "$TESTDIR/hghave" icasefs || exit 80
4 4
5 5 ################################
6 6 test for branch merging
7 7 ################################
8 8
9 9 test for rename awareness of case-folding collision check:
10 10
11 11 (1) colliding file is one renamed from collided file:
12 12 this is also case for issue3370.
13 13
14 14 $ hg init branch_merge_renaming
15 15 $ cd branch_merge_renaming
16 16
17 17 $ echo a > a
18 18 $ hg add a
19 19 $ hg commit -m '#0'
20 $ hg tag -l A
20 21 $ hg rename a tmp
21 22 $ hg rename tmp A
22 23 $ hg commit -m '#1'
24 $ hg tag -l B
23 25 $ hg update -q 0
24 26 $ touch x
25 27 $ hg add x
26 28 $ hg commit -m '#2'
27 29 created new head
30 $ hg tag -l C
28 31
29 32 $ hg merge -q
30 33 $ hg status -A
31 34 M A
32 35 R a
33 36 C x
34 37
35 38 $ hg update -q --clean 1
36 39 $ hg merge -q
37 40 $ hg status -A
38 41 M x
39 42 C A
43 $ hg commit -m '(D)'
44 $ hg tag -l D
45
46 additional test for issue3452:
47
48 | this assumes the history below.
49 |
50 | (A) -- (C) -- (E) -------
51 | \ \ \
52 | \ \ \
53 | (B) -- (D) -- (F) -- (G)
54 |
55 | A: add file 'a'
56 | B: rename from 'a' to 'A'
57 | C: add 'x' (or operation other than modification of 'a')
58 | D: merge C into B
59 | E: modify 'a'
60 | F: modify 'A'
61 | G: merge E into F
62 |
63 | issue3452 occurs when (B) is recorded before (C)
64
65 $ hg update -q --clean C
66 $ echo "modify 'a' at (E)" > a
67 $ hg commit -m '(E)'
68 created new head
69 $ hg tag -l E
70
71 $ hg update -q --clean D
72 $ echo "modify 'A' at (F)" > A
73 $ hg commit -m '(F)'
74 $ hg tag -l F
75
76 $ hg merge -q --tool internal:other E
77 $ hg status -A
78 M A
79 a
80 C x
81 $ cat A
82 modify 'a' at (E)
40 83
41 84 $ cd ..
42 85
43 86 (2) colliding file is not related to collided file
44 87
45 88 $ hg init branch_merge_collding
46 89 $ cd branch_merge_collding
47 90
48 91 $ echo a > a
49 92 $ hg add a
50 93 $ hg commit -m '#0'
51 94 $ hg remove a
52 95 $ hg commit -m '#1'
53 96 $ echo A > A
54 97 $ hg add A
55 98 $ hg commit -m '#2'
56 99 $ hg update --clean 0
57 100 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
58 101 $ echo x > x
59 102 $ hg add x
60 103 $ hg commit -m '#3'
61 104 created new head
62 105 $ echo 'modified at #4' > a
63 106 $ hg commit -m '#4'
64 107
65 108 $ hg merge
66 abort: case-folding collision between A and a
109 abort: case-folding collision between a and A
67 110 [255]
68 111 $ hg parents --template '{rev}\n'
69 112 4
70 113 $ hg status -A
71 114 C a
72 115 C x
73 116 $ cat a
74 117 modified at #4
75 118
76 119 $ hg update --clean 2
77 120 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
78 121 $ hg merge
79 122 abort: case-folding collision between a and A
80 123 [255]
81 124 $ hg parents --template '{rev}\n'
82 125 2
83 126 $ hg status -A
84 127 C A
85 128 $ cat A
86 129 A
87 130
88 131 test for deletion awareness of case-folding collision check (issue3648):
89 132 revision '#3' doesn't change 'a', so 'a' should be recognized as
90 133 safely removed in merging between #2 and #3.
91 134
92 135 $ hg update --clean 3
93 136 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
94 137 $ hg merge 2
95 138 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
96 139 (branch merge, don't forget to commit)
97 140 $ hg status -A
98 141 M A
99 142 R a
100 143 C x
101 144
102 145 $ hg update --clean 2
103 146 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
104 147 $ hg merge 3
105 148 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
106 149 (branch merge, don't forget to commit)
107 150 $ hg status -A
108 151 M x
109 152 C A
110 153
111 154 $ cd ..
112 155
113 156
114 157 ################################
115 158 test for linear updates
116 159 ################################
117 160
118 161 test for rename awareness of case-folding collision check:
119 162
120 163 (1) colliding file is one renamed from collided file
121 164
122 165 $ hg init linearupdate_renameaware_1
123 166 $ cd linearupdate_renameaware_1
124 167
125 168 $ echo a > a
126 169 $ hg add a
127 170 $ hg commit -m '#0'
128 171 $ hg rename a tmp
129 172 $ hg rename tmp A
130 173 $ hg commit -m '#1'
131 174
132 175 $ hg update 0
133 176 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
134 177
135 178 $ echo 'this is added line' >> a
136 179 $ hg update 1
137 180 merging a and A to A
138 181 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
139 182 $ hg status -A
140 183 M A
141 184 $ cat A
142 185 a
143 186 this is added line
144 187
145 188 $ cd ..
146 189
147 190 (2) colliding file is not related to collided file
148 191
149 192 $ hg init linearupdate_renameaware_2
150 193 $ cd linearupdate_renameaware_2
151 194
152 195 $ echo a > a
153 196 $ hg add a
154 197 $ hg commit -m '#0'
155 198 $ hg remove a
156 199 $ hg commit -m '#1'
157 200 $ echo A > A
158 201 $ hg add A
159 202 $ hg commit -m '#2'
160 203
161 204 $ hg update 0
162 205 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
163 206 $ hg parents --template '{rev}\n'
164 207 0
165 208 $ hg status -A
166 209 C a
167 210 $ cat A
168 211 a
169 212 $ hg up -qC 2
170 213
171 214 $ hg update --check 0
172 215 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
173 216 $ hg parents --template '{rev}\n'
174 217 0
175 218 $ hg status -A
176 219 C a
177 220 $ cat a
178 221 a
179 222
180 223 $ hg update --clean 2
181 224 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
182 225 $ hg parents --template '{rev}\n'
183 226 2
184 227 $ hg status -A
185 228 C A
186 229 $ cat A
187 230 A
188 231
189 232 $ cd ..
190 233
191 234 (3) colliding file is not related to collided file: added in working dir
192 235
193 236 $ hg init linearupdate_renameaware_3
194 237 $ cd linearupdate_renameaware_3
195 238
196 239 $ echo a > a
197 240 $ hg add a
198 241 $ hg commit -m '#0'
199 242 $ hg rename a b
200 243 $ hg commit -m '#1'
201 244 $ hg update 0
202 245 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
203 246
204 247 $ echo B > B
205 248 $ hg add B
206 249 $ hg status
207 250 A B
208 251 $ hg update
209 252 abort: case-folding collision between b and B
210 253 [255]
211 254
212 255 $ hg update --check
213 256 abort: uncommitted local changes
214 257 [255]
215 258
216 259 $ hg update --clean
217 260 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
218 261 $ hg parents --template '{rev}\n'
219 262 1
220 263 $ hg status -A
221 264 C b
222 265 $ cat b
223 266 a
224 267
225 268 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now