Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5eb89a953c | |||
| 0861986563 | |||
| ed03194471 | |||
| 29643ae86d | |||
| a3580ffc16 |
22
git-anchor.sln
Normal file
22
git-anchor.sln
Normal file
@@ -0,0 +1,22 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "git-anchor", "git-anchor\git-anchor.csproj", "{9B181097-6E9E-4FA1-8167-BFE34C03D08D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{9B181097-6E9E-4FA1-8167-BFE34C03D08D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9B181097-6E9E-4FA1-8167-BFE34C03D08D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9B181097-6E9E-4FA1-8167-BFE34C03D08D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9B181097-6E9E-4FA1-8167-BFE34C03D08D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
15
git-anchor/Actions/Base.cs
Normal file
15
git-anchor/Actions/Base.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System.Diagnostics;
|
||||
namespace GitAnchor.Actions;
|
||||
|
||||
public abstract class BaseAction {
|
||||
public virtual void Run()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public virtual async Task<ActivityStatusCode> RunAsync()
|
||||
{
|
||||
await Task.Delay(0);
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
99
git-anchor/Actions/Create.cs
Normal file
99
git-anchor/Actions/Create.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using GitAnchor.Lib;
|
||||
using Octokit;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace GitAnchor.Actions;
|
||||
public class Create : BaseAction
|
||||
{
|
||||
public new static async Task<ActivityStatusCode> RunAsync()
|
||||
{
|
||||
string[] args = Environment.GetCommandLineArgs();
|
||||
|
||||
string? token = args.FirstOrDefault(arg => arg.StartsWith("--token"));
|
||||
|
||||
string? backupName = args.FirstOrDefault(
|
||||
arg => arg.StartsWith("--name") || arg.StartsWith("-n"))?
|
||||
.Split("=")[1].Trim();
|
||||
|
||||
bool verbose = args.Any(arg => arg.StartsWith("--verbose")
|
||||
|| arg.StartsWith("-v"));
|
||||
|
||||
// find or configure the default backup directory
|
||||
DirectoryInfo backupDir = FileSystemTools.SetUpMainBackupFile()
|
||||
?? throw new Exception("Encountered a problem creating main backup directory");
|
||||
|
||||
// create backup file
|
||||
DirectoryInfo? anchorDir = FileSystemTools.SetUpAnchor(backupDir, backupName);
|
||||
|
||||
if (anchorDir == null)
|
||||
{
|
||||
Console.WriteLine("Could not create anchor directory.");
|
||||
return ActivityStatusCode.Error;
|
||||
}
|
||||
|
||||
// authenticate with github
|
||||
GitHubClient github = GitHub.CreateClient();
|
||||
token = GitHub.Authenticate(github, token);
|
||||
|
||||
// load accessible repositories
|
||||
IEnumerable<Repository> repos = await GitHub.GetRepos(github) ?? throw new Exception("No repos found");
|
||||
|
||||
// organize and ensure all repositories are unique
|
||||
HashSet<Repository> uniqueRepos = GitHub.GetUniqueRepos(repos);
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
// derive repository count and clone urls
|
||||
var privateRepos = uniqueRepos.Where(r => r.Private);
|
||||
var publicRepos = uniqueRepos.Where(r => !r.Private);
|
||||
var cloneUrls = uniqueRepos.Select(r => r.CloneUrl);
|
||||
|
||||
Console.WriteLine($"Found {privateRepos.Count()} private repos and {publicRepos.Count()} public repos.");
|
||||
}
|
||||
|
||||
// get user confirmation to proceed
|
||||
Console.WriteLine($"\n\nProceed to clone {repos.Count()} repositories? (y/n)");
|
||||
string? response = Console.ReadLine();
|
||||
|
||||
if (response?.ToLower() != "y")
|
||||
{
|
||||
Console.WriteLine("Aborting...");
|
||||
return ActivityStatusCode.Ok;
|
||||
}
|
||||
|
||||
// set up, update directories for hosting projects
|
||||
HashSet<Repository> newProjects = Volumes.PopulateBackupDirectories(uniqueRepos, anchorDir);
|
||||
|
||||
// identify all work to be done and prepare as tasks
|
||||
List<Task> taskPool = [];
|
||||
|
||||
foreach (Repository newProject in newProjects)
|
||||
{
|
||||
GitHub.GithubRepoOptions options = new()
|
||||
{
|
||||
BackupDir = anchorDir,
|
||||
Repo = newProject,
|
||||
Token = token,
|
||||
User = github.User.Current().Result,
|
||||
};
|
||||
|
||||
taskPool.Add(new Task(() => GitHub.CloneProject(options)));
|
||||
}
|
||||
|
||||
Console.WriteLine($"Preparing to clone {taskPool.Count} repositories. Starting...");
|
||||
|
||||
// start a timer to track progress
|
||||
using var reporter = new ProgressReporter();
|
||||
reporter.Start();
|
||||
|
||||
Parallel.ForEach(taskPool, task => task.Start());
|
||||
Task.WaitAll([.. taskPool]);
|
||||
|
||||
reporter.Stop();
|
||||
|
||||
var report = reporter.ToString();
|
||||
Console.WriteLine(report);
|
||||
|
||||
return ActivityStatusCode.Ok;
|
||||
}
|
||||
}
|
||||
37
git-anchor/Actions/Help.cs
Normal file
37
git-anchor/Actions/Help.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace GitAnchor.Actions;
|
||||
|
||||
public class Help : BaseAction
|
||||
{
|
||||
public static new ActivityStatusCode Run()
|
||||
{
|
||||
Console.WriteLine("\nGitAnchor - a tool for performing scoped backups of GitHub repositories based on credentials and configuration.");
|
||||
Console.WriteLine("Usage:\n");
|
||||
|
||||
Console.WriteLine("git-anchor create [--verbose, -v] [--name <name>] [--token <token>]");
|
||||
Console.WriteLine(" Creates a new backup directory and clones all repositories accessible by the provided token.");
|
||||
Console.WriteLine(" --name <name> The name of the backup directory to create. Defaults to the current date.");
|
||||
Console.WriteLine(" --token <token> The GitHub token to use for authentication.");
|
||||
Console.WriteLine(" --verbose Prints verbose output.\n");
|
||||
Console.WriteLine("Example: git-anchor create -v --name=cool-new-backup --token=(your token)");
|
||||
|
||||
Console.WriteLine("git-anchor pull [--name <name>] [--verbose, -v]");
|
||||
Console.WriteLine(" Pulls all repositories in an existing backup directory.");
|
||||
Console.WriteLine(" --name <name> The name of the backup directory to pull from. Defaults to the current date.");
|
||||
Console.WriteLine(" --verbose Prints verbose output.\n");
|
||||
Console.WriteLine("Example: git-anchor pull -v --name=cool-new-backup");
|
||||
|
||||
Console.WriteLine("git-anchor list");
|
||||
Console.WriteLine(" Lists all existing backups.\n");
|
||||
Console.WriteLine("Example: git-anchor list");
|
||||
|
||||
Console.WriteLine("git-anchor find [--regex, -e] [--name, -n <name>]");
|
||||
Console.WriteLine(" Finds an existing backup matching the provided pattern.");
|
||||
Console.WriteLine(" --regex, -e Find your backup directory based on a Regex search.");
|
||||
Console.WriteLine(" --name, -n The name of the backup directory to pull from. Defaults to the current date.\n\n");
|
||||
|
||||
return ActivityStatusCode.Ok;
|
||||
}
|
||||
|
||||
}
|
||||
35
git-anchor/Actions/List.cs
Normal file
35
git-anchor/Actions/List.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System.Diagnostics;
|
||||
using GitAnchor.Lib;
|
||||
|
||||
namespace GitAnchor.Actions;
|
||||
class List : BaseAction
|
||||
{
|
||||
public static new ActivityStatusCode Run()
|
||||
{
|
||||
// get main backup directory
|
||||
var backupLocation = FileSystemTools.MainBackupFile;
|
||||
var backupDir = new DirectoryInfo(backupLocation ?? throw new Exception("No main backup found"));
|
||||
string first = "";
|
||||
|
||||
// get all directories in main backup directory
|
||||
var directories = backupDir.GetDirectories();
|
||||
|
||||
Console.WriteLine("Found the following backups:\n");
|
||||
|
||||
// print all directories
|
||||
foreach (var dir in directories)
|
||||
{
|
||||
if (first == "") first = dir.Name;
|
||||
long size = FileSystemTools.GetBackupSize(dir);
|
||||
long sizeInGBs = Volumes.GetSizeInGB(size);
|
||||
long sizeInMBs = Volumes.GetSizeInMB(size);
|
||||
Console.WriteLine($"{dir.Name} - {(sizeInGBs > 0 ? sizeInGBs : sizeInMBs)}{(sizeInGBs > 0 ? "GB" : "MB")}");
|
||||
}
|
||||
|
||||
Console.WriteLine("\nUse git-anchor pull --name=<name> to pull a backup.");
|
||||
Console.WriteLine($"Example: git-anchor pull --name={first} -v\n");
|
||||
|
||||
return ActivityStatusCode.Ok;
|
||||
}
|
||||
|
||||
}
|
||||
80
git-anchor/Actions/Pull.cs
Normal file
80
git-anchor/Actions/Pull.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using System.Diagnostics;
|
||||
using GitAnchor.Lib;
|
||||
using GitAnchor.Actions;
|
||||
using Octokit;
|
||||
|
||||
namespace GitAnchor.Actions
|
||||
{
|
||||
internal class Pull : BaseAction
|
||||
{
|
||||
public new static async Task<ActivityStatusCode> RunAsync()
|
||||
{
|
||||
string[] args = Environment.GetCommandLineArgs();
|
||||
|
||||
// get main backup
|
||||
var backupLocation = FileSystemTools.MainBackupFile;
|
||||
var mainBackupDir = new DirectoryInfo(backupLocation ?? throw new Exception("No main backup found"));
|
||||
|
||||
// act on provided arguments
|
||||
string? token = args.FirstOrDefault(arg => arg.StartsWith("--token"));
|
||||
bool verbose = args.Any(arg => arg.StartsWith("--verbose") || arg.StartsWith("-v"));
|
||||
string anchorName = args.FirstOrDefault(arg => arg.StartsWith("--name") || arg.StartsWith("-n"))?.Split("=")[1].Trim()
|
||||
?? throw new Exception("No anchor name provided");
|
||||
|
||||
// find (or create) the anchor in question
|
||||
var anchor = Volumes.GetAnchor(mainBackupDir, anchorName) ?? FileSystemTools.SetUpAnchor(mainBackupDir, anchorName);
|
||||
var projectDirectories = anchor?.GetDirectories();
|
||||
|
||||
if (projectDirectories == null)
|
||||
{
|
||||
Console.WriteLine("No projects found in anchor.");
|
||||
return ActivityStatusCode.Error;
|
||||
}
|
||||
|
||||
// set up our github connection
|
||||
GitHubClient github = GitHub.CreateClient();
|
||||
token = GitHub.Authenticate(github, token);
|
||||
|
||||
var repoList = await GitHub.GetRepos(github);
|
||||
|
||||
// set up a task pool for all pull jobs
|
||||
List<Task> taskPool = [];
|
||||
|
||||
// iterate through all projects and prepare jobs
|
||||
foreach (DirectoryInfo backupDir in projectDirectories)
|
||||
{
|
||||
var repo = repoList?.Where(r => r.Name == backupDir.Name).FirstOrDefault();
|
||||
if (repo == null) continue;
|
||||
|
||||
if (verbose) Console.WriteLine($"Preparing pull task for {repo.Name}");
|
||||
|
||||
var options = new GitHub.GithubRepoOptions
|
||||
{
|
||||
Repo = repo,
|
||||
BackupDir = backupDir,
|
||||
Verbose = verbose,
|
||||
};
|
||||
|
||||
Task pullTask = new(() => GitHub.PullExistingRepo(options));
|
||||
taskPool.Add(pullTask);
|
||||
}
|
||||
|
||||
// start progress tracker
|
||||
using var reporter = new ProgressReporter();
|
||||
reporter.Start();
|
||||
|
||||
Parallel.ForEach(taskPool, task => task.Start());
|
||||
Task.WaitAll([.. taskPool]);
|
||||
|
||||
reporter.Stop();
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
var report = reporter.ToString();
|
||||
Console.WriteLine(report);
|
||||
}
|
||||
|
||||
return ActivityStatusCode.Ok;
|
||||
}
|
||||
}
|
||||
}
|
||||
44
git-anchor/Actions/Status.cs
Normal file
44
git-anchor/Actions/Status.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System.Diagnostics;
|
||||
using GitAnchor.Lib;
|
||||
|
||||
namespace GitAnchor.Actions;
|
||||
|
||||
public class Status : BaseAction
|
||||
{
|
||||
public new static async Task<ActivityStatusCode> RunAsync()
|
||||
{
|
||||
string[] args = Environment.GetCommandLineArgs();
|
||||
|
||||
// get main backup directory
|
||||
var backupLocation = FileSystemTools.MainBackupFile;
|
||||
var anchorName = CommandLineTools.ReadAnchorName();
|
||||
var backupDir = new DirectoryInfo(Path.Join(backupLocation, anchorName));
|
||||
|
||||
// get all directories in main backup directory
|
||||
var directories = backupDir.GetDirectories();
|
||||
|
||||
List<Task> taskPool = [];
|
||||
|
||||
foreach (var dir in directories)
|
||||
{
|
||||
GitHub.GithubRepoOptions options = new()
|
||||
{
|
||||
AnchorPath = Path.Join(backupDir.FullName, dir.Name),
|
||||
BackupDir = backupDir,
|
||||
ProjectPath = dir.Name,
|
||||
};
|
||||
|
||||
taskPool.Add(Task.Run(() => GitHub.QueryStatus(options)));
|
||||
}
|
||||
|
||||
using var reporter = new ProgressReporter();
|
||||
reporter.Start();
|
||||
|
||||
await Parallel.ForEachAsync(taskPool, async(task, cancellationToken) => await task);
|
||||
|
||||
reporter.Stop();
|
||||
Console.WriteLine(reporter.ToString());
|
||||
|
||||
return ActivityStatusCode.Ok;
|
||||
}
|
||||
}
|
||||
10
git-anchor/Lib/Callable.cs
Normal file
10
git-anchor/Lib/Callable.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace GitAnchor.Lib;
|
||||
|
||||
public partial class ActionResult
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
delegate Task<T> Callable<T>();
|
||||
45
git-anchor/Lib/CommandLine.cs
Normal file
45
git-anchor/Lib/CommandLine.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using Octokit;
|
||||
|
||||
namespace GitAnchor.Lib;
|
||||
|
||||
public static class CommandLineTools {
|
||||
public static string? ReadAnchorName()
|
||||
{
|
||||
return Environment.GetCommandLineArgs().FirstOrDefault(arg => arg.StartsWith("--name") || arg.StartsWith("-n"))?
|
||||
.Split("=")[1].Trim();
|
||||
}
|
||||
|
||||
public static int ReadAsInt()
|
||||
{
|
||||
string? input = Console.ReadLine();
|
||||
|
||||
while (input == null)
|
||||
{
|
||||
input = Console.ReadLine();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return int.Parse(input);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine("Invaid input. Please enter a number: ");
|
||||
return ReadAsInt();
|
||||
}
|
||||
}
|
||||
|
||||
public static string ReadAccessToken(GitHubClient github)
|
||||
{
|
||||
Console.WriteLine("Please enter your Github Personal Access Token: ");
|
||||
string? token = Console.ReadLine();
|
||||
|
||||
if (token == null)
|
||||
{
|
||||
Console.WriteLine("Received invalid input. Try again:");
|
||||
return ReadAccessToken(github);
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
}
|
||||
152
git-anchor/Lib/FileSystem.cs
Normal file
152
git-anchor/Lib/FileSystem.cs
Normal file
@@ -0,0 +1,152 @@
|
||||
using System.Runtime.InteropServices;
|
||||
namespace GitAnchor.Lib;
|
||||
|
||||
public static class FileSystemTools {
|
||||
public static string DefaultAnchorPath
|
||||
{
|
||||
get => DateTime.Now.ToString("Mddy");
|
||||
}
|
||||
|
||||
public static bool MainBackupExists
|
||||
{
|
||||
get => MainBackupFile != null;
|
||||
}
|
||||
|
||||
public static bool AnchorExists(string anchorName)
|
||||
{
|
||||
return MainBackupExists && Directory.Exists($"{MainBackupFile}/anchorName");
|
||||
}
|
||||
|
||||
public static string? MainBackupFile
|
||||
{
|
||||
get => Volumes.FindMainBackupFile();
|
||||
}
|
||||
|
||||
public static long GetBackupSize(DirectoryInfo dir)
|
||||
{
|
||||
long size = 0;
|
||||
foreach (FileInfo fi in dir.GetFiles("*", SearchOption.AllDirectories))
|
||||
{
|
||||
size += fi.Length;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
public static DirectoryInfo? SetUpMainBackupFile()
|
||||
{
|
||||
IEnumerable<DriveInfo> volumes = Volumes.GetVolumes();
|
||||
DriveInfo? selection = Volumes.SelectFromList(volumes);
|
||||
|
||||
if (selection == null)
|
||||
{
|
||||
Console.WriteLine("No selection found");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (MainBackupFile != null) return new DirectoryInfo(MainBackupFile);
|
||||
|
||||
bool isWindows = OperatingSystem.IsWindows();
|
||||
bool isMacOS = OperatingSystem.IsMacOS();
|
||||
bool isLinux = OperatingSystem.IsLinux();
|
||||
|
||||
if (isWindows)
|
||||
return CreateMainWindowsDirectory(selection);
|
||||
else if (isLinux || isMacOS)
|
||||
return CreateMainUnixDirectory(selection);
|
||||
else
|
||||
throw new Exception("Unsupported operating system");
|
||||
}
|
||||
|
||||
public static DirectoryInfo? SetUpAnchor(DirectoryInfo backupDir, string? initialInput)
|
||||
{
|
||||
string defaultName = FileSystemTools.DefaultAnchorPath;
|
||||
string? backupName = initialInput;
|
||||
|
||||
if (backupName == null)
|
||||
{
|
||||
Console.WriteLine($"Enter a name for this backup: (default {defaultName})");
|
||||
backupName = Console.ReadLine();
|
||||
if (backupName == "") backupName = defaultName;
|
||||
}
|
||||
|
||||
if (backupName == null || backupName == "") throw new Exception("No backup name provided");
|
||||
|
||||
// create backup file
|
||||
return Volumes.CreateAnchor(backupDir, backupName);
|
||||
}
|
||||
|
||||
private static DirectoryInfo? CreateMainWindowsDirectory(DriveInfo volume, string? location = ".git-anchor")
|
||||
{
|
||||
OSPlatform platform;
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
platform = OSPlatform.Windows;
|
||||
}
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
platform = OSPlatform.OSX;
|
||||
}
|
||||
else if (OperatingSystem.IsLinux())
|
||||
{
|
||||
platform = OSPlatform.Linux;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Unsupported operating system");
|
||||
}
|
||||
|
||||
bool isSystemDrive;
|
||||
bool isExternalDrive;
|
||||
|
||||
if (platform == OSPlatform.Windows)
|
||||
{
|
||||
isSystemDrive = volume.Name == "C:\\";
|
||||
isExternalDrive = volume.DriveType == DriveType.Removable && !isSystemDrive;
|
||||
}
|
||||
else
|
||||
{
|
||||
isSystemDrive = volume.Name == "/";
|
||||
isExternalDrive = volume.DriveType == DriveType.Removable && !isSystemDrive;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (isSystemDrive)
|
||||
{
|
||||
Console.WriteLine("Using system drive. Directing you to user folder...");
|
||||
|
||||
string? userPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||
DirectoryInfo userDir = new(userPath);
|
||||
|
||||
Console.WriteLine($"Checking for folder `.ghbackups` at `{userDir.FullName}`...");
|
||||
DirectoryInfo? backupDir = userDir.CreateSubdirectory(".ghbackups");
|
||||
|
||||
return backupDir;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Using {volume.Name}. Directing you to root folder...");
|
||||
|
||||
string rootPath = volume.RootDirectory.FullName;
|
||||
|
||||
DirectoryInfo rootDir = new(rootPath);
|
||||
Console.WriteLine($"Checking for folder `.ghbackups` at `{rootDir.FullName}`...");
|
||||
|
||||
DirectoryInfo backupDir = Directory.CreateDirectory($"{rootDir.FullName}.ghbackups");
|
||||
return backupDir;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e.Message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static DirectoryInfo? CreateMainUnixDirectory(DriveInfo volume)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,28 @@
|
||||
using Octokit;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace RepoBackupUtil.Lib
|
||||
namespace GitAnchor.Lib
|
||||
{
|
||||
public class GitHub
|
||||
{
|
||||
public static string GetCredentialsToken(GitHubClient github)
|
||||
public static GitHubClient CreateClient()
|
||||
{
|
||||
Console.WriteLine("Please enter your Github Personal Access Token: ");
|
||||
string? token = Console.ReadLine();
|
||||
|
||||
if (token == null)
|
||||
{
|
||||
Console.WriteLine("Received invalid input. Try again:");
|
||||
return GetCredentialsToken(github);
|
||||
ProductHeaderValue productHeaderValue = new("git-anchor");
|
||||
return new(productHeaderValue);
|
||||
}
|
||||
else
|
||||
|
||||
public static string Authenticate(GitHubClient github, string? initialToken = null)
|
||||
{
|
||||
string token = initialToken ?? CommandLineTools.ReadAccessToken(github);
|
||||
AuthenticationType authType = AuthenticationType.Bearer;
|
||||
Credentials auth = new(token, authType);
|
||||
github.Credentials = auth;
|
||||
Console.WriteLine("Successfully authenticated with GitHub.");
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
public static string SetOAuthCredentials(GitHubClient github)
|
||||
{
|
||||
Console.WriteLine("Provide OAuth Client Secret: ");
|
||||
@@ -83,21 +81,27 @@ namespace RepoBackupUtil.Lib
|
||||
return projectsToUpdate;
|
||||
}
|
||||
|
||||
public record CloneProjectOptions : ICloneProject
|
||||
public record GithubRepoOptions
|
||||
{
|
||||
public User User { get; init; } = default!;
|
||||
public Repository Repo { get; init; } = default!;
|
||||
public DirectoryInfo BackupDir { get; init; } = default!;
|
||||
public string Token { get; init; } = default!;
|
||||
public User? User { get; init; } = default!;
|
||||
public Repository? Repo { get; init; } = default!;
|
||||
public string? ProjectPath { get; init; }
|
||||
public string? Token { get; init; }
|
||||
public string? AnchorPath { get; init; }
|
||||
public bool? Verbose { get; init; } = false;
|
||||
}
|
||||
|
||||
public static void CloneProject(ICloneProject options)
|
||||
public static void CloneProject(GithubRepoOptions options)
|
||||
{
|
||||
if (options.User == null) throw new Exception("User not found");
|
||||
if (options.Repo == null) throw new Exception("Repository not found");
|
||||
|
||||
string cloneString = "";
|
||||
cloneString += $"clone https://{options.Token}@github.com/";
|
||||
cloneString += $"{options.User.Login}/{options.Repo.Name} .";
|
||||
|
||||
Console.WriteLine($"Cloning {options.Repo.Name}...");
|
||||
if (options.Verbose ?? false) Console.WriteLine($"Cloning {options.Repo.Name}...");
|
||||
|
||||
using (Process process = new())
|
||||
{
|
||||
@@ -105,20 +109,22 @@ namespace RepoBackupUtil.Lib
|
||||
{
|
||||
FileName = "git",
|
||||
Arguments = cloneString,
|
||||
WorkingDirectory = $"{options.BackupDir}/{options.Repo.Name}",
|
||||
WorkingDirectory = Path.Join(options.BackupDir.FullName, options.Repo.Name),
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true
|
||||
};
|
||||
|
||||
process.Start();
|
||||
Console.WriteLine(process.StandardOutput.ReadToEnd());
|
||||
if (options.Verbose ?? false) Console.WriteLine(process.StandardOutput.ReadToEnd());
|
||||
process.WaitForExit();
|
||||
Console.WriteLine($"Clone complete for {options.Repo.Name}");
|
||||
};
|
||||
}
|
||||
|
||||
public static void UpdateExistingRepo(Repository repo, DirectoryInfo backupDir)
|
||||
public static void PullExistingRepo(GithubRepoOptions options)
|
||||
{
|
||||
Console.WriteLine($"Preparing update task for {repo.Name}...");
|
||||
if (options.Repo == null) throw new Exception("Repository not found");
|
||||
Console.WriteLine($"Pulling {options.Repo.Name}...");
|
||||
|
||||
using (Process process = new())
|
||||
{
|
||||
@@ -126,7 +132,27 @@ namespace RepoBackupUtil.Lib
|
||||
{
|
||||
FileName = "git",
|
||||
Arguments = "pull",
|
||||
WorkingDirectory = $"{backupDir}/{repo.Name}",
|
||||
WorkingDirectory = Path.Join(options.BackupDir.FullName, options.Repo.Name),
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true
|
||||
};
|
||||
|
||||
process.Start();
|
||||
if (options.Verbose ?? false) Console.WriteLine(process.StandardOutput.ReadToEnd());
|
||||
process.WaitForExit();
|
||||
Console.WriteLine($"Pull complete for {options.Repo.Name}");
|
||||
};
|
||||
}
|
||||
|
||||
public static void QueryStatus(GithubRepoOptions options)
|
||||
{
|
||||
using (Process process = new())
|
||||
{
|
||||
process.StartInfo = new()
|
||||
{
|
||||
FileName = "git",
|
||||
Arguments = "status",
|
||||
WorkingDirectory = Path.Join(options.BackupDir.FullName, options.Repo?.Name ?? options.ProjectPath ?? throw new Exception("Unable to determine working directory")),
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true
|
||||
};
|
||||
31
git-anchor/Lib/ProgressReporter.cs
Normal file
31
git-anchor/Lib/ProgressReporter.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace GitAnchor.Lib;
|
||||
|
||||
public class ProgressReporter : Stopwatch, IDisposable
|
||||
{
|
||||
public ProgressReporter()
|
||||
{
|
||||
// progress = new();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.Stop();
|
||||
// progress = null!;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
// progress.ProgressChanged += (sender, message) => Console.WriteLine(message);
|
||||
|
||||
var elapsed = this.ElapsedMilliseconds < 1000
|
||||
? this.ElapsedMilliseconds
|
||||
: this.ElapsedMilliseconds / 1000;
|
||||
|
||||
string unit = this.ElapsedMilliseconds < 1000 ? "ms" : "s";
|
||||
|
||||
return $"All tasks completed in {elapsed}{unit}. Exiting...";
|
||||
}
|
||||
}
|
||||
5
git-anchor/Lib/Union.cs
Normal file
5
git-anchor/Lib/Union.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
namespace GitAnchor.Lib;
|
||||
|
||||
public class Union<T1, T2>
|
||||
{
|
||||
}
|
||||
124
git-anchor/Lib/Volumes.cs
Normal file
124
git-anchor/Lib/Volumes.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
using Octokit;
|
||||
using System.Collections;
|
||||
|
||||
namespace GitAnchor.Lib;
|
||||
public static class Volumes
|
||||
{
|
||||
public static DriveInfo[] GetVolumes()
|
||||
{
|
||||
try
|
||||
{
|
||||
return DriveInfo.GetDrives();
|
||||
}
|
||||
catch (UnauthorizedAccessException e)
|
||||
{
|
||||
Console.WriteLine("Unauthorized access to volumes. Please run as administrator.");
|
||||
Console.WriteLine(e.Message);
|
||||
return [];
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e.Message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private static DriveInfo? GetSelectedDrive(Hashtable options)
|
||||
{
|
||||
int inputAsInt = CommandLineTools.ReadAsInt();
|
||||
|
||||
if (!options.ContainsKey(inputAsInt)) return null;
|
||||
|
||||
foreach (DictionaryEntry entry in options)
|
||||
{
|
||||
if (entry.Key.Equals(inputAsInt))
|
||||
{
|
||||
return entry.Value != null ? (DriveInfo)entry.Value : null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static DriveInfo? SelectFromList(IEnumerable<DriveInfo> volumes)
|
||||
{
|
||||
int i = 0;
|
||||
Hashtable options = [];
|
||||
|
||||
// prepare table and present options to user
|
||||
Console.WriteLine("Select the drive you want to backup to (enter a number): \n");
|
||||
foreach (DriveInfo volume in volumes)
|
||||
{
|
||||
++i;
|
||||
string option = $"{i}: {volume.Name} ({GetSizeInGB(volume)}GB available)";
|
||||
Console.WriteLine(option);
|
||||
options.Add(i, volume);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// parse user input and return appropiate drive info
|
||||
return GetSelectedDrive(options);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e.Message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static DirectoryInfo? CreateAnchor(DirectoryInfo backupDir, string anchorName)
|
||||
{
|
||||
return backupDir.CreateSubdirectory(anchorName);
|
||||
}
|
||||
|
||||
public static DirectoryInfo? GetAnchor(DirectoryInfo backupDir, string anchorName)
|
||||
{
|
||||
return backupDir.GetDirectories().FirstOrDefault(dir => dir.Name == anchorName);
|
||||
}
|
||||
|
||||
public static string? FindMainBackupFile()
|
||||
{
|
||||
// find a folder entitled `.ghbackups`
|
||||
// check the system drive first, then scan for additional drives and check the root of each
|
||||
|
||||
var userFolder = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||
bool backupExistsInUserFolder = Directory.Exists($"{userFolder}/.ghbackups");
|
||||
|
||||
if (backupExistsInUserFolder) return $"{userFolder}/.ghbackups";
|
||||
|
||||
DriveInfo[] volumes = GetVolumes();
|
||||
foreach (DriveInfo volume in volumes)
|
||||
{
|
||||
bool backupExistsInRoot = Directory.Exists($"{volume.RootDirectory.FullName}/.ghbackups");
|
||||
if (backupExistsInRoot) return $"{volume.RootDirectory.FullName}/.ghbackups";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static long GetSizeInMB(long bytes) => bytes / 1024 / 1024;
|
||||
|
||||
public static long GetSizeInGB(long bytes) => bytes / 1024 / 1024 / 1024;
|
||||
public static long GetSizeInGB(DriveInfo volume) => volume.TotalSize / 1024 / 1024 / 1024;
|
||||
|
||||
public static HashSet<Repository> PopulateBackupDirectories(HashSet<Repository> repos, DirectoryInfo backupDir)
|
||||
{
|
||||
HashSet<Repository> newProjects = [];
|
||||
|
||||
foreach (Repository repo in repos)
|
||||
{
|
||||
bool exists = Directory.Exists($"{backupDir.FullName}/{repo.Name}");
|
||||
bool hasContents = exists && Directory.EnumerateFileSystemEntries(
|
||||
$"{backupDir.FullName}/{repo.Name}").Any();
|
||||
|
||||
if (exists && hasContents) continue;
|
||||
|
||||
DirectoryInfo? repoDir = backupDir.CreateSubdirectory(repo.Name);
|
||||
Console.WriteLine($"Created directory for project {repoDir.FullName}");
|
||||
newProjects.Add(repo);
|
||||
}
|
||||
|
||||
return newProjects;
|
||||
}
|
||||
}
|
||||
49
git-anchor/Program.cs
Normal file
49
git-anchor/Program.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System.Collections;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using GitAnchor.Actions;
|
||||
|
||||
string[] inputArgs = Environment.GetCommandLineArgs();
|
||||
|
||||
|
||||
// create pointers for entrypoints for each action
|
||||
var createFn = typeof(Create).GetMethod("RunAsync", BindingFlags.Static | BindingFlags.Public);
|
||||
var pullFn = typeof(Pull).GetMethod("RunAsync", BindingFlags.Static | BindingFlags.Public);
|
||||
var statusFn = typeof(Status).GetMethod("RunAsync", BindingFlags.Static | BindingFlags.Public);
|
||||
var listFn = typeof(List).GetMethod("Run", BindingFlags.Static | BindingFlags.Public);
|
||||
var helpFn = typeof(Help).GetMethod("Run", BindingFlags.Static | BindingFlags.Public);
|
||||
|
||||
var AllOptions = new Hashtable()
|
||||
{
|
||||
{ "create", createFn },
|
||||
{ "pull", pullFn },
|
||||
{ "status", statusFn },
|
||||
{ "list", listFn },
|
||||
{ "help", helpFn },
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
foreach (string a in inputArgs)
|
||||
{
|
||||
var titleCase = a.ToUpperInvariant();
|
||||
|
||||
if (AllOptions.ContainsKey(a))
|
||||
{
|
||||
var func = (MethodInfo)AllOptions[a];
|
||||
var result = func?.Invoke(null, null);
|
||||
|
||||
if (result is Task<ActivityStatusCode> task)
|
||||
{
|
||||
return (int)task.Result;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return (int)Help.Run();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.Message);
|
||||
return (int)ActivityStatusCode.Error;
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RootNamespace>RepoBackupUtil</RootNamespace>
|
||||
<RootNamespace>GitAnchor</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
@@ -1,25 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.8.34309.116
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "repo-backup-util", "repo-backup-util\repo-backup-util.csproj", "{352652F3-958B-416B-B5F5-CDC36B0AA0C4}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{352652F3-958B-416B-B5F5-CDC36B0AA0C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{352652F3-958B-416B-B5F5-CDC36B0AA0C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{352652F3-958B-416B-B5F5-CDC36B0AA0C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{352652F3-958B-416B-B5F5-CDC36B0AA0C4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {46CA2A1B-A03D-4D97-9DDF-4BCABF53C54A}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -1,82 +0,0 @@
|
||||
// See https://aka.ms/new-console-template for more information
|
||||
using Octokit;
|
||||
using RepoBackupUtil.Lib;
|
||||
using System.Diagnostics;
|
||||
|
||||
static async Task Main()
|
||||
{
|
||||
// initialize our github client
|
||||
ProductHeaderValue productHeaderValue = new("repo-backup-util");
|
||||
GitHubClient github = new(productHeaderValue);
|
||||
|
||||
// check for user with received input
|
||||
string token = GitHub.GetCredentialsToken(github);
|
||||
|
||||
string username = github.User.Current().Result.Login;
|
||||
User user = await GitHub.GetUser(username, github);
|
||||
|
||||
// get all repos for user
|
||||
IEnumerable<Repository> repos = await GitHub.GetRepos(github) ?? throw new Exception("No repos found");
|
||||
|
||||
// organize and ensure all repositories are unique
|
||||
HashSet<Repository> uniqueRepos = GitHub.GetUniqueRepos(repos);
|
||||
|
||||
// derive repository count and clone urls
|
||||
var privateRepos = uniqueRepos.Where(r => r.Private);
|
||||
var publicRepos = uniqueRepos.Where(r => !r.Private);
|
||||
var cloneUrls = uniqueRepos.Select(r => r.CloneUrl);
|
||||
|
||||
Console.WriteLine($"Found {privateRepos.Count()} private repos and {publicRepos.Count()} public repos.");
|
||||
|
||||
// create backup directory
|
||||
DirectoryInfo backupDir = Volumes.SetUpBackupFile() ?? throw new Exception("Error setting up backup directory");
|
||||
|
||||
// set up, update directories for hosting projects
|
||||
HashSet<Repository> newProjects = Volumes.PopulateBackupDirectories(uniqueRepos, backupDir);
|
||||
IEnumerable<Repository> projectsToUpdate = GitHub.GetProjectsToUpdate(uniqueRepos, backupDir);
|
||||
|
||||
Console.WriteLine(projectsToUpdate.Count() + " projects to update.");
|
||||
Console.WriteLine(newProjects.Count + " new projects to clone.");
|
||||
|
||||
// identify all work to be done and prepare as tasks
|
||||
List<Task> taskPool = [];
|
||||
|
||||
foreach (Repository project in projectsToUpdate)
|
||||
{
|
||||
taskPool.Add(new Task(() => GitHub.UpdateExistingRepo(project, backupDir)));
|
||||
}
|
||||
|
||||
foreach (Repository newProject in newProjects)
|
||||
{
|
||||
ICloneProject options = new GitHub.CloneProjectOptions
|
||||
{
|
||||
BackupDir = backupDir,
|
||||
Repo = newProject,
|
||||
Token = token,
|
||||
User = user
|
||||
};
|
||||
|
||||
taskPool.Add(new Task(() => GitHub.CloneProject(options)));
|
||||
}
|
||||
|
||||
Console.WriteLine($"Prepared {taskPool.Count} tasks. Starting...");
|
||||
|
||||
// start a timer to track progress
|
||||
Stopwatch stopwatch = new();
|
||||
stopwatch.Start();
|
||||
|
||||
// execute all tasks
|
||||
Parallel.ForEach(taskPool, task => task.Start());
|
||||
Task.WaitAll(taskPool.ToArray());
|
||||
|
||||
stopwatch.Stop();
|
||||
double elapsed = stopwatch.ElapsedMilliseconds < 1000
|
||||
? stopwatch.ElapsedMilliseconds
|
||||
: stopwatch.ElapsedMilliseconds / 1000;
|
||||
|
||||
string unit = stopwatch.ElapsedMilliseconds < 1000 ? "ms" : "s";
|
||||
|
||||
Console.WriteLine($"All tasks completed in {elapsed}{unit}. Exiting...");
|
||||
}
|
||||
|
||||
await Main();
|
||||
@@ -1,10 +0,0 @@
|
||||
using Octokit;
|
||||
|
||||
namespace RepoBackupUtil.Lib;
|
||||
public interface ICloneProject
|
||||
{
|
||||
Repository Repo { get; }
|
||||
DirectoryInfo BackupDir { get; }
|
||||
User User { get; }
|
||||
string Token { get; }
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
using Octokit;
|
||||
using System.Collections;
|
||||
|
||||
namespace RepoBackupUtil.Lib
|
||||
{
|
||||
public static class Volumes
|
||||
{
|
||||
public static DirectoryInfo? SetUpBackupFile()
|
||||
{
|
||||
IEnumerable<DriveInfo> volumes = GetVolumes();
|
||||
DriveInfo? selection = SelectFromList(volumes);
|
||||
if (selection == null)
|
||||
{
|
||||
Console.WriteLine("No selection found");
|
||||
return null;
|
||||
}
|
||||
|
||||
var backupDir = CreateBackupDirectory(selection);
|
||||
return backupDir;
|
||||
}
|
||||
|
||||
public static IEnumerable<DriveInfo> GetVolumes()
|
||||
{
|
||||
try
|
||||
{
|
||||
return DriveInfo.GetDrives();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e.Message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private static int ReadInput()
|
||||
{
|
||||
string? input = Console.ReadLine();
|
||||
|
||||
while (input == null)
|
||||
{
|
||||
Console.WriteLine("Please enter a number: ");
|
||||
input = Console.ReadLine();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return int.Parse(input);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine("Invaid input. Please enter a number: ");
|
||||
return ReadInput();
|
||||
}
|
||||
}
|
||||
|
||||
private static DriveInfo? GetSelection(Hashtable options)
|
||||
{
|
||||
int inputAsInt = ReadInput();
|
||||
|
||||
if (!options.ContainsKey(inputAsInt))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (DictionaryEntry entry in options)
|
||||
{
|
||||
if (entry.Key.Equals(inputAsInt))
|
||||
{
|
||||
Console.WriteLine($"value: {entry.Value}");
|
||||
Console.WriteLine($"type: {entry.Value?.GetType()}");
|
||||
return entry.Value != null ? (DriveInfo)entry.Value : null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static DriveInfo? SelectFromList(IEnumerable<DriveInfo> volumes)
|
||||
{
|
||||
int i = 0;
|
||||
Hashtable options = [];
|
||||
|
||||
// prepare table and present options to user
|
||||
Console.WriteLine("Select the drive you want to backup to (enter a number): \n");
|
||||
foreach (DriveInfo volume in volumes)
|
||||
{
|
||||
++i;
|
||||
string option = $"{i}: {volume.Name} ({GetSizeInGB(volume)}GB available)";
|
||||
Console.WriteLine(option);
|
||||
options.Add(i, volume);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// parse user input and return appropiate drive info
|
||||
return GetSelection(options);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e.Message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static double GetSizeInGB(DriveInfo volume) => volume.TotalSize / 1024 / 1024 / 1024;
|
||||
|
||||
public static DirectoryInfo? CreateBackupDirectory(DriveInfo volume)
|
||||
{
|
||||
bool isSystemDrive = volume.DriveType == DriveType.Fixed;
|
||||
|
||||
try
|
||||
{
|
||||
if (isSystemDrive)
|
||||
{
|
||||
Console.WriteLine("Using system drive. Directing you to user folder...");
|
||||
|
||||
string? userPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||
DirectoryInfo userDir = new(userPath);
|
||||
|
||||
Console.WriteLine($"Checking for folder `.ghbackups` at `{userDir.FullName}`...");
|
||||
DirectoryInfo? backupDir = userDir.CreateSubdirectory(".ghbackups");
|
||||
|
||||
return backupDir;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Using {volume.Name}. Directing you to root folder...");
|
||||
|
||||
DirectoryInfo? rootDir = volume.RootDirectory;
|
||||
Console.WriteLine($"Checking for folder `.ghbackups` at `{rootDir.FullName}`...");
|
||||
DirectoryInfo? backupDir = rootDir.CreateSubdirectory(".ghbackups");
|
||||
|
||||
return backupDir;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e.Message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static HashSet<Repository> PopulateBackupDirectories(HashSet<Repository> repos, DirectoryInfo backupDir)
|
||||
{
|
||||
HashSet<Repository> newProjects = [];
|
||||
|
||||
foreach (Repository repo in repos)
|
||||
{
|
||||
bool exists = Directory.Exists($"{backupDir.FullName}/{repo.Name}");
|
||||
bool hasContents = exists && Directory.EnumerateFileSystemEntries(
|
||||
$"{backupDir.FullName}/{repo.Name}").Any();
|
||||
|
||||
if (exists && hasContents) continue;
|
||||
|
||||
DirectoryInfo? repoDir = backupDir.CreateSubdirectory(repo.Name);
|
||||
Console.WriteLine($"Created directory for project {repoDir.FullName}");
|
||||
newProjects.Add(repo);
|
||||
}
|
||||
|
||||
return newProjects;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user