annotate: increase refcount of each revisions correctly (issue3841)
Before this patch, refcount (managed in "needed") of parents of each
revisions in "visit" is increased, only when parent is not annotated
yet (examined by "p not in hist").
But this causes less refcount of the revision like "A" in the tree
below ("A" is assumed as the second parent of "C"):
A --- B --- C
\ /
\-----/
Steps of annotation for "C" in this case are shown below:
1. for "C"
1.1 increase refcount of "B"
1.2 increase refcount of "A" (=> 1)
1.3 defer annotation for "C"
2. for "A"
2.1 annotate for "A" (=> put result into "hist[A]")
2.2 clear "pcache[A]" ("pcache[A] = []")
3. for "B"
3.1 not increase refcount of "A", because "A not in hist" is False
3.2 annotate for "B"
3.3 decrease refcount of "A" (=> 0)
3.4 delete "hist[A]", even though "A" is still needed by "C"
3.5 clear "pcache[B]"
4. for "C", again
4.1 not increase refcount of "B", because "B not in hist" is False
4.2 increase refcount of "A" (=> 1)
4.3 defer annotation for "C"
5. for "A", again
5.1 annotate for "A" (=> put result into "hist[A]", again)
5.2 clear "pcache[A]"
6. for "C", once again
6.1 not increase refcount of "B", because "B not in hist" is False
6.2 not increase refcount of "A", because "A not in hist" is False
6.3 annotate for "C"
6.4 decrease refcount of "A", and delete "hist[A]"
6.5 decrease refcount of "B", and delete "hist[B]"
6.6 clear "pcache[C]"
At step (5.1), annotation for "A" mis-recognizes that all lines are
created at "A", because "pcache[A]" already cleared at step (2.2)
prevents from scanning ancestors of "A".
So, annotation for "C" or its descendants loses information about "A"
or its ancestors.
The root cause of this problem is that refcount of "A" is decreased at
step (3.3), even though it isn't increased at step (3.1).
To increase refcount correctly, this patch increases refcount of each
parents of each revisions:
- regardless of "p not in hist" or not, and
- only once for each revisions in "visit" (by "not pcached")
In fact, this problem should occur only on legacy repositories in
which a filelog includes the merging between the revision and its
ancestor (as the second parent), because:
- tree is scanned in depth-first
without such merging, revisions in "visit" refer different
revisions as parent each other
- recent Mercurial doesn't allow such merging
changelog and manifest can include such merging someway, but
filelogs can't, because "localrepository._filecommit()" converts
such merging request to linear history.
This patch tests merging cases below: these cases are from filelog of
"mercurial/commands.py" in the repository of Mercurial itself.
- both parents are same
10 --- 11 --- 12
\_/
filelogrev: changesetid:
10 00ea3613f82c
11 fc4a6e5b5812
12 4f802588cdfb
- the second parent is also ancestor of the first one
37 --- 38 --- 39 --- 40
\________/
filelogrev: changesetid:
37 f8d56da6ac8f
38 38919e1c254d
39 d3400605d246
40 f06a4a3b86a7
$ hg init repo
$ cd repo
$ echo 123 > a
$ echo 123 > c
$ echo 123 > e
$ hg add a c e
$ hg commit -m "first" a c e
nothing changed
$ hg revert
abort: no files or directories specified
(use --all to revert all files)
[255]
$ hg revert --all
$ echo 123 > b
should show b unknown
$ hg status
? b
$ echo 12 > c
should show b unknown and c modified
$ hg status
M c
? b
$ hg add b
should show b added and c modified
$ hg status
M c
A b
$ hg rm a
should show a removed, b added and c modified
$ hg status
M c
A b
R a
$ hg revert a
should show b added, copy saved, and c modified
$ hg status
M c
A b
$ hg revert b
should show b unknown, and c modified
$ hg status
M c
? b
$ hg revert --no-backup c
should show unknown: b
$ hg status
? b
$ hg add b
should show b added
$ hg status b
A b
$ rm b
should show b deleted
$ hg status b
! b
$ hg revert -v b
forgetting b
should not find b
$ hg status b
b: * (glob)
should show a c e
$ ls
a
c
e
should verbosely save backup to e.orig
$ echo z > e
$ hg revert --all -v
saving current version of e as e.orig
reverting e
should say no changes needed
$ hg revert a
no changes needed to a
should say file not managed
$ echo q > q
$ hg revert q
file not managed: q
$ rm q
should say file not found
$ hg revert notfound
notfound: no such file in rev 334a9e57682c
$ touch d
$ hg add d
$ hg rm a
$ hg commit -m "second"
$ echo z > z
$ hg add z
$ hg st
A z
? e.orig
should add a, remove d, forget z
$ hg revert --all -r0
adding a
removing d
forgetting z
should forget a, undelete d
$ hg revert --all -rtip
forgetting a
undeleting d
$ rm a *.orig
should silently add a
$ hg revert -r0 a
$ hg st a
A a
$ hg rm d
$ hg st d
R d
should silently keep d removed
$ hg revert -r0 d
$ hg st d
R d
$ hg update -C
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
#if execbit
$ chmod +x c
$ hg revert --all
reverting c
should print non-executable
$ test -x c || echo non-executable
non-executable
$ chmod +x c
$ hg commit -m exe
$ chmod -x c
$ hg revert --all
reverting c
should print executable
$ test -x c && echo executable
executable
#endif
$ cd ..
Issue241: update and revert produces inconsistent repositories
$ hg init a
$ cd a
$ echo a >> a
$ hg commit -A -d '1 0' -m a
adding a
$ echo a >> a
$ hg commit -d '2 0' -m a
$ hg update 0
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ mkdir b
$ echo b > b/b
should fail - no arguments
$ hg revert -rtip
abort: no files or directories specified
(use --all to revert all files, or 'hg update 1' to update)
[255]
should succeed
$ hg revert --all -rtip
reverting a
Issue332: confusing message when reverting directory
$ hg ci -A -m b
adding b/b
created new head
$ echo foobar > b/b
$ mkdir newdir
$ echo foo > newdir/newfile
$ hg add newdir/newfile
$ hg revert b newdir
reverting b/b (glob)
forgetting newdir/newfile (glob)
$ echo foobar > b/b
$ hg revert .
reverting b/b (glob)
reverting a rename target should revert the source
$ hg mv a newa
$ hg revert newa
$ hg st a newa
? newa
$ cd ..
$ hg init ignored
$ cd ignored
$ echo '^ignored$' > .hgignore
$ echo '^ignoreddir$' >> .hgignore
$ echo '^removed$' >> .hgignore
$ mkdir ignoreddir
$ touch ignoreddir/file
$ touch ignoreddir/removed
$ touch ignored
$ touch removed
4 ignored files (we will add/commit everything)
$ hg st -A -X .hgignore
I ignored
I ignoreddir/file
I ignoreddir/removed
I removed
$ hg ci -qAm 'add files' ignored ignoreddir/file ignoreddir/removed removed
$ echo >> ignored
$ echo >> ignoreddir/file
$ hg rm removed ignoreddir/removed
should revert ignored* and undelete *removed
$ hg revert -a --no-backup
reverting ignored
reverting ignoreddir/file (glob)
undeleting ignoreddir/removed (glob)
undeleting removed
$ hg st -mardi
$ hg up -qC
$ echo >> ignored
$ hg rm removed
should silently revert the named files
$ hg revert --no-backup ignored removed
$ hg st -mardi
$ cd ..