using LibGit2Sharp; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Threading; using System.Web.Script.Serialization; using TGServiceInterface; using TGServiceInterface.Components; namespace TGServerService { sealed partial class ServerInstance : ITGRepository, IDisposable { /// /// The directory for the repository /// const string RepoPath = "Repository"; /// /// The path to the Repository's json /// const string RepoTGS3SettingsPath = RepoPath + "/TGS3.json"; /// /// Path to the 's json /// const string CachedTGS3SettingsPath = "TGS3.json"; /// /// Error message for when a merge operation fails due to the target branch already having the source branch's commits /// const string RepoErrorUpToDate = "Already up to date!"; /// /// Git remote for push operations /// const string SSHPushRemote = "ssh_push_target"; /// /// The directory for the repository SSH keys /// const string RepoKeyDir = "RepoKey/"; /// /// The path to the private ssh-rsa key file /// const string PrivateKeyPath = RepoKeyDir + "private_key.txt"; /// /// The path to the public ssh-rsa key file /// const string PublicKeyPath = RepoKeyDir + "public_key.txt"; /// /// File name for monitoring which github pull requests are currently test merged /// const string PRJobFile = "prtestjob.json"; /// /// The branch that points to the current commit that is live or staged to be live in DreamDaemon /// const string LiveTrackingBranch = "___TGSLiveCommitTrackingBranch"; /// /// Commit message for /// const string CommitMessage = "Automatic changelog compile, [ci skip]"; /// /// Used in conjunction with for multithreading safety /// object RepoLock = new object(); /// /// Used in conjunction with for multithreading safety /// bool RepoBusy = false; /// /// Whether or not a git clone operation is in progress /// bool Cloning = false; /// /// The repository object /// Repository Repo; /// /// Used for reporting operation progress to the /// int currentProgress = -1; /// /// Used for automatically updating the /// System.Timers.Timer autoUpdateTimer = new System.Timers.Timer() { AutoReset = true }; /// /// Initializes the repository /// void InitRepo() { Directory.CreateDirectory(RelativePath(RepoKeyDir)); if(Exists()) UpdateBridgeDll(false); if(LoadRepo() == null) DisableGarbageCollectionNoLock(); //start the autoupdate timer autoUpdateTimer.Elapsed += AutoUpdateTimer_Elapsed; SetAutoUpdateInterval(Config.AutoUpdateInterval); } /// /// Checks if the and json files match. /// /// if the jsons match, otherwise bool RepoConfigsMatch() { //this should never be called while the repo is busy RepoConfig I = null; lock (RepoLock) { if (!RepoBusy && LoadRepo() == null) I = new RepoConfig(RelativePath(RepoTGS3SettingsPath)); } if (I == null) throw new Exception("Unable to load TGS3.json from repo!"); var J = GetCachedRepoConfig(); return I == J; } /// /// Gets the for /// /// The for RepoConfig GetCachedRepoConfig() { return new RepoConfig(RelativePath(CachedTGS3SettingsPath)); } /// public bool OperationInProgress() { lock (RepoLock) { return RepoBusy; } } /// public int CheckoutProgress() { return currentProgress; } /// /// Initializes /// /// on success, error message on failure string LoadRepo() { if (Repo != null) return null; if (!Repository.IsValid(RelativePath(RepoPath))) return "Repository does not exist"; try { Repo = new Repository(RelativePath(RepoPath)); } catch (Exception e) { return e.ToString(); } return null; } /// /// Cleans up /// void DisposeRepo() { if (Repo != null) { Repo.Dispose(); Repo = null; } } /// public bool Exists() { lock (RepoLock) { return !Cloning && Repository.IsValid(RelativePath(RepoPath)); } } /// /// Updates with the progess of the current transfer operation /// /// The of the current transfer operation /// bool HandleTransferProgress(TransferProgress progress) { currentProgress = ((int)(((float)progress.ReceivedObjects / progress.TotalObjects) * 100) / 2) +( (int)(((float)progress.IndexedObjects / progress.TotalObjects) * 100) / 2); return true; } /// /// Updates with the progess of the current checkout operation /// /// Ignored /// Dividend for progress calculation /// Divisor for progress calculation void HandleCheckoutProgress(string path, int completedSteps, int totalSteps) { currentProgress = (int)(((float)completedSteps / totalSteps) * 100); } /// /// Backups up the and deletes the current if they exist. Clones the given branch of the given remote. Sets up new /// /// Remote and branch name, seperated by a ' ' void Clone(object twostrings) { //busy flag set by caller var ts = ((string)twostrings).Split(' '); var RepoURL = ts[0]; var BranchName = ts[1]; try { SendMessage(String.Format("REPO: {2} started: Cloning {0} branch of {1} ...", BranchName, RepoURL, Repository.IsValid(RepoPath) ? "Full reset" : "Setup"), MessageType.DeveloperInfo); try { DisposeRepo(); Program.DeleteDirectory(RelativePath(RepoPath)); DeletePRList(); lock (configLock) { BackupAndDeleteStaticDirectory(); } var Opts = new CloneOptions() { BranchName = BranchName, RecurseSubmodules = true, OnTransferProgress = HandleTransferProgress, OnCheckoutProgress = HandleCheckoutProgress, CredentialsProvider = GenerateGitCredentials, }; Repository.Clone(RepoURL, RelativePath(RepoPath), Opts); currentProgress = -1; LoadRepo(); DisableGarbageCollectionNoLock(); //create an ssh remote for pushing Repo.Network.Remotes.Add(SSHPushRemote, RepoURL.Replace("git://", "ssh://").Replace("https://", "ssh://")); InitialConfigureRepository(); SendMessage("REPO: Clone complete!", MessageType.DeveloperInfo); WriteInfo("Repository {0}:{1} successfully cloned", EventID.RepoClone); } finally { currentProgress = -1; } } catch (Exception e) { SendMessage("REPO: Setup failed!", MessageType.DeveloperInfo); WriteWarning(String.Format("Failed to clone {2}:{0}: {1}", BranchName, e.ToString(), RepoURL), EventID.RepoCloneFail); } finally { lock (RepoLock) { RepoBusy = false; Cloning = false; } } } /// /// Turns off the gc.auto git config setting /// void DisableGarbageCollectionNoLock() { Repo.Config.Set("gc.auto", false); } /// /// Copies the Static directory to the first available Static_BACKUP path in the then deleted the old directory /// void BackupAndDeleteStaticDirectory() { var rsd = RelativePath(StaticDirs); if (Directory.Exists(rsd)) { int count = 1; var rsbd = RelativePath(StaticBackupDir); string path = Path.GetDirectoryName(rsbd); string newFullPath = rsbd; while (File.Exists(newFullPath) || Directory.Exists(newFullPath)) { string tempDirName = string.Format("{0}({1})", rsbd, count++); newFullPath = Path.Combine(path, tempDirName); } Program.CopyDirectory(rsd, newFullPath); } Program.DeleteDirectory(rsd); } /// /// Updates the with the /// /// on success, error message on failure public string UpdateTGS3Json() { try { if (File.Exists(RelativePath(RepoTGS3SettingsPath))) File.Copy(RelativePath(RepoTGS3SettingsPath), RelativePath(CachedTGS3SettingsPath), true); else if (File.Exists(RelativePath(CachedTGS3SettingsPath))) File.Delete(RelativePath(CachedTGS3SettingsPath)); } catch(Exception e) { return e.ToString(); } return null; } /// /// Initial setup for the and /// void InitialConfigureRepository() { Directory.CreateDirectory(RelativePath(StaticDirs)); UpdateBridgeDll(false); UpdateTGS3Json(); var Config = GetCachedRepoConfig(); //RepoBusy is set if we're here foreach(var I in Config.StaticDirectoryPaths) { try { var source = Path.Combine(RelativePath(RepoPath), I); var dest = Path.Combine(RelativePath(StaticDirs), I); if (Directory.Exists(source)) Program.CopyDirectory(source, dest); else Directory.CreateDirectory(dest); } catch { WriteError("Could not setup static directory: " + I, EventID.RepoConfigurationFail); } } foreach(var I in Config.DLLPaths) { try { var source = Path.Combine(RelativePath(RepoPath), I); if (!File.Exists(source)) { WriteWarning("Could not find DLL: " + I, EventID.RepoConfigurationFail); continue; } var dest = Path.Combine(RelativePath(StaticDirs), I); Program.CopyFileForceDirectories(source, dest, false); } catch { WriteError("Could not setup static DLL: " + I, EventID.RepoConfigurationFail); } } } /// public string Setup(string RepoURL, string BranchName) { lock (RepoLock) { if (RepoBusy) return "Repo is busy!"; lock (CompilerLock) { if (!CompilerIdleNoLock()) return "Compiler is running!"; } if (DaemonStatus() != DreamDaemonStatus.Offline) return "DreamDaemon is running!"; if (RepoURL.Contains("ssh://") && !SSHAuth()) return String.Format("SSH url specified but either {0} or {1} does not exist in the server directory!", PrivateKeyPath, PublicKeyPath); RepoBusy = true; Cloning = true; new Thread(new ParameterizedThreadStart(Clone)) { IsBackground = true //make sure we don't hold up shutdown }.Start(RepoURL + ' ' + BranchName); return null; } } /// /// Gets the SHA or branch name of the 's HEAD /// /// on success, error message on failure /// If , returns the branch name instead of the SHA /// If , returns the tracked branch SHA and ignores /// The SHA or branch name of the 's HEAD string GetShaOrBranch(out string error, bool branch, bool tracked) { lock (RepoLock) { var result = LoadRepo(); if (result != null) { error = result; return null; } try { error = null; if (tracked && Repo.Head.TrackedBranch != null) return Repo.Head.TrackedBranch.Tip.Sha; return branch ? Repo.Head.FriendlyName : Repo.Head.Tip.Sha; } catch (Exception e) { error = e.ToString(); return null; } } } /// public string GetHead(bool useTracked, out string error) { return GetShaOrBranch(out error, false, useTracked); } /// public string GetBranch(out string error) { return GetShaOrBranch(out error, true, false); } /// public string GetRemote(out string error) { try { var res = LoadRepo(); if (res != null) { error = res; return null; } error = null; return Repo.Network.Remotes["origin"].Url; } catch (Exception e) { error = e.ToString(); return null; } } /// /// Equivalent of running `git reset --hard` on the repository. Requires /// /// If not , reset to this branch instead of HEAD /// on success, error message on failure string ResetNoLock(Branch targetBranch) { try { if (targetBranch != null) Repo.Reset(ResetMode.Hard, targetBranch.Tip); else Repo.Reset(ResetMode.Hard); return null; } catch (Exception e) { return e.ToString(); } } /// public string Checkout(string sha) { if (sha == LiveTrackingBranch) return "I'm sorry Dave, I'm afraid I can't do that..."; lock (RepoLock) { var result = LoadRepo(); if (result != null) return result; SendMessage("REPO: Checking out object: " + sha, MessageType.DeveloperInfo); try { if (Repo.Branches[sha] == null) { //see if origin has the branch result = Fetch(); var trackedBranch = Repo.Branches[String.Format("origin/{0}", sha)]; if (trackedBranch != null) { var newBranch = Repo.CreateBranch(sha, trackedBranch.Tip); //track it Repo.Branches.Update(newBranch, b => b.TrackedBranch = trackedBranch.CanonicalName); } else if (result != null) return result; } var Opts = new CheckoutOptions() { CheckoutModifiers = CheckoutModifiers.Force, OnCheckoutProgress = HandleCheckoutProgress, }; Commands.Checkout(Repo, sha, Opts); var res = ResetNoLock(null); UpdateSubmodules(); SendMessage("REPO: Checkout complete!", MessageType.DeveloperInfo); WriteInfo("Repo checked out " + sha, EventID.RepoCheckout); return res; } catch (Exception e) { SendMessage("REPO: Checkout failed!", MessageType.DeveloperInfo); WriteWarning(String.Format("Repo checkout of {0} failed: {1}", sha, e.ToString()), EventID.RepoCheckoutFail); return e.ToString(); } } } /// /// Merges given into the current branch /// /// The sha/branch/tag to merge /// on success, error message on failure string MergeBranch(string committish) { var mo = new MergeOptions() { OnCheckoutProgress = HandleCheckoutProgress }; var Result = Repo.Merge(committish, MakeSig()); currentProgress = -1; switch (Result.Status) { case MergeStatus.Conflicts: ResetNoLock(null); SendMessage("REPO: Merge conflicted, aborted.", MessageType.DeveloperInfo); return "Merge conflict occurred."; case MergeStatus.UpToDate: return RepoErrorUpToDate; } return null; } /// public string Update(bool reset) { return UpdateImpl(reset, true); } /// /// Fetches the origin and merges it into the current branch /// /// If , the operation will perform a hard reset instead of a merge /// If , a return value of will be changed to /// on success, error message on failure string UpdateImpl(bool reset, bool successOnUpToDate) { lock (RepoLock) { var result = LoadRepo(); if (result != null) return result; try { if (Repo.Head == null || !Repo.Head.IsTracking) return "Cannot update while not on a tracked branch"; var res = Fetch(); if (res != null) return res; var originBranch = Repo.Head.TrackedBranch; if (!successOnUpToDate && Repo.Head.Tip.Sha == originBranch.Tip.Sha) return RepoErrorUpToDate; SendMessage(String.Format("REPO: Updating origin branch...({0})", reset ? "Hard Reset" : "Merge"), MessageType.DeveloperInfo); if (reset) { var error = ResetNoLock(Repo.Head.TrackedBranch); UpdateSubmodules(); if (error != null) throw new Exception(error); DeletePRList(); WriteInfo("Repo hard updated to " + originBranch.Tip.Sha, EventID.RepoHardUpdate); return error; } res = MergeBranch(originBranch.FriendlyName); if (res != null) throw new Exception(res); UpdateSubmodules(); WriteInfo("Repo merge updated to " + originBranch.Tip.Sha, EventID.RepoMergeUpdate); return null; } catch (Exception E) { SendMessage("REPO: Update failed!", MessageType.DeveloperInfo); WriteWarning(String.Format("Repo{0} update failed", reset ? " hard" : ""), reset ? EventID.RepoHardUpdateFail : EventID.RepoMergeUpdateFail); return E.ToString(); } } } /// /// Properly updates any git submodules in the repository /// private void UpdateSubmodules() { var suo = new SubmoduleUpdateOptions { Init = true }; foreach (var I in Repo.Submodules) try { Repo.Submodules.Update(I.Name, suo); } catch (Exception e) { try { //workaround for https://github.com/libgit2/libgit2/issues/3820 //kill off the modules/ folder in .git and try again try { Program.DeleteDirectory(String.Format("{0}/.git/modules/{1}", RepoPath, I.Path)); } catch { throw e; } Repo.Submodules.Update(I.Name, suo); var msg = String.Format("I had to reclone submodule {0}. If this is happening a lot find a better hack or fix https://github.com/libgit2/libgit2/issues/3820!", I.Name); SendMessage(String.Format("REPO: {0}", msg), MessageType.DeveloperInfo); WriteWarning(msg, EventID.Submodule); } catch (Exception ex) { WriteError(String.Format("Failed to update submodule {0}! Error: {1}", I.Name, ex.ToString()), EventID.Submodule); } } } /// /// Creates a date and timestamped tag of the current HEAD /// /// on success, error message on failure string CreateBackup() { try { lock (RepoLock) { var res = LoadRepo(); if (res != null) return res; //Make sure we don't already have a backup at this commit var HEAD = Repo.Head.Tip.Sha; foreach (var T in Repo.Tags) if (T.Target.Sha == HEAD) return null; var tagName = "TGS-Compile-Backup-" + DateTime.Now.ToString("yyyy-MM-dd--HH.mm.ss"); var tag = Repo.ApplyTag(tagName); if (tag != null) { WriteInfo("Repo backup created at tag: " + tagName + " commit: " + HEAD, EventID.RepoBackupTag); return null; } throw new Exception("Tag creation failed!"); } } catch (Exception e) { WriteWarning(String.Format("Failed backup tag creation at commit {0}!", Repo.Head.Tip.Sha), EventID.RepoBackupTagFail); return e.ToString(); } } /// /// Lists tags in the repository created by the /// /// on success, error message on failure /// A dictionary of tag title -> commit SHA on success, on failure public IDictionary ListBackups(out string error) { try { lock (RepoLock) { error = LoadRepo(); if (error != null) return null; var res = new Dictionary(); foreach (var T in Repo.Tags) if (T.FriendlyName.Contains("TGS")) res.Add(T.FriendlyName, T.Target.Sha); return res; } } catch (Exception e) { error = e.ToString(); return null; } } /// public string Reset(bool trackedBranch) { lock (RepoLock) { var res = LoadRepo() ?? ResetNoLock(trackedBranch ? (Repo.Head.TrackedBranch ?? Repo.Head) : Repo.Head); if (res == null) { SendMessage(String.Format("REPO: Hard reset to {0}branch", trackedBranch ? "tracked " : ""), MessageType.DeveloperInfo); if (trackedBranch) DeletePRList(); WriteInfo(String.Format("Repo branch reset{0}", trackedBranch ? " to tracked branch" : ""), trackedBranch ? EventID.RepoResetTracked : EventID.RepoReset); return null; } WriteWarning(String.Format("Failed to reset{0}: {1}", trackedBranch ? " to tracked branch" : "", res), trackedBranch ? EventID.RepoResetTrackedFail : EventID.RepoResetFail); return res; } } /// /// Creates a commit based off of the configured name and e-mail /// /// The created Signature MakeSig() { return new Signature(new Identity(Config.CommitterName, Config.CommitterEmail), DateTimeOffset.Now); } /// /// Deletes the 's /// void DeletePRList() { if (File.Exists(RelativePath(PRJobFile))) try { File.Delete(RelativePath(PRJobFile)); } catch (Exception e) { WriteError("Failed to delete PR list: " + e.ToString(), EventID.RepoPRListError); } } /// /// Deserializes the /// /// A of . The outer one is keyed by PR# the inner one is keyed by internal s IDictionary> GetCurrentPRList() { if (!File.Exists(RelativePath(PRJobFile))) return new Dictionary>(); var rawdata = File.ReadAllText(RelativePath(PRJobFile)); var Deserializer = new JavaScriptSerializer(); return Deserializer.Deserialize>>(rawdata); } /// /// Serializes pull request info into /// /// void SetCurrentPRList(IDictionary> list) { var Serializer = new JavaScriptSerializer(); var rawdata = Serializer.Serialize(list); File.WriteAllText(RelativePath(PRJobFile), rawdata); } /// public string MergePullRequest(int PRNumber) { lock (RepoLock) { var result = LoadRepo(); if (result != null) return result; SendMessage(String.Format("REPO: Merging PR #{0}...", PRNumber), MessageType.DeveloperInfo); result = ResetNoLock(null); if (result != null) return result; try { //only supported with github var remoteUrl = Repo.Network.Remotes["origin"].Url; if (!remoteUrl.Contains("github.com")) return "Only supported with Github based repositories."; var Refspec = new List(); var PRBranchName = String.Format("pr-{0}", PRNumber); var LocalBranchName = String.Format("pull/{0}/headrefs/heads/{1}", PRNumber, PRBranchName); Refspec.Add(String.Format("pull/{0}/head:{1}", PRNumber, PRBranchName)); var logMessage = ""; var branch = Repo.Branches[LocalBranchName]; if (branch != null) //Need to delete the branch first in case of rebase Repo.Branches.Remove(branch); Commands.Fetch(Repo, "origin", Refspec, GenerateFetchOptions(), logMessage); //shitty api has no failure state for this currentProgress = -1; var Config = Properties.Settings.Default; branch = Repo.Branches[LocalBranchName]; if (branch == null) { SendMessage("REPO: PR could not be fetched. Does it exist?", MessageType.DeveloperInfo); return String.Format("PR #{0} could not be fetched. Does it exist?", PRNumber); } //so we'll know if this fails var Result = MergeBranch(LocalBranchName); if (Result == null) try { UpdateSubmodules(); } catch (Exception e) { Result = e.ToString(); } if (Result == null) { WriteInfo(String.Format("Merged pull request #{0}", PRNumber), EventID.RepoPRMerge); try { var CurrentPRs = GetCurrentPRList(); var PRNumberString = PRNumber.ToString(); CurrentPRs.Remove(PRNumberString); var newPR = new Dictionary(); //do some excellent remote fuckery here to get the api page var prAPI = remoteUrl; prAPI = prAPI.Replace("/.git", ""); prAPI = prAPI.Replace(".git", ""); prAPI = prAPI.Replace("github.com", "api.github.com/repos"); prAPI += "/pulls/" + PRNumberString + ".json"; string json; using (var wc = new WebClient()) { wc.Headers.Add("user-agent", "TGStationServerService"); json = wc.DownloadString(prAPI); } var Deserializer = new JavaScriptSerializer(); var dick = Deserializer.DeserializeObject(json) as IDictionary; var user = dick["user"] as IDictionary; newPR.Add("commit", branch.Tip.Sha); newPR.Add("author", (string)user["login"]); newPR.Add("title", (string)dick["title"]); CurrentPRs.Add(PRNumberString, newPR); SetCurrentPRList(CurrentPRs); } catch (Exception e) { WriteError("Failed to update PR list", EventID.RepoPRListError); return "PR Merged, JSON update failed: " + e.ToString(); } } return Result; } catch (Exception E) { SendMessage("REPO: PR merge failed!", MessageType.DeveloperInfo); WriteWarning(String.Format("Failed to merge pull request #{0}: {1}", PRNumber, E.ToString()), EventID.RepoPRMergeFail); return E.ToString(); } } } /// public IList MergedPullRequests(out string error) { lock (RepoLock) { var result = LoadRepo(); if (result != null) { error = result; return null; } try { var PRRawData = GetCurrentPRList(); IList output = new List(); foreach (var I in GetCurrentPRList()) output.Add(new PullRequestInfo(Convert.ToInt32(I.Key), I.Value["author"], I.Value["title"], I.Value["commit"])); error = null; return output; } catch (Exception e) { error = e.ToString(); return null; } } } /// public string GetCommitterName() { lock (RepoLock) { return Config.CommitterName; } } /// public void SetCommitterName(string newName) { lock (RepoLock) { Config.CommitterName = newName; } } /// public string GetCommitterEmail() { lock (RepoLock) { return Config.CommitterEmail; } } /// public void SetCommitterEmail(string newEmail) { lock (RepoLock) { Config.CommitterEmail = newEmail; } } /// public string SynchronizePush() { var Config = GetCachedRepoConfig(); if (Config == null) return "Error reading changelog configuration"; if(!Config.ChangelogSupport || !SSHAuth()) return null; return LocalIsRemote() ? Commit(Config) ?? Push() : "Can't push changelog: HEAD does not match tracked remote branch"; } /// /// Create that Prune and have the appropriate credentials and progress handler /// /// Properly configured FetchOptions GenerateFetchOptions() { return new FetchOptions() { CredentialsProvider = GenerateGitCredentials, OnTransferProgress = HandleTransferProgress, Prune = true, }; } /// /// Fetches origin /// /// null on success, error message on failure string Fetch() { try { string logMessage = ""; var R = Repo.Network.Remotes["origin"]; IEnumerable refSpecs = R.FetchRefSpecs.Select(X => X.Specification); Commands.Fetch(Repo, R.Name, refSpecs, GenerateFetchOptions(), logMessage); return null; } catch (Exception e) { return e.ToString(); } } /// /// Check if the current HEAD matches the tracked remote branch HEAD /// /// if the current HEAD matches the tracked remote branch HEAD, otherwise bool LocalIsRemote() { lock (RepoLock) { if (LoadRepo() != null) return false; if (Fetch() != null) return false; try { return Repo.Head.IsTracking && Repo.Head.TrackedBranch.Tip.Sha == Repo.Head.Tip.Sha; } catch { return false; } } } /// /// Create a commit based on a /// /// A with /// on success, error message on failure string Commit(RepoConfig Config) { lock (RepoLock) { var result = LoadRepo(); if (result != null) return result; try { // Stage the file foreach(var I in Config.PathsToStage) Commands.Stage(Repo, I); if (Repo.RetrieveStatus().Staged.Count() == 0) //nothing to commit return null; // Create the committer's signature and commit var authorandcommitter = MakeSig(); // Commit to the repository WriteInfo(String.Format("Commit {0} created from changelogs", Repo.Commit(CommitMessage, authorandcommitter, authorandcommitter)), EventID.RepoCommit); DeletePRList(); return null; } catch (Exception e) { WriteWarning("Repo commit failed: " + e.ToString(), EventID.RepoCommitFail); return e.ToString(); } } } /// string Push() { if (LocalIsRemote()) //nothing to push return null; lock (RepoLock) { var result = LoadRepo(); if (result != null) return result; try { if (!SSHAuth()) return String.Format("Either {0} or {1} is missing from the server directory. Unable to push!", PrivateKeyPath, PublicKeyPath); var options = new PushOptions() { CredentialsProvider = GenerateGitCredentials, }; Repo.Network.Push(Repo.Network.Remotes[SSHPushRemote], Repo.Head.CanonicalName, options); WriteInfo("Repo pushed up to commit: " + Repo.Head.Tip.Sha, EventID.RepoPush); return null; } catch (Exception e) { WriteWarning("Repo push failed: " + e.ToString(), EventID.RepoPushFail); return e.ToString(); } } } /// /// Check if the is configured for SSH pushing /// /// if the see cref="ServerInstance"/> is configured for SSH pushing, otherwise bool SSHAuth() { return File.Exists(RelativePath(PrivateKeyPath)) && File.Exists(RelativePath(PublicKeyPath)); } /// /// SSH credentials callback. Properly sets up SSH keys for an authorization operation /// /// Ignored /// The username to use for the operation /// The for the operation /// The proper for the operation Credentials GenerateGitCredentials(string url, string usernameFromUrl, SupportedCredentialTypes types) { var user = usernameFromUrl ?? "git"; if (types == SupportedCredentialTypes.UsernameQuery) return new UsernameQueryCredentials() { Username = user, }; return new SshUserKeyCredentials() { Username = user, PrivateKey = RelativePath(PrivateKeyPath), PublicKey = RelativePath(PublicKeyPath), Passphrase = "", }; } /// public string GenerateChangelog(out string error) { return GenerateChangelogImpl(out error); } /// /// Updates the html changelog /// /// on success, error on failure /// If , prevents a recursive call to this function after updating pip dependencies /// The output of the python script public string GenerateChangelogImpl(out string error, bool recurse = false) { var RConfig = GetCachedRepoConfig(); if (RConfig == null) { error = null; return "Error loading changelog config!"; } if (!RConfig.ChangelogSupport) { error = null; return null; } string ChangelogPy = RConfig.PathToChangelogPy; if (!Exists()) { error = "Repo does not exist!"; return null; } lock (RepoLock) { if (RepoBusy) { error = "Repo is busy!"; return null; } if (!File.Exists(Path.Combine(RelativePath(RepoPath), ChangelogPy))) { error = "Missing changelog generation script!"; return null; } var Config = Properties.Settings.Default; var PythonFile = Path.Combine(Config.PythonPath, "python.exe"); if (!File.Exists(PythonFile)) { error = "Cannot locate python!"; return null; } try { string result; int exitCode; using (var python = new Process()) { python.StartInfo.FileName = PythonFile; python.StartInfo.Arguments = String.Format("{0} {1}", ChangelogPy, RConfig.ChangelogPyArguments); python.StartInfo.UseShellExecute = false; python.StartInfo.WorkingDirectory = new DirectoryInfo(RelativePath(RepoPath)).FullName; python.StartInfo.RedirectStandardOutput = true; python.Start(); using (StreamReader reader = python.StandardOutput) { result = reader.ReadToEnd(); } python.WaitForExit(); exitCode = python.ExitCode; } if (exitCode != 0) { if (recurse || RConfig.PipDependancies.Count == 0) { error = "Script failed!"; return result; } //update pip deps and try again string PipFile = Config.PythonPath + "/scripts/pip.exe"; foreach(var I in RConfig.PipDependancies) using (var pip = new Process()) { pip.StartInfo.FileName = PipFile; pip.StartInfo.Arguments = "install " + I; pip.StartInfo.UseShellExecute = false; pip.StartInfo.RedirectStandardOutput = true; pip.Start(); using (StreamReader reader = pip.StandardOutput) { result += "\r\n---BEGIN-PIP-OUTPUT---\r\n" + reader.ReadToEnd(); } pip.WaitForExit(); if (pip.ExitCode != 0) { error = "Script and pip failed!"; return result; } } //and recurse return GenerateChangelogImpl(out error, true); } error = null; WriteInfo("Changelog generated" + error, EventID.RepoChangelog); return result; } catch (Exception e) { error = e.ToString(); WriteWarning("Changelog generation failed: " + error, EventID.RepoChangelogFail); return null; } } } /// public void SetAutoUpdateInterval(ulong newInterval) { lock (autoUpdateTimer) { autoUpdateTimer.Stop(); if (newInterval > 0) { autoUpdateTimer.Interval = newInterval * 60 * 1000; //convert from minutes to ms autoUpdateTimer.Start(); } } Config.AutoUpdateInterval = newInterval; } /// public ulong AutoUpdateInterval() { return Config.AutoUpdateInterval; } /// /// Runs on the configured of and tries to and the /// /// A /// The event arguments private void AutoUpdateTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { if (UpdateImpl(true, false) == null) { Compile(true); } } /// /// Updates with the staged 's SHA /// /// The commit SHA that was staged void UpdateLiveSha(string newSha) { if (LoadRepo() != null) return; var B = Repo.Branches[LiveTrackingBranch]; if (B != null) Repo.Branches.Remove(B); Repo.CreateBranch(LiveTrackingBranch, newSha); } /// /// Gets the current staged or live commit SHA /// /// The current staged or live commit SHA public string LiveSha() { var B = Repo.Branches[LiveTrackingBranch]; return B != null ? B.Tip.Sha : "UNKNOWN"; } } }