Skip to content

Add an editor-friendly Git working tree review for vendored patches #1283

Closed

Description

@sawachikaminoru

Problem

dfetch diff can generate patch files for local changes to vendored projects, but large patches can be difficult to review as standalone files. A single patch may touch many files and hunks, making it hard for maintainers to inspect exactly what differs between the clean upstream source and the patched vendored source.

Reviewing the same changes as a Git working tree would make the workflow easier to navigate: editors such as VS Code can group changes by file, show side-by-side diffs, and let users inspect each modified file with their normal source control tools.

Proposal

Add a command that materializes a temporary Git repository for patch review.

Example:

dfetch review-patch SomeProject --code

Suggested default behavior:

  1. Create a temporary review directory.
  2. Fetch the selected project without applying patches.
  3. Initialize a Git repository and commit that clean upstream state.
  4. Apply the project patches.
  5. Leave the working tree dirty with exactly the patch differences.
  6. Optionally open the review directory in VS Code.

This would let users inspect the patch as normal Git working-tree changes.

Possible modes

dfetch review-patch SomeProject --mode combined
dfetch review-patch SomeProject --mode per-patch
  • combined: HEAD is clean upstream, working tree contains all patches applied.
  • per-patch: apply/review patches individually, or create one commit per patch.

Acceptance criteria

  • Users can review patched changes as normal Git working-tree changes.
  • The review directory contains a clean upstream baseline commit.
  • The working tree shows only meaningful patched differences.
  • Multiple patch files are supported.
  • Git, SVN, and archive-backed projects continue to work through dfetch’s existing fetch logic.
  • The command does not modify the real project checkout.

