Skip to content

Commit 27c7164

Browse files
committed
Adds an option to include untracked files in diffs
- Creating a patch including working changes, include untracked files - Copying working changes to worktree, include untracked files
1 parent 762ee4a commit 27c7164

File tree

9 files changed

+167
-60
lines changed

9 files changed

+167
-60
lines changed

src/commands/git/worktree.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1142,10 +1142,10 @@ export class WorktreeGitCommand extends QuickCommand<State> {
11421142
}
11431143

11441144
if (!state.changes.contents || !state.changes.baseSha) {
1145-
const diff = await this.container.git.getDiff(
1146-
state.repo.uri,
1145+
const diff = await state.repo.git.getDiff(
11471146
state.changes.type === 'index' ? uncommittedStaged : uncommitted,
11481147
'HEAD',
1148+
{ includeUntracked: state.changes.type !== 'index' },
11491149
);
11501150
if (!diff?.contents) {
11511151
void window.showErrorMessage(`No changes to copy`);

src/commands/patches.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,9 @@ abstract class CreatePatchCommandBase extends Command {
141141
repo.uri,
142142
args?.to ?? uncommitted,
143143
args?.from ?? 'HEAD',
144-
args?.uris?.length ? { uris: args.uris } : undefined,
144+
args?.uris?.length
145+
? { uris: args.uris }
146+
: { includeUntracked: args?.to != null || args?.to === uncommitted },
145147
);
146148
}
147149

src/env/node/git/git.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -347,8 +347,8 @@ export class Git {
347347

348348
// Git commands
349349

350-
add(repoPath: string | undefined, pathspec: string) {
351-
return this.git<string>({ cwd: repoPath }, 'add', '-A', '--', pathspec);
350+
add(repoPath: string | undefined, pathspecs: string[], ...args: string[]) {
351+
return this.git<string>({ cwd: repoPath }, 'add', ...args, '--', ...pathspecs);
352352
}
353353

354354
apply(repoPath: string | undefined, patch: string, options: { allowConflicts?: boolean } = {}) {
@@ -1570,8 +1570,8 @@ export class Git {
15701570
return this.git<string>({ cwd: repoPath }, 'remote', 'get-url', remote);
15711571
}
15721572

1573-
reset(repoPath: string | undefined, fileName: string) {
1574-
return this.git<string>({ cwd: repoPath }, 'reset', '-q', '--', fileName);
1573+
reset(repoPath: string | undefined, pathspecs: string[]) {
1574+
return this.git<string>({ cwd: repoPath }, 'reset', '-q', '--', ...pathspecs);
15751575
}
15761576

15771577
async rev_list(

src/env/node/git/localGitProvider.ts

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3194,7 +3194,10 @@ export class LocalGitProvider implements GitProvider, Disposable {
31943194
repoPath: string,
31953195
to: string,
31963196
from?: string,
3197-
options?: { context?: number; uris?: Uri[] },
3197+
options?:
3198+
| { context?: number; includeUntracked?: never; uris?: never }
3199+
| { context?: number; includeUntracked?: never; uris: Uri[] }
3200+
| { context?: number; includeUntracked: boolean; uris?: never },
31983201
): Promise<GitDiff | undefined> {
31993202
const scope = getLogScope();
32003203
const params = [`-U${options?.context ?? 3}`];
@@ -3228,8 +3231,16 @@ export class LocalGitProvider implements GitProvider, Disposable {
32283231
params.push(from, to);
32293232
}
32303233

3234+
let untrackedPaths: string[] | undefined;
3235+
32313236
if (options?.uris) {
32323237
params.push('--', ...options.uris.map(u => u.fsPath));
3238+
} else if (options?.includeUntracked && to === uncommitted) {
3239+
const status = await this.getStatus(repoPath);
3240+
untrackedPaths = status?.untrackedChanges.map(f => f.path);
3241+
if (untrackedPaths?.length) {
3242+
await this.stageFiles(repoPath, untrackedPaths, { intentToAdd: true });
3243+
}
32333244
}
32343245

32353246
let data;
@@ -3239,6 +3250,10 @@ export class LocalGitProvider implements GitProvider, Disposable {
32393250
debugger;
32403251
Logger.error(ex, scope);
32413252
return undefined;
3253+
} finally {
3254+
if (untrackedPaths != null) {
3255+
await this.unstageFiles(repoPath, untrackedPaths);
3256+
}
32423257
}
32433258

32443259
const diff: GitDiff = { contents: data, from: from, to: to };
@@ -5753,31 +5768,60 @@ export class LocalGitProvider implements GitProvider, Disposable {
57535768
}
57545769

57555770
@log()
5756-
async stageFile(repoPath: string, pathOrUri: string | Uri): Promise<void> {
5757-
await this.git.add(repoPath, typeof pathOrUri === 'string' ? pathOrUri : splitPath(pathOrUri, repoPath)[0]);
5771+
async stageFile(repoPath: string, pathOrUri: string | Uri, options?: { intentToAdd?: boolean }): Promise<void> {
5772+
await this.git.add(
5773+
repoPath,
5774+
[typeof pathOrUri === 'string' ? pathOrUri : splitPath(pathOrUri, repoPath)[0]],
5775+
options?.intentToAdd ? '-N' : '-A',
5776+
);
57585777
}
57595778

57605779
@log()
5761-
async stageDirectory(repoPath: string, directoryOrUri: string | Uri): Promise<void> {
5780+
async stageFiles(
5781+
repoPath: string,
5782+
pathOrUri: string[] | Uri[],
5783+
options?: { intentToAdd?: boolean },
5784+
): Promise<void> {
57625785
await this.git.add(
57635786
repoPath,
5764-
typeof directoryOrUri === 'string' ? directoryOrUri : splitPath(directoryOrUri, repoPath)[0],
5787+
pathOrUri.map(p => (typeof p === 'string' ? p : splitPath(p, repoPath)[0])),
5788+
options?.intentToAdd ? '-N' : '-A',
5789+
);
5790+
}
5791+
5792+
@log()
5793+
async stageDirectory(
5794+
repoPath: string,
5795+
directoryOrUri: string | Uri,
5796+
options?: { intentToAdd?: boolean },
5797+
): Promise<void> {
5798+
await this.git.add(
5799+
repoPath,
5800+
[typeof directoryOrUri === 'string' ? directoryOrUri : splitPath(directoryOrUri, repoPath)[0]],
5801+
options?.intentToAdd ? '-N' : '-A',
57655802
);
57665803
}
57675804

57685805
@log()
57695806
async unstageFile(repoPath: string, pathOrUri: string | Uri): Promise<void> {
5770-
await this.git.reset(repoPath, typeof pathOrUri === 'string' ? pathOrUri : splitPath(pathOrUri, repoPath)[0]);
5807+
await this.git.reset(repoPath, [typeof pathOrUri === 'string' ? pathOrUri : splitPath(pathOrUri, repoPath)[0]]);
57715808
}
57725809

57735810
@log()
5774-
async unstageDirectory(repoPath: string, directoryOrUri: string | Uri): Promise<void> {
5811+
async unstageFiles(repoPath: string, pathOrUri: string[] | Uri[]): Promise<void> {
57755812
await this.git.reset(
57765813
repoPath,
5777-
typeof directoryOrUri === 'string' ? directoryOrUri : splitPath(directoryOrUri, repoPath)[0],
5814+
pathOrUri.map(p => (typeof p === 'string' ? p : splitPath(p, repoPath)[0])),
57785815
);
57795816
}
57805817

5818+
@log()
5819+
async unstageDirectory(repoPath: string, directoryOrUri: string | Uri): Promise<void> {
5820+
await this.git.reset(repoPath, [
5821+
typeof directoryOrUri === 'string' ? directoryOrUri : splitPath(directoryOrUri, repoPath)[0],
5822+
]);
5823+
}
5824+
57815825
@log()
57825826
async applyStash(repoPath: string, stashName: string, options?: { deleteAfter?: boolean }): Promise<void> {
57835827
try {

src/git/gitProvider.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,10 @@ export interface GitProviderRepository {
234234
repoPath: string | Uri,
235235
to: string,
236236
from?: string,
237-
options?: { context?: number; uris?: Uri[] },
237+
options?:
238+
| { context?: number; includeUntracked?: never; uris?: never }
239+
| { context?: number; includeUntracked?: never; uris: Uri[] }
240+
| { context?: number; includeUntracked: boolean; uris?: never },
238241
): Promise<GitDiff | undefined>;
239242
getDiffFiles?(repoPath: string | Uri, contents: string): Promise<GitDiffFiles | undefined>;
240243
getDiffStatus(
@@ -412,10 +415,12 @@ export interface GitProviderRepository {
412415
validatePatch?(repoPath: string | undefined, contents: string): Promise<boolean>;
413416
validateReference(repoPath: string, ref: string): Promise<boolean>;
414417

415-
stageFile(repoPath: string, pathOrUri: string | Uri): Promise<void>;
416-
stageDirectory(repoPath: string, directoryOrUri: string | Uri): Promise<void>;
417-
unstageFile(repoPath: string, pathOrUri: string | Uri): Promise<void>;
418-
unstageDirectory(repoPath: string, directoryOrUri: string | Uri): Promise<void>;
418+
stageFile?(repoPath: string, pathOrUri: string | Uri, options?: { intentToAdd?: boolean }): Promise<void>;
419+
stageFiles?(repoPath: string, pathOrUri: string[] | Uri[], options?: { intentToAdd?: boolean }): Promise<void>;
420+
stageDirectory?(repoPath: string, directoryOrUri: string | Uri, options?: { intentToAdd?: boolean }): Promise<void>;
421+
unstageFile?(repoPath: string, pathOrUri: string | Uri): Promise<void>;
422+
unstageFiles?(repoPath: string, pathOrUri: string[] | Uri[]): Promise<void>;
423+
unstageDirectory?(repoPath: string, directoryOrUri: string | Uri): Promise<void>;
419424

420425
applyStash?(repoPath: string, stashName: string, options?: { deleteAfter?: boolean | undefined }): Promise<void>;
421426
deleteStash?(repoPath: string, stashName: string, ref?: string): Promise<void>;

src/git/gitProviderService.ts

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1872,7 +1872,10 @@ export class GitProviderService implements Disposable {
18721872
repoPath: string | Uri,
18731873
to: string,
18741874
from?: string,
1875-
options?: { context?: number; uris?: Uri[] },
1875+
options?:
1876+
| { context?: number; includeUntracked?: never; uris?: never }
1877+
| { context?: number; includeUntracked?: never; uris: Uri[] }
1878+
| { context?: number; includeUntracked: boolean; uris?: never },
18761879
): Promise<GitDiff | undefined> {
18771880
const { provider, path } = this.getProvider(repoPath);
18781881
return provider.getDiff?.(path, to, from, options);
@@ -2786,36 +2789,64 @@ export class GitProviderService implements Disposable {
27862789
return provider.validateReference(path, ref);
27872790
}
27882791

2789-
stageFile(repoPath: string | Uri, path: string): Promise<void>;
2790-
stageFile(repoPath: string | Uri, uri: Uri): Promise<void>;
2792+
stageFile(repoPath: string | Uri, path: string, options?: { intentToAdd?: boolean }): Promise<void>;
2793+
stageFile(repoPath: string | Uri, uri: Uri, options?: { intentToAdd?: boolean }): Promise<void>;
2794+
@log()
2795+
async stageFile(
2796+
repoPath: string | Uri,
2797+
pathOrUri: string | Uri,
2798+
options?: { intentToAdd?: boolean },
2799+
): Promise<void> {
2800+
const { provider, path } = this.getProvider(repoPath);
2801+
return provider.stageFile?.(path, pathOrUri, options);
2802+
}
2803+
2804+
stageFiles(repoPath: string | Uri, path: string[], options?: { intentToAdd?: boolean }): Promise<void>;
2805+
stageFiles(repoPath: string | Uri, uri: Uri[], options?: { intentToAdd?: boolean }): Promise<void>;
27912806
@log()
2792-
stageFile(repoPath: string | Uri, pathOrUri: string | Uri): Promise<void> {
2807+
async stageFiles(
2808+
repoPath: string | Uri,
2809+
pathOrUri: string[] | Uri[],
2810+
options?: { intentToAdd?: boolean },
2811+
): Promise<void> {
27932812
const { provider, path } = this.getProvider(repoPath);
2794-
return provider.stageFile(path, pathOrUri);
2813+
return provider.stageFiles?.(path, pathOrUri, options);
27952814
}
27962815

2797-
stageDirectory(repoPath: string | Uri, directory: string): Promise<void>;
2798-
stageDirectory(repoPath: string | Uri, uri: Uri): Promise<void>;
2816+
stageDirectory(repoPath: string | Uri, directory: string, options?: { intentToAdd?: boolean }): Promise<void>;
2817+
stageDirectory(repoPath: string | Uri, uri: Uri, options?: { intentToAdd?: boolean }): Promise<void>;
27992818
@log()
2800-
stageDirectory(repoPath: string | Uri, directoryOrUri: string | Uri): Promise<void> {
2819+
async stageDirectory(
2820+
repoPath: string | Uri,
2821+
directoryOrUri: string | Uri,
2822+
options?: { intentToAdd?: boolean },
2823+
): Promise<void> {
28012824
const { provider, path } = this.getProvider(repoPath);
2802-
return provider.stageDirectory(path, directoryOrUri);
2825+
return provider.stageDirectory?.(path, directoryOrUri, options);
28032826
}
28042827

28052828
unstageFile(repoPath: string | Uri, path: string): Promise<void>;
28062829
unstageFile(repoPath: string | Uri, uri: Uri): Promise<void>;
28072830
@log()
2808-
unstageFile(repoPath: string | Uri, pathOrUri: string | Uri): Promise<void> {
2831+
async unstageFile(repoPath: string | Uri, pathOrUri: string | Uri): Promise<void> {
2832+
const { provider, path } = this.getProvider(repoPath);
2833+
return provider.unstageFile?.(path, pathOrUri);
2834+
}
2835+
2836+
unstageFiles(repoPath: string | Uri, path: string[]): Promise<void>;
2837+
unstageFiles(repoPath: string | Uri, uri: Uri[]): Promise<void>;
2838+
@log()
2839+
async unstageFiles(repoPath: string | Uri, pathOrUri: string[] | Uri[]): Promise<void> {
28092840
const { provider, path } = this.getProvider(repoPath);
2810-
return provider.unstageFile(path, pathOrUri);
2841+
return provider.unstageFiles?.(path, pathOrUri);
28112842
}
28122843

28132844
unstageDirectory(repoPath: string | Uri, directory: string): Promise<void>;
28142845
unstageDirectory(repoPath: string | Uri, uri: Uri): Promise<void>;
28152846
@log()
2816-
unstageDirectory(repoPath: string | Uri, directoryOrUri: string | Uri): Promise<void> {
2847+
async unstageDirectory(repoPath: string | Uri, directoryOrUri: string | Uri): Promise<void> {
28172848
const { provider, path } = this.getProvider(repoPath);
2818-
return provider.unstageDirectory(path, directoryOrUri);
2849+
return provider.unstageDirectory?.(path, directoryOrUri);
28192850
}
28202851

28212852
@log()

src/git/models/repository.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,29 @@ import { getNameWithoutRemote, isBranchReference } from './reference';
3232
import type { GitRemote } from './remote';
3333
import type { GitWorktree } from './worktree';
3434

35-
type RemoveFirstArg<F> = F extends (first: any, ...args: infer P) => infer R ? (...args: P) => R : never;
35+
type RemoveFirstArg<F> = F extends {
36+
(first: any, ...args: infer A1): infer R1;
37+
(first: any, ...args: infer A2): infer R2;
38+
(first: any, ...args: infer A3): infer R3;
39+
(first: any, ...args: infer A4): infer R4;
40+
}
41+
? ((...args: A1) => R1) & ((...args: A2) => R2) & ((...args: A3) => R3) & ((...args: A4) => R4)
42+
: F extends {
43+
(first: any, ...args: infer A1): infer R1;
44+
(first: any, ...args: infer A2): infer R2;
45+
(first: any, ...args: infer A3): infer R3;
46+
}
47+
? ((...args: A1) => R1) & ((...args: A2) => R2) & ((...args: A3) => R3)
48+
: F extends {
49+
(first: any, ...args: infer A1): infer R1;
50+
(first: any, ...args: infer A2): infer R2;
51+
}
52+
? ((...args: A1) => R1) & ((...args: A2) => R2)
53+
: F extends {
54+
(first: any, ...args: infer A1): infer R1;
55+
}
56+
? (...args: A1) => R1
57+
: never;
3658

3759
export type RepoGitProviderService = Pick<
3860
{

src/git/models/status.ts

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,38 @@ export class GitStatus {
5252
}
5353
}
5454

55+
get branchStatus(): GitBranchStatus {
56+
if (this.upstream == null) return this.detached ? 'detached' : 'local';
57+
58+
if (this.upstream.missing) return 'missingUpstream';
59+
if (this.state.ahead && this.state.behind) return 'diverged';
60+
if (this.state.ahead) return 'ahead';
61+
if (this.state.behind) return 'behind';
62+
return 'upToDate';
63+
}
64+
65+
get hasChanges() {
66+
return this.files.length !== 0;
67+
}
68+
69+
@memoize()
70+
get hasConflicts() {
71+
return this.files.some(f => f.conflicted);
72+
}
73+
5574
@memoize()
5675
get conflicts() {
5776
return this.files.filter(f => f.conflicted);
5877
}
5978

60-
get hasChanges() {
61-
return this.files.length !== 0;
79+
@memoize()
80+
get hasUntrackedChanges() {
81+
return this.files.some(f => f.workingTreeStatus == GitFileWorkingTreeStatus.Untracked);
82+
}
83+
84+
@memoize()
85+
get untrackedChanges() {
86+
return this.files.filter(f => f.workingTreeStatus == GitFileWorkingTreeStatus.Untracked);
6287
}
6388

6489
@memoize()
@@ -67,24 +92,14 @@ export class GitStatus {
6792
}
6893

6994
@memoize()
70-
get hasConflicts() {
71-
return this.files.some(f => f.conflicted);
95+
get workingTreeChanges() {
96+
return this.files.filter(f => f.workingTreeStatus != null);
7297
}
7398

7499
get ref() {
75100
return this.detached ? this.sha : this.branch;
76101
}
77102

78-
get branchStatus(): GitBranchStatus {
79-
if (this.upstream == null) return this.detached ? 'detached' : 'local';
80-
81-
if (this.upstream.missing) return 'missingUpstream';
82-
if (this.state.ahead && this.state.behind) return 'diverged';
83-
if (this.state.ahead) return 'ahead';
84-
if (this.state.behind) return 'behind';
85-
return 'upToDate';
86-
}
87-
88103
@memoize()
89104
computeWorkingTreeStatus(): ComputedWorkingTreeGitStatus {
90105
let conflictedAdds = 0;

src/plus/integrations/providers/github/githubGitProvider.ts

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3365,18 +3365,6 @@ export class GitHubGitProvider implements GitProvider, Disposable {
33653365
return true;
33663366
}
33673367

3368-
@log()
3369-
async stageFile(_repoPath: string, _pathOrUri: string | Uri): Promise<void> {}
3370-
3371-
@log()
3372-
async stageDirectory(_repoPath: string, _directoryOrUri: string | Uri): Promise<void> {}
3373-
3374-
@log()
3375-
async unstageFile(_repoPath: string, _pathOrUri: string | Uri): Promise<void> {}
3376-
3377-
@log()
3378-
async unstageDirectory(_repoPath: string, _directoryOrUri: string | Uri): Promise<void> {}
3379-
33803368
@gate()
33813369
private async ensureRepositoryContext(
33823370
repoPath: string,

0 commit comments

Comments
 (0)