mirror of
https://chromium.googlesource.com/chromium/tools/depot_tools.git
synced 2026-01-11 18:51:29 +00:00
Speed up presubmit by caching code-owners check
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>
This commit is contained in:
140
gerrit_cache.py
Normal file
140
gerrit_cache.py
Normal file
@@ -0,0 +1,140 @@
|
||||
# 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))
|
||||
Reference in New Issue
Block a user