Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/env/node/git/sub-providers/staging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,15 @@ export class StagingGitSubProvider implements GitStagingSubProvider {
}

@log()
async stageFiles(repoPath: string, pathOrUri: string[] | Uri[]): Promise<void> {
async stageFiles(
repoPath: string,
pathOrUri: string[] | Uri[],
options?: { intentToAdd?: boolean },
): Promise<void> {
await this.git.exec(
{ cwd: repoPath },
'add',
'-A',
options?.intentToAdd ? '-N' : '-A',
'--',
...pathOrUri.map(p => (typeof p === 'string' ? p : splitPath(p, repoPath)[0])),
);
Expand Down
2 changes: 1 addition & 1 deletion src/git/gitProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,7 @@ export interface DisposableTemporaryGitIndex extends UnifiedAsyncDisposable {
export interface GitStagingSubProvider {
createTemporaryIndex(repoPath: string, base: string): Promise<DisposableTemporaryGitIndex>;
stageFile(repoPath: string, pathOrUri: string | Uri): Promise<void>;
stageFiles(repoPath: string, pathOrUri: string[] | Uri[]): Promise<void>;
stageFiles(repoPath: string, pathOrUri: string[] | Uri[], options?: { intentToAdd?: boolean }): Promise<void>;
stageDirectory(repoPath: string, directoryOrUri: string | Uri): Promise<void>;
unstageFile(repoPath: string, pathOrUri: string | Uri): Promise<void>;
unstageFiles(repoPath: string, pathOrUri: string[] | Uri[]): Promise<void>;
Expand Down
52 changes: 48 additions & 4 deletions src/webviews/plus/composer/composerWebview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ import {
ReloadComposerCommand,
} from './protocol';
import type { ComposerWebviewShowingArgs } from './registration';
import type { WorkingTreeDiffs } from './utils';
import {
convertToComposerDiffInfo,
createHunksFromDiffs,
Expand All @@ -101,6 +102,9 @@ export class ComposerWebviewProvider implements WebviewProvider<State, State, Co
// Telemetry context - tracks composer-specific data for getTelemetryContext
private _context: ComposerContext;

// Flag to ignore index change tracking for when we need to stage untracked files
private _ignoreIndexChange = false;

constructor(
protected readonly container: Container,
protected readonly host: WebviewHost<'gitlens.composer'>,
Expand Down Expand Up @@ -283,13 +287,30 @@ export class ComposerWebviewProvider implements WebviewProvider<State, State, Co
source?: Sources,
isReload?: boolean,
): Promise<State> {
// 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),
repo.git.commits.getCommit('HEAD'),
repo.git.branches.getBranch(),
]);

if (untrackedPaths?.length) {
try {
await repo.git.staging?.unstageFiles(untrackedPaths);
} catch {}
}

const diffs = getSettledValue(diffsResult)!;

this._context.diff.unstagedIncluded = false;
Expand Down Expand Up @@ -741,9 +762,13 @@ export class ComposerWebviewProvider implements WebviewProvider<State, State, Co

private async onRepositoryChanged(e: RepositoryChangeEvent): Promise<void> {
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;
}

Expand Down Expand Up @@ -1055,7 +1080,26 @@ export class ComposerWebviewProvider implements WebviewProvider<State, State, Co
);

// Validate repository safety state before proceeding
const validation = await validateSafetyState(repo, params.safetyState, hunksBeingCommitted);
// Stop repo change subscription so we can deal with untracked files
let workingTreeDiffs: WorkingTreeDiffs | undefined;
if (this._context.diff.unstagedIncluded) {
this._repositorySubscription?.dispose();
const status = await repo.git.status?.getStatus();
const untrackedPaths = status?.untrackedChanges.map(f => 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);
Expand Down Expand Up @@ -1164,7 +1208,7 @@ export class ComposerWebviewProvider implements WebviewProvider<State, State, Co
if (
stashCommit &&
stashCommit.ref !== previousStashCommit?.ref &&
stashCommit.message === stashMessage
stashCommit.message?.includes(stashMessage)
) {
stashedSuccessfully = true;
}
Expand Down
3 changes: 2 additions & 1 deletion src/webviews/plus/composer/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ export async function validateSafetyState(
repo: Repository,
safetyState: ComposerSafetyState,
hunksBeingCommitted?: ComposerHunk[],
workingTreeDiffs?: WorkingTreeDiffs,
): Promise<{ isValid: boolean; errors: string[] }> {
const errors: string[] = [];

Expand All @@ -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,
Expand Down