From 0638666bc7ebc9c55134648d0c4f3cb21932a680 Mon Sep 17 00:00:00 2001 From: Changaco Date: Fri, 10 Apr 2026 13:38:23 +0000 Subject: [PATCH 01/17] handle more network errors ```python-traceback Traceback (most recent call last): File ".local/bin/github-backup", line 6, in sys.exit(main()) ~~~~^^ File ".local/share/pipx/venvs/github-backup/lib/python3.14/site-packages/github_backup/cli.py", line 83, in main backup_repositories(args, output_directory, repositories) ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File ".local/share/pipx/venvs/github-backup/lib/python3.14/site-packages/github_backup/github_backup.py", line 1845, in backup_repositories backup_pulls(args, repo_cwd, repository, repos_template) ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File ".local/share/pipx/venvs/github-backup/lib/python3.14/site-packages/github_backup/github_backup.py", line 2019, in backup_pulls pulls[number]["commit_data"] = retrieve_data(args, template) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^ File ".local/share/pipx/venvs/github-backup/lib/python3.14/site-packages/github_backup/github_backup.py", line 766, in retrieve_data return list(fetch_all()) File ".local/share/pipx/venvs/github-backup/lib/python3.14/site-packages/github_backup/github_backup.py", line 717, in fetch_all response = json.loads(http_response.read().decode("utf-8")) ~~~~~~~~~~~~~~~~~~^^ File "/usr/lib/python3.14/http/client.py", line 500, in read s = self._safe_read(self.length) File "/usr/lib/python3.14/http/client.py", line 648, in _safe_read data = self.fp.read(cursize) File "/usr/lib/python3.14/socket.py", line 725, in readinto return self._sock.recv_into(b) ~~~~~~~~~~~~~~~~~~~~^^^ File "/usr/lib/python3.14/ssl.py", line 1304, in recv_into return self.read(nbytes, buffer) ~~~~~~~~~^^^^^^^^^^^^^^^^ File "/usr/lib/python3.14/ssl.py", line 1138, in read return self._sslobj.read(len, buffer) ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^ ConnectionResetError: [Errno 104] Connection reset by peer ``` --- github_backup/github_backup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/github_backup/github_backup.py b/github_backup/github_backup.py index ae4ef2e..73a8a75 100644 --- a/github_backup/github_backup.py +++ b/github_backup/github_backup.py @@ -806,6 +806,7 @@ def _extract_legal_url(response_body_bytes): response = json.loads(http_response.read().decode("utf-8")) break # Exit retry loop and handle the data returned except ( + ConnectionError, IncompleteRead, json.decoder.JSONDecodeError, TimeoutError, From ddf82f1115f7d635993aa44454fb58c034624272 Mon Sep 17 00:00:00 2001 From: Changaco Date: Fri, 10 Apr 2026 15:25:05 +0000 Subject: [PATCH 02/17] suppress output of call to `git lfs version` --- github_backup/github_backup.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/github_backup/github_backup.py b/github_backup/github_backup.py index ae4ef2e..317a803 100644 --- a/github_backup/github_backup.py +++ b/github_backup/github_backup.py @@ -1781,7 +1781,10 @@ def get_authenticated_user(args): def check_git_lfs_install(): - exit_code = subprocess.call(["git", "lfs", "version"]) + exit_code = subprocess.call( + ["git", "lfs", "version"], + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, + ) if exit_code != 0: raise Exception( "The argument --lfs requires you to have Git LFS installed.\nYou can get it from https://git-lfs.github.com." From ddf7f82e65e5e57f0d5c499ed6f56234cb686eb3 Mon Sep 17 00:00:00 2001 From: Changaco Date: Fri, 10 Apr 2026 13:46:44 +0000 Subject: [PATCH 03/17] add missing `context` argument to `urlopen` call --- github_backup/github_backup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/github_backup/github_backup.py b/github_backup/github_backup.py index ae4ef2e..6670d2d 100644 --- a/github_backup/github_backup.py +++ b/github_backup/github_backup.py @@ -1297,7 +1297,7 @@ def get_jwt_signed_url_via_markdown_api(url, token, repo_context): request.add_header("Content-Type", "application/json") request.add_header("Accept", "application/vnd.github+json") - html = urlopen(request, timeout=30).read().decode("utf-8") + html = urlopen(request, context=https_ctx, timeout=30).read().decode("utf-8") # Parse JWT-signed URL from HTML response # Format: From 2f130ecd6692bf8bc6e51bade07b5f36e56181ff Mon Sep 17 00:00:00 2001 From: Changaco Date: Fri, 10 Apr 2026 13:54:13 +0000 Subject: [PATCH 04/17] remove bad invocation of the system shell --- github_backup/github_backup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/github_backup/github_backup.py b/github_backup/github_backup.py index 6670d2d..80689b8 100644 --- a/github_backup/github_backup.py +++ b/github_backup/github_backup.py @@ -2980,7 +2980,7 @@ def fetch_repository( masked_remote_url = mask_password(remote_url) initialized = subprocess.call( - "git ls-remote " + remote_url, stdout=FNULL, stderr=FNULL, shell=True + ["git", "ls-remote", remote_url], stdout=FNULL, stderr=FNULL ) if initialized == 128: if ".wiki.git" in remote_url: From b92aee6f114f98502fea616abeefbbe924229ff0 Mon Sep 17 00:00:00 2001 From: Changaco Date: Fri, 10 Apr 2026 15:12:13 +0000 Subject: [PATCH 05/17] use `subprocess.DEVNULL` instead of emulating it --- github_backup/github_backup.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/github_backup/github_backup.py b/github_backup/github_backup.py index 8b96622..990993b 100644 --- a/github_backup/github_backup.py +++ b/github_backup/github_backup.py @@ -40,7 +40,6 @@ DISCUSSION_REPLIES_QUERY, ) -FNULL = open(os.devnull, "w") FILE_URI_PREFIX = "file://" logger = logging.getLogger(__name__) @@ -529,19 +528,18 @@ def get_auth(args, encode=True, for_git_cli=False): if platform.system() != "Darwin": raise Exception("Keychain arguments are only supported on Mac OSX") try: - with open(os.devnull, "w") as devnull: - token = subprocess.check_output( - [ - "security", - "find-generic-password", - "-s", - args.osx_keychain_item_name, - "-a", - args.osx_keychain_item_account, - "-w", - ], - stderr=devnull, - ).strip() + token = subprocess.check_output( + [ + "security", + "find-generic-password", + "-s", + args.osx_keychain_item_name, + "-a", + args.osx_keychain_item_account, + "-w", + ], + stderr=subprocess.DEVNULL, + ).strip() token = token.decode("utf-8") auth = token + ":" + "x-oauth-basic" except subprocess.SubprocessError: @@ -2984,7 +2982,8 @@ def fetch_repository( masked_remote_url = mask_password(remote_url) initialized = subprocess.call( - ["git", "ls-remote", remote_url], stdout=FNULL, stderr=FNULL + ["git", "ls-remote", remote_url], + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) if initialized == 128: if ".wiki.git" in remote_url: From f3eabf0bfe522b7749d693ceaa65c5de4f13d8bc Mon Sep 17 00:00:00 2001 From: Changaco Date: Fri, 10 Apr 2026 16:23:03 +0000 Subject: [PATCH 06/17] don't pass stdin when doing so can't do any good When the child process doesn't inherit stderr, it can't ask the user for input, so it shouldn't inherit stdin either. --- github_backup/github_backup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/github_backup/github_backup.py b/github_backup/github_backup.py index 990993b..b76322a 100644 --- a/github_backup/github_backup.py +++ b/github_backup/github_backup.py @@ -1781,7 +1781,7 @@ def get_authenticated_user(args): def check_git_lfs_install(): exit_code = subprocess.call( - ["git", "lfs", "version"], + ["git", "lfs", "version"], stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) if exit_code != 0: @@ -2982,7 +2982,7 @@ def fetch_repository( masked_remote_url = mask_password(remote_url) initialized = subprocess.call( - ["git", "ls-remote", remote_url], + ["git", "ls-remote", remote_url], stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) if initialized == 128: From ccc27b95f7203ec42bf695cc270317fdd73f4489 Mon Sep 17 00:00:00 2001 From: Changaco Date: Thu, 30 Apr 2026 10:46:46 +0000 Subject: [PATCH 07/17] remove legacy code in `mkdir_p` function --- github_backup/github_backup.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/github_backup/github_backup.py b/github_backup/github_backup.py index b76322a..4c07808 100644 --- a/github_backup/github_backup.py +++ b/github_backup/github_backup.py @@ -6,7 +6,6 @@ import base64 import calendar import codecs -import errno import json import logging import os @@ -127,13 +126,7 @@ def check_io(): def mkdir_p(*args): for path in args: - try: - os.makedirs(path) - except OSError as exc: # Python >2.5 - if exc.errno == errno.EEXIST and os.path.isdir(path): - pass - else: - raise + os.makedirs(path, exist_ok=True) def mask_password(url, secret="*****"): From f1fca0f9b7379e02c3d0903daee9d1954d7009eb Mon Sep 17 00:00:00 2001 From: Changaco Date: Thu, 30 Apr 2026 10:53:40 +0000 Subject: [PATCH 08/17] don't leave files open --- github_backup/github_backup.py | 41 ++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/github_backup/github_backup.py b/github_backup/github_backup.py index 4c07808..e567d3e 100644 --- a/github_backup/github_backup.py +++ b/github_backup/github_backup.py @@ -624,7 +624,8 @@ def get_github_host(args): def read_file_contents(file_uri): - return open(file_uri[len(FILE_URI_PREFIX) :], "rt").readline().strip() + with open(file_uri[len(FILE_URI_PREFIX) :], "rt") as f: + return f.readline().strip() def read_token_from_gh_cli(args): @@ -1964,10 +1965,11 @@ def read_legacy_last_update(args, output_directory): return None, None last_update_path = os.path.join(output_directory, INCREMENTAL_LAST_UPDATE_FILENAME) - if os.path.exists(last_update_path): - return last_update_path, open(last_update_path).read().strip() - - return last_update_path, None + try: + with open(last_update_path) as f: + return last_update_path, f.read().strip() + except FileNotFoundError: + return last_update_path, None def read_resource_last_update(args, resource_cwd, legacy_last_update=None): @@ -1975,13 +1977,13 @@ def read_resource_last_update(args, resource_cwd, legacy_last_update=None): return None last_update_path = os.path.join(resource_cwd, INCREMENTAL_LAST_UPDATE_FILENAME) - if os.path.exists(last_update_path): - return open(last_update_path).read().strip() - - if legacy_last_update and resource_backup_exists(resource_cwd): - return legacy_last_update - - return None + try: + with open(last_update_path) as f: + return f.read().strip() + except FileNotFoundError: + if legacy_last_update and resource_backup_exists(resource_cwd): + return legacy_last_update + return None def write_resource_last_update(args, resource_cwd, repository): @@ -1990,7 +1992,8 @@ def write_resource_last_update(args, resource_cwd, repository): mkdir_p(resource_cwd) last_update_path = os.path.join(resource_cwd, INCREMENTAL_LAST_UPDATE_FILENAME) - open(last_update_path, "w").write(get_repository_checkpoint_time(repository)) + with open(last_update_path, "w") as f: + f.write(get_repository_checkpoint_time(repository)) def iter_incremental_resource_dirs(output_directory): @@ -2378,7 +2381,8 @@ def backup_discussions(args, repo_cwd, repository): discussions_since = None discussion_last_update_path = os.path.join(discussion_cwd, "last_update") if args.incremental and os.path.exists(discussion_last_update_path): - discussions_since = open(discussion_last_update_path).read().strip() + with open(discussion_last_update_path) as f: + discussions_since = f.read().strip() logger.info("Retrieving {0} discussions".format(repository["full_name"])) try: @@ -2464,7 +2468,8 @@ def backup_discussions(args, repo_cwd, repository): and newest_seen and (not discussions_since or newest_seen > discussions_since) ): - open(discussion_last_update_path, "w").write(newest_seen) + with open(discussion_last_update_path, "w") as f: + f.write(newest_seen) attempted_count = len(summaries) - skipped_count if not summaries: @@ -2601,7 +2606,8 @@ def get_pull_reviews_since(args, pulls_cwd): # repository-level checkpoint would otherwise skip old PRs forever. return None, None, reviews_last_update_path - reviews_since = open(reviews_last_update_path).read().strip() + with open(reviews_last_update_path) as f: + reviews_since = f.read().strip() if args_since and reviews_since: return min(args_since, reviews_since), reviews_since, reviews_last_update_path @@ -2753,7 +2759,8 @@ def pull_is_due_for_repository_checkpoint(pull): and not pull_review_errors and (not pull_reviews_since or newest_pull_update > pull_reviews_since) ): - open(pull_reviews_last_update_path, "w").write(newest_pull_update) + with open(pull_reviews_last_update_path, "w") as f: + f.write(newest_pull_update) def backup_milestones(args, repo_cwd, repository, repos_template): From 17b79fcbef880e529ab376090fbd193f102300ac Mon Sep 17 00:00:00 2001 From: Changaco Date: Thu, 30 Apr 2026 10:58:08 +0000 Subject: [PATCH 09/17] rename a function to match what it actually does --- github_backup/github_backup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/github_backup/github_backup.py b/github_backup/github_backup.py index e567d3e..f4a94b9 100644 --- a/github_backup/github_backup.py +++ b/github_backup/github_backup.py @@ -545,7 +545,7 @@ def get_auth(args, encode=True, for_git_cli=False): ) elif args.token_fine: if args.token_fine.startswith(FILE_URI_PREFIX): - args.token_fine = read_file_contents(args.token_fine) + args.token_fine = read_first_line(args.token_fine) if args.token_fine.startswith("github_pat_"): auth = args.token_fine @@ -561,7 +561,7 @@ def get_auth(args, encode=True, for_git_cli=False): ) args.token_classic = read_token_from_gh_cli(args) elif args.token_classic.startswith(FILE_URI_PREFIX): - args.token_classic = read_file_contents(args.token_classic) + args.token_classic = read_first_line(args.token_classic) if not args.as_app: auth = args.token_classic + ":" + "x-oauth-basic" @@ -623,7 +623,7 @@ def get_github_host(args): return host -def read_file_contents(file_uri): +def read_first_line(file_uri): with open(file_uri[len(FILE_URI_PREFIX) :], "rt") as f: return f.readline().strip() From 3cda5a01fdf094ea33de7d3c02aa7cc60d553e9b Mon Sep 17 00:00:00 2001 From: Changaco Date: Fri, 10 Apr 2026 20:32:16 +0000 Subject: [PATCH 10/17] document that `--all` doesn't imply `--attachments` --- README.rst | 2 +- github_backup/github_backup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 3a4be3b..ed037fd 100644 --- a/README.rst +++ b/README.rst @@ -325,7 +325,7 @@ Gotchas / Known-issues All is not everything --------------------- -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. +The ``--all`` argument does not include: downloading attachments from issue and pull request comments (``--attachments``), 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. Starred repository size ----------------------- diff --git a/github_backup/github_backup.py b/github_backup/github_backup.py index 8b96622..dc872c7 100644 --- a/github_backup/github_backup.py +++ b/github_backup/github_backup.py @@ -488,7 +488,7 @@ def parse_args(args=None): "--attachments", action="store_true", dest="include_attachments", - help="download user-attachments from issues, pull requests, and discussions", + help="download user-attachments from issues, pull requests, and discussions [*]", ) parser.add_argument( "--throttle-limit", From 543d76f24bc4eb808618e7a8b5ccbabea80fa700 Mon Sep 17 00:00:00 2001 From: Changaco Date: Fri, 10 Apr 2026 20:35:06 +0000 Subject: [PATCH 11/17] fix a typo in the README --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index ed037fd..e5f0f14 100644 --- a/README.rst +++ b/README.rst @@ -363,7 +363,7 @@ This means any blocking errors on previous runs can cause missing data in backup 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. -Still saver than the previous version. +Still safer than the previous version. Specifically, issues and pull requests are handled like this. From 9340aa3aaada4c2d41aa8f9c1b6164f9ee9ed082 Mon Sep 17 00:00:00 2001 From: Changaco Date: Fri, 10 Apr 2026 20:35:47 +0000 Subject: [PATCH 12/17] try to clarify what `--incremental` actually does --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index e5f0f14..1bd3ff6 100644 --- a/README.rst +++ b/README.rst @@ -365,7 +365,7 @@ Using (``--incremental-by-files``) will request new data from the API **based on Still safer than the previous version. -Specifically, issues and pull requests are handled like this. +Incremental backup only changes how issue and pull request data is fetched. Known blocking errors --------------------- From a2391a550e45ff4882f006696599fcd408317781 Mon Sep 17 00:00:00 2001 From: Changaco Date: Fri, 10 Apr 2026 20:37:05 +0000 Subject: [PATCH 13/17] remove pointless and unsafe `export`s in examples --- README.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 1bd3ff6..33a89fb 100644 --- a/README.rst +++ b/README.rst @@ -429,12 +429,12 @@ Github Backup Examples Backup all repositories, including private ones using a classic token:: - export ACCESS_TOKEN=SOME-GITHUB-TOKEN + ACCESS_TOKEN=SOME-GITHUB-TOKEN github-backup WhiteHouse --token $ACCESS_TOKEN --organization --output-directory /tmp/white-house --repositories --private Use a fine-grained access token to backup a single organization repository with everything else (wiki, pull requests, comments, issues etc):: - export FINE_ACCESS_TOKEN=SOME-GITHUB-TOKEN + FINE_ACCESS_TOKEN=SOME-GITHUB-TOKEN ORGANIZATION=docker REPO=cli # e.g. git@github.com:docker/cli.git @@ -442,14 +442,14 @@ Use a fine-grained access token to backup a single organization repository with Quietly and incrementally backup useful Github user data (public and private repos with SSH) including; all issues, pulls, all public starred repos and gists (omitting "hooks", "releases" and therefore "assets" to prevent blocking). *Great for a cron job.* :: - export FINE_ACCESS_TOKEN=SOME-GITHUB-TOKEN + FINE_ACCESS_TOKEN=SOME-GITHUB-TOKEN GH_USER=YOUR-GITHUB-USER github-backup -f $FINE_ACCESS_TOKEN --prefer-ssh -o ~/github-backup/ -l error -P -i --all-starred --starred --watched --followers --following --issues --issue-comments --issue-events --pulls --pull-comments --pull-reviews --pull-commits --labels --milestones --security-advisories --discussions --repositories --wikis --releases --assets --attachments --pull-details --gists --starred-gists $GH_USER Debug an error/block or incomplete backup into a temporary directory. Omit "incremental" to fill a previous incomplete backup. :: - export FINE_ACCESS_TOKEN=SOME-GITHUB-TOKEN + FINE_ACCESS_TOKEN=SOME-GITHUB-TOKEN GH_USER=YOUR-GITHUB-USER github-backup -f $FINE_ACCESS_TOKEN -o /tmp/github-backup/ -l debug -P --all-starred --starred --watched --followers --following --issues --issue-comments --issue-events --pulls --pull-comments --pull-reviews --pull-commits --labels --milestones --discussions --repositories --wikis --releases --assets --pull-details --gists --starred-gists $GH_USER From d30d9bfe6034b174ae3839f7aa13f4ad2eff4dc3 Mon Sep 17 00:00:00 2001 From: Changaco Date: Fri, 10 Apr 2026 20:38:31 +0000 Subject: [PATCH 14/17] eliminate trailing spaces --- README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 33a89fb..c4d0fd0 100644 --- a/README.rst +++ b/README.rst @@ -22,7 +22,7 @@ Using PIP via PyPI:: Using PIP via Github (more likely the latest version):: pip install git+https://github.com/josegonzalez/python-github-backup.git#egg=github-backup - + *Install note for python newcomers:* Python scripts are unlikely to be included in your ``$PATH`` by default, this means it cannot be run directly in terminal with ``$ github-backup ...``, you can either add python's install path to your environments ``$PATH`` or call the script directly e.g. using ``$ ~/.local/bin/github-backup``.* @@ -249,7 +249,7 @@ Note: When you run github-backup, you will be asked whether you want to allow " Github Rate-limit and Throttling -------------------------------- -"github-backup" will automatically throttle itself based on feedback from the Github API. +"github-backup" will automatically throttle itself based on feedback from the Github API. Their API is usually rate-limited to 5000 calls per hour. The API will ask github-backup to pause until a specific time when the limit is reset again (at the start of the next hour). This continues until the backup is complete. @@ -446,7 +446,7 @@ Quietly and incrementally backup useful Github user data (public and private rep GH_USER=YOUR-GITHUB-USER github-backup -f $FINE_ACCESS_TOKEN --prefer-ssh -o ~/github-backup/ -l error -P -i --all-starred --starred --watched --followers --following --issues --issue-comments --issue-events --pulls --pull-comments --pull-reviews --pull-commits --labels --milestones --security-advisories --discussions --repositories --wikis --releases --assets --attachments --pull-details --gists --starred-gists $GH_USER - + Debug an error/block or incomplete backup into a temporary directory. Omit "incremental" to fill a previous incomplete backup. :: FINE_ACCESS_TOKEN=SOME-GITHUB-TOKEN From 8e76089565d7822bd94816433c2509daee40f26b Mon Sep 17 00:00:00 2001 From: Changaco Date: Sat, 25 Apr 2026 07:07:24 +0000 Subject: [PATCH 15/17] document that nothing is saved by default --- README.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.rst b/README.rst index c4d0fd0..c3d5d5d 100644 --- a/README.rst +++ b/README.rst @@ -327,6 +327,11 @@ All is not everything The ``--all`` argument does not include: downloading attachments from issue and pull request comments (``--attachments``), 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. +Saves nothing if no arguments are passed +---------------------------------------- + +At least one argument like ``--all`` or ``--repositories`` is needed for github-backup to actually save data. Without relevant arguments, github-backup fetches some data from GitHub but doesn't put any of it into files. + Starred repository size ----------------------- From bd6eea02d5095a83d25f2d57202bb78c93be1cc2 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Thu, 30 Apr 2026 15:52:41 +0000 Subject: [PATCH 16/17] Release version 0.62.1 --- CHANGES.rst | 58 ++++++++++++++++++++++++++++++++++++++- github_backup/__init__.py | 2 +- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 86bcb32..20ac838 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,9 +1,65 @@ Changelog ========= -0.62.0 (2026-04-29) +0.62.1 (2026-04-30) ------------------- ------------------------ +- Document that nothing is saved by default. [Changaco] +- Eliminate trailing spaces. [Changaco] +- Remove pointless and unsafe `export`s in examples. [Changaco] +- Try to clarify what `--incremental` actually does. [Changaco] +- Fix a typo in the README. [Changaco] +- Document that `--all` doesn't imply `--attachments` [Changaco] +- Rename a function to match what it actually does. [Changaco] +- Don't leave files open. [Changaco] +- Remove legacy code in `mkdir_p` function. [Changaco] +- Don't pass stdin when doing so can't do any good. [Changaco] + + When the child process doesn't inherit stderr, it can't ask the user for input, so it shouldn't inherit stdin either. +- Use `subprocess.DEVNULL` instead of emulating it. [Changaco] +- Remove bad invocation of the system shell. [Changaco] +- Add missing `context` argument to `urlopen` call. [Changaco] +- Suppress output of call to `git lfs version` [Changaco] +- Handle more network errors. [Changaco] + + ```python-traceback + Traceback (most recent call last): + File ".local/bin/github-backup", line 6, in + sys.exit(main()) + ~~~~^^ + File ".local/share/pipx/venvs/github-backup/lib/python3.14/site-packages/github_backup/cli.py", line 83, in main + backup_repositories(args, output_directory, repositories) + ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File ".local/share/pipx/venvs/github-backup/lib/python3.14/site-packages/github_backup/github_backup.py", line 1845, in backup_repositories + backup_pulls(args, repo_cwd, repository, repos_template) + ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File ".local/share/pipx/venvs/github-backup/lib/python3.14/site-packages/github_backup/github_backup.py", line 2019, in backup_pulls + pulls[number]["commit_data"] = retrieve_data(args, template) + ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^ + File ".local/share/pipx/venvs/github-backup/lib/python3.14/site-packages/github_backup/github_backup.py", line 766, in retrieve_data + return list(fetch_all()) + File ".local/share/pipx/venvs/github-backup/lib/python3.14/site-packages/github_backup/github_backup.py", line 717, in fetch_all + response = json.loads(http_response.read().decode("utf-8")) + ~~~~~~~~~~~~~~~~~~^^ + File "/usr/lib/python3.14/http/client.py", line 500, in read + s = self._safe_read(self.length) + File "/usr/lib/python3.14/http/client.py", line 648, in _safe_read + data = self.fp.read(cursize) + File "/usr/lib/python3.14/socket.py", line 725, in readinto + return self._sock.recv_into(b) + ~~~~~~~~~~~~~~~~~~~~^^^ + File "/usr/lib/python3.14/ssl.py", line 1304, in recv_into + return self.read(nbytes, buffer) + ~~~~~~~~~^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.14/ssl.py", line 1138, in read + return self._sslobj.read(len, buffer) + ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^ + ConnectionResetError: [Errno 104] Connection reset by peer + ``` + + +0.62.0 (2026-04-29) +------------------- - Skip checkpoint-equal incremental items. [Duncan Ogilvie] - Avoid redundant release asset list requests. [Duncan Ogilvie] - Reduce unnecessary pull requests with incremental fetching. [Duncan diff --git a/github_backup/__init__.py b/github_backup/__init__.py index 647040d..b7b61f3 100644 --- a/github_backup/__init__.py +++ b/github_backup/__init__.py @@ -1 +1 @@ -__version__ = "0.62.0" +__version__ = "0.62.1" From 2cbce1425cbb2a2f00ba7996f795415d2ede6c37 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 22:45:36 +0000 Subject: [PATCH 17/17] chore(deps): bump black in the python-packages group Bumps the python-packages group with 1 update: [black](https://github.com/psf/black). Updates `black` from 26.3.1 to 26.5.1 - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/compare/26.3.1...26.5.1) --- updated-dependencies: - dependency-name: black dependency-version: 26.5.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: python-packages ... Signed-off-by: dependabot[bot] --- release-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release-requirements.txt b/release-requirements.txt index ad8bc5c..117aeea 100644 --- a/release-requirements.txt +++ b/release-requirements.txt @@ -1,6 +1,6 @@ # Linting & Formatting autopep8==2.3.2 -black==26.3.1 +black==26.5.1 flake8==7.3.0 # Testing