Skip to content

Commit 61ebcee

Browse files
committed
Cleanup and add setting
1 parent 5c4edad commit 61ebcee

File tree

12 files changed

+627
-76
lines changed

12 files changed

+627
-76
lines changed

package.json

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
"onFileSystem:review",
6060
"onWebviewPanel:IssueOverview",
6161
"onWebviewPanel:PullRequestOverview",
62-
"onWebviewPanel:github-pull-request.tasksDashboard"
62+
"onWebviewPanel:github-pull-request.projectTasksDashboard"
6363
],
6464
"browser": "./dist/browser/extension",
6565
"l10n": "./dist/browser/extension",
@@ -768,10 +768,18 @@
768768
"default": false,
769769
"description": "%githubIssues.alwaysPromptForNewIssueRepo.description%"
770770
},
771-
"githubIssues.taskDashboard.query": {
771+
"githubPullRequests.projectTasksDashboard.enabled": {
772+
"type": "boolean",
773+
"default": false,
774+
"markdownDescription": "Enable the experimental project tasks dashboard feature. This adds a new dashboard for managing GitHub issues and tasks.",
775+
"tags": [
776+
"experimental"
777+
]
778+
},
779+
"githubPullRequests.projectTasksDashboard.issueQuery": {
772780
"type": "string",
773781
"default": "state:open assignee:@me milestone:\"September 2025\"",
774-
"description": "The GitHub query to use for the task dashboard"
782+
"description": "The GitHub query to use for the project tasks dashboard"
775783
}
776784
}
777785
},
@@ -881,7 +889,8 @@
881889
"command": "pr.openTasksDashboard",
882890
"title": "%command.pr.openTasksDashboard.title%",
883891
"category": "%command.pull.request.category%",
884-
"icon": "$(dashboard)"
892+
"icon": "$(dashboard)",
893+
"when": "config.githubPullRequests.projectTasksDashboard.enabled"
885894
},
886895
{
887896
"command": "githubpr.remoteAgent",

src/github/dashboardWebviewProvider.ts

Lines changed: 250 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import * as vscode from 'vscode';
77
import Logger from '../common/logger';
88
import { ITelemetry } from '../common/telemetry';
99
import { getNonce, IRequestMessage, WebviewBase } from '../common/webview';
10+
import { CreatePullRequestDataModel } from '../view/createPullRequestDataModel';
1011
import { CopilotRemoteAgentManager } from './copilotRemoteAgent';
1112
import { FolderRepositoryManager, ReposManagerState } from './folderRepositoryManager';
1213
import { IssueModel } from './issueModel';
@@ -30,6 +31,7 @@ export interface DashboardReady {
3031
owner: string;
3132
name: string;
3233
};
34+
currentBranch?: string;
3335
}
3436

