diff --git a/ASS.Server/ASS.Server.csproj b/ASS.Server/ASS.Server.csproj index 346dd4b..ceba0cb 100644 --- a/ASS.Server/ASS.Server.csproj +++ b/ASS.Server/ASS.Server.csproj @@ -6,8 +6,8 @@ - - + + diff --git a/ASS.Server/Services/GitCredentialService.cs b/ASS.Server/Services/GitCredentialService.cs new file mode 100644 index 0000000..73befbb --- /dev/null +++ b/ASS.Server/Services/GitCredentialService.cs @@ -0,0 +1,34 @@ +using LibGit2Sharp; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Text; + +namespace ASS.Server.Services +{ + class GitCredentialService + { + IConfiguration config; + ILogger logger; + + public GitCredentialService(IConfiguration globalConfig, ILogger log) + { + config = globalConfig; + logger = log; + } + + public Credentials GetCredentials(string url, string usernameFromUrl, SupportedCredentialTypes types) + { + var uri = new Uri(url); + var cleaned = uri.Authority + uri.PathAndQuery + uri.Fragment; + var creds = config.GetSection("Repository:Credentials").GetSection(cleaned); + if (!string.IsNullOrEmpty(creds["Username"]) && !string.IsNullOrEmpty(creds["Password"])) + { + return new UsernamePasswordCredentials() { Username = creds["Username"], Password = creds["Password"] }; + } + logger.LogWarning("Failed to get credentials for '{cleaned}'.", cleaned); + return null; + } + } +} diff --git a/ASS.Server/Services/UpdateService.cs b/ASS.Server/Services/UpdateService.cs index 851429e..62ba6a2 100644 --- a/ASS.Server/Services/UpdateService.cs +++ b/ASS.Server/Services/UpdateService.cs @@ -1,8 +1,15 @@ -using ASS.Server.Helpers; +using ASS.Server.Extensions; +using ASS.Server.Helpers; +using Emet.FileSystems; +using LibGit2Sharp; +using LibGit2Sharp.Handlers; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; namespace ASS.Server.Services @@ -11,19 +18,148 @@ namespace ASS.Server.Services class UpdateService { - IConfiguration config; - + private IConfiguration config; + private IServiceProvider serviceProvider; + private string realLivePath; + private Repository repo; + private ILogger logger; - public UpdateService(IConfiguration configuration) + public UpdateService(IServiceProvider sp, IConfiguration configuration, ILogger log) { config = configuration; - //Emet.FileSystems.FileSystem.ReadLink(GetLiveDirectory()); + serviceProvider = sp; + logger = log; } public string GetRpositoryDirectory(params string[] extraPaths) => FileSystemHelper.GetPath(config, extraPaths, "Repo"); public string GetOverrideDirectory(params string[] extraPaths) => FileSystemHelper.GetPath(config, extraPaths, "Override"); public string GetLiveDirectory(params string[] extraPaths) => FileSystemHelper.GetPath(config, extraPaths, "Live"); - public string GetRealLiveDirectory(params string[] extraPaths) => throw new NotImplementedException(); - public string GetStagingDirectory(params string[] extraPaths) => throw new NotImplementedException(); + public string GetRealLiveDirectory(params string[] extraPaths) + { + if (string.IsNullOrEmpty(realLivePath)) + realLivePath = Path.GetFullPath(Path.Combine(GetLiveDirectory(), "..", FileSystem.ReadLink(GetLiveDirectory()))); + return Path.GetFullPath(Path.Combine(extraPaths.PreAppend(realLivePath))); + } + + public string GetStagingDirectory(params string[] extraPaths) => FileSystemHelper.GetPath(config, extraPaths, getStagingName(Path.GetFileName(GetRealLiveDirectory()))); + private CredentialsHandler getCredentialsHandler() => serviceProvider.GetRequiredService().GetCredentials; + private Signature getSignature() => new Signature(config["Repository:Author:Name"] ?? "Aurora Server System", config["Repository:Author:Email"] ?? "ass@aurorastation.org", DateTimeOffset.Now); + private string getStagingName(string liveRealName = null) + { + switch (liveRealName) + { + case "DM_A": + return "DM_B"; + case "DM_B": + return "DM_A"; + default: + return "DM_A"; + } + } + + private void loadRepo() + { + if (repo != null) + return; + repo = new Repository(GetRpositoryDirectory()); + } + + public void InitilizeRepo() + { + var repoDir = GetRpositoryDirectory(); + if (Directory.Exists(repoDir)) + Directory.Delete(repoDir); + var cloneOptions = new CloneOptions() + { + CredentialsProvider = getCredentialsHandler(), + RecurseSubmodules = true, + }; + if (!string.IsNullOrEmpty(config["Repository:Branch"])) + cloneOptions.BranchName = config["Repository:Branch"]; + Repository.Clone(config["Repository:URL"], GetRpositoryDirectory(), cloneOptions); + } + + public void UpdateRepo() + { + loadRepo(); + if (!(repo.Head?.IsTracking ?? false)) + throw new Exception("Cannot update while not on a tracked branch"); + Fetch(); + + var trackedBranch = repo.Head.TrackedBranch; + var originalCommit = repo.Head.Tip; + if (repo.Head.Commits.Count(c => c.Sha == trackedBranch.Tip.Sha) != 0) + { + logger.LogInformation("Repository is up to date"); + return; + } + var mergeResult = repo.Merge(trackedBranch, getSignature()); + switch (mergeResult.Status) + { + case MergeStatus.FastForward: + case MergeStatus.NonFastForward: + logger.LogInformation("Repository has been update successfully."); + break; + case MergeStatus.Conflicts: + repo.Reset(ResetMode.Hard, originalCommit); + throw new Exception("Merge introduced merge conflicts, please reset and try again."); + default: + break; + } + // TODO: Stage a update + } + + public void CheckoutRepo(string branch = "master", string remote = "origin") + { + loadRepo(); + if (repo.Branches[branch] == null) + { + Fetch(remote); + var trackedBranch = repo.Branches[$"{remote}/{branch}"]; + if (trackedBranch == null) + throw new Exception($"Branch '{branch}' is not found localy or on remote '{remote}'."); + var newBranch = repo.CreateBranch(branch, trackedBranch.Tip); + repo.Branches.Update(newBranch, b => b.TrackedBranch = trackedBranch.CanonicalName); + } + repo.Reset(ResetMode.Hard, repo.Head.TrackedBranch?.Tip ?? repo.Head.Tip); + + var checkoutOptions = new CheckoutOptions() + { + CheckoutModifiers = CheckoutModifiers.Force, + }; + Commands.Checkout(repo, branch, checkoutOptions); + //repo.Reset(ResetMode.Hard); // TGS has it, but do we need it? + UpdateSubmodules(); + + // TODO: Stage a update + } + + public void Fetch(string remote = "origin") + { + loadRepo(); + Fetch(repo.Network.Remotes[remote]); + } + public void Fetch(Remote remote) + { + loadRepo(); + var fetchOptions = new FetchOptions() + { + Prune = true, + CredentialsProvider = getCredentialsHandler() + }; + Commands.Fetch(repo, remote.Name, remote.FetchRefSpecs.Select(X => X.Specification), fetchOptions, ""); + } + + public void UpdateSubmodules() + { + var submoduleUpdate = new SubmoduleUpdateOptions + { + CredentialsProvider = getCredentialsHandler(), + Init = true + }; + loadRepo(); + foreach (var module in repo.Submodules) + repo.Submodules.Update(module.Name, submoduleUpdate); + } } } diff --git a/ASS.Server/defaultConfig.json b/ASS.Server/defaultConfig.json index ea2f481..274bca8 100644 --- a/ASS.Server/defaultConfig.json +++ b/ASS.Server/defaultConfig.json @@ -9,7 +9,15 @@ "Host": "0.0.0.0" }, "Repository": { - "URL": "https://github.com/Aurorastation/Aurora.3.git" + "URL": "https://github.com/Aurorastation/Aurora.3.git", + "Branch": "", + "Author": { + "Name": "Aurora Server System", + "Email": "ass@aurorastation.org" + } + "Credentials": { + "github.com/Aurorastation/Aurora.3.git": {} + } }, "BYOND": { "Version": {