rust-utils: move the `filesystem_now` function to a util
This is going to be useful for an upcoming `hg update` fastpath.
#testcases dirstate-v1 dirstate-v2
#if dirstate-v2
$ cat >> $HGRCPATH << EOF
> [format]
> use-dirstate-v2=1
> [storage]
> dirstate-v2.slow-path=allow
> EOF
#endif
$ hg init repo1
$ cd repo1
$ mkdir a b a/1 b/1 b/2
$ touch in_root a/in_a b/in_b a/1/in_a_1 b/1/in_b_1 b/2/in_b_2
hg status in repo root:
$ hg status
? a/1/in_a_1
? a/in_a
? b/1/in_b_1
? b/2/in_b_2
? b/in_b
? in_root
hg status . in repo root:
$ hg status .
? a/1/in_a_1
? a/in_a
? b/1/in_b_1
? b/2/in_b_2
? b/in_b
? in_root
$ hg status --cwd a
? a/1/in_a_1
? a/in_a
? b/1/in_b_1
? b/2/in_b_2
? b/in_b
? in_root
$ hg status --cwd a .
? 1/in_a_1
? in_a
$ hg status --cwd a ..
? 1/in_a_1
? in_a
? ../b/1/in_b_1
? ../b/2/in_b_2
? ../b/in_b
? ../in_root
$ hg status --cwd b
? a/1/in_a_1
? a/in_a
? b/1/in_b_1
? b/2/in_b_2
? b/in_b
? in_root
$ hg status --cwd b .
? 1/in_b_1
? 2/in_b_2
? in_b
$ hg status --cwd b ..
? ../a/1/in_a_1
? ../a/in_a
? 1/in_b_1
? 2/in_b_2
? in_b
? ../in_root
$ hg status --cwd a/1
? a/1/in_a_1
? a/in_a
? b/1/in_b_1
? b/2/in_b_2
? b/in_b
? in_root
$ hg status --cwd a/1 .
? in_a_1
$ hg status --cwd a/1 ..
? in_a_1
? ../in_a
$ hg status --cwd b/1
? a/1/in_a_1
? a/in_a
? b/1/in_b_1
? b/2/in_b_2
? b/in_b
? in_root
$ hg status --cwd b/1 .
? in_b_1
$ hg status --cwd b/1 ..
? in_b_1
? ../2/in_b_2
? ../in_b
$ hg status --cwd b/2
? a/1/in_a_1
? a/in_a
? b/1/in_b_1
? b/2/in_b_2
? b/in_b
? in_root
$ hg status --cwd b/2 .
? in_b_2
$ hg status --cwd b/2 ..
? ../1/in_b_1
? in_b_2
? ../in_b
combining patterns with root and patterns without a root works
$ hg st a/in_a re:.*b$
? a/in_a
? b/in_b
tweaking defaults works
$ hg status --cwd a --config ui.tweakdefaults=yes
? 1/in_a_1
? in_a
? ../b/1/in_b_1
? ../b/2/in_b_2
? ../b/in_b
? ../in_root
$ HGPLAIN=1 hg status --cwd a --config ui.tweakdefaults=yes
? a/1/in_a_1 (glob)
? a/in_a (glob)
? b/1/in_b_1 (glob)
? b/2/in_b_2 (glob)
? b/in_b (glob)
? in_root
$ HGPLAINEXCEPT=tweakdefaults hg status --cwd a --config ui.tweakdefaults=yes
? 1/in_a_1
? in_a
? ../b/1/in_b_1
? ../b/2/in_b_2
? ../b/in_b
? ../in_root (glob)
relative paths can be requested
$ hg status --cwd a --config ui.relative-paths=yes
? 1/in_a_1
? in_a
? ../b/1/in_b_1
? ../b/2/in_b_2
? ../b/in_b
? ../in_root
$ hg status --cwd a . --config ui.relative-paths=legacy
? 1/in_a_1
? in_a
$ hg status --cwd a . --config ui.relative-paths=no
? a/1/in_a_1
? a/in_a
commands.status.relative overrides ui.relative-paths
$ cat >> $HGRCPATH <<EOF
> [ui]
> relative-paths = False
> [commands]
> status.relative = True
> EOF
$ hg status --cwd a
? 1/in_a_1
? in_a
? ../b/1/in_b_1
? ../b/2/in_b_2
? ../b/in_b
? ../in_root
$ HGPLAIN=1 hg status --cwd a
? a/1/in_a_1 (glob)
? a/in_a (glob)
? b/1/in_b_1 (glob)
? b/2/in_b_2 (glob)
? b/in_b (glob)
? in_root
if relative paths are explicitly off, tweakdefaults doesn't change it
$ cat >> $HGRCPATH <<EOF
> [commands]
> status.relative = False
> EOF
$ hg status --cwd a --config ui.tweakdefaults=yes
? a/1/in_a_1
? a/in_a
? b/1/in_b_1
? b/2/in_b_2
? b/in_b
? in_root
$ cd ..
$ hg init repo2
$ cd repo2
$ touch modified removed deleted ignored
$ echo "^ignored$" > .hgignore
$ hg ci -A -m 'initial checkin'
adding .hgignore
adding deleted
adding modified
adding removed
$ touch modified added unknown ignored
$ hg add added
$ hg remove removed
$ rm deleted
hg status:
$ hg status
A added
R removed
! deleted
? unknown
hg status -n:
$ env RHG_ON_UNSUPPORTED=abort hg status -n
added
removed
deleted
unknown
hg status modified added removed deleted unknown never-existed ignored:
$ hg status modified added removed deleted unknown never-existed ignored
never-existed: * (glob)
A added
R removed
! deleted
? unknown
$ hg copy modified copied
hg status -C:
$ hg status -C
A added
A copied
modified
R removed
! deleted
? unknown
hg status -0:
$ hg status -0 --config rhg.on-unsupported=abort
A added\x00A copied\x00R removed\x00! deleted\x00? unknown\x00 (no-eol) (esc)
hg status -A:
$ hg status -A
A added
A copied
modified
R removed
! deleted
? unknown
I ignored
C .hgignore
C modified
$ hg status -A -T '{status} {path} {node|shortest}\n'
A added ffff
A copied ffff
R removed ffff
! deleted ffff
? unknown ffff
I ignored ffff
C .hgignore ffff
C modified ffff
$ hg status -A -Tjson
[
{
"itemtype": "file",
"path": "added",
"status": "A"
},
{
"itemtype": "file",
"path": "copied",
"source": "modified",
"status": "A"
},
{
"itemtype": "file",
"path": "removed",
"status": "R"
},
{
"itemtype": "file",
"path": "deleted",
"status": "!"
},
{
"itemtype": "file",
"path": "unknown",
"status": "?"
},
{
"itemtype": "file",
"path": "ignored",
"status": "I"
},
{
"itemtype": "file",
"path": ".hgignore",
"status": "C"
},
{
"itemtype": "file",
"path": "modified",
"status": "C"
}
]
$ hg status -A -Tpickle > pickle
>>> import pickle
>>> from mercurial import util
>>> data = sorted((x[b'status'].decode(), x[b'path'].decode()) for x in pickle.load(open("pickle", r"rb")))
>>> for s, p in data: print("%s %s" % (s, p))
! deleted
? pickle
? unknown
A added
A copied
C .hgignore
C modified
I ignored
R removed
$ rm pickle
$ echo "^ignoreddir$" > .hgignore
$ mkdir ignoreddir
$ touch ignoreddir/file
Test templater support:
$ hg status -AT "[{status}]\t{if(source, '{source} -> ')}{path}\n"
[M] .hgignore
[A] added
[A] modified -> copied
[R] removed
[!] deleted
[?] ignored
[?] unknown
[I] ignoreddir/file
[C] modified
$ hg status -AT default
M .hgignore
A added
A copied
modified
R removed
! deleted
? ignored
? unknown
I ignoreddir/file
C modified
$ hg status -T compact
abort: "status" not in template map
[255]
hg status ignoreddir/file:
$ hg status ignoreddir/file
hg status -i ignoreddir/file:
$ hg status -i ignoreddir/file
I ignoreddir/file
$ cd ..
Check 'status -q' and some combinations
$ hg init repo3
$ cd repo3
$ touch modified removed deleted ignored
$ echo "^ignored$" > .hgignore
$ hg commit -A -m 'initial checkin'
adding .hgignore
adding deleted
adding modified
adding removed
$ touch added unknown ignored
$ hg add added
$ echo "test" >> modified
$ hg remove removed
$ rm deleted
$ hg copy modified copied
Specify working directory revision explicitly, that should be the same as
"hg status"
$ hg status --change "wdir()"
M modified
A added
A copied
R removed
! deleted
? unknown
Run status with 2 different flags.
Check if result is the same or different.
If result is not as expected, raise error
$ assert() {
> hg status $1 > ../a
> hg status $2 > ../b
> if diff ../a ../b > /dev/null; then
> out=0
> else
> out=1
> fi
> if [ $3 -eq 0 ]; then
> df="same"
> else
> df="different"
> fi
> if [ $out -ne $3 ]; then
> echo "Error on $1 and $2, should be $df."
> fi
> }
Assert flag1 flag2 [0-same | 1-different]
$ assert "-q" "-mard" 0
$ assert "-A" "-marduicC" 0
$ assert "-qA" "-mardcC" 0
$ assert "-qAui" "-A" 0
$ assert "-qAu" "-marducC" 0
$ assert "-qAi" "-mardicC" 0
$ assert "-qu" "-u" 0
$ assert "-q" "-u" 1
$ assert "-m" "-a" 1
$ assert "-r" "-d" 1
$ cd ..
$ hg init repo4
$ cd repo4
$ touch modified removed deleted
$ hg ci -q -A -m 'initial checkin'
$ touch added unknown
$ hg add added
$ hg remove removed
$ rm deleted
$ echo x > modified
$ hg copy modified copied
$ hg ci -m 'test checkin' -d "1000001 0"
$ rm *
$ touch unrelated
$ hg ci -q -A -m 'unrelated checkin' -d "1000002 0"
hg status --change 1:
$ hg status --change 1
M modified
A added
A copied
R removed
hg status --change 1 unrelated:
$ hg status --change 1 unrelated
hg status -C --change 1 added modified copied removed deleted:
$ hg status -C --change 1 added modified copied removed deleted
M modified
A added
A copied
modified
R removed
hg status -A --change 1 and revset:
$ hg status -A --change '1|1'
M modified
A added
A copied
modified
R removed
C deleted
$ cd ..
hg status with --rev and reverted changes:
$ hg init reverted-changes-repo
$ cd reverted-changes-repo
$ echo a > file
$ hg add file
$ hg ci -m a
$ echo b > file
$ hg ci -m b
reverted file should appear clean
$ hg revert -r 0 .
reverting file
$ hg status -A --rev 0
C file
#if execbit
reverted file with changed flag should appear modified
$ chmod +x file
$ hg status -A --rev 0
M file
$ hg revert -r 0 .
reverting file
reverted and committed file with changed flag should appear modified
$ hg co -C .
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ chmod +x file
$ hg ci -m 'change flag'
$ hg status -A --rev 1 --rev 2
M file
$ hg diff -r 1 -r 2
#endif
$ cd ..
hg status of binary file starting with '\1\n', a separator for metadata:
$ hg init repo5
$ cd repo5
>>> open("010a", r"wb").write(b"\1\nfoo") and None
$ hg ci -q -A -m 'initial checkin'
$ hg status -A
C 010a
>>> open("010a", r"wb").write(b"\1\nbar") and None
$ hg status -A
M 010a
$ hg ci -q -m 'modify 010a'
$ hg status -A --rev 0:1
M 010a
$ touch empty
$ hg ci -q -A -m 'add another file'
$ hg status -A --rev 1:2 010a
C 010a
$ cd ..
test "hg status" with "directory pattern" which matches against files
only known on target revision.
$ hg init repo6
$ cd repo6
$ echo a > a.txt
$ hg add a.txt
$ hg commit -m '#0'
$ mkdir -p 1/2/3/4/5
$ echo b > 1/2/3/4/5/b.txt
$ hg add 1/2/3/4/5/b.txt
$ hg commit -m '#1'
$ hg update -C 0 > /dev/null
$ hg status -A
C a.txt
the directory matching against specified pattern should be removed,
because directory existence prevents 'dirstate.walk()' from showing
warning message about such pattern.
$ test ! -d 1
$ hg status -A --rev 1 1/2/3/4/5/b.txt
R 1/2/3/4/5/b.txt
$ hg status -A --rev 1 1/2/3/4/5
R 1/2/3/4/5/b.txt
$ hg status -A --rev 1 1/2/3
R 1/2/3/4/5/b.txt
$ hg status -A --rev 1 1
R 1/2/3/4/5/b.txt
$ hg status --config ui.formatdebug=True --rev 1 1
status = [
{
'itemtype': 'file',
'path': '1/2/3/4/5/b.txt',
'status': 'R'
},
]
#if windows
$ hg --config ui.slash=false status -A --rev 1 1
R 1\2\3\4\5\b.txt
#endif
$ cd ..
Status after move overwriting a file (issue4458)
=================================================
$ hg init issue4458
$ cd issue4458
$ echo a > a
$ echo b > b
$ hg commit -Am base
adding a
adding b
with --force
$ hg mv b --force a
$ hg st --copies
M a
b
R b
$ hg revert --all
reverting a
undeleting b
$ rm *.orig
without force
$ hg rm a
$ hg st --copies
R a
$ hg mv b a
$ hg st --copies
M a
b
R b
using ui.statuscopies setting
$ hg st --config ui.statuscopies=true
M a
b
R b
$ hg st --config ui.statuscopies=true --no-copies
M a
R b
$ hg st --config ui.statuscopies=false
M a
R b
$ hg st --config ui.statuscopies=false --copies
M a
b
R b
$ hg st --config ui.tweakdefaults=yes
M a
b
R b
using log status template (issue5155)
$ hg log -Tstatus -r 'wdir()' -C
changeset: 2147483647:ffffffffffff
parent: 0:8c55c58b4c0e
user: test
date: * (glob)
files:
M a
b
R b
$ hg log -GTstatus -r 'wdir()' -C
o changeset: 2147483647:ffffffffffff
| parent: 0:8c55c58b4c0e
~ user: test
date: * (glob)
files:
M a
b
R b
Other "bug" highlight, the revision status does not report the copy information.
This is buggy behavior.
$ hg commit -m 'blah'
$ hg st --copies --change .
M a
R b
using log status template, the copy information is displayed correctly.
$ hg log -Tstatus -r. -C
changeset: 1:6685fde43d21
tag: tip
user: test
date: * (glob)
summary: blah
files:
M a
b
R b
$ cd ..
Make sure .hg doesn't show up even as a symlink
$ hg init repo0
$ mkdir symlink-repo0
$ cd symlink-repo0
$ ln -s ../repo0/.hg
$ hg status
If the size hasn’t changed but mtime has, status needs to read the contents
of the file to check whether it has changed
$ echo 1 > a
$ echo 1 > b
$ touch -t 200102030000 a b
$ hg commit -Aqm '#0'
$ echo 2 > a
$ touch -t 200102040000 a b
$ hg status
M a
Asking specifically for the status of a deleted/removed file
$ rm a
$ rm b
$ hg status a
! a
$ hg rm a
$ hg rm b
$ hg status a
R a
$ hg commit -qm '#1'
$ hg status a
a: $ENOENT$
Check using include flag with pattern when status does not need to traverse
the working directory (issue6483)
$ cd ..
$ hg init issue6483
$ cd issue6483
$ touch a.py b.rs
$ hg add a.py b.rs
$ hg st -aI "*.py"
A a.py
Also check exclude pattern
$ hg st -aX "*.rs"
A a.py
issue6335
When a directory containing a tracked file gets symlinked, as of 5.8
`hg st` only gives the correct answer about clean (or deleted) files
if also listing unknowns.
The tree-based dirstate and status algorithm fix this:
#if symlink no-dirstate-v1 rust
$ cd ..
$ hg init issue6335
$ cd issue6335
$ mkdir foo
$ touch foo/a
$ hg ci -Ama
adding foo/a
$ mv foo bar
$ ln -s bar foo
$ hg status
! foo/a
? bar/a
? foo
$ hg status -c # incorrect output without the Rust implementation
$ hg status -cu
? bar/a
? foo
$ hg status -d # incorrect output without the Rust implementation
! foo/a
$ hg status -du
! foo/a
? bar/a
? foo
#endif
Create a repo with files in each possible status
$ cd ..
$ hg init repo7
$ cd repo7
$ mkdir subdir
$ touch clean modified deleted removed
$ touch subdir/clean subdir/modified subdir/deleted subdir/removed
$ echo ignored > .hgignore
$ hg ci -Aqm '#0'
$ echo 1 > modified
$ echo 1 > subdir/modified
$ rm deleted
$ rm subdir/deleted
$ hg rm removed
$ hg rm subdir/removed
$ touch unknown ignored
$ touch subdir/unknown subdir/ignored
Check the output
$ hg status
M modified
M subdir/modified
R removed
R subdir/removed
! deleted
! subdir/deleted
? subdir/unknown
? unknown
$ hg status -mard
M modified
M subdir/modified
R removed
R subdir/removed
! deleted
! subdir/deleted
$ hg status -A
M modified
M subdir/modified
R removed
R subdir/removed
! deleted
! subdir/deleted
? subdir/unknown
? unknown
I ignored
I subdir/ignored
C .hgignore
C clean
C subdir/clean
Test various matchers interatction with dirstate code:
$ hg status path:subdir
M subdir/modified
R subdir/removed
! subdir/deleted
? subdir/unknown
$ hg status 'glob:subdir/*'
M subdir/modified
R subdir/removed
! subdir/deleted
? subdir/unknown
$ hg status rootfilesin:subdir
M subdir/modified
R subdir/removed
! subdir/deleted
? subdir/unknown
Note: `hg status some-name` creates a patternmatcher which is not supported
yet by the Rust implementation of status, but includematcher is supported.
--include is used below for that reason
#if unix-permissions
Not having permission to read a directory that contains tracked files makes
status emit a warning then behave as if the directory was empty or removed
entirely:
$ chmod 0 subdir
$ hg status --include subdir
subdir: $EACCES$
R subdir/removed
! subdir/clean
! subdir/deleted
! subdir/modified
$ chmod 755 subdir
#endif
Remove a directory that contains tracked files
$ rm -r subdir
$ hg status --include subdir
R subdir/removed
! subdir/clean
! subdir/deleted
! subdir/modified
… and replace it by a file
$ touch subdir
$ hg status --include subdir
R subdir/removed
! subdir/clean
! subdir/deleted
! subdir/modified
? subdir
Replaced a deleted or removed file with a directory
$ mkdir deleted removed
$ touch deleted/1 removed/1
$ hg status --include deleted --include removed
R removed
! deleted
? deleted/1
? removed/1
$ hg add removed/1
$ hg status --include deleted --include removed
A removed/1
R removed
! deleted
? deleted/1
Deeply nested files in an ignored directory are still listed on request
$ echo ignored-dir >> .hgignore
$ mkdir ignored-dir
$ mkdir ignored-dir/subdir
$ touch ignored-dir/subdir/1
$ hg status --ignored
I ignored
I ignored-dir/subdir/1
Check using include flag while listing ignored composes correctly (issue6514)
$ cd ..
$ hg init issue6514
$ cd issue6514
$ mkdir ignored-folder
$ touch A.hs B.hs C.hs ignored-folder/other.txt ignored-folder/ctest.hs
$ cat >.hgignore <<EOF
> A.hs
> B.hs
> ignored-folder/
> EOF
$ hg st -i -I 're:.*\.hs$'
I A.hs
I B.hs
I ignored-folder/ctest.hs
#if rust dirstate-v2
Check read_dir caching
$ cd ..
$ hg init repo8
$ cd repo8
$ mkdir subdir
$ touch subdir/a subdir/b
$ hg ci -Aqm '#0'
The cached mtime is initially unset
$ hg debugdirstate --all --no-dates | grep '^ '
0 -1 unset subdir
It is still not set when there are unknown files
$ touch subdir/unknown
$ hg status
? subdir/unknown
$ hg debugdirstate --all --no-dates | grep '^ '
0 -1 unset subdir
Now the directory is eligible for caching, so its mtime is saved in the dirstate
$ rm subdir/unknown
$ sleep 0.1 # ensure the kernel’s internal clock for mtimes has ticked
$ hg status
$ hg debugdirstate --all --no-dates | grep '^ '
0 -1 set subdir
This time the command should be ever so slightly faster since it does not need `read_dir("subdir")`
$ hg status
Creating a new file changes the directory’s mtime, invalidating the cache
$ touch subdir/unknown
$ hg status
? subdir/unknown
$ rm subdir/unknown
$ hg status
Removing a node from the dirstate resets the cache for its parent directory
$ hg forget subdir/a
$ hg debugdirstate --all --no-dates | grep '^ '
0 -1 set subdir
$ hg ci -qm '#1'
$ hg debugdirstate --all --no-dates | grep '^ '
0 -1 unset subdir
$ hg status
? subdir/a
Changing the hgignore rules makes us recompute the status (and rewrite the dirstate).
$ rm subdir/a
$ mkdir another-subdir
$ touch another-subdir/something-else
$ cat > "$TESTTMP"/extra-hgignore <<EOF
> something-else
> EOF
$ hg status --config ui.ignore.global="$TESTTMP"/extra-hgignore
$ hg debugdirstate --all --no-dates | grep '^ '
0 -1 set subdir
$ hg status
? another-subdir/something-else
One invocation of status is enough to populate the cache even if it's invalidated
in the same run.
$ hg debugdirstate --all --no-dates | grep '^ '
0 -1 set subdir
#endif
Test copy source formatting.
$ cd ..
$ hg init copy-source-repo
$ cd copy-source-repo
$ mkdir -p foo/bar
$ cd foo/bar
$ touch file
$ hg addremove
adding foo/bar/file
$ hg commit -m 'add file'
$ hg mv file copy
Copy source respects relative path setting.
$ hg st --config ui.statuscopies=true --config commands.status.relative=true
A copy
file
R file
Copy source is not shown when --no-status is passed.
$ hg st --config ui.statuscopies=true --no-status
foo/bar/copy
foo/bar/file