view tests/test-status.t @ 52310:a876ab6c3fd5

rust: fix `cargo doc` warnings It makes sense to keep our doc build happy, even if it is lacking.
author Raphaël Gomès <rgomes@octobus.net>
date Mon, 04 Nov 2024 15:17:54 +0100
parents b39057b713b1
children
line wrap: on
line source

#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