├── README.md └── github-sync.py /README.md: -------------------------------------------------------------------------------- 1 | # GitHub Sync 2 | 3 | This is a handy tool to mirror and keep updated all GitHub repositories owned 4 | by a given user into a local directory. Usage: 5 | 6 | github-sync.py --user USERNAME --directory ~/repositories/ 7 | 8 | To keep your repositories up-to-date, run this program regularly in cron. It 9 | will pick up new repositories automatically, but it will not delete repositories 10 | that cease to exist on GitHub. By default, it will not mirror repos that are 11 | forks of other repos. If you redirect stdout to /dev/null, github-sync.py should 12 | be silent except for errors, which go to stderr. 13 | 14 | Reminder: [do you know who owns your availability](http://www.whoownsmyavailability.com/)? 15 | -------------------------------------------------------------------------------- /github-sync.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import argparse 3 | import json 4 | import urllib3 5 | import os 6 | import subprocess 7 | import re 8 | 9 | parser = argparse.ArgumentParser( 10 | description='Mirror all GitHub repos for a given user locally.') 11 | parser.add_argument('--forks', dest='forks', 12 | action='store_true', help='Include forked repos') 13 | parser.add_argument('--user', dest='user', 14 | action='store', type=str, required=True, help='GitHub user name to sync') 15 | parser.add_argument('--directory', dest='directory', 16 | action='store', type=str, required=True, help='Directory to store repos in') 17 | parser.set_defaults(forks=False) 18 | args = parser.parse_args() 19 | 20 | os.chdir(args.directory) 21 | http = urllib3.PoolManager() 22 | f = http.request("GET", "https://api.github.com/users/%s/repos" % args.user) 23 | repo_list = json.loads(f.data) 24 | 25 | for repo in repo_list: 26 | name, url = repo["name"], repo["clone_url"] 27 | if re.search("[^A-z0-9-_]", name): 28 | print("Skipping invalid name", name) 29 | continue 30 | if re.search("[^A-z0-9-_:/.]", url): 31 | print ("Skipping invalid url", url) 32 | continue 33 | if repo["private"]: 34 | print ("Skipping private repo", name) 35 | continue 36 | if repo["fork"] and not args.forks: 37 | print ("Skipping forked repo", name) 38 | continue 39 | if not os.path.exists(name): 40 | print("New repo", name) 41 | subprocess.check_output(["git", "clone", "--mirror", url, name], stderr=subprocess.STDOUT) 42 | os.chdir(name) 43 | print("Updating", name) 44 | subprocess.check_output(["git", "remote", "update"]) 45 | os.chdir("..") 46 | --------------------------------------------------------------------------------