Fall back to original base commit when git am --3way fails due to merge conflicts#23132
Fall back to original base commit when git am --3way fails due to merge conflicts#23132
Conversation
When git am --3way fails due to merge conflicts (intervening commits to the target branch), fall back to creating the PR branch at the original base commit. The patch is then applied cleanly at that earlier commit, and the resulting PR will show merge conflicts in GitHub for manual resolution. This implements Option 2 (try --3way first) with fallback to Option 1 (apply at original base commit) as discussed in the issue. The original base commit is found by: 1. Parsing the "From <sha1>" header of the first patch in the series 2. Resolving the parent commit with git rev-parse <sha>^ 3. Verifying it exists locally (fails gracefully for cross-repo cases) Agent-Logs-Url: https://github.com/github/gh-aw/sessions/0f83490d-837a-47a6-bd2c-e11e6563085e Co-authored-by: dsyme <7204669+dsyme@users.noreply.github.com>
- Rename PATCH_COMMIT_SHA/PARENT_SHA to MOCK_PATCH_COMMIT_SHA/MOCK_PARENT_COMMIT_SHA - Use single template literal instead of string concatenation in warning - Replace fragile negative lookahead regex with explicit /^git am [^-]/ pattern Agent-Logs-Url: https://github.com/github/gh-aw/sessions/0f83490d-837a-47a6-bd2c-e11e6563085e Co-authored-by: dsyme <7204669+dsyme@users.noreply.github.com>
| } | ||
|
|
||
| return { success: false, error: "Failed to apply patch" }; | ||
| // Abort the failed git am before attempting any fallback |
There was a problem hiding this comment.
@copilot Add brief docs to the Safe Outputs (Pull Requests) doc page about how, at a high level, gh-aw agentic workflows take the change generated by the coding agent and create or update a pull request. Cover what happens if the target branch has changed, if there are conflicts, if there are files that are protected, if there are .github/workflows files being changed.
There was a problem hiding this comment.
Added a "How PR creation works" section to the Safe Outputs (Pull Requests) page in commit dfece51. It covers:
- The end-to-end mechanism:
git format-patch→git am --3way→ push → GitHub API creates PR - What happens when the target branch has changed — clean merge succeeds normally; unresolvable conflicts fall back to applying the patch at the original base commit so GitHub surfaces the conflicts for manual resolution
- The cross-repo caveat (original base commit fallback requires the commit to be present locally)
- What happens with
.github/workflowschanges —.github/is protected by default and either hard-blocks or falls back to a review issue depending on the configuredprotected-filespolicy
|
Adding some docs |
Explains the git patch mechanism, what happens when the target branch has changed (no conflicts vs merge conflicts with fallback), and what happens when .github/workflows files are modified (protected files). Agent-Logs-Url: https://github.com/github/gh-aw/sessions/5a1f5ac0-7653-4770-8fb5-92b991bccb60 Co-authored-by: dsyme <7204669+dsyme@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Improves safe-output PR creation resiliency by attempting to still create a pull request even when git am --3way fails due to merge conflicts, so GitHub can surface conflicts for manual resolution.
Changes:
- Add a fallback path in
create_pull_request.cjsaftergit am --3wayfailure: abortgit am, attempt to recreate the PR branch at an “original base” commit, and re-apply the patch without--3way. - Add new tests intended to cover fallback success, double-failure, and cross-repo base-SHA-unavailable scenarios.
- Document the PR creation flow and the intended conflict-handling fallback behavior.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| docs/src/content/docs/reference/safe-outputs-pull-requests.md | Documents patch application flow and the intended fallback behavior when the base branch has advanced. |
| actions/setup/js/create_pull_request.cjs | Implements the fallback logic after git am --3way failure. |
| actions/setup/js/create_pull_request.test.cjs | Adds tests covering the new fallback path. |
Comments suppressed due to low confidence (2)
actions/setup/js/create_pull_request.cjs:775
- The fallback path also interpolates
patchFilePathintogit am ${patchFilePath}. For the same reasons as the primary apply path, prefer callingexec.exec("git", ["am", patchFilePath])so the patch path can’t be misparsed by shell tokenization.
// Apply the patch without --3way; we are on the correct base so it should apply cleanly
await exec.exec(`git am ${patchFilePath}`);
core.info("Patch applied successfully at original base commit");
docs/src/content/docs/reference/safe-outputs-pull-requests.md:29
- The doc states the job can “fall back to applying the patch at the commit the agent originally branched from”. As implemented, the fallback currently tries to infer that base from the patch’s
From <sha>header, which won’t be resolvable in the target checkout (the commit object doesn’t exist there). Once the implementation is corrected (e.g., by recording the base commit alongside the patch or embedding abase-commit:header), please ensure this section reflects the actual source of truth for the original base commit.
max: 3 # max PRs per run (default: 1)
expires: 14 # auto-close after 14 days (same-repo only)
if-no-changes: "warn" # "warn" (default), "error", or "ignore"
target-repo: "owner/repo" # cross-repository
allowed-repos: ["org/repo1", "org/repo2"] # additional allowed repositories
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| getExecOutput: vi.fn().mockImplementation((cmd, args) => { | ||
| // Return the parent SHA for git rev-parse calls | ||
| if (cmd === "git" && Array.isArray(args) && args[0] === "rev-parse") { | ||
| return Promise.resolve({ exitCode: 0, stdout: `${MOCK_PARENT_COMMIT_SHA}\n`, stderr: "" }); | ||
| } |
There was a problem hiding this comment.
These tests assume the SHA from the patch From <sha> header can be used with git rev-parse <sha>^ in the PR-creation job. In practice that <sha> refers to the agent’s new commit object, which won’t exist in the clean target checkout, so rev-parse will fail and this test would not represent real behavior. Once the implementation records the base commit explicitly (or parses a base-commit: header), adjust the mocks/assertions to match that mechanism rather than mocking rev-parse success for an unknown commit.
| getExecOutput: vi.fn().mockImplementation((cmd, args) => { | |
| // Return the parent SHA for git rev-parse calls | |
| if (cmd === "git" && Array.isArray(args) && args[0] === "rev-parse") { | |
| return Promise.resolve({ exitCode: 0, stdout: `${MOCK_PARENT_COMMIT_SHA}\n`, stderr: "" }); | |
| } | |
| getExecOutput: vi.fn().mockImplementation((_cmd, _args) => { | |
| // Return a generic successful result for commands that use getExecOutput. | |
| // We deliberately do not special-case "git rev-parse" here, to avoid | |
| // assuming that the patch's "From <sha>" commit exists in the clean checkout. |
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
The fallback in create_pull_request.cjs tried to derive the base commit by parsing the From <sha> header from git format-patch output and running git rev-parse <sha>^. That SHA is the agent's new commit which doesn't exist in the clean target checkout, so rev-parse always fails and the fallback never activates. Fix: record the resolved base commit SHA explicitly in generate_git_patch.cjs (where it's known), pass it through the safe outputs entry as base_commit, and use it directly in the create_pull_request fallback path.
PR creation was failing outright when
git am --3waycouldn't resolve conflicts caused by intervening commits on the target branch. Instead of failing, the PR should still be created at the original base commit so GitHub can surface the conflicts for manual resolution.Changes
create_pull_request.cjs: Ongit am --3wayfailure, instead of immediately returning an error:git amFrom <sha1>header in the patch filegit rev-parse <sha>^) — this is the original base the patch was generated from--3waycreate_pull_request.test.cjs: Three new tests covering the fallback path — success via fallback, double failure, and cross-repo SHA unavailable.docs/src/content/docs/reference/safe-outputs-pull-requests.md: Added a "How PR creation works" section explaining the end-to-end git patch mechanism, what happens when the target branch has changed (clean merge vs. conflict fallback), the cross-repo limitation, and how.github/workflowschanges are handled via the protected files policy.✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.