Skip to content

Commit 1122439

Browse files
[update-checkout] add a check for locked repositories
1 parent 95dc7df commit 1122439

File tree

3 files changed

+126
-3
lines changed

3 files changed

+126
-3
lines changed

utils/update_checkout/tests/scheme_mock.py

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

170170
BASEDIR_ENV_VAR = 'UPDATECHECKOUT_TEST_WORKSPACE_DIR'
171171
CURRENT_FILE_DIR = os.path.dirname(os.path.abspath(__file__))
172+
UPDATE_CHECKOUT_EXECUTABLE = 'update-checkout.cmd' if os.name == 'nt' else 'update-checkout'
172173
UPDATE_CHECKOUT_PATH = os.path.abspath(os.path.join(CURRENT_FILE_DIR,
173174
os.path.pardir,
174175
os.path.pardir,
175-
'update-checkout'))
176+
UPDATE_CHECKOUT_EXECUTABLE))
176177

177178

178179
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 & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import sys
1717
import traceback
1818
from multiprocessing import Lock, Pool, cpu_count, freeze_support
19-
from typing import Optional
19+
from typing import Optional, Set, List, Any
2020

2121
from build_swift.build_swift.constants import SWIFT_SOURCE_ROOT
2222

@@ -68,8 +68,11 @@ def check_parallel_results(results, op):
6868
if r is not None:
6969
if fail_count == 0:
7070
print("======%s FAILURES======" % op)
71-
print("%s failed (ret=%d): %s" % (r.repo_path, r.ret, r))
7271
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))
7376
if r.stderr:
7477
print(r.stderr)
7578
return fail_count
@@ -337,6 +340,30 @@ def get_scheme_map(config, scheme_name):
337340

338341
return None
339342

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

341368
def update_all_repositories(args, config, scheme_name, scheme_map, cross_repos_pr):
342369
pool_args = []
@@ -371,6 +398,12 @@ def update_all_repositories(args, config, scheme_name, scheme_map, cross_repos_p
371398
cross_repos_pr]
372399
pool_args.append(my_args)
373400

401+
locked_repositories: set[str] = _is_any_repository_locked(pool_args)
402+
if len(locked_repositories) > 0:
403+
return [
404+
f"'{repo_name}' is locked by git. Cannot update it."
405+
for repo_name in locked_repositories
406+
]
374407
return run_parallel(update_single_repository, pool_args, args.n_processes)
375408

376409

0 commit comments

Comments
 (0)