github-backup 0.47.0__py3-none-any.whl → 0.49.0__py3-none-any.whl
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.
- github_backup/__init__.py +1 -1
- github_backup/github_backup.py +65 -27
- {github_backup-0.47.0.dist-info → github_backup-0.49.0.dist-info}/METADATA +19 -10
- github_backup-0.49.0.dist-info/RECORD +9 -0
- {github_backup-0.47.0.dist-info → github_backup-0.49.0.dist-info}/WHEEL +1 -1
- github_backup-0.47.0.dist-info/RECORD +0 -9
- {github_backup-0.47.0.data → github_backup-0.49.0.data}/scripts/github-backup +0 -0
- {github_backup-0.47.0.dist-info → github_backup-0.49.0.dist-info}/LICENSE.txt +0 -0
- {github_backup-0.47.0.dist-info → github_backup-0.49.0.dist-info}/top_level.txt +0 -0
- {github_backup-0.47.0.dist-info → github_backup-0.49.0.dist-info}/zip-safe +0 -0
github_backup/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.
|
|
1
|
+
__version__ = "0.49.0"
|
github_backup/github_backup.py
CHANGED
|
@@ -19,12 +19,12 @@ import ssl
|
|
|
19
19
|
import subprocess
|
|
20
20
|
import sys
|
|
21
21
|
import time
|
|
22
|
+
from datetime import datetime
|
|
22
23
|
from http.client import IncompleteRead
|
|
23
24
|
from urllib.error import HTTPError, URLError
|
|
24
25
|
from urllib.parse import quote as urlquote
|
|
25
26
|
from urllib.parse import urlencode, urlparse
|
|
26
27
|
from urllib.request import HTTPRedirectHandler, Request, build_opener, urlopen
|
|
27
|
-
from datetime import datetime
|
|
28
28
|
|
|
29
29
|
try:
|
|
30
30
|
from . import __version__
|
|
@@ -40,16 +40,21 @@ logger = logging.getLogger(__name__)
|
|
|
40
40
|
https_ctx = ssl.create_default_context()
|
|
41
41
|
if not https_ctx.get_ca_certs():
|
|
42
42
|
import warnings
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
43
|
+
|
|
44
|
+
warnings.warn(
|
|
45
|
+
"\n\nYOUR DEFAULT CA CERTS ARE EMPTY.\n"
|
|
46
|
+
+ "PLEASE POPULATE ANY OF:"
|
|
47
|
+
+ "".join(
|
|
48
|
+
["\n - " + x for x in ssl.get_default_verify_paths() if type(x) is str]
|
|
49
|
+
)
|
|
50
|
+
+ "\n",
|
|
51
|
+
stacklevel=2,
|
|
52
|
+
)
|
|
50
53
|
import certifi
|
|
54
|
+
|
|
51
55
|
https_ctx = ssl.create_default_context(cafile=certifi.where())
|
|
52
56
|
|
|
57
|
+
|
|
53
58
|
def logging_subprocess(
|
|
54
59
|
popenargs, stdout_log_level=logging.DEBUG, stderr_log_level=logging.ERROR, **kwargs
|
|
55
60
|
):
|
|
@@ -176,6 +181,12 @@ def parse_args(args=None):
|
|
|
176
181
|
dest="incremental",
|
|
177
182
|
help="incremental backup",
|
|
178
183
|
)
|
|
184
|
+
parser.add_argument(
|
|
185
|
+
"--incremental-by-files",
|
|
186
|
+
action="store_true",
|
|
187
|
+
dest="incremental_by_files",
|
|
188
|
+
help="incremental backup based on modification date of files",
|
|
189
|
+
)
|
|
179
190
|
parser.add_argument(
|
|
180
191
|
"--starred",
|
|
181
192
|
action="store_true",
|
|
@@ -524,7 +535,7 @@ def get_github_host(args):
|
|
|
524
535
|
|
|
525
536
|
|
|
526
537
|
def read_file_contents(file_uri):
|
|
527
|
-
return open(file_uri[len(FILE_URI_PREFIX):], "rt").readline().strip()
|
|
538
|
+
return open(file_uri[len(FILE_URI_PREFIX) :], "rt").readline().strip()
|
|
528
539
|
|
|
529
540
|
|
|
530
541
|
def get_github_repo_url(args, repository):
|
|
@@ -795,13 +806,15 @@ def download_file(url, path, auth, as_app=False, fine=False):
|
|
|
795
806
|
if os.path.exists(path):
|
|
796
807
|
return
|
|
797
808
|
|
|
798
|
-
request = _construct_request(
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
809
|
+
request = _construct_request(
|
|
810
|
+
per_page=100,
|
|
811
|
+
page=1,
|
|
812
|
+
query_args={},
|
|
813
|
+
template=url,
|
|
814
|
+
auth=auth,
|
|
815
|
+
as_app=as_app,
|
|
816
|
+
fine=fine,
|
|
817
|
+
)
|
|
805
818
|
request.add_header("Accept", "application/octet-stream")
|
|
806
819
|
opener = build_opener(S3HTTPRedirectHandler)
|
|
807
820
|
|
|
@@ -944,11 +957,15 @@ def filter_repositories(args, unfiltered_repositories):
|
|
|
944
957
|
if r.get("language") and r.get("language").lower() in languages
|
|
945
958
|
] # noqa
|
|
946
959
|
if name_regex:
|
|
947
|
-
repositories = [
|
|
960
|
+
repositories = [
|
|
961
|
+
r for r in repositories if "name" not in r or name_regex.match(r["name"])
|
|
962
|
+
]
|
|
948
963
|
if args.skip_archived:
|
|
949
964
|
repositories = [r for r in repositories if not r.get("archived")]
|
|
950
965
|
if args.exclude:
|
|
951
|
-
repositories = [
|
|
966
|
+
repositories = [
|
|
967
|
+
r for r in repositories if "name" not in r or r["name"] not in args.exclude
|
|
968
|
+
]
|
|
952
969
|
|
|
953
970
|
return repositories
|
|
954
971
|
|
|
@@ -1103,6 +1120,14 @@ def backup_issues(args, repo_cwd, repository, repos_template):
|
|
|
1103
1120
|
comments_template = _issue_template + "/{0}/comments"
|
|
1104
1121
|
events_template = _issue_template + "/{0}/events"
|
|
1105
1122
|
for number, issue in list(issues.items()):
|
|
1123
|
+
issue_file = "{0}/{1}.json".format(issue_cwd, number)
|
|
1124
|
+
if args.incremental_by_files and os.path.isfile(issue_file):
|
|
1125
|
+
modified = os.path.getmtime(issue_file)
|
|
1126
|
+
modified = datetime.fromtimestamp(modified).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
1127
|
+
if modified > issue["updated_at"]:
|
|
1128
|
+
logger.info("Skipping issue {0} because it wasn't modified since last backup".format(number))
|
|
1129
|
+
continue
|
|
1130
|
+
|
|
1106
1131
|
if args.include_issue_comments or args.include_everything:
|
|
1107
1132
|
template = comments_template.format(number)
|
|
1108
1133
|
issues[number]["comment_data"] = retrieve_data(args, template)
|
|
@@ -1110,9 +1135,9 @@ def backup_issues(args, repo_cwd, repository, repos_template):
|
|
|
1110
1135
|
template = events_template.format(number)
|
|
1111
1136
|
issues[number]["event_data"] = retrieve_data(args, template)
|
|
1112
1137
|
|
|
1113
|
-
issue_file
|
|
1114
|
-
with codecs.open(issue_file, "w", encoding="utf-8") as f:
|
|
1138
|
+
with codecs.open(issue_file + ".temp", "w", encoding="utf-8") as f:
|
|
1115
1139
|
json_dump(issue, f)
|
|
1140
|
+
os.rename(issue_file + ".temp", issue_file) # Unlike json_dump, this is atomic
|
|
1116
1141
|
|
|
1117
1142
|
|
|
1118
1143
|
def backup_pulls(args, repo_cwd, repository, repos_template):
|
|
@@ -1165,6 +1190,13 @@ def backup_pulls(args, repo_cwd, repository, repos_template):
|
|
|
1165
1190
|
comments_template = _pulls_template + "/{0}/comments"
|
|
1166
1191
|
commits_template = _pulls_template + "/{0}/commits"
|
|
1167
1192
|
for number, pull in list(pulls.items()):
|
|
1193
|
+
pull_file = "{0}/{1}.json".format(pulls_cwd, number)
|
|
1194
|
+
if args.incremental_by_files and os.path.isfile(pull_file):
|
|
1195
|
+
modified = os.path.getmtime(pull_file)
|
|
1196
|
+
modified = datetime.fromtimestamp(modified).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
1197
|
+
if modified > pull["updated_at"]:
|
|
1198
|
+
logger.info("Skipping pull request {0} because it wasn't modified since last backup".format(number))
|
|
1199
|
+
continue
|
|
1168
1200
|
if args.include_pull_comments or args.include_everything:
|
|
1169
1201
|
template = comments_regular_template.format(number)
|
|
1170
1202
|
pulls[number]["comment_regular_data"] = retrieve_data(args, template)
|
|
@@ -1174,9 +1206,9 @@ def backup_pulls(args, repo_cwd, repository, repos_template):
|
|
|
1174
1206
|
template = commits_template.format(number)
|
|
1175
1207
|
pulls[number]["commit_data"] = retrieve_data(args, template)
|
|
1176
1208
|
|
|
1177
|
-
pull_file
|
|
1178
|
-
with codecs.open(pull_file, "w", encoding="utf-8") as f:
|
|
1209
|
+
with codecs.open(pull_file + ".temp", "w", encoding="utf-8") as f:
|
|
1179
1210
|
json_dump(pull, f)
|
|
1211
|
+
os.rename(pull_file + ".temp", pull_file) # Unlike json_dump, this is atomic
|
|
1180
1212
|
|
|
1181
1213
|
|
|
1182
1214
|
def backup_milestones(args, repo_cwd, repository, repos_template):
|
|
@@ -1244,10 +1276,16 @@ def backup_releases(args, repo_cwd, repository, repos_template, include_assets=F
|
|
|
1244
1276
|
if args.skip_prerelease:
|
|
1245
1277
|
releases = [r for r in releases if not r["prerelease"] and not r["draft"]]
|
|
1246
1278
|
|
|
1247
|
-
if args.number_of_latest_releases and args.number_of_latest_releases < len(
|
|
1248
|
-
releases
|
|
1249
|
-
|
|
1250
|
-
releases
|
|
1279
|
+
if args.number_of_latest_releases and args.number_of_latest_releases < len(
|
|
1280
|
+
releases
|
|
1281
|
+
):
|
|
1282
|
+
releases.sort(
|
|
1283
|
+
key=lambda item: datetime.strptime(
|
|
1284
|
+
item["created_at"], "%Y-%m-%dT%H:%M:%SZ"
|
|
1285
|
+
),
|
|
1286
|
+
reverse=True,
|
|
1287
|
+
)
|
|
1288
|
+
releases = releases[: args.number_of_latest_releases]
|
|
1251
1289
|
logger.info("Saving the latest {0} releases to disk".format(len(releases)))
|
|
1252
1290
|
else:
|
|
1253
1291
|
logger.info("Saving {0} releases to disk".format(len(releases)))
|
|
@@ -1274,7 +1312,7 @@ def backup_releases(args, repo_cwd, repository, repos_template, include_assets=F
|
|
|
1274
1312
|
os.path.join(release_assets_cwd, asset["name"]),
|
|
1275
1313
|
get_auth(args, encode=not args.as_app),
|
|
1276
1314
|
as_app=args.as_app,
|
|
1277
|
-
fine=True if args.token_fine is not None else False
|
|
1315
|
+
fine=True if args.token_fine is not None else False,
|
|
1278
1316
|
)
|
|
1279
1317
|
|
|
1280
1318
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: github-backup
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.49.0
|
|
4
4
|
Summary: backup a github user or organization
|
|
5
5
|
Home-page: http://github.com/josegonzalez/python-github-backup
|
|
6
6
|
Author: Jose Diaz-Gonzalez
|
|
@@ -16,6 +16,14 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
17
|
Description-Content-Type: text/x-rst
|
|
18
18
|
License-File: LICENSE.txt
|
|
19
|
+
Dynamic: author
|
|
20
|
+
Dynamic: author-email
|
|
21
|
+
Dynamic: classifier
|
|
22
|
+
Dynamic: description
|
|
23
|
+
Dynamic: description-content-type
|
|
24
|
+
Dynamic: home-page
|
|
25
|
+
Dynamic: license
|
|
26
|
+
Dynamic: summary
|
|
19
27
|
|
|
20
28
|
=============
|
|
21
29
|
github-backup
|
|
@@ -99,6 +107,7 @@ CLI Help output::
|
|
|
99
107
|
log level to use (default: info, possible levels:
|
|
100
108
|
debug, info, warning, error, critical)
|
|
101
109
|
-i, --incremental incremental backup
|
|
110
|
+
--incremental-by-files incremental backup using modified time of files
|
|
102
111
|
--starred include JSON output of starred repositories in backup
|
|
103
112
|
--all-starred include starred repositories in backup [*]
|
|
104
113
|
--watched include JSON output of watched repositories in backup
|
|
@@ -186,7 +195,7 @@ Customise the permissions for your use case, but for a personal account full bac
|
|
|
186
195
|
|
|
187
196
|
**User permissions**: Read access to followers, starring, and watching.
|
|
188
197
|
|
|
189
|
-
**Repository permissions**: Read access to
|
|
198
|
+
**Repository permissions**: Read access to contents, issues, metadata, pull requests, and webhooks.
|
|
190
199
|
|
|
191
200
|
|
|
192
201
|
Prefer SSH
|
|
@@ -244,7 +253,7 @@ Gotchas / Known-issues
|
|
|
244
253
|
All is not everything
|
|
245
254
|
---------------------
|
|
246
255
|
|
|
247
|
-
The ``--all`` argument does not include: cloning private repos (``-P, --private``), cloning forks (``-F, --fork``), cloning starred repositories (``--all-starred``), ``--pull-details``, cloning LFS repositories (``--lfs``), cloning gists (``--
|
|
256
|
+
The ``--all`` argument does not include: cloning private repos (``-P, --private``), cloning forks (``-F, --fork``), cloning starred repositories (``--all-starred``), ``--pull-details``, cloning LFS repositories (``--lfs``), cloning gists (``--gists``) or cloning starred gist repos (``--starred-gists``). See examples for more.
|
|
248
257
|
|
|
249
258
|
Cloning all starred size
|
|
250
259
|
------------------------
|
|
@@ -258,6 +267,12 @@ Using (``-i, --incremental``) will only request new data from the API **since th
|
|
|
258
267
|
|
|
259
268
|
This means any blocking errors on previous runs can cause a large amount of missing data in backups.
|
|
260
269
|
|
|
270
|
+
Using (``--incremental-by-files``) will request new data from the API **based on when the file was modified on filesystem**. e.g. if you modify the file yourself you may miss something.
|
|
271
|
+
|
|
272
|
+
Still saver than the previous version.
|
|
273
|
+
|
|
274
|
+
Specifically, issues and pull requests are handled like this.
|
|
275
|
+
|
|
261
276
|
Known blocking errors
|
|
262
277
|
---------------------
|
|
263
278
|
|
|
@@ -273,12 +288,6 @@ It's therefore recommended to only use the incremental argument if the output/re
|
|
|
273
288
|
|
|
274
289
|
This is due to needing the correct permission for ``--hooks`` on public repos.
|
|
275
290
|
|
|
276
|
-
2. **Releases blocking**
|
|
277
|
-
|
|
278
|
-
A known ``--releases`` (required for ``--assets``) error will sometimes block the backup.
|
|
279
|
-
|
|
280
|
-
If you're backing up a lot of repositories with releases e.g. an organisation or ``--all-starred``. You may need to remove ``--releases`` (and therefore ``--assets``) to complete a backup. Documented in `issue 209 <https://github.com/josegonzalez/python-github-backup/issues/209>`_.
|
|
281
|
-
|
|
282
291
|
|
|
283
292
|
"bare" is actually "mirror"
|
|
284
293
|
---------------------------
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
github_backup/__init__.py,sha256=09FNUq8XbPsGZin0IRhNT7qwi97ZXQafNn7GixZiqAw,23
|
|
2
|
+
github_backup/github_backup.py,sha256=TI4cb_oHwI6P58upMF1jTbC-1nquog7C4q2sCM9PqMA,49844
|
|
3
|
+
github_backup-0.49.0.data/scripts/github-backup,sha256=I79TNA67phytRmI9nm_LlD9h2zauXFOsOn9tYyAy9pU,1546
|
|
4
|
+
github_backup-0.49.0.dist-info/LICENSE.txt,sha256=ML-pLyn1qtAKo_DHMcQ8-p7k3f8cp-IR-mf5B49R2rk,1085
|
|
5
|
+
github_backup-0.49.0.dist-info/METADATA,sha256=YfkUDfbWpZGTFN3wlThxbblaXVRMNFBLQD1jwpo9RSc,19380
|
|
6
|
+
github_backup-0.49.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
7
|
+
github_backup-0.49.0.dist-info/top_level.txt,sha256=Za6fKRAlxZ0b3ZBH_osPqklLe14LetdNAkvAQANT2AA,14
|
|
8
|
+
github_backup-0.49.0.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
9
|
+
github_backup-0.49.0.dist-info/RECORD,,
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
github_backup/__init__.py,sha256=aqLqdmGXCjcE1UvvJF8OHznHKbGMeoRrk-C4_mjzdPs,23
|
|
2
|
-
github_backup/github_backup.py,sha256=h6lu2QBhjx5yHrSA0ocQ3xga0GdJuj4yG92zJZYtz-0,48732
|
|
3
|
-
github_backup-0.47.0.data/scripts/github-backup,sha256=I79TNA67phytRmI9nm_LlD9h2zauXFOsOn9tYyAy9pU,1546
|
|
4
|
-
github_backup-0.47.0.dist-info/LICENSE.txt,sha256=ML-pLyn1qtAKo_DHMcQ8-p7k3f8cp-IR-mf5B49R2rk,1085
|
|
5
|
-
github_backup-0.47.0.dist-info/METADATA,sha256=Yokav5VhHSroGqxzvkvXZSSPRrD4K4t9vGsCSt1H0Mg,19291
|
|
6
|
-
github_backup-0.47.0.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
|
7
|
-
github_backup-0.47.0.dist-info/top_level.txt,sha256=Za6fKRAlxZ0b3ZBH_osPqklLe14LetdNAkvAQANT2AA,14
|
|
8
|
-
github_backup-0.47.0.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
9
|
-
github_backup-0.47.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|