ok
d85b22c9b652f16460dbbc39f751452b8ef1e15e
9 files changed
src/GitServer/Properties/PublishProfiles/productie-aster.pubxmlsrc/GitServer/Services/GitProcessService.cssrc/GitServer/Web.Configsrc/GitServer/gitserver.db-shmsrc/GitServer/gitserver.db-walsrc/GitServer/wwwroot/Repo/Blob.cshtmlsrc/GitServer/wwwroot/Repo/Raw.cshtml.cssrc/GitServer/wwwroot/Repo/View.cshtmlsrc/GitServer/wwwroot/css/repo.css
diff --git a/src/GitServer/Properties/PublishProfiles/productie-aster.pubxml b/src/GitServer/Properties/PublishProfiles/productie-aster.pubxml
index 79d1bb0..816b00c 100644
--- a/src/GitServer/Properties/PublishProfiles/productie-aster.pubxml
+++ b/src/GitServer/Properties/PublishProfiles/productie-aster.pubxml
@@ -1,24 +1,24 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
-<Project>
+<Project>
<PropertyGroup>
<WebPublishMethod>MSDeploy</WebPublishMethod>
<LaunchSiteAfterPublish>true</LaunchSiteAfterPublish>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
- <SiteUrlToLaunchAfterPublish>aa</SiteUrlToLaunchAfterPublish>
+ <SiteUrlToLaunchAfterPublish>https://git.heijden.com</SiteUrlToLaunchAfterPublish>
<ExcludeApp_Data>false</ExcludeApp_Data>
- <ProjectGuid>ba2214e9-6c25-b1ab-5ff1-0ae5b748ee72</ProjectGuid>
+ <ProjectGuid>71c97b60-c794-add9-a935-bab7418b67f3</ProjectGuid>
<SelfContained>false</SelfContained>
- <MSDeployServiceURL>aaaa</MSDeployServiceURL>
- <DeployIisAppPath>bbb</DeployIisAppPath>
+ <MSDeployServiceURL>192.168.74.200</MSDeployServiceURL>
+ <DeployIisAppPath>git.heijden.com</DeployIisAppPath>
<RemoteSitePhysicalPath />
<SkipExtraFilesOnServer>true</SkipExtraFilesOnServer>
<MSDeployPublishMethod>WMSVC</MSDeployPublishMethod>
<EnableMSDeployBackup>true</EnableMSDeployBackup>
<EnableMsDeployAppOffline>true</EnableMsDeployAppOffline>
- <UserName>aa</UserName>
- <_SavePWD>false</_SavePWD>
+ <AllowUntrustedCertificate>true</AllowUntrustedCertificate>
+ <UserName>Administrator</UserName>
+ <_SavePWD>true</_SavePWD>
<_TargetId>IISWebDeploy</_TargetId>
+ <TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
</Project>
\ No newline at end of file
diff --git a/src/GitServer/Services/GitProcessService.cs b/src/GitServer/Services/GitProcessService.cs
index bfdd4d0..0864b79 100644
--- a/src/GitServer/Services/GitProcessService.cs
+++ b/src/GitServer/Services/GitProcessService.cs
@@ -261,8 +261,8 @@ public class GitProcessService(IOptions<GitServerOptions> options, ILogger<GitPr
public async Task<List<TreeEntry>> GetTree(string repoPath, string treeish, string path)
{
- var pathArg = string.IsNullOrEmpty(path) ? "" : $":{path}";
- var result = await RunGitAsync(repoPath, $"ls-tree {treeish}{pathArg}");
+ var treeishArg = string.IsNullOrEmpty(path) ? treeish : $"{treeish}:{path}";
+ var result = await RunGitAsync(repoPath, $"ls-tree \"{treeishArg}\"");
var entries = new List<TreeEntry>();
foreach (var line in result.Split('\n', StringSplitOptions.RemoveEmptyEntries))
@@ -283,12 +283,12 @@ public class GitProcessService(IOptions<GitServerOptions> options, ILogger<GitPr
public async Task<string> GetFileContent(string repoPath, string treeish, string path)
{
- return await RunGitAsync(repoPath, $"show {treeish}:{path}");
+ return await RunGitAsync(repoPath, $"show \"{treeish}:{path}\"");
}
public async Task<long> GetFileSize(string repoPath, string treeish, string path)
{
- var result = await RunGitAsync(repoPath, $"cat-file -s {treeish}:{path}");
+ var result = await RunGitAsync(repoPath, $"cat-file -s \"{treeish}:{path}\"");
return long.TryParse(result.Trim(), out var size) ? size : 0;
}
@@ -296,7 +296,7 @@ public class GitProcessService(IOptions<GitServerOptions> options, ILogger<GitPr
{
var psi = new ProcessStartInfo(_gitExe)
{
- Arguments = $"show {treeish}:{path}",
+ Arguments = $"show \"{treeish}:{path}\"",
WorkingDirectory = repoPath,
RedirectStandardOutput = true,
RedirectStandardError = true,
diff --git a/src/GitServer/Web.Config b/src/GitServer/Web.Config
index 2fe12cd..a1ab78c 100644
--- a/src/GitServer/Web.Config
+++ b/src/GitServer/Web.Config
@@ -9,8 +9,7 @@
<security>
<requestFiltering>
<fileExtensions>
- <remove fileExtension=".cs" />
- <add fileExtension=".cs" allowed="true" />
+ <clear />
</fileExtensions>
<!-- IIS default is ~28.6MB; git pushes (large repos/binaries) need much more room -->
<requestLimits maxAllowedContentLength="4294967295" />
diff --git a/src/GitServer/gitserver.db-shm b/src/GitServer/gitserver.db-shm
index 6969eb0..1c42523 100644
Binary files a/src/GitServer/gitserver.db-shm and b/src/GitServer/gitserver.db-shm differ
diff --git a/src/GitServer/gitserver.db-wal b/src/GitServer/gitserver.db-wal
index 2721e15..3c9d691 100644
Binary files a/src/GitServer/gitserver.db-wal and b/src/GitServer/gitserver.db-wal differ
diff --git a/src/GitServer/wwwroot/Repo/Blob.cshtml b/src/GitServer/wwwroot/Repo/Blob.cshtml
index acca760..f8a5207 100644
--- a/src/GitServer/wwwroot/Repo/Blob.cshtml
+++ b/src/GitServer/wwwroot/Repo/Blob.cshtml
@@ -5,6 +5,8 @@
var ext = System.IO.Path.GetExtension(Model.FilePath).TrimStart('.').ToLower();
var imageExts = new HashSet<string> { "png", "jpg", "jpeg", "gif" };
var isImage = imageExts.Contains(ext);
+ var trimmedContent = Model.Content.TrimEnd('\n', '\r');
+ var lineCount = trimmedContent.Length == 0 ? 0 : trimmedContent.Split('\n').Length;
}
<div class="container">
<div class="repo-header">
@@ -35,8 +37,32 @@
<div class="file-view">
<div class="file-view-header">
- <span class="file-name">@Model.FilePath</span>
- <span class="file-size text-muted">@Model.FileSize bytes</span>
+ <div class="file-view-info text-muted">
+ <span>Code</span>
+ @if (!isImage && !Model.IsBinary)
+ {
+ <span class="separator">·</span>
+ <span>@lineCount lines</span>
+ }
+ <span class="separator">·</span>
+ <span>@Model.FileSize bytes</span>
+ </div>
+ <div class="file-view-actions">
+ <a class="file-action-link" href="/@Model.UserName/@Model.RepoName/raw/@Model.Branch/@Model.FilePath">Raw</a>
+ <button type="button" class="icon-btn" id="copyRawBtn" title="Copy Raw File" aria-label="Copy Raw File">
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+ <rect x="9" y="9" width="11" height="11" rx="1" />
+ <path d="M5 15V5a1 1 0 0 1 1-1h10" />
+ </svg>
+ </button>
+ <a class="icon-btn" href="/@Model.UserName/@Model.RepoName/raw/@Model.Branch/@Model.FilePath" download title="Download Raw File" aria-label="Download Raw File">
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+ <path d="M12 3v12" />
+ <path d="M7 10l5 5 5-5" />
+ <path d="M5 21h14" />
+ </svg>
+ </a>
+ </div>
</div>
@if (isImage)
{
@@ -51,8 +77,24 @@
else
{
<div class="file-content">
- <pre class="code-view lang-@ext"><code>@Model.Content</code></pre>
+ <pre class="code-view lang-@ext"><span class="line-numbers">@string.Join("\n", Enumerable.Range(1, lineCount))</span><code class="language-@ext">@trimmedContent</code></pre>
</div>
}
</div>
</div>
+
+@section Scripts {
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/github-dark.min.css">
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js"></script>
+ <script>hljs.highlightAll();</script>
+ <script>
+ document.getElementById('copyRawBtn')?.addEventListener('click', async function () {
+ const code = document.querySelector('.code-view code');
+ if (!code) return;
+ await navigator.clipboard.writeText(code.innerText);
+ const original = this.title;
+ this.title = 'Copied!';
+ setTimeout(() => { this.title = original; }, 1500);
+ });
+ </script>
+}
diff --git a/src/GitServer/wwwroot/Repo/Raw.cshtml.cs b/src/GitServer/wwwroot/Repo/Raw.cshtml.cs
index 5a6321e..461ff5b 100644
--- a/src/GitServer/wwwroot/Repo/Raw.cshtml.cs
+++ b/src/GitServer/wwwroot/Repo/Raw.cshtml.cs
@@ -28,7 +28,7 @@ public class RawModel(
if (!await repos.CanReadAsync(repoObj, userId)) return Forbid();
var ext = System.IO.Path.GetExtension(path);
- if (!ContentTypes.TryGetValue(ext, out var contentType)) return NotFound();
+ if (!ContentTypes.TryGetValue(ext, out var contentType)) contentType = "text/plain; charset=utf-8";
var repoPath = repos.GetRepoPath(user, repo);
Response.ContentType = contentType;
diff --git a/src/GitServer/wwwroot/Repo/View.cshtml b/src/GitServer/wwwroot/Repo/View.cshtml
index 8a4cd41..1f64789 100644
--- a/src/GitServer/wwwroot/Repo/View.cshtml
+++ b/src/GitServer/wwwroot/Repo/View.cshtml
@@ -65,9 +65,26 @@ git push -u origin @Model.DefaultBranch</pre>
<div class="file-tree">
@if (!string.IsNullOrEmpty(Model.CurrentPath))
{
- <div class="file-tree-header">
- <a href="/@Model.UserName/@Model.RepoName?branch=@Model.CurrentBranch">@L["back_to_root"]</a>
- / @Model.CurrentPath
+ <div class="file-tree-header file-breadcrumb">
+ <a href="/@Model.UserName/@Model.RepoName?branch=@Model.CurrentBranch">@Model.RepoName</a>
+ @{
+ var treeParts = Model.CurrentPath.Split('/');
+ var treeAccumulated = "";
+ }
+ @for (int i = 0; i < treeParts.Length; i++)
+ {
+ treeAccumulated += (i > 0 ? "/" : "") + treeParts[i];
+ var treeAcc = treeAccumulated;
+ <span class="separator">/</span>
+ @if (i == treeParts.Length - 1)
+ {
+ <strong>@treeParts[i]</strong>
+ }
+ else
+ {
+ <a href="/@Model.UserName/@Model.RepoName?branch=@Model.CurrentBranch&path=@treeAcc">@treeParts[i]</a>
+ }
+ }
</div>
}
@foreach (var entry in Model.Tree)
diff --git a/src/GitServer/wwwroot/css/repo.css b/src/GitServer/wwwroot/css/repo.css
index eaa8867..df3d6c7 100644
--- a/src/GitServer/wwwroot/css/repo.css
+++ b/src/GitServer/wwwroot/css/repo.css
@@ -201,6 +201,48 @@
.file-name { font-family: var(--font-mono); }
.file-size { font-size: 0.8125rem; }
+.file-view-info {
+ display: flex;
+ align-items: center;
+ gap: 0.375rem;
+}
+
+.file-view-info .separator { opacity: 0.5; }
+
+.file-view-actions {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.file-action-link {
+ font-size: 0.8125rem;
+}
+
+.icon-btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 1.75rem;
+ height: 1.75rem;
+ padding: 0;
+ border: none;
+ background: transparent;
+ color: var(--text-muted, inherit);
+ cursor: pointer;
+ border-radius: var(--radius);
+}
+
+.icon-btn:hover {
+ background: var(--surface3, rgba(127, 127, 127, 0.15));
+ color: var(--text);
+}
+
+.icon-btn svg {
+ width: 1rem;
+ height: 1rem;
+}
+
.file-content {
overflow-x: auto;
}
@@ -222,6 +264,27 @@
display: table-cell;
padding: 0.75rem 1rem;
white-space: pre;
+ tab-size: 4;
+ -moz-tab-size: 4;
+ font-family: var(--font-mono);
+ font-size: 0.8125rem;
+ line-height: 1.6;
+ vertical-align: top;
+}
+
+.line-numbers {
+ display: table-cell;
+ padding: 0.75rem 0.75rem;
+ white-space: pre;
+ text-align: right;
+ color: var(--text-muted);
+ background: var(--surface2);
+ border-right: 1px solid var(--border);
+ user-select: none;
+ font-family: var(--font-mono);
+ font-size: 0.8125rem;
+ line-height: 1.6;
+ vertical-align: top;
}
.file-binary-notice {