Show More
@@ -0,0 +1,119 b'' | |||
|
1 | # reproduce issue2264, issue2516 | |
|
2 | ||
|
3 | create test repo | |
|
4 | $ cat <<EOF >> $HGRCPATH | |
|
5 | > [extensions] | |
|
6 | > transplant = | |
|
7 | > graphlog = | |
|
8 | > EOF | |
|
9 | $ hg init repo | |
|
10 | $ cd repo | |
|
11 | $ template="{rev} {desc|firstline} [{branch}]\n" | |
|
12 | ||
|
13 | # we need to start out with two changesets on the default branch | |
|
14 | # in order to avoid the cute little optimization where transplant | |
|
15 | # pulls rather than transplants | |
|
16 | add initial changesets | |
|
17 | $ echo feature1 > file1 | |
|
18 | $ hg ci -Am"feature 1" | |
|
19 | adding file1 | |
|
20 | $ echo feature2 >> file2 | |
|
21 | $ hg ci -Am"feature 2" | |
|
22 | adding file2 | |
|
23 | ||
|
24 | # The changes to 'bugfix' are enough to show the bug: in fact, with only | |
|
25 | # those changes, it's a very noisy crash ("RuntimeError: nothing | |
|
26 | # committed after transplant"). But if we modify a second file in the | |
|
27 | # transplanted changesets, the bug is much more subtle: transplant | |
|
28 | # silently drops the second change to 'bugfix' on the floor, and we only | |
|
29 | # see it when we run 'hg status' after transplanting. Subtle data loss | |
|
30 | # bugs are worse than crashes, so reproduce the subtle case here. | |
|
31 | commit bug fixes on bug fix branch | |
|
32 | $ hg branch fixes | |
|
33 | marked working directory as branch fixes | |
|
34 | $ echo fix1 > bugfix | |
|
35 | $ echo fix1 >> file1 | |
|
36 | $ hg ci -Am"fix 1" | |
|
37 | adding bugfix | |
|
38 | $ echo fix2 > bugfix | |
|
39 | $ echo fix2 >> file1 | |
|
40 | $ hg ci -Am"fix 2" | |
|
41 | $ hg glog --template="$template" | |
|
42 | @ 3 fix 2 [fixes] | |
|
43 | | | |
|
44 | o 2 fix 1 [fixes] | |
|
45 | | | |
|
46 | o 1 feature 2 [default] | |
|
47 | | | |
|
48 | o 0 feature 1 [default] | |
|
49 | ||
|
50 | transplant bug fixes onto release branch | |
|
51 | $ hg update 0 | |
|
52 | 1 files updated, 0 files merged, 2 files removed, 0 files unresolved | |
|
53 | $ hg branch release | |
|
54 | marked working directory as branch release | |
|
55 | $ hg transplant 2 3 | |
|
56 | applying [0-9a-f]{12} (re) | |
|
57 | [0-9a-f]{12} transplanted to [0-9a-f]{12} (re) | |
|
58 | applying [0-9a-f]{12} (re) | |
|
59 | [0-9a-f]{12} transplanted to [0-9a-f]{12} (re) | |
|
60 | $ hg glog --template="$template" | |
|
61 | @ 5 fix 2 [release] | |
|
62 | | | |
|
63 | o 4 fix 1 [release] | |
|
64 | | | |
|
65 | | o 3 fix 2 [fixes] | |
|
66 | | | | |
|
67 | | o 2 fix 1 [fixes] | |
|
68 | | | | |
|
69 | | o 1 feature 2 [default] | |
|
70 | |/ | |
|
71 | o 0 feature 1 [default] | |
|
72 | ||
|
73 | $ hg status | |
|
74 | $ hg status --rev 0:4 | |
|
75 | M file1 | |
|
76 | A bugfix | |
|
77 | $ hg status --rev 4:5 | |
|
78 | M bugfix | |
|
79 | M file1 | |
|
80 | ||
|
81 | now test that we fixed the bug for all scripts/extensions | |
|
82 | $ cat > $TESTTMP/committwice.py <<__EOF__ | |
|
83 | > from mercurial import ui, hg, match, node | |
|
84 | > | |
|
85 | > def replacebyte(fn, b): | |
|
86 | > f = open("file1", "rb+") | |
|
87 | > f.seek(0, 0) | |
|
88 | > f.write(b) | |
|
89 | > f.close() | |
|
90 | > | |
|
91 | > repo = hg.repository(ui.ui(), '.') | |
|
92 | > assert len(repo) == 6, \ | |
|
93 | > "initial: len(repo) == %d, expected 6" % len(repo) | |
|
94 | > try: | |
|
95 | > wlock = repo.wlock() | |
|
96 | > lock = repo.lock() | |
|
97 | > m = match.exact(repo.root, '', ['file1']) | |
|
98 | > replacebyte("file1", "x") | |
|
99 | > n = repo.commit(text="x", user="test", date=(0, 0), match=m) | |
|
100 | > print "commit 1: len(repo) == %d" % len(repo) | |
|
101 | > replacebyte("file1", "y") | |
|
102 | > n = repo.commit(text="y", user="test", date=(0, 0), match=m) | |
|
103 | > print "commit 2: len(repo) == %d" % len(repo) | |
|
104 | > finally: | |
|
105 | > lock.release() | |
|
106 | > wlock.release() | |
|
107 | > __EOF__ | |
|
108 | $ $PYTHON $TESTTMP/committwice.py | |
|
109 | commit 1: len(repo) == 7 | |
|
110 | commit 2: len(repo) == 8 | |
|
111 | ||
|
112 | Do a size-preserving modification outside of that process | |
|
113 | $ echo abcd > bugfix | |
|
114 | $ hg status | |
|
115 | M bugfix | |
|
116 | $ hg log --template "{rev} {desc} {files}\n" -r5: | |
|
117 | 5 fix 2 bugfix file1 | |
|
118 | 6 x file1 | |
|
119 | 7 y file1 |
@@ -49,6 +49,7 b' class dirstate(object):' | |||
|
49 | 49 | self._rootdir = os.path.join(root, '') |
|
50 | 50 | self._dirty = False |
|
51 | 51 | self._dirtypl = False |
|
52 | self._lastnormal = set() # files believed to be normal | |
|
52 | 53 | self._ui = ui |
|
53 | 54 | |
|
54 | 55 | @propertycache |
@@ -285,6 +286,12 b' class dirstate(object):' | |||
|
285 | 286 | if f in self._copymap: |
|
286 | 287 | del self._copymap[f] |
|
287 | 288 | |
|
289 | # Right now, this file is clean: but if some code in this | |
|
290 | # process modifies it without changing its size before the clock | |
|
291 | # ticks over to the next second, then it won't be clean anymore. | |
|
292 | # So make sure that status() will look harder at it. | |
|
293 | self._lastnormal.add(f) | |
|
294 | ||
|
288 | 295 | def normallookup(self, f): |
|
289 | 296 | '''Mark a file normal, but possibly dirty.''' |
|
290 | 297 | if self._pl[1] != nullid and f in self._map: |
@@ -308,6 +315,7 b' class dirstate(object):' | |||
|
308 | 315 | self._map[f] = ('n', 0, -1, -1) |
|
309 | 316 | if f in self._copymap: |
|
310 | 317 | del self._copymap[f] |
|
318 | self._lastnormal.discard(f) | |
|
311 | 319 | |
|
312 | 320 | def otherparent(self, f): |
|
313 | 321 | '''Mark as coming from the other parent, always dirty.''' |
@@ -319,6 +327,7 b' class dirstate(object):' | |||
|
319 | 327 | self._map[f] = ('n', 0, -2, -1) |
|
320 | 328 | if f in self._copymap: |
|
321 | 329 | del self._copymap[f] |
|
330 | self._lastnormal.discard(f) | |
|
322 | 331 | |
|
323 | 332 | def add(self, f): |
|
324 | 333 | '''Mark a file added.''' |
@@ -327,6 +336,7 b' class dirstate(object):' | |||
|
327 | 336 | self._map[f] = ('a', 0, -1, -1) |
|
328 | 337 | if f in self._copymap: |
|
329 | 338 | del self._copymap[f] |
|
339 | self._lastnormal.discard(f) | |
|
330 | 340 | |
|
331 | 341 | def remove(self, f): |
|
332 | 342 | '''Mark a file removed.''' |
@@ -343,6 +353,7 b' class dirstate(object):' | |||
|
343 | 353 | self._map[f] = ('r', 0, size, 0) |
|
344 | 354 | if size == 0 and f in self._copymap: |
|
345 | 355 | del self._copymap[f] |
|
356 | self._lastnormal.discard(f) | |
|
346 | 357 | |
|
347 | 358 | def merge(self, f): |
|
348 | 359 | '''Mark a file merged.''' |
@@ -352,6 +363,7 b' class dirstate(object):' | |||
|
352 | 363 | self._map[f] = ('m', s.st_mode, s.st_size, int(s.st_mtime)) |
|
353 | 364 | if f in self._copymap: |
|
354 | 365 | del self._copymap[f] |
|
366 | self._lastnormal.discard(f) | |
|
355 | 367 | |
|
356 | 368 | def forget(self, f): |
|
357 | 369 | '''Forget a file.''' |
@@ -361,6 +373,7 b' class dirstate(object):' | |||
|
361 | 373 | del self._map[f] |
|
362 | 374 | except KeyError: |
|
363 | 375 | self._ui.warn(_("not in dirstate: %s\n") % f) |
|
376 | self._lastnormal.discard(f) | |
|
364 | 377 | |
|
365 | 378 | def _normalize(self, path, knownpath): |
|
366 | 379 | norm_path = os.path.normcase(path) |
@@ -640,6 +653,7 b' class dirstate(object):' | |||
|
640 | 653 | radd = removed.append |
|
641 | 654 | dadd = deleted.append |
|
642 | 655 | cadd = clean.append |
|
656 | lastnormal = self._lastnormal.__contains__ | |
|
643 | 657 | |
|
644 | 658 | lnkkind = stat.S_IFLNK |
|
645 | 659 | |
@@ -672,6 +686,18 b' class dirstate(object):' | |||
|
672 | 686 | elif (time != int(st.st_mtime) |
|
673 | 687 | and (mode & lnkkind != lnkkind or self._checklink)): |
|
674 | 688 | ladd(fn) |
|
689 | elif lastnormal(fn): | |
|
690 | # If previously in this process we recorded that | |
|
691 | # this file is clean, think twice: intervening code | |
|
692 | # may have modified the file in the same second | |
|
693 | # without changing its size. So force caller to | |
|
694 | # check file contents. Because we're not updating | |
|
695 | # self._map, this only affects the current process. | |
|
696 | # That should be OK because this mainly affects | |
|
697 | # multiple commits in the same process, and each | |
|
698 | # commit by definition makes the committed files | |
|
699 | # clean. | |
|
700 | ladd(fn) | |
|
675 | 701 | elif listclean: |
|
676 | 702 | cadd(fn) |
|
677 | 703 | elif state == 'm': |
General Comments 0
You need to be logged in to leave comments.
Login now