Skip to content

Commit 82a4d66

Browse files
author
HR
committed
Add OAuth token support, docs and better arg parsing via docopt
1 parent e53b7ab commit 82a4d66

4 files changed

Lines changed: 91 additions & 20 deletions

File tree

Pipfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ name = "pypi"
66
[dev-packages]
77

88
[packages]
9-
requests = ">=2.20.0"
9+
requests = ">=2.20.0"
10+
docopt = "*"

Pipfile.lock

Lines changed: 8 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,35 @@
1-
# github-clone
2-
Recursively clone a GitHub repo sub-dir
1+
# GitHub clone
2+
Git clone sub-directories of a GitHub repository (at any reference) without having to clone the entire repository.
3+
Uses the GitHub API to recursively clone the sub-directories tree and files.
4+
5+
# Rate limit
6+
The GitHub API imposes a [rate limiting](https://developer.github.com/v3/#rate-limiting) of up to 60 requests per hour applies but can be increased to up to 5000 requests per hour using an _OAuth token_ (to get one see https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line).
7+
8+
# Private repositories
9+
To clone private repositories you need to supply an _OAuth token_ for an account with access to the private repository (to get one see https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line).
10+
11+
# Installation
12+
13+
14+
# Usage
15+
```
16+
GitHub clone (git.io/ghclone)
17+
18+
Usage:
19+
ghclone.py <url> [-t | --token=<token>]
20+
ghclone.py (-h | --help)
21+
ghclone.py (-v | --version)
22+
23+
Examples:
24+
ghclone.py https://github.com/HR/Crypter/tree/master/app
25+
ghclone.py https://github.com/HR/Crypter/tree/dev/app
26+
ghclone.py https://github.com/HR/Crypter/tree/v3.1.0/build
27+
ghclone.py https://github.com/HR/Crypter/tree/cbee54dd720bb8aaa3a2111fcec667ca5f700510/build
28+
ghclone.py https://github.com/HR/Picturesque/tree/master/app/src -t li50d67757gm20556d53f08126215725a698560b
29+
30+
Options:
31+
-h --help Show this screen.
32+
-v --version Show version.
33+
-t --token=<token> Set a GitHub OAuth token (see https://developer.github.com/v3/#rate-limiting).
34+
```
35+

clone.py renamed to ghclone.py

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,40 @@
1+
"""
2+
GitHub clone (git.io/ghclone)
3+
4+
Usage:
5+
ghclone.py <url> [-t | --token=<token>]
6+
ghclone.py (-h | --help)
7+
ghclone.py (-v | --version)
8+
9+
Examples:
10+
ghclone.py https://github.com/HR/Crypter/tree/master/app
11+
ghclone.py https://github.com/HR/Crypter/tree/dev/app
12+
ghclone.py https://github.com/HR/Crypter/tree/v3.1.0/build
13+
ghclone.py https://github.com/HR/Crypter/tree/cbee54dd720bb8aaa3a2111fcec667ca5f700510/build
14+
ghclone.py https://github.com/HR/Picturesque/tree/master/app/src -t li50d67757gm20556d53f08126215725a698560b
15+
16+
Options:
17+
-h --help Show this screen.
18+
-v --version Show version.
19+
-t --token=<token> Set a GitHub OAuth token (see https://developer.github.com/v3/#rate-limiting).
20+
21+
(C) 2019 Habib Rehman (git.io/HR)
22+
"""
123
import requests
224
import re
325
import sys
426
import os
527
import errno
28+
from docopt import docopt
629

7-
30+
VERSION = '1.0.0'
831
GH_API_BASE_URL = 'https://api.github.com'
932
GH_REPO_CONTENTS_ENDPOINT = GH_API_BASE_URL + '/repos/{}/{}/contents'
1033
BASE_NORMALIZE_REGEX = re.compile(r'.*github\.com\/')
1134

35+
verbose = False
36+
req = requests.Session()
37+
req.headers.update({'User-Agent': 'git.io/ghclone '+VERSION})
1238

1339
def exit_with_m(m='An error occured'):
1440
print(m)
@@ -27,11 +53,11 @@ def clone_file(download_url, file_path):
2753
"""
2854
Clones the file at the download_url to the file_path
2955
"""
30-
r = requests.get(download_url, stream=True)
56+
r = req.get(download_url, stream=True)
3157
try:
3258
r.raise_for_status()
3359
except Exception as e:
34-
exit_with_m('Failed cloneing ' + download_url, e)
60+
exit_with_m('Failed cloning ' + download_url, e)
3561

3662
with open(file_path, 'wb') as fd:
3763
for chunk in r.iter_content(chunk_size=128):
@@ -43,11 +69,11 @@ def clone(base_url, path=None, ref=None):
4369
"""
4470
req_url = base_url if not path else os.path.join(base_url, path)
4571
# Get path metadata
46-
r = requests.get(req_url) if not ref else requests.get(req_url, params={'ref': ref})
72+
r = req.get(req_url) if not ref else req.get(req_url, params={'ref': ref})
4773
try:
4874
r.raise_for_status()
4975
except Exception as e:
50-
exit_with_m('Failed fetching metadata of dir: ', e)
76+
exit_with_m('Failed fetching metadata for ' + path, e)
5177
repo_data = r.json()
5278

5379
# Create path locally
@@ -68,20 +94,24 @@ def clone(base_url, path=None, ref=None):
6894
###
6995
# Main
7096
###
71-
arg_len = len(sys.argv)
72-
if arg_len >= 2:
73-
# Github URL
74-
gh_url = sys.argv[1]
97+
if __name__ == '__main__':
98+
arguments = docopt(__doc__)
99+
if arguments['--version']:
100+
print(VERSION)
101+
sys.exit(0)
102+
103+
# Get params
104+
gh_url = arguments['<url>']
105+
token = arguments['--token']
106+
if token:
107+
req.headers.update({'Authorization': 'token '+token[0]})
75108
# Normalize & parse input
76109
normal_gh_url = re.sub(BASE_NORMALIZE_REGEX, '', gh_url).replace('/tree', '')
77110
gh_url_comps = normal_gh_url.split('/')
78111
user, repo = gh_url_comps[:2]
79112
ref = gh_url_comps[2]
80113
path = os.path.join(*gh_url_comps[3:])
81-
else:
82-
exit_with_m('Nothing to clone :(')
83-
84-
api_req_url = GH_REPO_CONTENTS_ENDPOINT.format(user, repo)
85-
print("Cloning into '%s'..." % path)
86-
clone(api_req_url, path, ref)
87-
print("done.")
114+
api_req_url = GH_REPO_CONTENTS_ENDPOINT.format(user, repo)
115+
print("Cloning into '%s'..." % path)
116+
clone(api_req_url, path, ref)
117+
print("done.")

0 commit comments

Comments
 (0)