summaryrefslogtreecommitdiffstats
path: root/subcmds
diff options
context:
space:
mode:
authorJason Chang <jasonnc@google.com>2023-08-08 14:12:53 -0700
committerLUCI <gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com>2023-08-10 23:46:31 +0000
commit1a3612fe6d347e458a53d7a9e920a91ea502e6ba (patch)
tree02b1a61f1d97e32201ea5fa309bf1f1b6050e929 /subcmds
parentf0aeb220def22edfac9838288ad251f86da782c1 (diff)
downloadgit-repo-1a3612fe6d347e458a53d7a9e920a91ea502e6ba.tar.gz
Raise RepoExitError in place of sys.exit
Bug: b/293344017 Change-Id: Icae4932b00e4068cba502a5ab4a0274fd7854d9d Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/382214 Reviewed-by: Gavin Mak <gavinmak@google.com> Tested-by: Jason Chang <jasonnc@google.com> Reviewed-by: Aravind Vasudevan <aravindvasudev@google.com> Commit-Queue: Jason Chang <jasonnc@google.com>
Diffstat (limited to 'subcmds')
-rw-r--r--subcmds/checkout.py50
-rw-r--r--subcmds/cherry_pick.py76
-rw-r--r--subcmds/download.py23
-rw-r--r--subcmds/grep.py84
-rw-r--r--subcmds/help.py7
-rw-r--r--subcmds/selfupdate.py10
-rw-r--r--subcmds/start.py38
7 files changed, 201 insertions, 87 deletions
diff --git a/subcmds/checkout.py b/subcmds/checkout.py
index 6448518f..033fd349 100644
--- a/subcmds/checkout.py
+++ b/subcmds/checkout.py
@@ -15,8 +15,26 @@
15import functools 15import functools
16import sys 16import sys
17 17
18from typing import NamedTuple
18from command import Command, DEFAULT_LOCAL_JOBS 19from command import Command, DEFAULT_LOCAL_JOBS
19from progress import Progress 20from progress import Progress
21from project import Project
22from error import GitError, RepoExitError
23
24
25class CheckoutBranchResult(NamedTuple):
26 # Whether the Project is on the branch (i.e. branch exists and no errors)
27 result: bool
28 project: Project
29 error: Exception
30
31
32class CheckoutCommandError(RepoExitError):
33 """Exception thrown when checkout command fails."""
34
35
36class MissingBranchError(RepoExitError):
37 """Exception thrown when no project has specified branch."""
20 38
21 39
22class Checkout(Command): 40class Checkout(Command):
@@ -41,23 +59,30 @@ The command is equivalent to:
41 59
42 def _ExecuteOne(self, nb, project): 60 def _ExecuteOne(self, nb, project):
43 """Checkout one project.""" 61 """Checkout one project."""
44 return (project.CheckoutBranch(nb), project) 62 error = None
63 result = None
64 try:
65 result = project.CheckoutBranch(nb)
66 except GitError as e:
67 error = e
68 return CheckoutBranchResult(result, project, error)
45 69
46 def Execute(self, opt, args): 70 def Execute(self, opt, args):
47 nb = args[0] 71 nb = args[0]
48 err = [] 72 err = []
73 err_projects = []
49 success = [] 74 success = []
50 all_projects = self.GetProjects( 75 all_projects = self.GetProjects(
51 args[1:], all_manifests=not opt.this_manifest_only 76 args[1:], all_manifests=not opt.this_manifest_only
52 ) 77 )
53 78
54 def _ProcessResults(_pool, pm, results): 79 def _ProcessResults(_pool, pm, results):
55 for status, project in results: 80 for result in results:
56 if status is not None: 81 if result.error is not None:
57 if status: 82 err.append(result.error)
58 success.append(project) 83 err_projects.append(result.project)
59 else: 84 elif result.result:
60 err.append(project) 85 success.append(result.project)
61 pm.update(msg="") 86 pm.update(msg="")
62 87
63 self.ExecuteInParallel( 88 self.ExecuteInParallel(
@@ -70,13 +95,14 @@ The command is equivalent to:
70 ), 95 ),
71 ) 96 )
72 97
73 if err: 98 if err_projects:
74 for p in err: 99 for p in err_projects:
75 print( 100 print(
76 "error: %s/: cannot checkout %s" % (p.relpath, nb), 101 "error: %s/: cannot checkout %s" % (p.relpath, nb),
77 file=sys.stderr, 102 file=sys.stderr,
78 ) 103 )
79 sys.exit(1) 104 raise CheckoutCommandError(aggregate_errors=err)
80 elif not success: 105 elif not success:
81 print("error: no project has branch %s" % nb, file=sys.stderr) 106 msg = f"error: no project has branch {nb}"
82 sys.exit(1) 107 print(msg, file=sys.stderr)
108 raise MissingBranchError(msg)
diff --git a/subcmds/cherry_pick.py b/subcmds/cherry_pick.py
index 4cfb8c88..7a4dd09e 100644
--- a/subcmds/cherry_pick.py
+++ b/subcmds/cherry_pick.py
@@ -16,6 +16,7 @@ import re
16import sys 16import sys
17from command import Command 17from command import Command
18from git_command import GitCommand 18from git_command import GitCommand
19from error import GitError
19 20
20CHANGE_ID_RE = re.compile(r"^\s*Change-Id: I([0-9a-f]{40})\s*$") 21CHANGE_ID_RE = re.compile(r"^\s*Change-Id: I([0-9a-f]{40})\s*$")
21 22
@@ -44,18 +45,31 @@ change id will be added.
44 ["rev-parse", "--verify", reference], 45 ["rev-parse", "--verify", reference],
45 capture_stdout=True, 46 capture_stdout=True,
46 capture_stderr=True, 47 capture_stderr=True,
48 verify_command=True,
47 ) 49 )
48 if p.Wait() != 0: 50 try:
51 p.Wait()
52 except GitError:
49 print(p.stderr, file=sys.stderr) 53 print(p.stderr, file=sys.stderr)
50 sys.exit(1) 54 raise
55
51 sha1 = p.stdout.strip() 56 sha1 = p.stdout.strip()
52 57
53 p = GitCommand(None, ["cat-file", "commit", sha1], capture_stdout=True) 58 p = GitCommand(
54 if p.Wait() != 0: 59 None,
60 ["cat-file", "commit", sha1],
61 capture_stdout=True,
62 verify_command=True,
63 )
64
65 try:
66 p.Wait()
67 except GitError:
55 print( 68 print(
56 "error: Failed to retrieve old commit message", file=sys.stderr 69 "error: Failed to retrieve old commit message", file=sys.stderr
57 ) 70 )
58 sys.exit(1) 71 raise
72
59 old_msg = self._StripHeader(p.stdout) 73 old_msg = self._StripHeader(p.stdout)
60 74
61 p = GitCommand( 75 p = GitCommand(
@@ -63,37 +77,47 @@ change id will be added.
63 ["cherry-pick", sha1], 77 ["cherry-pick", sha1],
64 capture_stdout=True, 78 capture_stdout=True,
65 capture_stderr=True, 79 capture_stderr=True,
80 verify_command=True,
66 ) 81 )
67 status = p.Wait() 82
83 try:
84 p.Wait()
85 except GitError as e:
86 print(str(e))
87 print(
88 "NOTE: When committing (please see above) and editing the "
89 "commit message, please remove the old Change-Id-line and "
90 "add:"
91 )
92 print(self._GetReference(sha1), file=sys.stderr)
93 print(file=sys.stderr)
94 raise
68 95
69 if p.stdout: 96 if p.stdout:
70 print(p.stdout.strip(), file=sys.stdout) 97 print(p.stdout.strip(), file=sys.stdout)
71 if p.stderr: 98 if p.stderr:
72 print(p.stderr.strip(), file=sys.stderr) 99 print(p.stderr.strip(), file=sys.stderr)
73 100
74 if status == 0: 101 # The cherry-pick was applied correctly. We just need to edit
75 # The cherry-pick was applied correctly. We just need to edit the 102 # the commit message.
76 # commit message. 103 new_msg = self._Reformat(old_msg, sha1)
77 new_msg = self._Reformat(old_msg, sha1)
78
79 p = GitCommand(
80 None,
81 ["commit", "--amend", "-F", "-"],
82 input=new_msg,
83 capture_stdout=True,
84 capture_stderr=True,
85 )
86 if p.Wait() != 0:
87 print("error: Failed to update commit message", file=sys.stderr)
88 sys.exit(1)
89 104
90 else: 105 p = GitCommand(
106 None,
107 ["commit", "--amend", "-F", "-"],
108 input=new_msg,
109 capture_stdout=True,
110 capture_stderr=True,
111 verify_command=True,
112 )
113 try:
114 p.Wait()
115 except GitError:
91 print( 116 print(
92 "NOTE: When committing (please see above) and editing the " 117 "error: Failed to update commit message",
93 "commit message, please remove the old Change-Id-line and add:" 118 file=sys.stderr,
94 ) 119 )
95 print(self._GetReference(sha1), file=sys.stderr) 120 raise
96 print(file=sys.stderr)
97 121
98 def _IsChangeId(self, line): 122 def _IsChangeId(self, line):
99 return CHANGE_ID_RE.match(line) 123 return CHANGE_ID_RE.match(line)
diff --git a/subcmds/download.py b/subcmds/download.py
index 475c0bc2..18e555be 100644
--- a/subcmds/download.py
+++ b/subcmds/download.py
@@ -16,11 +16,15 @@ import re
16import sys 16import sys
17 17
18from command import Command 18from command import Command
19from error import GitError, NoSuchProjectError 19from error import GitError, NoSuchProjectError, RepoExitError
20 20
21CHANGE_RE = re.compile(r"^([1-9][0-9]*)(?:[/\.-]([1-9][0-9]*))?$") 21CHANGE_RE = re.compile(r"^([1-9][0-9]*)(?:[/\.-]([1-9][0-9]*))?$")
22 22
23 23
24class DownloadCommandError(RepoExitError):
25 """Error raised when download command fails."""
26
27
24class Download(Command): 28class Download(Command):
25 COMMON = True 29 COMMON = True
26 helpSummary = "Download and checkout a change" 30 helpSummary = "Download and checkout a change"
@@ -137,15 +141,16 @@ If no project is specified try to use current directory as a project.
137 ) 141 )
138 142
139 def Execute(self, opt, args): 143 def Execute(self, opt, args):
144 try:
145 self._ExecuteHelper(opt, args)
146 except Exception as e:
147 if isinstance(e, RepoExitError):
148 raise e
149 raise DownloadCommandError(aggregate_errors=[e])
150
151 def _ExecuteHelper(self, opt, args):
140 for project, change_id, ps_id in self._ParseChangeIds(opt, args): 152 for project, change_id, ps_id in self._ParseChangeIds(opt, args):
141 dl = project.DownloadPatchSet(change_id, ps_id) 153 dl = project.DownloadPatchSet(change_id, ps_id)
142 if not dl:
143 print(
144 "[%s] change %d/%d not found"
145 % (project.name, change_id, ps_id),
146 file=sys.stderr,
147 )
148 sys.exit(1)
149 154
150 if not opt.revert and not dl.commits: 155 if not opt.revert and not dl.commits:
151 print( 156 print(
@@ -201,4 +206,4 @@ If no project is specified try to use current directory as a project.
201 % (project.name, mode, dl.commit), 206 % (project.name, mode, dl.commit),
202 file=sys.stderr, 207 file=sys.stderr,
203 ) 208 )
204 sys.exit(1) 209 raise
diff --git a/subcmds/grep.py b/subcmds/grep.py
index 5cd33763..9ebd776c 100644
--- a/subcmds/grep.py
+++ b/subcmds/grep.py
@@ -17,8 +17,10 @@ import sys
17 17
18from color import Coloring 18from color import Coloring
19from command import DEFAULT_LOCAL_JOBS, PagedCommand 19from command import DEFAULT_LOCAL_JOBS, PagedCommand
20from error import GitError 20from error import GitError, InvalidArgumentsError, SilentRepoExitError
21from git_command import GitCommand 21from git_command import GitCommand
22from typing import NamedTuple
23from project import Project
22 24
23 25
24class GrepColoring(Coloring): 26class GrepColoring(Coloring):
@@ -28,6 +30,22 @@ class GrepColoring(Coloring):
28 self.fail = self.printer("fail", fg="red") 30 self.fail = self.printer("fail", fg="red")
29 31
30 32
33class ExecuteOneResult(NamedTuple):
34 """Result from an execute instance."""
35
36 project: Project
37 rc: int
38 stdout: str
39 stderr: str
40 error: GitError
41
42
43class GrepCommandError(SilentRepoExitError):
44 """Grep command failure. Since Grep command
45 output already outputs errors ensure that
46 aggregate errors exit silently."""
47
48
31class Grep(PagedCommand): 49class Grep(PagedCommand):
32 COMMON = True 50 COMMON = True
33 helpSummary = "Print lines matching a pattern" 51 helpSummary = "Print lines matching a pattern"
@@ -246,11 +264,18 @@ contain a line that matches both expressions:
246 bare=False, 264 bare=False,
247 capture_stdout=True, 265 capture_stdout=True,
248 capture_stderr=True, 266 capture_stderr=True,
267 verify_command=True,
249 ) 268 )
250 except GitError as e: 269 except GitError as e:
251 return (project, -1, None, str(e)) 270 return ExecuteOneResult(project, -1, None, str(e), e)
252 271
253 return (project, p.Wait(), p.stdout, p.stderr) 272 try:
273 error = None
274 rc = p.Wait()
275 except GitError as e:
276 rc = 1
277 error = e
278 return ExecuteOneResult(project, rc, p.stdout, p.stderr, error)
254 279
255 @staticmethod 280 @staticmethod
256 def _ProcessResults(full_name, have_rev, opt, _pool, out, results): 281 def _ProcessResults(full_name, have_rev, opt, _pool, out, results):
@@ -258,31 +283,40 @@ contain a line that matches both expressions:
258 bad_rev = False 283 bad_rev = False
259 have_match = False 284 have_match = False
260 _RelPath = lambda p: p.RelPath(local=opt.this_manifest_only) 285 _RelPath = lambda p: p.RelPath(local=opt.this_manifest_only)
286 errors = []
261 287
262 for project, rc, stdout, stderr in results: 288 for result in results:
263 if rc < 0: 289 if result.rc < 0:
264 git_failed = True 290 git_failed = True
265 out.project("--- project %s ---" % _RelPath(project)) 291 out.project("--- project %s ---" % _RelPath(result.project))
266 out.nl() 292 out.nl()
267 out.fail("%s", stderr) 293 out.fail("%s", result.stderr)
268 out.nl() 294 out.nl()
295 errors.append(result.error)
269 continue 296 continue
270 297
271 if rc: 298 if result.rc:
272 # no results 299 # no results
273 if stderr: 300 if result.stderr:
274 if have_rev and "fatal: ambiguous argument" in stderr: 301 if (
302 have_rev
303 and "fatal: ambiguous argument" in result.stderr
304 ):
275 bad_rev = True 305 bad_rev = True
276 else: 306 else:
277 out.project("--- project %s ---" % _RelPath(project)) 307 out.project(
308 "--- project %s ---" % _RelPath(result.project)
309 )
278 out.nl() 310 out.nl()
279 out.fail("%s", stderr.strip()) 311 out.fail("%s", result.stderr.strip())
280 out.nl() 312 out.nl()
313 if result.error is not None:
314 errors.append(result.error)
281 continue 315 continue
282 have_match = True 316 have_match = True
283 317
284 # We cut the last element, to avoid a blank line. 318 # We cut the last element, to avoid a blank line.
285 r = stdout.split("\n") 319 r = result.stdout.split("\n")
286 r = r[0:-1] 320 r = r[0:-1]
287 321
288 if have_rev and full_name: 322 if have_rev and full_name:
@@ -290,13 +324,13 @@ contain a line that matches both expressions:
290 rev, line = line.split(":", 1) 324 rev, line = line.split(":", 1)
291 out.write("%s", rev) 325 out.write("%s", rev)
292 out.write(":") 326 out.write(":")
293 out.project(_RelPath(project)) 327 out.project(_RelPath(result.project))
294 out.write("/") 328 out.write("/")
295 out.write("%s", line) 329 out.write("%s", line)
296 out.nl() 330 out.nl()
297 elif full_name: 331 elif full_name:
298 for line in r: 332 for line in r:
299 out.project(_RelPath(project)) 333 out.project(_RelPath(result.project))
300 out.write("/") 334 out.write("/")
301 out.write("%s", line) 335 out.write("%s", line)
302 out.nl() 336 out.nl()
@@ -304,7 +338,7 @@ contain a line that matches both expressions:
304 for line in r: 338 for line in r:
305 print(line) 339 print(line)
306 340
307 return (git_failed, bad_rev, have_match) 341 return (git_failed, bad_rev, have_match, errors)
308 342
309 def Execute(self, opt, args): 343 def Execute(self, opt, args):
310 out = GrepColoring(self.manifest.manifestProject.config) 344 out = GrepColoring(self.manifest.manifestProject.config)
@@ -333,16 +367,14 @@ contain a line that matches both expressions:
333 have_rev = False 367 have_rev = False
334 if opt.revision: 368 if opt.revision:
335 if "--cached" in cmd_argv: 369 if "--cached" in cmd_argv:
336 print( 370 msg = "fatal: cannot combine --cached and --revision"
337 "fatal: cannot combine --cached and --revision", 371 print(msg, file=sys.stderr)
338 file=sys.stderr, 372 raise InvalidArgumentsError(msg)
339 )
340 sys.exit(1)
341 have_rev = True 373 have_rev = True
342 cmd_argv.extend(opt.revision) 374 cmd_argv.extend(opt.revision)
343 cmd_argv.append("--") 375 cmd_argv.append("--")
344 376
345 git_failed, bad_rev, have_match = self.ExecuteInParallel( 377 git_failed, bad_rev, have_match, errors = self.ExecuteInParallel(
346 opt.jobs, 378 opt.jobs,
347 functools.partial(self._ExecuteOne, cmd_argv), 379 functools.partial(self._ExecuteOne, cmd_argv),
348 projects, 380 projects,
@@ -354,12 +386,12 @@ contain a line that matches both expressions:
354 ) 386 )
355 387
356 if git_failed: 388 if git_failed:
357 sys.exit(1) 389 raise GrepCommandError(
390 "error: git failures", aggregate_errors=errors
391 )
358 elif have_match: 392 elif have_match:
359 sys.exit(0) 393 sys.exit(0)
360 elif have_rev and bad_rev: 394 elif have_rev and bad_rev:
361 for r in opt.revision: 395 for r in opt.revision:
362 print("error: can't search revision %s" % r, file=sys.stderr) 396 print("error: can't search revision %s" % r, file=sys.stderr)
363 sys.exit(1) 397 raise GrepCommandError(aggregate_errors=errors)
364 else:
365 sys.exit(1)
diff --git a/subcmds/help.py b/subcmds/help.py
index 50a48047..593bf676 100644
--- a/subcmds/help.py
+++ b/subcmds/help.py
@@ -26,6 +26,11 @@ from command import (
26) 26)
27import gitc_utils 27import gitc_utils
28from wrapper import Wrapper 28from wrapper import Wrapper
29from error import RepoExitError
30
31
32class InvalidHelpCommand(RepoExitError):
33 """Invalid command passed into help."""
29 34
30 35
31class Help(PagedCommand, MirrorSafeCommand): 36class Help(PagedCommand, MirrorSafeCommand):
@@ -202,7 +207,7 @@ Displays detailed usage information about a command.
202 print( 207 print(
203 "repo: '%s' is not a repo command." % name, file=sys.stderr 208 "repo: '%s' is not a repo command." % name, file=sys.stderr
204 ) 209 )
205 sys.exit(1) 210 raise InvalidHelpCommand(name)
206 211
207 self._PrintCommandHelp(cmd) 212 self._PrintCommandHelp(cmd)
208 213
diff --git a/subcmds/selfupdate.py b/subcmds/selfupdate.py
index d5d0a838..00376b66 100644
--- a/subcmds/selfupdate.py
+++ b/subcmds/selfupdate.py
@@ -18,6 +18,11 @@ import sys
18from command import Command, MirrorSafeCommand 18from command import Command, MirrorSafeCommand
19from subcmds.sync import _PostRepoUpgrade 19from subcmds.sync import _PostRepoUpgrade
20from subcmds.sync import _PostRepoFetch 20from subcmds.sync import _PostRepoFetch
21from error import RepoExitError
22
23
24class SelfupdateError(RepoExitError):
25 """Exit error for failed selfupdate command."""
21 26
22 27
23class Selfupdate(Command, MirrorSafeCommand): 28class Selfupdate(Command, MirrorSafeCommand):
@@ -58,9 +63,10 @@ need to be performed by an end-user.
58 _PostRepoUpgrade(self.manifest) 63 _PostRepoUpgrade(self.manifest)
59 64
60 else: 65 else:
61 if not rp.Sync_NetworkHalf().success: 66 result = rp.Sync_NetworkHalf()
67 if result.error:
62 print("error: can't update repo", file=sys.stderr) 68 print("error: can't update repo", file=sys.stderr)
63 sys.exit(1) 69 raise SelfupdateError(aggregate_errors=[result.error])
64 70
65 rp.bare_git.gc("--auto") 71 rp.bare_git.gc("--auto")
66 _PostRepoFetch(rp, repo_verify=opt.repo_verify, verbose=True) 72 _PostRepoFetch(rp, repo_verify=opt.repo_verify, verbose=True)
diff --git a/subcmds/start.py b/subcmds/start.py
index f6355126..67ac7df9 100644
--- a/subcmds/start.py
+++ b/subcmds/start.py
@@ -21,7 +21,18 @@ from git_config import IsImmutable
21from git_command import git 21from git_command import git
22import gitc_utils 22import gitc_utils
23from progress import Progress 23from progress import Progress
24from project import SyncBuffer 24from project import SyncBuffer, Project
25from typing import NamedTuple
26from error import RepoExitError
27
28
29class ExecuteOneResult(NamedTuple):
30 project: Project
31 error: Exception
32
33
34class StartError(RepoExitError):
35 """Exit error for failed start command."""
25 36
26 37
27class Start(Command): 38class Start(Command):
@@ -73,6 +84,7 @@ revision specified in the manifest.
73 # a change, then we can't push back to it. Substitute with 84 # a change, then we can't push back to it. Substitute with
74 # dest_branch, if defined; or with manifest default revision instead. 85 # dest_branch, if defined; or with manifest default revision instead.
75 branch_merge = "" 86 branch_merge = ""
87 error = None
76 if IsImmutable(project.revisionExpr): 88 if IsImmutable(project.revisionExpr):
77 if project.dest_branch: 89 if project.dest_branch:
78 branch_merge = project.dest_branch 90 branch_merge = project.dest_branch
@@ -80,7 +92,7 @@ revision specified in the manifest.
80 branch_merge = self.manifest.default.revisionExpr 92 branch_merge = self.manifest.default.revisionExpr
81 93
82 try: 94 try:
83 ret = project.StartBranch( 95 project.StartBranch(
84 nb, branch_merge=branch_merge, revision=revision 96 nb, branch_merge=branch_merge, revision=revision
85 ) 97 )
86 except Exception as e: 98 except Exception as e:
@@ -88,11 +100,12 @@ revision specified in the manifest.
88 "error: unable to checkout %s: %s" % (project.name, e), 100 "error: unable to checkout %s: %s" % (project.name, e),
89 file=sys.stderr, 101 file=sys.stderr,
90 ) 102 )
91 ret = False 103 error = e
92 return (ret, project) 104 return ExecuteOneResult(project, error)
93 105
94 def Execute(self, opt, args): 106 def Execute(self, opt, args):
95 nb = args[0] 107 nb = args[0]
108 err_projects = []
96 err = [] 109 err = []
97 projects = [] 110 projects = []
98 if not opt.all: 111 if not opt.all:
@@ -146,9 +159,10 @@ revision specified in the manifest.
146 pm.end() 159 pm.end()
147 160
148 def _ProcessResults(_pool, pm, results): 161 def _ProcessResults(_pool, pm, results):
149 for result, project in results: 162 for result in results:
150 if not result: 163 if result.error:
151 err.append(project) 164 err_projects.append(result.project)
165 err.append(result.error)
152 pm.update(msg="") 166 pm.update(msg="")
153 167
154 self.ExecuteInParallel( 168 self.ExecuteInParallel(
@@ -161,13 +175,15 @@ revision specified in the manifest.
161 ), 175 ),
162 ) 176 )
163 177
164 if err: 178 if err_projects:
165 for p in err: 179 for p in err_projects:
166 print( 180 print(
167 "error: %s/: cannot start %s" 181 "error: %s/: cannot start %s"
168 % (p.RelPath(local=opt.this_manifest_only), nb), 182 % (p.RelPath(local=opt.this_manifest_only), nb),
169 file=sys.stderr, 183 file=sys.stderr,
170 ) 184 )
171 msg_fmt = "cannot start %d project(s)" 185 msg_fmt = "cannot start %d project(s)"
172 self.git_event_log.ErrorEvent(msg_fmt % (len(err)), msg_fmt) 186 self.git_event_log.ErrorEvent(
173 sys.exit(1) 187 msg_fmt % (len(err_projects)), msg_fmt
188 )
189 raise StartError(aggregate_errors=err)