From b92aee6f114f98502fea616abeefbbe924229ff0 Mon Sep 17 00:00:00 2001 From: Changaco Date: Fri, 10 Apr 2026 15:12:13 +0000 Subject: [PATCH 1/5] 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 @@ from .graphql_queries import ( 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 2/5] 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 3/5] 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 argparse import base64 import calendar import codecs -import errno import json import logging import os @@ -127,13 +126,7 @@ def logging_subprocess( 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 4/5] 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 backup_pulls(args, repo_cwd, repository, repos_template): 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 5/5] 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()