Skip to content

Commit 32d6ef5

Browse files
Adds support for recomposing selected commits in graph and composer (#4771)
* Adds support for recomposing selected commits in graph and composer * Prevents draft commits from being dragged across locked commits * Fixes undo after recompose
1 parent 08a3ef2 commit 32d6ef5

File tree

18 files changed

+504
-97
lines changed

18 files changed

+504
-97
lines changed

ThirdPartyNotices.txt

Lines changed: 12 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,18 @@ This project incorporates components from the projects listed below.
2727
22. fflate version 0.8.2 (https://github.com/101arrowz/fflate)
2828
23. https-proxy-agent version 5.0.1 (https://github.com/TooTallNate/node-https-proxy-agent)
2929
24. iconv-lite version 0.6.3 (https://github.com/ashtuchkin/iconv-lite)
30-
25. ignore version 7.0.5 (https://github.com/kaelzhang/node-ignore)
31-
26. lit version 3.3.1 (https://github.com/lit/lit)
32-
27. marked version 17.0.0 (https://github.com/markedjs/marked)
33-
28. microsoft/vscode (https://github.com/microsoft/vscode)
34-
29. node-fetch version 2.7.0 (https://github.com/bitinn/node-fetch)
35-
30. os-browserify version 0.3.0 (https://github.com/CoderPuppy/os-browserify)
36-
31. path-browserify version 1.0.1 (https://github.com/browserify/path-browserify)
37-
32. react-dom version 19.2.0 (https://github.com/facebook/react)
38-
33. react version 19.2.0 (https://github.com/facebook/react)
39-
34. signal-utils version 0.21.1 (https://github.com/proposal-signals/signal-utils)
40-
35. slug version 11.0.1 (https://github.com/Trott/slug)
41-
36. sortablejs version 1.15.6 (https://github.com/SortableJS/Sortable)
42-
37. vscode-uri version 3.1.0 (https://github.com/microsoft/vscode-uri)
30+
25. lit version 3.3.1 (https://github.com/lit/lit)
31+
26. marked version 17.0.0 (https://github.com/markedjs/marked)
32+
27. microsoft/vscode (https://github.com/microsoft/vscode)
33+
28. node-fetch version 2.7.0 (https://github.com/bitinn/node-fetch)
34+
29. os-browserify version 0.3.0 (https://github.com/CoderPuppy/os-browserify)
35+
30. path-browserify version 1.0.1 (https://github.com/browserify/path-browserify)
36+
31. react-dom version 19.2.0 (https://github.com/facebook/react)
37+
32. react version 19.2.0 (https://github.com/facebook/react)
38+
33. signal-utils version 0.21.1 (https://github.com/proposal-signals/signal-utils)
39+
34. slug version 11.0.1 (https://github.com/Trott/slug)
40+
35. sortablejs version 1.15.6 (https://github.com/SortableJS/Sortable)
41+
36. vscode-uri version 3.1.0 (https://github.com/microsoft/vscode-uri)
4342

4443
%% @gk-nzaytsev/fast-string-truncated-width NOTICES AND INFORMATION BEGIN HERE
4544
=========================================
@@ -2077,32 +2076,6 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20772076
=========================================
20782077
END OF iconv-lite NOTICES AND INFORMATION
20792078

2080-
%% ignore NOTICES AND INFORMATION BEGIN HERE
2081-
=========================================
2082-
Copyright (c) 2013 Kael Zhang <[email protected]>, contributors
2083-
http://kael.me/
2084-
2085-
Permission is hereby granted, free of charge, to any person obtaining
2086-
a copy of this software and associated documentation files (the
2087-
"Software"), to deal in the Software without restriction, including
2088-
without limitation the rights to use, copy, modify, merge, publish,
2089-
distribute, sublicense, and/or sell copies of the Software, and to
2090-
permit persons to whom the Software is furnished to do so, subject to
2091-
the following conditions:
2092-
2093-
The above copyright notice and this permission notice shall be
2094-
included in all copies or substantial portions of the Software.
2095-
2096-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
2097-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
2098-
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
2099-
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
2100-
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
2101-
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
2102-
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2103-
=========================================
2104-
END OF ignore NOTICES AND INFORMATION
2105-
21062079
%% lit NOTICES AND INFORMATION BEGIN HERE
21072080
=========================================
21082081
BSD 3-Clause License

contributions.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4495,6 +4495,23 @@
44954495
]
44964496
}
44974497
},
4498+
"gitlens.recomposeSelectedCommits": {
4499+
"label": "Recompose Selected Commits (Preview)...",
4500+
"commandPalette": "gitlens:enabled && !gitlens:readonly && !gitlens:untrusted && gitlens:gk:organization:ai:enabled"
4501+
},
4502+
"gitlens.recomposeSelectedCommits:graph": {
4503+
"label": "Recompose Selected Commits (Preview)",
4504+
"icon": "$(sparkle)",
4505+
"menus": {
4506+
"webview/context": [
4507+
{
4508+
"when": "webviewItem =~ /gitlens:commit\\b(?=.*?\\b\\+unique\\b)/ && !gitlens:readonly && !gitlens:untrusted && gitlens:gk:organization:ai:enabled && listContiguousSelection && listUniqueBranchSelection",
4509+
"group": "1_gitlens_actions_1",
4510+
"order": 3
4511+
}
4512+
]
4513+
}
4514+
},
44984515
"gitlens.regenerateMarkdownDocument": {
44994516
"label": "Regenerate",
45004517
"icon": "$(refresh)",

package.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7993,6 +7993,16 @@
79937993
"title": "Recompose Commits (Preview)",
79947994
"icon": "$(sparkle)"
79957995
},
7996+
{
7997+
"command": "gitlens.recomposeSelectedCommits",
7998+
"title": "Recompose Selected Commits (Preview)...",
7999+
"category": "GitLens"
8000+
},
8001+
{
8002+
"command": "gitlens.recomposeSelectedCommits:graph",
8003+
"title": "Recompose Selected Commits (Preview)",
8004+
"icon": "$(sparkle)"
8005+
},
79968006
{
79978007
"command": "gitlens.regenerateMarkdownDocument",
79988008
"title": "Regenerate",
@@ -12929,6 +12939,14 @@
1292912939
"command": "gitlens.recomposeBranch:views",
1293012940
"when": "false"
1293112941
},
12942+
{
12943+
"command": "gitlens.recomposeSelectedCommits",
12944+
"when": "gitlens:enabled && !gitlens:readonly && !gitlens:untrusted && gitlens:gk:organization:ai:enabled"
12945+
},
12946+
{
12947+
"command": "gitlens.recomposeSelectedCommits:graph",
12948+
"when": "false"
12949+
},
1293212950
{
1293312951
"command": "gitlens.regenerateMarkdownDocument",
1293412952
"when": "false"
@@ -24944,6 +24962,11 @@
2494424962
"when": "webviewItem =~ /gitlens:(commit|stash)\\b/ && !listMultiSelection && !gitlens:untrusted && !gitlens:hasVirtualFolders && gitlens:gk:organization:drafts:enabled && config.gitlens.cloudPatches.enabled",
2494524963
"group": "1_gitlens_actions_1@3"
2494624964
},
24965+
{
24966+
"command": "gitlens.recomposeSelectedCommits:graph",
24967+
"when": "webviewItem =~ /gitlens:commit\\b(?=.*?\\b\\+unique\\b)/ && !gitlens:readonly && !gitlens:untrusted && gitlens:gk:organization:ai:enabled && listContiguousSelection && listUniqueBranchSelection",
24968+
"group": "1_gitlens_actions_1@3"
24969+
},
2494724970
{
2494824971
"command": "gitlens.graph.createTag",
2494924972
"when": "webviewItem =~ /gitlens:commit\\b/ && !listMultiSelection && !gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders",

src/commands/recomposeBranch.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@ import { isCommandContextViewNodeHasBranch } from './commandContext.utils';
1414
export interface RecomposeBranchCommandArgs {
1515
repoPath?: string;
1616
branchName?: string;
17+
commitShas?: string[];
1718
source?: Sources;
1819
}
1920

2021
@command()
2122
export class RecomposeBranchCommand extends GlCommandBase {
2223
constructor(private readonly container: Container) {
23-
super(['gitlens.recomposeBranch', 'gitlens.recomposeBranch:views']);
24+
super(['gitlens.recomposeBranch', 'gitlens.recomposeBranch:views', 'gitlens.recomposeSelectedCommits']);
2425
}
2526

2627
protected override preExecute(context: CommandContext, args?: RecomposeBranchCommandArgs): Promise<void> {
@@ -92,6 +93,7 @@ export class RecomposeBranchCommand extends GlCommandBase {
9293
source: args?.source,
9394
mode: 'preview',
9495
branchName: branchName,
96+
commitShas: args?.commitShas,
9597
});
9698
} catch (ex) {
9799
void window.showErrorMessage(`Failed to recompose branch: ${ex}`);

src/constants.commands.generated.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ export type ContributedCommands =
254254
| 'gitlens.quickOpenFileHistory:graphDetails'
255255
| 'gitlens.recomposeBranch:graph'
256256
| 'gitlens.recomposeBranch:views'
257+
| 'gitlens.recomposeSelectedCommits:graph'
257258
| 'gitlens.regenerateMarkdownDocument'
258259
| 'gitlens.restore.file:commitDetails'
259260
| 'gitlens.restore.file:graphDetails'
@@ -958,6 +959,7 @@ export type ContributedPaletteCommands =
958959
| 'gitlens.pushRepositories'
959960
| 'gitlens.quickOpenFileHistory'
960961
| 'gitlens.recomposeBranch'
962+
| 'gitlens.recomposeSelectedCommits'
961963
| 'gitlens.reset'
962964
| 'gitlens.resetViewsLayout'
963965
| 'gitlens.revealCommitInView'

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

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ export class GraphGitSubProvider implements GitGraphSubProvider {
156156
const avatars = new Map<string, string>();
157157
const ids = new Set<string>();
158158
const reachableFromHEAD = new Set<string>();
159+
const reachableFromBranches = new Map<string, Set<string>>();
159160
const rowStats: GitGraphRowsStats = new Map<string, GitGraphRowStats>();
160161
let pendingRowsStatsCount = 0;
161162
let iterations = 0;
@@ -454,6 +455,31 @@ export class GraphGitSubProvider implements GitGraphSubProvider {
454455
}
455456
}
456457

458+
if (refHeads.length > 0) {
459+
let branches = reachableFromBranches.get(shaOrRemapped);
460+
if (branches == null) {
461+
branches = new Set<string>();
462+
reachableFromBranches.set(shaOrRemapped, branches);
463+
}
464+
for (const refHead of refHeads) {
465+
branches.add(refHead.name);
466+
}
467+
}
468+
469+
const currentBranches = reachableFromBranches.get(shaOrRemapped);
470+
if (currentBranches != null && currentBranches.size > 0) {
471+
for (parent of parents) {
472+
let parentBranches = reachableFromBranches.get(parent);
473+
if (parentBranches == null) {
474+
parentBranches = new Set<string>();
475+
reachableFromBranches.set(parent, parentBranches);
476+
}
477+
for (const branchName of currentBranches) {
478+
parentBranches.add(branchName);
479+
}
480+
}
481+
}
482+
457483
stash = gitStash?.stashes.get(shaOrRemapped);
458484
if (stash != null) {
459485
contexts.row = serializeWebviewItemContext<GraphItemRefContext>({
@@ -469,6 +495,7 @@ export class GraphGitSubProvider implements GitGraphSubProvider {
469495
},
470496
});
471497

498+
const branches = reachableFromBranches.get(shaOrRemapped);
472499
rows.push({
473500
sha: shaOrRemapped,
474501
// Always only return the first parent for stashes, as it is a Git implementation for the index and untracked files
@@ -482,6 +509,7 @@ export class GraphGitSubProvider implements GitGraphSubProvider {
482509
remotes: refRemoteHeads,
483510
tags: refTags,
484511
contexts: contexts,
512+
reachableFromBranches: branches ? Array.from(branches) : undefined,
485513
});
486514

487515
if (stash.stats != null) {
@@ -501,10 +529,14 @@ export class GraphGitSubProvider implements GitGraphSubProvider {
501529
}
502530
}
503531

532+
const branches = reachableFromBranches.get(shaOrRemapped);
533+
const isUniqueToBranch =
534+
branches?.size === 1 && refTags.length === 0 && refRemoteHeads.length === 0;
535+
504536
contexts.row = serializeWebviewItemContext<GraphItemRefContext>({
505537
webviewItem: `gitlens:commit${head ? '+HEAD' : ''}${
506538
reachableFromHEAD.has(shaOrRemapped) ? '+current' : ''
507-
}`,
539+
}${isUniqueToBranch ? '+unique' : ''}`,
508540
webviewItemValue: {
509541
type: 'commit',
510542
ref: createReference(shaOrRemapped, repoPath, {
@@ -537,6 +569,7 @@ export class GraphGitSubProvider implements GitGraphSubProvider {
537569
remotes: refRemoteHeads,
538570
tags: refTags,
539571
contexts: contexts,
572+
reachableFromBranches: branches ? Array.from(branches) : undefined,
540573
});
541574

542575
if (commit.stats != null) {

src/git/models/graph.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export interface GitGraphRow extends GraphRow {
3434
remotes?: GitGraphRowRemoteHead[];
3535
tags?: GitGraphRowTag[];
3636
contexts?: GitGraphRowContexts;
37+
reachableFromBranches?: string[];
3738
}
3839

3940
export interface GitGraph {

0 commit comments

Comments
 (0)