Skip to content

Commit a687605

Browse files
Merge pull request #85056 from charles-zablit/charles-zablit/update-checkout/refactor-shell-invocations
[update-checkout] refactor shell invocations
2 parents 45fa670 + f094494 commit a687605

File tree

5 files changed

+106
-109
lines changed

5 files changed

+106
-109
lines changed

utils/swift_build_support/swift_build_support/shell.py

Lines changed: 0 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -209,50 +209,3 @@ def remove(path, dry_run=None, echo=True):
209209
if dry_run:
210210
return
211211
os.remove(path)
212-
213-
214-
# Initialized later
215-
lock = None
216-
217-
218-
def run(*args, **kwargs):
219-
repo_path = kwargs.pop('repo_path', os.getcwd())
220-
echo_output = kwargs.pop('echo', False)
221-
dry_run = kwargs.pop('dry_run', False)
222-
env = kwargs.get('env', None)
223-
prefix = kwargs.pop('prefix', '')
224-
if dry_run:
225-
_echo_command(dry_run, *args, env=env, prompt="{0}+ ".format(prefix))
226-
return (None, 0, args)
227-
228-
my_pipe = subprocess.Popen(
229-
*args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
230-
universal_newlines=True,
231-
encoding='utf-8',
232-
**kwargs)
233-
(output, _) = my_pipe.communicate()
234-
output = output.encode(encoding='ascii', errors='replace')
235-
ret = my_pipe.wait()
236-
237-
if lock:
238-
lock.acquire()
239-
if echo_output:
240-
sys.stdout.flush()
241-
sys.stderr.flush()
242-
_echo_command(dry_run, *args, env=env, prompt="{0}+ ".format(prefix))
243-
if output:
244-
for line in output.splitlines():
245-
print("{0}{1}".format(prefix, line.decode('utf-8', errors='replace')))
246-
sys.stdout.flush()
247-
sys.stderr.flush()
248-
if lock:
249-
lock.release()
250-
251-
if ret != 0:
252-
eout = Exception(f"[{repo_path}] '{args}' failed with '{output.decode('utf-8')}'")
253-
eout.ret = ret
254-
eout.arguments = args
255-
eout.repo_path = repo_path
256-
eout.stderr = output
257-
raise eout
258-
return (output, 0, args)

utils/update_checkout/tests/test_clone.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,7 @@ def test_clone_with_additional_scheme(self):
4141
'--verbose'])
4242

4343
# Test that we're actually checking out the 'extra' scheme based on the output
44-
self.assertIn(
45-
f"git -C {os.path.join(self.source_root, 'repo1')} checkout refs/heads/main",
46-
output.decode("utf-8"),
47-
)
44+
self.assertIn("git checkout refs/heads/main", output.decode("utf-8"))
4845

4946
def test_manager_not_called_on_long_socket(self):
5047
fake_tmpdir = '/tmp/very/' + '/long' * 20 + '/tmp'
Lines changed: 84 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,90 @@
1-
from typing import List, Any, Optional, Union
2-
3-
from swift_build_support.swift_build_support import shell
1+
import shlex
2+
import subprocess
3+
import sys
4+
from typing import List, Any, Optional, Dict
45

56

