Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 15 additions & 9 deletions .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,28 @@
"permissions": {
"allow": [
"Bash(codespell)",
"Bash(bandit -r dfetch)",
"Bash(black --check dfetch)",
"Bash(doc8 doc:*)",
"Bash(git fetch origin:*)",
"Bash(git stash:*)",
"Bash(git update-index --refresh)",
"Bash(isort --diff dfetch)",
"Bash(lint-imports)",
"Bash(mypy dfetch:*)",
"Bash(pip show:*)",
"Bash(pre-commit run:*)",
Comment thread
coderabbitai[bot] marked this conversation as resolved.
"Bash(pydocstyle dfetch:*)",
"Bash(pylint dfetch:*)",
"Bash(pyroma --directory --min=10 .)",
"Bash(pytest tests/test_sbom_reporter.py -q)",
"Bash(python -m behave *)",
"Bash(python -m pytest tests/*)",
"Bash(pre-commit run:*)",
"Bash(git stash:*)",
"Bash(xenon *)",
"Bash(radon *)",
"Bash(isort --diff dfetch)",
"Bash(black --check dfetch)",
"Bash(pylint dfetch:*)",
"Bash(ruff check:*)",
Comment thread
coderabbitai[bot] marked this conversation as resolved.
"Bash(mypy dfetch:*)",
"Bash(pip show:*)",
"Bash(doc8 doc:*)",
"Bash(pydocstyle dfetch:*)",
"Bash(bandit -r dfetch)",
"Bash(pyroma --directory --min=10 .)",
"Bash(xargs pyupgrade:*)",
"Bash(lint-imports)",
"Bash(pip install:*)",
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Release 0.14.0 (unreleased)
* Edit manifest in-place when freezing inside a git or SVN superproject, preserving comments and layout (#1063)
* Add new ``remove`` command to remove projects from manifest and disk (#26)
* Fix "unsafe symlink target" error for archives containing relative ``..`` symlinks (#1122)
* 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)
* Fix ``dfetch add`` crashing with a ``ValueError`` when the remote URL has a trailing slash (#1137)
* Fix unhelpful error message when a metadata file is malformed (#1145)
* Fix arbitrary file write via malicious tar/zip symlink (#1152)
Expand Down
22 changes: 13 additions & 9 deletions dfetch/commands/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
from dfetch.reporting.check.reporter import CheckReporter
from dfetch.reporting.check.sarif_reporter import SarifReporter
from dfetch.reporting.check.stdout_reporter import CheckStdoutReporter
from dfetch.util.util import catch_runtime_exceptions, in_directory
from dfetch.util.util import in_directory

logger = get_logger(__name__)

Expand Down Expand Up @@ -102,23 +102,27 @@ def __call__(self, args: argparse.Namespace) -> None:
reporters = self._get_reporters(args, superproject.manifest)

with in_directory(superproject.root_directory):
exceptions: list[str] = []
had_errors: bool = False
for project in superproject.manifest.selected_projects(args.projects):
with catch_runtime_exceptions(exceptions) as exceptions:
try:
dfetch.project.create_sub_project(project).check_for_update(
reporters,
files_to_ignore=superproject.ignored_files(project.destination),
)

if not args.no_recommendations and os.path.isdir(project.destination):
with in_directory(project.destination):
check_sub_manifests(superproject.manifest, project)
if not args.no_recommendations and os.path.isdir(
project.destination
):
with in_directory(project.destination):
check_sub_manifests(superproject.manifest, project)
except RuntimeError as exc:
logger.print_warning_line(project.name, str(exc))
had_errors = True

for reporter in reporters:
reporter.dump_to_file()

if exceptions:
raise RuntimeError("\n".join(exceptions))
if had_errors:
raise RuntimeError()

@staticmethod
def _get_reporters(
Expand Down
94 changes: 53 additions & 41 deletions dfetch/commands/diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,12 @@
import pathlib

import dfetch.commands.command
import dfetch.manifest.project
from dfetch.log import get_logger
from dfetch.project import create_super_project
from dfetch.project.metadata import Metadata
from dfetch.project.superproject import NoVcsSuperProject, RevisionRange
from dfetch.util.util import catch_runtime_exceptions, in_directory
from dfetch.project.superproject import NoVcsSuperProject, RevisionRange, SuperProject
from dfetch.util.util import in_directory

logger = get_logger(__name__)

Expand Down Expand Up @@ -115,51 +116,62 @@ def __call__(self, args: argparse.Namespace) -> None:
)

with in_directory(superproject.root_directory):
exceptions: list[str] = []
projects = superproject.manifest.selected_projects(args.projects)
if not projects:
raise RuntimeError(
f"No (such) project found! {', '.join(args.projects)}"
)
had_errors: bool = False
for project in projects:
with catch_runtime_exceptions(exceptions) as exceptions:
if not os.path.exists(project.destination):
raise RuntimeError(
"You cannot generate a diff of a project that was never fetched"
)
subproject = superproject.get_sub_project(project)

if not subproject:
raise RuntimeError("No subproject!")

old_rev = old_rev or superproject.get_file_revision(
subproject.metadata_path
)
if not old_rev:
raise RuntimeError(
"When not providing any revisions, dfetch starts from"
f" the last revision to {Metadata.FILENAME} in {subproject.local_path}."
" Please either commit this, or specify a revision to start from with --revs"
)
patch = superproject.diff(
project.destination,
revisions=RevisionRange(old_rev, new_rev),
ignore=(Metadata.FILENAME,),
)

msg = self._rev_msg(old_rev, new_rev)
if patch:
patch_path = pathlib.Path(f"{project.name}.patch")
logger.print_info_line(
project.name,
f"Generating patch {patch_path} {msg} in {superproject.root_directory}",
)
patch_path.write_text(patch, encoding="UTF-8")
else:
logger.print_info_line(project.name, f"No diffs found {msg}")

if exceptions:
raise RuntimeError("\n".join(exceptions))
try:
self._diff_project(superproject, project, old_rev, new_rev)
except RuntimeError as exc:
logger.print_warning_line(project.name, str(exc))
had_errors = True

if had_errors:
raise RuntimeError()

def _diff_project(
self,
superproject: SuperProject,
project: dfetch.manifest.project.ProjectEntry,
old_rev: str,
new_rev: str,
) -> None:
"""Generate a diff patch for a single project."""
if not os.path.exists(project.destination):
raise RuntimeError(
"You cannot generate a diff of a project that was never fetched"
)
subproject = superproject.get_sub_project(project)

if not subproject:
raise RuntimeError("No subproject!")

old_rev = old_rev or superproject.get_file_revision(subproject.metadata_path)
if not old_rev:
raise RuntimeError(
"When not providing any revisions, dfetch starts from"
f" the last revision to {Metadata.FILENAME} in {subproject.local_path}."
" Please either commit this, or specify a revision to start from with --revs"
)
patch = superproject.diff(
project.destination,
revisions=RevisionRange(old_rev, new_rev),
ignore=(Metadata.FILENAME,),
)

msg = self._rev_msg(old_rev, new_rev)
if patch:
patch_path = pathlib.Path(f"{project.name}.patch")
logger.print_info_line(
project.name,
f"Generating patch {patch_path} {msg} in {superproject.root_directory}",
)
patch_path.write_text(patch, encoding="UTF-8")
else:
logger.print_info_line(project.name, f"No diffs found {msg}")

@staticmethod
def _parse_revs(revs_arg: str) -> tuple[str, str]:
Expand Down
12 changes: 7 additions & 5 deletions dfetch/commands/format_patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
from dfetch.project.subproject import SubProject
from dfetch.project.svnsubproject import SvnSubProject
from dfetch.util.util import (
catch_runtime_exceptions,
check_no_path_traversal,
in_directory,
)
Expand Down Expand Up @@ -84,7 +83,7 @@ def __call__(self, args: argparse.Namespace) -> None:
"""Perform the format patch."""
superproject = create_super_project()

exceptions: list[str] = []
had_errors: bool = False

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

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

with in_directory(superproject.root_directory):
for project in superproject.manifest.selected_projects(args.projects):
with catch_runtime_exceptions(exceptions) as exceptions:
try:
subproject = dfetch.project.create_sub_project(project)

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

if exceptions:
raise RuntimeError("\n".join(exceptions))
if had_errors:
raise RuntimeError()


def _determine_target_patch_type(subproject: SubProject) -> PatchType:
Expand Down
12 changes: 9 additions & 3 deletions dfetch/commands/freeze.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
from dfetch.log import get_logger
from dfetch.project import create_super_project
from dfetch.project.superproject import NoVcsSuperProject
from dfetch.util.util import catch_runtime_exceptions, in_directory
from dfetch.util.util import in_directory

logger = get_logger(__name__)

Expand Down Expand Up @@ -99,8 +99,8 @@ def __call__(self, args: argparse.Namespace) -> None:
superproject = create_super_project()
make_backup = isinstance(superproject, NoVcsSuperProject)

exceptions: list[str] = []
manifest_updated = False
had_errors = False

with in_directory(superproject.root_directory):
manifest_path = superproject.manifest.path
Expand All @@ -110,7 +110,7 @@ def __call__(self, args: argparse.Namespace) -> None:
shutil.copyfile(manifest_path, manifest_path + ".backup")

for project in projects_to_freeze:
with catch_runtime_exceptions(exceptions) as exceptions:
try:
sub_project = dfetch.project.create_sub_project(project)
on_disk_version = sub_project.on_disk_version()

Expand All @@ -133,7 +133,13 @@ def __call__(self, args: argparse.Namespace) -> None:
)
superproject.manifest.update_project_version(project)
manifest_updated = True
except RuntimeError as exc:
logger.print_warning_line(project.name, str(exc))
had_errors = True

if manifest_updated:
superproject.manifest.dump()
logger.info(f"Updated manifest ({manifest_path}) in {os.getcwd()}")

if had_errors:
raise RuntimeError()
23 changes: 15 additions & 8 deletions dfetch/commands/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
from dfetch.log import get_logger
from dfetch.project import create_super_project
from dfetch.util.util import (
catch_runtime_exceptions,
check_no_path_traversal,
in_directory,
)
Expand Down Expand Up @@ -78,20 +77,25 @@ def __call__(self, args: argparse.Namespace) -> None:
"""Perform the update."""
superproject = create_super_project()

exceptions: list[str] = []
had_errors: bool = False
destinations: list[str] = [
os.path.realpath(project.destination)
for project in superproject.manifest.projects
]
with in_directory(superproject.root_directory):
for project in superproject.manifest.selected_projects(args.projects):
with catch_runtime_exceptions(exceptions) as exceptions:
try:
self._check_destination(project, destinations)
destination = project.destination
except RuntimeError:
had_errors = True
continue

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

def _ignored(dst: str = destination) -> list[str]:
return list(superproject.ignored_files(dst))

try:
dfetch.project.create_sub_project(project).update(
force=args.force,
ignored_files_callback=_ignored,
Expand All @@ -102,9 +106,12 @@ def _ignored(dst: str = destination) -> list[str]:
):
with in_directory(project.destination):
check_sub_manifests(superproject.manifest, project)
except RuntimeError as exc:
logger.print_warning_line(project.name, str(exc))
had_errors = True

if exceptions:
raise RuntimeError("\n".join(exceptions))
if had_errors:
raise RuntimeError()

@staticmethod
def _check_destination(
Expand Down
Loading
Loading