Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions src/DiffEngine.Tests/TrackedKeyCaseTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/// <summary>
/// Tracked keys fold case exactly where inline keys do.
/// <para>
/// TrackedKeys lower-cased unconditionally while InlineKey folds only on Windows and macOS, and
/// InlineKey says why: on Linux two paths differing only in case are two files, and giving them
/// one key means the second takes over the first's entry. For a tracked move that shows up as
/// ViewerSession.EnqueueTracked dropping one of them - a parameterised test with value=a and
/// value=A silently loses a pending file.
/// </para>
/// <para>
/// Asserted as agreement between the two rather than as a platform's answer, so it holds wherever
/// it runs.
/// </para>
/// </summary>
public class TrackedKeyCaseTests
{
const string upper = "/repo/tests/Value.txt";
const string lower = "/repo/tests/value.txt";

[Test]
public async Task MoveKeysFoldWithInlineKeys()
{
var inlineFolds = InlineKey.For(upper, 1) == InlineKey.For(lower, 1);

await Assert.That(TrackedKeys.ForMove(upper) == TrackedKeys.ForMove(lower))
.IsEqualTo(inlineFolds);
}

[Test]
public async Task DeleteKeysFoldWithInlineKeys()
{
var inlineFolds = InlineKey.For(upper, 1) == InlineKey.For(lower, 1);

await Assert.That(TrackedKeys.ForDelete(upper) == TrackedKeys.ForDelete(lower))
.IsEqualTo(inlineFolds);
}
}
9 changes: 8 additions & 1 deletion src/DiffEngine/Inline/InlineKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,14 @@ public static class InlineKey
/// </para>
/// </summary>
public static string For(string sourceFile, int line) =>
$"{(caseInsensitivePaths ? sourceFile.ToLowerInvariant() : sourceFile)}|{line}";
$"{FoldPath(sourceFile)}|{line}";

/// <summary>
/// A path folded the way this machine's file system folds it, so anything else keyed by path
/// agrees with <see cref="For" /> about when two paths are one file.
/// </summary>
internal static string FoldPath(string path) =>
caseInsensitivePaths ? path.ToLowerInvariant() : path;

static readonly bool caseInsensitivePaths =
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ||
Expand Down
14 changes: 10 additions & 4 deletions src/DiffEngine/Protocol/TrackedKeys.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@ namespace DiffEngine;

/// <summary>
/// Keys for the tray's tracked moves and deletes when they ride the inline listing. Prefixed so
/// they cannot collide with an inline key — a lower-cased path plus "|" plus a line number, where
/// a Windows path cannot put a colon at index four — and so only the tray has to know which
/// they cannot collide with an inline key — a folded path plus "|" plus a line number, where a
/// Windows path cannot put a colon at index four — and so only the tray has to know which
/// collection a key belongs to. Every other process echoes keys back opaquely.
/// <para>
/// Folded through <see cref="InlineKey.FoldPath" />, which folds only where the file system does.
/// Lower-casing unconditionally made two Linux files differing only in case - which is two files,
/// not one - share a key, and the viewer's enqueue drops the earlier of two entries with the same
/// key. A parameterised test producing value=a and value=A lost one of them.
/// </para>
/// </summary>
static class TrackedKeys
{
Expand All @@ -13,10 +19,10 @@ static class TrackedKeys
public const string DeletePrefix = "delete:";

public static string ForMove(string temp) =>
MovePrefix + temp.ToLowerInvariant();
MovePrefix + InlineKey.FoldPath(temp);

public static string ForDelete(string file) =>
DeletePrefix + file.ToLowerInvariant();
DeletePrefix + InlineKey.FoldPath(file);

public static bool IsTracked(string key) =>
key.StartsWith(MovePrefix, StringComparison.Ordinal) ||
Expand Down
Loading