##// END OF EJS Templates
icasefs: make case-folding collision detection as rename aware (issue3370)...
FUJIWARA Katsunori -
r16478:cbf2ea2f stable
parent child Browse files
Show More
@@ -1,595 +1,607 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 import scmutil, util, filemerge, copies, subrepo
11 11 import errno, os, shutil
12 12
13 13 class mergestate(object):
14 14 '''track 3-way merge state of individual files'''
15 15 def __init__(self, repo):
16 16 self._repo = repo
17 17 self._dirty = False
18 18 self._read()
19 19 def reset(self, node=None):
20 20 self._state = {}
21 21 if node:
22 22 self._local = node
23 23 shutil.rmtree(self._repo.join("merge"), True)
24 24 self._dirty = False
25 25 def _read(self):
26 26 self._state = {}
27 27 try:
28 28 f = self._repo.opener("merge/state")
29 29 for i, l in enumerate(f):
30 30 if i == 0:
31 31 self._local = bin(l[:-1])
32 32 else:
33 33 bits = l[:-1].split("\0")
34 34 self._state[bits[0]] = bits[1:]
35 35 f.close()
36 36 except IOError, err:
37 37 if err.errno != errno.ENOENT:
38 38 raise
39 39 self._dirty = False
40 40 def commit(self):
41 41 if self._dirty:
42 42 f = self._repo.opener("merge/state", "w")
43 43 f.write(hex(self._local) + "\n")
44 44 for d, v in self._state.iteritems():
45 45 f.write("\0".join([d] + v) + "\n")
46 46 f.close()
47 47 self._dirty = False
48 48 def add(self, fcl, fco, fca, fd, flags):
49 49 hash = util.sha1(fcl.path()).hexdigest()
50 50 self._repo.opener.write("merge/" + hash, fcl.data())
51 51 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
52 52 hex(fca.filenode()), fco.path(), flags]
53 53 self._dirty = True
54 54 def __contains__(self, dfile):
55 55 return dfile in self._state
56 56 def __getitem__(self, dfile):
57 57 return self._state[dfile][0]
58 58 def __iter__(self):
59 59 l = self._state.keys()
60 60 l.sort()
61 61 for f in l:
62 62 yield f
63 63 def mark(self, dfile, state):
64 64 self._state[dfile][0] = state
65 65 self._dirty = True
66 66 def resolve(self, dfile, wctx, octx):
67 67 if self[dfile] == 'r':
68 68 return 0
69 69 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
70 70 f = self._repo.opener("merge/" + hash)
71 71 self._repo.wwrite(dfile, f.read(), flags)
72 72 f.close()
73 73 fcd = wctx[dfile]
74 74 fco = octx[ofile]
75 75 fca = self._repo.filectx(afile, fileid=anode)
76 76 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
77 77 if r is None:
78 78 # no real conflict
79 79 del self._state[dfile]
80 80 elif not r:
81 81 self.mark(dfile, 'r')
82 82 return r
83 83
84 84 def _checkunknownfile(repo, wctx, mctx, f):
85 85 return (not repo.dirstate._ignore(f)
86 86 and os.path.exists(repo.wjoin(f))
87 87 and repo.dirstate.normalize(f) not in repo.dirstate
88 88 and mctx[f].cmp(wctx[f]))
89 89
90 90 def _checkunknown(repo, wctx, mctx):
91 91 "check for collisions between unknown files and files in mctx"
92 92
93 93 error = False
94 94 for f in mctx:
95 95 if f not in wctx and _checkunknownfile(repo, wctx, mctx, f):
96 96 error = True
97 97 wctx._repo.ui.warn(_("%s: untracked file differs\n") % f)
98 98 if error:
99 99 raise util.Abort(_("untracked files in working directory differ "
100 100 "from files in requested revision"))
101 101
102 102 def _checkcollision(mctx, wctx):
103 103 "check for case folding collisions in the destination context"
104 104 folded = {}
105 105 for fn in mctx:
106 106 fold = util.normcase(fn)
107 107 if fold in folded:
108 108 raise util.Abort(_("case-folding collision between %s and %s")
109 109 % (fn, folded[fold]))
110 110 folded[fold] = fn
111 111
112 112 if wctx:
113 # class to delay looking up copy mapping
114 class pathcopies(object):
115 @util.propertycache
116 def map(self):
117 # {dst@mctx: src@wctx} copy mapping
118 return copies.pathcopies(wctx, mctx)
119 pc = pathcopies()
120
113 121 for fn in wctx:
114 122 fold = util.normcase(fn)
115 123 mfn = folded.get(fold, None)
116 if mfn and (mfn != fn):
124 if mfn and mfn != fn and pc.map.get(mfn) != fn:
117 125 raise util.Abort(_("case-folding collision between %s and %s")
118 126 % (mfn, fn))
119 127
120 128 def _forgetremoved(wctx, mctx, branchmerge):
121 129 """
122 130 Forget removed files
123 131
124 132 If we're jumping between revisions (as opposed to merging), and if
125 133 neither the working directory nor the target rev has the file,
126 134 then we need to remove it from the dirstate, to prevent the
127 135 dirstate from listing the file when it is no longer in the
128 136 manifest.
129 137
130 138 If we're merging, and the other revision has removed a file
131 139 that is not present in the working directory, we need to mark it
132 140 as removed.
133 141 """
134 142
135 143 action = []
136 144 state = branchmerge and 'r' or 'f'
137 145 for f in wctx.deleted():
138 146 if f not in mctx:
139 147 action.append((f, state))
140 148
141 149 if not branchmerge:
142 150 for f in wctx.removed():
143 151 if f not in mctx:
144 152 action.append((f, "f"))
145 153
146 154 return action
147 155
148 156 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
149 157 """
150 158 Merge p1 and p2 with ancestor pa and generate merge action list
151 159
152 160 overwrite = whether we clobber working files
153 161 partial = function to filter file lists
154 162 """
155 163
156 164 def fmerge(f, f2, fa):
157 165 """merge flags"""
158 166 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
159 167 if m == n: # flags agree
160 168 return m # unchanged
161 169 if m and n and not a: # flags set, don't agree, differ from parent
162 170 r = repo.ui.promptchoice(
163 171 _(" conflicting flags for %s\n"
164 172 "(n)one, e(x)ec or sym(l)ink?") % f,
165 173 (_("&None"), _("E&xec"), _("Sym&link")), 0)
166 174 if r == 1:
167 175 return "x" # Exec
168 176 if r == 2:
169 177 return "l" # Symlink
170 178 return ""
171 179 if m and m != a: # changed from a to m
172 180 return m
173 181 if n and n != a: # changed from a to n
174 182 if (n == 'l' or a == 'l') and m1.get(f) != ma.get(f):
175 183 # can't automatically merge symlink flag when there
176 184 # are file-level conflicts here, let filemerge take
177 185 # care of it
178 186 return m
179 187 return n
180 188 return '' # flag was cleared
181 189
182 190 def act(msg, m, f, *args):
183 191 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
184 192 action.append((f, m) + args)
185 193
186 194 action, copy = [], {}
187 195
188 196 if overwrite:
189 197 pa = p1
190 198 elif pa == p2: # backwards
191 199 pa = p1.p1()
192 200 elif pa and repo.ui.configbool("merge", "followcopies", True):
193 201 copy, diverge = copies.mergecopies(repo, p1, p2, pa)
194 202 for of, fl in diverge.iteritems():
195 203 act("divergent renames", "dr", of, fl)
196 204
197 205 repo.ui.note(_("resolving manifests\n"))
198 206 repo.ui.debug(" overwrite: %s, partial: %s\n"
199 207 % (bool(overwrite), bool(partial)))
200 208 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, p1, p2))
201 209
202 210 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
203 211 copied = set(copy.values())
204 212
205 213 if '.hgsubstate' in m1:
206 214 # check whether sub state is modified
207 215 for s in p1.substate:
208 216 if p1.sub(s).dirty():
209 217 m1['.hgsubstate'] += "+"
210 218 break
211 219
212 220 # Compare manifests
213 221 for f, n in m1.iteritems():
214 222 if partial and not partial(f):
215 223 continue
216 224 if f in m2:
217 225 rflags = fmerge(f, f, f)
218 226 a = ma.get(f, nullid)
219 227 if n == m2[f] or m2[f] == a: # same or local newer
220 228 # is file locally modified or flags need changing?
221 229 # dirstate flags may need to be made current
222 230 if m1.flags(f) != rflags or n[20:]:
223 231 act("update permissions", "e", f, rflags)
224 232 elif n == a: # remote newer
225 233 act("remote is newer", "g", f, rflags)
226 234 else: # both changed
227 235 act("versions differ", "m", f, f, f, rflags, False)
228 236 elif f in copied: # files we'll deal with on m2 side
229 237 pass
230 238 elif f in copy:
231 239 f2 = copy[f]
232 240 if f2 not in m2: # directory rename
233 241 act("remote renamed directory to " + f2, "d",
234 242 f, None, f2, m1.flags(f))
235 243 else: # case 2 A,B/B/B or case 4,21 A/B/B
236 244 act("local copied/moved to " + f2, "m",
237 245 f, f2, f, fmerge(f, f2, f2), False)
238 246 elif f in ma: # clean, a different, no remote
239 247 if n != ma[f]:
240 248 if repo.ui.promptchoice(
241 249 _(" local changed %s which remote deleted\n"
242 250 "use (c)hanged version or (d)elete?") % f,
243 251 (_("&Changed"), _("&Delete")), 0):
244 252 act("prompt delete", "r", f)
245 253 else:
246 254 act("prompt keep", "a", f)
247 255 elif n[20:] == "a": # added, no remote
248 256 act("remote deleted", "f", f)
249 257 else:
250 258 act("other deleted", "r", f)
251 259
252 260 for f, n in m2.iteritems():
253 261 if partial and not partial(f):
254 262 continue
255 263 if f in m1 or f in copied: # files already visited
256 264 continue
257 265 if f in copy:
258 266 f2 = copy[f]
259 267 if f2 not in m1: # directory rename
260 268 act("local renamed directory to " + f2, "d",
261 269 None, f, f2, m2.flags(f))
262 270 elif f2 in m2: # rename case 1, A/A,B/A
263 271 act("remote copied to " + f, "m",
264 272 f2, f, f, fmerge(f2, f, f2), False)
265 273 else: # case 3,20 A/B/A
266 274 act("remote moved to " + f, "m",
267 275 f2, f, f, fmerge(f2, f, f2), True)
268 276 elif f not in ma:
269 277 if (not overwrite
270 278 and _checkunknownfile(repo, p1, p2, f)):
271 279 rflags = fmerge(f, f, f)
272 280 act("remote differs from untracked local",
273 281 "m", f, f, f, rflags, False)
274 282 else:
275 283 act("remote created", "g", f, m2.flags(f))
276 284 elif n != ma[f]:
277 285 if repo.ui.promptchoice(
278 286 _("remote changed %s which local deleted\n"
279 287 "use (c)hanged version or leave (d)eleted?") % f,
280 288 (_("&Changed"), _("&Deleted")), 0) == 0:
281 289 act("prompt recreating", "g", f, m2.flags(f))
282 290
283 291 return action
284 292
285 293 def actionkey(a):
286 294 return a[1] == 'r' and -1 or 0, a
287 295
288 296 def applyupdates(repo, action, wctx, mctx, actx, overwrite):
289 297 """apply the merge action list to the working directory
290 298
291 299 wctx is the working copy context
292 300 mctx is the context to be merged into the working copy
293 301 actx is the context of the common ancestor
294 302
295 303 Return a tuple of counts (updated, merged, removed, unresolved) that
296 304 describes how many files were affected by the update.
297 305 """
298 306
299 307 updated, merged, removed, unresolved = 0, 0, 0, 0
300 308 ms = mergestate(repo)
301 309 ms.reset(wctx.p1().node())
302 310 moves = []
303 311 action.sort(key=actionkey)
304 312
305 313 # prescan for merges
306 314 for a in action:
307 315 f, m = a[:2]
308 316 if m == 'm': # merge
309 317 f2, fd, flags, move = a[2:]
310 318 if f == '.hgsubstate': # merged internally
311 319 continue
312 320 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
313 321 fcl = wctx[f]
314 322 fco = mctx[f2]
315 323 if mctx == actx: # backwards, use working dir parent as ancestor
316 324 if fcl.parents():
317 325 fca = fcl.p1()
318 326 else:
319 327 fca = repo.filectx(f, fileid=nullrev)
320 328 else:
321 329 fca = fcl.ancestor(fco, actx)
322 330 if not fca:
323 331 fca = repo.filectx(f, fileid=nullrev)
324 332 ms.add(fcl, fco, fca, fd, flags)
325 333 if f != fd and move:
326 334 moves.append(f)
327 335
328 336 audit = scmutil.pathauditor(repo.root)
329 337
330 338 # remove renamed files after safely stored
331 339 for f in moves:
332 340 if os.path.lexists(repo.wjoin(f)):
333 341 repo.ui.debug("removing %s\n" % f)
334 342 audit(f)
335 343 os.unlink(repo.wjoin(f))
336 344
337 345 numupdates = len(action)
338 346 for i, a in enumerate(action):
339 347 f, m = a[:2]
340 348 repo.ui.progress(_('updating'), i + 1, item=f, total=numupdates,
341 349 unit=_('files'))
342 350 if f and f[0] == "/":
343 351 continue
344 352 if m == "r": # remove
345 353 repo.ui.note(_("removing %s\n") % f)
346 354 audit(f)
347 355 if f == '.hgsubstate': # subrepo states need updating
348 356 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
349 357 try:
350 358 util.unlinkpath(repo.wjoin(f))
351 359 except OSError, inst:
352 360 if inst.errno != errno.ENOENT:
353 361 repo.ui.warn(_("update failed to remove %s: %s!\n") %
354 362 (f, inst.strerror))
355 363 removed += 1
356 364 elif m == "m": # merge
357 365 if f == '.hgsubstate': # subrepo states need updating
358 366 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx), overwrite)
359 367 continue
360 368 f2, fd, flags, move = a[2:]
361 369 repo.wopener.audit(fd)
362 370 r = ms.resolve(fd, wctx, mctx)
363 371 if r is not None and r > 0:
364 372 unresolved += 1
365 373 else:
366 374 if r is None:
367 375 updated += 1
368 376 else:
369 377 merged += 1
370 378 if (move and repo.dirstate.normalize(fd) != f
371 379 and os.path.lexists(repo.wjoin(f))):
372 380 repo.ui.debug("removing %s\n" % f)
373 381 audit(f)
374 382 os.unlink(repo.wjoin(f))
375 383 elif m == "g": # get
376 384 flags = a[2]
377 385 repo.ui.note(_("getting %s\n") % f)
378 386 t = mctx.filectx(f).data()
379 387 repo.wwrite(f, t, flags)
380 388 t = None
381 389 updated += 1
382 390 if f == '.hgsubstate': # subrepo states need updating
383 391 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
384 392 elif m == "d": # directory rename
385 393 f2, fd, flags = a[2:]
386 394 if f:
387 395 repo.ui.note(_("moving %s to %s\n") % (f, fd))
388 396 audit(f)
389 397 t = wctx.filectx(f).data()
390 398 repo.wwrite(fd, t, flags)
391 399 util.unlinkpath(repo.wjoin(f))
392 400 if f2:
393 401 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
394 402 t = mctx.filectx(f2).data()
395 403 repo.wwrite(fd, t, flags)
396 404 updated += 1
397 405 elif m == "dr": # divergent renames
398 406 fl = a[2]
399 407 repo.ui.warn(_("note: possible conflict - %s was renamed "
400 408 "multiple times to:\n") % f)
401 409 for nf in fl:
402 410 repo.ui.warn(" %s\n" % nf)
403 411 elif m == "e": # exec
404 412 flags = a[2]
405 413 repo.wopener.audit(f)
406 414 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
407 415 ms.commit()
408 416 repo.ui.progress(_('updating'), None, total=numupdates, unit=_('files'))
409 417
410 418 return updated, merged, removed, unresolved
411 419
412 420 def recordupdates(repo, action, branchmerge):
413 421 "record merge actions to the dirstate"
414 422
415 423 for a in action:
416 424 f, m = a[:2]
417 425 if m == "r": # remove
418 426 if branchmerge:
419 427 repo.dirstate.remove(f)
420 428 else:
421 429 repo.dirstate.drop(f)
422 430 elif m == "a": # re-add
423 431 if not branchmerge:
424 432 repo.dirstate.add(f)
425 433 elif m == "f": # forget
426 434 repo.dirstate.drop(f)
427 435 elif m == "e": # exec change
428 436 repo.dirstate.normallookup(f)
429 437 elif m == "g": # get
430 438 if branchmerge:
431 439 repo.dirstate.otherparent(f)
432 440 else:
433 441 repo.dirstate.normal(f)
434 442 elif m == "m": # merge
435 443 f2, fd, flag, move = a[2:]
436 444 if branchmerge:
437 445 # We've done a branch merge, mark this file as merged
438 446 # so that we properly record the merger later
439 447 repo.dirstate.merge(fd)
440 448 if f != f2: # copy/rename
441 449 if move:
442 450 repo.dirstate.remove(f)
443 451 if f != fd:
444 452 repo.dirstate.copy(f, fd)
445 453 else:
446 454 repo.dirstate.copy(f2, fd)
447 455 else:
448 456 # We've update-merged a locally modified file, so
449 457 # we set the dirstate to emulate a normal checkout
450 458 # of that file some time in the past. Thus our
451 459 # merge will appear as a normal local file
452 460 # modification.
453 461 if f2 == fd: # file not locally copied/moved
454 462 repo.dirstate.normallookup(fd)
455 463 if move:
456 464 repo.dirstate.drop(f)
457 465 elif m == "d": # directory rename
458 466 f2, fd, flag = a[2:]
459 467 if not f2 and f not in repo.dirstate:
460 468 # untracked file moved
461 469 continue
462 470 if branchmerge:
463 471 repo.dirstate.add(fd)
464 472 if f:
465 473 repo.dirstate.remove(f)
466 474 repo.dirstate.copy(f, fd)
467 475 if f2:
468 476 repo.dirstate.copy(f2, fd)
469 477 else:
470 478 repo.dirstate.normal(fd)
471 479 if f:
472 480 repo.dirstate.drop(f)
473 481
474 482 def update(repo, node, branchmerge, force, partial, ancestor=None):
475 483 """
476 484 Perform a merge between the working directory and the given node
477 485
478 486 node = the node to update to, or None if unspecified
479 487 branchmerge = whether to merge between branches
480 488 force = whether to force branch merging or file overwriting
481 489 partial = a function to filter file lists (dirstate not updated)
482 490
483 491 The table below shows all the behaviors of the update command
484 492 given the -c and -C or no options, whether the working directory
485 493 is dirty, whether a revision is specified, and the relationship of
486 494 the parent rev to the target rev (linear, on the same named
487 495 branch, or on another named branch).
488 496
489 497 This logic is tested by test-update-branches.t.
490 498
491 499 -c -C dirty rev | linear same cross
492 500 n n n n | ok (1) x
493 501 n n n y | ok ok ok
494 502 n n y * | merge (2) (2)
495 503 n y * * | --- discard ---
496 504 y n y * | --- (3) ---
497 505 y n n * | --- ok ---
498 506 y y * * | --- (4) ---
499 507
500 508 x = can't happen
501 509 * = don't-care
502 510 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
503 511 2 = abort: crosses branches (use 'hg merge' to merge or
504 512 use 'hg update -C' to discard changes)
505 513 3 = abort: uncommitted local changes
506 514 4 = incompatible options (checked in commands.py)
507 515
508 516 Return the same tuple as applyupdates().
509 517 """
510 518
511 519 onode = node
512 520 wlock = repo.wlock()
513 521 try:
514 522 wc = repo[None]
515 523 if node is None:
516 524 # tip of current branch
517 525 try:
518 526 node = repo.branchtags()[wc.branch()]
519 527 except KeyError:
520 528 if wc.branch() == "default": # no default branch!
521 529 node = repo.lookup("tip") # update to tip
522 530 else:
523 531 raise util.Abort(_("branch %s not found") % wc.branch())
524 532 overwrite = force and not branchmerge
525 533 pl = wc.parents()
526 534 p1, p2 = pl[0], repo[node]
527 535 if ancestor:
528 536 pa = repo[ancestor]
529 537 else:
530 538 pa = p1.ancestor(p2)
531 539
532 540 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
533 541
534 542 ### check phase
535 543 if not overwrite and len(pl) > 1:
536 544 raise util.Abort(_("outstanding uncommitted merges"))
537 545 if branchmerge:
538 546 if pa == p2:
539 547 raise util.Abort(_("merging with a working directory ancestor"
540 548 " has no effect"))
541 549 elif pa == p1:
542 550 if p1.branch() == p2.branch():
543 551 raise util.Abort(_("nothing to merge"),
544 552 hint=_("use 'hg update' "
545 553 "or check 'hg heads'"))
546 554 if not force and (wc.files() or wc.deleted()):
547 555 raise util.Abort(_("outstanding uncommitted changes"),
548 556 hint=_("use 'hg status' to list changes"))
549 557 for s in wc.substate:
550 558 if wc.sub(s).dirty():
551 559 raise util.Abort(_("outstanding uncommitted changes in "
552 560 "subrepository '%s'") % s)
553 561
554 562 elif not overwrite:
555 563 if pa == p1 or pa == p2: # linear
556 564 pass # all good
557 565 elif wc.dirty(missing=True):
558 566 raise util.Abort(_("crosses branches (merge branches or use"
559 567 " --clean to discard changes)"))
560 568 elif onode is None:
561 569 raise util.Abort(_("crosses branches (merge branches or update"
562 570 " --check to force update)"))
563 571 else:
564 572 # Allow jumping branches if clean and specific rev given
565 573 pa = p1
566 574
567 575 ### calculate phase
568 576 action = []
569 577 folding = not util.checkcase(repo.path)
570 578 if folding:
571 _checkcollision(p2, branchmerge and p1)
579 # collision check is not needed for clean update
580 if not branchmerge and force:
581 _checkcollision(p2, None)
582 else:
583 _checkcollision(p2, wc)
572 584 if not force:
573 585 _checkunknown(repo, wc, p2)
574 586 action += _forgetremoved(wc, p2, branchmerge)
575 587 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
576 588
577 589 ### apply phase
578 590 if not branchmerge: # just jump to the new rev
579 591 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
580 592 if not partial:
581 593 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
582 594
583 595 stats = applyupdates(repo, action, wc, p2, pa, overwrite)
584 596
585 597 if not partial:
586 598 repo.dirstate.setparents(fp1, fp2)
587 599 recordupdates(repo, action, branchmerge)
588 600 if not branchmerge:
589 601 repo.dirstate.setbranch(p2.branch())
590 602 finally:
591 603 wlock.release()
592 604
593 605 if not partial:
594 606 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
595 607 return stats
@@ -1,109 +1,209 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 $ hg init repo1
10 $ cd repo1
11
12 create base revision
9 test for rename awareness of case-folding collision check:
13 10
14 $ echo base > base.txt
15 $ hg add base.txt
16 $ hg commit -m 'base'
11 (1) colliding file is one renamed from collided file:
12 this is also case for issue3370.
17 13
18 add same file in different case on both heads
14 $ hg init merge_renameaware_1
15 $ cd merge_renameaware_1
19 16
20 $ echo a > a.txt
21 $ hg add a.txt
22 $ hg commit -m 'add a.txt'
23
17 $ echo a > a
18 $ hg add a
19 $ hg commit -m '#0'
20 $ hg rename a tmp
21 $ hg rename tmp A
22 $ hg commit -m '#1'
24 23 $ hg update 0
25 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
26
27 $ echo A > A.TXT
28 $ hg add A.TXT
29 $ hg commit -m 'add A.TXT'
24 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
25 $ echo 'modified at #2' > a
26 $ hg commit -m '#2'
30 27 created new head
31 28
32 merge another, and fail with case-folding collision
29 $ hg merge
30 merging a and A to A
31 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
32 (branch merge, don't forget to commit)
33 $ hg status -A
34 M A
35 a
36 R a
37 $ cat A
38 modified at #2
39
40 $ hg update --clean 1
41 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
42 $ hg merge
43 merging A and a to A
44 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
45 (branch merge, don't forget to commit)
46 $ hg status -A
47 M A
48 a
49 $ cat A
50 modified at #2
51
52 $ cd ..
53
54 (2) colliding file is not related to collided file
55
56 $ hg init merge_renameaware_2
57 $ cd merge_renameaware_2
58
59 $ echo a > a
60 $ hg add a
61 $ hg commit -m '#0'
62 $ hg remove a
63 $ hg commit -m '#1'
64 $ echo A > A
65 $ hg add A
66 $ hg commit -m '#2'
67 $ hg update --clean 0
68 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
69 $ echo 'modified at #3' > a
70 $ hg commit -m '#3'
71 created new head
33 72
34 73 $ hg merge
35 abort: case-folding collision between a.txt and A.TXT
74 abort: case-folding collision between A and a
36 75 [255]
76 $ hg parents --template '{rev}\n'
77 3
78 $ hg status -A
79 C a
80 $ cat a
81 modified at #3
37 82
38 check clean-ness of working directory
39
40 $ hg status
83 $ hg update --clean 2
84 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
85 $ hg merge
86 abort: case-folding collision between a and A
87 [255]
41 88 $ hg parents --template '{rev}\n'
42 89 2
90 $ hg status -A
91 C A
92 $ cat A
93 A
94
43 95 $ cd ..
44 96
97
45 98 ################################
46 99 test for linear updates
47 100 ################################
48 101
49 $ hg init repo2
50 $ cd repo2
102 test for rename awareness of case-folding collision check:
51 103
52 create base revision (rev:0)
104 (1) colliding file is one renamed from collided file
105
106 $ hg init linearupdate_renameaware_1
107 $ cd linearupdate_renameaware_1
53 108
54 $ hg import --bypass --exact - <<EOF
55 > # HG changeset patch
56 > # User null
57 > # Date 1 0
58 > # Node ID e1bdf414b0ea9c831fd3a14e94a0a18e1410f98b
59 > # Parent 0000000000000000000000000000000000000000
60 > add a
61 >
62 > diff --git a/a b/a
63 > new file mode 100644
64 > --- /dev/null
65 > +++ b/a
66 > @@ -0,0 +1,3 @@
67 > +this is line 1
68 > +this is line 2
69 > +this is line 3
70 > EOF
71 applying patch from stdin
72
73 create rename revision (rev:1)
74
75 $ hg import --bypass --exact - <<EOF
76 > # HG changeset patch
77 > # User null
78 > # Date 1 0
79 > # Node ID 9dca9f19bb91851bc693544b598b0740629edfad
80 > # Parent e1bdf414b0ea9c831fd3a14e94a0a18e1410f98b
81 > rename a to A
82 >
83 > diff --git a/a b/A
84 > rename from a
85 > rename to A
86 > EOF
87 applying patch from stdin
88
89 update to base revision, and modify 'a'
109 $ echo a > a
110 $ hg add a
111 $ hg commit -m '#0'
112 $ hg rename a tmp
113 $ hg rename tmp A
114 $ hg commit -m '#1'
90 115
91 116 $ hg update 0
92 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
93 $ echo 'this is added line' >> a
117 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
94 118
95 update to current tip linearly
96
119 $ echo 'this is added line' >> a
97 120 $ hg update 1
98 121 merging a and A to A
99 122 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
100
101 check status and contents of file
102
103 123 $ hg status -A
104 124 M A
105 125 $ cat A
106 this is line 1
107 this is line 2
108 this is line 3
126 a
109 127 this is added line
128
129 $ cd ..
130
131 (2) colliding file is not related to collided file
132
133 $ hg init linearupdate_renameaware_2
134 $ cd linearupdate_renameaware_2
135
136 $ echo a > a
137 $ hg add a
138 $ hg commit -m '#0'
139 $ hg remove a
140 $ hg commit -m '#1'
141 $ echo A > A
142 $ hg add A
143 $ hg commit -m '#2'
144
145 $ hg update 0
146 abort: case-folding collision between a and A
147 [255]
148 $ hg parents --template '{rev}\n'
149 2
150 $ hg status -A
151 C A
152 $ cat A
153 A
154
155 $ hg update --check 0
156 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
157 $ hg parents --template '{rev}\n'
158 0
159 $ hg status -A
160 C a
161 $ cat a
162 a
163
164 $ hg update --clean 2
165 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
166 $ hg parents --template '{rev}\n'
167 2
168 $ hg status -A
169 C A
170 $ cat A
171 A
172
173 $ cd ..
174
175 (3) colliding file is not related to collided file: added in working dir
176
177 $ hg init linearupdate_renameaware_3
178 $ cd linearupdate_renameaware_3
179
180 $ echo a > a
181 $ hg add a
182 $ hg commit -m '#0'
183 $ hg rename a b
184 $ hg commit -m '#1'
185 $ hg update 0
186 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
187
188 $ echo B > B
189 $ hg add B
190 $ hg status
191 A B
192 $ hg update
193 abort: case-folding collision between b and B
194 [255]
195
196 $ hg update --check
197 abort: uncommitted local changes
198 [255]
199
200 $ hg update --clean
201 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
202 $ hg parents --template '{rev}\n'
203 1
204 $ hg status -A
205 C b
206 $ cat b
207 a
208
209 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now