Skip to content

Instantly share code, notes, and snippets.

@maejok-xx
Last active August 29, 2023 03:56
Show Gist options
  • Save maejok-xx/ba8de8d512d52155275389aa845d3fbe to your computer and use it in GitHub Desktop.
Save maejok-xx/ba8de8d512d52155275389aa845d3fbe to your computer and use it in GitHub Desktop.
Easily Fork multiple GitHub repositories (and set them private) using GitHub API
# Author: https://github.com/maej20
# ---- repolist.txt EXAMPLE ----
#
# user1/repo-name
# # someoneelse/skipped-repo-name
# user524/you-get-the-repo
# CoolDude_69420/For_Good-Measure-repo
#
# -------- END EXAMPLE --------
from colorama import Fore, Style, Back
import requests
import json
import time
# NewLine Separated List of repos to fork (format: 0wnerName/the-repo-name)
REPO_LIST = 'repolist.txt'
# GitHub Developer Personal Access Token (https://github.com/settings/tokens)
API_KEY = '<API_KEY>'
# GitHub Username you're forking to
USERNAME = '<YOUR_GITHUB_USERNAME>'
# GitHub Organization you're forking yo (blank if not using an organization)
ORGANIZATION = '<YOUR_GITHUB_ORGANIZATION>'
# Make new forks private?
PRIVATE = 0
# Hide commented repos outputs? (use # to comment a line/repo)
HIDE_SKIPPED_REPOS = 0
# Prevent actually sending the payload and just runs the logic?
TESTING = 0
with open(REPO_LIST) as f:
errors = []
repos = list(filter(None, (line.rstrip() for line in f)))
for count, line in enumerate(repos):
if line[0] == "#": # skip commented out repos
if not HIDE_SKIPPED_REPOS: print(Style.DIM + "{}/{} - skipped - {}".format(count+1, len(repos), line.split("#", 1)[1].strip()) + Style.RESET_ALL)
continue
repoStr = line.split("/", 1)
repo_owner = repoStr[0]
repo_name = repoStr[1]
print("{}/{} - Forking - {}".format(count+1, len(repos), line))
url = "https://api.github.com/repos/{}/{}/forks".format(repo_owner,repo_name)
payload = json.dumps({
"owner": repo_owner,
"repo": repo_name,
"organization": ORGANIZATION
})
headers = {
'Accept': 'application/json',
'Authorization': 'Bearer {}'.format(API_KEY),
'Content-Type': 'application/json',
'X-GitHub-Api-Version': '2022-11-28'
}
if not TESTING:
response = requests.request("POST", url, headers=headers, data=payload)
if not response.status_code == 200 and not response.status_code == 202:
errors.append([line, response.status_code, "FORKING"])
print(Fore.YELLOW + 'Forking Error: {} [{}]'.format(response.status_code, line) + Style.RESET_ALL)
if PRIVATE:
url = "https://api.github.com/repos/{}/{}".format(ORGANIZATION if ORGANIZATION else USERNAME, repo_name)
payload = json.dumps({
"visibility": "private"
})
headers = {
'Accept': 'application/json',
'owner': ORGANIZATION if ORGANIZATION else USERNAME,
'new_owner': ORGANIZATION if ORGANIZATION else USERNAME,
'repo': repo_name,
'Authorization': 'Bearer {}'.format(API_KEY),
'Content-Type': 'application/json',
'X-GitHub-Api-Version': '2022-11-28'
}
if not TESTING:
response = requests.request("PATCH", url, headers=headers, data=payload)
if not response.status_code == 200 and not response.status_code == 202:
errors.append([line, response.status_code, "PRIVATING"])
print(Fore.YELLOW + 'Privating Error: {} [{}]'.format(response.status_code, line) + Style.RESET_ALL)
print(' ')
print(Fore.GREEN + "DONE!" + Style.RESET_ALL)
if len(errors):
print(' ')
print("Completed with errors: ")
for count, error in enumerate(errors):
print(Fore.YELLOW, end ='')
print('{}/{} {} had a {} error while {}'.format(count, len(errors), error[0], error[1], error[2]))
print(' ' + Style.RESET_ALL)
print(Fore.BLUE + "Note: errors don't mean the repo(s) didn't fork or get their visibility set to private. Verify for yourself." + Style.RESET_ALL)
@maejok-xx
Copy link
Author

This was way less painful to write than manually forking a project with 80+ repos.
I hope it helps somebody else.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment