On Mon, Jun 28, 2021 at 2:50 AM Raphaël Gomès <raphael.go...@octobus.net>
wrote:

> Hello all,
>
> As you probably know my colleagues at Octobus and I have been working on
> a new version of the dirstate, and we're coming pretty close to
> something usable in production, so we need to freeze the format soon.
> This email is not meant to discuss the exact byte-per-byte layout
> details of the format, but rather its contents: what do you think should
> be included (or at least have space reserved for) in the new version?
>
> We have already discussed this at previous sprints and various other
> discussion channels, but I thought it'd be better to give a "last call"
> chance for people to get their voices heard.
>
> I remember Google people saying they'd like to separate information that
> is frequently written to a separate file to help with their filesystem
> shenanigans. What exactly would be the plan and can we do it easily? I
> may be pessimistic, but this looks like it would require a lot of work
> which (so far) no one wants to sponsor, though I'm happy to be proven
> wrong either way.
>

The original thinking had been that we'd have two or three files:
1. p1/p2
2. anything the user did (`hg mv/cp/add/rm`)
3. anything hg can generate in `hg debugrebuilddirstate`

The thinking was that #3 could either be generated by the filesystem
itself, or if there was a network write race (when using filesystems like
our internal CitC filesystem, or maybe with things like NFS, if it can
determine write races, I'm honestly not sure...) it could either just let
one side win arbitrarily.

After learning more, neither of those really work for us. Our virtual
filesystem is "dumb" - it honestly knows very little about the files it's
being asked to store, so it would be a huge change to have it track enough
information to feasibly produce something that could replace the data in #3
above. Additionally, in the network write race scenario, letting one side
win arbitrarily just opens you up to dirstate corruption, which is not a
place anyone wants to be in. :) In the network write race case, we could
teach the virtual filesystem server to delete/poison the file (triggering a
dirstate rebuild on the next command), but it's probably not worthwhile at
this point.

I was then thinking that we could just store #3 "where it belongs", in
.hg/wcache, and just not replicate it. That still opens you up for dirstate
corruption issues (modify the working directory on machine A, and then use
it on machine B - we still need some way of telling machine B it's out of
date; that could be a timestamp in the non-cache part of the dirstate, I
guess?).


> To Matt Harbison: you said something about storing exec bit and symlink
> info explicitly to help platforms like Windows that don't have them,
> could you please elaborate?
>
> As a general recap (and to help understand some decisions), the new
> format will be an append-only tree with no stem compression for
> performance reasons.


