Skip to content

Commit 1b803a8

Browse files
Adds merge detect actions & squash merge detect
1 parent 289fb9e commit 1b803a8

File tree

5 files changed

+128
-3
lines changed

5 files changed

+128
-3
lines changed

src/constants.commands.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,8 @@ type InternalGraphWebviewCommands =
148148
| 'gitlens.graph.skipPausedOperation';
149149

150150
type InternalHomeWebviewCommands =
151+
| 'gitlens.home.deleteBranchOrWorktree'
152+
| 'gitlens.home.pushBranch'
151153
| 'gitlens.home.openMergeTargetComparison'
152154
| 'gitlens.home.openPullRequestChanges'
153155
| 'gitlens.home.openPullRequestComparison'

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

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -446,12 +446,39 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
446446
} catch {}
447447

448448
// Cherry-pick detection (handles cherry-picks, rebases, etc)
449-
const data = await this.git.exec({ cwd: repoPath }, 'cherry', '--abbrev', '-v', into.name, branch.name);
449+
let data = await this.git.exec({ cwd: repoPath }, 'cherry', '--abbrev', '-v', into.name, branch.name);
450450
// Check if there are no lines or all lines startwith a `-` (i.e. likely merged)
451-
if (!data || data.split('\n').every(l => l.startsWith('-'))) {
451+
if (
452+
!data ||
453+
data
454+
.trim()
455+
.split('\n')
456+
.every(l => l.startsWith('-'))
457+
) {
452458
return { merged: true, confidence: 'high' };
453459
}
454460

461+
// Attempt to detect squash merges by checking if the diff of the branch can be cleanly removed from the target
462+
const mergeBase = await this.getMergeBase(repoPath, into.name, branch.name);
463+
data = await this.git.exec<string>({ cwd: repoPath }, 'diff', mergeBase, branch.name);
464+
if (data?.length) {
465+
// Create a temporary index file
466+
await using disposableIndex = await this.provider.staging!.createTemporaryIndex(repoPath, into.name);
467+
const { env } = disposableIndex;
468+
469+
data = await this.git.exec<string>(
470+
{ cwd: repoPath, env: env, stdin: data },
471+
'apply',
472+
'--cached',
473+
'--reverse',
474+
'--check',
475+
'-',
476+
);
477+
if (!data?.trim().length) {
478+
return { merged: true, confidence: 'medium' };
479+
}
480+
}
481+
455482
return { merged: false };
456483
} catch (ex) {
457484
Logger.error(ex, scope);

src/webviews/apps/plus/home/components/merge-target-status.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,23 @@ export class GlMergeTargetStatus extends LitElement {
285285
private renderContent() {
286286
const target = renderBranchName(this.target?.name);
287287

288+
const mergeTargetRef =
289+
this.mergedStatus?.merged && this.mergedStatus.localBranchOnly
290+
? {
291+
repoPath: this.branch.repoPath,
292+
branchId: this.mergedStatus.localBranchOnly.id!,
293+
branchName: this.mergedStatus.localBranchOnly.name,
294+
branchUpstreamName: this.mergedStatus.localBranchOnly.upstream?.name,
295+
}
296+
: this.target
297+
? {
298+
repoPath: this.target.repoPath,
299+
branchId: this.target.id,
300+
branchName: this.target.name,
301+
branchUpstreamName: undefined,
302+
}
303+
: undefined;
304+
288305
if (this.mergedStatus?.merged) {
289306
if (this.mergedStatus.localBranchOnly) {
290307
return html`<div class="header">
@@ -301,6 +318,29 @@ export class GlMergeTargetStatus extends LitElement {
301318
${this.mergedStatus.confidence !== 'highest' ? 'likely ' : ''}been merged into its merge
302319
target's local branch ${renderBranchName(this.mergedStatus.localBranchOnly.name)}.
303320
</p>
321+
<div class="button-container">
322+
<gl-button
323+
full
324+
href="${createCommandLink(
325+
'gitlens.home.pushBranch',
326+
mergeTargetRef! satisfies BranchRef,
327+
)}"
328+
>Push ${renderBranchName(this.mergedStatus.localBranchOnly.name)}</gl-button
329+
>
330+
<gl-button
331+
full
332+
appearance="secondary"
333+
href="${createCommandLink('gitlens.home.deleteBranchOrWorktree', [
334+
this.branchRef,
335+
mergeTargetRef,
336+
])}"
337+
>Delete
338+
${this.branch.worktree != null && !this.branch.worktree.isDefault
339+
? 'Worktree'
340+
: 'Branch'}
341+
${renderBranchName(this.branch.name, this.branch.worktree != null)}</gl-button
342+
>
343+
</div>
304344
</div>`;
305345
}
306346

@@ -318,6 +358,18 @@ export class GlMergeTargetStatus extends LitElement {
318358
${this.mergedStatus.confidence !== 'highest' ? 'likely ' : ''}been merged into its merge target
319359
${target}.
320360
</p>
361+
<div class="button-container">
362+
<gl-button
363+
full
364+
href="${createCommandLink('gitlens.home.deleteBranchOrWorktree', [
365+
this.branchRef,
366+
mergeTargetRef,
367+
])}"
368+
>Delete
369+
${this.branch.worktree != null && !this.branch.worktree.isDefault ? 'Worktree' : 'Branch'}
370+
${renderBranchName(this.branch.name, this.branch.worktree != null)}</gl-button
371+
>
372+
</div>
321373
</div>`;
322374
}
323375

src/webviews/home/homeWebview.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { getBranchTargetInfo } from '../../git/utils/-webview/branch.utils';
3636
import { getReferenceFromBranch } from '../../git/utils/-webview/reference.utils';
3737
import { sortBranches } from '../../git/utils/-webview/sorting';
3838
import { getOpenedWorktreesByBranch, groupWorktreesByBranch } from '../../git/utils/-webview/worktree.utils';
39+
import { getBranchNameWithoutRemote } from '../../git/utils/branch.utils';
3940
import { getComparisonRefsForPullRequest } from '../../git/utils/pullRequest.utils';
4041
import { createRevisionRange } from '../../git/utils/revision.utils';
4142
import { showPatchesView } from '../../plus/drafts/actions';
@@ -318,6 +319,8 @@ export class HomeWebviewProvider implements WebviewProvider<State, State, HomeWe
318319
(src?: Source) => this.container.subscription.validate({ force: true }, src),
319320
this,
320321
),
322+
registerCommand('gitlens.home.deleteBranchOrWorktree', this.deleteBranchOrWorktree, this),
323+
registerCommand('gitlens.home.pushBranch', this.pushBranch, this),
321324
registerCommand('gitlens.home.openMergeTargetComparison', this.mergeTargetCompare, this),
322325
registerCommand('gitlens.home.openPullRequestChanges', this.pullRequestChanges, this),
323326
registerCommand('gitlens.home.openPullRequestComparison', this.pullRequestCompare, this),
@@ -1163,6 +1166,45 @@ export class HomeWebviewProvider implements WebviewProvider<State, State, HomeWe
11631166
});
11641167
}
11651168

1169+
private async deleteBranchOrWorktree(ref: BranchRef, mergeTarget?: BranchRef) {
1170+
const repo = this._repositoryBranches.get(ref.repoPath);
1171+
const branch = repo?.branches.find(b => b.id === ref.branchId);
1172+
if (branch == null) return;
1173+
if (branch.current && mergeTarget != null) {
1174+
if (mergeTarget != null) {
1175+
const mergeTargetLocalBranch = getBranchNameWithoutRemote(mergeTarget.branchName);
1176+
await this.container.git.checkout(ref.repoPath, mergeTargetLocalBranch);
1177+
}
1178+
}
1179+
1180+
void executeGitCommand({
1181+
command: 'branch',
1182+
state: {
1183+
subcommand: 'delete',
1184+
repo: ref.repoPath,
1185+
references: branch,
1186+
},
1187+
});
1188+
}
1189+
1190+
private pushBranch(ref: BranchRef) {
1191+
void this.container.git.push(ref.repoPath, {
1192+
reference: {
1193+
name: ref.branchName,
1194+
ref: ref.branchId,
1195+
refType: 'branch',
1196+
remote: false,
1197+
repoPath: ref.repoPath,
1198+
upstream: ref.branchUpstreamName
1199+
? {
1200+
name: ref.branchUpstreamName,
1201+
missing: false,
1202+
}
1203+
: undefined,
1204+
},
1205+
});
1206+
}
1207+
11661208
private mergeTargetCompare(ref: BranchAndTargetRefs) {
11671209
return this.container.views.searchAndCompare.compare(ref.repoPath, ref.branchName, ref.mergeTargetName);
11681210
}
@@ -1397,7 +1439,7 @@ function getOverviewBranchesCore(
13971439
timestamp: timestamp,
13981440
status: branch.status,
13991441
upstream: branch.upstream,
1400-
worktree: wt ? { name: wt.name, uri: wt.uri.toString() } : undefined,
1442+
worktree: wt ? { name: wt.name, uri: wt.uri.toString(), isDefault: wt.isDefault } : undefined,
14011443
});
14021444
}
14031445

src/webviews/home/protocol.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ export interface GetOverviewBranch {
181181
worktree?: {
182182
name: string;
183183
uri: string;
184+
isDefault: boolean;
184185
};
185186
}
186187

@@ -320,6 +321,7 @@ export interface BranchRef {
320321
repoPath: string;
321322
branchId: string;
322323
branchName: string;
324+
branchUpstreamName?: string;
323325
}
324326

325327
export interface BranchAndTargetRefs extends BranchRef {

0 commit comments

Comments
 (0)