working proof of concept
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
// See https://aka.ms/new-console-template for more information
|
||||
using Octokit;
|
||||
using RepoBackupUtil.Lib;
|
||||
using System.Diagnostics;
|
||||
|
||||
static async Task Main()
|
||||
{
|
||||
@@ -9,7 +10,9 @@ static async Task Main()
|
||||
GitHubClient github = new(productHeaderValue);
|
||||
|
||||
// check for user with received input
|
||||
string username = GitHub.SetCredentialsToken(github);
|
||||
string token = GitHub.GetCredentialsToken(github);
|
||||
|
||||
string username = github.User.Current().Result.Login;
|
||||
User user = await GitHub.GetUser(username, github);
|
||||
|
||||
// get all repos for user
|
||||
@@ -21,24 +24,59 @@ static async Task Main()
|
||||
// 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.");
|
||||
|
||||
var cloneUrls = uniqueRepos.Select(r => r.CloneUrl);
|
||||
}
|
||||
// create backup directory
|
||||
DirectoryInfo backupDir = Volumes.SetUpBackupFile() ?? throw new Exception("Error setting up backup directory");
|
||||
|
||||
static void DriveStuff()
|
||||
// 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)
|
||||
{
|
||||
IEnumerable<DriveInfo> volumes = Volumes.GetVolumes();
|
||||
DriveInfo? selection = Volumes.SelectFromList(volumes);
|
||||
if (selection == null)
|
||||
taskPool.Add(new Task(() => GitHub.UpdateExistingRepo(project, backupDir)));
|
||||
}
|
||||
|
||||
foreach (Repository newProject in newProjects)
|
||||
{
|
||||
Console.WriteLine("No selection found");
|
||||
return;
|
||||
ICloneProject options = new GitHub.CloneProjectOptions
|
||||
{
|
||||
BackupDir = backupDir,
|
||||
Repo = newProject,
|
||||
Token = token,
|
||||
User = user
|
||||
};
|
||||
|
||||
taskPool.Add(new Task(() => GitHub.CloneProject(options)));
|
||||
}
|
||||
|
||||
var backupDir = Volumes.CreateBackupDirectory(selection);
|
||||
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...");
|
||||
}
|
||||
|
||||
DriveStuff();
|
||||
//await Main();
|
||||
await Main();
|
||||
@@ -1,10 +1,11 @@
|
||||
using Octokit;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace RepoBackupUtil.Lib
|
||||
{
|
||||
public static class GitHub
|
||||
public class GitHub
|
||||
{
|
||||
public static string SetCredentialsToken(GitHubClient github)
|
||||
public static string GetCredentialsToken(GitHubClient github)
|
||||
{
|
||||
Console.WriteLine("Please enter your Github Personal Access Token: ");
|
||||
string? token = Console.ReadLine();
|
||||
@@ -12,7 +13,7 @@ namespace RepoBackupUtil.Lib
|
||||
if (token == null)
|
||||
{
|
||||
Console.WriteLine("Received invalid input. Try again:");
|
||||
return SetCredentialsToken(github);
|
||||
return GetCredentialsToken(github);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -20,7 +21,7 @@ namespace RepoBackupUtil.Lib
|
||||
Credentials auth = new(token, authType);
|
||||
github.Credentials = auth;
|
||||
Console.WriteLine("Successfully authenticated with GitHub.");
|
||||
return github.User.Current().Result.Login;
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,27 +47,13 @@ namespace RepoBackupUtil.Lib
|
||||
public static async Task<User> GetUser(string username, GitHubClient github)
|
||||
{
|
||||
Console.WriteLine("Checking for user...");
|
||||
User? user = await github.User.Get(username);
|
||||
User user = await github.User.Get(username) ?? throw new Exception("User does not exist");
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
Console.WriteLine("User does not exist");
|
||||
string newUsername = SetCredentialsToken(github);
|
||||
return await GetUser(newUsername, github);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Found user {user.Login}. Loading repos...");
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<IReadOnlyList<Repository>?> GetRepos(GitHubClient github)
|
||||
{
|
||||
Console.WriteLine("Loading repos...");
|
||||
var repos = await github.Repository.GetAllForCurrent();
|
||||
return repos;
|
||||
}
|
||||
public static async Task<IReadOnlyList<Repository>?> GetRepos(GitHubClient github) => await github.Repository.GetAllForCurrent();
|
||||
|
||||
public static HashSet<Repository> GetUniqueRepos(IEnumerable<Repository> repos)
|
||||
{
|
||||
@@ -81,5 +68,73 @@ namespace RepoBackupUtil.Lib
|
||||
|
||||
return uniqueRepos;
|
||||
}
|
||||
|
||||
public static IEnumerable<Repository> GetProjectsToUpdate(HashSet<Repository> allRepos, DirectoryInfo backupDir)
|
||||
{
|
||||
HashSet<Repository> projectsToUpdate = [];
|
||||
foreach (DirectoryInfo dir in backupDir.EnumerateDirectories())
|
||||
{
|
||||
bool isRepo = dir.GetDirectories(".git").Any();
|
||||
if (!isRepo) continue;
|
||||
Repository? repo = allRepos.FirstOrDefault(r => r.Name == dir.Name);
|
||||
if (repo != null) projectsToUpdate.Add(repo);
|
||||
}
|
||||
|
||||
return projectsToUpdate;
|
||||
}
|
||||
|
||||
public record CloneProjectOptions : ICloneProject
|
||||
{
|
||||
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 static void CloneProject(ICloneProject options)
|
||||
{
|
||||
string cloneString = "";
|
||||
cloneString += $"clone https://{options.Token}@github.com/";
|
||||
cloneString += $"{options.User.Login}/{options.Repo.Name} .";
|
||||
|
||||
Console.WriteLine($"Cloning {options.Repo.Name}...");
|
||||
|
||||
using (Process process = new())
|
||||
{
|
||||
process.StartInfo = new()
|
||||
{
|
||||
FileName = "git",
|
||||
Arguments = cloneString,
|
||||
WorkingDirectory = $"{options.BackupDir}/{options.Repo.Name}",
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true
|
||||
};
|
||||
|
||||
process.Start();
|
||||
Console.WriteLine(process.StandardOutput.ReadToEnd());
|
||||
process.WaitForExit();
|
||||
};
|
||||
}
|
||||
|
||||
public static void UpdateExistingRepo(Repository repo, DirectoryInfo backupDir)
|
||||
{
|
||||
Console.WriteLine($"Preparing update task for {repo.Name}...");
|
||||
|
||||
using (Process process = new())
|
||||
{
|
||||
process.StartInfo = new()
|
||||
{
|
||||
FileName = "git",
|
||||
Arguments = "pull",
|
||||
WorkingDirectory = $"{backupDir}/{repo.Name}",
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true
|
||||
};
|
||||
|
||||
process.Start();
|
||||
Console.WriteLine(process.StandardOutput.ReadToEnd());
|
||||
process.WaitForExit();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
10
repo-backup-util/lib/ICloneProject.cs
Normal file
10
repo-backup-util/lib/ICloneProject.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using Octokit;
|
||||
|
||||
namespace RepoBackupUtil.Lib;
|
||||
public interface ICloneProject
|
||||
{
|
||||
Repository Repo { get; }
|
||||
DirectoryInfo BackupDir { get; }
|
||||
User User { get; }
|
||||
string Token { get; }
|
||||
}
|
||||
@@ -1,9 +1,24 @@
|
||||
using System.Collections;
|
||||
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
|
||||
@@ -95,7 +110,6 @@ namespace RepoBackupUtil.Lib
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
if (isSystemDrive)
|
||||
{
|
||||
Console.WriteLine("Using system drive. Directing you to user folder...");
|
||||
@@ -125,5 +139,25 @@ namespace RepoBackupUtil.Lib
|
||||
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