annotate tests/test-commit-multiple.t @ 13704:a464763e99f1

dirstate: avoid a race with multiple commits in the same process (issue2264, issue2516) The race happens when two commits in a row change the same file without changing its size, *if* those two commits happen in the same second in the same process while holding the same repo lock. For example: commit 1: M a M b commit 2: # same process, same second, same repo lock M b # modify b without changing its size M c This first manifested in transplant, which is the most common way to do multiple commits in the same process. But it can manifest in any script or extension that does multiple commits under the same repo lock. (Thus, the test script tests both transplant and a custom script.) The problem was that dirstate.status() failed to notice the change to b when localrepo is about to do the second commit, meaning that change gets left in the working directory. In the context of transplant, that means either a crash ("RuntimeError: nothing committed after transplant") or a silently inaccurate transplant, depending on whether any other files were modified by the second transplanted changeset. The fix is to make status() work a little harder when we have previously marked files as clean (state 'normal') in the same process. Specifically, dirstate.normal() adds files to self._lastnormal, and other state-changing methods remove them. Then dirstate.status() puts any files in self._lastnormal into state 'lookup', which will make localrepository.status() read file contents to see if it has really changed. So we pay a small performance penalty for the second (and subsequent) commits in the same process, without affecting the common case. Anything that does lots of status updates and checks in the same process could suffer a performance hit. Incidentally, there is a simpler fix: call dirstate.normallookup() on every file updated by commit() at the end of the commit. The trouble with that solution is that it imposes a performance penalty on the common case: it means the next status-dependent hg command after every "hg commit" will be a little bit slower. The patch here is more complex, but only affects performance for the uncommon case.
author Greg Ward <greg@gerg.ca>
date Sun, 20 Mar 2011 17:41:09 -0400
parents
children 8bb03283e9b9
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
13704
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
1 # reproduce issue2264, issue2516
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
2
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
3 create test repo
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
4 $ cat <<EOF >> $HGRCPATH
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
5 > [extensions]
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
6 > transplant =
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
7 > graphlog =
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
8 > EOF
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
9 $ hg init repo
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
10 $ cd repo
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
11 $ template="{rev} {desc|firstline} [{branch}]\n"
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
12
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
13 # we need to start out with two changesets on the default branch
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
14 # in order to avoid the cute little optimization where transplant
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
15 # pulls rather than transplants
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
16 add initial changesets
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
17 $ echo feature1 > file1
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
18 $ hg ci -Am"feature 1"
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
19 adding file1
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
20 $ echo feature2 >> file2
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
21 $ hg ci -Am"feature 2"
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
22 adding file2
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
23
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
24 # The changes to 'bugfix' are enough to show the bug: in fact, with only
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
25 # those changes, it's a very noisy crash ("RuntimeError: nothing
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
26 # committed after transplant"). But if we modify a second file in the
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
27 # transplanted changesets, the bug is much more subtle: transplant
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
28 # silently drops the second change to 'bugfix' on the floor, and we only
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
29 # see it when we run 'hg status' after transplanting. Subtle data loss
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
30 # bugs are worse than crashes, so reproduce the subtle case here.
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
31 commit bug fixes on bug fix branch
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
32 $ hg branch fixes
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
33 marked working directory as branch fixes
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
34 $ echo fix1 > bugfix
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
35 $ echo fix1 >> file1
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
36 $ hg ci -Am"fix 1"
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
37 adding bugfix
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
38 $ echo fix2 > bugfix
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
39 $ echo fix2 >> file1
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
40 $ hg ci -Am"fix 2"
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
41 $ hg glog --template="$template"
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
42 @ 3 fix 2 [fixes]
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
43 |
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
44 o 2 fix 1 [fixes]
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
45 |
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
46 o 1 feature 2 [default]
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
47 |
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
48 o 0 feature 1 [default]
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
49
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
50 transplant bug fixes onto release branch
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
51 $ hg update 0
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
52 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
53 $ hg branch release
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
54 marked working directory as branch release
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
55 $ hg transplant 2 3
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
56 applying [0-9a-f]{12} (re)
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
57 [0-9a-f]{12} transplanted to [0-9a-f]{12} (re)
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
58 applying [0-9a-f]{12} (re)
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
59 [0-9a-f]{12} transplanted to [0-9a-f]{12} (re)
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
60 $ hg glog --template="$template"
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
61 @ 5 fix 2 [release]
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
62 |
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
63 o 4 fix 1 [release]
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
64 |
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
65 | o 3 fix 2 [fixes]
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
66 | |
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
67 | o 2 fix 1 [fixes]
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
68 | |
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
69 | o 1 feature 2 [default]
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
70 |/
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
71 o 0 feature 1 [default]
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
72
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
73 $ hg status
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
74 $ hg status --rev 0:4
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
75 M file1
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
76 A bugfix
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
77 $ hg status --rev 4:5
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
78 M bugfix
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
79 M file1
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
80
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
81 now test that we fixed the bug for all scripts/extensions
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
82 $ cat > $TESTTMP/committwice.py <<__EOF__
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
83 > from mercurial import ui, hg, match, node
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
84 >
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
85 > def replacebyte(fn, b):
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
86 > f = open("file1", "rb+")
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
87 > f.seek(0, 0)
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
88 > f.write(b)
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
89 > f.close()
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
90 >
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
91 > repo = hg.repository(ui.ui(), '.')
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
92 > assert len(repo) == 6, \
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
93 > "initial: len(repo) == %d, expected 6" % len(repo)
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
94 > try:
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
95 > wlock = repo.wlock()
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
96 > lock = repo.lock()
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
97 > m = match.exact(repo.root, '', ['file1'])
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
98 > replacebyte("file1", "x")
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
99 > n = repo.commit(text="x", user="test", date=(0, 0), match=m)
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
100 > print "commit 1: len(repo) == %d" % len(repo)
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
101 > replacebyte("file1", "y")
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
102 > n = repo.commit(text="y", user="test", date=(0, 0), match=m)
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
103 > print "commit 2: len(repo) == %d" % len(repo)
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
104 > finally:
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
105 > lock.release()
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
106 > wlock.release()
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
107 > __EOF__
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
108 $ $PYTHON $TESTTMP/committwice.py
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
109 commit 1: len(repo) == 7
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
110 commit 2: len(repo) == 8
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
111
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
112 Do a size-preserving modification outside of that process
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
113 $ echo abcd > bugfix
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
114 $ hg status
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
115 M bugfix
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
116 $ hg log --template "{rev} {desc} {files}\n" -r5:
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
117 5 fix 2 bugfix file1
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
118 6 x file1
a464763e99f1 dirstate: avoid a race with multiple commits in the same process
Greg Ward <greg@gerg.ca>
parents:
diff changeset
119 7 y file1