67
class Git:
78
@staticmethod
8-
def run(repo_path: Optional[str], args: List[str], **kwargs):
9-
cmd = ["git"]
10-
if repo_path is not None:
11-
cmd += ["-C", repo_path]
12-
kwargs["repo_path"] = repo_path
13-
# FIXME: The way we are passing args below is broken. shell.run takes
14-
# *args as list arguments and sometimes treats them as one positional
15-
# argument or as a list of arguments.
16-
return shell.run(cmd+args, **kwargs)
17-
18-
@staticmethod
19-
def capture(
9+
def run(
2010
repo_path: str,
2111
args: List[str],
22-
stderr=None,
23-
env=None,
24-
dry_run=None,
25-
echo=True,
26-
optional=False,
27-
allow_non_zero_exit=False,
28-
) -> Union[str, Any, None]:
29-
return shell.capture(
30-
["git", "-C", repo_path] + args,
31-
stderr=stderr,
32-
env=env,
33-
dry_run=dry_run,
34-
echo=echo,
35-
optional=optional,
36-
allow_non_zero_exit=allow_non_zero_exit,
37-
)
12+
echo: bool = False,
13+
env: Optional[Dict[str, Any]] = None,
14+
prefix: str = "",
15+
allow_non_zero_exit: bool = False,
16+
fatal: bool = False,
17+
**kwargs,
18+
):
19+
command = Git._build_command(args)
20+
21+
try:
22+
result = subprocess.run(
23+
command,
24+
stdout=subprocess.PIPE,
25+
stderr=subprocess.STDOUT,
26+
universal_newlines=True,
27+
encoding="utf-8",
28+
env=env,
29+
cwd=repo_path,
30+
**kwargs,
31+
)
32+
output = result.stdout
33+
if echo:
34+
Git._echo_command(command, output, env, prefix)
35+
if not allow_non_zero_exit:
36+
result.check_returncode()
37+
except subprocess.CalledProcessError as e:
38+
if fatal:
39+
sys.exit(
40+
f"command `{command}` terminated with a non-zero exit "
41+
f"status {str(e.returncode)}, aborting")
42+
eout = Exception(
43+
f"[{repo_path}] '{Git._quote_command(command)}' failed with '{output}'"
44+
)
45+
eout.ret = e.returncode
46+
eout.arguments = command
47+
eout.repo_path = repo_path
48+
eout.stderr = output
49+
raise eout
50+
except OSError as e:
51+
if fatal:
52+
sys.exit(
53+
f"could not execute '{Git._quote_command(command)}': "
54+
f"{e.strerror}"
55+
)
56+
return (output.strip(), result.returncode, command)
57+
58+
@staticmethod
59+
def _echo_command(
60+
command: List[str],
61+
output: Optional[str] = None,
62+
env: Optional[Dict[str, Any]] = None,
63+
prefix: str = "",
64+
):
65+
sys.stdout.flush()
66+
sys.stderr.flush()
67+
command_str = []
68+
if env is not None:
69+
command_str += ["env"] + [
70+
Git._quote(f"{k}={v}") for (k, v) in sorted(env.items())
71+
]
72+
command_str.append(Git._quote_command(command))
73+
print(f"{prefix}+ {' '.join(command_str)}", file=sys.stderr)
74+
if output:
75+
for line in output.splitlines():
76+
print(prefix+line)
77+
sys.stdout.flush()
78+
sys.stderr.flush()
79+
80+
@staticmethod
81+
def _build_command(args: List[str]) -> List[str]:
82+
return ["git"] + args
83+
84+
@staticmethod
85+
def _quote(arg: Any) -> str:
86+
return shlex.quote(str(arg))
87+
88+
@staticmethod
89+
def _quote_command(command: Any) -> str:
90+
return " ".join(Git._quote(arg) for arg in command)

utils/update_checkout/update_checkout/parallel_runner.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def mark_task_as_done(self, task_name: str):
3131
self._done_task_counter += 1
3232
self._lock.release()
3333

34-
def status(self) -> Tuple[List[str], int]:
34+
def status(self) -> Tuple[str, int]:
3535
self._lock.acquire()
3636
running_tasks_str = ", ".join(self.running_tasks)
3737
done_tasks = self.done_task_counter

utils/update_checkout/update_checkout/update_checkout.py

