diff options
author | Gavin Mak <gavinmak@google.com> | 2025-08-13 23:42:00 -0700 |
---|---|---|
committer | LUCI <gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com> | 2025-08-14 09:54:15 -0700 |
commit | d534a5537fd317cd769fed54eceb8248777db701 (patch) | |
tree | 2c036f9540f0eb723ab48ba28549758033ecc269 | |
parent | a64149a7a77814132629bbb4c07d922c2222df25 (diff) | |
download | git-repo-d534a5537fd317cd769fed54eceb8248777db701.tar.gz |
sync: Fix missing error details in interleaved summary
When checkout errors occurred in interleaved sync, they were wrapped in
a SyncError with no message, causing blank lines in the final summary.
Refactor _SyncResult to hold a list of exceptions, ensuring the original
error messages are propagated correctly.
Bug: 438178765
Change-Id: Ic25e515068959829cb6290cfd9e4c2d3963bbbea
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/498342
Reviewed-by: Scott Lee <ddoman@google.com>
Tested-by: Gavin Mak <gavinmak@google.com>
Commit-Queue: Gavin Mak <gavinmak@google.com>
-rw-r--r-- | subcmds/sync.py | 42 | ||||
-rw-r--r-- | tests/test_subcmds_sync.py | 14 |
2 files changed, 27 insertions, 29 deletions
diff --git a/subcmds/sync.py b/subcmds/sync.py index ed2e516a..582bd057 100644 --- a/subcmds/sync.py +++ b/subcmds/sync.py | |||
@@ -204,14 +204,13 @@ class _SyncResult(NamedTuple): | |||
204 | relpath (str): The project's relative path from the repo client top. | 204 | relpath (str): The project's relative path from the repo client top. |
205 | remote_fetched (bool): True if the remote was actually queried. | 205 | remote_fetched (bool): True if the remote was actually queried. |
206 | fetch_success (bool): True if the fetch operation was successful. | 206 | fetch_success (bool): True if the fetch operation was successful. |
207 | fetch_error (Optional[Exception]): The Exception from a failed fetch, | 207 | fetch_errors (List[Exception]): The Exceptions from a failed fetch. |
208 | or None. | ||
209 | fetch_start (Optional[float]): The time.time() when fetch started. | 208 | fetch_start (Optional[float]): The time.time() when fetch started. |
210 | fetch_finish (Optional[float]): The time.time() when fetch finished. | 209 | fetch_finish (Optional[float]): The time.time() when fetch finished. |
211 | checkout_success (bool): True if the checkout operation was | 210 | checkout_success (bool): True if the checkout operation was |
212 | successful. | 211 | successful. |
213 | checkout_error (Optional[Exception]): The Exception from a failed | 212 | checkout_errors (List[Exception]): The Exceptions from a failed |
214 | checkout, or None. | 213 | checkout. |
215 | checkout_start (Optional[float]): The time.time() when checkout | 214 | checkout_start (Optional[float]): The time.time() when checkout |
216 | started. | 215 | started. |
217 | checkout_finish (Optional[float]): The time.time() when checkout | 216 | checkout_finish (Optional[float]): The time.time() when checkout |
@@ -224,12 +223,12 @@ class _SyncResult(NamedTuple): | |||
224 | 223 | ||
225 | remote_fetched: bool | 224 | remote_fetched: bool |
226 | fetch_success: bool | 225 | fetch_success: bool |
227 | fetch_error: Optional[Exception] | 226 | fetch_errors: List[Exception] |
228 | fetch_start: Optional[float] | 227 | fetch_start: Optional[float] |
229 | fetch_finish: Optional[float] | 228 | fetch_finish: Optional[float] |
230 | 229 | ||
231 | checkout_success: bool | 230 | checkout_success: bool |
232 | checkout_error: Optional[Exception] | 231 | checkout_errors: List[Exception] |
233 | checkout_start: Optional[float] | 232 | checkout_start: Optional[float] |
234 | checkout_finish: Optional[float] | 233 | checkout_finish: Optional[float] |
235 | 234 | ||
@@ -2210,7 +2209,7 @@ later is required to fix a server side protocol bug. | |||
2210 | """Syncs a single project for interleaved sync.""" | 2209 | """Syncs a single project for interleaved sync.""" |
2211 | fetch_success = False | 2210 | fetch_success = False |
2212 | remote_fetched = False | 2211 | remote_fetched = False |
2213 | fetch_error = None | 2212 | fetch_errors = [] |
2214 | fetch_start = None | 2213 | fetch_start = None |
2215 | fetch_finish = None | 2214 | fetch_finish = None |
2216 | network_output = "" | 2215 | network_output = "" |
@@ -2243,16 +2242,17 @@ later is required to fix a server side protocol bug. | |||
2243 | ) | 2242 | ) |
2244 | fetch_success = sync_result.success | 2243 | fetch_success = sync_result.success |
2245 | remote_fetched = sync_result.remote_fetched | 2244 | remote_fetched = sync_result.remote_fetched |
2246 | fetch_error = sync_result.error | 2245 | if sync_result.error: |
2246 | fetch_errors.append(sync_result.error) | ||
2247 | except KeyboardInterrupt: | 2247 | except KeyboardInterrupt: |
2248 | logger.error( | 2248 | logger.error( |
2249 | "Keyboard interrupt while processing %s", project.name | 2249 | "Keyboard interrupt while processing %s", project.name |
2250 | ) | 2250 | ) |
2251 | except GitError as e: | 2251 | except GitError as e: |
2252 | fetch_error = e | 2252 | fetch_errors.append(e) |
2253 | logger.error("error.GitError: Cannot fetch %s", e) | 2253 | logger.error("error.GitError: Cannot fetch %s", e) |
2254 | except Exception as e: | 2254 | except Exception as e: |
2255 | fetch_error = e | 2255 | fetch_errors.append(e) |
2256 | logger.error( | 2256 | logger.error( |
2257 | "error: Cannot fetch %s (%s: %s)", | 2257 | "error: Cannot fetch %s (%s: %s)", |
2258 | project.name, | 2258 | project.name, |
@@ -2264,7 +2264,7 @@ later is required to fix a server side protocol bug. | |||
2264 | network_output = network_output_capture.getvalue() | 2264 | network_output = network_output_capture.getvalue() |
2265 | 2265 | ||
2266 | checkout_success = False | 2266 | checkout_success = False |
2267 | checkout_error = None | 2267 | checkout_errors = [] |
2268 | checkout_start = None | 2268 | checkout_start = None |
2269 | checkout_finish = None | 2269 | checkout_finish = None |
2270 | checkout_stderr = "" | 2270 | checkout_stderr = "" |
@@ -2293,22 +2293,20 @@ later is required to fix a server side protocol bug. | |||
2293 | ) | 2293 | ) |
2294 | checkout_success = syncbuf.Finish() | 2294 | checkout_success = syncbuf.Finish() |
2295 | if syncbuf.errors: | 2295 | if syncbuf.errors: |
2296 | checkout_error = SyncError( | 2296 | checkout_errors.extend(syncbuf.errors) |
2297 | aggregate_errors=syncbuf.errors | ||
2298 | ) | ||
2299 | except KeyboardInterrupt: | 2297 | except KeyboardInterrupt: |
2300 | logger.error( | 2298 | logger.error( |
2301 | "Keyboard interrupt while processing %s", project.name | 2299 | "Keyboard interrupt while processing %s", project.name |
2302 | ) | 2300 | ) |
2303 | except GitError as e: | 2301 | except GitError as e: |
2304 | checkout_error = e | 2302 | checkout_errors.append(e) |
2305 | logger.error( | 2303 | logger.error( |
2306 | "error.GitError: Cannot checkout %s: %s", | 2304 | "error.GitError: Cannot checkout %s: %s", |
2307 | project.name, | 2305 | project.name, |
2308 | e, | 2306 | e, |
2309 | ) | 2307 | ) |
2310 | except Exception as e: | 2308 | except Exception as e: |
2311 | checkout_error = e | 2309 | checkout_errors.append(e) |
2312 | logger.error( | 2310 | logger.error( |
2313 | "error: Cannot checkout %s: %s: %s", | 2311 | "error: Cannot checkout %s: %s: %s", |
2314 | project.name, | 2312 | project.name, |
@@ -2333,8 +2331,8 @@ later is required to fix a server side protocol bug. | |||
2333 | fetch_success=fetch_success, | 2331 | fetch_success=fetch_success, |
2334 | remote_fetched=remote_fetched, | 2332 | remote_fetched=remote_fetched, |
2335 | checkout_success=checkout_success, | 2333 | checkout_success=checkout_success, |
2336 | fetch_error=fetch_error, | 2334 | fetch_errors=fetch_errors, |
2337 | checkout_error=checkout_error, | 2335 | checkout_errors=checkout_errors, |
2338 | stderr_text=stderr_text.strip(), | 2336 | stderr_text=stderr_text.strip(), |
2339 | fetch_start=fetch_start, | 2337 | fetch_start=fetch_start, |
2340 | fetch_finish=fetch_finish, | 2338 | fetch_finish=fetch_finish, |
@@ -2429,14 +2427,14 @@ later is required to fix a server side protocol bug. | |||
2429 | if not success: | 2427 | if not success: |
2430 | ret = False | 2428 | ret = False |
2431 | err_event.set() | 2429 | err_event.set() |
2432 | if result.fetch_error: | 2430 | if result.fetch_errors: |
2433 | errors.append(result.fetch_error) | 2431 | errors.extend(result.fetch_errors) |
2434 | self._interleaved_err_network = True | 2432 | self._interleaved_err_network = True |
2435 | self._interleaved_err_network_results.append( | 2433 | self._interleaved_err_network_results.append( |
2436 | result.relpath | 2434 | result.relpath |
2437 | ) | 2435 | ) |
2438 | if result.checkout_error: | 2436 | if result.checkout_errors: |
2439 | errors.append(result.checkout_error) | 2437 | errors.extend(result.checkout_errors) |
2440 | self._interleaved_err_checkout = True | 2438 | self._interleaved_err_checkout = True |
2441 | self._interleaved_err_checkout_results.append( | 2439 | self._interleaved_err_checkout_results.append( |
2442 | result.relpath | 2440 | result.relpath |
diff --git a/tests/test_subcmds_sync.py b/tests/test_subcmds_sync.py index 940c69fc..6c9cc9ab 100644 --- a/tests/test_subcmds_sync.py +++ b/tests/test_subcmds_sync.py | |||
@@ -810,8 +810,8 @@ class InterleavedSyncTest(unittest.TestCase): | |||
810 | result = result_obj.results[0] | 810 | result = result_obj.results[0] |
811 | self.assertTrue(result.fetch_success) | 811 | self.assertTrue(result.fetch_success) |
812 | self.assertTrue(result.checkout_success) | 812 | self.assertTrue(result.checkout_success) |
813 | self.assertIsNone(result.fetch_error) | 813 | self.assertEqual(result.fetch_errors, []) |
814 | self.assertIsNone(result.checkout_error) | 814 | self.assertEqual(result.checkout_errors, []) |
815 | project.Sync_NetworkHalf.assert_called_once() | 815 | project.Sync_NetworkHalf.assert_called_once() |
816 | project.Sync_LocalHalf.assert_called_once() | 816 | project.Sync_LocalHalf.assert_called_once() |
817 | 817 | ||
@@ -833,8 +833,8 @@ class InterleavedSyncTest(unittest.TestCase): | |||
833 | 833 | ||
834 | self.assertFalse(result.fetch_success) | 834 | self.assertFalse(result.fetch_success) |
835 | self.assertFalse(result.checkout_success) | 835 | self.assertFalse(result.checkout_success) |
836 | self.assertEqual(result.fetch_error, fetch_error) | 836 | self.assertEqual(result.fetch_errors, [fetch_error]) |
837 | self.assertIsNone(result.checkout_error) | 837 | self.assertEqual(result.checkout_errors, []) |
838 | project.Sync_NetworkHalf.assert_called_once() | 838 | project.Sync_NetworkHalf.assert_called_once() |
839 | project.Sync_LocalHalf.assert_not_called() | 839 | project.Sync_LocalHalf.assert_not_called() |
840 | 840 | ||
@@ -871,7 +871,7 @@ class InterleavedSyncTest(unittest.TestCase): | |||
871 | 871 | ||
872 | self.assertFalse(result.fetch_success) | 872 | self.assertFalse(result.fetch_success) |
873 | self.assertFalse(result.checkout_success) | 873 | self.assertFalse(result.checkout_success) |
874 | self.assertEqual(result.fetch_error, fetch_error) | 874 | self.assertEqual(result.fetch_errors, [fetch_error]) |
875 | project.Sync_NetworkHalf.assert_called_once() | 875 | project.Sync_NetworkHalf.assert_called_once() |
876 | project.Sync_LocalHalf.assert_not_called() | 876 | project.Sync_LocalHalf.assert_not_called() |
877 | 877 | ||
@@ -893,8 +893,8 @@ class InterleavedSyncTest(unittest.TestCase): | |||
893 | 893 | ||
894 | self.assertTrue(result.fetch_success) | 894 | self.assertTrue(result.fetch_success) |
895 | self.assertFalse(result.checkout_success) | 895 | self.assertFalse(result.checkout_success) |
896 | self.assertIsNone(result.fetch_error) | 896 | self.assertEqual(result.fetch_errors, []) |
897 | self.assertEqual(result.checkout_error, checkout_error) | 897 | self.assertEqual(result.checkout_errors, [checkout_error]) |
898 | project.Sync_NetworkHalf.assert_called_once() | 898 | project.Sync_NetworkHalf.assert_called_once() |
899 | project.Sync_LocalHalf.assert_called_once() | 899 | project.Sync_LocalHalf.assert_called_once() |
900 | 900 | ||