Skip to content

Commit df63293

Browse files
spoorccclaude
andcommitted
Fix #1096: print runtime errors in context of failing subproject
Replace the pattern of collecting RuntimeErrors across all subprojects and printing them at the end with immediate in-context logging via `logger.print_warning_line(project.name, str(exc))` at the point of failure. This ensures errors like "svn not available on system" appear next to the subproject they belong to instead of at the end of the run. The final `raise RuntimeError()` (no message) still produces a non-zero exit code without double-printing. Also bumps version to 0.14.0 and updates CHANGELOG, feature tests. https://claude.ai/code/session_01RTgPqrX37jFkK843dq65Bu Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 04569c9 commit df63293

39 files changed

Lines changed: 128 additions & 129 deletions

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Release 0.14.0 (unreleased)
1010
* Allow ``dfetch freeze`` to accept project names to freeze only specific projects (#1063)
1111
* Edit manifest in-place when freezing inside a git or SVN superproject, preserving comments and layout (#1063)
1212
* Add new ``remove`` command to remove projects from manifest and disk (#26)
13+
* Print runtime errors (e.g. ``svn not available on system``) directly in context of the failing subproject instead of collecting and showing them at the end (#1096)
1314

1415
Release 0.13.0 (released 2026-03-30)
1516
====================================

dfetch/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""Dfetch."""
22

3-
__version__ = "0.13.0"
3+
__version__ = "0.14.0"
44

55
DEFAULT_MANIFEST_NAME: str = "dfetch.yaml"

dfetch/commands/check.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
from dfetch.reporting.check.reporter import CheckReporter
4646
from dfetch.reporting.check.sarif_reporter import SarifReporter
4747
from dfetch.reporting.check.stdout_reporter import CheckStdoutReporter
48-
from dfetch.util.util import catch_runtime_exceptions, in_directory
48+
from dfetch.util.util import in_directory
4949

5050
logger = get_logger(__name__)
5151

@@ -102,13 +102,16 @@ def __call__(self, args: argparse.Namespace) -> None:
102102
reporters = self._get_reporters(args, superproject.manifest)
103103

104104
with in_directory(superproject.root_directory):
105-
exceptions: list[str] = []
105+
had_errors: bool = False
106106
for project in superproject.manifest.selected_projects(args.projects):
107-
with catch_runtime_exceptions(exceptions) as exceptions:
107+
try:
108108
dfetch.project.create_sub_project(project).check_for_update(
109109
reporters,
110110
files_to_ignore=superproject.ignored_files(project.destination),
111111
)
112+
except RuntimeError as exc:
113+
logger.print_warning_line(project.name, str(exc))
114+
had_errors = True
112115

113116
if not args.no_recommendations and os.path.isdir(project.destination):
114117
with in_directory(project.destination):
@@ -117,8 +120,8 @@ def __call__(self, args: argparse.Namespace) -> None:
117120
for reporter in reporters:
118121
reporter.dump_to_file()
119122

120-
if exceptions:
121-
raise RuntimeError("\n".join(exceptions))
123+
if had_errors:
124+
raise RuntimeError()
122125

123126
@staticmethod
124127
def _get_reporters(

dfetch/commands/format_patch.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
from dfetch.project.subproject import SubProject
3939
from dfetch.project.svnsubproject import SvnSubProject
4040
from dfetch.util.util import (
41-
catch_runtime_exceptions,
4241
check_no_path_traversal,
4342
in_directory,
4443
)
@@ -84,7 +83,7 @@ def __call__(self, args: argparse.Namespace) -> None:
8483
"""Perform the format patch."""
8584
superproject = create_super_project()
8685

87-
exceptions: list[str] = []
86+
had_errors: bool = False
8887

8988
output_dir_path = pathlib.Path(args.output_directory).resolve()
9089

@@ -94,7 +93,7 @@ def __call__(self, args: argparse.Namespace) -> None:
9493

9594
with in_directory(superproject.root_directory):
9695
for project in superproject.manifest.selected_projects(args.projects):
97-
with catch_runtime_exceptions(exceptions) as exceptions:
96+
try:
9897
subproject = dfetch.project.create_sub_project(project)
9998

10099
# Check if the project has a patch, maybe suggest creating one?
@@ -139,9 +138,12 @@ def __call__(self, args: argparse.Namespace) -> None:
139138
project.name,
140139
f"formatted patch written to {output_patch_file.relative_to(os.getcwd())}",
141140
)
141+
except RuntimeError as exc:
142+
logger.print_warning_line(project.name, str(exc))
143+
had_errors = True
142144

143-
if exceptions:
144-
raise RuntimeError("\n".join(exceptions))
145+
if had_errors:
146+
raise RuntimeError()
145147

146148

147149
def _determine_target_patch_type(subproject: SubProject) -> PatchType:

dfetch/commands/freeze.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
from dfetch.log import get_logger
6969
from dfetch.project import create_super_project
7070
from dfetch.project.superproject import NoVcsSuperProject
71-
from dfetch.util.util import catch_runtime_exceptions, in_directory
71+
from dfetch.util.util import in_directory
7272

7373
logger = get_logger(__name__)
7474

@@ -99,7 +99,6 @@ def __call__(self, args: argparse.Namespace) -> None:
9999
superproject = create_super_project()
100100
make_backup = isinstance(superproject, NoVcsSuperProject)
101101

102-
exceptions: list[str] = []
103102
manifest_updated = False
104103

105104
with in_directory(superproject.root_directory):
@@ -110,7 +109,7 @@ def __call__(self, args: argparse.Namespace) -> None:
110109
shutil.copyfile(manifest_path, manifest_path + ".backup")
111110

112111
for project in projects_to_freeze:
113-
with catch_runtime_exceptions(exceptions) as exceptions:
112+
try:
114113
sub_project = dfetch.project.create_sub_project(project)
115114
on_disk_version = sub_project.on_disk_version()
116115

@@ -133,6 +132,8 @@ def __call__(self, args: argparse.Namespace) -> None:
133132
)
134133
superproject.manifest.update_project_version(project)
135134
manifest_updated = True
135+
except RuntimeError as exc:
136+
logger.print_warning_line(project.name, str(exc))
136137

137138
if manifest_updated:
138139
superproject.manifest.dump()

dfetch/commands/update.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
from dfetch.log import get_logger
2929
from dfetch.project import create_super_project
3030
from dfetch.util.util import (
31-
catch_runtime_exceptions,
3231
check_no_path_traversal,
3332
in_directory,
3433
)
@@ -78,20 +77,25 @@ def __call__(self, args: argparse.Namespace) -> None:
7877
"""Perform the update."""
7978
superproject = create_super_project()
8079

81-
exceptions: list[str] = []
80+
had_errors: bool = False
8281
destinations: list[str] = [
8382
os.path.realpath(project.destination)
8483
for project in superproject.manifest.projects
8584
]
8685
with in_directory(superproject.root_directory):
8786
for project in superproject.manifest.selected_projects(args.projects):
88-
with catch_runtime_exceptions(exceptions) as exceptions:
87+
try:
8988
self._check_destination(project, destinations)
90-
destination = project.destination
89+
except RuntimeError:
90+
had_errors = True
91+
continue
9192

92-
def _ignored(dst: str = destination) -> list[str]:
93-
return list(superproject.ignored_files(dst))
93+
destination = project.destination
9494

95+
def _ignored(dst: str = destination) -> list[str]:
96+
return list(superproject.ignored_files(dst))
97+
98+
try:
9599
dfetch.project.create_sub_project(project).update(
96100
force=args.force,
97101
ignored_files_callback=_ignored,
@@ -102,9 +106,12 @@ def _ignored(dst: str = destination) -> list[str]:
102106
):
103107
with in_directory(project.destination):
104108
check_sub_manifests(superproject.manifest, project)
109+
except RuntimeError as exc:
110+
logger.print_warning_line(project.name, str(exc))
111+
had_errors = True
105112

106-
if exceptions:
107-
raise RuntimeError("\n".join(exceptions))
113+
if had_errors:
114+
raise RuntimeError()
108115

109116
@staticmethod
110117
def _check_destination(

dfetch/util/util.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -176,18 +176,6 @@ def in_directory(path: str | Path) -> Generator[str, None, None]:
176176
os.chdir(pwd)
177177

178178

179-
@contextmanager
180-
def catch_runtime_exceptions(
181-
exc_list: list[str] | None = None,
182-
) -> Generator[list[str], None, None]:
183-
"""Catch all runtime errors and add it to list of strings."""
184-
exc_list = exc_list or []
185-
try:
186-
yield exc_list
187-
except RuntimeError as exc:
188-
exc_list += [str(exc)]
189-
190-
191179
@contextmanager
192180
def prefix_runtime_exceptions(
193181
prefix: str,

features/check-archive.feature

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Feature: Checking dependencies from an archive
2424
When I run "dfetch check" in MyProject
2525
Then the output shows
2626
"""
27-
Dfetch (0.13.0)
27+
Dfetch (0.14.0)
2828
SomeProject:
2929
> up-to-date (some-remote-server/SomeProject.tar.gz)
3030
"""
@@ -48,7 +48,7 @@ Feature: Checking dependencies from an archive
4848
When I run "dfetch check" in MyProject
4949
Then the output shows
5050
"""
51-
Dfetch (0.13.0)
51+
Dfetch (0.14.0)
5252
SomeProject:
5353
> up-to-date (sha256:<archive-sha256>)
5454
"""
@@ -69,7 +69,7 @@ Feature: Checking dependencies from an archive
6969
When I run "dfetch check" in MyProject
7070
Then the output shows
7171
"""
72-
Dfetch (0.13.0)
72+
Dfetch (0.14.0)
7373
SomeProject:
7474
> wanted (some-remote-server/SomeProject.tar.gz), available (some-remote-server/SomeProject.tar.gz)
7575
"""
@@ -87,7 +87,7 @@ Feature: Checking dependencies from an archive
8787
When I run "dfetch check"
8888
Then the output shows
8989
"""
90-
Dfetch (0.13.0)
90+
Dfetch (0.14.0)
9191
non-existent-archive:
9292
> wanted (https://dfetch.invalid/does-not-exist.tar.gz), but not available at the upstream.
9393
"""
@@ -113,7 +113,7 @@ Feature: Checking dependencies from an archive
113113
When I run "dfetch check SomeProject" in MyProject
114114
Then the output shows
115115
"""
116-
Dfetch (0.13.0)
116+
Dfetch (0.14.0)
117117
SomeProject:
118118
> up-to-date (some-remote-server/SomeProject.tar.gz)
119119
"""
@@ -136,7 +136,7 @@ Feature: Checking dependencies from an archive
136136
When I run "dfetch check SomeProject" in MyProject
137137
Then the output shows
138138
"""
139-
Dfetch (0.13.0)
139+
Dfetch (0.14.0)
140140
SomeProject:
141141
> Local changes were detected, please generate a patch using 'dfetch diff SomeProject' and add it to your manifest using 'patch:'. Alternatively overwrite the local changes with 'dfetch update --force SomeProject'
142142
> up-to-date (some-remote-server/SomeProject.tar.gz)

features/check-git-repo.feature

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Feature: Checking dependencies from a git repository
2626
When I run "dfetch check"
2727
Then the output shows
2828
"""
29-
Dfetch (0.13.0)
29+
Dfetch (0.14.0)
3030
ext/test-repo-rev-only:
3131
> wanted (e1fda19a57b873eb8e6ae37780594cbb77b70f1a), available (e1fda19a57b873eb8e6ae37780594cbb77b70f1a)
3232
ext/test-rev-and-branch:
@@ -52,7 +52,7 @@ Feature: Checking dependencies from a git repository
5252
When I run "dfetch check"
5353
Then the output shows
5454
"""
55-
Dfetch (0.13.0)
55+
Dfetch (0.14.0)
5656
ext/test-repo-tag-v1:
5757
> wanted (v1), available (v2.0)
5858
"""
@@ -82,7 +82,7 @@ Feature: Checking dependencies from a git repository
8282
When I run "dfetch check"
8383
Then the output shows
8484
"""
85-
Dfetch (0.13.0)
85+
Dfetch (0.14.0)
8686
ext/test-repo-rev-only:
8787
> up-to-date (e1fda19a57b873eb8e6ae37780594cbb77b70f1a)
8888
ext/test-rev-and-branch:
@@ -116,7 +116,7 @@ Feature: Checking dependencies from a git repository
116116
And I run "dfetch check"
117117
Then the output shows
118118
"""
119-
Dfetch (0.13.0)
119+
Dfetch (0.14.0)
120120
ext/test-repo-tag:
121121
> wanted (v2.0), current (v1), available (v2.0)
122122
"""
@@ -138,7 +138,7 @@ Feature: Checking dependencies from a git repository
138138
When I run "dfetch check SomeProject"
139139
Then the output shows
140140
"""
141-
Dfetch (0.13.0)
141+
Dfetch (0.14.0)
142142
SomeProject:
143143
> Local changes were detected, please generate a patch using 'dfetch diff SomeProject' and add it to your manifest using 'patch:'. Alternatively overwrite the local changes with 'dfetch update --force SomeProject'
144144
> up-to-date (master - 90be799b58b10971691715bdc751fbe5237848a0)
@@ -159,7 +159,7 @@ Feature: Checking dependencies from a git repository
159159
When I run "dfetch check SomeProject"
160160
Then the output shows
161161
"""
162-
Dfetch (0.13.0)
162+
Dfetch (0.14.0)
163163
SomeProject:
164164
> up-to-date (master - 90be799b58b10971691715bdc751fbe5237848a0)
165165
"""
@@ -179,7 +179,7 @@ Feature: Checking dependencies from a git repository
179179
When I run "dfetch check"
180180
Then the output shows
181181
"""
182-
Dfetch (0.13.0)
182+
Dfetch (0.14.0)
183183
>>>git ls-remote --heads https://giiiiiidhub.com/i-do-not-exist/broken<<< failed!
184184
'https://giiiiiidhub.com/i-do-not-exist/broken' is not a valid URL or unreachable:
185185
fatal: unable to access 'https://giiiiiidhub.com/i-do-not-exist/broken/': Could not resolve host: giiiiiidhub.com
@@ -208,7 +208,7 @@ Feature: Checking dependencies from a git repository
208208
When I run "dfetch check"
209209
Then the output shows
210210
"""
211-
Dfetch (0.13.0)
211+
Dfetch (0.14.0)
212212
SomeProjectMissingTag:
213213
> wanted (i-dont-exist), but not available at the upstream.
214214
SomeProjectNonExistentBranch:
@@ -230,7 +230,7 @@ Feature: Checking dependencies from a git repository
230230
When I run "dfetch check"
231231
Then the output starts with:
232232
"""
233-
Dfetch (0.13.0)
233+
Dfetch (0.14.0)
234234
>>>git ls-remote --heads --tags https://github.com/dfetch-org/test-repo-private.git<<< returned 128:
235235
"""
236236

@@ -247,6 +247,6 @@ Feature: Checking dependencies from a git repository
247247
When I run "dfetch check"
248248
Then the output starts with:
249249
"""
250-
Dfetch (0.13.0)
250+
Dfetch (0.14.0)
251251
>>>git ls-remote --heads --tags git@github.com:dfetch-org/test-repo-private.git<<< returned 128:
252252
"""

features/check-specific-projects.feature

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Feature: Checking specific projects
2727
When I run "dfetch check ext/test-rev-and-branch"
2828
Then the output shows
2929
"""
30-
Dfetch (0.13.0)
30+
Dfetch (0.14.0)
3131
ext/test-rev-and-branch:
3232
> wanted (main - 8df389d0524863b85f484f15a91c5f2c40aefda1), available (main - e1fda19a57b873eb8e6ae37780594cbb77b70f1a)
3333
"""

0 commit comments

Comments
 (0)