Skip to content

Commit 6b50a10

Browse files
Adds support for untracked files in composer
1 parent 7374dff commit 6b50a10

File tree

4 files changed

+57
-8
lines changed

4 files changed

+57
-8
lines changed

src/env/node/git/sub-providers/staging.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,15 @@ export class StagingGitSubProvider implements GitStagingSubProvider {
7878
}
7979

8080
@log()
81-
async stageFiles(repoPath: string, pathOrUri: string[] | Uri[]): Promise<void> {
81+
async stageFiles(
82+
repoPath: string,
83+
pathOrUri: string[] | Uri[],
84+
options?: { intentToAdd?: boolean },
85+
): Promise<void> {
8286
await this.git.exec(
8387
{ cwd: repoPath },
8488
'add',
85-
'-A',
89+
options?.intentToAdd ? '-N' : '-A',
8690
'--',
8791
...pathOrUri.map(p => (typeof p === 'string' ? p : splitPath(p, repoPath)[0])),
8892
);

src/git/gitProvider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -653,7 +653,7 @@ export interface DisposableTemporaryGitIndex extends UnifiedAsyncDisposable {
653653
export interface GitStagingSubProvider {
654654
createTemporaryIndex(repoPath: string, base: string): Promise<DisposableTemporaryGitIndex>;
655655
stageFile(repoPath: string, pathOrUri: string | Uri): Promise<void>;
656-
stageFiles(repoPath: string, pathOrUri: string[] | Uri[]): Promise<void>;
656+
stageFiles(repoPath: string, pathOrUri: string[] | Uri[], options?: { intentToAdd?: boolean }): Promise<void>;
657657
stageDirectory(repoPath: string, directoryOrUri: string | Uri): Promise<void>;
658658
unstageFile(repoPath: string, pathOrUri: string | Uri): Promise<void>;
659659
unstageFiles(repoPath: string, pathOrUri: string[] | Uri[]): Promise<void>;

src/webviews/plus/composer/composerWebview.ts

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ import {
7676
ReloadComposerCommand,
7777
} from './protocol';
7878
import type { ComposerWebviewShowingArgs } from './registration';
79+
import type { WorkingTreeDiffs } from './utils';
7980
import {
8081
convertToComposerDiffInfo,
8182
createHunksFromDiffs,
@@ -101,6 +102,9 @@ export class ComposerWebviewProvider implements WebviewProvider<State, State, Co
101102
// Telemetry context - tracks composer-specific data for getTelemetryContext
102103
private _context: ComposerContext;
103104

105+
// Flag to ignore index change tracking for when we need to stage untracked files
106+
private _ignoreIndexChange = false;
107+
104108
constructor(
105109
protected readonly container: Container,
106110
protected readonly host: WebviewHost<'gitlens.composer'>,
@@ -283,13 +287,30 @@ export class ComposerWebviewProvider implements WebviewProvider<State, State, Co
283287
source?: Sources,
284288
isReload?: boolean,
285289
): Promise<State> {
290+
// Stop repo change subscription so we can deal with untracked files
291+
this._repositorySubscription?.dispose();
292+
const status = await repo.git.status?.getStatus();
293+
const untrackedPaths = status?.untrackedChanges.map(f => f.path);
294+
if (untrackedPaths?.length) {
295+
try {
296+
await repo.git.staging?.stageFiles(untrackedPaths, { intentToAdd: true });
297+
this._ignoreIndexChange = true;
298+
} catch {}
299+
}
300+
286301
const [diffsResult, commitResult, branchResult] = await Promise.allSettled([
287302
// Handle baseCommit - could be string (old format) or ComposerBaseCommit (new format)
288303
getWorkingTreeDiffs(repo),
289304
repo.git.commits.getCommit('HEAD'),
290305
repo.git.branches.getBranch(),
291306
]);
292307

308+
if (untrackedPaths?.length) {
309+
try {
310+
await repo.git.staging?.unstageFiles(untrackedPaths);
311+
} catch {}
312+
}
313+
293314
const diffs = getSettledValue(diffsResult)!;
294315

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

742763
private async onRepositoryChanged(e: RepositoryChangeEvent): Promise<void> {
743764
if (e.repository.id !== this._currentRepository?.id) return;
744-
765+
const ignoreIndexChange = this._ignoreIndexChange;
766+
this._ignoreIndexChange = false;
745767
// Only care about index changes (staged/unstaged changes)
746-
if (!e.changed(RepositoryChange.Index, RepositoryChangeComparisonMode.Any)) {
768+
if (
769+
!e.changed(RepositoryChange.Index, RepositoryChangeComparisonMode.Any) ||
770+
(ignoreIndexChange && e.changed(RepositoryChange.Index, RepositoryChangeComparisonMode.Exclusive))
771+
) {
747772
return;
748773
}
749774

@@ -1055,7 +1080,26 @@ export class ComposerWebviewProvider implements WebviewProvider<State, State, Co
10551080
);
10561081

10571082
// Validate repository safety state before proceeding
1058-
const validation = await validateSafetyState(repo, params.safetyState, hunksBeingCommitted);
1083+
// Stop repo change subscription so we can deal with untracked files
1084+
let workingTreeDiffs: WorkingTreeDiffs | undefined;
1085+
if (this._context.diff.unstagedIncluded) {
1086+
this._repositorySubscription?.dispose();
1087+
const status = await repo.git.status?.getStatus();
1088+
const untrackedPaths = status?.untrackedChanges.map(f => f.path);
1089+
if (untrackedPaths?.length) {
1090+
try {
1091+
workingTreeDiffs = await getWorkingTreeDiffs(repo);
1092+
await repo.git.staging?.stageFiles(untrackedPaths);
1093+
} catch {}
1094+
}
1095+
}
1096+
1097+
const validation = await validateSafetyState(
1098+
repo,
1099+
params.safetyState,
1100+
hunksBeingCommitted,
1101+
workingTreeDiffs,
1102+
);
10591103
if (!validation.isValid) {
10601104
// Clear loading state and show safety error
10611105
await this.host.notify(DidFinishCommittingNotification, undefined);
@@ -1164,7 +1208,7 @@ export class ComposerWebviewProvider implements WebviewProvider<State, State, Co
11641208
if (
11651209
stashCommit &&
11661210
stashCommit.ref !== previousStashCommit?.ref &&
1167-
stashCommit.message === stashMessage
1211+
stashCommit.message?.includes(stashMessage)
11681212
) {
11691213
stashedSuccessfully = true;
11701214
}

src/webviews/plus/composer/utils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,7 @@ export async function validateSafetyState(
360360
repo: Repository,
361361
safetyState: ComposerSafetyState,
362362
hunksBeingCommitted?: ComposerHunk[],
363+
workingTreeDiffs?: WorkingTreeDiffs,
363364
): Promise<{ isValid: boolean; errors: string[] }> {
364365
const errors: string[] = [];
365366

@@ -378,7 +379,7 @@ export async function validateSafetyState(
378379

379380
// 2. Smart diff validation - only check diffs for sources being committed
380381
if (hunksBeingCommitted?.length) {
381-
const { staged, unstaged /*, unified*/ } = await getWorkingTreeDiffs(repo);
382+
const { staged, unstaged /*, unified*/ } = workingTreeDiffs ?? (await getWorkingTreeDiffs(repo));
382383

383384
const hashes = {
384385
staged: staged?.contents ? await sha256(staged.contents) : null,

0 commit comments

Comments
 (0)