Skip to content

safe-outputs: preserve merge/rebase commit history in code-push flow #19982

@strawgate

Description

@strawgate

Problem

safe-outputs.create-pull-request and safe-outputs.push-to-pull-request-branch currently transport changes via git format-patch ... --stdout and apply via git am --3way.

This loses merge topology and can drop merge-resolution-only content. It also does not preserve per-commit metadata robustly for complex histories.

Why this matters

For PR-update workflows, we need to preserve:

  • merge commits and their resolved content
  • per-commit messages/authorship
  • commit graph fidelity when possible

Current behavior is good for linear histories, but not for merge-heavy or rebased histories.

Current implementation

Patch generation is in actions/setup/js/generate_git_patch.cjs and uses format-patch for both full and incremental modes.
Application is in:

  • actions/setup/js/create_pull_request.cjs (git am --3way)
  • actions/setup/js/push_to_pull_request_branch.cjs (git am --3way)

Minimal implementation proposal

Introduce a commit-object transport path using git bundle and keep current patch flow as fallback.

MVP scope

  1. Add actions/setup/js/generate_git_bundle.cjs

    • Inputs similar to generate_git_patch.cjs: branchName, baseBranch, mode, cwd, repoSlug
    • Outputs bundle file path in /tmp/gh-aw/
    • Range semantics:
      • full mode: <baseRef>..<branchName>
      • incremental mode: origin/<branchName>..<branchName>
  2. In safe_outputs_handlers.cjs

    • Include bundle_path in safe-output entries (alongside patch_path for compatibility)
  3. In create_pull_request.cjs

    • If bundle_path exists, fetch from bundle and push branch from fetched tip
    • Skip git am in bundle path
  4. In push_to_pull_request_branch.cjs

    • If bundle_path exists, fetch from bundle and push ref to target branch
    • Keep current non-fast-forward behavior unchanged for MVP

Non-goals (MVP)

  • force-push support
  • policy changes for rebases/non-fast-forward
  • removing patch mode entirely

Acceptance criteria

  • Merge commit with manual conflict-resolution-only content is preserved after safe-output push
  • Per-commit messages and authorship are preserved
  • Existing linear workflows continue to pass
  • Non-fast-forward behavior remains unchanged (still fails without force)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions