tools/code/publisher/Git.cs (107 lines of code) (raw):
using common;
using LanguageExt;
using LibGit2Sharp;
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace publisher;
public sealed record CommitId : NonEmptyString
{
public CommitId(string value) : base(value) { }
}
public static class Git
{
public static FrozenSet<FileInfo> GetChangedFilesInCommit(DirectoryInfo directory, CommitId commitId)
{
var repositoryDirectory = GetRepositoryDirectory(directory);
using var repository = new Repository(repositoryDirectory.FullName);
return GetChanges(repository, commitId)
.SelectMany(change => (change.Path, change.OldPath) switch
{
(null, not null) => [change.OldPath],
(not null, null) => [change.Path],
(null, null) => [],
(var path, var oldPath) => new[] { path, oldPath }.Distinct()
})
.Select(path => new FileInfo(Path.Combine(repositoryDirectory.FullName, path)))
.ToFrozenSet(x => x.FullName);
}
private static TreeChanges GetChanges(Repository repository, CommitId commitId)
{
var commit = GetCommit(repository, commitId);
var parentCommit = commit.Parents.FirstOrDefault();
return repository.Diff
.Compare<TreeChanges>(parentCommit?.Tree, commit.Tree);
}
private static DirectoryInfo GetRepositoryDirectory(DirectoryInfo directory)
{
var repositoryDirectory = directory.EnumerateDirectories(".git", SearchOption.TopDirectoryOnly)
.FirstOrDefault();
if (repositoryDirectory is not null)
{
return directory;
}
var parentDirectory = directory.Parent;
return parentDirectory is null
? throw new InvalidOperationException("Could not find a Git repository.")
: GetRepositoryDirectory(parentDirectory);
}
private static Commit GetCommit(Repository repository, CommitId commitId) =>
repository.Commits
.Where(commit => commit.Id.Sha == commitId.Value)
.HeadOrNone()
.IfNone(() => throw new InvalidOperationException($"Could not find commit with ID {commitId.Value}."));
public static Option<CommitId> TryGetPreviousCommitId(DirectoryInfo directory, CommitId commitId)
{
var repositoryDirectory = GetRepositoryDirectory(directory);
using var repository = new Repository(repositoryDirectory.FullName);
var commit = GetCommit(repository, commitId);
return commit.Parents
.HeadOrNone()
.Map(parent => new CommitId(parent.Id.Sha));
}
public static Option<Stream> TryGetFileContentsInCommit(DirectoryInfo directory, FileInfo file, CommitId commitId)
{
var repositoryDirectory = GetRepositoryDirectory(directory);
using var repository = new Repository(repositoryDirectory.FullName);
var relativePath = Path.GetRelativePath(repositoryDirectory.FullName, file.FullName);
var relativePathString = Path.DirectorySeparatorChar == '\\'
? relativePath.Replace('\\', '/')
: relativePath;
var blob = repository.Lookup<Blob>($"{commitId.Value}:{relativePathString}");
return blob is null
? Option<Stream>.None
: blob.GetContentStream();
}
public static FrozenSet<FileInfo> GetExistingFilesInCommit(DirectoryInfo directory, CommitId commitId)
{
var repositoryDirectory = GetRepositoryDirectory(directory);
using var repository = new Repository(repositoryDirectory.FullName);
var commit = GetCommit(repository, commitId);
return commit.Tree
.SelectMany(treeEntry => GetFilesFromTreeEntry(treeEntry, repositoryDirectory))
.ToFrozenSet(x => x.FullName);
}
private static IEnumerable<FileInfo> GetFilesFromTreeEntry(TreeEntry entry, DirectoryInfo repositoryDirectory) =>
entry.Target switch
{
Blob blob => [new FileInfo(Path.Combine(repositoryDirectory.FullName, entry.Path))],
Tree tree => tree.SelectMany(child => GetFilesFromTreeEntry(child, repositoryDirectory)),
_ => []
};
public static void InitializeRepository(DirectoryInfo directory, string commitMessage, string authorName, string authorEmail, DateTimeOffset signatureDate)
{
Repository.Init(directory.FullName);
CommitChanges(directory, commitMessage, authorName, authorEmail, signatureDate);
}
public static Commit CommitChanges(DirectoryInfo directory, string commitMessage, string authorName, string authorEmail, DateTimeOffset signatureDate)
{
using var repository = new Repository(directory.FullName);
Commands.Stage(repository, "*");
repository.Index.Write();
var author = new Signature(authorName, authorEmail, signatureDate);
return repository.Commit(commitMessage, author, author);
}
}