githubtakeout 0.1.10__tar.gz → 0.1.11__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {githubtakeout-0.1.10/src/githubtakeout.egg-info → githubtakeout-0.1.11}/PKG-INFO +1 -1
- {githubtakeout-0.1.10 → githubtakeout-0.1.11}/pyproject.toml +11 -5
- {githubtakeout-0.1.10 → githubtakeout-0.1.11/src/githubtakeout.egg-info}/PKG-INFO +1 -1
- {githubtakeout-0.1.10 → githubtakeout-0.1.11}/src/githubtakeout.py +42 -48
- {githubtakeout-0.1.10 → githubtakeout-0.1.11}/src/progress.py +7 -5
- {githubtakeout-0.1.10 → githubtakeout-0.1.11}/LICENSE +0 -0
- {githubtakeout-0.1.10 → githubtakeout-0.1.11}/README.md +0 -0
- {githubtakeout-0.1.10 → githubtakeout-0.1.11}/setup.cfg +0 -0
- {githubtakeout-0.1.10 → githubtakeout-0.1.11}/src/githubtakeout.egg-info/SOURCES.txt +0 -0
- {githubtakeout-0.1.10 → githubtakeout-0.1.11}/src/githubtakeout.egg-info/dependency_links.txt +0 -0
- {githubtakeout-0.1.10 → githubtakeout-0.1.11}/src/githubtakeout.egg-info/entry_points.txt +0 -0
- {githubtakeout-0.1.10 → githubtakeout-0.1.11}/src/githubtakeout.egg-info/requires.txt +0 -0
- {githubtakeout-0.1.10 → githubtakeout-0.1.11}/src/githubtakeout.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "githubtakeout"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.11"
|
|
8
8
|
description = "Archive public Git Repos and Gists from GitHub"
|
|
9
9
|
license = "MIT"
|
|
10
10
|
license-files = ["LICENSE"]
|
|
@@ -39,18 +39,24 @@ download = "https://pypi.org/project/githubtakeout"
|
|
|
39
39
|
package-dir = {"" = "src"}
|
|
40
40
|
|
|
41
41
|
[tool.black]
|
|
42
|
-
line-length =
|
|
42
|
+
line-length = 120
|
|
43
43
|
target-version = ["py39", "py310", "py311", "py312", "py313"]
|
|
44
44
|
|
|
45
45
|
[tool.ruff]
|
|
46
|
-
line-length =
|
|
46
|
+
line-length = 120
|
|
47
47
|
respect-gitignore = true
|
|
48
48
|
target-version = "py312"
|
|
49
49
|
|
|
50
50
|
[tool.ruff.lint]
|
|
51
|
-
|
|
51
|
+
select = [
|
|
52
|
+
"D", "E", "F", "I", "PT", "RUF", "TID", "W"
|
|
53
|
+
]
|
|
54
|
+
extend-ignore = [
|
|
55
|
+
"D100", "D101", "D102", "D103", "D104", "D105", "D106", "D107", "D203",
|
|
56
|
+
"D205", "D211", "D213", "D400", "D401", "RUF012"
|
|
57
|
+
]
|
|
52
58
|
fixable = ["ALL"]
|
|
53
59
|
|
|
54
60
|
[tool.ruff.format]
|
|
55
61
|
docstring-code-format = true
|
|
56
|
-
docstring-code-line-length =
|
|
62
|
+
docstring-code-line-length = 120
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
# Corey Goldberg
|
|
1
|
+
# Copyright (c) 2015-2025 Corey Goldberg
|
|
2
2
|
# License: MIT
|
|
3
3
|
|
|
4
|
-
"""Archive Git Repos and Gists from GitHub"""
|
|
4
|
+
"""Archive Git Repos and Gists from GitHub."""
|
|
5
5
|
|
|
6
6
|
import argparse
|
|
7
7
|
import getpass
|
|
@@ -32,7 +32,7 @@ def convert_size(size_bytes):
|
|
|
32
32
|
if size_bytes == 0:
|
|
33
33
|
return "0 B"
|
|
34
34
|
size_name = ("B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB")
|
|
35
|
-
i =
|
|
35
|
+
i = math.floor(math.log(size_bytes, 1024))
|
|
36
36
|
p = math.pow(1024, i)
|
|
37
37
|
s = round(size_bytes / p, 2)
|
|
38
38
|
return f"{s} {size_name[i]}"
|
|
@@ -101,9 +101,7 @@ def clone(repo_url, local_repo_dir, include_history):
|
|
|
101
101
|
pass
|
|
102
102
|
|
|
103
103
|
|
|
104
|
-
def clone_and_archive_repo(
|
|
105
|
-
repo_url, local_repo_dir, archive_format, include_history, is_gist=False
|
|
106
|
-
):
|
|
104
|
+
def clone_and_archive_repo(repo_url, local_repo_dir, archive_format, include_history, is_gist=False):
|
|
107
105
|
def remove_readonly(func, path, _):
|
|
108
106
|
# This is necessary so rmtree() doesn't fail if there are any readonly
|
|
109
107
|
# dirs/files when trying to delete. This seems to happen after cloning on
|
|
@@ -147,32 +145,12 @@ def clone_and_archive_repo(
|
|
|
147
145
|
logger.info(f"successfully backed up '{base_name}' repo in {elapsed:.3f} secs\n")
|
|
148
146
|
|
|
149
147
|
|
|
150
|
-
def
|
|
151
|
-
if
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
sys.exit("error: auth token cannot be empty")
|
|
148
|
+
def get_repos(username, token, include_gists):
|
|
149
|
+
if token is not None:
|
|
150
|
+
# you need to be authenticated and then call the API
|
|
151
|
+
# with no username to get public and private repos
|
|
155
152
|
auth = github.Auth.Token(token)
|
|
156
153
|
gh = github.Github(auth=auth)
|
|
157
|
-
else:
|
|
158
|
-
token = os.getenv("GITHUB_TOKEN")
|
|
159
|
-
if token is not None:
|
|
160
|
-
auth = github.Auth.Token(token)
|
|
161
|
-
gh = github.Github(auth=auth)
|
|
162
|
-
else:
|
|
163
|
-
gh = github.Github()
|
|
164
|
-
if token is None:
|
|
165
|
-
try:
|
|
166
|
-
user = gh.get_user(username)
|
|
167
|
-
except github.GithubException as e:
|
|
168
|
-
if e.data["status"] == "404":
|
|
169
|
-
sys.exit(f"error: user '{username}' not found")
|
|
170
|
-
else:
|
|
171
|
-
raise e
|
|
172
|
-
repos = user.get_repos()
|
|
173
|
-
else:
|
|
174
|
-
# you need to be authenticated and then call the API
|
|
175
|
-
# with no username to get private repos
|
|
176
154
|
user = gh.get_user()
|
|
177
155
|
repos = user.get_repos(affiliation="owner")
|
|
178
156
|
try:
|
|
@@ -183,8 +161,31 @@ def get_user(username, prompt_token):
|
|
|
183
161
|
sys.exit(f"error: invalid auth token for user '{username}'")
|
|
184
162
|
else:
|
|
185
163
|
raise e
|
|
186
|
-
|
|
187
|
-
|
|
164
|
+
else:
|
|
165
|
+
gh = github.Github()
|
|
166
|
+
try:
|
|
167
|
+
user = gh.get_user(username)
|
|
168
|
+
except github.GithubException as e:
|
|
169
|
+
if e.data["status"] == "404":
|
|
170
|
+
sys.exit(f"error: user '{username}' not found")
|
|
171
|
+
else:
|
|
172
|
+
raise e
|
|
173
|
+
repos = user.get_repos()
|
|
174
|
+
if include_gists:
|
|
175
|
+
gists = user.get_gists()
|
|
176
|
+
else:
|
|
177
|
+
gists = None
|
|
178
|
+
return repos, gists
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def get_token(prompt_for_token):
|
|
182
|
+
if prompt_for_token:
|
|
183
|
+
token = getpass.getpass("Token:")
|
|
184
|
+
if not token:
|
|
185
|
+
sys.exit("error: auth token cannot be empty")
|
|
186
|
+
else:
|
|
187
|
+
token = os.getenv("GITHUB_TOKEN")
|
|
188
|
+
return token
|
|
188
189
|
|
|
189
190
|
|
|
190
191
|
def run(
|
|
@@ -194,10 +195,11 @@ def run(
|
|
|
194
195
|
include_gists,
|
|
195
196
|
include_history,
|
|
196
197
|
list_only,
|
|
197
|
-
|
|
198
|
+
prompt_for_token,
|
|
198
199
|
):
|
|
199
200
|
working_dir = os.path.join(base_dir, "backups")
|
|
200
|
-
|
|
201
|
+
token = get_token(prompt_for_token)
|
|
202
|
+
repos, gists = get_repos(username, token, include_gists)
|
|
201
203
|
num_repos = repos.totalCount
|
|
202
204
|
if not list_only:
|
|
203
205
|
logger.info(f"creating archives in: {working_dir}\n")
|
|
@@ -209,7 +211,7 @@ def run(
|
|
|
209
211
|
logger.info(url)
|
|
210
212
|
else:
|
|
211
213
|
clone_and_archive_repo(url, local_repo_dir, archive_format, include_history)
|
|
212
|
-
if
|
|
214
|
+
if gists is not None:
|
|
213
215
|
num_gists = gists.totalCount
|
|
214
216
|
logger.info("")
|
|
215
217
|
logger.info(f"found {num_gists} gists for user '{username}'\n")
|
|
@@ -219,9 +221,7 @@ def run(
|
|
|
219
221
|
if list_only:
|
|
220
222
|
logger.info(url)
|
|
221
223
|
else:
|
|
222
|
-
clone_and_archive_repo(
|
|
223
|
-
url, local_repo_dir, archive_format, include_history, is_gist=True
|
|
224
|
-
)
|
|
224
|
+
clone_and_archive_repo(url, local_repo_dir, archive_format, include_history, is_gist=True)
|
|
225
225
|
|
|
226
226
|
|
|
227
227
|
def main():
|
|
@@ -240,21 +240,15 @@ def main():
|
|
|
240
240
|
default="zip",
|
|
241
241
|
help="archive format (default: %(default)s)",
|
|
242
242
|
)
|
|
243
|
-
parser.add_argument(
|
|
244
|
-
"--gists", action="store_true", default=False, help="include gists"
|
|
245
|
-
)
|
|
243
|
+
parser.add_argument("--gists", action="store_true", default=False, help="include gists")
|
|
246
244
|
parser.add_argument(
|
|
247
245
|
"--history",
|
|
248
246
|
action="store_true",
|
|
249
247
|
default=False,
|
|
250
248
|
help="include commit history and branches (.git directory)",
|
|
251
249
|
)
|
|
252
|
-
parser.add_argument(
|
|
253
|
-
|
|
254
|
-
)
|
|
255
|
-
parser.add_argument(
|
|
256
|
-
"--token", action="store_true", default=False, help="prompt for auth token"
|
|
257
|
-
)
|
|
250
|
+
parser.add_argument("--list", action="store_true", default=False, help="list repos only")
|
|
251
|
+
parser.add_argument("--token", action="store_true", default=False, help="prompt for auth token")
|
|
258
252
|
args = parser.parse_args()
|
|
259
253
|
try:
|
|
260
254
|
run(
|
|
@@ -264,7 +258,7 @@ def main():
|
|
|
264
258
|
include_gists=args.gists,
|
|
265
259
|
include_history=args.history,
|
|
266
260
|
list_only=args.list,
|
|
267
|
-
|
|
261
|
+
prompt_for_token=args.token,
|
|
268
262
|
)
|
|
269
263
|
except KeyboardInterrupt:
|
|
270
264
|
sys.exit("\nexiting program ...")
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
# Copyright (c) 2025 Corey Goldberg
|
|
2
|
+
# License: MIT
|
|
3
|
+
# adapted from: https://stackoverflow.com/a/71285627/16148
|
|
4
|
+
|
|
5
|
+
"""Progress bar for cloning Git repos."""
|
|
6
|
+
|
|
1
7
|
import git
|
|
2
8
|
from rich import console, progress
|
|
3
9
|
|
|
4
|
-
# adapted from: https://stackoverflow.com/a/71285627/16148
|
|
5
|
-
|
|
6
10
|
|
|
7
11
|
class GitProgress(git.RemoteProgress):
|
|
8
12
|
OP_CODES = [
|
|
@@ -16,9 +20,7 @@ class GitProgress(git.RemoteProgress):
|
|
|
16
20
|
"RESOLVING",
|
|
17
21
|
"WRITING",
|
|
18
22
|
]
|
|
19
|
-
OP_CODE_MAP = {
|
|
20
|
-
getattr(git.RemoteProgress, _op_code): _op_code for _op_code in OP_CODES
|
|
21
|
-
}
|
|
23
|
+
OP_CODE_MAP = {getattr(git.RemoteProgress, _op_code): _op_code for _op_code in OP_CODES}
|
|
22
24
|
|
|
23
25
|
def __init__(self):
|
|
24
26
|
super().__init__()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{githubtakeout-0.1.10 → githubtakeout-0.1.11}/src/githubtakeout.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|