Skip to content

Instantly share code, notes, and snippets.

@Tatsh
Last active January 23, 2020 03:49
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 Tatsh/887a826ac04fd039b21f7c7026311cd8 to your computer and use it in GitHub Desktop.
Save Tatsh/887a826ac04fd039b21f7c7026311cd8 to your computer and use it in GitHub Desktop.
Automate transferring of repositories from one user to another.

How to use

  1. Change OLD_USER to the current owner of the repositories to transfer.
  2. Change NEW_USER to the new owner.
  3. Change REPOS to be an iterable of repository names (str) to transfer.
  4. Transfer one repository normally through the web browser, because you need to type your password once (this only lasts for a few hours).
  5. Extract cookies from the browser after signing into GitHub. Save in Netscape format in cookies.txt in the same location as the script. On every line in cookies.txt that starts with 'github.com' change it to '.github.com'.
  6. python gh-transfer.py because the API does not have this feature!

You can extract cookies from Chrome in Netscape format with EditThisCookie.

#!/usr/bin/env python
import string
import sys
from http.cookiejar import MozillaCookieJar
from time import sleep
from typing import cast
# pip install requests beautifulsoup4
import requests
from bs4 import BeautifulSoup as Soup
from requests.cookies import RequestsCookieJar
OLD_USER = ''
NEW_USER = ''
REPOS = ('repo1', 'repo2')
SETTINGS_URL_TEMPLATE = string.Template(
f'https://github.com/{OLD_USER}/${{repo_name}}/settings')
TRANSFER_AUTH_SELECTOR_TEMPLATE = string.Template(
f'[action="/{OLD_USER}/${{repo_name}}/settings/transfer"] '
'[name="authenticity_token"]')
TRANSFER_URL_TEMPLATE = string.Template(
f'https://github.com/{OLD_USER}/${{repo_name}}/settings/transfer')
def main() -> int:
session = requests.Session()
session.headers.update({
'user-agent':
('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 '
'(KHTML, like Gecko) Chrome/80.0.3987.53 Safari/537.36')
})
jar = MozillaCookieJar('./cookies.txt')
jar.load()
session.cookies = cast(RequestsCookieJar, jar)
for repo_name in REPOS:
print(f'{repo_name}: Fetching Settings page', file=sys.stderr)
r = session.get(SETTINGS_URL_TEMPLATE.substitute(repo_name=repo_name))
r.raise_for_status()
sleep(2)
soup = Soup(r.content.decode('utf-8'), 'lxml')
auth_token = soup.select_one(
TRANSFER_AUTH_SELECTOR_TEMPLATE.substitute(
repo_name=repo_name))['value']
print(
f'{repo_name}: Sending request to transfer (auth = {auth_token})',
file=sys.stderr)
r = session.post(TRANSFER_URL_TEMPLATE.substitute(repo_name=repo_name),
data=dict(utf8='✓',
authenticity_token=auth_token,
new_owner=NEW_USER))
r.raise_for_status()
if b'password' in r.content:
print(
f'Must authenticate this transfer: {repo_name}, then remove '
'it from REPOS',
file=sys.stderr)
return 1
sleep(2)
return 0
if __name__ == "__main__":
sys.exit(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment