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 CHANGED
@@ -1 +1 @@
1
- __version__ = "0.47.0"
1
+ __version__ = "0.49.0"
@@ -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
- warnings.warn('\n\nYOUR DEFAULT CA CERTS ARE EMPTY.\n' +
44
- 'PLEASE POPULATE ANY OF:' +
45
- ''.join([
46
- '\n - ' + x
47
- for x in ssl.get_default_verify_paths()
48
- if type(x) is str
49
- ]) + '\n', stacklevel=2)
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(per_page=100,
799
- page=1,
800
- query_args={},
801
- template=url,
802
- auth=auth,
803
- as_app=as_app,
804
- fine=fine)
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 = [r for r in repositories if "name" not in r or name_regex.match(r["name"])]
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 = [r for r in repositories if "name" not in r or r["name"] not in args.exclude]
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 = "{0}/{1}.json".format(issue_cwd, number)
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 = "{0}/{1}.json".format(pulls_cwd, number)
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(releases):
1248
- releases.sort(key=lambda item: datetime.strptime(item["created_at"], "%Y-%m-%dT%H:%M:%SZ"),
1249
- reverse=True)
1250
- releases = releases[:args.number_of_latest_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
1
+ Metadata-Version: 2.2
2
2
  Name: github-backup
3
- Version: 0.47.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 code, commit statuses, issues, metadata, pages, pull requests, and repository hooks.
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 (``--starred-gists``) or cloning starred gist repos (``--starred-gists``). See examples for more.
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,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.6.0)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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,,