From da49ca88c279fd766d5cd30719af9327e3aa2757 Mon Sep 17 00:00:00 2001 From: Egor Tensin Date: Thu, 15 Oct 2026 09:28:25 +0500 Subject: don't touch the global ~/.gitconfig This practice was sus from the very beginning. --- cgitize/cgit.py | 17 +++++----- cgitize/git.py | 70 +++++++++++++++--------------------------- test/integration/lib/common.sh | 2 +- 3 files changed, 34 insertions(+), 55 deletions(-) diff --git a/cgitize/cgit.py b/cgitize/cgit.py index 547235c..d4a8ae2 100644 --- a/cgitize/cgit.py +++ b/cgitize/cgit.py @@ -70,7 +70,7 @@ class AgeFile: # Except I think that the committer date, not author date better # represents activity. with chdir(repo_dir): - success, output = Git.capture( + success, output = Git().capture( "for-each-ref", "--sort=-committerdate", "--count=1", @@ -123,7 +123,7 @@ class CGitRepositories: return self._mirror(repo) with chdir(repo_dir): - success, output = Git.capture("rev-parse", "--is-inside-work-tree") + success, output = Git().capture("rev-parse", "--is-inside-work-tree") if not success: # Overwrite the existing directory if it's not a Git repository. logging.warning( @@ -132,7 +132,7 @@ class CGitRepositories: ) return self._mirror(repo) - success, output = Git.capture("config", "--get", "remote.origin.url") + success, output = Git().capture("config", "--get", "remote.origin.url") if not success: # Every repository managed by this script should have the # 'origin' remote. If it doesn't, it's trash. Overwrite the @@ -171,17 +171,16 @@ class CGitRepositories: except Exception as e: logging.exception(e) return False - with Git.setup_auth(repo): - return Git.check("clone", "--mirror", "--quiet", repo.clone_url, repo_dir) + with Git.setup_auth(repo) as git: + return git.check("clone", "--mirror", "--quiet", repo.clone_url, repo_dir) def _fix_upstream_url(self, repo): repo_dir = self.get_repo_dir(repo) with chdir(repo_dir): - return Git.check("remote", "set-url", "origin", repo.clone_url) + return Git().check("remote", "set-url", "origin", repo.clone_url) def _update_existing(self, repo): logging.info("Updating repository '%s'", repo.name) repo_dir = self.get_repo_dir(repo) - with chdir(repo_dir): - with Git.setup_auth(repo): - return Git.check("remote", "update", "--prune") + with chdir(repo_dir), Git.setup_auth(repo) as git: + return git.check("remote", "update", "--prune") diff --git a/cgitize/git.py b/cgitize/git.py index 4f5eb19..fdb2fef 100644 --- a/cgitize/git.py +++ b/cgitize/git.py @@ -5,6 +5,7 @@ from contextlib import contextmanager import os +import tempfile from cgitize import utils @@ -12,37 +13,21 @@ GIT_ENV = os.environ.copy() GIT_ENV["GIT_SSH_COMMAND"] = ( "ssh -oBatchMode=yes -oLogLevel=QUIET -oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null" ) +GIT_ENV["GIT_CONFIG_NOSYSTEM"] = "true" +GIT_ENV["GIT_CONFIG_GLOBAL"] = "/dev/null" class Config: - def __init__(self, path): - self.path = path - - def exists(self): - return os.path.exists(self.path) - - def open(self, mode="r"): - return open(self.path, mode=mode, encoding="utf-8") - - def read(self): - with self.open(mode="r") as fd: - return fd.read() - - def write(self, contents): - with self.open(mode="w") as fd: - fd.write(contents) - - @contextmanager - def backup(self): - old_contents = self.read() - try: - yield old_contents - finally: - self.write(old_contents) - # What follows is an exteremely loose interpretation of what the .gitconfig # syntax is. The source was git-config(1). + @staticmethod + @contextmanager + def make_tmp(): + tmp = tempfile.NamedTemporaryFile(mode="w", encoding="utf-8") + with tmp as result, utils.protected_file(result.name): + yield result + class Section: def __init__(self, name, variables): Config.Section.validate_name(name) @@ -148,31 +133,26 @@ class Config: class Git: EXE = "git" - @staticmethod - def check(*args, **kwargs): - return utils.try_run(Git.EXE, *args, env=GIT_ENV, **kwargs) + def __init__(self, config=None): + self._env = GIT_ENV.copy() + if config is not None: + self._env["GIT_CONFIG_GLOBAL"] = config - @staticmethod - def capture(*args, **kwargs): - return utils.try_capture(Git.EXE, *args, env=GIT_ENV, **kwargs) + def check(self, *args, **kwargs): + return utils.try_run(Git.EXE, *args, env=self._env, **kwargs) - @staticmethod - def get_global_config(): - return Config(os.path.expanduser("~/.gitconfig")) + def capture(self, *args, **kwargs): + return utils.try_capture(Git.EXE, *args, env=self._env, **kwargs) @staticmethod @contextmanager def setup_auth(repo): if not repo.url_auth: - yield + yield Git() return - config = Git.get_global_config() - with utils.protected_file(config.path): - with config.backup() as old_contents: - variables = [Config.Variable("insteadOf", repo.clone_url)] - subsection = Config.Subsection( - "url", repo.clone_url_with_auth, variables - ) - new_contents = f"{old_contents}\n{subsection.format()}" - config.write(new_contents) - yield + with Config.make_tmp() as config: + variables = [Config.Variable("insteadOf", repo.clone_url)] + subsection = Config.Subsection("url", repo.clone_url_with_auth, variables) + config.write(subsection.format()) + config.flush() + yield Git(config.name) diff --git a/test/integration/lib/common.sh b/test/integration/lib/common.sh index 10774a7..45dad8c 100644 --- a/test/integration/lib/common.sh +++ b/test/integration/lib/common.sh @@ -4,7 +4,7 @@ # Distributed under the MIT License. export GIT_CONFIG_NOSYSTEM=true -#export GIT_CONFIG_GLOBAL=/dev/null +export GIT_CONFIG_GLOBAL=/dev/null test_run_cgitize() { local cmd=(python3 -m cgitize.main --config "$test_config_path" "$@") -- cgit v1.3