Lines changed: 20 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,13 @@ def confirm_tag_in_repo(repo_path: str, tag: str, repo_name: str) -> Optional[st
4242
exist.
4343
"""
4444

45-
tag_exists = Git.capture(
46-
repo_path, ["ls-remote", "--tags", "origin", tag], echo=False
45+
tag_exists, _, _ = Git.run(
46+
repo_path, ["ls-remote", "--tags", "origin", tag], fatal=True
4747
)
4848
if not tag_exists:
4949
print("Tag '" + tag + "' does not exist for '" +
5050
repo_name + "', just updating regularly")
51-
tag = None
51+
return None
5252
return tag
5353

5454

@@ -61,7 +61,7 @@ def find_rev_by_timestamp(repo_path: str, timestamp: str, repo_name: str, refspe
6161
args = ["log", "-1", "--format=%H", "--first-parent", "--before=" + timestamp]
6262
if refspec_exists:
6363
args.append(refspec)
64-
rev = Git.capture(repo_path, args).strip()
64+
rev, _, _ = Git.run(repo_path, args, fatal=True)
6565
if rev:
6666
return rev
6767
else:
@@ -98,7 +98,7 @@ def get_branch_for_repo(repo_path, config, repo_name, scheme_name, scheme_map,
9898
pr_id = cross_repos_pr[remote_repo_id]
9999
repo_branch = "ci_pr_{0}".format(pr_id)
100100
Git.run(repo_path, ["checkout", scheme_branch], echo=True)
101-
Git.capture(repo_path, ["branch", "-D", repo_branch], echo=True, allow_non_zero_exit=True)
101+
Git.run(repo_path, ["branch", "-D", repo_branch], echo=True, allow_non_zero_exit=True, fatal=True)
102102
Git.run(repo_path, ["fetch", "origin", "pull/{0}/merge:{1}".format(pr_id, repo_branch), "--tags"], echo=True)
103103
return repo_branch, cross_repo
104104

@@ -172,7 +172,7 @@ def run_for_repo_and_each_submodule_rec(args: List[str]):
172172
pass
173173

174174
if checkout_target:
175-
Git.run(repo_path, ['status', '--porcelain', '-uno'], echo=False)
175+
Git.run(repo_path, ['status', '--porcelain', '-uno'])
176176

177177
# Some of the projects switch branches/tags when they
178178
# are updated. Local checkout might not have that tag/branch
@@ -199,8 +199,7 @@ def run_for_repo_and_each_submodule_rec(args: List[str]):
199199
)
200200
except Exception:
201201
try:
202-
result = Git.run(repo_path, ["rev-parse", checkout_target])
203-
revision = result[0].strip()
202+
revision, _, _ = Git.run(repo_path, ["rev-parse", checkout_target])
204203
Git.run(
205204
repo_path, ["checkout", revision], echo=verbose, prefix=prefix
206205
)
@@ -233,7 +232,7 @@ def run_for_repo_and_each_submodule_rec(args: List[str]):
233232
# This git command returns error code 1 if HEAD is detached.
234233
# Otherwise there was some other error, and we need to handle
235234
# it like other command errors.
236-
Git.run(repo_path, ["symbolic-ref", "-q", "HEAD"], echo=False)
235+
Git.run(repo_path, ["symbolic-ref", "-q", "HEAD"])
237236
except Exception as e:
238237
if e.ret == 1:
239238
detached_head = True
@@ -286,9 +285,8 @@ def get_timestamp_to_match(match_timestamp, source_root):
286285
if not match_timestamp:
287286
return None
288287
swift_repo_path = os.path.join(source_root, "swift")
289-
return Git.capture(
290-
swift_repo_path, ["log", "-1", "--format=%cI"], echo=False
291-
).strip()
288+
output, _, _ = Git.run(swift_repo_path, ["log", "-1", "--format=%cI"], fatal=True)
289+
return output
292290

293291

294292
def get_scheme_map(config, scheme_name):
@@ -410,35 +408,31 @@ def obtain_additional_swift_sources(pool_args: AdditionalSwiftSourcesArguments):
410408
print("Cloning '" + pool_args.repo_name + "'")
411409

412410
if pool_args.skip_history:
413-
Git.run(None, ['clone', '--recursive', '--depth', '1',
411+
Git.run(args.source_root, ['clone', '--recursive', '--depth', '1',
414412
'--branch', repo_branch, remote, repo_name] +
415413
(['--no-tags'] if skip_tags else []),
416-
cwd=args.source_root,
417414
env=env,
418415
echo=verbose)
419416
elif pool_args.use_submodules:
420-
Git.run(None, ['submodule', 'add', remote, repo_name] +
417+
Git.run(args.source_root, ['submodule', 'add', remote, repo_name] +
421418
(['--no-tags'] if skip_tags else []),
422-
cwd=args.source_root,
423419
env=env,
424420
echo=verbose)
425421
else:
426-
Git.run(None, ['clone', '--recursive', remote, repo_name] +
422+
Git.run(args.source_root, ['clone', '--recursive', remote, repo_name] +
427423
(['--no-tags'] if skip_tags else []),
428-
cwd=args.source_root,
429424
env=env,
430425
echo=verbose)
431426

432427
repo_path = os.path.join(args.source_root, repo_name)
433428
if pool_args.scheme_name:
434429
src_path = os.path.join(repo_path, ".git")
435430
Git.run(
436-
None,
431+
args.source_root,
437432
["--git-dir", src_path, "--work-tree", repo_path, "checkout", repo_branch],
438433
env=env,
439-
echo=False,
440434
)
441-
Git.run(repo_path, ["submodule", "update", "--recursive"], env=env, echo=False)
435+
Git.run(repo_path, ["submodule", "update", "--recursive"], env=env)
442436

443437

444438
def obtain_all_additional_swift_sources(args, config, with_ssh, scheme_name,
@@ -455,8 +449,7 @@ def obtain_all_additional_swift_sources(args, config, with_ssh, scheme_name,
455449

456450
if use_submodules:
457451
repo_exists = False
458-
submodules_status = Git.capture(repo_path, ['submodule', 'status'],
459-
echo=False)
452+
submodules_status, _, _ = Git.run(repo_path, ['submodule', 'status'], fatal=True)
460453
if submodules_status:
461454
for line in submodules_status.splitlines():
462455
if line[0].endswith(repo_name):
@@ -557,7 +550,7 @@ def repo_hashes(args, config):
557550
for repo_name, _ in sorted(config['repos'].items(), key=lambda x: x[0]):
558551
repo_path = os.path.join(args.source_root, repo_name)
559552
if os.path.exists(repo_path):
560-
h = Git.capture(repo_path, ["rev-parse", "HEAD"], echo=False).strip()
553+
h, _, _ = Git.run(repo_path, ["rev-parse", "HEAD"], fatal=True)
561554
else:
562555
h = "skip"
563556
repos[repo_name] = str(h)
@@ -633,11 +626,12 @@ def validate_config(config: Dict[str, Any]):
633626

634627

635628
def full_target_name(repo_path, repository, target):
636-
tag = Git.capture(repo_path, ["tag", "-l", target], echo=False).strip()
629+
tag, _, _ = Git.run(repo_path, ["tag", "-l", target], fatal=True)
637630
if tag == target:
638631
return tag
639632

640-
branch = Git.capture(repo_path, ["branch", "--list", target], echo=False).strip().replace("* ", "")
633+
branch, _, _ = Git.run(repo_path, ["branch", "--list", target], fatal=True)
634+
branch = branch.replace("* ", "")
641635
if branch == target:
642636
name = "%s/%s" % (repository, target)
643637
return name

0 commit comments

Comments
 (0)