3537
export interface GlobalDashboardLoading {
@@ -66,6 +68,12 @@ export interface IssueData {
6668
url: string;
6769
createdAt: string;
6870
updatedAt: string;
71+
localTaskBranch?: string; // Name of the local task branch if it exists
72+
pullRequest?: {
73+
number: number;
74+
title: string;
75+
url: string;
76+
};
6977
}
7078

7179
export class DashboardWebviewProvider extends WebviewBase {
@@ -168,8 +176,9 @@ export class DashboardWebviewProvider extends WebviewBase {
168176

169177
const data = await this.getDashboardData();
170178

171-
// Get current repository info
179+
// Get current repository info and branch
172180
let repository: { owner: string; name: string } | undefined;
181+
let currentBranch: string | undefined;
173182
const targetRepos = this.getTargetRepositories();
174183
if (targetRepos.length > 0) {
175184
const [owner, name] = targetRepos[0].split('/');
@@ -178,12 +187,19 @@ export class DashboardWebviewProvider extends WebviewBase {
178187
}
179188
}
180189

190+
// Get current branch name
191+
if (this._repositoriesManager.folderManagers.length > 0) {
192+
const folderManager = this._repositoriesManager.folderManagers[0];
193+
currentBranch = folderManager.repository.state.HEAD?.name;
194+
}
195+
181196
const readyData: DashboardReady = {
182197
state: 'ready',
183198
issueQuery: this._issueQuery,
184199
activeSessions: data.activeSessions,
185200
milestoneIssues: data.milestoneIssues,
186-
repository
201+
repository,
202+
currentBranch
187203
};
188204
this._postMessage({
189205
command: 'update-dashboard',
@@ -531,7 +547,7 @@ export class DashboardWebviewProvider extends WebviewBase {
531547
}
532548

533549
private async convertIssueToData(issue: IssueModel): Promise<IssueData> {
534-
return {
550+
const issueData: IssueData = {
535551
number: issue.number,
536552
title: issue.title,
537553
assignee: issue.assignees?.[0]?.login,
@@ -541,6 +557,231 @@ export class DashboardWebviewProvider extends WebviewBase {
541557
createdAt: issue.createdAt,
542558
updatedAt: issue.updatedAt
543559
};
560+
561+
// Check for local task branch
562+
try {
563+
const taskBranchName = `task/issue-${issue.number}`;
564+
const localTaskBranch = await this.findLocalTaskBranch(taskBranchName);
565+
if (localTaskBranch) {
566+
issueData.localTaskBranch = localTaskBranch;
567+
568+
// Check for associated pull request for this branch
569+
const pullRequest = await this.findPullRequestForBranch(localTaskBranch);
570+
if (pullRequest) {
571+
issueData.pullRequest = pullRequest;
572+
}
573+
}
574+
} catch (error) {
575+
// If we can't check for branches, just continue without the local task info
576+
Logger.debug(`Could not check for local task branch: ${error}`, DashboardWebviewProvider.ID);
577+
}
578+
579+
return issueData;
580+
}
581+
582+
private async findLocalTaskBranch(branchName: string): Promise<string | undefined> {
583+
try {
584+
// Use the same logic as TaskManager to get all task branches
585+
for (const folderManager of this._repositoriesManager.folderManagers) {
586+
if (folderManager.repository.getRefs) {
587+
const refs = await folderManager.repository.getRefs({ pattern: 'refs/heads/' });
588+
589+
// Debug: log all branches
590+
Logger.debug(`All local branches: ${refs.map(r => r.name).join(', ')}`, DashboardWebviewProvider.ID);
591+
592+
// Filter for task branches and look for our specific branch
593+
const taskBranches = refs.filter(ref =>
594+
ref.name &&
595+
ref.name.startsWith('task/')
596+
);
597+
598+
Logger.debug(`Task branches: ${taskBranches.map(r => r.name).join(', ')}`, DashboardWebviewProvider.ID);
599+
Logger.debug(`Looking for branch: ${branchName}`, DashboardWebviewProvider.ID);
600+
601+
const matchingBranch = taskBranches.find(ref => ref.name === branchName);
602+
603+
if (matchingBranch) {
604+
Logger.debug(`Found local task branch: ${branchName}`, DashboardWebviewProvider.ID);
605+
return branchName;
606+
}
607+
}
608+
}
609+
Logger.debug(`Local task branch ${branchName} not found in any repository`, DashboardWebviewProvider.ID);
610+
return undefined;
611+
} catch (error) {
612+
Logger.debug(`Failed to find local task branch ${branchName}: ${error}`, DashboardWebviewProvider.ID);
613+
return undefined;
614+
}
615+
}
616+
617+
private async findPullRequestForBranch(branchName: string): Promise<{ number: number; title: string; url: string } | undefined> {
618+
try {
619+
for (const folderManager of this._repositoriesManager.folderManagers) {
620+
if (folderManager.gitHubRepositories.length === 0) {
621+
continue;
622+
}
623+
624+
// Try each GitHub repository in this folder manager
625+
for (const githubRepository of folderManager.gitHubRepositories) {
626+
try {
627+
// Use the getPullRequestForBranch method to find PRs for this branch
628+
const pullRequest = await githubRepository.getPullRequestForBranch(branchName, githubRepository.remote.owner);
629+
630+
if (pullRequest) {
631+
Logger.debug(`Found PR #${pullRequest.number} for branch ${branchName}`, DashboardWebviewProvider.ID);
632+
return {
633+
number: pullRequest.number,
634+
title: pullRequest.title,
635+
url: pullRequest.html_url
636+
};
637+
}
638+
} catch (error) {
639+
Logger.debug(`Failed to find PR for branch ${branchName} in ${githubRepository.remote.owner}/${githubRepository.remote.repositoryName}: ${error}`, DashboardWebviewProvider.ID);
640+
// Continue to next repository
641+
}
642+
}
643+
}
644+
645+
Logger.debug(`No PR found for branch ${branchName}`, DashboardWebviewProvider.ID);
646+
return undefined;
647+
} catch (error) {
648+
Logger.debug(`Failed to find PR for branch ${branchName}: ${error}`, DashboardWebviewProvider.ID);
649+
return undefined;
650+
}
651+
}
652+
653+
private async switchToMainBranch(): Promise<void> {
654+
try {
655+
// Find the first available folder manager with a repository
656+
const folderManager = this._repositoriesManager.folderManagers.find(fm =>
657+
fm.gitHubRepositories.length > 0
658+
);
659+
660+
if (!folderManager) {
661+
vscode.window.showErrorMessage('No GitHub repository found in the current workspace.');
662+
return;
663+
}
664+
665+
// Get the default branch (usually main or master)
666+
const defaultBranch = await this.getDefaultBranch(folderManager) || 'main';
667+
668+
// Switch to the default branch
669+
await folderManager.repository.checkout(defaultBranch);
670+
vscode.window.showInformationMessage(`Switched to branch: ${defaultBranch}`);
671+
672+
// Update dashboard to reflect the branch change
673+
setTimeout(() => {
674+
this.updateDashboard();
675+
}, 500);
676+
} catch (error) {
677+
Logger.error(`Failed to switch to main branch: ${error}`, DashboardWebviewProvider.ID);
678+
vscode.window.showErrorMessage(`Failed to switch to main branch: ${error}`);
679+
}
680+
}
681+
682+
private async createPullRequest(): Promise<void> {
683+
try {
684+
// Find the first available folder manager with a repository
685+
const folderManager = this._repositoriesManager.folderManagers.find(fm =>
686+
fm.gitHubRepositories.length > 0
687+
);
688+
689+
if (!folderManager) {
690+
vscode.window.showErrorMessage('No GitHub repository found in the current workspace.');
691+
return;
692+
}
693+
694+
const repository = folderManager.repository;
695+
const currentBranch = repository.state.HEAD?.name;
696+
697+
if (!currentBranch) {
698+
vscode.window.showErrorMessage('No current branch found.');
699+
return;
700+
}
701+
702+
// Check if there are any commits on this branch that aren't on main
703+
const hasCommits = await this.hasCommitsOnBranch(repository, currentBranch);
704+
705+
if (!hasCommits) {
706+
// No commits yet, stage files, generate commit message, and open SCM view
707+
try {
708+
// Stage all changed files
709+
const workingTreeChanges = repository.state.workingTreeChanges;
710+
if (workingTreeChanges.length > 0) {
711+
await repository.add(workingTreeChanges.map(change => change.uri.fsPath));
712+
Logger.debug(`Staged ${workingTreeChanges.length} files`, DashboardWebviewProvider.ID);
713+
}
714+
715+
// Open SCM view first
716+
await vscode.commands.executeCommand('workbench.view.scm');
717+
718+
// Generate commit message using Copilot
719+
try {
720+
await vscode.commands.executeCommand('github.copilot.git.generateCommitMessage');
721+
} catch (commitMsgError) {
722+
Logger.debug(`Failed to generate commit message: ${commitMsgError}`, DashboardWebviewProvider.ID);
723+
// Don't fail the whole operation if commit message generation fails
724+
}
725+
726+
vscode.window.showInformationMessage('Files staged and commit message generated. Make your first commit before creating a pull request.');
727+
} catch (stagingError) {
728+
Logger.error(`Failed to stage files: ${stagingError}`, DashboardWebviewProvider.ID);
729+
// Fall back to just opening SCM view
730+
await vscode.commands.executeCommand('workbench.view.scm');
731+
vscode.window.showInformationMessage('Make your first commit before creating a pull request.');
732+
}
733+
} else {
734+
// Has commits, proceed with create pull request flow
735+
await vscode.commands.executeCommand('pr.create');
736+
}
737+
} catch (error) {
738+
Logger.error(`Failed to create pull request: ${error}`, DashboardWebviewProvider.ID);
739+
vscode.window.showErrorMessage(`Failed to create pull request: ${error}`);
740+
}
741+
}
742+
743+
private async hasCommitsOnBranch(repository: any, branchName: string): Promise<boolean> {
744+
try {
745+
// Find the folder manager that contains this repository
746+
const folderManager = this._repositoriesManager.folderManagers.find(fm =>
747+
fm.repository === repository
748+
);
749+
750+
if (!folderManager) {
751+
Logger.debug(`Could not find folder manager for repository`, DashboardWebviewProvider.ID);
752+
return true;
753+
}
754+
755+
// Get the default branch (usually main or master)
756+
const defaultBranch = await this.getDefaultBranch(folderManager) || 'main';
757+
758+
// Get the GitHub repository for this folder manager
759+
const githubRepo = folderManager.gitHubRepositories[0];
760+
if (!githubRepo) {
761+
Logger.debug(`No GitHub repository found in folder manager`, DashboardWebviewProvider.ID);
762+
return true;
763+
}
764+
765+
// Create a CreatePullRequestDataModel to check for changes
766+
const dataModel = new CreatePullRequestDataModel(
767+
folderManager,
768+
githubRepo.remote.owner,
769+
defaultBranch,
770+
githubRepo.remote.owner,
771+
branchName,
772+
githubRepo.remote.repositoryName
773+
);
774+
775+
// Check if there are any changes between the branch and the base
776+
const commits = await dataModel.gitCommits();
777+
dataModel.dispose();
778+
779+
return commits.length > 0;
780+
} catch (error) {
781+
// If we can't determine commit status, assume there are commits and proceed
782+
Logger.debug(`Could not check branch commits: ${error}`, DashboardWebviewProvider.ID);
783+
return true;
784+
}
544785
}
545786

546787
protected override async _onDidReceiveMessage(message: IRequestMessage<any>): Promise<void> {
@@ -587,6 +828,12 @@ export class DashboardWebviewProvider extends WebviewBase {
587828
case 'open-external-url':
588829
await vscode.env.openExternal(vscode.Uri.parse(message.args.url));
589830
break;
831+
case 'switch-to-main':
832+
await this.switchToMainBranch();
833+
break;
834+
case 'create-pull-request':
835+
await this.createPullRequest();
836+
break;
590837
default:
591838
await super._onDidReceiveMessage(message);
592839
break;

0 commit comments

Comments
 (0)