Skip to content

Commit 22bf966

Browse files
committed
Adds AI rebase commands
(#4443)
1 parent 3a7fd74 commit 22bf966

File tree

7 files changed

+208
-1
lines changed

7 files changed

+208
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
88

99
### Added
1010

11-
- Adds AI powered operations for a branch: "Explain Unpushed Changed". They are added to the _Commit Graph_ and views context menu for branches ([#4443](https://github.com/gitkraken/vscode-gitlens/issues/4443))
11+
- Adds AI powered operations for a branch: "Recompose branch commits", "Recompose unpushed commits", "Explain Unpushed Changed". They are added to the _Commit Graph_ and views context menu for branches ([#4443](https://github.com/gitkraken/vscode-gitlens/issues/4443))
1212

1313
### Fixed
1414

contributions.json

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,58 @@
2323
]
2424
}
2525
},
26+
"gitlens.ai.aiRebaseBranch:graph": {
27+
"label": "AI Recompose Branch Commits (Preview)",
28+
"icon": "$(sparkle)",
29+
"menus": {
30+
"webview/context": [
31+
{
32+
"when": "webviewItem =~ /gitlens:branch\\b(?=.*?\\b\\+recomposable\\b)/ && !listMultiSelection && !gitlens:readonly && !gitlens:untrusted && gitlens:gk:organization:ai:enabled",
33+
"group": "1_gitlens_actions",
34+
"order": 6
35+
}
36+
]
37+
}
38+
},
39+
"gitlens.ai.aiRebaseBranch:views": {
40+
"label": "AI Recompose Branch Commits (Preview)",
41+
"icon": "$(sparkle)",
42+
"menus": {
43+
"view/item/context": [
44+
{
45+
"when": "viewItem =~ /gitlens:branch\\b(?=.*?\\b\\+recomposable\\b)/ && !listMultiSelection && !gitlens:readonly && !gitlens:untrusted && gitlens:gk:organization:ai:enabled",
46+
"group": "1_gitlens_actions",
47+
"order": 6
48+
}
49+
]
50+
}
51+
},
52+
"gitlens.ai.aiRebaseUnpushed:graph": {
53+
"label": "AI Recompose Unpushed Commits (Preview)",
54+
"icon": "$(sparkle)",
55+
"menus": {
56+
"webview/context": [
57+
{
58+
"when": "webviewItem =~ /gitlens:branch\\b(?=.*?\\b\\+ahead\\b)/ && !listMultiSelection && !gitlens:readonly && !gitlens:untrusted && gitlens:gk:organization:ai:enabled",
59+
"group": "1_gitlens_actions",
60+
"order": 7
61+
}
62+
]
63+
}
64+
},
65+
"gitlens.ai.aiRebaseUnpushed:views": {
66+
"label": "AI Recompose Unpushed Commits (Preview)",
67+
"icon": "$(sparkle)",
68+
"menus": {
69+
"view/item/context": [
70+
{
71+
"when": "viewItem =~ /gitlens:branch\\b(?=.*?\\b\\+ahead\\b)/ && !listMultiSelection && !gitlens:readonly && !gitlens:untrusted && gitlens:gk:organization:ai:enabled",
72+
"group": "1_gitlens_actions",
73+
"order": 7
74+
}
75+
]
76+
}
77+
},
2678
"gitlens.ai.explainBranch": {
2779
"label": "Explain Branch Changes (Preview)...",
2880
"commandPalette": "gitlens:enabled && !gitlens:readonly && !gitlens:untrusted && gitlens:gk:organization:ai:enabled"

package.json

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6189,6 +6189,26 @@
61896189
"category": "GitLens",
61906190
"icon": "$(person-add)"
61916191
},
6192+
{
6193+
"command": "gitlens.ai.aiRebaseBranch:graph",
6194+
"title": "AI Recompose Branch Commits (Preview)",
6195+
"icon": "$(sparkle)"
6196+
},
6197+
{
6198+
"command": "gitlens.ai.aiRebaseBranch:views",
6199+
"title": "AI Recompose Branch Commits (Preview)",
6200+
"icon": "$(sparkle)"
6201+
},
6202+
{
6203+
"command": "gitlens.ai.aiRebaseUnpushed:graph",
6204+
"title": "AI Recompose Unpushed Commits (Preview)",
6205+
"icon": "$(sparkle)"
6206+
},
6207+
{
6208+
"command": "gitlens.ai.aiRebaseUnpushed:views",
6209+
"title": "AI Recompose Unpushed Commits (Preview)",
6210+
"icon": "$(sparkle)"
6211+
},
61926212
{
61936213
"command": "gitlens.ai.explainBranch",
61946214
"title": "Explain Branch Changes (Preview)...",
@@ -11014,6 +11034,22 @@
1101411034
"command": "gitlens.addAuthors",
1101511035
"when": "gitlens:enabled && !gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders"
1101611036
},
11037+
{
11038+
"command": "gitlens.ai.aiRebaseBranch:graph",
11039+
"when": "false"
11040+
},
11041+
{
11042+
"command": "gitlens.ai.aiRebaseBranch:views",
11043+
"when": "false"
11044+
},
11045+
{
11046+
"command": "gitlens.ai.aiRebaseUnpushed:graph",
11047+
"when": "false"
11048+
},
11049+
{
11050+
"command": "gitlens.ai.aiRebaseUnpushed:views",
11051+
"when": "false"
11052+
},
1101711053
{
1101811054
"command": "gitlens.ai.explainBranch",
1101911055
"when": "gitlens:enabled && !gitlens:readonly && !gitlens:untrusted && gitlens:gk:organization:ai:enabled"
@@ -17410,6 +17446,16 @@
1741017446
"when": "viewItem =~ /gitlens:branch\\b(?=.*?\\b\\+(remote|tracking)\\b)(?!.*?\\b\\+closed\\b)/ && !listMultiSelection && !gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders && gitlens:repos:withRemotes",
1741117447
"group": "1_gitlens_actions@3"
1741217448
},
17449+
{
17450+
"command": "gitlens.ai.aiRebaseBranch:views",
17451+
"when": "viewItem =~ /gitlens:branch\\b(?=.*?\\b\\+recomposable\\b)/ && !listMultiSelection && !gitlens:readonly && !gitlens:untrusted && gitlens:gk:organization:ai:enabled",
17452+
"group": "1_gitlens_actions@6"
17453+
},
17454+
{
17455+
"command": "gitlens.ai.aiRebaseUnpushed:views",
17456+
"when": "viewItem =~ /gitlens:branch\\b(?=.*?\\b\\+ahead\\b)/ && !listMultiSelection && !gitlens:readonly && !gitlens:untrusted && gitlens:gk:organization:ai:enabled",
17457+
"group": "1_gitlens_actions@7"
17458+
},
1741317459
{
1741417460
"command": "gitlens.views.mergeBranchInto",
1741517461
"when": "viewItem =~ /gitlens:branch\\b(?!.*?\\b\\+current\\b)(?!.*?\\b\\+closed\\b)/ && !listMultiSelection && !gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders",
@@ -23287,6 +23333,16 @@
2328723333
"when": "webviewItem =~ /gitlens:branch\\b(?=.*?\\b\\+(remote|tracking)\\b)(?!.*?\\b\\+closed\\b)/ && !gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders && gitlens:repos:withRemotes",
2328823334
"group": "1_gitlens_actions@3"
2328923335
},
23336+
{
23337+
"command": "gitlens.ai.aiRebaseBranch:graph",
23338+
"when": "webviewItem =~ /gitlens:branch\\b(?=.*?\\b\\+recomposable\\b)/ && !listMultiSelection && !gitlens:readonly && !gitlens:untrusted && gitlens:gk:organization:ai:enabled",
23339+
"group": "1_gitlens_actions@6"
23340+
},
23341+
{
23342+
"command": "gitlens.ai.aiRebaseUnpushed:graph",
23343+
"when": "webviewItem =~ /gitlens:branch\\b(?=.*?\\b\\+ahead\\b)/ && !listMultiSelection && !gitlens:readonly && !gitlens:untrusted && gitlens:gk:organization:ai:enabled",
23344+
"group": "1_gitlens_actions@7"
23345+
},
2329023346
{
2329123347
"command": "gitlens.graph.mergeBranchInto",
2329223348
"when": "webviewItem =~ /gitlens:branch\\b(?!.*?\\b\\+current\\b)/ && !gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders",

src/constants.commands.generated.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
export type ContributedCommands =
55
| ContributedKeybindingCommands
66
| ContributedPaletteCommands
7+
| 'gitlens.ai.aiRebaseBranch:graph'
8+
| 'gitlens.ai.aiRebaseBranch:views'
9+
| 'gitlens.ai.aiRebaseUnpushed:graph'
10+
| 'gitlens.ai.aiRebaseUnpushed:views'
711
| 'gitlens.ai.explainBranch:graph'
812
| 'gitlens.ai.explainBranch:views'
913
| 'gitlens.ai.explainCommit:graph'

src/views/nodes/branchNode.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,7 @@ export class BranchNode
394394
useBaseNameOnly: !(this.view.config.branches?.layout !== 'tree' || this.compacted || this.avoidCompacting),
395395
worktree: this.worktree,
396396
worktreesByBranch: this.context.worktreesByBranch,
397+
isRecomposable: Boolean(this.mergeBase),
397398
});
398399

399400
// TODO@axosoft-ramint Temporary workaround, remove when our git commands work on closed repos.
@@ -519,6 +520,7 @@ export async function getBranchNodeParts(
519520
useBaseNameOnly: boolean;
520521
worktree?: GitWorktree;
521522
worktreesByBranch?: Map<string, GitWorktree>;
523+
isRecomposable?: boolean;
522524
},
523525
): Promise<{
524526
label: string;
@@ -718,6 +720,10 @@ export async function getBranchNodeParts(
718720
iconPath = getBranchIconPath(container, branch);
719721
}
720722

723+
if (options?.isRecomposable) {
724+
contextValue += '+recomposable';
725+
}
726+
721727
return {
722728
label: label,
723729
description: description,

src/views/viewCommands.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -927,6 +927,45 @@ export class ViewCommands implements Disposable {
927927
});
928928
}
929929

