Skip to content
Snippets Groups Projects
  • Katsunori FUJIWARA's avatar
    fe03f522
    context: write dirstate out explicitly after marking files as clean · fe03f522
    Katsunori FUJIWARA authored
    To detect change of a file without redundant comparison of file
    content, dirstate recognizes a file as certainly clean, if:
    
      (1) it is already known as "normal",
      (2) dirstate entry for it has valid (= not "-1") timestamp, and
      (3) mode, size and timestamp of it on the filesystem are as same as
          ones expected in dirstate
    
    This works as expected in many cases, but doesn't in the corner case
    that changing a file keeps mode, size and timestamp of it on the
    filesystem.
    
    The timetable below shows steps in one of typical such situations:
    
      ---- ----------------------------------- ----------------
                                               timestamp of "f"
                                               ----------------
                                               dirstate   file-
      time          action                     mem  file  system
      ---- ----------------------------------- ---- ----- -----
      N                                              -1    ***
           - make file "f" clean                            N
    
           - execute 'hg foobar'
             - instantiate 'dirstate'           -1   -1
             - 'dirstate.normal("f")'           N    -1
               (e.g. via dirty check)
             - change "f", but keep size                    N
      N+1
             - release wlock
               - 'dirstate.write()'             N    N
    
           - 'hg status' shows "f" as "clean"   N    N      N
      ---- ----------------------------------- ---- ----- -----
    
    The most important point is that 'dirstate.write()' is executed at N+1
    or later. This causes writing dirstate timestamp N of "f" out
    successfully. If it is executed at N, 'parsers.pack_dirstate()'
    replaces timestamp N with "-1" before actual writing dirstate out.
    
    Occasional test failure for unexpected file status is typical example
    of this corner case. Batch execution with small working directory is
    finished in no time, and rarely satisfies condition (2) above.
    
    This issue can occur in cases below;
    
      - 'hg revert --rev REV' for revisions other than the parent
      - failure of 'merge.update()' before 'merge.recordupdates()'
    
    The root cause of this issue is that files are changed without
    flushing in-memory dirstate changes via 'repo.commit()' (even though
    omitting 'dirstate.normallookup()' on changed files also causes this
    issue).
    
    To detect changes of files correctly, this patch writes in-memory
    dirstate changes out explicitly after marking files as clean in
    'workingctx._checklookup()', which is invoked via 'repo.status()'.
    
    After this change, timetable is changed as below:
    
      ---- ----------------------------------- ----------------
                                               timestamp of "f"
                                               ----------------
                                               dirstate   file-
      time          action                     mem  file  system
      ---- ----------------------------------- ---- ----- -----
      N                                              -1    ***
           - make file "f" clean                            N
    
           - execute 'hg foobar'
             - instantiate 'dirstate'           -1   -1
             - 'dirstate.normal("f")'           N    -1
               (e.g. via dirty check)
           ----------------------------------- ---- ----- -----
             - 'dirsttate.write()'              -1   -1
           ----------------------------------- ---- ----- -----
             - change "f", but keep size                    N
      N+1
             - release wlock
               - 'dirstate.write()'             -1   -1
    
           - 'hg status'                        -1   -1      N
      ---- ----------------------------------- ---- ----- -----
    
    To reproduce this issue in tests certainly, this patch emulates some
    timing critical actions as below:
    
      - timestamp of "f" in '.hg/dirstate' is -1 at the beginning
    
        'hg debugrebuildstate' before command invocation ensures it.
    
      - make file "f" clean at N
      - change "f" at N
    
        'touch -t 200001010000' before and after command invocation
        changes mtime of "f" to "2000-01-01 00:00" (= N).
    
      - invoke 'dirstate.write()' via 'repo.status()' at N
    
        'fakedirstatewritetime.py' forces 'pack_dirstate()' to use
        "2000-01-01 00:00" as "now", only if 'pack_dirstate()' is invoked
        via 'workingctx._checklookup()'.
    
      - invoke 'dirstate.write()' via releasing wlock at N+1 (or "not at N")
    
        'pack_dirstate()' via releasing wlock uses actual timestamp at
        runtime as "now", and it should be different from the "2000-01-01
        00:00" of "f".
    
    BTW, this patch also changes 'test-largefiles-misc.t', because adding
    'dirstate.write()' makes recent dirstate changes visible to external
    process.
    fe03f522
    History
    context: write dirstate out explicitly after marking files as clean
    Katsunori FUJIWARA authored
    To detect change of a file without redundant comparison of file
    content, dirstate recognizes a file as certainly clean, if:
    
      (1) it is already known as "normal",
      (2) dirstate entry for it has valid (= not "-1") timestamp, and
      (3) mode, size and timestamp of it on the filesystem are as same as
          ones expected in dirstate
    
    This works as expected in many cases, but doesn't in the corner case
    that changing a file keeps mode, size and timestamp of it on the
    filesystem.
    
    The timetable below shows steps in one of typical such situations:
    
      ---- ----------------------------------- ----------------
                                               timestamp of "f"
                                               ----------------
                                               dirstate   file-
      time          action                     mem  file  system
      ---- ----------------------------------- ---- ----- -----
      N                                              -1    ***
           - make file "f" clean                            N
    
           - execute 'hg foobar'
             - instantiate 'dirstate'           -1   -1
             - 'dirstate.normal("f")'           N    -1
               (e.g. via dirty check)
             - change "f", but keep size                    N
      N+1
             - release wlock
               - 'dirstate.write()'             N    N
    
           - 'hg status' shows "f" as "clean"   N    N      N
      ---- ----------------------------------- ---- ----- -----
    
    The most important point is that 'dirstate.write()' is executed at N+1
    or later. This causes writing dirstate timestamp N of "f" out
    successfully. If it is executed at N, 'parsers.pack_dirstate()'
    replaces timestamp N with "-1" before actual writing dirstate out.
    
    Occasional test failure for unexpected file status is typical example
    of this corner case. Batch execution with small working directory is
    finished in no time, and rarely satisfies condition (2) above.
    
    This issue can occur in cases below;
    
      - 'hg revert --rev REV' for revisions other than the parent
      - failure of 'merge.update()' before 'merge.recordupdates()'
    
    The root cause of this issue is that files are changed without
    flushing in-memory dirstate changes via 'repo.commit()' (even though
    omitting 'dirstate.normallookup()' on changed files also causes this
    issue).
    
    To detect changes of files correctly, this patch writes in-memory
    dirstate changes out explicitly after marking files as clean in
    'workingctx._checklookup()', which is invoked via 'repo.status()'.
    
    After this change, timetable is changed as below:
    
      ---- ----------------------------------- ----------------
                                               timestamp of "f"
                                               ----------------
                                               dirstate   file-
      time          action                     mem  file  system
      ---- ----------------------------------- ---- ----- -----
      N                                              -1    ***
           - make file "f" clean                            N
    
           - execute 'hg foobar'
             - instantiate 'dirstate'           -1   -1
             - 'dirstate.normal("f")'           N    -1
               (e.g. via dirty check)
           ----------------------------------- ---- ----- -----
             - 'dirsttate.write()'              -1   -1
           ----------------------------------- ---- ----- -----
             - change "f", but keep size                    N
      N+1
             - release wlock
               - 'dirstate.write()'             -1   -1
    
           - 'hg status'                        -1   -1      N
      ---- ----------------------------------- ---- ----- -----
    
    To reproduce this issue in tests certainly, this patch emulates some
    timing critical actions as below:
    
      - timestamp of "f" in '.hg/dirstate' is -1 at the beginning
    
        'hg debugrebuildstate' before command invocation ensures it.
    
      - make file "f" clean at N
      - change "f" at N
    
        'touch -t 200001010000' before and after command invocation
        changes mtime of "f" to "2000-01-01 00:00" (= N).
    
      - invoke 'dirstate.write()' via 'repo.status()' at N
    
        'fakedirstatewritetime.py' forces 'pack_dirstate()' to use
        "2000-01-01 00:00" as "now", only if 'pack_dirstate()' is invoked
        via 'workingctx._checklookup()'.
    
      - invoke 'dirstate.write()' via releasing wlock at N+1 (or "not at N")
    
        'pack_dirstate()' via releasing wlock uses actual timestamp at
        runtime as "now", and it should be different from the "2000-01-01
        00:00" of "f".
    
    BTW, this patch also changes 'test-largefiles-misc.t', because adding
    'dirstate.write()' makes recent dirstate changes visible to external
    process.
test-subrepo.t 44.70 KiB