##// END OF EJS Templates
dirstate: avoid a race with multiple commits in the same process...
Greg Ward -
r13704:a464763e default
parent child Browse files
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 self._rootdir = os.path.join(root, '')
49 self._rootdir = os.path.join(root, '')
50 self._dirty = False
50 self._dirty = False
51 self._dirtypl = False
51 self._dirtypl = False
52 self._lastnormal = set() # files believed to be normal
52 self._ui = ui
53 self._ui = ui
53
54
54 @propertycache
55 @propertycache
@@ -285,6 +286,12 b' class dirstate(object):'
285 if f in self._copymap:
286 if f in self._copymap:
286 del self._copymap[f]
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 def normallookup(self, f):
295 def normallookup(self, f):
289 '''Mark a file normal, but possibly dirty.'''
296 '''Mark a file normal, but possibly dirty.'''
290 if self._pl[1] != nullid and f in self._map:
297 if self._pl[1] != nullid and f in self._map:
@@ -308,6 +315,7 b' class dirstate(object):'
308 self._map[f] = ('n', 0, -1, -1)
315 self._map[f] = ('n', 0, -1, -1)
309 if f in self._copymap:
316 if f in self._copymap:
310 del self._copymap[f]
317 del self._copymap[f]
318 self._lastnormal.discard(f)
311
319
312 def otherparent(self, f):
320 def otherparent(self, f):
313 '''Mark as coming from the other parent, always dirty.'''
321 '''Mark as coming from the other parent, always dirty.'''
@@ -319,6 +327,7 b' class dirstate(object):'
319 self._map[f] = ('n', 0, -2, -1)
327 self._map[f] = ('n', 0, -2, -1)
320 if f in self._copymap:
328 if f in self._copymap:
321 del self._copymap[f]
329 del self._copymap[f]
330 self._lastnormal.discard(f)
322
331
323 def add(self, f):
332 def add(self, f):
324 '''Mark a file added.'''
333 '''Mark a file added.'''
@@ -327,6 +336,7 b' class dirstate(object):'
327 self._map[f] = ('a', 0, -1, -1)
336 self._map[f] = ('a', 0, -1, -1)
328 if f in self._copymap:
337 if f in self._copymap:
329 del self._copymap[f]
338 del self._copymap[f]
339 self._lastnormal.discard(f)
330
340
331 def remove(self, f):
341 def remove(self, f):
332 '''Mark a file removed.'''
342 '''Mark a file removed.'''
@@ -343,6 +353,7 b' class dirstate(object):'
343 self._map[f] = ('r', 0, size, 0)
353 self._map[f] = ('r', 0, size, 0)
344 if size == 0 and f in self._copymap:
354 if size == 0 and f in self._copymap:
345 del self._copymap[f]
355 del self._copymap[f]
356 self._lastnormal.discard(f)
346
357
347 def merge(self, f):
358 def merge(self, f):
348 '''Mark a file merged.'''
359 '''Mark a file merged.'''
@@ -352,6 +363,7 b' class dirstate(object):'
352 self._map[f] = ('m', s.st_mode, s.st_size, int(s.st_mtime))
363 self._map[f] = ('m', s.st_mode, s.st_size, int(s.st_mtime))
353 if f in self._copymap:
364 if f in self._copymap:
354 del self._copymap[f]
365 del self._copymap[f]
366 self._lastnormal.discard(f)
355
367
356 def forget(self, f):
368 def forget(self, f):
357 '''Forget a file.'''
369 '''Forget a file.'''
@@ -361,6 +373,7 b' class dirstate(object):'
361 del self._map[f]
373 del self._map[f]
362 except KeyError:
374 except KeyError:
363 self._ui.warn(_("not in dirstate: %s\n") % f)
375 self._ui.warn(_("not in dirstate: %s\n") % f)
376 self._lastnormal.discard(f)
364
377
365 def _normalize(self, path, knownpath):
378 def _normalize(self, path, knownpath):
366 norm_path = os.path.normcase(path)
379 norm_path = os.path.normcase(path)
@@ -640,6 +653,7 b' class dirstate(object):'
640 radd = removed.append
653 radd = removed.append
641 dadd = deleted.append
654 dadd = deleted.append
642 cadd = clean.append
655 cadd = clean.append
656 lastnormal = self._lastnormal.__contains__
643
657
644 lnkkind = stat.S_IFLNK
658 lnkkind = stat.S_IFLNK
645
659
@@ -672,6 +686,18 b' class dirstate(object):'
672 elif (time != int(st.st_mtime)
686 elif (time != int(st.st_mtime)
673 and (mode & lnkkind != lnkkind or self._checklink)):
687 and (mode & lnkkind != lnkkind or self._checklink)):
674 ladd(fn)
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 elif listclean:
701 elif listclean:
676 cadd(fn)
702 cadd(fn)
677 elif state == 'm':
703 elif state == 'm':
General Comments 0
You need to be logged in to leave comments. Login now