-
Notifications
You must be signed in to change notification settings - Fork 2.3k
fix: check staged changes before committing to prevent 'nothing to commit' error #2991
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
2008d4c
5b6b69d
fe3c952
fb1d387
ac702ae
d343737
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 |
|---|---|---|
|
|
@@ -26,6 +26,7 @@ import { | |
| gitRemove, | ||
| gitAddAll, | ||
| getGitUncommittedFiles, | ||
| hasStagedChanges, | ||
| } from "../utils/git_utils"; | ||
| import { readSettings } from "@/main/settings"; | ||
| import { writeMigrationFile } from "../utils/file_utils"; | ||
|
|
@@ -558,45 +559,59 @@ export async function processFullResponseActions( | |
| let message = chatSummary | ||
| ? `[dyad] ${chatSummary} - ${changes.join(", ")}` | ||
| : `[dyad] ${changes.join(", ")}`; | ||
| // Use chat summary, if provided, or default for commit message | ||
| let commitHash = await gitCommit({ | ||
| path: appPath, | ||
| message, | ||
| }); | ||
| logger.log(`Successfully committed changes: ${changes.join(", ")}`); | ||
|
|
||
| // Check for any uncommitted changes after the commit | ||
| uncommittedFiles = await getGitUncommittedFiles({ path: appPath }); | ||
| // Verify there are actual staged changes before committing. | ||
| // Files may be "written" with identical content (e.g. regenerated by the AI), | ||
| // resulting in no actual git diff and causing "nothing to commit" errors. | ||
| // We check staged changes specifically (not the entire working tree) to avoid | ||
| // false positives from unrelated unstaged/untracked files. | ||
| const staged = await hasStagedChanges({ path: appPath }); | ||
|
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. P2: Don't derive Prompt for AI agents |
||
| if (!staged) { | ||
| logger.log( | ||
| "No actual git changes detected after staging (files may have been rewritten with identical content), skipping commit", | ||
| ); | ||
| hasChanges = false; | ||
| } else { | ||
| // Use chat summary, if provided, or default for commit message | ||
| let commitHash = await gitCommit({ | ||
| path: appPath, | ||
| message, | ||
| }); | ||
| logger.log(`Successfully committed changes: ${changes.join(", ")}`); | ||
|
|
||
| if (uncommittedFiles.length > 0) { | ||
| // Stage all changes | ||
| await gitAddAll({ path: appPath }); | ||
| try { | ||
| commitHash = await gitCommit({ | ||
| path: appPath, | ||
| message: message + " + extra files edited outside of Dyad", | ||
| amend: true, | ||
| }); | ||
| logger.log( | ||
| `Amend commit with changes outside of dyad: ${uncommittedFiles.join(", ")}`, | ||
| ); | ||
| } catch (error) { | ||
| // Just log, but don't throw an error because the user can still | ||
| // commit these changes outside of Dyad if needed. | ||
| logger.error( | ||
| `Failed to commit changes outside of dyad: ${uncommittedFiles.join(", ")}`, | ||
| ); | ||
| extraFilesError = (error as any).toString(); | ||
| // Check for any uncommitted changes after the commit | ||
| uncommittedFiles = await getGitUncommittedFiles({ path: appPath }); | ||
|
|
||
| if (uncommittedFiles.length > 0) { | ||
| // Stage all changes | ||
| await gitAddAll({ path: appPath }); | ||
| try { | ||
| commitHash = await gitCommit({ | ||
| path: appPath, | ||
| message: message + " + extra files edited outside of Dyad", | ||
| amend: true, | ||
| }); | ||
| logger.log( | ||
| `Amend commit with changes outside of dyad: ${uncommittedFiles.join(", ")}`, | ||
| ); | ||
| } catch (error) { | ||
| // Just log, but don't throw an error because the user can still | ||
| // commit these changes outside of Dyad if needed. | ||
| logger.error( | ||
| `Failed to commit changes outside of dyad: ${uncommittedFiles.join(", ")}`, | ||
| ); | ||
| extraFilesError = (error as any).toString(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Save the commit hash to the message | ||
| await db | ||
| .update(messages) | ||
| .set({ | ||
| commitHash: commitHash, | ||
| }) | ||
| .where(eq(messages.id, messageId)); | ||
| // Save the commit hash to the message | ||
| await db | ||
| .update(messages) | ||
| .set({ | ||
| commitHash: commitHash, | ||
| }) | ||
| .where(eq(messages.id, messageId)); | ||
| } | ||
| } | ||
| logger.log("mark as approved: hasChanges", hasChanges); | ||
| // Update the message to approved | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -259,6 +259,29 @@ export async function isGitStatusClean({ | |
| } | ||
| } | ||
|
|
||
| export async function hasStagedChanges({ | ||
| path, | ||
| }: { | ||
| path: string; | ||
| }): Promise<boolean> { | ||
| const settings = readSettings(); | ||
| if (settings.enableNativeGit) { | ||
| // git diff --cached --quiet exits with 1 if there are staged changes, 0 if none | ||
| const result = await execGit(["diff", "--cached", "--quiet"], path); | ||
| if (result.exitCode !== 0 && result.exitCode !== 1) { | ||
| throw new Error( | ||
| `Failed to check staged changes: ${result.stderr.trim() || result.stdout.trim()}`, | ||
| ); | ||
| } | ||
| return result.exitCode === 1; | ||
| } else { | ||
| const statusMatrix = await git.statusMatrix({ fs, dir: path }); | ||
| // row[1] = HEAD status, row[3] = stage status | ||
| // If stage differs from HEAD, there are staged changes | ||
| return statusMatrix.some((row) => row[3] !== row[1]); | ||
|
Comment on lines
+278
to
+281
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. The logic const statusMatrix = await git.statusMatrix({ fs, dir: path });
// row[1] = HEAD status, row[3] = stage status.
// If stage differs from HEAD, there are staged changes. This works because of the
// status codes used by isomorphic-git's statusMatrix:
// - head: 0=absent, 1=present
// - stage: 0=absent, 1=identical to HEAD, 2=added, 3=modified
// This correctly covers new (0 vs 2), modified (1 vs 3), and deleted (1 vs 0)
// staged files, while ignoring unstaged changes.
return statusMatrix.some((row) => row[3] !== row[1]);References
|
||
| } | ||
| } | ||
|
|
||
| export async function gitCommit({ | ||
| path, | ||
| message, | ||
|
|
||
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.
While this mock is necessary, the pull request would be strengthened by adding a new test case to verify the new behavior. Specifically, a test that mocks
hasStagedChangesto returnfalseand asserts thatgitCommitis not called, ensuring the commit-skipping logic is working as intended.