Can you elaborate a bit on what this append-only tree looks like (and why
that's preferred) and why stem compression would cause performance issues?

When loading this new dirstate, would it require loading the entire thing
from the beginning and replacing entries with the newer ones? IMHO, we
should be optimizing as much as possible for the read performance, even if
it costs some small amount of write performance. Writes seem less frequent
to me (and more tolerant of slightly higher latency) than things like `hg
status` (being executed by an IDE, or by someone in `watch`, or in a shell
prompt, or something...)


> The Python implementation will be functional but
> very basic and will offer no purposeful performance improvements (unless
> someone wants to have fun!), as we currently only have the bandwidth for
> optimizing the Rust implementation.
>

You say the Python implementation will offer no purposeful performance
improvements, but how likely is it that it will be slower than the current
format? What level of performance degradation would be considered
acceptable?


>
> An overview of the current target (some implementation-detail level
> contents omitted):
>
>      - A docket file that contains global metadata about the dirstate:
>

What happens if the docket and data file get out of sync somehow (maybe hg
crashes in the middle of writing, or Google has a network write race)?


>          - NodeID of the parents (32 bytes reserved, 20 used for now)
>          - A total count of files (including Removed ones)

         - A count of dead (unreachable) bytes
>          - A count of alive (reachable) bytes
>

What are these two?


>          - A hash of ignore patterns (see
> https://phab.mercurial-scm.org/D10836)

     - In the data file, for each directory/file (it can be both at the
> same time):
>          - The full path in bytes of the file (or directory)
>          - The full path of the copy source (optional)
>          - How many tracked recursive descendants it has
>          - How many recursive copies it has
>          - Exec bit
>          - mtime (probably up to nanosecond precision, both files and
> directories)
>

Is there a good way of determining what the timestamp resolution of a
filesystem is? (I can google it, of course, but that doesn't help us
determine it programmatically). Having it able to store nanosecond
precision seems good even if we don't have a way of reliably obtaining the
information such that we can be confident that we're actually getting
something more precise than 1s? (I don't know how various OSes treat these
timestamps when the underlying filesystem doesn't support higher precision;
is it 100% guaranteed that they just extend it with zeroes?)


>          - Clean file size when applicable
>          - Its state: if it's removed, added, clean, etc.
>          - Whether it's from p1 or p2
>          - Whether it's ambiguous (it appears clean but the mtime is the
> same as the last status, probably will only happen with the Python
> implementation)
>          - All of the info needed to get the previous state of a Removed
> file in case we `hg add` it back
>

Can you explain the use case for this (and/or what would be in it)? I would
think that `hg rm foo && echo hi > foo && hg add foo` should be equivalent
to `echo hi > foo`, but I might be missing something?


>          - (My idea as I type this: ) store the "raw bytes" version of
> the OS path if it differs from the normalized hg version (on Windows and
> MacOS for example) to cache the filefoldmap.
>

It might be useful to describe what's in the current dirstate for
comparison. I believe it's:
- p1, p2 (20 byte hashes, 40 bytes total)
- for each *file*:
  - file name (bytes)
  - mtime (1s precision); this is -1 if it's unknown/ambiguous
  - state (normal/add/remove/etc.)
  - copy info (just path? not sure what's in that)

So the following would be new:
- tracking directories
- count of recursive descendants
- count of recursive copies
- exec bit
- mtime with increased precision
- whether it's from p1 or p2
- whether it's ambiguous
- info to recover 'Removed' file state
- "raw bytes" version of the OS path if it differs


> I *think* that's it? I might be wrong, if so, please tell me!
>

My biggest concern is extensibility. As an example, as you were writing
this up, you thought of something else to add, so we probably don't want to
restrict ourselves too much :) The file format is already going to not be
anything resembling fixed record size, having a section for generic
key/value data that extensions can use might be quite useful (and maybe
future core code, though I'm assuming the format can be such that this
would be able to be made to work without the size/parsing complexity of
key/value).

Random things that might make sense to keep in dirstate (I don't have any
real uses for them right now, just thinking what might be desired now or in
the indefinite future):
- some merge conflict information (just a bit saying a file is in a merge
conflict state?)
  - somewhat similar, for cases where hg generates files such as .orig,
.rej, etc., maybe we store these in the dirstate marked as a new state
("generated"? "internal"?). `hg status` could show them using a new
character, and there could be a command (`hg resolve --cleanup`?) that
deletes these files. Right now I believe people generally add these file
names to the ignored pattern, but that's caused us some problems with
overly generic globs in build files.
- information that might be useful for interacting with other VCSes. This
would almost certainly be done via Mercurial extension modules and thus
relegated to the generic key/value store, but as a specific example: when
interfacing with a Perforce repository (
https://www.mercurial-scm.org/wiki/PerfarceExtension), it might be useful
to be able to keep track of the Perforce file type (
https://www.perforce.com/manuals/v15.1/p4guide/appendix.filetypes.html), if
it's non-obvious (something like symlink would be "obvious", some of the
modifiers [specifically the exec bit] are tracked elsewhere, but there's no
way of storing that you want the file to be 'binary', for example).
- (really unsure): case collision information?
- (unsure): some information about files being "smeared", maybe just a list
of filters applied? Things like the EOL extension (
https://www.mercurial-scm.org/wiki/EolExtension) could possibly use it.
- Is there anything LFS would want to put in here?
- Is there anything fsmonitor would want to add?
- Would it make sense to put the "active" bookmark into the dirstate?
Active topic? Active branch? (I'm leaning towards no for all three, but
figured I should mention them :))



> Raphaël
>
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel@mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
>
_______________________________________________
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel

Reply via email to