Change:
<!-- Thank you for filing a bug report! Please provide a short summary of the bug, along with any information you feel relevant to replicating the bug. --I tried : I expected to see this happen: Different hashes since these represent different paths. Instead, this happened: The hashes are equal. This makes sense for redundant path separators (e.g. ), but not for all path separators. I a test case to the standard library — which fails. The relevant code is probably around which last changed in If I revert that commit, the error persists. It looks like the relevant change is , if I revert that commit the test passes. This was the solution I opted for downstream (in ) before performing this investigation. I tested this on stable (1.79.0), nightly (1.81.0), and on (). Discovered via
is not a cryptographic hash, so it does not promise a collision probability so low that practically all inputs map to distinct outputs. So if the correctness of your program relies on the absence of collisions then it's your program that's wrong, not . There may be some quality-of-implementation argument about avoiding collisions for data patterns that are likely to coexist. I'm not sure if it is the case here, I'll have to think about it. And even if it is then there also is the matter of making such a avoidance fast. The current code is optimized for speed.
I think that's a fair point, but the current implementation already goes out if its way to create equal hashes for paths like and — why is and different? It seems like a very surprising collision for a user to encounter. Thanks for taking a look!
The difference is that is a companion for . If things compare equal then they must hash the same because that's what hashmaps rely on. and do compare equally because normalizes the dot away. Therefore they must hash equally, which is why Hash must spend effort on that. Things comparing inequal does not require the Hash to be different. Instead it's only a matter of quality-of-implementation whether they produce distinct hashes, not a matter of correctness.
That's a great point, thanks for clarifying. I agree this is not a "correctness" problem, but more of a practical consideration for the hashing algorithm.

Discussion:
let bytes = &bytes[prefix_len..]; let mut component_start = 0; <del> let mut bytes_hashed = 0; </del> <ins> // track some extra state to avoid prefix collisions. // ["foo", "bar"] and ["foobar"], will have the same payload bytes // but result in different chunk_bits let mut chunk_bits: usize = 0; </ins> for i in 0..bytes.len() { let is_sep = if verbatim { is_verbatim_sep(bytes[i]) } else { is_sep_byte(bytes[i]) }; if is_sep { if i > component_start { let to_hash = &bytes[component_start..i]; <ins> chunk_bits = chunk_bits.wrapping_add(to_hash.len()); chunk_bits = chunk_bits.rotate_right(2); </ins> h.write(to_hash); <del> bytes_hashed += to_hash.len(); </del> } // skip over separator and optionally a following CurDir item

if component_start < bytes.len() { let to_hash = &bytes[component_start..]; <ins> chunk_bits = chunk_bits.wrapping_add(to_hash.len()); chunk_bits = chunk_bits.rotate_right(2); </ins> h.write(to_hash); <del> bytes_hashed += to_hash.len(); </del> } <del> h.write_usize(bytes_hashed); </del> <ins> h.write_usize(chunk_bits); </ins> } }

relative_from: Some("") ); <ins> tc!("foo//", "foo", eq: true, starts_with: true, ends_with: true, relative_from: Some("") ); tc!("foo///", "foo", eq: true, starts_with: true, ends_with: true, relative_from: Some("") ); </ins> tc!("foo/.", "foo", eq: true, starts_with: true,

relative_from: Some("") ); <ins> tc!("foo/.//bar", "foo/bar", eq: true, starts_with: true, ends_with: true, relative_from: Some("") ); tc!("foo//./bar", "foo/bar", eq: true, starts_with: true, ends_with: true, relative_from: Some("") ); </ins> tc!("foo/bar", "foo", eq: false, starts_with: true,

relative_from: Some("bar") ); <ins> tc!("foo/bar", "foobar", eq: false, starts_with: false, ends_with: false, relative_from: None ); </ins> tc!("foo/bar/baz", "foo/bar", eq: false, starts_with: true,