Repository navigation
fix(server): Commit refuses while a merge still has conflict markers #35132
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e2effba
6d66290
e4f909c
fae1611
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2543,6 +2543,78 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* | |||||||||||||||||||||
| }, | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| } else { | ||||||||||||||||||||||
| // add -A marks unmerged paths resolved, so refuse while one still has conflict markers. | ||||||||||||||||||||||
| const unmergedPaths = (yield* runGitStdoutWithOptions( | ||||||||||||||||||||||
| "GitVcsDriver.commit.unmergedPaths", | ||||||||||||||||||||||
| cwd, | ||||||||||||||||||||||
| ["-c", "diff.relative=false", "diff", "--name-only", "--diff-filter=U", "-z"], | ||||||||||||||||||||||
| env ? { env } : {}, | ||||||||||||||||||||||
| )) | ||||||||||||||||||||||
| .split("\0") | ||||||||||||||||||||||
| .filter((file) => file.length > 0); | ||||||||||||||||||||||
| if (unmergedPaths.length > 0) { | ||||||||||||||||||||||
| const check = yield* executeGit( | ||||||||||||||||||||||
| "GitVcsDriver.commit.conflictMarkers", | ||||||||||||||||||||||
| cwd, | ||||||||||||||||||||||
| [ | ||||||||||||||||||||||
| "-c", | ||||||||||||||||||||||
| "diff.relative=false", | ||||||||||||||||||||||
| "diff", | ||||||||||||||||||||||
| "--check", | ||||||||||||||||||||||
| "--no-color", | ||||||||||||||||||||||
| "--", | ||||||||||||||||||||||
| ...unmergedPaths.map((file) => `:(top,literal)${file}`), | ||||||||||||||||||||||
| ], | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| ...(env ? { env } : {}), | ||||||||||||||||||||||
| allowNonZeroExit: true, | ||||||||||||||||||||||
| maxOutputBytes: REVIEW_METADATA_MAX_OUTPUT_BYTES, | ||||||||||||||||||||||
| appendTruncationMarker: true, | ||||||||||||||||||||||
| }, | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| const reports = check.stdout.split("\n"); | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 High When + if (check.stdoutTruncated) {
+ return yield* new GitCommandError({
+ operation: "GitVcsDriver.commit",
+ command: "git commit",
+ cwd,
+ detail: "Cannot verify unresolved merge conflicts because conflict-check output was truncated.",
+ });
+ }🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fail closed when the conflict-check output is truncated.
Suggested fix
Committable suggestion
|
||||||||||||||||||||||
| const conflicted: string[] = []; | ||||||||||||||||||||||
| let topLevel: string | undefined; | ||||||||||||||||||||||
| for (const file of unmergedPaths) { | ||||||||||||||||||||||
| const markerLines = new Set( | ||||||||||||||||||||||
| reports.flatMap((line) => { | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Medium A commit with many unmerged files and diagnostic lines can synchronously stall the server because this rescans all of 🚀 Reply "fix it for me" or copy this AI Prompt for your agent: |
||||||||||||||||||||||
| const match = line.startsWith(`${file}:`) | ||||||||||||||||||||||
| ? /^(\d+): leftover conflict marker$/.exec(line.slice(file.length + 1)) | ||||||||||||||||||||||
|
Comment on lines
+2581
to
+2582
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Handle filenames containing newlines in conflict reports. For an unmerged file named
|
||||||||||||||||||||||
| : null; | ||||||||||||||||||||||
| return match ? [Number(match[1]) - 1] : []; | ||||||||||||||||||||||
| }), | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| if (markerLines.size === 0) continue; | ||||||||||||||||||||||
| topLevel ??= (yield* runGitStdout("GitVcsDriver.commit.topLevel", cwd, [ | ||||||||||||||||||||||
| "rev-parse", | ||||||||||||||||||||||
| "--show-toplevel", | ||||||||||||||||||||||
| ])).trim(); | ||||||||||||||||||||||
| const lines = (yield* fileSystem.readFileString(path.join(topLevel, file)).pipe( | ||||||||||||||||||||||
| Effect.mapError( | ||||||||||||||||||||||
| (cause) => | ||||||||||||||||||||||
| new GitCommandError({ | ||||||||||||||||||||||
| operation: "GitVcsDriver.commit.conflictMarkers", | ||||||||||||||||||||||
| command: "git commit", | ||||||||||||||||||||||
| cwd, | ||||||||||||||||||||||
| detail: `Could not check ${file} for conflict markers.`, | ||||||||||||||||||||||
| cause, | ||||||||||||||||||||||
| }), | ||||||||||||||||||||||
| ), | ||||||||||||||||||||||
| )).split("\n"); | ||||||||||||||||||||||
| // Git also reports a lone "=======", which is valid content such as a heading underline. | ||||||||||||||||||||||
| const reported = (marker: string) => | ||||||||||||||||||||||
| lines.some((line, index) => markerLines.has(index) && line.startsWith(marker)); | ||||||||||||||||||||||
| if (reported("<") && reported(">")) conflicted.push(file); | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reject a reported opening or closing boundary on its own. If a user removes
|
||||||||||||||||||||||
| } | ||||||||||||||||||||||
| if (conflicted.length > 0) { | ||||||||||||||||||||||
| return yield* new GitCommandError({ | ||||||||||||||||||||||
| operation: "GitVcsDriver.commit", | ||||||||||||||||||||||
| command: "git commit", | ||||||||||||||||||||||
| cwd, | ||||||||||||||||||||||
| detail: `Cannot commit with unresolved merge conflicts in ${conflicted.join(", ")}. Resolve them, then commit.`, | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bound the conflicted-path list in the error. A merge with many conflicted paths puts the entire list in
|
||||||||||||||||||||||
| }); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| yield* runGit( | ||||||||||||||||||||||
| "GitVcsDriver.commit.addAll", | ||||||||||||||||||||||
| cwd, | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Validate the content that will be staged.
If an editor or agent writes conflict markers after
git diff --checkfinishes but beforegit add -Aruns, the guard checks the old content and Git stages the new content. The subsequent commit can record those markers. Validate the staged snapshot while preserving the unmerged index when validation fails. Git stages file content at the timegit addruns. (git-scm.com)