mirror of
https://chromium.googlesource.com/chromium/tools/depot_tools.git
synced 2026-01-11 10:41:31 +00:00
Currently, presubmit queries the gerrit server every run to check if the code-owners plugin is enabled. This CL caches that result in a /tmp file per repository. This saves as much as 1.5s per `git cl presubmit`. Change-Id: I1a3631074d1bcbb1a254caa6654fd8434f069aa2 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/7227749 Commit-Queue: Josiah Kiehl <kiehl@google.com> Reviewed-by: Yiwei Zhang <yiwzhang@google.com>
141 lines
4.6 KiB
Python
141 lines
4.6 KiB
Python
# Copyright (c) 2025 The Chromium Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
import contextlib
|
|
import json
|
|
import os
|
|
import re
|
|
import tempfile
|
|
|
|
import gclient_utils
|
|
import lockfile
|
|
import scm
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def _AtomicFileWriter(path, mode='w'):
|
|
"""Atomic file writer context manager."""
|
|
# Create temp file in same directory to ensure atomic rename works
|
|
fd, temp_path = tempfile.mkstemp(dir=os.path.dirname(path),
|
|
text='b' not in mode)
|
|
try:
|
|
with os.fdopen(fd, mode) as f:
|
|
yield f
|
|
# Atomic rename
|
|
gclient_utils.safe_replace(temp_path, path)
|
|
except Exception:
|
|
# Cleanup on failure
|
|
if os.path.exists(temp_path):
|
|
os.remove(temp_path)
|
|
raise
|
|
|
|
|
|
class GerritCache(object):
|
|
"""Simple JSON file-based cache for Gerrit API results."""
|
|
|
|
def __init__(self, root_dir):
|
|
self.root_dir = root_dir
|
|
self.cache_path = self._get_cache_path()
|
|
|
|
@staticmethod
|
|
def get_repo_code_owners_enabled_key(host, project):
|
|
return 'code-owners.%s.%s.enabled' % (host, project)
|
|
|
|
@staticmethod
|
|
def get_host_code_owners_enabled_key(host):
|
|
return 'code-owners.%s.enabled' % host
|
|
|
|
def _get_cache_path(self):
|
|
path = os.environ.get('DEPOT_TOOLS_GERRIT_CACHE_PATH')
|
|
if path:
|
|
return path
|
|
|
|
try:
|
|
path = scm.GIT.GetConfig(self.root_dir,
|
|
'depot-tools.gerrit-cache-path')
|
|
if path:
|
|
return path
|
|
except Exception:
|
|
pass
|
|
|
|
try:
|
|
if self.root_dir:
|
|
# Use a deterministic name based on the repo path
|
|
sanitized_path = re.sub(r'[^a-zA-Z0-9]', '_',
|
|
os.path.abspath(self.root_dir))
|
|
filename = 'depot_tools_gerrit_cache_%s.json' % sanitized_path
|
|
path = os.path.join(tempfile.gettempdir(), filename)
|
|
else:
|
|
fd, path = tempfile.mkstemp(prefix='depot_tools_gerrit_cache_',
|
|
suffix='.json')
|
|
os.close(fd)
|
|
|
|
if not os.path.exists(path):
|
|
# Initialize with empty JSON object
|
|
with open(path, 'w') as f:
|
|
json.dump({}, f)
|
|
|
|
try:
|
|
scm.GIT.SetConfig(self.root_dir,
|
|
'depot-tools.gerrit-cache-path', path)
|
|
except Exception:
|
|
# If we can't set config (e.g. not a git repo and no env var
|
|
# set), just return the temp path. It will be a per-process
|
|
# cache in that case.
|
|
pass
|
|
return path
|
|
except Exception:
|
|
# Fallback to random temp file if everything else fails
|
|
fd, path = tempfile.mkstemp(prefix='gerrit_cache_', suffix='.json')
|
|
os.close(fd)
|
|
return path
|
|
|
|
def get(self, key):
|
|
if not self.cache_path:
|
|
return None
|
|
try:
|
|
with lockfile.lock(self.cache_path, timeout=1):
|
|
if os.path.exists(self.cache_path):
|
|
with open(self.cache_path, 'r') as f:
|
|
try:
|
|
data = json.load(f)
|
|
return data.get(key)
|
|
except ValueError:
|
|
# Corrupt cache file, treat as miss
|
|
return None
|
|
except Exception:
|
|
# Ignore cache errors
|
|
return None
|
|
|
|
def set(self, key, value):
|
|
if not self.cache_path:
|
|
return
|
|
try:
|
|
with lockfile.lock(self.cache_path, timeout=1):
|
|
data = {}
|
|
if os.path.exists(self.cache_path):
|
|
with open(self.cache_path, 'r') as f:
|
|
try:
|
|
data = json.load(f)
|
|
except ValueError:
|
|
# Corrupt cache, start fresh
|
|
data = {}
|
|
data[key] = value
|
|
with _AtomicFileWriter(self.cache_path, 'w') as f:
|
|
json.dump(data, f)
|
|
except Exception:
|
|
# Ignore cache errors
|
|
pass
|
|
|
|
def getBoolean(self, key):
|
|
"""Returns the value for key as a boolean, or None if missing."""
|
|
val = self.get(key)
|
|
if val is None:
|
|
return None
|
|
return bool(val)
|
|
|
|
def setBoolean(self, key, value):
|
|
"""Sets the value for key as a boolean."""
|
|
self.set(key, bool(value))
|