Skip to content

Commit 558cce1

Browse files
Adds support for untracked files in composer (#4653)
* Adds support for untracked files in composer * Shortens try/catch block
1 parent 47038c9 commit 558cce1

File tree

4 files changed

+55
-8
lines changed

4 files changed

+55
-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: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ import {
7878
ReloadComposerCommand,
7979
} from './protocol';
8080
import type { ComposerWebviewShowingArgs } from './registration';
81+
import type { WorkingTreeDiffs } from './utils';
8182
import {
8283
convertToComposerDiffInfo,
8384
createCombinedDiffForCommit,
@@ -108,6 +109,9 @@ export class ComposerWebviewProvider implements WebviewProvider<State, State, Co
108109
// Telemetry context - tracks composer-specific data for getTelemetryContext
109110
private _context: ComposerContext;
110111

112+
// Flag to ignore index change tracking for when we need to stage untracked files
113+
private _ignoreIndexChange = false;
114+
111115
constructor(
112116
protected readonly container: Container,
113117
protected readonly host: WebviewHost<'gitlens.composer'>,
@@ -299,13 +303,28 @@ export class ComposerWebviewProvider implements WebviewProvider<State, State, Co
299303
source?: Sources,
300304
isReload?: boolean,
301305
): Promise<State> {
306+
// Stop repo change subscription so we can deal with untracked files
307+
this._repositorySubscription?.dispose();
308+
const status = await repo.git.status?.getStatus();
309+
const untrackedPaths = status?.untrackedChanges.map(f => f.path);
310+
if (untrackedPaths?.length) {
311+
try {
312+
await repo.git.staging?.stageFiles(untrackedPaths, { intentToAdd: true });
313+
this._ignoreIndexChange = true;
314+
} catch {}
315+
}
316+
302317
const [diffsResult, commitResult, branchResult] = await Promise.allSettled([
303318
// Handle baseCommit - could be string (old format) or ComposerBaseCommit (new format)
304319
getWorkingTreeDiffs(repo),
305320
repo.git.commits.getCommit('HEAD'),
306321
repo.git.branches.getBranch(),
307322
]);
308323

324+
if (untrackedPaths?.length) {
325+
await repo.git.staging?.unstageFiles(untrackedPaths).catch();
326+
}
327+
309328
const diffs = getSettledValue(diffsResult)!;
310329

311330
this._context.diff.unstagedIncluded = false;
@@ -755,9 +774,13 @@ export class ComposerWebviewProvider implements WebviewProvider<State, State, Co
755774

756775
private async onRepositoryChanged(e: RepositoryChangeEvent): Promise<void> {
757776
if (e.repository.id !== this._currentRepository?.id) return;
758-
777+
const ignoreIndexChange = this._ignoreIndexChange;
778+
this._ignoreIndexChange = false;
759779
// Only care about index changes (staged/unstaged changes)
760-
if (!e.changed(RepositoryChange.Index, RepositoryChangeComparisonMode.Any)) {
780+
if (
781+
!e.changed(RepositoryChange.Index, RepositoryChangeComparisonMode.Any) ||
782+
(ignoreIndexChange && e.changed(RepositoryChange.Index, RepositoryChangeComparisonMode.Exclusive))
783+
) {
761784
return;
762785
}
763786

@@ -1089,7 +1112,26 @@ export class ComposerWebviewProvider implements WebviewProvider<State, State, Co
10891112
);
10901113

10911114
// Validate repository safety state before proceeding
1092-
const validation = await validateSafetyState(repo, this._safetyState, hunksBeingCommitted);
1115+
// Stop repo change subscription so we can deal with untracked files
1116+
let workingTreeDiffs: WorkingTreeDiffs | undefined;
1117+
if (this._context.diff.unstagedIncluded) {
1118+
this._repositorySubscription?.dispose();
1119+
const status = await repo.git.status?.getStatus();
1120+
const untrackedPaths = status?.untrackedChanges.map(f => f.path);
1121+
if (untrackedPaths?.length) {
1122+
try {
1123+
workingTreeDiffs = await getWorkingTreeDiffs(repo);
1124+
await repo.git.staging?.stageFiles(untrackedPaths);
1125+
} catch {}
1126+
}
1127+
}
1128+
1129+
const validation = await validateSafetyState(
1130+
repo,
1131+
this._safetyState,
1132+
hunksBeingCommitted,
1133+
workingTreeDiffs,
1134+
);
10931135
if (!validation.isValid) {
10941136
// Clear loading state and show safety error
10951137
await this.host.notify(DidFinishCommittingNotification, undefined);
@@ -1197,7 +1239,7 @@ export class ComposerWebviewProvider implements WebviewProvider<State, State, Co
11971239
if (
11981240
stashCommit &&
11991241
stashCommit.ref !== previousStashCommit?.ref &&
1200-
stashCommit.message === stashMessage
1242+
stashCommit.message?.includes(stashMessage)
12011243
) {
12021244
stashedSuccessfully = true;
12031245
}

src/webviews/plus/composer/utils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,7 @@ export async function validateSafetyState(
343343
repo: Repository,
344344
safetyState: ComposerSafetyState,
345345
hunksBeingCommitted?: ComposerHunk[],
346+
workingTreeDiffs?: WorkingTreeDiffs,
346347
): Promise<{ isValid: boolean; errors: string[] }> {
347348
const errors: string[] = [];
348349

@@ -361,7 +362,7 @@ export async function validateSafetyState(
361362

362363
// 2. Smart diff validation - only check diffs for sources being committed
363364
if (hunksBeingCommitted?.length) {
364-
const { staged, unstaged /*, unified*/ } = await getWorkingTreeDiffs(repo);
365+
const { staged, unstaged /*, unified*/ } = workingTreeDiffs ?? (await getWorkingTreeDiffs(repo));
365366

366367
const hashes = {
367368
staged: staged?.contents ? await sha256(staged.contents) : null,

0 commit comments

Comments
 (0)