@page "/git/{Repository}/commit/{Sha}" @inherits GitPage @Title — git
@if (View is not { } view) {

No commit @Sha in @Repository.

} else {

@view.Commit.Summary @foreach (var name in view.Commit.Refs) { @name }

@if (view.Commit.Body is { Length: > 0 } body) {

@body

}
author
@view.Commit.Author.Name <@view.Commit.Author.Email> · @Exact(view.Commit.Author.When)
@if (Differs(view.Commit)) {
committer
@view.Commit.Committer.Name <@view.Commit.Committer.Email> · @Exact(view.Commit.Committer.When)
}
commit
@view.Commit.Sha
@if (view.Commit.Parents.Count > 0) {
parent@(view.Commit.Parents.Count == 1 ? "" : "s")
@foreach (var parent in view.Commit.Parents) { @Shorten(parent) }
}
tree
browse at this commit

@Changed(view.Diff) +@view.Diff.Added -@view.Diff.Deleted

@if (view.Diff.Files.Count == 0) {

@(view.Commit.IsMerge ? "Nothing changed against the first parent — the other side brought it all." : "No changes.")

} else {
@for (var index = 0; index < view.Diff.Files.Count; index++) { var file = view.Diff.Files[index]; }
@file.Path @ChangeLabel(file.Change) +@file.Added -@file.Deleted
} @if (view.Diff.Truncated) {

This diff is longer than one page will show. The files below it keep their counts; read them at this commit's tree instead.

}
@for (var index = 0; index < view.Diff.Files.Count; index++) { var file = view.Diff.Files[index]; var grammar = Highlighted ? SyntaxHighlighter.GrammarFor(file.Path) : null;

@if (file.OldPath is { } old) { @old → } @file.Path +@file.Added -@file.Deleted

@if (file.IsBinary) {

Binary file. Download it as it was here.

} else if (file.Skipped) {

Too far down a long diff to render. @file.Added added, @file.Deleted removed.

} else if (file.Hunks.Count == 0) {

@(ChangeLabel(file.Change) is { Length: > 0 } label ? label : "No textual change") — no lines changed.

} else {
@foreach (var hunk in file.Hunks) { var tokens = Highlight(hunk, grammar); @for (var row = 0; row < hunk.Lines.Count; row++) { var line = hunk.Lines[row]; @if (line.Origin == '\\') { } else { } } }
@hunk.Header
@line.Text
@line.OldNumber @line.NewNumber
}
} }
@code { /// /// A diff longer than this is shown without highlighting. Every token in a highlighted line /// becomes a span, which is several times the length of the line itself, and a diff of /// machine-generated markup is almost entirely tokens — the commit that vendored the Warframe /// drop tables turned a 0.5 MB page into a 2.3 MB one. Nobody is reading a diff that size /// line by line, so it keeps its numbers and loses its spans. /// private const int MaxHighlightCharacters = 60_000; [Parameter] public string Sha { get; set; } = ""; private GitCommitView? View { get; set; } /// Whether this diff is small enough to be worth marking up. See above. private bool Highlighted { get; set; } private string Short => Shorten(View?.Commit.Sha ?? Sha); private string Title => View is { } view ? $"{view.Commit.Summary} · {Repository}" : Repository; protected override void OnParametersSet() { View = Service.GetCommit(Repository, Sha); if (View is null) { NotFound(); return; } Highlighted = View.Diff.Files .Sum(file => file.Hunks.Sum(hunk => hunk.Lines.Sum(line => line.Text.Length))) <= MaxHighlightCharacters; } private static string Shorten(string sha) => sha.Length >= 10 ? sha[..10] : sha; /// A committer line worth printing is one that says something the author's didn't. private static bool Differs(GitCommitInfo commit) => commit.Committer.Email != commit.Author.Email || commit.Committer.When != commit.Author.When; private static string Changed(GitDiff diff) => diff.Files.Count == 1 ? "1 file changed" : $"{diff.Files.Count} files changed"; private static string Anchor(int index) => $"f{index}"; /// Tokens for every line of a hunk, in the order the hunk lists them. /// /// A diff line on its own is not enough to highlight with. A block comment or a template /// literal that opened three lines above it would go unnoticed, and the prose inside it would /// come back out as keywords. /// /// A hunk is enough, because a hunk is two contiguous runs of text: the lines the new file has /// (context and added) and the lines the old file had (context and removed). Each side is /// highlighted whole and each line takes its share back. Context lines belong to both, so the /// new side runs second and wins — the text is the same either way. /// /// A comment that opened before the hunk began is still lost. Git did not send those lines, /// so there is nothing here that could know about it. /// private static IReadOnlyList> Highlight(GitDiffHunk hunk, SyntaxGrammar? grammar) { var tokens = new IReadOnlyList[hunk.Lines.Count]; Array.Fill(tokens, []); Side('-'); Side('+'); return tokens; void Side(char origin) { var rows = Enumerable.Range(0, hunk.Lines.Count) .Where(row => hunk.Lines[row].Origin == origin || hunk.Lines[row].Origin == ' ') .ToArray(); var lines = SyntaxHighlighter.Highlight( string.Join('\n', rows.Select(row => hunk.Lines[row].Text)), grammar); for (var row = 0; row < rows.Length && row < lines.Count; row++) tokens[rows[row]] = lines[row]; } } private static string Row(char origin) => origin switch { '+' => "add", '-' => "del", _ => "ctx" }; }