diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9f7f0d2..921f01a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,7 +50,7 @@ jobs: release_name="GitTimelapseView-$tag-${{ matrix.target }}" # Build everything - dotnet publish GitTimelapseView/GitTimelapseView.csproj --framework net6.0-windows --runtime "${{ matrix.target }}" -c Release -o "$release_name" + dotnet publish GitTimelapseView/GitTimelapseView.csproj --framework net6.0-windows --runtime "${{ matrix.target }}" -c Release -o "$release_name" --self-contained # Pack files if [ "${{ matrix.target }}" == "win-x64" ]; then diff --git a/GitTimelapseView.Core/Common/GitHelpers.cs b/GitTimelapseView.Core/Common/GitHelpers.cs index 8f676b7..c76bb13 100644 --- a/GitTimelapseView.Core/Common/GitHelpers.cs +++ b/GitTimelapseView.Core/Common/GitHelpers.cs @@ -49,6 +49,20 @@ public static string RunGitCommand(string gitRootPath, string args, ILogger logg return gitProcess.StandardOutput.ReadToEnd().Trim(); } + public static string? GetRemotePlatform(string remoteUrl) + { + if (remoteUrl.Contains("github.com", StringComparison.OrdinalIgnoreCase)) + { + return "GitHub"; + } + else if (remoteUrl.Contains("gitlab", StringComparison.OrdinalIgnoreCase)) + { + return "GitLab"; + } + + return null; + } + internal static IReadOnlyList GetCommitFileLines(this Repository repository, string relativeFilePath, string sha) { var commit = repository.Lookup(sha); @@ -79,6 +93,40 @@ internal static IReadOnlyList GetCommitFileLines(this Repository reposit return relRoot.MakeRelativeUri(fullPath).ToString(); } + internal static string? FindRemoteUrl(this Repository repository) + { + if (repository.Network.Remotes.Any()) + { + var remote = repository.Network.Remotes.First(); + var url = remote.Url; + if (url.EndsWith(".git", StringComparison.Ordinal)) + { + if (url.StartsWith("git", StringComparison.Ordinal)) + { + url = url.Replace(":", "/", StringComparison.Ordinal).Replace("git@", "https://", StringComparison.Ordinal); + } + + return url.Replace(".git", string.Empty, StringComparison.Ordinal).TrimEnd('/'); + } + } + + return null; + } + + internal static string? GetCommitUrl(string remoteUrl, string sha) + { + if (remoteUrl.Contains("github.com", StringComparison.OrdinalIgnoreCase)) + { + return $"{remoteUrl}/commit/{sha}"; + } + else if (remoteUrl.Contains("gitlab", StringComparison.OrdinalIgnoreCase)) + { + return $"{remoteUrl}/-/commit/{sha}"; + } + + return null; + } + private static void HandleGitCommandErrors(object? sender, ILogger logger, string? onGitErrorMessage = null) { if (sender is Process gitProcess) diff --git a/GitTimelapseView.Core/Models/BlameBlock.cs b/GitTimelapseView.Core/Models/BlameBlock.cs index a08ac47..f5b4c2e 100644 --- a/GitTimelapseView.Core/Models/BlameBlock.cs +++ b/GitTimelapseView.Core/Models/BlameBlock.cs @@ -6,12 +6,12 @@ namespace GitTimelapseView.Core.Models { public class BlameBlock { - public BlameBlock(BlameHunk block, FileRevision fileRevision, IReadOnlyList lines) + public BlameBlock(BlameHunk block, FileRevision fileRevision, IReadOnlyList lines, string? remoteUrl) { InitialSignature = block.InitialSignature; FinalSignature = block.FinalSignature; - InitialCommit = new Commit(block.InitialCommit, fileRevision.FileHistory); - FinalCommit = new Commit(block.FinalCommit, fileRevision.FileHistory); + InitialCommit = new Commit(block.InitialCommit, fileRevision.FileHistory, remoteUrl); + FinalCommit = new Commit(block.FinalCommit, fileRevision.FileHistory, remoteUrl); StartLine = block.FinalStartLineNumber + 1; LineCount = block.LineCount; FileRevision = fileRevision.FileHistory.GetRevisionPerCommit(FinalCommit); diff --git a/GitTimelapseView.Core/Models/Commit.cs b/GitTimelapseView.Core/Models/Commit.cs index 95f9ca5..5575389 100644 --- a/GitTimelapseView.Core/Models/Commit.cs +++ b/GitTimelapseView.Core/Models/Commit.cs @@ -12,7 +12,7 @@ public class Commit { private readonly List _fileChanges = new(); - public Commit(LibGit2Sharp.Commit commit, FileHistory fileHistory) + public Commit(LibGit2Sharp.Commit commit, FileHistory fileHistory, string? remoteUrl) { Message = commit.Message; MessageShort = commit.MessageShort; @@ -22,6 +22,10 @@ public Commit(LibGit2Sharp.Commit commit, FileHistory fileHistory) Committer = commit.Committer; FileHistory = fileHistory; Parents = commit.Parents.Select(x => x.Sha).ToArray(); + if (remoteUrl != null) + { + WebUrl = GitHelpers.GetCommitUrl(remoteUrl, commit.Sha); + } } public string Message { get; init; } @@ -44,6 +48,8 @@ public Commit(LibGit2Sharp.Commit commit, FileHistory fileHistory) public string? ContainedInTag { get; private set; } + public string? WebUrl { get; } + public void UpdateInfo(ILogger logger) { if (FileChanges.Any()) diff --git a/GitTimelapseView.Core/Models/FileHistory.cs b/GitTimelapseView.Core/Models/FileHistory.cs index facc19a..04e7def 100644 --- a/GitTimelapseView.Core/Models/FileHistory.cs +++ b/GitTimelapseView.Core/Models/FileHistory.cs @@ -49,6 +49,8 @@ public void Initialize(ILogger logger) var commitIds = GetFileCommitIDs(logger).Reverse().ToArray(); using (var repository = new Repository(GitRootPath)) { + var remoteUrl = repository.FindRemoteUrl(); + var relativeFilePath = repository.MakeRelativeFilePath(FilePath); if (relativeFilePath == null) throw new Exception($"Unable to blame '{FilePath}'. Path is not located in the repository working directory."); @@ -57,7 +59,7 @@ public void Initialize(ILogger logger) { var commitId = commitIds[index]; var commit = repository.Lookup(commitId); - _revisions.Add(new FileRevision(index, new Commit(commit, this), this)); + _revisions.Add(new FileRevision(index, new Commit(commit, this, remoteUrl), this)); } } } diff --git a/GitTimelapseView.Core/Models/FileRevision.cs b/GitTimelapseView.Core/Models/FileRevision.cs index 89b1453..735a74a 100644 --- a/GitTimelapseView.Core/Models/FileRevision.cs +++ b/GitTimelapseView.Core/Models/FileRevision.cs @@ -34,6 +34,8 @@ public void LoadBlocks(ILogger logger) using (var repository = new Repository(FileHistory.GitRootPath)) { + var remoteUrl = repository.FindRemoteUrl(); + var relativeFilePath = repository.MakeRelativeFilePath(FileHistory.FilePath); if (relativeFilePath == null) throw new Exception($"Unable to blame '{FileHistory.FilePath}'. Path is not located in the repository working directory."); @@ -43,7 +45,7 @@ public void LoadBlocks(ILogger logger) foreach (var block in blocks) { - Blocks.Add(new BlameBlock(block, this, lines)); + Blocks.Add(new BlameBlock(block, this, lines, remoteUrl)); } } } diff --git a/GitTimelapseView/Components/CommitInfo/CommitInfo.razor b/GitTimelapseView/Components/CommitInfo/CommitInfo.razor index 68a5bb6..5dd2ef7 100644 --- a/GitTimelapseView/Components/CommitInfo/CommitInfo.razor +++ b/GitTimelapseView/Components/CommitInfo/CommitInfo.razor @@ -1,4 +1,6 @@ @namespace GitTimelapseView +@using System.Diagnostics +@using GitTimelapseView.Core.Common @if (_commit == null) { @@ -9,28 +11,32 @@ else { -
+
Commit #@_commit.ShortId - by + by - on @_authoredString -
+ on @_authoredString + @if(_commit.WebUrl != null && GitHelpers.GetRemotePlatform(_commit.WebUrl) is string platform) + { +  @platform + } +
@if(!string.IsNullOrEmpty(_commit.ContainedInTag)) { - - + + @_commit.ContainedInTag @@ -39,7 +45,7 @@ else -
+
@_commit.Message
@@ -86,5 +92,17 @@ else { await new CopyToClipboardAction(TimelapseService.CurrentCommit.Id, "commit id").ExecuteAsync().ConfigureAwait(false); } - } + } + + private void OnWebUrlClicked() + { + if (_commit?.WebUrl != null) + { + Process.Start(new ProcessStartInfo + { + FileName = _commit?.WebUrl, + UseShellExecute = true, + }); + } + } } diff --git a/GitTimelapseView/Components/CommitInfo/CommitInfo.razor.css b/GitTimelapseView/Components/CommitInfo/CommitInfo.razor.css index fbab8eb..20f5a30 100644 --- a/GitTimelapseView/Components/CommitInfo/CommitInfo.razor.css +++ b/GitTimelapseView/Components/CommitInfo/CommitInfo.razor.css @@ -1,3 +1,28 @@ -.card-title{ +.card-title { + font-weight: bolder; + font-size: 12px; +} + +.open-remote-url { + opacity: 0.4; + margin-left: 10px; + font-size: 12px; + color: var(--gtlv-foreground); + text-decoration: none; +} + +.tag-label { + opacity: 0.4; + margin-left: 20px; + font-size: 12px; +} +.commit-message { + margin: 10px; + overflow: scroll; + max-height: 220px; + max-width: 750px; + overflow-y: scroll; + overflow-x: scroll; + white-space: pre-wrap; }