Activity

  1. spoorcc commented on Jun 17, 2026

    @spoorcc
    Contributor

    Thanks for the write-up! A nice feature I haven't thought of :)

    Before proposing an alternative I want to make sure I understand the goal correctly. My assumption: you want to walk through the patches one by one to review what each one contributes, rather than review the combined diff of all patches at once. If that's wrong, please correct me.

    With that assumption, here are the constraints I'd like to work within:

    • No external state. A temporary directory with its own Git repo means dfetch has to track it, clean it up, and handle the case where it goes stale. State makes everything harder.
    • No editor coupling. Launching VS Code (or any editor) from a CLI tool is fragile. It won't work in CI, remote sessions, or for users who prefer something else. The tool should put the files in the right state; the user opens whatever they want.

    I briefly considered adopting quilt as a dependency, since its push/pop/goto model is exactly the right mental model here. The patches dfetch already generates are standard unified diff format, which is quilt-compatible. But this makes it too complicated and fragile as well.

    Creating an entire copy of the project in a temp directory also feels heavy for what is essentially a navigation problem.

    Proposed alternative: dfetch rewind-patch

    A new command that moves the vendored project directory to a specific point in its patch stack, directly in place, with no copy:

    dfetch rewind-patch <project> <index|patch-name>
    dfetch rewind-patch <project> --interactive

    Examples:

    dfetch rewind-patch mylib 0              # clean upstream, no patches
    dfetch rewind-patch mylib 1             # first patch applied
    dfetch rewind-patch mylib my-fix.patch  # up to and including named patch
    dfetch rewind-patch mylib -i            # interactive TUI picker

    The interactive mode (-i) shows a scrollable list of the patch stack. Navigate with arrow keys, press Enter to jump to that state:

      Patch stack: mylib
    
        (unpatched)
        fix-null-check.patch
      ▶ add-feature.patch          <- current
    
      ↑/↓ navigate  Enter jump to state  Esc exit
    

    After pressing Up and Enter:

      Patch stack: mylib
    
        (unpatched)
      ▶ fix-null-check.patch       <- current
        add-feature.patch
    
      ↑/↓ navigate  Enter jump to state  Esc exit
    

    Your editor refreshes automatically because the files change in place. There is no copy to manage.

    Preconditions (same as update-patch): must be inside a VCS superproject with no uncommitted changes in the project directory. Recovery is trivial: dfetch update -f <project> or a VCS undo restores the original state, with no dfetch-managed cleanup needed.

    Side note: existing workaround with format-patch and git am

    If you primarily want to see each patch as a proper Git commit, there is already a partial path with existing dfetch commands. dfetch format-patch produces email-style patches (one per file) that git am can apply as individual commits. So you could clone the upstream repo yourself, run dfetch format-patch <project>, and pipe the result through git am to get one commit per patch in a real Git history you can browse with any tool. It requires manual setup and only works for Git-backed projects, but it may already cover your use case without waiting for a new command.

    On naming: review-patch describes why you might use the command, not what it does. The existing commands update-patch and format-patch name what happens to the patch. rewind-patch follows that pattern. But I'm still not completely sold on the name.

  2. sawachikaminoru commented on Jun 17, 2026

    @sawachikaminoru
    Author

    Thanks, that helps. I think my goal is slightly different from your assumption.

    I am not mainly trying to walk through the patch stack one patch at a time. That could be useful, but my primary need is to review the final vendored result against clean upstream in an editor-friendly way.

    In other words, I want to answer:

    “What exactly is different between upstream as fetched by dfetch, and the same project after all dfetch patches have been applied?”

    The reason is that reading .patch files directly becomes hard when they touch many files and hunks. What I would like is the same experience I get when reviewing normal Git working tree changes in VS Code:

    • a file tree showing Modified / Added / Deleted files
    • click a file
    • see a side-by-side diff
    • left side: clean upstream
    • right side: patched vendored result

    For that use case, rewind-patch sounds useful for navigating the patch stack, but I am not sure it gives the same review experience. If it rewinds the real vendored directory in place, the editor may show a reverse diff against the current committed vendored state, rather than a clean “upstream -> patched result” comparison.

    So my main request is not editor coupling specifically, and it does not have to be VS Code-only. The core request is: provide a simple command that prepares the project in a state where any Git-aware editor can show the full upstream-vs-patched diff as normal working tree changes.

    If there is already a way to do that with existing dfetch commands, I may have missed it.

  3. spoorcc commented on Jun 18, 2026

    @spoorcc
    Contributor

    Thanks for your feedback! I'm still thinking, but started a prototype to discover a workflow that works and fits for dfetch

  4. spoorcc commented on Jun 19, 2026

    @spoorcc
    Contributor

    There is a prototype ready in the linked pr. I would love some feedback:

    You can install it (in a venv preferentially) by doing

    pip install git+https://github.com/dfetch-org/dfetch@claude/review-patch-command-xyosoo
    • I'm still thinking about the name, and also considering your suggestion of reviewing it out of tree

    any feedback is welcome!

  5. sawachikaminoru commented on Jun 19, 2026

    @sawachikaminoru
    Author

    Thanks! This is exactly what I needed.

    I tested the prototype on my project with a few dfetch patch stacks and so far I have not noticed any unexpected behavior. Reviewing the result through git diff / VS Code is much easier than reading the patch files directly.

    One thing that could make this more flexible: when passing multiple projects, review-patch currently reviews them sequentially, pausing and restoring one project before moving to the next. That behavior makes sense as a safe default.

    Would you consider adding an explicit mode to review multiple projects in a single pass? Naming aside, something like:

    dfetch review-patch --mode combined project-a project-b
    # or, with no project names, all projects from dfetch.yaml in one pass
    dfetch review-patch --mode combined

    The idea would be to validate all selected projects first, stage the clean upstream state for all of them, apply all selected patch stacks, and then pause once. That would let a Git-aware editor show the full upstream-vs-patched diff across multiple vendored projects in a single review pass.

    I realize this may complicate restore/error handling, so I am not sure whether it fits the command design. But from a user perspective, it would be useful when reviewing the overall impact of several vendored patch stacks together.

    One small UX/documentation note: in VS Code it is important to review the regular "Changes" view, not "Staged Changes", because the staged diff is an implementation detail and appears reversed. Once I realized that, the workflow made sense.

    I expect I will use this feature a lot. I will report back if I run into any edge cases.

  6. spoorcc commented on Jun 23, 2026

    @spoorcc
    Contributor

    Thanks again for your feedback. I've updated the docs.

    I'm still pondering on a better name. I want to keep the flexibility to add edit capabilty to it. Naming always remains one of the hardest problems in software :).

    I'm also looking at your combined suggestion, my preference would be if 1 project given, then as-is currently, if 0 it means all projects with patches, else the number of projects given. For TUI a tree structure could make it possible to select any combination of patches applied to the projects:

      ← → step    ↑ ↓ switch project    Enter restore and exit    Ctrl-C abort
      ─────────────────────────────────────────────────────────────
      proj-a  [2/3 patches applied]
        [x] patches/00-fix.patch
        [x] patches/01-improve.patch
        [ ] patches/02-final.patch
    > proj-b  [0/2 patches applied]     ← focused (> marker)
        [ ] patches/00-base.patch
        [ ] patches/01-extra.patch
    

    For non-interactive mode a :N suffix in the project name could also be used:

    # Single project — unchanged behaviour
    dfetch review-patch proj-a
    dfetch review-patch --count 2 proj-a
    dfetch review-patch --interactive proj-a
    
    # Single project with :N shorthand (equivalent to --count N)
    dfetch review-patch proj-a:2
    
    # Combined non-interactive (2+ projects, pause once)
    dfetch review-patch                          # all projects, all patches
    dfetch review-patch proj-a proj-b            # two projects, all patches
    dfetch review-patch proj-a:0 proj-b proj-c:1 # 0 / all / 1 patches
    
    # Combined interactive tree TUI
    dfetch review-patch --interactive            # all projects, tree TUI
    dfetch review-patch --interactive proj-a proj-b
    
  7. ben-edna commented on Jun 23, 2026

    @ben-edna
    Contributor

    The command re-executes a sequence of patches against a clean upstream state to show their cumulative effect, then tears everything down and restores the original state on exit, so the name needs to describe that mechanical action rather than the goal of reviewing or inspecting, and it needs to read naturally with a --count argument since you're typically stepping through a subset of a multi-patch series rather than acting on one patch in isolation, which rules out singular phrasing like review-patch and argues for a plural like *-patches. Among the verb candidates, apply was the closest competitor but was rejected because in git's own vocabulary apply/am connote a persistent change, exactly the opposite of what this command does, since nothing should still be applied once it exits; try and simulate were considered for honestly signaling the non-destructive, temporary nature of the operation, but they describe the property of the execution (safe, reversible) rather than the execution itself, and don't capture that the patches are being run in sequence against a known base; step and run were too generic to convey that patches already exist and are being re-executed rather than freshly authored or run for the first time. replay was chosen because it's the one verb that simultaneously captures sequential re-execution (a patch series is replayed in order, like a log or a recording), implies a known, deterministic input being reproduced rather than something new being created, and is naturally compatible with stepping through a subset via --count (dfetch replay-patches my-project --count 3 reads as "replay the first three patches," not "review the first three" or "apply the first three," both of which carry the wrong implications). The resulting name, replay-patches, names the mechanical action rather than the user's goal, takes the plural naturally, and, while it doesn't explicitly assert the non-destructive/temporary behavior the way try-patches would, is close enough to that connotation (replaying something doesn't inherently commit it to permanent state) that the trade-off was judged acceptable in favor of better describing what the command actually does step by step.

  8. linked a pull request that will close this issueAdd replay-patches command #1290on Jun 24, 2026
  9. ben-edna commented on Jun 24, 2026

    @ben-edna
    Contributor

    The prototype branch was updated with the new name, combined mode

  10. sawachikaminoru commented on Jun 25, 2026

    @sawachikaminoru
    Author

    Thanks! I tested the updated prototype and everything worked as expected.

    I tried:

    • replaying a single project
    • stepping through its patches interactively
    • replaying multiple projects in a single combined review
    • using the per-project name:N syntax
    • using the combined interactive TUI
    • replaying all patched projects by omitting the project names

    The combined mode is exactly what I had in mind, and the repository is restored correctly when the command exits.

    The interactive tree also feels natural: switching projects with ↑/↓ and stepping through patches with ←/→ worked well.

    replay-patches now seems like a good name to me. It describes the operation more accurately than review-patch, especially now that the command can replay only part of each patch stack.

    I did not encounter any unexpected behavior in my tests. Thanks for implementing the suggestion!

  11. spoorcc commented on Jun 25, 2026

    @spoorcc
    Contributor

    Thank you for your feedback, i'm cleaning up the implementation a little bit, but will be part of 0.15.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions