Commit: c457163
Parent: 3634940

Find bare ".git" repositories

Mårten Åsberg committed on 2025-11-10 at 17:32
GitBrowser/Components/Pages/Commit.razor.cs +6 -40
diff --git a/GitBrowser/Components/Pages/Commit.razor.cs b/GitBrowser/Components/Pages/Commit.razor.cs
index 42cb650..81b0c8a 100644
@@ -1,11 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using GitBrowser.Services;
using LibGit2Sharp;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
namespace GitBrowser.Components.Pages;
@@ -18,7 +17,7 @@ public partial class Commit : IDisposable
public string? Hash { get; set; }
[Inject]
public required IOptions<RepositoryConfiguration> RepoConfig { get; set; }
public required RepositoryService RepositoryService { get; set; }
[Inject]
public required IHttpContextAccessor HttpContextAccessor { get; set; }
@@ -57,55 +56,22 @@ public partial class Commit : IDisposable
return;
}
var basePath = RepoConfig.Value.Path;
if (string.IsNullOrEmpty(basePath))
{
SetNotFound();
return;
}
// Find the actual repository directory (case-insensitive)
string? actualRepoName = null;
string? repoPath = null;
if (Directory.Exists(basePath))
{
var directories = Directory.GetDirectories(basePath);
foreach (var dir in directories)
{
var dirName = System.IO.Path.GetFileName(dir);
if (string.Equals(dirName, RepoName, StringComparison.OrdinalIgnoreCase))
{
actualRepoName = dirName;
repoPath = dir;
break;
}
}
}
if (string.IsNullOrEmpty(repoPath) || !Repository.IsValid(repoPath))
{
SetNotFound();
return;
}
// Check for git-daemon-export-ok file
var exportOkPath = System.IO.Path.Combine(repoPath, "git-daemon-export-ok");
if (!File.Exists(exportOkPath))
if (!RepositoryService.TryGetRepository(RepoName, out var repoInfo))
{
SetNotFound();
return;
}
// Check if casing matches - redirect if not
if (actualRepoName != RepoName)
if (repoInfo.Name != RepoName)
{
var correctPath = $"/{Uri.EscapeDataString(actualRepoName)}/commit/{Hash}";
var correctPath = $"/{Uri.EscapeDataString(repoInfo.Name)}/commit/{Hash}";
NavigationManager.NavigateTo(correctPath, forceLoad: true);
return;
}
repo = new Repository(repoPath);
repo = repoInfo.Repository;
try
{
GitBrowser/Components/Pages/Repo.razor.cs +8 -42
diff --git a/GitBrowser/Components/Pages/Repo.razor.cs b/GitBrowser/Components/Pages/Repo.razor.cs
index a854b4a..02471b9 100644
@@ -1,11 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using GitBrowser.Services;
using LibGit2Sharp;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
namespace GitBrowser.Components.Pages;
@@ -21,7 +20,7 @@ public partial class Repo : IDisposable
public string? Path { get; set; }
[Inject]
public required IOptions<RepositoryConfiguration> RepoConfig { get; set; }
public required RepositoryService RepositoryService { get; set; }
[Inject]
public required IHttpContextAccessor HttpContextAccessor { get; set; }
@@ -58,59 +57,26 @@ public partial class Repo : IDisposable
return;
}
var basePath = RepoConfig.Value.Path;
if (string.IsNullOrEmpty(basePath))
{
SetNotFound();
return;
}
// Find the actual repository directory (case-insensitive)
string? actualRepoName = null;
string? repoPath = null;
if (Directory.Exists(basePath))
{
var directories = Directory.GetDirectories(basePath);
foreach (var dir in directories)
{
var dirName = System.IO.Path.GetFileName(dir);
if (string.Equals(dirName, RepoName, StringComparison.OrdinalIgnoreCase))
{
actualRepoName = dirName;
repoPath = dir;
break;
}
}
}
if (string.IsNullOrEmpty(repoPath) || !Repository.IsValid(repoPath))
{
SetNotFound();
return;
}
// Check for git-daemon-export-ok file
var exportOkPath = System.IO.Path.Combine(repoPath, "git-daemon-export-ok");
if (!File.Exists(exportOkPath))
if (!RepositoryService.TryGetRepository(RepoName, out var repoInfo))
{
SetNotFound();
return;
}
// Check if casing matches - redirect if not
if (actualRepoName != RepoName)
if (repoInfo.Name != RepoName)
{
var correctPath =
string.IsNullOrEmpty(Branch) ? $"/{Uri.EscapeDataString(actualRepoName)}"
: string.IsNullOrEmpty(Path) ? $"/{Uri.EscapeDataString(actualRepoName)}/tree/{Branch}"
: $"/{Uri.EscapeDataString(actualRepoName)}/tree/{Branch}/{Path}";
string.IsNullOrEmpty(Branch) ? $"/{Uri.EscapeDataString(repoInfo.Name)}"
: string.IsNullOrEmpty(Path) ? $"/{Uri.EscapeDataString(repoInfo.Name)}/tree/{Branch}"
: $"/{Uri.EscapeDataString(repoInfo.Name)}/tree/{Branch}/{Path}";
NavigationManager.NavigateTo(correctPath, forceLoad: true);
return;
}
repo = new Repository(repoPath);
repo = repoInfo.Repository;
// Determine which branch/commit to use
var branchOrCommit = Branch ?? repo.Head.FriendlyName;
GitBrowser/Program.cs +2 -1
diff --git a/GitBrowser/Program.cs b/GitBrowser/Program.cs
index bbda766..f280994 100644
@@ -1,5 +1,6 @@
using GitBrowser;
using GitBrowser.Components;
using GitBrowser.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@@ -10,7 +11,7 @@ var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorComponents();
builder.Services.AddHttpContextAccessor();
builder.Services.AddRepositoryConfiguration();
builder.Services.AddRepositoryConfiguration().AddRepositoryService();
var app = builder.Build();
GitBrowser/Services/RepositoryService.cs +62 -0
diff --git a/GitBrowser/Services/RepositoryService.cs b/GitBrowser/Services/RepositoryService.cs
new file mode 100644
index 0000000..7cd234b
@@ -0,0 +1,62 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using LibGit2Sharp;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace GitBrowser.Services;
public sealed partial class RepositoryService(IOptions<RepositoryConfiguration> options)
{
private static readonly EnumerationOptions repositoryDirectoryEnumerationOptions = new()
{
MatchCasing = MatchCasing.CaseInsensitive,
RecurseSubdirectories = false,
};
private readonly string repositoriesPath = options.Value.Path;
public bool TryGetRepository(string name, [NotNullWhen(true)] out RepositoryInformation? repositoryInformation)
{
var searchName = StripGitEnding(name);
var matchingDirectories = Directory.GetDirectories(
repositoriesPath,
searchName,
repositoryDirectoryEnumerationOptions
);
if (matchingDirectories is not [var repositoryDirectory])
{
repositoryInformation = null;
return false;
}
if (!Repository.IsValid(repositoryDirectory))
{
repositoryInformation = null;
return false;
}
if (!File.Exists(Path.Combine(repositoryDirectory, "git-daemon-export-ok")))
{
repositoryInformation = null;
return false;
}
var prettyName = StripGitEnding(Path.GetFileName(repositoryDirectory));
var repository = new Repository(repositoryDirectory);
repositoryInformation = new(prettyName, repository);
return true;
}
private static string StripGitEnding(string name) =>
name.EndsWith(".git", StringComparison.OrdinalIgnoreCase) ? name[..^4] : name;
}
public sealed record RepositoryInformation(string Name, Repository Repository);
public static class RepositoryServiceServiceCollectionExtensions
{
public static IServiceCollection AddRepositoryService(this IServiceCollection services)
{
services.AddTransient<RepositoryService>();
return services;
}
}