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