Skip to content

Commit c9e21db

Browse files
committed
Adds copy patch for multiselect of commit files
Ensures copy patch multiselect of unstaged files handles untracked files
1 parent c184dc4 commit c9e21db

File tree

8 files changed

+56
-52
lines changed

8 files changed

+56
-52
lines changed

contributions.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@
394394
"order": 97
395395
},
396396
{
397-
"when": "viewItem =~ /gitlens:file(\\b(?=.*?\\b\\+committed\\b)|:results)/ && !listMultiSelection && !gitlens:hasVirtualFolders && !gitlens:untrusted",
397+
"when": "viewItem =~ /gitlens:file(\\b(?=.*?\\b\\+committed\\b)|:results)/ && !gitlens:hasVirtualFolders && !gitlens:untrusted",
398398
"group": "7_gitlens_cutcopypaste",
399399
"order": 3
400400
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16212,7 +16212,7 @@
1621216212
},
1621316213
{
1621416214
"command": "gitlens.copyPatchToClipboard",
16215-
"when": "viewItem =~ /gitlens:file(\\b(?=.*?\\b\\+committed\\b)|:results)/ && !listMultiSelection && !gitlens:hasVirtualFolders && !gitlens:untrusted",
16215+
"when": "viewItem =~ /gitlens:file(\\b(?=.*?\\b\\+committed\\b)|:results)/ && !gitlens:hasVirtualFolders && !gitlens:untrusted",
1621616216
"group": "7_gitlens_cutcopypaste@3"
1621716217
},
1621816218
{

src/commands/commandBase.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import type { TextEditor, TextEditorEdit } from 'vscode';
22
import { commands, Disposable } from 'vscode';
33
import type { GlCommands } from '../constants.commands';
44
import { registerCommand } from '../system/-webview/command';
5-
import { runSequentially } from '../system/function';
65
import type { CommandContext } from './commandContext';
76
import type { CommandContextParsingOptions } from './commandContext.utils';
87
import { parseCommandContext } from './commandContext.utils';
@@ -38,16 +37,6 @@ export abstract class GlCommandBase implements Disposable {
3837

3938
protected _execute(command: GlCommands, ...args: any[]): Promise<unknown> {
4039
const [context, rest] = parseCommandContext(command, { ...this.contextParsingOptions }, ...args);
41-
42-
// If there an array of contexts, then we want to execute the command for each
43-
if (Array.isArray(context)) {
44-
return runSequentially(
45-
this.preExecute,
46-
context.map<[CommandContext, ...any[]]>((c: CommandContext) => [c, ...rest]),
47-
this,
48-
);
49-
}
50-
5140
return this.preExecute(context, ...rest);
5241
}
5342
}

src/commands/commandContext.utils.ts

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ export function parseCommandContext(
161161
command: GlCommands,
162162
options?: CommandContextParsingOptions,
163163
...args: any[]
164-
): [CommandContext | CommandContext[], any[]] {
164+
): [CommandContext, any[]] {
165165
let editor: TextEditor | undefined = undefined;
166166

167167
const originalArgs = [...args];
@@ -219,25 +219,15 @@ export function parseCommandContext(
219219
}
220220

221221
if (firstArg instanceof ViewNode) {
222-
let [node, ...rest] = args as [ViewNode, unknown];
223-
224-
// If there is a node followed by an array of nodes, then we want to execute the command for each
225-
firstArg = rest[0];
226-
if (Array.isArray(firstArg) && firstArg[0] instanceof ViewNode) {
227-
let nodes;
228-
[nodes, ...rest] = rest as unknown as [ViewNode[], unknown];
229-
230-
const contexts: CommandContext[] = [];
231-
for (const n of nodes) {
232-
if (n?.constructor === node.constructor) {
233-
contexts.push({ command: command, type: 'viewItem', args: originalArgs, node: n, uri: n.uri });
234-
}
235-
}
222+
const [active, selection, ...rest] = args as [ViewNode, unknown];
236223

237-
return [contexts, rest];
224+
// If there is a node followed by an array of nodes, then check how we want to execute the command
225+
if (active instanceof ViewNode && Array.isArray(selection) && selection[0] instanceof ViewNode) {
226+
const nodes = selection.filter((n): n is ViewNode => n?.constructor === active.constructor);
227+
return [{ command: command, type: 'viewItems', args: originalArgs, node: active, nodes: nodes }, rest];
238228
}
239229

240-
return [{ command: command, type: 'viewItem', args: originalArgs, node: node, uri: node.uri }, rest];
230+
return [{ command: command, type: 'viewItem', args: originalArgs, node: active, uri: active.uri }, rest];
241231
}
242232

243233
if (isScmResourceState(firstArg)) {

src/commands/patches.ts

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { EntityIdentifierUtils } from '@gitkraken/provider-apis/entity-identifie
22
import type { TextEditor } from 'vscode';
33
import { env, Uri, window, workspace } from 'vscode';
44
import type { ScmResource } from '../@types/vscode.git.resources';
5-
import { ScmResourceGroupType } from '../@types/vscode.git.resources.enums';
5+
import { ScmResourceGroupType, ScmStatus } from '../@types/vscode.git.resources.enums';
66
import type { GlCommands } from '../constants.commands';
77
import { GlCommand } from '../constants.commands';
88
import type { IntegrationId } from '../constants.integrations';
@@ -22,6 +22,7 @@ import { getRepositoryOrShowPicker } from '../quickpicks/repositoryPicker';
2222
import { command } from '../system/-webview/command';
2323
import { map } from '../system/iterable';
2424
import { Logger } from '../system/logger';
25+
import { isViewRefFileNode } from '../views/nodes/utils/-webview/node.utils';
2526
import type { Change, CreateDraft } from '../webviews/plus/patchDetails/protocol';
2627
import { ActiveEditorCommand, GlCommandBase } from './commandBase';
2728
import type { CommandContext } from './commandContext';
@@ -38,6 +39,8 @@ export interface CreatePatchCommandArgs {
3839
repoPath?: string;
3940
uris?: Uri[];
4041

42+
includeUntracked?: boolean;
43+
4144
title?: string;
4245
description?: string;
4346
}
@@ -56,10 +59,13 @@ abstract class CreatePatchCommandBase extends GlCommandBase {
5659
const resourcesByGroup = new Map<ScmResourceGroupType, ScmResource[]>();
5760
const uris = new Set<string>();
5861

62+
let includeUntracked = false;
63+
5964
let repo;
6065
for (const resource of context.scmResourceStates as ScmResource[]) {
6166
repo ??= await this.container.git.getOrOpenRepository(resource.resourceUri);
6267

68+
includeUntracked = includeUntracked || resource.type === ScmStatus.UNTRACKED;
6369
uris.add(resource.resourceUri.toString());
6470

6571
let groupResources = resourcesByGroup.get(resource.resourceGroupType!);
@@ -81,6 +87,7 @@ abstract class CreatePatchCommandBase extends GlCommandBase {
8187
from: 'HEAD',
8288
uris: [...map(uris, u => Uri.parse(u))],
8389
title: to === uncommittedStaged ? 'Staged Changes' : 'Uncommitted Changes',
90+
includeUntracked: includeUntracked ? true : undefined,
8491
};
8592
} else if (context.type === 'scm-groups') {
8693
const group = context.scmResourceGroups[0];
@@ -131,6 +138,22 @@ abstract class CreatePatchCommandBase extends GlCommandBase {
131138
uris: [context.node.uri],
132139
};
133140
}
141+
} else if (context.type === 'viewItems') {
142+
if (isViewRefFileNode(context.node)) {
143+
args = {
144+
repoPath: context.node.repoPath,
145+
to: context.node.ref.sha,
146+
from: `${context.node.ref.sha}^`,
147+
uris: [context.node.uri],
148+
title: `Changes (partial) in ${shortenRevision(context.node.ref.sha)}`,
149+
};
150+
151+
for (const node of context.nodes) {
152+
if (isViewRefFileNode(node) && node !== context.node && node.ref.sha === args.to) {
153+
args.uris!.push(node.uri);
154+
}
155+
}
156+
}
134157
}
135158
}
136159

@@ -147,13 +170,10 @@ abstract class CreatePatchCommandBase extends GlCommandBase {
147170

148171
return repo.git
149172
.diff()
150-
.getDiff?.(
151-
args?.to ?? uncommitted,
152-
args?.from ?? 'HEAD',
153-
args?.uris?.length
154-
? { uris: args.uris }
155-
: { includeUntracked: args?.to != null || args?.to === uncommitted },
156-
);
173+
.getDiff?.(args?.to ?? uncommitted, args?.from ?? 'HEAD', {
174+
includeUntracked: args?.includeUntracked ?? (args?.to != null || args?.to === uncommitted),
175+
uris: args?.uris,
176+
});
157177
}
158178

159179
abstract override execute(args?: CreatePatchCommandArgs): Promise<void>;

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

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,7 @@ export class DiffGitSubProvider implements GitDiffSubProvider {
7373
repoPath: string,
7474
to: string,
7575
from?: string,
76-
options?:
77-
| { context?: number; includeUntracked?: never; uris?: never }
78-
| { context?: number; includeUntracked?: never; uris: Uri[] }
79-
| { context?: number; includeUntracked: boolean; uris?: never },
76+
options?: { context?: number; includeUntracked: boolean; uris?: Uri[] },
8077
): Promise<GitDiff | undefined> {
8178
const scope = getLogScope();
8279
const args = [`-U${options?.context ?? 3}`];
@@ -110,15 +107,27 @@ export class DiffGitSubProvider implements GitDiffSubProvider {
110107
args.push(from, to);
111108
}
112109

110+
let paths: Set<string> | undefined;
113111
let untrackedPaths: string[] | undefined;
114112

115113
if (options?.uris) {
116-
args.push('--', ...options.uris.map(u => u.fsPath));
117-
} else if (options?.includeUntracked && to === uncommitted) {
114+
paths = new Set<string>(options.uris.map(u => this.provider.getRelativePath(u, repoPath)));
115+
args.push('--', ...paths);
116+
}
117+
118+
if (options?.includeUntracked && to === uncommitted) {
118119
const status = await this.provider.status?.getStatus(repoPath);
120+
119121
untrackedPaths = status?.untrackedChanges.map(f => f.path);
122+
120123
if (untrackedPaths?.length) {
121-
await this.provider.staging?.stageFiles(repoPath, untrackedPaths, { intentToAdd: true });
124+
if (paths?.size) {
125+
untrackedPaths = untrackedPaths.filter(p => paths.has(p));
126+
}
127+
128+
if (untrackedPaths.length) {
129+
await this.provider.staging?.stageFiles(repoPath, untrackedPaths, { intentToAdd: true });
130+
}
122131
}
123132
}
124133

@@ -135,7 +144,7 @@ export class DiffGitSubProvider implements GitDiffSubProvider {
135144
Logger.error(ex, scope);
136145
return undefined;
137146
} finally {
138-
if (untrackedPaths != null) {
147+
if (untrackedPaths?.length) {
139148
await this.provider.staging?.unstageFiles(repoPath, untrackedPaths);
140149
}
141150
}

src/git/gitProvider.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -363,10 +363,7 @@ export interface GitDiffSubProvider {
363363
repoPath: string | Uri,
364364
to: string,
365365
from?: string,
366-
options?:
367-
| { context?: number; includeUntracked?: never; uris?: never }
368-
| { context?: number; includeUntracked?: never; uris: Uri[] }
369-
| { context?: number; includeUntracked: boolean; uris?: never },
366+
options?: { context?: number; includeUntracked?: boolean; uris?: Uri[] },
370367
): Promise<GitDiff | undefined>;
371368
getDiffFiles?(repoPath: string | Uri, contents: string): Promise<GitDiffFiles | undefined>;
372369
getDiffStatus(

src/webviews/plus/graph/graphWebview.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -453,8 +453,7 @@ export class GraphWebviewProvider implements WebviewProvider<State, State, Graph
453453
}
454454

455455
if (this.repository == null && this.container.git.repositoryCount > 1) {
456-
const [contexts] = parseCommandContext(GlCommand.ShowGraph, undefined, ...args);
457-
const context = Array.isArray(contexts) ? contexts[0] : contexts;
456+
const [context] = parseCommandContext(GlCommand.ShowGraph, undefined, ...args);
458457

459458
if (context.type === 'scm' && context.scm.rootUri != null) {
460459
this.repository = this.container.git.getRepository(context.scm.rootUri);

0 commit comments

Comments
 (0)