Skip to content

Instantly share code, notes, and snippets.

@BlythMeister
Last active July 8, 2021 14:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save BlythMeister/5b44c826f97119efa141b7d043ef9116 to your computer and use it in GitHub Desktop.
Save BlythMeister/5b44c826f97119efa141b7d043ef9116 to your computer and use it in GitHub Desktop.
Fork Sync
<Query Kind="Program">
<NuGetReference>Octokit</NuGetReference>
<Namespace>Octokit</Namespace>
<Namespace>System.Threading.Tasks</Namespace>
</Query>
GitHubClient githubClient;
/* START SETTINGS */
string githubToken = "[TOKEN]";
string githubUserName = "BlythMeister";
string basePath = "F:\\ForkSync";
/* END SETTINGS */
async Task Main()
{
githubClient = new GitHubClient(new ProductHeaderValue("ForkSync"));
githubClient.Credentials = new Credentials(githubUserName, githubToken);
Console.WriteLine($"Base Directory For Sync: {basePath}");
Directory.CreateDirectory(basePath);
try
{
var forks = await GetForks();
Console.WriteLine($"Found {forks.Count()} to update");
foreach (var fork in forks)
{
var repoDir = Path.Combine(basePath, fork.Name);
try
{
Console.WriteLine("-----------------------------");
try
{
Console.WriteLine($"Syncing branch {fork.ForkDefaultBranch} to {fork.UpstreamDefaultBranch} for repo {fork.Name}");
if(!Directory.Exists(repoDir))
{
RunGit(basePath, $"clone --origin fork --quiet {fork.ForkSsh} {repoDir}");
RunGit(repoDir, $"remote add upstream {fork.UpstreamSsh}");
}
RunGit(repoDir, $"fetch upstream --quiet");
RunGit(repoDir, $"merge upstream/{fork.UpstreamDefaultBranch} --ff --quiet");
RunGit(repoDir, $"push --quiet");
}
catch (Exception e)
{
Console.WriteLine($"Error on git sync fork {fork.Name} - {e.Message}");
}
try
{
Console.WriteLine("Removing Fully Merged Branches");
await RemoveFullyMergedBranches(fork.Id, fork.ForkDefaultBranch);
}
catch (Exception e)
{
Console.WriteLine($"Error on remove branches on fork {fork.Name} - {e.Message}");
}
}
catch (Exception e)
{
Console.WriteLine($"Error on fork {fork.Name} - {e.Message}");
}
}
}
catch (Exception e)
{
Console.WriteLine("Error Occured");
Console.WriteLine(e.ToString());
}
}
private async Task<List<(long Id, string Name, string UpstreamDefaultBranch, string UpstreamSsh, string ForkDefaultBranch, string ForkSsh)>> GetForks()
{
var currentUser = await githubClient.User.Current();
var currentUserRepos = await githubClient.Repository.GetAllForCurrent();
var repos = new List<(long Id, string Name, string UpstreamDefaultBranch, string UpstreamSsh, string ForkDefaultBranch, string ForkSsh)>();
foreach (var repo in currentUserRepos.Where(x => x.Fork && x.Owner.Id == currentUser.Id))
{
var repoFull = await githubClient.Repository.Get(repo.Id);
repos.Add((repo.Id, repo.Name, repoFull.Parent.DefaultBranch, repoFull.Parent.SshUrl, repoFull.DefaultBranch, repo.SshUrl));
}
return repos;
}
private async Task RemoveFullyMergedBranches(long repoId, string defaultBranchName)
{
var repo = await githubClient.Repository.Get(repoId);
var branches = await githubClient.Repository.Branch.GetAll(repoId);
var defaultBranch = branches.FirstOrDefault(x => x.Name == defaultBranchName);
if (defaultBranch != null)
{
Console.WriteLine("-----------------------------");
Console.WriteLine("Cleaning Branches On Fork");
foreach (var branch in branches.Where(x => x.Name != defaultBranchName && !x.Protected))
{
var commitDetails = await githubClient.Repository.Commit.Get(repoId, branch.Commit.Sha);
if (commitDetails.Author.Id != repo.Owner.Id && commitDetails.Commit.Author.Date < DateTime.UtcNow.AddDays(-90))
{
Console.WriteLine($"Removing branch {branch.Name} (Last commit over 90 days ago & not by me)");
await githubClient.Git.Reference.Delete(repo.Id, $"heads/{branch.Name}");
}
else
{
var compare = await githubClient.Repository.Commit.Compare(repoId, defaultBranch.Commit.Sha, branch.Commit.Sha);
if (compare.AheadBy == 0)
{
Console.WriteLine($"Removing branch {branch.Name} (Fully merged)");
await githubClient.Git.Reference.Delete(repo.Id, $"heads/{branch.Name}");
}
else
{
var differencesInFilesFound = false;
foreach (var compareFile in compare.Files)
{
if (compareFile.Additions > 0 || compareFile.Deletions > 0)
{
differencesInFilesFound = true;
break;
}
}
if (differencesInFilesFound)
{
Console.WriteLine($"Unmerged commits on {branch.Name}");
}
else
{
Console.WriteLine($"Removing branch {branch.Name} (No File Changes)");
await githubClient.Git.Reference.Delete(repo.Id, $"heads/{branch.Name}");
}
}
}
}
}
}
private void RunGit(string workingDirectory, string args, bool ignoreError = false)
{
RunCommand(workingDirectory, "git", args, ignoreError);
}
private void RunCommand(string workingDirectory, string fileName, string args, bool ignoreError)
{
Process process = new Process();
Console.WriteLine("-----------------------------");
Console.WriteLine($"WorkingDirectory: {workingDirectory}");
Console.WriteLine($"Exec: {fileName} {args}");
try
{
process.StartInfo.FileName = fileName;
process.StartInfo.WorkingDirectory = workingDirectory;
process.StartInfo.Arguments = args;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.EnableRaisingEvents = false;
process.OutputDataReceived += (sender, eventArgs) => WriteConsoleOutput("stdOut", eventArgs);
process.ErrorDataReceived += (sender, eventArgs) => WriteConsoleOutput("stdErr", eventArgs);
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
if (process.ExitCode == 0)
{
Console.WriteLine($"SUCCESS: Process exited with code of {process.ExitCode}");
}
else if (ignoreError)
{
Console.WriteLine($"WARN: Process exited with code of {process.ExitCode}");
}
else
{
Console.WriteLine($"ERROR: Process exited with code of {process.ExitCode}");
throw new Exception($"Process exited with non-zero exit code of {process.ExitCode}");
}
}
finally
{
process.Close();
}
}
private void WriteConsoleOutput(string type, DataReceivedEventArgs eventArgs)
{
if (eventArgs.Data != null)
{
Console.WriteLine($"{type} : {eventArgs.Data}");
}
}
Start-SshAgent
$startLocation = Get-Location
$RunDirectory = "F:\GIT\MyRepo" #Path to Repo
$UpstreamName = "Upstream" #Remote Name for Origin
$ForkName = "BlythMeister" #Remote Name for Fork
$BranchName = 'develop' #Branch in BOTH repos to sync
Set-Location $RunDirectory
write-host
write-host "Get Status"
&"C:\Program Files\Git\bin\git.exe" status
$continue = Read-Host 'Are You Sure You Want To Start? (y or n)'
if($continue.toLower() -ne "y")
{
write-host "Stopping Process"
break
}
write-host
write-host "Fetch All"
&"C:\Program Files\Git\bin\git.exe" fetch --all
write-host
write-host "Checkout: _$ForkName/$BranchName"
&"C:\Program Files\Git\bin\git.exe" checkout "_$ForkName/$BranchName"
write-host
write-host "Merge To Head: _$ForkName/$BranchName"
&"C:\Program Files\Git\bin\git.exe" merge "_$ForkName/$BranchName"
write-host
write-host "Merge Previous Version: $UpstreamName/$BranchName"
&"C:\Program Files\Git\bin\git.exe" merge remotes/$UpstreamName/$BranchName
write-host
write-host "Get Status"
&"C:\Program Files\Git\bin\git.exe" status
$continue = Read-Host 'Good To Push? (y or n)'
if($continue.toLower() -ne "y")
{
write-host "Stopping Process"
break
}
write-host
write-host "Push to $ForkName"
$data = "refs/heads/_$ForkName/$BranchName :refs/heads/$BranchName".Replace(" ", "")
&"C:\Program Files\Git\bin\git.exe" push --recurse-submodules=check --progress $ForkName $data
Set-Location $startLocation
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment