diff --git a/src/env/node/git/sub-providers/staging.ts b/src/env/node/git/sub-providers/staging.ts index b247ab6ed3693..44cfc189f864c 100644 --- a/src/env/node/git/sub-providers/staging.ts +++ b/src/env/node/git/sub-providers/staging.ts @@ -78,11 +78,15 @@ export class StagingGitSubProvider implements GitStagingSubProvider { } @log() - async stageFiles(repoPath: string, pathOrUri: string[] | Uri[]): Promise { + async stageFiles( + repoPath: string, + pathOrUri: string[] | Uri[], + options?: { intentToAdd?: boolean }, + ): Promise { await this.git.exec( { cwd: repoPath }, 'add', - '-A', + options?.intentToAdd ? '-N' : '-A', '--', ...pathOrUri.map(p => (typeof p === 'string' ? p : splitPath(p, repoPath)[0])), ); diff --git a/src/git/gitProvider.ts b/src/git/gitProvider.ts index 462e3af94b83a..0245547ea384a 100644 --- a/src/git/gitProvider.ts +++ b/src/git/gitProvider.ts @@ -653,7 +653,7 @@ export interface DisposableTemporaryGitIndex extends UnifiedAsyncDisposable { export interface GitStagingSubProvider { createTemporaryIndex(repoPath: string, base: string): Promise; stageFile(repoPath: string, pathOrUri: string | Uri): Promise; - stageFiles(repoPath: string, pathOrUri: string[] | Uri[]): Promise; + stageFiles(repoPath: string, pathOrUri: string[] | Uri[], options?: { intentToAdd?: boolean }): Promise; stageDirectory(repoPath: string, directoryOrUri: string | Uri): Promise; unstageFile(repoPath: string, pathOrUri: string | Uri): Promise; unstageFiles(repoPath: string, pathOrUri: string[] | Uri[]): Promise; diff --git a/src/webviews/plus/composer/composerWebview.ts b/src/webviews/plus/composer/composerWebview.ts index 00a89ad1f4d60..2912099c6405c 100644 --- a/src/webviews/plus/composer/composerWebview.ts +++ b/src/webviews/plus/composer/composerWebview.ts @@ -76,6 +76,7 @@ import { ReloadComposerCommand, } from './protocol'; import type { ComposerWebviewShowingArgs } from './registration'; +import type { WorkingTreeDiffs } from './utils'; import { convertToComposerDiffInfo, createHunksFromDiffs, @@ -101,6 +102,9 @@ export class ComposerWebviewProvider implements WebviewProvider, @@ -283,6 +287,17 @@ export class ComposerWebviewProvider implements WebviewProvider { + // Stop repo change subscription so we can deal with untracked files + this._repositorySubscription?.dispose(); + const status = await repo.git.status?.getStatus(); + const untrackedPaths = status?.untrackedChanges.map(f => f.path); + if (untrackedPaths?.length) { + try { + await repo.git.staging?.stageFiles(untrackedPaths, { intentToAdd: true }); + this._ignoreIndexChange = true; + } catch {} + } + const [diffsResult, commitResult, branchResult] = await Promise.allSettled([ // Handle baseCommit - could be string (old format) or ComposerBaseCommit (new format) getWorkingTreeDiffs(repo), @@ -290,6 +305,12 @@ export class ComposerWebviewProvider implements WebviewProvider { if (e.repository.id !== this._currentRepository?.id) return; - + const ignoreIndexChange = this._ignoreIndexChange; + this._ignoreIndexChange = false; // Only care about index changes (staged/unstaged changes) - if (!e.changed(RepositoryChange.Index, RepositoryChangeComparisonMode.Any)) { + if ( + !e.changed(RepositoryChange.Index, RepositoryChangeComparisonMode.Any) || + (ignoreIndexChange && e.changed(RepositoryChange.Index, RepositoryChangeComparisonMode.Exclusive)) + ) { return; } @@ -1055,7 +1080,26 @@ export class ComposerWebviewProvider implements WebviewProvider f.path); + if (untrackedPaths?.length) { + try { + workingTreeDiffs = await getWorkingTreeDiffs(repo); + await repo.git.staging?.stageFiles(untrackedPaths); + } catch {} + } + } + + const validation = await validateSafetyState( + repo, + params.safetyState, + hunksBeingCommitted, + workingTreeDiffs, + ); if (!validation.isValid) { // Clear loading state and show safety error await this.host.notify(DidFinishCommittingNotification, undefined); @@ -1164,7 +1208,7 @@ export class ComposerWebviewProvider implements WebviewProvider { const errors: string[] = []; @@ -378,7 +379,7 @@ export async function validateSafetyState( // 2. Smart diff validation - only check diffs for sources being committed if (hunksBeingCommitted?.length) { - const { staged, unstaged /*, unified*/ } = await getWorkingTreeDiffs(repo); + const { staged, unstaged /*, unified*/ } = workingTreeDiffs ?? (await getWorkingTreeDiffs(repo)); const hashes = { staged: staged?.contents ? await sha256(staged.contents) : null,