github-backup 0.62.1__tar.gz → 0.63.0__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.
- {github_backup-0.62.1 → github_backup-0.63.0}/CHANGES.rst +47 -1
- {github_backup-0.62.1/github_backup.egg-info → github_backup-0.63.0}/PKG-INFO +1 -1
- github_backup-0.63.0/github_backup/__init__.py +1 -0
- {github_backup-0.62.1 → github_backup-0.63.0}/github_backup/github_backup.py +41 -24
- {github_backup-0.62.1 → github_backup-0.63.0/github_backup.egg-info}/PKG-INFO +1 -1
- {github_backup-0.62.1 → github_backup-0.63.0}/github_backup.egg-info/SOURCES.txt +1 -0
- {github_backup-0.62.1 → github_backup-0.63.0}/release-requirements.txt +2 -2
- github_backup-0.63.0/tests/test_disabled_issues_pulls.py +105 -0
- github_backup-0.62.1/github_backup/__init__.py +0 -1
- {github_backup-0.62.1 → github_backup-0.63.0}/LICENSE.txt +0 -0
- {github_backup-0.62.1 → github_backup-0.63.0}/MANIFEST.in +0 -0
- {github_backup-0.62.1 → github_backup-0.63.0}/README +0 -0
- {github_backup-0.62.1 → github_backup-0.63.0}/README.rst +0 -0
- {github_backup-0.62.1 → github_backup-0.63.0}/github_backup/__main__.py +0 -0
- {github_backup-0.62.1 → github_backup-0.63.0}/github_backup/cli.py +0 -0
- {github_backup-0.62.1 → github_backup-0.63.0}/github_backup/graphql_queries.py +0 -0
- {github_backup-0.62.1 → github_backup-0.63.0}/github_backup.egg-info/dependency_links.txt +0 -0
- {github_backup-0.62.1 → github_backup-0.63.0}/github_backup.egg-info/entry_points.txt +0 -0
- {github_backup-0.62.1 → github_backup-0.63.0}/github_backup.egg-info/top_level.txt +0 -0
- {github_backup-0.62.1 → github_backup-0.63.0}/github_backup.egg-info/zip-safe +0 -0
- {github_backup-0.62.1 → github_backup-0.63.0}/requirements.txt +0 -0
- {github_backup-0.62.1 → github_backup-0.63.0}/setup.cfg +0 -0
- {github_backup-0.62.1 → github_backup-0.63.0}/setup.py +0 -0
- {github_backup-0.62.1 → github_backup-0.63.0}/tests/test_all_starred.py +0 -0
- {github_backup-0.62.1 → github_backup-0.63.0}/tests/test_attachments.py +0 -0
- {github_backup-0.62.1 → github_backup-0.63.0}/tests/test_auth.py +0 -0
- {github_backup-0.62.1 → github_backup-0.63.0}/tests/test_case_sensitivity.py +0 -0
- {github_backup-0.62.1 → github_backup-0.63.0}/tests/test_discussions.py +0 -0
- {github_backup-0.62.1 → github_backup-0.63.0}/tests/test_http_451.py +0 -0
- {github_backup-0.62.1 → github_backup-0.63.0}/tests/test_incremental_per_repository.py +0 -0
- {github_backup-0.62.1 → github_backup-0.63.0}/tests/test_json_dump_if_changed.py +0 -0
- {github_backup-0.62.1 → github_backup-0.63.0}/tests/test_pagination.py +0 -0
- {github_backup-0.62.1 → github_backup-0.63.0}/tests/test_pull_incremental_pagination.py +0 -0
- {github_backup-0.62.1 → github_backup-0.63.0}/tests/test_pull_reviews.py +0 -0
- {github_backup-0.62.1 → github_backup-0.63.0}/tests/test_releases.py +0 -0
- {github_backup-0.62.1 → github_backup-0.63.0}/tests/test_retrieve_data.py +0 -0
- {github_backup-0.62.1 → github_backup-0.63.0}/tests/test_skip_assets_on.py +0 -0
- {github_backup-0.62.1 → github_backup-0.63.0}/tests/test_starred_skip_size_over.py +0 -0
|
@@ -1,9 +1,55 @@
|
|
|
1
1
|
Changelog
|
|
2
2
|
=========
|
|
3
3
|
|
|
4
|
-
0.
|
|
4
|
+
0.63.0 (2026-06-17)
|
|
5
5
|
-------------------
|
|
6
6
|
------------------------
|
|
7
|
+
- Skip repositories with pull requests disabled (HTTP 404) instead of
|
|
8
|
+
crashing (#511) [Rodos]
|
|
9
|
+
|
|
10
|
+
Also harden the advisories/hooks 404 checks to match HTTPError.code instead of a substring.
|
|
11
|
+
- Chore(deps): bump pytest in the python-packages group.
|
|
12
|
+
[dependabot[bot]]
|
|
13
|
+
|
|
14
|
+
Bumps the python-packages group with 1 update: [pytest](https://github.com/pytest-dev/pytest).
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
Updates `pytest` from 9.0.3 to 9.1.0
|
|
18
|
+
- [Release notes](https://github.com/pytest-dev/pytest/releases)
|
|
19
|
+
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
|
|
20
|
+
- [Commits](https://github.com/pytest-dev/pytest/compare/9.0.3...9.1.0)
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
updated-dependencies:
|
|
24
|
+
- dependency-name: pytest
|
|
25
|
+
dependency-version: 9.1.0
|
|
26
|
+
dependency-type: direct:production
|
|
27
|
+
update-type: version-update:semver-minor
|
|
28
|
+
dependency-group: python-packages
|
|
29
|
+
...
|
|
30
|
+
- Chore(deps): bump black in the python-packages group.
|
|
31
|
+
[dependabot[bot]]
|
|
32
|
+
|
|
33
|
+
Bumps the python-packages group with 1 update: [black](https://github.com/psf/black).
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
Updates `black` from 26.3.1 to 26.5.1
|
|
37
|
+
- [Release notes](https://github.com/psf/black/releases)
|
|
38
|
+
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
|
|
39
|
+
- [Commits](https://github.com/psf/black/compare/26.3.1...26.5.1)
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
updated-dependencies:
|
|
43
|
+
- dependency-name: black
|
|
44
|
+
dependency-version: 26.5.1
|
|
45
|
+
dependency-type: direct:production
|
|
46
|
+
update-type: version-update:semver-minor
|
|
47
|
+
dependency-group: python-packages
|
|
48
|
+
...
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
0.62.1 (2026-04-30)
|
|
52
|
+
-------------------
|
|
7
53
|
- Document that nothing is saved by default. [Changaco]
|
|
8
54
|
- Eliminate trailing spaces. [Changaco]
|
|
9
55
|
- Remove pointless and unsafe `export`s in examples. [Changaco]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.63.0"
|
|
@@ -2657,10 +2657,20 @@ def backup_pulls(args, repo_cwd, repository, repos_template):
|
|
|
2657
2657
|
def pull_is_due_for_repository_checkpoint(pull):
|
|
2658
2658
|
return not repository_since or pull["updated_at"] > repository_since
|
|
2659
2659
|
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2660
|
+
try:
|
|
2661
|
+
if not args.include_pull_details:
|
|
2662
|
+
pull_states = ["open", "closed"]
|
|
2663
|
+
for pull_state in pull_states:
|
|
2664
|
+
query_args["state"] = pull_state
|
|
2665
|
+
for pull in retrieve_data(
|
|
2666
|
+
args, _pulls_template, query_args=query_args, lazy=True
|
|
2667
|
+
):
|
|
2668
|
+
track_newest_pull_update(pull)
|
|
2669
|
+
if pulls_since and pull["updated_at"] <= pulls_since:
|
|
2670
|
+
break
|
|
2671
|
+
if not pulls_since or pull["updated_at"] > pulls_since:
|
|
2672
|
+
pulls[pull["number"]] = pull
|
|
2673
|
+
else:
|
|
2664
2674
|
for pull in retrieve_data(
|
|
2665
2675
|
args, _pulls_template, query_args=query_args, lazy=True
|
|
2666
2676
|
):
|
|
@@ -2669,22 +2679,29 @@ def backup_pulls(args, repo_cwd, repository, repos_template):
|
|
|
2669
2679
|
break
|
|
2670
2680
|
if not pulls_since or pull["updated_at"] > pulls_since:
|
|
2671
2681
|
pulls[pull["number"]] = pull
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2682
|
+
except HTTPError as e:
|
|
2683
|
+
# Repositories with pull requests disabled return HTTP 404 for the
|
|
2684
|
+
# listing endpoint; treat that as "no pull requests" rather than a
|
|
2685
|
+
# fatal error. retrieve_data is lazy here, so the 404 surfaces during
|
|
2686
|
+
# iteration. Only the listing call is guarded — a 404 on an individual
|
|
2687
|
+
# pull's detail fetch below is a different condition and must propagate.
|
|
2688
|
+
if e.code == 404:
|
|
2689
|
+
logger.info("Pull requests are disabled for this repository, skipping")
|
|
2690
|
+
return
|
|
2691
|
+
raise
|
|
2692
|
+
|
|
2693
|
+
if args.include_pull_details:
|
|
2694
|
+
# Replace the summary payload with the full pull detail. Done outside
|
|
2695
|
+
# the disabled-feature guard above so a 404 on a single pull (e.g. one
|
|
2696
|
+
# deleted between listing and fetch) is not mistaken for the whole
|
|
2697
|
+
# feature being disabled, which would discard every collected pull.
|
|
2698
|
+
for number in list(pulls.keys()):
|
|
2699
|
+
if pull_is_due_for_repository_checkpoint(pulls[number]):
|
|
2700
|
+
pulls[number] = retrieve_data(
|
|
2701
|
+
args,
|
|
2702
|
+
_pulls_template + "/{}".format(number),
|
|
2703
|
+
paginated=False,
|
|
2704
|
+
)[0]
|
|
2688
2705
|
|
|
2689
2706
|
logger.info("Saving {0} pull requests to disk".format(len(list(pulls.keys()))))
|
|
2690
2707
|
# Comments from pulls API are only _review_ comments
|
|
@@ -2813,8 +2830,8 @@ def backup_security_advisories(args, repo_cwd, repository, repos_template):
|
|
|
2813
2830
|
|
|
2814
2831
|
try:
|
|
2815
2832
|
_advisories = retrieve_data(args, template)
|
|
2816
|
-
except
|
|
2817
|
-
if
|
|
2833
|
+
except HTTPError as e:
|
|
2834
|
+
if e.code == 404:
|
|
2818
2835
|
logger.info("Security advisories are not available for this repository, skipping")
|
|
2819
2836
|
return
|
|
2820
2837
|
raise
|
|
@@ -2861,8 +2878,8 @@ def backup_hooks(args, repo_cwd, repository, repos_template):
|
|
|
2861
2878
|
template = "{0}/{1}/hooks".format(repos_template, repository["full_name"])
|
|
2862
2879
|
try:
|
|
2863
2880
|
_backup_data(args, "hooks", template, output_file, hook_cwd)
|
|
2864
|
-
except
|
|
2865
|
-
if
|
|
2881
|
+
except HTTPError as e:
|
|
2882
|
+
if e.code == 404:
|
|
2866
2883
|
logger.info("Unable to read hooks, skipping")
|
|
2867
2884
|
else:
|
|
2868
2885
|
raise e
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Tests for repositories with pull requests disabled (GitHub issue #511).
|
|
2
|
+
|
|
3
|
+
When pull requests are disabled on a repository, the ``/pulls`` endpoint
|
|
4
|
+
responds with HTTP 404. The backup should treat that as "no pull requests"
|
|
5
|
+
and continue, rather than aborting the whole run. (The ``/issues`` endpoint
|
|
6
|
+
returns 200 with an empty list when issues are disabled, so it needs no such
|
|
7
|
+
handling.)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from urllib.error import HTTPError
|
|
12
|
+
|
|
13
|
+
from github_backup import github_backup
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _http_error(template, code, reason):
|
|
17
|
+
return HTTPError(template, code, reason, hdrs=None, fp=None)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_backup_pulls_skips_on_404(create_args, tmp_path, monkeypatch, caplog):
|
|
21
|
+
args = create_args(include_pulls=True, since=None)
|
|
22
|
+
repository = {"full_name": "owner/repo"}
|
|
23
|
+
|
|
24
|
+
def fake_retrieve_data(passed_args, template, query_args=None, lazy=False, **kwargs):
|
|
25
|
+
# retrieve_data is called lazily for the pull listing; the 404 must
|
|
26
|
+
# surface while iterating the returned generator.
|
|
27
|
+
def gen():
|
|
28
|
+
raise _http_error(template, 404, "Not Found")
|
|
29
|
+
yield # pragma: no cover - generator marker
|
|
30
|
+
|
|
31
|
+
if lazy:
|
|
32
|
+
return gen()
|
|
33
|
+
raise _http_error(template, 404, "Not Found")
|
|
34
|
+
|
|
35
|
+
monkeypatch.setattr(github_backup, "retrieve_data", fake_retrieve_data)
|
|
36
|
+
|
|
37
|
+
# Should not raise, and should log that it skipped.
|
|
38
|
+
with caplog.at_level(logging.INFO, logger=github_backup.logger.name):
|
|
39
|
+
github_backup.backup_pulls(
|
|
40
|
+
args, tmp_path, repository, "https://api.github.com/repos"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
assert "Pull requests are disabled for this repository, skipping" in caplog.text
|
|
44
|
+
|
|
45
|
+
pulls_dir = tmp_path / "pulls"
|
|
46
|
+
if pulls_dir.exists():
|
|
47
|
+
assert not [p for p in pulls_dir.iterdir() if p.suffix == ".json"]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_backup_pulls_reraises_non_404(create_args, tmp_path, monkeypatch):
|
|
51
|
+
args = create_args(include_pulls=True, since=None)
|
|
52
|
+
repository = {"full_name": "owner/repo"}
|
|
53
|
+
|
|
54
|
+
def fake_retrieve_data(passed_args, template, query_args=None, lazy=False, **kwargs):
|
|
55
|
+
def gen():
|
|
56
|
+
raise _http_error(template, 500, "Server Error")
|
|
57
|
+
yield # pragma: no cover - generator marker
|
|
58
|
+
|
|
59
|
+
if lazy:
|
|
60
|
+
return gen()
|
|
61
|
+
raise _http_error(template, 500, "Server Error")
|
|
62
|
+
|
|
63
|
+
monkeypatch.setattr(github_backup, "retrieve_data", fake_retrieve_data)
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
github_backup.backup_pulls(
|
|
67
|
+
args, tmp_path, repository, "https://api.github.com/repos"
|
|
68
|
+
)
|
|
69
|
+
except HTTPError as exc:
|
|
70
|
+
assert exc.code == 500
|
|
71
|
+
else:
|
|
72
|
+
raise AssertionError("Expected non-404 error to propagate")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_backup_pulls_details_individual_404_propagates(
|
|
76
|
+
create_args, tmp_path, monkeypatch
|
|
77
|
+
):
|
|
78
|
+
# With --pull-details the listing succeeds (feature is enabled), but an
|
|
79
|
+
# individual pull's detail fetch 404s (e.g. deleted mid-backup). That must
|
|
80
|
+
# propagate, NOT be swallowed as "pull requests are disabled".
|
|
81
|
+
args = create_args(include_pulls=True, include_pull_details=True, since=None)
|
|
82
|
+
repository = {"full_name": "owner/repo"}
|
|
83
|
+
|
|
84
|
+
listing = "https://api.github.com/repos/owner/repo/pulls"
|
|
85
|
+
|
|
86
|
+
def fake_retrieve_data(passed_args, template, query_args=None, lazy=False, **kwargs):
|
|
87
|
+
if template == listing:
|
|
88
|
+
# Listing works fine -> pull requests are clearly not disabled.
|
|
89
|
+
def gen():
|
|
90
|
+
yield {"number": 1, "updated_at": "2026-02-01T00:00:00Z"}
|
|
91
|
+
|
|
92
|
+
return gen()
|
|
93
|
+
# Individual pull detail fetch (.../pulls/1) is missing.
|
|
94
|
+
raise _http_error(template, 404, "Not Found")
|
|
95
|
+
|
|
96
|
+
monkeypatch.setattr(github_backup, "retrieve_data", fake_retrieve_data)
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
github_backup.backup_pulls(
|
|
100
|
+
args, tmp_path, repository, "https://api.github.com/repos"
|
|
101
|
+
)
|
|
102
|
+
except HTTPError as exc:
|
|
103
|
+
assert exc.code == 404
|
|
104
|
+
else:
|
|
105
|
+
raise AssertionError("Expected individual-pull 404 to propagate")
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.62.1"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|