diff --git a/src/ScmBackup/Configuration/ConfigOptions.cs b/src/ScmBackup/Configuration/ConfigOptions.cs index a3332b0..c5029af 100644 --- a/src/ScmBackup/Configuration/ConfigOptions.cs +++ b/src/ScmBackup/Configuration/ConfigOptions.cs @@ -9,5 +9,24 @@ namespace ScmBackup.Configuration /// class ConfigOptions { + public ConfigOptions() + { + this.Git = new GitOptions(); + } + + public GitOptions Git { get; set; } + } + + class GitOptions + { + /// + /// Git implementation that will be used for all Git operations + /// + public string Implementation { get; set; } + + /// + /// LFS implementation for Git + /// + public bool UseLfs { get; set; } } -} +} \ No newline at end of file diff --git a/src/ScmBackup/Scm/CommandLineScm.cs b/src/ScmBackup/Scm/CommandLineScm.cs index cc4525f..0b8926b 100644 --- a/src/ScmBackup/Scm/CommandLineScm.cs +++ b/src/ScmBackup/Scm/CommandLineScm.cs @@ -33,6 +33,11 @@ internal abstract class CommandLineScm : IScm /// public abstract string GetVersionNumber(); + /// + /// Checks whether git lfs is installed on this computer + /// + public abstract bool LFSIsOnThisComputer(); + /// /// Executes the command line tool. /// @@ -50,12 +55,29 @@ protected CommandLineResult ExecuteCommand(string args) info.RedirectStandardError = true; info.RedirectStandardOutput = true; + //var proc = Process.Start(info); + //var result = new CommandLineResult(); + //result.StandardError = proc.StandardError.ReadToEnd(); + //result.StandardOutput = proc.StandardOutput.ReadToEnd(); + //proc.WaitForExit(); + + //result.ExitCode = proc.ExitCode; + //return result; + + //deadlock fix to make function 'RepositoryContainsLFS' run without deadlock var proc = Process.Start(info); var result = new CommandLineResult(); - result.StandardError = proc.StandardError.ReadToEnd(); - result.StandardOutput = proc.StandardOutput.ReadToEnd(); + var output = new System.Text.StringBuilder(); + var error = new System.Text.StringBuilder(); + + proc.OutputDataReceived += (sender, eventArgs) => output.AppendLine(eventArgs.Data); + proc.ErrorDataReceived += (sender, eventArgs) => error.AppendLine(eventArgs.Data); + proc.BeginOutputReadLine(); + proc.BeginErrorReadLine(); proc.WaitForExit(); + result.StandardOutput = output.ToString(); + result.StandardError = error.ToString(); result.ExitCode = proc.ExitCode; return result; } @@ -102,6 +124,16 @@ public void PullFromRemote(string remoteUrl, string directory) /// public abstract void PullFromRemote(string remoteUrl, string directory, ScmCredentials credentials); + /// + /// Pulls LFS files from a remote repository into a local folder. + /// + public abstract void PullLFSFromRemote(string remoteUrl, string directory, ScmCredentials credentials); + + /// + /// Checks whether the repo contains LFS files + /// + public abstract bool RepositoryContainsLFS(string directory); + /// /// Checks whether the repo in this directory contains a commit with this ID /// Must be implemented in the child classes by calling ExecuteCommand and checking the result. @@ -138,5 +170,6 @@ private void GetExecutable() } } } + } } diff --git a/src/ScmBackup/Scm/GitScm.cs b/src/ScmBackup/Scm/GitScm.cs index 9eb2150..04476da 100644 --- a/src/ScmBackup/Scm/GitScm.cs +++ b/src/ScmBackup/Scm/GitScm.cs @@ -54,6 +54,20 @@ public override string GetVersionNumber() throw new InvalidOperationException(result.Output); } + public override bool LFSIsOnThisComputer() + { + var result = this.ExecuteCommand("lfs version"); + + if (result.Successful) + { + return true; //git lfs found: run lfs commands + } + else + { + return false; //git lfs not found: do not run lfs commands + } + } + public override bool DirectoryIsRepository(string directory) { // SCM Backup uses bare repos only, so we don't need to check for non-bare repos at all @@ -103,7 +117,7 @@ public override void PullFromRemote(string remoteUrl, string directory, ScmCrede { throw new InvalidOperationException(string.Format(Resource.ScmTargetDirectoryNotEmpty, directory)); } - + this.CreateRepository(directory); } @@ -111,7 +125,7 @@ public override void PullFromRemote(string remoteUrl, string directory, ScmCrede { remoteUrl = this.CreateRepoUrlWithCredentials(remoteUrl, credentials); } - + string cmd = string.Format("-C \"{0}\" fetch --force --prune {1} refs/heads/*:refs/heads/* refs/tags/*:refs/tags/*", directory, remoteUrl); var result = this.ExecuteCommand(cmd); @@ -119,6 +133,51 @@ public override void PullFromRemote(string remoteUrl, string directory, ScmCrede { throw new InvalidOperationException(result.Output); } + + if (LFSIsOnThisComputer()) + { + if (RepositoryContainsLFS(directory)) + { + PullLFSFromRemote(remoteUrl, directory, credentials); + } + } + } + + public override void PullLFSFromRemote(string remoteUrl, string directory, ScmCredentials credentials) + { + if (credentials != null) + { + remoteUrl = this.CreateRepoUrlWithCredentials(remoteUrl, credentials); + } + + string cmd = string.Format("-C \"{0}\" lfs fetch --all {1}", directory, remoteUrl); // git -C *DIR* lfs fetch --all *REMOTE* + var result = this.ExecuteCommand(cmd); + + if (!result.Successful) + { + throw new InvalidOperationException(result.Output); + } + } + + public override bool RepositoryContainsLFS(string directory) //test if repo contains lfs files + { + //do not run if LFSIsOnThisComputer = false + string cmd = string.Format("-C \"{0}\" lfs ls-files", directory); + var result = this.ExecuteCommand(cmd); + + if (!result.Successful) + { + throw new InvalidOperationException(result.Output); + } + + if (String.IsNullOrWhiteSpace(result.Output)) + { + return false; //no lfs files found, continuing + } + else + { + return true; //lfs files found, backing them up + } } public override bool RepositoryContainsCommit(string directory, string commitid) @@ -154,4 +213,4 @@ public string CreateRepoUrlWithCredentials(string url, ScmCredentials credential return uri.ToString(); } } -} \ No newline at end of file +} diff --git a/src/ScmBackup/Scm/IScm.cs b/src/ScmBackup/Scm/IScm.cs index f3def49..105043b 100644 --- a/src/ScmBackup/Scm/IScm.cs +++ b/src/ScmBackup/Scm/IScm.cs @@ -25,6 +25,11 @@ internal interface IScm /// string GetVersionNumber(); + /// + /// Checks whether the git LFS is present on this computer + /// + bool LFSIsOnThisComputer(); + /// /// Checks whether the given directory is a repository /// @@ -57,9 +62,20 @@ internal interface IScm /// void PullFromRemote(string remoteUrl, string directory, ScmCredentials credentials); + /// + /// Pulls all LFS files from a remote repository into a local folder. + /// + void PullLFSFromRemote(string remoteUrl, string directory, ScmCredentials credentials); + + /// + /// Checks whether the repo in this directory contains LFS files + /// + bool RepositoryContainsLFS(string directory); + /// /// Checks whether the repo in this directory contains a commit with this ID /// bool RepositoryContainsCommit(string directory, string commitid); + } } diff --git a/src/ScmBackup/Scm/MercurialScm.cs b/src/ScmBackup/Scm/MercurialScm.cs index 7e187f0..ce4adcf 100644 --- a/src/ScmBackup/Scm/MercurialScm.cs +++ b/src/ScmBackup/Scm/MercurialScm.cs @@ -56,6 +56,11 @@ public override string GetVersionNumber() throw new InvalidOperationException(result.Output); } + public override bool LFSIsOnThisComputer() + { + return false; + } + public override bool DirectoryIsRepository(string directory) { string hgdir = Path.Combine(directory, ".hg"); @@ -141,6 +146,16 @@ public override bool RepositoryContainsCommit(string directory, string commitid) return false; } + public override void PullLFSFromRemote(string remoteUrl, string directory, ScmCredentials credentials) + { + throw new NotImplementedException(); + } + + public override bool RepositoryContainsLFS(string directory) + { + return false; + } + private string RemoveCredentialsFromUrl(string url, ScmCredentials credentials) { // Issue #19: if credentials are passed via --config, remove the username from the URL (the Bitbucket API returns the clone URL with username, for example). @@ -164,5 +179,6 @@ private string CreateParametersWithCredentials(ScmCredentials credentials, strin return string.Format(" --config auth.x.prefix={0} --config auth.x.username={1} --config auth.x.password={2}", baseurl, credentials.User, credentials.Password); } + } }