930+
@command('gitlens.ai.aiRebaseBranch:views')
931+
@log()
932+
private async aiRebaseBranch(node: BranchNode) {
933+
const mergeBase = node.is('branch') && node.mergeBase;
934+
if (!mergeBase) {
935+
return Promise.resolve();
936+
}
937+
938+
await executeCommand<GenerateRebaseCommandArgs>('gitlens.ai.generateRebase', {
939+
repoPath: node.repoPath,
940+
head: node.ref,
941+
base: createReference(mergeBase.branch, node.repoPath, {
942+
refType: 'branch',
943+
name: mergeBase.branch,
944+
remote: mergeBase.remote,
945+
}),
946+
source: { source: 'view', detail: 'branch' },
947+
});
948+
}
949+
950+
@command('gitlens.ai.aiRebaseUnpushed:views')
951+
@log()
952+
private async aiRebaseUnpushed(node: BranchNode) {
953+
if (!node.is('branch') || !node.branch.upstream) {
954+
return Promise.resolve();
955+
}
956+
957+
await executeCommand<GenerateRebaseCommandArgs>('gitlens.ai.generateRebase', {
958+
repoPath: node.repoPath,
959+
head: node.ref,
960+
base: createReference(node.branch.upstream.name, node.repoPath, {
961+
refType: 'branch',
962+
name: node.branch.upstream.name,
963+
remote: true,
964+
}),
965+
source: { source: 'view', detail: 'branch' },
966+
});
967+
}
968+
930969
@command('gitlens.ai.explainUnpushed:views')
931970
@log()
932971
private async explainUnpushed(node: BranchNode) {

src/webviews/plus/graph/graphWebview.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,8 @@ export class GraphWebviewProvider implements WebviewProvider<State, State, Graph
532532
this.host.registerWebviewCommand('gitlens.graph.associateIssueWithBranch', this.associateIssueWithBranch),
533533
this.host.registerWebviewCommand('gitlens.changeUpstream:graph', this.changeUpstreamBranch),
534534
this.host.registerWebviewCommand('gitlens.setUpstream:graph', this.changeUpstreamBranch),
535+
this.host.registerWebviewCommand('gitlens.ai.aiRebaseBranch:graph', this.aiRebaseBranch),
536+
this.host.registerWebviewCommand('gitlens.ai.aiRebaseUnpushed:graph', this.aiRebaseUnpushed),
535537

536538
this.host.registerWebviewCommand('gitlens.graph.switchToBranch', this.switchTo),
537539

@@ -3275,6 +3277,54 @@ export class GraphWebviewProvider implements WebviewProvider<State, State, Graph
32753277
return Promise.resolve();
32763278
}
32773279

