diff --git a/README.md b/README.md new file mode 100644 index 0000000..d8d4be7 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# GitHub Repository Bookup Tool + +This is a lightweight command line interface for backing up a GitHub user's repositories. Depending on permissions provided +during authentication, the tool can backup public and private repositories. + +The tool is writting in C# and uses the Octokit.net library to interact with the GitHub API. + +## Usage + +The tool is a command line interface. It can be run from the command line or from a batch file. The tool requires a GitHub +Personal Access Token to be provided as a command line argument. The token can be generated from the GitHub website. It is +recommended that the user only provide the minimum permissions required to backup the repositories. GitHub provides a fine-grained +access token generator that makes this very simple. \ No newline at end of file diff --git a/repo-backup-util.sln b/repo-backup-util.sln new file mode 100644 index 0000000..d5d8ff0 --- /dev/null +++ b/repo-backup-util.sln @@ -0,0 +1,25 @@ + +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 diff --git a/repo-backup-util/Program.cs b/repo-backup-util/Program.cs new file mode 100644 index 0000000..466aa16 --- /dev/null +++ b/repo-backup-util/Program.cs @@ -0,0 +1,30 @@ +// See https://aka.ms/new-console-template for more information +using Octokit; +using RepoBackupUtil.Lib; + +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 username = GitHub.SetCredentialsToken(github); + User user = await GitHub.GetUser(username, github); + + // get all repos for user + IEnumerable repos = await GitHub.GetRepos(github) ?? throw new Exception("No repos found"); + + // organize and ensure all repositories are unique + HashSet uniqueRepos = GitHub.GetUniqueRepos(repos); + + // derive repository count and clone urls + var privateRepos = uniqueRepos.Where(r => r.Private); + var publicRepos = uniqueRepos.Where(r => !r.Private); + + Console.WriteLine($"Found {privateRepos.Count()} private repos and {publicRepos.Count()} public repos."); + + var cloneUrls = uniqueRepos.Select(r => r.CloneUrl); +} + +await Main(); \ No newline at end of file diff --git a/repo-backup-util/lib/GitHub.cs b/repo-backup-util/lib/GitHub.cs new file mode 100644 index 0000000..f40c39d --- /dev/null +++ b/repo-backup-util/lib/GitHub.cs @@ -0,0 +1,85 @@ +using Octokit; + +namespace RepoBackupUtil.Lib +{ + public static class GitHub + { + public static string SetCredentialsToken(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 SetCredentialsToken(github); + } + else + { + AuthenticationType authType = AuthenticationType.Bearer; + Credentials auth = new(token, authType); + github.Credentials = auth; + Console.WriteLine("Successfully authenticated with GitHub."); + return github.User.Current().Result.Login; + } + } + + public static string SetOAuthCredentials(GitHubClient github) + { + Console.WriteLine("Provide OAuth Client Secret: "); + string? clientSecret = Console.ReadLine(); + + if (clientSecret == null) + { + Console.WriteLine("Received invalid input. Try again:"); + return SetOAuthCredentials(github); + } + else + { + Credentials auth = new(clientSecret, AuthenticationType.Oauth); + github.Credentials = auth; + Console.WriteLine("Successfully authenticated with GitHub."); + return github.User.Current().Result.Login; + } + } + + public static async Task GetUser(string username, GitHubClient github) + { + Console.WriteLine("Checking for user..."); + User? user = await github.User.Get(username); + + 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?> GetRepos(GitHubClient github) + { + Console.WriteLine("Loading repos..."); + var repos = await github.Repository.GetAllForCurrent(); + return repos; + } + + public static HashSet GetUniqueRepos(IEnumerable repos) + { + HashSet uniqueRepos = []; + int i = 0; + + foreach (var repo in repos) + { + bool added = uniqueRepos.Add(repo); + Console.WriteLine($"{++i} - {(added ? "added" : null)}{repo.Name} - {(repo.Private ? "Private" : "Public")}"); + } + + return uniqueRepos; + } + } +} diff --git a/repo-backup-util/lib/Volumes.cs b/repo-backup-util/lib/Volumes.cs new file mode 100644 index 0000000..6f59b67 --- /dev/null +++ b/repo-backup-util/lib/Volumes.cs @@ -0,0 +1,10 @@ +namespace RepoBackupUtil.Lib +{ + public static class Volumes + { + public static IEnumerable GetVolumes() + { + return DriveInfo.GetDrives(); + } + } +} diff --git a/repo-backup-util/repo-backup-util.csproj b/repo-backup-util/repo-backup-util.csproj new file mode 100644 index 0000000..14c587d --- /dev/null +++ b/repo-backup-util/repo-backup-util.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + RepoBackupUtil + enable + enable + + + + + + +