Skip to content

Commit 7305871

Browse files
authored
Merge pull request josegonzalez#117 from QuicketSolutions/master
Add option for Releases
2 parents ad8c5b8 + baf7b1a commit 7305871

2 files changed

Lines changed: 84 additions & 2 deletions

File tree

README.rst

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@ CLI Usage is as follows::
3232
[--watched] [--followers] [--following] [--all]
3333
[--issues] [--issue-comments] [--issue-events] [--pulls]
3434
[--pull-comments] [--pull-commits] [--labels] [--hooks]
35-
[--milestones] [--repositories] [--bare] [--lfs]
36-
[--wikis] [--gists] [--starred-gists] [--skip-existing]
35+
[--milestones] [--repositories] [--releases] [--assets]
36+
[--bare] [--lfs] [--wikis] [--gists] [--starred-gists]
37+
[--skip-existing]
3738
[-L [LANGUAGES [LANGUAGES ...]]] [-N NAME_REGEX]
3839
[-H GITHUB_HOST] [-O] [-R REPOSITORY] [-P] [-F]
3940
[--prefer-ssh] [-v]
@@ -76,6 +77,8 @@ CLI Usage is as follows::
7677
authenticated)
7778
--milestones include milestones in backup
7879
--repositories include repository clone in backup
80+
--releases include repository releases' information without assets or binaries
81+
--assets include assets alongside release information; only applies if including releases
7982
--bare clone bare repositories
8083
--lfs clone LFS repositories (requires Git LFS to be
8184
installed, https://git-lfs.github.com)

bin/github-backup

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import subprocess
1818
import sys
1919
import time
2020
import platform
21+
PY2 = False
2122
try:
2223
# python 3
2324
from urllib.parse import urlparse
@@ -26,14 +27,19 @@ try:
2627
from urllib.error import HTTPError, URLError
2728
from urllib.request import urlopen
2829
from urllib.request import Request
30+
from urllib.request import HTTPRedirectHandler
31+
from urllib.request import build_opener
2932
except ImportError:
3033
# python 2
34+
PY2 = True
3135
from urlparse import urlparse
3236
from urllib import quote as urlquote
3337
from urllib import urlencode
3438
from urllib2 import HTTPError, URLError
3539
from urllib2 import urlopen
3640
from urllib2 import Request
41+
from urllib2 import HTTPRedirectHandler
42+
from urllib2 import build_opener
3743

3844
from github_backup import __version__
3945

@@ -303,6 +309,15 @@ def parse_args():
303309
parser.add_argument('--keychain-account',
304310
dest='osx_keychain_item_account',
305311
help='OSX ONLY: account field of password item in OSX keychain that holds the personal access or OAuth token')
312+
parser.add_argument('--releases',
313+
action='store_true',
314+
dest='include_releases',
315+
help='include release information, not including assets or binaries'
316+
)
317+
parser.add_argument('--assets',
318+
action='store_true',
319+
dest='include_assets',
320+
help='include assets alongside release information; only applies if including releases')
306321
return parser.parse_args()
307322

308323

@@ -532,6 +547,39 @@ def _request_url_error(template, retry_timeout):
532547
return False
533548

534549

550+
class S3HTTPRedirectHandler(HTTPRedirectHandler):
551+
"""
552+
A subclassed redirect handler for downloading Github assets from S3.
553+
554+
urllib will add the Authorization header to the redirected request to S3, which will result in a 400,
555+
so we should remove said header on redirect.
556+
"""
557+
def redirect_request(self, req, fp, code, msg, headers, newurl):
558+
if PY2:
559+
# HTTPRedirectHandler is an old style class
560+
request = HTTPRedirectHandler.redirect_request(self, req, fp, code, msg, headers, newurl)
561+
else:
562+
request = super(S3HTTPRedirectHandler, self).redirect_request(req, fp, code, msg, headers, newurl)
563+
del request.headers['Authorization']
564+
return request
565+
566+
567+
def download_file(url, path, auth):
568+
request = Request(url)
569+
request.add_header('Accept', 'application/octet-stream')
570+
request.add_header('Authorization', 'Basic '.encode('ascii') + auth)
571+
opener = build_opener(S3HTTPRedirectHandler)
572+
response = opener.open(request)
573+
574+
chunk_size = 16 * 1024
575+
with open(path, 'wb') as f:
576+
while True:
577+
chunk = response.read(chunk_size)
578+
if not chunk:
579+
break
580+
f.write(chunk)
581+
582+
535583
def get_authenticated_user(args):
536584
template = 'https://{0}/user'.format(get_github_api_host(args))
537585
data = retrieve_data(args, template, single_request=True)
@@ -699,6 +747,10 @@ def backup_repositories(args, output_directory, repositories):
699747
if args.include_hooks or args.include_everything:
700748
backup_hooks(args, repo_cwd, repository, repos_template)
701749

750+
if args.include_releases or args.include_everything:
751+
backup_releases(args, repo_cwd, repository, repos_template,
752+
include_assets=args.include_assets or args.include_everything)
753+
702754
if args.incremental:
703755
open(last_update_path, 'w').write(last_update)
704756

@@ -880,6 +932,33 @@ def backup_hooks(args, repo_cwd, repository, repos_template):
880932
log_info("Unable to read hooks, skipping")
881933

882934

935+
def backup_releases(args, repo_cwd, repository, repos_template, include_assets=False):
936+
repository_fullname = repository['full_name']
937+
938+
# give release files somewhere to live & log intent
939+
release_cwd = os.path.join(repo_cwd, 'releases')
940+
log_info('Retrieving {0} releases'.format(repository_fullname))
941+
mkdir_p(repo_cwd, release_cwd)
942+
943+
query_args = {}
944+
945+
release_template = '{0}/{1}/releases'.format(repos_template, repository_fullname)
946+
releases = retrieve_data(args, release_template, query_args=query_args)
947+
948+
# for each release, store it
949+
log_info('Saving {0} releases to disk'.format(len(releases)))
950+
for release in releases:
951+
release_name = release['tag_name']
952+
output_filepath = os.path.join(release_cwd, '{0}.json'.format(release_name))
953+
with codecs.open(output_filepath, 'w+', encoding='utf-8') as f:
954+
json_dump(release, f)
955+
956+
if include_assets:
957+
assets = retrieve_data(args, release['assets_url'])
958+
for asset in assets:
959+
download_file(asset['url'], os.path.join(release_cwd, asset['name']), get_auth(args))
960+
961+
883962
def fetch_repository(name,
884963
remote_url,
885964
local_dir,

0 commit comments

Comments
 (0)