3280+
@log()
3281+
private aiRebaseBranch(item?: GraphItemContext) {
3282+
if (isGraphItemRefContext(item, 'branch')) {
3283+
const { ref, mergeBase } = item.webviewItemValue;
3284+
3285+
if (!mergeBase) {
3286+
return Promise.resolve();
3287+
}
3288+
3289+
return executeCommand<GenerateRebaseCommandArgs>('gitlens.ai.generateRebase', {
3290+
repoPath: ref.repoPath,
3291+
head: ref,
3292+
base: createReference(mergeBase.branch, ref.repoPath, {
3293+
refType: 'branch',
3294+
name: mergeBase.branch,
3295+
remote: mergeBase.remote,
3296+
}),
3297+
source: { source: 'graph' },
3298+
});
3299+
}
3300+
3301+
return Promise.resolve();
3302+
}
3303+
3304+
@log()
3305+
private aiRebaseUnpushed(item?: GraphItemContext) {
3306+
if (isGraphItemRefContext(item, 'branch')) {
3307+
const { ref } = item.webviewItemValue;
3308+
3309+
if (!ref.upstream) {
3310+
return Promise.resolve();
3311+
}
3312+
3313+
return executeCommand<GenerateRebaseCommandArgs>('gitlens.ai.generateRebase', {
3314+
repoPath: ref.repoPath,
3315+
head: ref,
3316+
base: createReference(ref.upstream.name, ref.repoPath, {
3317+
refType: 'branch',
3318+
name: ref.upstream.name,
3319+
remote: true,
3320+
}),
3321+
source: { source: 'graph' },
3322+
});
3323+
}
3324+
3325+
return Promise.resolve();
3326+
}
3327+
32783328
@log()
32793329
private cherryPick(item?: GraphItemContext) {
32803330
const { selection } = this.getGraphItemRefs(item, 'revision');

0 commit comments

Comments
 (0)