github-backup 0.65.0__tar.gz → 0.66.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.65.0 → github_backup-0.66.0}/CHANGES.rst +148 -1
- {github_backup-0.65.0/github_backup.egg-info → github_backup-0.66.0}/PKG-INFO +3 -1
- {github_backup-0.65.0 → github_backup-0.66.0}/README +2 -0
- {github_backup-0.65.0 → github_backup-0.66.0}/README.rst +2 -0
- github_backup-0.66.0/github_backup/__init__.py +1 -0
- {github_backup-0.65.0 → github_backup-0.66.0}/github_backup/github_backup.py +35 -16
- {github_backup-0.65.0 → github_backup-0.66.0/github_backup.egg-info}/PKG-INFO +3 -1
- {github_backup-0.65.0 → github_backup-0.66.0}/github_backup.egg-info/SOURCES.txt +1 -0
- {github_backup-0.65.0 → github_backup-0.66.0}/release-requirements.txt +4 -4
- github_backup-0.66.0/tests/test_app_repositories.py +71 -0
- {github_backup-0.65.0 → github_backup-0.66.0}/tests/test_issue_timeline.py +31 -6
- github_backup-0.65.0/github_backup/__init__.py +0 -1
- {github_backup-0.65.0 → github_backup-0.66.0}/LICENSE.txt +0 -0
- {github_backup-0.65.0 → github_backup-0.66.0}/MANIFEST.in +0 -0
- {github_backup-0.65.0 → github_backup-0.66.0}/github_backup/__main__.py +0 -0
- {github_backup-0.65.0 → github_backup-0.66.0}/github_backup/cli.py +0 -0
- {github_backup-0.65.0 → github_backup-0.66.0}/github_backup/graphql_queries.py +0 -0
- {github_backup-0.65.0 → github_backup-0.66.0}/github_backup.egg-info/dependency_links.txt +0 -0
- {github_backup-0.65.0 → github_backup-0.66.0}/github_backup.egg-info/entry_points.txt +0 -0
- {github_backup-0.65.0 → github_backup-0.66.0}/github_backup.egg-info/top_level.txt +0 -0
- {github_backup-0.65.0 → github_backup-0.66.0}/github_backup.egg-info/zip-safe +0 -0
- {github_backup-0.65.0 → github_backup-0.66.0}/requirements.txt +0 -0
- {github_backup-0.65.0 → github_backup-0.66.0}/setup.cfg +0 -0
- {github_backup-0.65.0 → github_backup-0.66.0}/setup.py +0 -0
- {github_backup-0.65.0 → github_backup-0.66.0}/tests/test_all_starred.py +0 -0
- {github_backup-0.65.0 → github_backup-0.66.0}/tests/test_attachments.py +0 -0
- {github_backup-0.65.0 → github_backup-0.66.0}/tests/test_auth.py +0 -0
- {github_backup-0.65.0 → github_backup-0.66.0}/tests/test_case_sensitivity.py +0 -0
- {github_backup-0.65.0 → github_backup-0.66.0}/tests/test_disabled_issues_pulls.py +0 -0
- {github_backup-0.65.0 → github_backup-0.66.0}/tests/test_discussions.py +0 -0
- {github_backup-0.65.0 → github_backup-0.66.0}/tests/test_gists.py +0 -0
- {github_backup-0.65.0 → github_backup-0.66.0}/tests/test_http_451.py +0 -0
- {github_backup-0.65.0 → github_backup-0.66.0}/tests/test_incremental_per_repository.py +0 -0
- {github_backup-0.65.0 → github_backup-0.66.0}/tests/test_json_dump_if_changed.py +0 -0
- {github_backup-0.65.0 → github_backup-0.66.0}/tests/test_logging_subprocess.py +0 -0
- {github_backup-0.65.0 → github_backup-0.66.0}/tests/test_pagination.py +0 -0
- {github_backup-0.65.0 → github_backup-0.66.0}/tests/test_pull_incremental_pagination.py +0 -0
- {github_backup-0.65.0 → github_backup-0.66.0}/tests/test_pull_reviews.py +0 -0
- {github_backup-0.65.0 → github_backup-0.66.0}/tests/test_releases.py +0 -0
- {github_backup-0.65.0 → github_backup-0.66.0}/tests/test_retrieve_data.py +0 -0
- {github_backup-0.65.0 → github_backup-0.66.0}/tests/test_skip_assets_on.py +0 -0
- {github_backup-0.65.0 → github_backup-0.66.0}/tests/test_starred_skip_size_over.py +0 -0
|
@@ -1,9 +1,156 @@
|
|
|
1
1
|
Changelog
|
|
2
2
|
=========
|
|
3
3
|
|
|
4
|
-
0.
|
|
4
|
+
0.66.0 (2026-10-28)
|
|
5
5
|
-------------------
|
|
6
6
|
------------------------
|
|
7
|
+
- List a user's private repositories when authenticated as a GitHub App.
|
|
8
|
+
[AC Capehart, Claude Opus 5.5]
|
|
9
|
+
|
|
10
|
+
With --as-app there is no authenticated user, so a user-account backup
|
|
11
|
+
fell through to /users/{user}/repos, which only returns public
|
|
12
|
+
repositories. --private silently backed up nothing private.
|
|
13
|
+
|
|
14
|
+
An installation token can list everything the installation can access
|
|
15
|
+
via /installation/repositories, private repositories included. Org
|
|
16
|
+
backups keep using /orgs/{org}/repos, and -R still fetches the single
|
|
17
|
+
repository directly.
|
|
18
|
+
- Chore(deps): bump setuptools. [dependabot[bot]]
|
|
19
|
+
|
|
20
|
+
Bumps the python-packages group with 1 update: [setuptools](https://github.com/pypa/setuptools).
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
Updates `setuptools` from 84.2.0 to 84.2.1
|
|
24
|
+
- [Release notes](https://github.com/pypa/setuptools/releases)
|
|
25
|
+
- [Changelog](https://github.com/pypa/setuptools/blob/main/NEWS.rst)
|
|
26
|
+
- [Commits](https://github.com/pypa/setuptools/compare/v84.2.0...v84.2.1)
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
updated-dependencies:
|
|
30
|
+
- dependency-name: setuptools
|
|
31
|
+
dependency-version: 84.2.1
|
|
32
|
+
dependency-type: direct:production
|
|
33
|
+
update-type: version-update:semver-patch
|
|
34
|
+
dependency-group: python-packages
|
|
35
|
+
...
|
|
36
|
+
- Chore(deps): bump setuptools. [dependabot[bot]]
|
|
37
|
+
|
|
38
|
+
Bumps the python-packages group with 1 update: [setuptools](https://github.com/pypa/setuptools).
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
Updates `setuptools` from 84.0.0 to 84.2.0
|
|
42
|
+
- [Release notes](https://github.com/pypa/setuptools/releases)
|
|
43
|
+
- [Changelog](https://github.com/pypa/setuptools/blob/main/NEWS.rst)
|
|
44
|
+
- [Commits](https://github.com/pypa/setuptools/compare/v84.0.0...v84.2.0)
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
updated-dependencies:
|
|
48
|
+
- dependency-name: setuptools
|
|
49
|
+
dependency-version: 84.2.0
|
|
50
|
+
dependency-type: direct:production
|
|
51
|
+
update-type: version-update:semver-minor
|
|
52
|
+
dependency-group: python-packages
|
|
53
|
+
...
|
|
54
|
+
- Chore(deps): bump black. [dependabot[bot]]
|
|
55
|
+
|
|
56
|
+
Bumps the python-packages group with 1 update: [black](https://github.com/psf/black).
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
Updates `black` from 26.10.0 to 26.10.1
|
|
60
|
+
- [Release notes](https://github.com/psf/black/releases)
|
|
61
|
+
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
|
|
62
|
+
- [Commits](https://github.com/psf/black/compare/26.10.0...26.10.1)
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
updated-dependencies:
|
|
66
|
+
- dependency-name: black
|
|
67
|
+
dependency-version: 26.10.1
|
|
68
|
+
dependency-type: direct:production
|
|
69
|
+
update-type: version-update:semver-patch
|
|
70
|
+
dependency-group: python-packages
|
|
71
|
+
...
|
|
72
|
+
- Chore(deps): bump black. [dependabot[bot]]
|
|
73
|
+
|
|
74
|
+
Bumps the python-packages group with 1 update: [black](https://github.com/psf/black).
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
Updates `black` from 26.5.1 to 26.10.0
|
|
78
|
+
- [Release notes](https://github.com/psf/black/releases)
|
|
79
|
+
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
|
|
80
|
+
- [Commits](https://github.com/psf/black/compare/26.5.1...26.10.0)
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
updated-dependencies:
|
|
84
|
+
- dependency-name: black
|
|
85
|
+
dependency-version: 26.10.0
|
|
86
|
+
dependency-type: direct:production
|
|
87
|
+
update-type: version-update:semver-minor
|
|
88
|
+
dependency-group: python-packages
|
|
89
|
+
...
|
|
90
|
+
- Chore(deps): bump flake8 in the python-packages group.
|
|
91
|
+
[dependabot[bot]]
|
|
92
|
+
|
|
93
|
+
Bumps the python-packages group with 1 update: [flake8](https://github.com/pycqa/flake8).
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
Updates `flake8` from 7.3.0 to 7.4.1
|
|
97
|
+
- [Commits](https://github.com/pycqa/flake8/compare/7.3.0...7.4.1)
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
updated-dependencies:
|
|
101
|
+
- dependency-name: flake8
|
|
102
|
+
dependency-version: 7.4.1
|
|
103
|
+
dependency-type: direct:production
|
|
104
|
+
update-type: version-update:semver-minor
|
|
105
|
+
dependency-group: python-packages
|
|
106
|
+
...
|
|
107
|
+
- Chore(deps): bump setuptools in the python-packages group.
|
|
108
|
+
[dependabot[bot]]
|
|
109
|
+
|
|
110
|
+
Bumps the python-packages group with 1 update: [setuptools](https://github.com/pypa/setuptools).
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
Updates `setuptools` from 83.0.0 to 84.0.0
|
|
114
|
+
- [Release notes](https://github.com/pypa/setuptools/releases)
|
|
115
|
+
- [Changelog](https://github.com/pypa/setuptools/blob/main/NEWS.rst)
|
|
116
|
+
- [Commits](https://github.com/pypa/setuptools/compare/v83.0.0...v84.0.0)
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
updated-dependencies:
|
|
120
|
+
- dependency-name: setuptools
|
|
121
|
+
dependency-version: 84.0.0
|
|
122
|
+
dependency-type: direct:production
|
|
123
|
+
update-type: version-update:semver-major
|
|
124
|
+
dependency-group: python-packages
|
|
125
|
+
...
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
0.65.1 (2026-08-01)
|
|
129
|
+
-------------------
|
|
130
|
+
- Fix timeline not being properly incremental. [Duncan Ogilvie]
|
|
131
|
+
- Chore(deps): bump twine from 6.2.0 to 7.0.0 in the python-packages
|
|
132
|
+
group. [dependabot[bot]]
|
|
133
|
+
|
|
134
|
+
Bumps the python-packages group with 1 update: [twine](https://github.com/pypa/twine).
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
Updates `twine` from 6.2.0 to 7.0.0
|
|
138
|
+
- [Release notes](https://github.com/pypa/twine/releases)
|
|
139
|
+
- [Changelog](https://github.com/pypa/twine/blob/main/docs/changelog.rst)
|
|
140
|
+
- [Commits](https://github.com/pypa/twine/compare/6.2.0...7.0.0)
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
updated-dependencies:
|
|
144
|
+
- dependency-name: twine
|
|
145
|
+
dependency-version: 7.0.0
|
|
146
|
+
dependency-type: direct:production
|
|
147
|
+
update-type: version-update:semver-major
|
|
148
|
+
dependency-group: python-packages
|
|
149
|
+
...
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
0.65.0 (2026-07-27)
|
|
153
|
+
-------------------
|
|
7
154
|
- Refresh issue timelines when cross-references change (#168) [Rodos]
|
|
8
155
|
|
|
9
156
|
Cross-references do not change an issue's updated_at, so an incremental
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: github-backup
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.66.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
|
|
@@ -229,6 +229,8 @@ GitHub Apps
|
|
|
229
229
|
|
|
230
230
|
GitHub Apps are ideal for organization backups in CI/CD. Tokens are scoped to specific repositories and expire after 1 hour.
|
|
231
231
|
|
|
232
|
+
For a user account (no ``--organization``), ``--as-app`` lists the repositories the installation can access, so private repositories are included.
|
|
233
|
+
|
|
232
234
|
**One-time setup:**
|
|
233
235
|
|
|
234
236
|
1. Create a GitHub App at *Settings -> Developer Settings -> GitHub Apps -> New GitHub App*
|
|
@@ -199,6 +199,8 @@ GitHub Apps
|
|
|
199
199
|
|
|
200
200
|
GitHub Apps are ideal for organization backups in CI/CD. Tokens are scoped to specific repositories and expire after 1 hour.
|
|
201
201
|
|
|
202
|
+
For a user account (no ``--organization``), ``--as-app`` lists the repositories the installation can access, so private repositories are included.
|
|
203
|
+
|
|
202
204
|
**One-time setup:**
|
|
203
205
|
|
|
204
206
|
1. Create a GitHub App at *Settings -> Developer Settings -> GitHub Apps -> New GitHub App*
|
|
@@ -199,6 +199,8 @@ GitHub Apps
|
|
|
199
199
|
|
|
200
200
|
GitHub Apps are ideal for organization backups in CI/CD. Tokens are scoped to specific repositories and expire after 1 hour.
|
|
201
201
|
|
|
202
|
+
For a user account (no ``--organization``), ``--as-app`` lists the repositories the installation can access, so private repositories are included.
|
|
203
|
+
|
|
202
204
|
**One-time setup:**
|
|
203
205
|
|
|
204
206
|
1. Create a GitHub App at *Settings -> Developer Settings -> GitHub Apps -> New GitHub App*
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.66.0"
|
|
@@ -1838,6 +1838,11 @@ def retrieve_repositories(args, authenticated_user):
|
|
|
1838
1838
|
if args.user == authenticated_user["login"]:
|
|
1839
1839
|
# we must use the /user/repos API to be able to access private repos
|
|
1840
1840
|
template = "https://{0}/user/repos".format(get_github_api_host(args))
|
|
1841
|
+
elif args.as_app and not args.organization:
|
|
1842
|
+
# /users/{user}/repos is public-only; the installation sees private repos too
|
|
1843
|
+
template = "https://{0}/installation/repositories".format(
|
|
1844
|
+
get_github_api_host(args)
|
|
1845
|
+
)
|
|
1841
1846
|
else:
|
|
1842
1847
|
if args.private and not args.organization:
|
|
1843
1848
|
logger.warning(
|
|
@@ -1868,6 +1873,10 @@ def retrieve_repositories(args, authenticated_user):
|
|
|
1868
1873
|
logger.warning(f"Legal notice: {e.legal_url}")
|
|
1869
1874
|
return []
|
|
1870
1875
|
|
|
1876
|
+
if template.endswith("/installation/repositories"):
|
|
1877
|
+
# Each page is {"total_count": ..., "repositories": [...]}
|
|
1878
|
+
repos = [repo for page in repos for repo in page["repositories"]]
|
|
1879
|
+
|
|
1871
1880
|
return retrieve_additional_repositories(args, authenticated_user, repos)
|
|
1872
1881
|
|
|
1873
1882
|
|
|
@@ -2757,19 +2766,24 @@ def backup_issues(args, repo_cwd, repository, repos_template):
|
|
|
2757
2766
|
stale_timelines = find_stale_timeline_issues(
|
|
2758
2767
|
args, issue_cwd, repository, include_pulls=not should_include_pulls
|
|
2759
2768
|
)
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2769
|
+
|
|
2770
|
+
# Issues selected only by the cross-reference sweep have not otherwise
|
|
2771
|
+
# changed. Reuse their complete stored payload and refresh only timeline_data.
|
|
2772
|
+
# Fetching the issue, comments, events and attachments again turns the first
|
|
2773
|
+
# --issue-timeline run into a near-full backup and can discard optional data
|
|
2774
|
+
# when the corresponding flags are absent.
|
|
2775
|
+
timeline_only_issues = stale_timelines - set(issues)
|
|
2776
|
+
for number in sorted(timeline_only_issues):
|
|
2777
|
+
existing = read_json_file_if_exists(
|
|
2778
|
+
"{0}/{1}.json".format(issue_cwd, number)
|
|
2779
|
+
)
|
|
2780
|
+
if existing is None:
|
|
2781
|
+
logger.warning(
|
|
2782
|
+
"Unable to refresh cross-references for issue {0}: "
|
|
2783
|
+
"stored issue data is unreadable".format(number)
|
|
2784
|
+
)
|
|
2785
|
+
continue
|
|
2786
|
+
issues[number] = existing
|
|
2773
2787
|
|
|
2774
2788
|
if issues_skipped:
|
|
2775
2789
|
issues_skipped_message = " (skipped {0} pull requests)".format(issues_skipped)
|
|
@@ -2784,6 +2798,7 @@ def backup_issues(args, repo_cwd, repository, repos_template):
|
|
|
2784
2798
|
timeline_template = _issue_template + "/{0}/timeline"
|
|
2785
2799
|
for number, issue in list(issues.items()):
|
|
2786
2800
|
issue_file = "{0}/{1}.json".format(issue_cwd, number)
|
|
2801
|
+
timeline_only_refresh = number in timeline_only_issues
|
|
2787
2802
|
if (
|
|
2788
2803
|
args.incremental_by_files
|
|
2789
2804
|
and os.path.isfile(issue_file)
|
|
@@ -2799,10 +2814,14 @@ def backup_issues(args, repo_cwd, repository, repos_template):
|
|
|
2799
2814
|
)
|
|
2800
2815
|
continue
|
|
2801
2816
|
|
|
2802
|
-
if
|
|
2817
|
+
if not timeline_only_refresh and (
|
|
2818
|
+
args.include_issue_comments or args.include_everything
|
|
2819
|
+
):
|
|
2803
2820
|
template = comments_template.format(number)
|
|
2804
2821
|
issues[number]["comment_data"] = retrieve_data(args, template)
|
|
2805
|
-
if
|
|
2822
|
+
if not timeline_only_refresh and (
|
|
2823
|
+
args.include_issue_events or args.include_everything
|
|
2824
|
+
):
|
|
2806
2825
|
template = events_template.format(number)
|
|
2807
2826
|
issues[number]["event_data"] = retrieve_data(args, template)
|
|
2808
2827
|
if include_timeline:
|
|
@@ -2815,7 +2834,7 @@ def backup_issues(args, repo_cwd, repository, repos_template):
|
|
|
2815
2834
|
for item in retrieve_data(args, template)
|
|
2816
2835
|
if item.get("event") != "commented"
|
|
2817
2836
|
]
|
|
2818
|
-
if args.include_attachments:
|
|
2837
|
+
if args.include_attachments and not timeline_only_refresh:
|
|
2819
2838
|
download_attachments(
|
|
2820
2839
|
args, issue_cwd, issues[number], number, repository, item_type="issue"
|
|
2821
2840
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: github-backup
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.66.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
|
|
@@ -229,6 +229,8 @@ GitHub Apps
|
|
|
229
229
|
|
|
230
230
|
GitHub Apps are ideal for organization backups in CI/CD. Tokens are scoped to specific repositories and expire after 1 hour.
|
|
231
231
|
|
|
232
|
+
For a user account (no ``--organization``), ``--as-app`` lists the repositories the installation can access, so private repositories are included.
|
|
233
|
+
|
|
232
234
|
**One-time setup:**
|
|
233
235
|
|
|
234
236
|
1. Create a GitHub App at *Settings -> Developer Settings -> GitHub Apps -> New GitHub App*
|
|
@@ -18,6 +18,7 @@ github_backup.egg-info/entry_points.txt
|
|
|
18
18
|
github_backup.egg-info/top_level.txt
|
|
19
19
|
github_backup.egg-info/zip-safe
|
|
20
20
|
tests/test_all_starred.py
|
|
21
|
+
tests/test_app_repositories.py
|
|
21
22
|
tests/test_attachments.py
|
|
22
23
|
tests/test_auth.py
|
|
23
24
|
tests/test_case_sensitivity.py
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# Linting & Formatting
|
|
2
2
|
autopep8==2.3.2
|
|
3
|
-
black==26.
|
|
4
|
-
flake8==7.
|
|
3
|
+
black==26.10.1
|
|
4
|
+
flake8==7.4.1
|
|
5
5
|
|
|
6
6
|
# Testing
|
|
7
7
|
pytest==9.1.1
|
|
8
8
|
|
|
9
9
|
# Release & Publishing
|
|
10
|
-
twine==
|
|
10
|
+
twine==7.0.0
|
|
11
11
|
gitchangelog==3.0.4
|
|
12
|
-
setuptools==
|
|
12
|
+
setuptools==84.2.1
|
|
13
13
|
|
|
14
14
|
# Documentation
|
|
15
15
|
restructuredtext-lint==2.0.2
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Tests for listing a user's repositories with a GitHub App installation token."""
|
|
2
|
+
|
|
3
|
+
from unittest.mock import patch
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from github_backup import github_backup
|
|
8
|
+
|
|
9
|
+
NO_USER = {"login": None}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def page(*names):
|
|
13
|
+
return {
|
|
14
|
+
"total_count": 3,
|
|
15
|
+
"repositories": [{"name": name, "private": True} for name in names],
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.fixture
|
|
20
|
+
def mock_retrieve_data():
|
|
21
|
+
with patch("github_backup.github_backup.retrieve_data") as mock:
|
|
22
|
+
yield mock
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_app_lists_user_repositories_from_installation(create_args, mock_retrieve_data):
|
|
26
|
+
args = create_args(as_app=True, include_repository=True)
|
|
27
|
+
mock_retrieve_data.return_value = [page("a", "b"), page("c")]
|
|
28
|
+
|
|
29
|
+
repos = github_backup.retrieve_repositories(args, NO_USER)
|
|
30
|
+
|
|
31
|
+
assert mock_retrieve_data.call_args.args[1] == (
|
|
32
|
+
"https://api.github.com/installation/repositories"
|
|
33
|
+
)
|
|
34
|
+
assert [r["name"] for r in repos] == ["a", "b", "c"]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_app_installation_listing_uses_ghes_host(create_args, mock_retrieve_data):
|
|
38
|
+
args = create_args(
|
|
39
|
+
as_app=True, include_repository=True, github_host="ghe.example.com"
|
|
40
|
+
)
|
|
41
|
+
mock_retrieve_data.return_value = [page("a")]
|
|
42
|
+
|
|
43
|
+
github_backup.retrieve_repositories(args, NO_USER)
|
|
44
|
+
|
|
45
|
+
assert mock_retrieve_data.call_args.args[1] == (
|
|
46
|
+
"https://ghe.example.com/api/v3/installation/repositories"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_app_organization_still_lists_org_repositories(create_args, mock_retrieve_data):
|
|
51
|
+
args = create_args(as_app=True, include_repository=True, organization=True)
|
|
52
|
+
mock_retrieve_data.return_value = [{"name": "a"}]
|
|
53
|
+
|
|
54
|
+
repos = github_backup.retrieve_repositories(args, NO_USER)
|
|
55
|
+
|
|
56
|
+
assert mock_retrieve_data.call_args.args[1] == (
|
|
57
|
+
"https://api.github.com/orgs/testuser/repos"
|
|
58
|
+
)
|
|
59
|
+
assert repos == [{"name": "a"}]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_app_single_repository_is_fetched_directly(create_args, mock_retrieve_data):
|
|
63
|
+
args = create_args(as_app=True, repository="dotfiles")
|
|
64
|
+
mock_retrieve_data.return_value = [{"name": "dotfiles"}]
|
|
65
|
+
|
|
66
|
+
repos = github_backup.retrieve_repositories(args, NO_USER)
|
|
67
|
+
|
|
68
|
+
assert mock_retrieve_data.call_args.args[1] == (
|
|
69
|
+
"https://api.github.com/repos/testuser/dotfiles"
|
|
70
|
+
)
|
|
71
|
+
assert repos == [{"name": "dotfiles"}]
|
|
@@ -274,12 +274,24 @@ def test_graphql_failure_warns_and_continues(
|
|
|
274
274
|
def test_stale_issue_backed_up_though_since_excludes_it(
|
|
275
275
|
create_args, tmp_path, monkeypatch
|
|
276
276
|
):
|
|
277
|
-
"""
|
|
278
|
-
|
|
277
|
+
"""A sweep-only issue refreshes just its timeline and preserves its data."""
|
|
278
|
+
issue_cwd = tmp_path / "issues"
|
|
279
|
+
issue_cwd.mkdir()
|
|
280
|
+
stored = _issue(
|
|
281
|
+
7,
|
|
282
|
+
body="stored body",
|
|
283
|
+
comment_data=[{"id": 1, "body": "stored comment"}],
|
|
284
|
+
event_data=[{"id": 2, "event": "referenced"}],
|
|
285
|
+
timeline_data=[],
|
|
286
|
+
)
|
|
287
|
+
(issue_cwd / "7.json").write_text(json.dumps(stored))
|
|
279
288
|
args = _sweep_args(
|
|
280
289
|
create_args,
|
|
281
290
|
include_issues=True,
|
|
291
|
+
include_issue_comments=True,
|
|
292
|
+
include_issue_events=True,
|
|
282
293
|
include_issue_timeline=True,
|
|
294
|
+
include_attachments=True,
|
|
283
295
|
since="2026-07-01T00:00:00Z",
|
|
284
296
|
)
|
|
285
297
|
timeline = [_xref("2026-07-20T00:00:00Z")]
|
|
@@ -287,22 +299,35 @@ def test_stale_issue_backed_up_though_since_excludes_it(
|
|
|
287
299
|
monkeypatch.setattr(
|
|
288
300
|
github_backup,
|
|
289
301
|
"retrieve_data",
|
|
290
|
-
_fake_retrieve(
|
|
291
|
-
{"/issues": [], "/issues/7": [_issue(7)], "/7/timeline": timeline}, calls
|
|
292
|
-
),
|
|
302
|
+
_fake_retrieve({"/issues": [], "/7/timeline": timeline}, calls),
|
|
293
303
|
)
|
|
294
304
|
monkeypatch.setattr(
|
|
295
305
|
github_backup,
|
|
296
306
|
"retrieve_graphql_data",
|
|
297
307
|
_graphql_state({7: (1, "2026-07-20T00:00:00Z")}),
|
|
298
308
|
)
|
|
309
|
+
attachment_calls = []
|
|
310
|
+
monkeypatch.setattr(
|
|
311
|
+
github_backup,
|
|
312
|
+
"download_attachments",
|
|
313
|
+
lambda *args, **kwargs: attachment_calls.append((args, kwargs)),
|
|
314
|
+
)
|
|
299
315
|
|
|
300
316
|
github_backup.backup_issues(
|
|
301
317
|
args, str(tmp_path), {"full_name": "owner/repo"}, "https://api.github.com/repos"
|
|
302
318
|
)
|
|
303
319
|
|
|
304
|
-
saved = json.loads((
|
|
320
|
+
saved = json.loads((issue_cwd / "7.json").read_text())
|
|
305
321
|
assert saved["timeline_data"] == timeline
|
|
322
|
+
assert saved["body"] == "stored body"
|
|
323
|
+
assert saved["comment_data"] == stored["comment_data"]
|
|
324
|
+
assert saved["event_data"] == stored["event_data"]
|
|
325
|
+
assert calls == [
|
|
326
|
+
"https://api.github.com/repos/owner/repo/issues",
|
|
327
|
+
"https://api.github.com/repos/owner/repo/issues",
|
|
328
|
+
"https://api.github.com/repos/owner/repo/issues/7/timeline",
|
|
329
|
+
]
|
|
330
|
+
assert attachment_calls == []
|
|
306
331
|
|
|
307
332
|
|
|
308
333
|
def test_sweep_follows_pagination_cursors(create_args, tmp_path, monkeypatch):
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.65.0"
|
|
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
|
|
File without changes
|