Skip to content

Commit 82a5093

Browse files
committed
Always label gist MRU entries as owner/shortsha:file
Avoid ambiguous owner/filename labels in the picker by always including the short gist id.
1 parent 7384e5b commit 82a5093

3 files changed

Lines changed: 35 additions & 94 deletions

File tree

readme.md

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,8 @@ With an empty history, or when stdin is redirected / non-interactive (CI, pipes)
5454
Local paths that no longer exist are dropped from the picker; remote refs stay listed
5555
even when their download bundle is gone (the next run re-downloads as usual).
5656

57-
Gists are labeled like GitHub’s UI as `owner/filename` (for example `kzu/run.cs`)
58-
instead of the full host URL. When two or more history entries share the same
59-
owner and file name, the picker switches to a short ref form with the first
60-
seven characters of the gist id: `owner/shortsha:file` (for example
61-
`kzu/0ac826d:run.cs`).
57+
Gists are labeled as `owner/shortsha:file` (first seven characters of the gist id),
58+
for example `kzu/0ac826d:run.cs`, instead of the full host URL.
6259

6360
```console
6461
# Interactive: pick a previous run, then optional args

src/Tests/RunHistoryTests.cs

Lines changed: 22 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ public void ParseArgsLine_roundtrips_via_ToRunInputs_as_shipped_path()
316316
}
317317

318318
[Fact]
319-
public void List_formats_gists_as_owner_filename_from_path_suffix()
319+
public void List_formats_gists_as_owner_shortsha_file_from_path_suffix()
320320
{
321321
var path = NewSettingsPath();
322322
try
@@ -342,7 +342,7 @@ public void List_formats_gists_as_owner_filename_from_path_suffix()
342342

343343
var listed = RunHistory.List(path);
344344
var gist = listed.Single(e => e.Input.Contains("0ac826dc", StringComparison.Ordinal));
345-
Assert.Equal("kzu/run.cs", gist.Display);
345+
Assert.Equal("kzu/0ac826d:run.cs", gist.Display);
346346

347347
var repo = listed.Single(e => e.Input == "kzu/sandbox");
348348
Assert.Equal("kzu/sandbox", repo.Display);
@@ -354,7 +354,7 @@ public void List_formats_gists_as_owner_filename_from_path_suffix()
354354
}
355355

356356
[Fact]
357-
public void List_formats_gists_as_owner_filename_from_cached_entry()
357+
public void List_formats_gists_as_owner_shortsha_file_from_cached_entry()
358358
{
359359
var path = NewSettingsPath();
360360
try
@@ -375,7 +375,7 @@ public void List_formats_gists_as_owner_filename_from_cached_entry()
375375

376376
var listed = RunHistory.List(path);
377377
Assert.Single(listed);
378-
Assert.Equal("kzu/hello.cs", listed[0].Display);
378+
Assert.Equal("kzu/aaaaaaa:hello.cs", listed[0].Display);
379379
}
380380
finally
381381
{
@@ -384,7 +384,7 @@ public void List_formats_gists_as_owner_filename_from_cached_entry()
384384
}
385385

386386
[Fact]
387-
public void List_disambiguates_same_owner_file_with_short_gist_id()
387+
public void List_always_includes_short_gist_id_even_for_unique_owner_file()
388388
{
389389
var path = NewSettingsPath();
390390
try
@@ -424,8 +424,7 @@ public void List_disambiguates_same_owner_file_with_short_gist_id()
424424

425425
Assert.Equal("kzu/aaaaaaa:run.cs", kzuA.Display);
426426
Assert.Equal("kzu/bbbbbbb:run.cs", kzuB.Display);
427-
// Different owner — no disambiguation needed even though file name matches.
428-
Assert.Equal("other/run.cs", other.Display);
427+
Assert.Equal("other/ccccccc:run.cs", other.Display);
429428
}
430429
finally
431430
{
@@ -464,34 +463,24 @@ public void List_gist_without_known_file_keeps_raw_input()
464463
}
465464

466465
[Fact]
467-
public void FormatBaseDisplay_and_ShortId_helpers()
466+
public void FormatDisplay_and_ShortId_helpers()
468467
{
469468
Assert.Equal("aaaaaaa", RunHistory.ShortId("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
470469
Assert.Equal("short", RunHistory.ShortId("short"));
471470

472-
var withPath = RunHistory.FormatBaseDisplay(
473-
"gist.github.com/kzu/0ac826dc7de666546aaedd38e5965381:app.cs",
474-
cachedEntry: null,
475-
out var owner, out var gistId, out var file);
476-
Assert.Equal("kzu/app.cs", withPath);
477-
Assert.Equal("kzu", owner);
478-
Assert.Equal("0ac826dc7de666546aaedd38e5965381", gistId);
479-
Assert.Equal("app.cs", file);
480-
481-
var withCache = RunHistory.FormatBaseDisplay(
482-
"gist.github.com/kzu/0ac826dc7de666546aaedd38e5965381",
483-
cachedEntry: "from-cache.cs",
484-
out owner, out gistId, out file);
485-
Assert.Equal("kzu/from-cache.cs", withCache);
486-
Assert.Equal("kzu", owner);
487-
Assert.Equal("0ac826dc7de666546aaedd38e5965381", gistId);
488-
Assert.Equal("from-cache.cs", file);
489-
490-
var repo = RunHistory.FormatBaseDisplay("kzu/sandbox", null, out owner, out gistId, out file);
491-
Assert.Equal("kzu/sandbox", repo);
492-
Assert.Null(owner);
493-
Assert.Null(gistId);
494-
Assert.Null(file);
471+
Assert.Equal(
472+
"kzu/0ac826d:app.cs",
473+
RunHistory.FormatDisplay(
474+
"gist.github.com/kzu/0ac826dc7de666546aaedd38e5965381:app.cs",
475+
cachedEntry: null));
476+
477+
Assert.Equal(
478+
"kzu/0ac826d:from-cache.cs",
479+
RunHistory.FormatDisplay(
480+
"gist.github.com/kzu/0ac826dc7de666546aaedd38e5965381",
481+
cachedEntry: "from-cache.cs"));
482+
483+
Assert.Equal("kzu/sandbox", RunHistory.FormatDisplay("kzu/sandbox", null));
495484
}
496485

497486
[Fact]
@@ -507,7 +496,7 @@ public void Record_captures_gist_entry_from_path_suffix()
507496
Assert.Equal("run.cs", settings.History![0].Entry);
508497

509498
var listed = RunHistory.List(path);
510-
Assert.Equal("kzu/run.cs", listed[0].Display);
499+
Assert.Equal("kzu/0ac826d:run.cs", listed[0].Display);
511500
}
512501
finally
513502
{
@@ -537,7 +526,7 @@ public void List_formats_https_gist_url_with_cached_entry()
537526

538527
var listed = RunHistory.List(path);
539528
Assert.Single(listed);
540-
Assert.Equal("kzu/run.cs", listed[0].Display);
529+
Assert.Equal("kzu/0ac826d:run.cs", listed[0].Display);
541530
}
542531
finally
543532
{

src/go/RunHistory.cs

Lines changed: 11 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace Devlooped;
55
/// <summary>A previously run go entry from root <c>go.toml</c> history.</summary>
66
public record RunHistoryEntry(string Input, DateTimeOffset LastUsedUtc, int UseCount)
77
{
8-
/// <summary>Display label for the picker (forward slashes for paths; gists as owner/file).</summary>
8+
/// <summary>Display label for the picker (forward slashes for paths; gists as owner/shortsha:file).</summary>
99
public string Display { get; init; } = Input.Replace('\\', '/');
1010
}
1111

@@ -28,8 +28,7 @@ public static IReadOnlyList<RunHistoryEntry> List(string? settingsPath = null)
2828
return [];
2929

3030
var pruned = false;
31-
// BaseDisplay is owner/file for gists when known; Gist* fields support collision disambiguation.
32-
var pending = new List<(string Input, DateTimeOffset LastUsedUtc, int UseCount, string BaseDisplay, string? GistOwner, string? GistId, string? GistFile)>(settings.History.Count);
31+
var listed = new List<RunHistoryEntry>(settings.History.Count);
3332
for (var i = settings.History.Count - 1; i >= 0; i--)
3433
{
3534
var h = settings.History[i];
@@ -40,48 +39,18 @@ public static IReadOnlyList<RunHistoryEntry> List(string? settingsPath = null)
4039
continue;
4140
}
4241

43-
var baseDisplay = FormatBaseDisplay(h.Input, h.Entry, out var gistOwner, out var gistId, out var gistFile);
44-
pending.Add((
42+
listed.Add(new RunHistoryEntry(
4543
h.Input,
4644
h.LastUsedUtc == default ? DateTimeOffset.MinValue : h.LastUsedUtc,
47-
h.UseCount <= 0 ? 1 : h.UseCount,
48-
baseDisplay,
49-
gistOwner,
50-
gistId,
51-
gistFile));
45+
h.UseCount <= 0 ? 1 : h.UseCount)
46+
{
47+
Display = FormatDisplay(h.Input, h.Entry),
48+
});
5249
}
5350

5451
if (pruned)
5552
SettingsStore.Save(settings, settingsPath);
5653

57-
// Disambiguate gist labels that share the same owner/file with a short gist id.
58-
var gistLabelCounts = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
59-
foreach (var item in pending)
60-
{
61-
if (item.GistId is null)
62-
continue;
63-
gistLabelCounts[item.BaseDisplay] = gistLabelCounts.GetValueOrDefault(item.BaseDisplay) + 1;
64-
}
65-
66-
var listed = new List<RunHistoryEntry>(pending.Count);
67-
foreach (var item in pending)
68-
{
69-
var display = item.BaseDisplay;
70-
// Colliding owner/file gists → owner/shortsha:file (ref-like, familiar).
71-
if (item.GistId is { } id
72-
&& item.GistFile is { } file
73-
&& gistLabelCounts.TryGetValue(item.BaseDisplay, out var count)
74-
&& count > 1)
75-
{
76-
display = $"{item.GistOwner}/{ShortId(id)}:{file}";
77-
}
78-
79-
listed.Add(new RunHistoryEntry(item.Input, item.LastUsedUtc, item.UseCount)
80-
{
81-
Display = display,
82-
});
83-
}
84-
8554
listed.Sort(static (a, b) =>
8655
{
8756
var byCount = b.UseCount.CompareTo(a.UseCount);
@@ -297,31 +266,17 @@ public static string NormalizeInput(string input)
297266
}
298267

299268
/// <summary>
300-
/// Builds the base picker label. Gists become <c>owner/filename</c> when the file is known
269+
/// Builds the picker label. Gists use <c>owner/shortsha:file</c> when the file is known
301270
/// (explicit <c>:path</c>, cached history entry, or live download bundle); otherwise the
302-
/// normalized input. When a gist label is produced, owner/id/file outs are set for
303-
/// collision disambiguation as <c>owner/shortsha:file</c>.
271+
/// normalized input.
304272
/// </summary>
305-
internal static string FormatBaseDisplay(
306-
string input,
307-
string? cachedEntry,
308-
out string? gistOwner,
309-
out string? gistId,
310-
out string? gistFile)
273+
internal static string FormatDisplay(string input, string? cachedEntry)
311274
{
312-
gistOwner = null;
313-
gistId = null;
314-
gistFile = null;
315275
if (TryGetGistParts(input, out var owner, out var id, out var remote))
316276
{
317277
var file = ResolveGistFileName(remote, cachedEntry);
318278
if (file is not null)
319-
{
320-
gistOwner = owner;
321-
gistId = id;
322-
gistFile = file;
323-
return $"{owner}/{file}";
324-
}
279+
return $"{owner}/{ShortId(id)}:{file}";
325280
}
326281

327282
return input.Replace('\\', '/');

0 commit comments

Comments
 (0)