[update-checkout] add a check for locked repositories

This commit is contained in:
Charles Zablit
2025-09-03 12:38:05 +01:00
parent 174636c7c9
commit 47da1dcc2e
3 changed files with 126 additions and 2 deletions

View File

@@ -145,10 +145,11 @@ def setup_mock_remote(base_dir, base_config):
BASEDIR_ENV_VAR = 'UPDATECHECKOUT_TEST_WORKSPACE_DIR'
CURRENT_FILE_DIR = os.path.dirname(os.path.abspath(__file__))
UPDATE_CHECKOUT_EXECUTABLE = 'update-checkout.cmd' if os.name == 'nt' else 'update-checkout'
UPDATE_CHECKOUT_PATH = os.path.abspath(os.path.join(CURRENT_FILE_DIR,
os.path.pardir,
os.path.pardir,
'update-checkout'))
UPDATE_CHECKOUT_EXECUTABLE))
class SchemeMockTestCase(unittest.TestCase):

View File

@@ -0,0 +1,89 @@
import unittest
from unittest.mock import patch
from update_checkout.update_checkout import _is_any_repository_locked
class TestIsAnyRepositoryLocked(unittest.TestCase):
@patch("os.path.exists")
@patch("os.path.isdir")
@patch("os.listdir")
def test_repository_with_lock_file(self, mock_listdir, mock_isdir, mock_exists):
pool_args = [
("/fake_path", None, "repo1"),
("/fake_path", None, "repo2"),
]
def listdir_side_effect(path):
if "repo1" in path:
return ["index.lock", "config"]
elif "repo2" in path:
return ["HEAD", "config"]
return []
mock_exists.return_value = True
mock_isdir.return_value = True
mock_listdir.side_effect = listdir_side_effect
result = _is_any_repository_locked(pool_args)
self.assertEqual(result, {"repo1"})
@patch("os.path.exists")
@patch("os.path.isdir")
@patch("os.listdir")
def test_repository_without_git_dir(self, mock_listdir, mock_isdir, mock_exists):
pool_args = [
("/fake_path", None, "repo1"),
]
mock_exists.return_value = False
mock_isdir.return_value = False
mock_listdir.return_value = []
result = _is_any_repository_locked(pool_args)
self.assertEqual(result, set())
@patch("os.path.exists")
@patch("os.path.isdir")
@patch("os.listdir")
def test_repository_with_git_file(self, mock_listdir, mock_isdir, mock_exists):
pool_args = [
("/fake_path", None, "repo1"),
]
mock_exists.return_value = True
mock_isdir.return_value = False
mock_listdir.return_value = []
result = _is_any_repository_locked(pool_args)
self.assertEqual(result, set())
@patch("os.path.exists")
@patch("os.path.isdir")
@patch("os.listdir")
def test_repository_with_multiple_lock_files(self, mock_listdir, mock_isdir, mock_exists):
pool_args = [
("/fake_path", None, "repo1"),
]
mock_exists.return_value = True
mock_isdir.return_value = True
mock_listdir.return_value = ["index.lock", "merge.lock", "HEAD"]
result = _is_any_repository_locked(pool_args)
self.assertEqual(result, {"repo1"})
@patch("os.path.exists")
@patch("os.path.isdir")
@patch("os.listdir")
def test_repository_with_no_lock_files(self, mock_listdir, mock_isdir, mock_exists):
pool_args = [
("/fake_path", None, "repo1"),
]
mock_exists.return_value = True
mock_isdir.return_value = True
mock_listdir.return_value = ["HEAD", "config", "logs"]
result = _is_any_repository_locked(pool_args)
self.assertEqual(result, set())

View File

@@ -16,6 +16,7 @@ import re
import sys
import traceback
from multiprocessing import Lock, Pool, cpu_count, freeze_support
from typing import Set, List, Any
from build_swift.build_swift.constants import SWIFT_SOURCE_ROOT
@@ -67,8 +68,11 @@ def check_parallel_results(results, op):
if r is not None:
if fail_count == 0:
print("======%s FAILURES======" % op)
print("%s failed (ret=%d): %s" % (r.repo_path, r.ret, r))
fail_count += 1
if isinstance(r, str):
print(r)
continue
print("%s failed (ret=%d): %s" % (r.repo_path, r.ret, r))
if r.stderr:
print(r.stderr)
return fail_count
@@ -329,6 +333,30 @@ def get_scheme_map(config, scheme_name):
return None
def _is_any_repository_locked(pool_args: List[Any]) -> Set[str]:
"""Returns the set of locked repositories.
A repository is considered to be locked if its .git directory contains a
file ending in ".lock".
Args:
pool_args (List[Any]): List of arguments passed to the
`update_single_repository` function.
Returns:
Set[str]: The names of the locked repositories if any.
"""
repos = [(x[0], x[2]) for x in pool_args]
locked_repositories = set()
for source_root, repo_name in repos:
dot_git_path = os.path.join(source_root, repo_name, ".git")
if not os.path.exists(dot_git_path) or not os.path.isdir(dot_git_path):
continue
for file in os.listdir(dot_git_path):
if file.endswith(".lock"):
locked_repositories.add(repo_name)
return locked_repositories
def update_all_repositories(args, config, scheme_name, scheme_map, cross_repos_pr):
pool_args = []
@@ -363,6 +391,12 @@ def update_all_repositories(args, config, scheme_name, scheme_map, cross_repos_p
cross_repos_pr]
pool_args.append(my_args)
locked_repositories: set[str] = _is_any_repository_locked(pool_args)
if len(locked_repositories) > 0:
return [
f"'{repo_name}' is locked by git. Cannot update it."
for repo_name in locked_repositories
]
return run_parallel(update_single_repository, pool_args, args.n_processes)