Skip to content

Commit 7a06e5e

Browse files
committed
Shows AI rebase option only for recomposable branches
Updates branch context detection to add a '+recomposable' flag for branches that have commits eligible for recomposition, determined by checking recent merge bases against stored targets. Adjusts menu conditions so the AI rebase command is shown only when a branch can be recomposed, preventing unnecessary options for branches without suitable commits. (#4443, #4522)
1 parent f778e23 commit 7a06e5e

File tree

3 files changed

+60
-3
lines changed

3 files changed

+60
-3
lines changed

contributions.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
"menus": {
3030
"webview/context": [
3131
{
32-
"when": "webviewItem =~ /gitlens:branch\\b/ && !listMultiSelection",
32+
"when": "webviewItem =~ /gitlens:branch\\b(?=.*?\\b\\+recomposable\\b)/ && !listMultiSelection && !gitlens:readonly && !gitlens:untrusted && gitlens:gk:organization:ai:enabled",
3333
"group": "1_gitlens_actions",
3434
"order": 6
3535
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22852,7 +22852,7 @@
2285222852
},
2285322853
{
2285422854
"command": "gitlens.ai.aiRebaseBranch:graph",
22855-
"when": "webviewItem =~ /gitlens:branch\\b/ && !listMultiSelection",
22855+
"when": "webviewItem =~ /gitlens:branch\\b(?=.*?\\b\\+recomposable\\b)/ && !listMultiSelection && !gitlens:readonly && !gitlens:untrusted && gitlens:gk:organization:ai:enabled",
2285622856
"group": "1_gitlens_actions@6"
2285722857
},
2285822858
{

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

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,10 @@ export class GraphGitSubProvider implements GitGraphSubProvider {
347347

348348
branch = branchMap.get(tip);
349349
branchId = branch?.id ?? getBranchId(repoPath, false, tip);
350+
351+
// Check if branch has commits that can be recomposed
352+
const recomposable = await this.isBranchRecomposable(branch, repoPath);
353+
350354
context = {
351355
webviewItem: `gitlens:branch${head ? '+current' : ''}${
352356
branch?.upstream != null ? '+tracking' : ''
@@ -358,7 +362,7 @@ export class GraphGitSubProvider implements GitGraphSubProvider {
358362
: ''
359363
}${branch?.starred ? '+starred' : ''}${branch?.upstream?.state.ahead ? '+ahead' : ''}${
360364
branch?.upstream?.state.behind ? '+behind' : ''
361-
}`,
365+
}${recomposable ? '+recomposable' : ''}`,
362366
webviewItemValue: {
363367
type: 'branch',
364368
ref: createReference(tip, repoPath, {
@@ -617,6 +621,59 @@ export class GraphGitSubProvider implements GitGraphSubProvider {
617621
return getCommitsForGraphCore.call(this, defaultLimit, selectSha, undefined, cancellation);
618622
}
619623

624+
private async isBranchRecomposable(branch: GitBranch | undefined, repoPath: string): Promise<boolean> {
625+
if (!branch || branch.remote) return false;
626+
627+
try {
628+
const upstreamName = branch.upstream?.name;
629+
const svc = this.container.git.getRepositoryService(repoPath);
630+
631+
// Get stored merge target configurations
632+
const [storedTargetResult, storedMergeBaseResult] = await Promise.allSettled([
633+
svc.branches.getStoredMergeTargetBranchName?.(branch.name),
634+
svc.branches.getBaseBranchName?.(branch.name),
635+
]);
636+
const storedTarget = getSettledValue(storedTargetResult);
637+
const validStoredTarget = storedTarget && storedTarget !== upstreamName ? storedTarget : undefined;
638+
const storedMergeBase = getSettledValue(storedMergeBaseResult);
639+
const validStoredMergeBase =
640+
storedMergeBase && storedMergeBase !== upstreamName ? storedMergeBase : undefined;
641+
642+
// Select target with most recent common commit (closest to branch tip)
643+
const validTargets = [validStoredTarget, validStoredMergeBase];
644+
const targetCommit = await this.selectMostRecentMergeBase(branch.name, validTargets, svc);
645+
646+
return Boolean(targetCommit && targetCommit !== branch.sha);
647+
} catch {
648+
// If we can't determine, assume not recomposable
649+
return false;
650+
}
651+
}
652+
653+
private async selectMostRecentMergeBase(
654+
branchName: string,
655+
targets: (string | undefined)[],
656+
svc: ReturnType<typeof this.container.git.getRepositoryService>,
657+
): Promise<string | undefined> {
658+
const mergeBaseResults = await Promise.allSettled(
659+
targets.map(target => target && svc.refs.getMergeBase(branchName, target)),
660+
);
661+
const isString = (t: string | undefined): t is string => Boolean(t);
662+
const mergeBases = mergeBaseResults.map(result => getSettledValue(result)).filter(isString);
663+
664+
if (mergeBases.length === 0) return undefined;
665+
666+
let mostRecentMergeBase = mergeBases[0];
667+
for (let i = 1; i < mergeBases.length; i++) {
668+
const isCurrentMoreRecent = await svc.commits.isAncestorOf(mostRecentMergeBase, mergeBases[i]);
669+
if (isCurrentMoreRecent) {
670+
mostRecentMergeBase = mergeBases[i];
671+
}
672+
}
673+
674+
return mostRecentMergeBase;
675+
}
676+
620677
@log<GraphGitSubProvider['searchGraph']>({
621678
args: {
622679
1: s =>

0 commit comments

Comments
 (0)