Skip to content

Commit 3d184e3

Browse files
committed
Enhances patch copying for uncommitted changes
- Adds _Copy Changes (Patch)_ to uncommitted files in the _Worktrees_ view - Adds _Copy Changes (Patch)_ to uncommitted files in the _Commit Details_ and _Graph Details_ views
1 parent 091fe3e commit 3d184e3

File tree

5 files changed

+119
-51
lines changed

5 files changed

+119
-51
lines changed

contributions.json

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -912,7 +912,7 @@
912912
"menus": {
913913
"webview/context": [
914914
{
915-
"when": "webviewItem =~ /gitlens:file\\b(?=.*?\\b\\+committed\\b)/ && !gitlens:untrusted && !gitlens:hasVirtualFolders && webview == gitlens.views.commitDetails",
915+
"when": "webviewItem =~ /gitlens:file\\b(?=.*?\\b\\+(committed|stashed|staged|unstaged)\\b)/ && !gitlens:untrusted && !gitlens:hasVirtualFolders && webview == gitlens.views.commitDetails",
916916
"group": "7_gitlens_cutcopypaste",
917917
"order": 3
918918
}
@@ -936,7 +936,7 @@
936936
"menus": {
937937
"webview/context": [
938938
{
939-
"when": "webviewItem =~ /gitlens:file\\b(?=.*?\\b\\+committed\\b)/ && !gitlens:untrusted && !gitlens:hasVirtualFolders && webview == gitlens.views.graphDetails",
939+
"when": "webviewItem =~ /gitlens:file\\b(?=.*?\\b\\+(committed|stashed|staged|unstaged)\\b)/ && !gitlens:untrusted && !gitlens:hasVirtualFolders && webview == gitlens.views.graphDetails",
940940
"group": "7_gitlens_cutcopypaste",
941941
"order": 3
942942
}
@@ -991,9 +991,14 @@
991991
"order": 97
992992
},
993993
{
994-
"when": "viewItem =~ /gitlens:file(\\b(?=.*?\\b\\+committed\\b)|:results)/ && !gitlens:untrusted && !gitlens:hasVirtualFolders",
994+
"when": "viewItem =~ /gitlens:file($|\\b(?=.*?\\b\\+(committed|stashed|staged|unstaged)\\b)|:results)/ && !gitlens:untrusted && !gitlens:hasVirtualFolders",
995995
"group": "7_gitlens_cutcopypaste",
996996
"order": 3
997+
},
998+
{
999+
"when": "viewItem =~ /gitlens:uncommitted:files\\b/ && !listMultiSelection && !gitlens:untrusted && !gitlens:hasVirtualFolders",
1000+
"group": "7_gitlens_cutcopypaste",
1001+
"order": 97
9971002
}
9981003
]
9991004
}

package.json

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18907,7 +18907,7 @@
1890718907
},
1890818908
{
1890918909
"command": "gitlens.copyPatchToClipboard:views",
18910-
"when": "viewItem =~ /gitlens:file(\\b(?=.*?\\b\\+committed\\b)|:results)/ && !gitlens:untrusted && !gitlens:hasVirtualFolders",
18910+
"when": "viewItem =~ /gitlens:file($|\\b(?=.*?\\b\\+(committed|stashed|staged|unstaged)\\b)|:results)/ && !gitlens:untrusted && !gitlens:hasVirtualFolders",
1891118911
"group": "7_gitlens_cutcopypaste@3"
1891218912
},
1891318913
{
@@ -19723,6 +19723,11 @@
1972319723
"when": "viewItem =~ /gitlens:(worktree\\b(?=.*?\\b\\+working\\b)|uncommitted)\\b/ && !listMultiSelection && !gitlens:readonly && !gitlens:untrusted && gitlens:gk:organization:ai:enabled",
1972419724
"group": "3_gitlens_ai@10"
1972519725
},
19726+
{
19727+
"command": "gitlens.copyPatchToClipboard:views",
19728+
"when": "viewItem =~ /gitlens:uncommitted:files\\b/ && !listMultiSelection && !gitlens:untrusted && !gitlens:hasVirtualFolders",
19729+
"group": "7_gitlens_cutcopypaste@97"
19730+
},
1972619731
{
1972719732
"command": "gitlens.views.switchToAnotherBranch",
1972819733
"when": "viewItem == gitlens:views:branches && !gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders",
@@ -24348,12 +24353,12 @@
2434824353
},
2434924354
{
2435024355
"command": "gitlens.copyPatchToClipboard:commitDetails",
24351-
"when": "webviewItem =~ /gitlens:file\\b(?=.*?\\b\\+committed\\b)/ && !gitlens:untrusted && !gitlens:hasVirtualFolders && webview == gitlens.views.commitDetails",
24356+
"when": "webviewItem =~ /gitlens:file\\b(?=.*?\\b\\+(committed|stashed|staged|unstaged)\\b)/ && !gitlens:untrusted && !gitlens:hasVirtualFolders && webview == gitlens.views.commitDetails",
2435224357
"group": "7_gitlens_cutcopypaste@3"
2435324358
},
2435424359
{
2435524360
"command": "gitlens.copyPatchToClipboard:graphDetails",
24356-
"when": "webviewItem =~ /gitlens:file\\b(?=.*?\\b\\+committed\\b)/ && !gitlens:untrusted && !gitlens:hasVirtualFolders && webview == gitlens.views.graphDetails",
24361+
"when": "webviewItem =~ /gitlens:file\\b(?=.*?\\b\\+(committed|stashed|staged|unstaged)\\b)/ && !gitlens:untrusted && !gitlens:hasVirtualFolders && webview == gitlens.views.graphDetails",
2435724362
"group": "7_gitlens_cutcopypaste@3"
2435824363
},
2435924364
{

src/commands/patches.ts

Lines changed: 76 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import type { GitDiff } from '../git/models/diff';
1212
import type { Repository } from '../git/models/repository';
1313
import { uncommitted, uncommittedStaged } from '../git/models/revision';
1414
import { splitCommitMessage } from '../git/utils/commit.utils';
15-
import { isSha, shortenRevision } from '../git/utils/revision.utils';
15+
import { isSha, isUncommitted, isUncommittedStaged, shortenRevision } from '../git/utils/revision.utils';
1616
import { showPatchesView } from '../plus/drafts/actions';
1717
import type { ProviderAuth } from '../plus/drafts/draftsService';
1818
import type { Draft, LocalDraft } from '../plus/drafts/models/drafts';
@@ -30,6 +30,7 @@ import {
3030
isCommandContextViewNodeHasComparison,
3131
isCommandContextViewNodeHasFileCommit,
3232
isCommandContextViewNodeHasFileRefs,
33+
isCommandContextViewNodeHasRefFile,
3334
} from './commandContext.utils';
3435

3536
export interface CreatePatchCommandArgs {
@@ -83,7 +84,6 @@ abstract class CreatePatchCommandBase extends GlCommandBase {
8384
args = {
8485
repoPath: repo?.path,
8586
to: to,
86-
from: 'HEAD',
8787
uris: [...map(uris, u => Uri.parse(u))],
8888
title: to === uncommittedStaged ? 'Staged Changes' : 'Uncommitted Changes',
8989
includeUntracked: includeUntracked ? true : undefined,
@@ -98,25 +98,33 @@ abstract class CreatePatchCommandBase extends GlCommandBase {
9898
args = {
9999
repoPath: repo?.path,
100100
to: to,
101-
from: 'HEAD',
102101
title: to === uncommittedStaged ? 'Staged Changes' : 'Uncommitted Changes',
103102
};
104103
} else if (context.type === 'viewItem') {
105104
if (isCommandContextViewNodeHasCommit(context)) {
106105
const { commit } = context.node;
107-
if (commit.message == null) {
108-
await commit.ensureFullDetails();
109-
}
106+
if (commit.isUncommitted) {
107+
const to = commit.isUncommittedStaged ? uncommittedStaged : uncommitted;
108+
args = {
109+
repoPath: context.node.commit.repoPath,
110+
to: to,
111+
title: to === uncommittedStaged ? 'Staged Changes' : 'Uncommitted Changes',
112+
};
113+
} else {
114+
if (commit.message == null) {
115+
await commit.ensureFullDetails();
116+
}
110117

111-
const { summary: title, body: description } = splitCommitMessage(commit.message);
118+
const { summary: title, body: description } = splitCommitMessage(commit.message);
112119

113-
args = {
114-
repoPath: context.node.commit.repoPath,
115-
to: context.node.commit.ref,
116-
from: `${context.node.commit.ref}^`,
117-
title: title,
118-
description: description,
119-
};
120+
args = {
121+
repoPath: context.node.commit.repoPath,
122+
to: context.node.commit.ref,
123+
from: `${context.node.commit.ref}^`,
124+
title: title,
125+
description: description,
126+
};
127+
}
120128
if (isCommandContextViewNodeHasFileCommit(context)) {
121129
args.uris = [context.node.uri];
122130
}
@@ -136,16 +144,53 @@ abstract class CreatePatchCommandBase extends GlCommandBase {
136144
from: context.node.ref1,
137145
uris: [context.node.uri],
138146
};
139-
}
140-
} else if (context.type === 'viewItems') {
141-
if (isViewRefFileNode(context.node)) {
147+
} else if (context.node.is('uncommitted-files')) {
142148
args = {
143149
repoPath: context.node.repoPath,
144-
to: context.node.ref.sha,
145-
from: `${context.node.ref.sha}^`,
146-
uris: [context.node.uri],
147-
title: `Changes (partial) in ${shortenRevision(context.node.ref.sha)}`,
150+
to: uncommitted,
151+
from: 'HEAD',
152+
title: 'Uncommitted Changes',
148153
};
154+
} else if (isCommandContextViewNodeHasRefFile(context)) {
155+
if (isUncommitted(context.node.ref.ref)) {
156+
const to = isUncommittedStaged(context.node.ref.ref) ? uncommittedStaged : uncommitted;
157+
args = {
158+
repoPath: context.node.repoPath,
159+
to: to,
160+
from: context.node.is('uncommitted-file') ? 'HEAD' : undefined,
161+
uris: [context.node.uri],
162+
title: to === uncommittedStaged ? 'Staged Changes' : 'Uncommitted Changes',
163+
};
164+
} else {
165+
args = {
166+
repoPath: context.node.repoPath,
167+
to: context.node.ref.sha,
168+
from: `${context.node.ref.sha}^`,
169+
uris: [context.node.uri],
170+
title: `Changes (partial) in ${shortenRevision(context.node.ref.sha)}`,
171+
};
172+
}
173+
}
174+
} else if (context.type === 'viewItems') {
175+
if (isViewRefFileNode(context.node)) {
176+
if (isUncommitted(context.node.ref.ref)) {
177+
const to = isUncommittedStaged(context.node.ref.ref) ? uncommittedStaged : uncommitted;
178+
args = {
179+
repoPath: context.node.repoPath,
180+
to: to,
181+
from: context.node.is('uncommitted-file') ? 'HEAD' : undefined,
182+
uris: [context.node.uri],
183+
title: to === uncommittedStaged ? 'Staged Changes' : 'Uncommitted Changes',
184+
};
185+
} else {
186+
args = {
187+
repoPath: context.node.repoPath,
188+
to: context.node.ref.sha,
189+
from: `${context.node.ref.sha}^`,
190+
uris: [context.node.uri],
191+
title: `Changes (partial) in ${shortenRevision(context.node.ref.sha)}`,
192+
};
193+
}
149194

150195
for (const node of context.nodes) {
151196
if (isViewRefFileNode(node) && node !== context.node && node.ref.sha === args.to) {
@@ -160,34 +205,34 @@ abstract class CreatePatchCommandBase extends GlCommandBase {
160205
}
161206

162207
protected async getDiff(title: string, args?: CreatePatchCommandArgs): Promise<GitDiff | undefined> {
163-
let repo;
208+
let git;
164209
if (args?.repoPath != null) {
165-
repo = this.container.git.getRepository(args.repoPath);
210+
git =
211+
this.container.git.getRepository(args.repoPath)?.git ??
212+
this.container.git.getRepositoryService(args.repoPath);
166213
}
167-
repo ??= await getRepositoryOrShowPicker(this.container, title);
168-
if (repo == null) return;
214+
git ??= (await getRepositoryOrShowPicker(this.container, title, undefined, args?.repoPath))?.git;
215+
if (git == null) return;
169216

170217
let untrackedPaths: string[] | undefined;
171218
try {
172219
if (args?.to === uncommitted) {
173220
// stage any untracked files to include them in the diff
174-
untrackedPaths = (await repo.git.status?.getUntrackedFiles())?.map(f => f.path);
221+
untrackedPaths = (await git.status?.getUntrackedFiles())?.map(f => f.path);
175222
if (untrackedPaths?.length) {
176223
try {
177-
await repo.git.staging?.stageFiles(untrackedPaths);
224+
await git.staging?.stageFiles(untrackedPaths);
178225
} catch (ex) {
179226
Logger.error(ex, `Failed to stage (${untrackedPaths.length}) untracked files for patch`);
180227
}
181228
}
182229
}
183230

184-
return await repo.git.diff.getDiff?.(args?.to ?? uncommitted, args?.from ?? 'HEAD', {
185-
uris: args?.uris,
186-
});
231+
return await git.diff.getDiff?.(args?.to ?? uncommitted, args?.from, { uris: args?.uris });
187232
} finally {
188233
if (untrackedPaths?.length) {
189234
try {
190-
await repo.git.staging?.unstageFiles(untrackedPaths);
235+
await git.staging?.unstageFiles(untrackedPaths);
191236
} catch (ex) {
192237
Logger.error(ex, `Failed to unstage (${untrackedPaths.length}) untracked files for patch`);
193238
}

src/quickpicks/repositoryPicker.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,12 @@ export async function getRepositoryOrShowPicker(
3636
container: Container,
3737
title: string,
3838
placeholder?: string,
39-
uri?: Uri,
39+
pathOrUri?: string | Uri,
4040
options?: { excludeWorktrees?: boolean; filter?: (r: Repository) => Promise<boolean> },
4141
): Promise<Repository | undefined> {
4242
return getRepositoryOrShowPickerCore(
4343
container,
44-
uri == null ? container.git.highlander : await container.git.getOrOpenRepository(uri),
44+
pathOrUri == null ? container.git.highlander : await container.git.getOrOpenRepository(pathOrUri),
4545
title,
4646
placeholder,
4747
options,

src/webviews/commitDetails/commitDetailsWebview.ts

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1991,20 +1991,33 @@ export class CommitDetailsWebviewProvider implements WebviewProvider<State, Stat
19911991
const [commit, file] = await this.getFileCommitFromContextOrParams(item);
19921992
if (commit == null) return;
19931993

1994-
if (commit.message == null) {
1995-
await commit.ensureFullDetails();
1996-
}
1994+
let args: CreatePatchCommandArgs;
1995+
if (commit.isUncommitted) {
1996+
const to = commit.isUncommittedStaged ? uncommittedStaged : uncommitted;
1997+
args = {
1998+
repoPath: commit.repoPath,
1999+
to: to,
2000+
title: to === uncommittedStaged ? 'Staged Changes' : 'Uncommitted Changes',
2001+
uris: [file.uri],
2002+
};
2003+
} else {
2004+
if (commit.message == null) {
2005+
await commit.ensureFullDetails();
2006+
}
19972007

1998-
const { summary: title, body: description } = splitCommitMessage(commit.message);
2008+
const { summary: title, body: description } = splitCommitMessage(commit.message);
19992009

2000-
void executeCommand<CreatePatchCommandArgs>('gitlens.copyPatchToClipboard', {
2001-
repoPath: commit.repoPath,
2002-
to: commit.ref,
2003-
from: `${commit.ref}^`,
2004-
title: title,
2005-
description: description,
2006-
uris: [file.uri],
2007-
});
2010+
args = {
2011+
repoPath: commit.repoPath,
2012+
to: commit.ref,
2013+
from: `${commit.ref}^`,
2014+
title: title,
2015+
description: description,
2016+
uris: [file.uri],
2017+
};
2018+
}
2019+
2020+
void executeCommand<CreatePatchCommandArgs>('gitlens.copyPatchToClipboard', args);
20082021
}
20092022

20102023
@command('gitlens.views.openFileRevision:')

0 commit comments

Comments
 (0)