Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
f0e4131
Add review-patch command for interactive patch inspection
claude Jun 18, 2026
431ab80
Fix pylint R0801, isort order, and code-review findings in review-patch
claude Jun 18, 2026
8f2eb9e
Add review-patch to CI run, demo script, and docs
claude Jun 18, 2026
0b4cbb1
Skip redundant restore fetch when all patches already applied
claude Jun 18, 2026
68eb668
Eliminate fetches after the first in review-patch
claude Jun 18, 2026
b6d97bd
Refactor review-patch to reduce cyclomatic complexity below 8
claude Jun 19, 2026
61c35cb
Update review-patch feature files to match reduced-fetch behaviour
claude Jun 19, 2026
be99c10
Address review comments: count validation, pathspec hardening, VCS di…
claude Jun 19, 2026
1bce041
Update review-patch asciicast and add rendered GIF
claude Jun 19, 2026
9ddf70b
Fix pyright errors in test_review_patch.py
claude Jun 19, 2026
33720c6
Fix bandit B101 by using GitSuperProject | None type narrowing
claude Jun 19, 2026
e467e71
Restore dfetch_data.yaml metadata after review-patch exits
claude Jun 19, 2026
1fac638
Replace metadata patch assertion with git status no-changes check
claude Jun 19, 2026
8c9d18c
Silence patch_ng logs during interactive TUI patch steps
claude Jun 19, 2026
71a3e55
Clear TUI screen on patch failure before propagating the error
claude Jun 19, 2026
63bb5a5
Fix restore after patch failure by using git restore --source=HEAD
claude Jun 19, 2026
d5ecbf6
Fix pre-commit
ben-edna Jun 19, 2026
d5bf817
Improve docs
spoorcc Jun 23, 2026
b8bdece
review comments
spoorcc Jun 24, 2026
8a63de6
Rename to replay-patches
spoorcc Jun 24, 2026
bc34a58
Add combined mode
spoorcc Jun 24, 2026
67fbbc9
review comments
spoorcc Jun 25, 2026
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
2 changes: 2 additions & 0 deletions .github/workflows/run.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ jobs:
- run: dfetch update
- run: dfetch update
- run: dfetch update-patch
- run: dfetch replay-patches
- run: dfetch format-patch
- run: dfetch report -t sbom
- run: dfetch remove test-repo
Expand Down Expand Up @@ -198,6 +199,7 @@ jobs:
- run: dfetch update
- run: dfetch update
- run: dfetch update-patch
- run: dfetch replay-patches
- run: dfetch format-patch
- run: dfetch report -t sbom
- run: dfetch remove test-repo
Expand Down
8 changes: 6 additions & 2 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
Release 0.15.0 (unreleased)
==============================

* Add ``replay-patches`` command to step through patch contributions interactively (#1290)

Release 0.14.3 (unreleased)
====================================

* Update ``dfetch environment`` to show newer version inline (`#1310`)
* Update ``dfetch environment`` to show newer version inline (#1310)

Release 0.14.2 (released 2026-06-21)
====================================
Expand All @@ -11,7 +16,6 @@ Release 0.14.2 (released 2026-06-21)

Release 0.14.1 (released 2026-06-19)
====================================

* Implement C-043: add ``pip-audit`` OSV gate to the release workflow
* Add CRA Compliance: OSCAL 1.2.2 Component Definition
* Fix ``dfetch import`` mangling the namespace of a generic VCS URL whose path contains ``.git`` (#1268)
Expand Down
2 changes: 2 additions & 0 deletions dfetch/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import dfetch.commands.import_
import dfetch.commands.init
import dfetch.commands.remove
import dfetch.commands.replay_patches
import dfetch.commands.report
import dfetch.commands.update
import dfetch.commands.update_patch
Expand Down Expand Up @@ -56,6 +57,7 @@ def create_parser() -> argparse.ArgumentParser:
dfetch.commands.remove.Remove.create_menu(subparsers)
dfetch.commands.report.Report.create_menu(subparsers)
dfetch.commands.update.Update.create_menu(subparsers)
dfetch.commands.replay_patches.ReplayPatches.create_menu(subparsers)
dfetch.commands.update_patch.UpdatePatch.create_menu(subparsers)
dfetch.commands.validate.Validate.create_menu(subparsers)

Expand Down
26 changes: 26 additions & 0 deletions dfetch/commands/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@
import sys
from abc import ABC, abstractmethod
from argparse import ArgumentParser # pylint: disable=unused-import
from collections.abc import Callable
from typing import TYPE_CHECKING, TypeVar

from dfetch.log import get_logger
from dfetch.manifest.project import ProjectEntry
from dfetch.project.superproject import SuperProject
from dfetch.util.util import in_directory

if TYPE_CHECKING and sys.version_info >= (3, 10):
from typing import TypeAlias

Expand All @@ -18,6 +24,8 @@
argparse._SubParsersAction # pyright: ignore[reportPrivateUsage] #pylint: disable=protected-access
)

_command_logger = get_logger(__name__)


def pascal_to_kebab(name: str) -> str:
"""Insert a dash before each uppercase letter (except the first) and lowercase everything."""
Expand Down Expand Up @@ -60,6 +68,24 @@ def __call__(self, args: argparse.Namespace) -> None:
NotImplementedError: This is an abstract method that should be implemented by a subclass.
"""

@staticmethod
def _iter_projects(
superproject: SuperProject,
project_names: list[str],
process: Callable[[ProjectEntry], None],
) -> None:
"""Iterate over selected projects, log per-project errors, re-raise if any failed."""
had_errors = False
with in_directory(superproject.root_directory):
for project in superproject.manifest.selected_projects(project_names):
try:
process(project)
except RuntimeError as exc:
_command_logger.print_error_line(project.name, str(exc))
had_errors = True
if had_errors:
raise RuntimeError()

@staticmethod
def parser(
subparsers: SubparserActionType,
Expand Down
Loading
Loading