Skip to content

Commit 35c6b99

Browse files
Merge pull request swiftlang#84156 from charles-zablit/charles-zablit/update-checkout/add-locked-repository-check-to-6.2
🍒 [update-checkout] add a check for locked repositories
2 parents 94b6d59 + f2c3d97 commit 35c6b99

File tree

3 files changed

+126
-2
lines changed

3 files changed

+126
-2
lines changed

utils/update_checkout/tests/scheme_mock.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,11 @@ def setup_mock_remote(base_dir, base_config):
145145

146146
BASEDIR_ENV_VAR = 'UPDATECHECKOUT_TEST_WORKSPACE_DIR'
147147
CURRENT_FILE_DIR = os.path.dirname(os.path.abspath(__file__))
148+
UPDATE_CHECKOUT_EXECUTABLE = 'update-checkout.cmd' if os.name == 'nt' else 'update-checkout'
148149
UPDATE_CHECKOUT_PATH = os.path.abspath(os.path.join(CURRENT_FILE_DIR,
149150
os.path.pardir,
150151
os.path.pardir,
151-
'update-checkout'))
152+
UPDATE_CHECKOUT_EXECUTABLE))
152153

153154

154155
class SchemeMockTestCase(unittest.TestCase):
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import unittest
2+
from unittest.mock import patch
3+
4+
from update_checkout.update_checkout import _is_any_repository_locked
5+
6+
class TestIsAnyRepositoryLocked(unittest.TestCase):
7+
@patch("os.path.exists")
8+
@patch("os.path.isdir")
9+
@patch("os.listdir")
10+
def test_repository_with_lock_file(self, mock_listdir, mock_isdir, mock_exists):
11+
pool_args = [
12+
("/fake_path", None, "repo1"),
13+
("/fake_path", None, "repo2"),
14+
]
15+
16+
def listdir_side_effect(path):
17+
if "repo1" in path:
18+
return ["index.lock", "config"]
19+
elif "repo2" in path:
20+
return ["HEAD", "config"]
21+
return []
22+
23+
mock_exists.return_value = True
24+
mock_isdir.return_value = True
25+
mock_listdir.side_effect = listdir_side_effect
26+
27+
result = _is_any_repository_locked(pool_args)
28+
self.assertEqual(result, {"repo1"})
29+
30+
@patch("os.path.exists")
31+
@patch("os.path.isdir")
32+
@patch("os.listdir")
33+
def test_repository_without_git_dir(self, mock_listdir, mock_isdir, mock_exists):
34+
pool_args = [
35+
("/fake_path", None, "repo1"),
36+
]
37+
38+
mock_exists.return_value = False
39+
mock_isdir.return_value = False
40+
mock_listdir.return_value = []
41+
42+
result = _is_any_repository_locked(pool_args)
43+
self.assertEqual(result, set())
44+
45+
@patch("os.path.exists")
46+
@patch("os.path.isdir")
47+
@patch("os.listdir")
48+
def test_repository_with_git_file(self, mock_listdir, mock_isdir, mock_exists):
49+
pool_args = [
50+
("/fake_path", None, "repo1"),
51+
]
52+
53+
mock_exists.return_value = True
54+
mock_isdir.return_value = False
55+
mock_listdir.return_value = []
56+
57+
result = _is_any_repository_locked(pool_args)
58+
self.assertEqual(result, set())
59+
60+
@patch("os.path.exists")
61+
@patch("os.path.isdir")
62+
@patch("os.listdir")
63+
def test_repository_with_multiple_lock_files(self, mock_listdir, mock_isdir, mock_exists):
64+
pool_args = [
65+
("/fake_path", None, "repo1"),
66+
]
67+
68+
mock_exists.return_value = True
69+
mock_isdir.return_value = True
70+
mock_listdir.return_value = ["index.lock", "merge.lock", "HEAD"]
71+
72+
result = _is_any_repository_locked(pool_args)
73+
self.assertEqual(result, {"repo1"})
74+
75+
@patch("os.path.exists")
76+
@patch("os.path.isdir")
77+
@patch("os.listdir")
78+
def test_repository_with_no_lock_files(self, mock_listdir, mock_isdir, mock_exists):
79+
pool_args = [
80+
("/fake_path", None, "repo1"),
81+
]
82+
83+
mock_exists.return_value = True
84+
mock_isdir.return_value = True
85+
mock_listdir.return_value = ["HEAD", "config", "logs"]
86+
87+
result = _is_any_repository_locked(pool_args)
88+
self.assertEqual(result, set())
89+

utils/update_checkout/update_checkout/update_checkout.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import sys
1717
import traceback
1818
from multiprocessing import Lock, Pool, cpu_count, freeze_support
19+
from typing import Set, List, Any
1920

2021
from build_swift.build_swift.constants import SWIFT_SOURCE_ROOT
2122

@@ -67,8 +68,11 @@ def check_parallel_results(results, op):
6768
if r is not None:
6869
if fail_count == 0:
6970
print("======%s FAILURES======" % op)
70-
print("%s failed (ret=%d): %s" % (r.repo_path, r.ret, r))
7171
fail_count += 1
72+
if isinstance(r, str):
73+
print(r)
74+
continue
75+
print("%s failed (ret=%d): %s" % (r.repo_path, r.ret, r))
7276
if r.stderr:
7377
print(r.stderr)
7478
return fail_count
@@ -329,6 +333,30 @@ def get_scheme_map(config, scheme_name):
329333

330334
return None
331335

336+
def _is_any_repository_locked(pool_args: List[Any]) -> Set[str]:
337+
"""Returns the set of locked repositories.
338+
339+
A repository is considered to be locked if its .git directory contains a
340+
file ending in ".lock".
341+
342+
Args:
343+
pool_args (List[Any]): List of arguments passed to the
344+
`update_single_repository` function.
345+
346+
Returns:
347+
Set[str]: The names of the locked repositories if any.
348+
"""
349+
350+
repos = [(x[0], x[2]) for x in pool_args]
351+
locked_repositories = set()
352+
for source_root, repo_name in repos:
353+
dot_git_path = os.path.join(source_root, repo_name, ".git")
354+
if not os.path.exists(dot_git_path) or not os.path.isdir(dot_git_path):
355+
continue
356+
for file in os.listdir(dot_git_path):
357+
if file.endswith(".lock"):
358+
locked_repositories.add(repo_name)
359+
return locked_repositories
332360

333361
def update_all_repositories(args, config, scheme_name, scheme_map, cross_repos_pr):
334362
pool_args = []
@@ -363,6 +391,12 @@ def update_all_repositories(args, config, scheme_name, scheme_map, cross_repos_p
363391
cross_repos_pr]
364392
pool_args.append(my_args)
365393

394+
locked_repositories: set[str] = _is_any_repository_locked(pool_args)
395+
if len(locked_repositories) > 0:
396+
return [
397+
f"'{repo_name}' is locked by git. Cannot update it."
398+
for repo_name in locked_repositories
399+
]
366400
return run_parallel(update_single_repository, pool_args, args.n_processes)
367401

368402

0 commit comments

Comments
 (0)