summaryrefslogtreecommitdiffstats
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
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>
-rw-r--r--error.py8
-rwxr-xr-xmain.py23
-rw-r--r--project.py54
-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
10 files changed, 251 insertions, 122 deletions
diff --git a/error.py b/error.py
index ed4a90b7..cee977f9 100644
--- a/error.py
+++ b/error.py
@@ -56,6 +56,10 @@ class RepoUnhandledExceptionError(RepoExitError):
56 self.error = error 56 self.error = error
57 57
58 58
59class SilentRepoExitError(RepoExitError):
60 """RepoExitError that should no include CLI logging of issue/issues."""
61
62
59class ManifestParseError(RepoExitError): 63class ManifestParseError(RepoExitError):
60 """Failed to parse the manifest file.""" 64 """Failed to parse the manifest file."""
61 65
@@ -125,6 +129,10 @@ class DownloadError(RepoExitError):
125 return self.reason 129 return self.reason
126 130
127 131
132class InvalidArgumentsError(RepoExitError):
133 """Invalid command Arguments."""
134
135
128class SyncError(RepoExitError): 136class SyncError(RepoExitError):
129 """Cannot sync repo.""" 137 """Cannot sync repo."""
130 138
diff --git a/main.py b/main.py
index b213f0a4..ffed0b72 100755
--- a/main.py
+++ b/main.py
@@ -57,6 +57,7 @@ from error import RepoChangedException
57from error import RepoExitError 57from error import RepoExitError
58from error import RepoUnhandledExceptionError 58from error import RepoUnhandledExceptionError
59from error import RepoError 59from error import RepoError
60from error import SilentRepoExitError
60import gitc_utils 61import gitc_utils
61from manifest_xml import GitcClient, RepoClient 62from manifest_xml import GitcClient, RepoClient
62from pager import RunPager, TerminatePager 63from pager import RunPager, TerminatePager
@@ -872,16 +873,20 @@ def _Main(argv):
872 873
873 result = repo._Run(name, gopts, argv) or 0 874 result = repo._Run(name, gopts, argv) or 0
874 except RepoExitError as e: 875 except RepoExitError as e:
875 exception_name = type(e).__name__ 876 if not isinstance(e, SilentRepoExitError):
877 exception_name = type(e).__name__
878 print("fatal: %s" % e, file=sys.stderr)
879 if e.aggregate_errors:
880 print(f"{exception_name} Aggregate Errors")
881 for err in e.aggregate_errors[:MAX_PRINT_ERRORS]:
882 print(err)
883 if (
884 e.aggregate_errors
885 and len(e.aggregate_errors) > MAX_PRINT_ERRORS
886 ):
887 diff = len(e.aggregate_errors) - MAX_PRINT_ERRORS
888 print(f"+{diff} additional errors ...")
876 result = e.exit_code 889 result = e.exit_code
877 print("fatal: %s" % e, file=sys.stderr)
878 if e.aggregate_errors:
879 print(f"{exception_name} Aggregate Errors")
880 for err in e.aggregate_errors[:MAX_PRINT_ERRORS]:
881 print(err)
882 if len(e.aggregate_errors) > MAX_PRINT_ERRORS:
883 diff = len(e.aggregate_errors) - MAX_PRINT_ERRORS
884 print(f"+{diff} additional errors ...")
885 except KeyboardInterrupt: 890 except KeyboardInterrupt:
886 print("aborted by user", file=sys.stderr) 891 print("aborted by user", file=sys.stderr)
887 result = KEYBOARD_INTERRUPT_EXIT 892 result = KEYBOARD_INTERRUPT_EXIT
diff --git a/project.py b/project.py
index c3eb09c9..6e6a605e 100644
--- a/project.py
+++ b/project.py
@@ -1733,8 +1733,7 @@ class Project(object):
1733 cmd.append( 1733 cmd.append(
1734 "refs/changes/%2.2d/%d/%d" % (change_id % 100, change_id, patch_id) 1734 "refs/changes/%2.2d/%d/%d" % (change_id % 100, change_id, patch_id)
1735 ) 1735 )
1736 if GitCommand(self, cmd, bare=True).Wait() != 0: 1736 GitCommand(self, cmd, bare=True, verify_command=True).Wait()
1737 return None
1738 return DownloadedChange( 1737 return DownloadedChange(
1739 self, 1738 self,
1740 self.GetRevisionId(), 1739 self.GetRevisionId(),
@@ -1911,7 +1910,10 @@ class Project(object):
1911 1910
1912 all_refs = self.bare_ref.all 1911 all_refs = self.bare_ref.all
1913 if R_HEADS + name in all_refs: 1912 if R_HEADS + name in all_refs:
1914 return GitCommand(self, ["checkout", "-q", name, "--"]).Wait() == 0 1913 GitCommand(
1914 self, ["checkout", "-q", name, "--"], verify_command=True
1915 ).Wait()
1916 return True
1915 1917
1916 branch = self.GetBranch(name) 1918 branch = self.GetBranch(name)
1917 branch.remote = self.GetRemote() 1919 branch.remote = self.GetRemote()
@@ -1938,15 +1940,13 @@ class Project(object):
1938 branch.Save() 1940 branch.Save()
1939 return True 1941 return True
1940 1942
1941 if ( 1943 GitCommand(
1942 GitCommand( 1944 self,
1943 self, ["checkout", "-q", "-b", branch.name, revid] 1945 ["checkout", "-q", "-b", branch.name, revid],
1944 ).Wait() 1946 verify_command=True,
1945 == 0 1947 ).Wait()
1946 ): 1948 branch.Save()
1947 branch.Save() 1949 return True
1948 return True
1949 return False
1950 1950
1951 def CheckoutBranch(self, name): 1951 def CheckoutBranch(self, name):
1952 """Checkout a local topic branch. 1952 """Checkout a local topic branch.
@@ -1955,8 +1955,8 @@ class Project(object):
1955 name: The name of the branch to checkout. 1955 name: The name of the branch to checkout.
1956 1956
1957 Returns: 1957 Returns:
1958 True if the checkout succeeded; False if it didn't; None if the 1958 True if the checkout succeeded; False if the
1959 branch didn't exist. 1959 branch doesn't exist.
1960 """ 1960 """
1961 rev = R_HEADS + name 1961 rev = R_HEADS + name
1962 head = self.work_git.GetHead() 1962 head = self.work_git.GetHead()
@@ -1969,7 +1969,7 @@ class Project(object):
1969 revid = all_refs[rev] 1969 revid = all_refs[rev]
1970 except KeyError: 1970 except KeyError:
1971 # Branch does not exist in this project. 1971 # Branch does not exist in this project.
1972 return None 1972 return False
1973 1973
1974 if head.startswith(R_HEADS): 1974 if head.startswith(R_HEADS):
1975 try: 1975 try:
@@ -1986,15 +1986,14 @@ class Project(object):
1986 ) 1986 )
1987 return True 1987 return True
1988 1988
1989 return ( 1989 GitCommand(
1990 GitCommand( 1990 self,
1991 self, 1991 ["checkout", name, "--"],
1992 ["checkout", name, "--"], 1992 capture_stdout=True,
1993 capture_stdout=True, 1993 capture_stderr=True,
1994 capture_stderr=True, 1994 verify_command=True,
1995 ).Wait() 1995 ).Wait()
1996 == 0 1996 return True
1997 )
1998 1997
1999 def AbandonBranch(self, name): 1998 def AbandonBranch(self, name):
2000 """Destroy a local topic branch. 1999 """Destroy a local topic branch.
@@ -4458,9 +4457,12 @@ class ManifestProject(MetaProject):
4458 syncbuf.Finish() 4457 syncbuf.Finish()
4459 4458
4460 if is_new or self.CurrentBranch is None: 4459 if is_new or self.CurrentBranch is None:
4461 if not self.StartBranch("default"): 4460 try:
4461 self.StartBranch("default")
4462 except GitError as e:
4463 msg = str(e)
4462 print( 4464 print(
4463 "fatal: cannot create default in manifest", 4465 f"fatal: cannot create default in manifest {msg}",
4464 file=sys.stderr, 4466 file=sys.stderr,
4465 ) 4467 )
4466 return False 4468 return False
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)