Extension: Merge show team relevant repo management buttons (#17)
* Extension: Show team relevant repo management buttons * fix formatting * Fix: Actually delete team repo contents Was passing incomplete path to remove API * Extra logging * Formatting * Fix path on windows * Remove logs --------- Co-authored-by: orosmatthew <orosmatthew@pm.me>
This commit is contained in:
parent
40634d80e6
commit
42a58afbde
@ -68,8 +68,25 @@
|
|||||||
{
|
{
|
||||||
"command": "bwcontest.toggleFastPolling",
|
"command": "bwcontest.toggleFastPolling",
|
||||||
"title": "BWContest Developer: Toggle Fast Polling"
|
"title": "BWContest Developer: Toggle Fast Polling"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "bwcontest.refreshState",
|
||||||
|
"title": "Refresh"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "bwcontest.showTestSubmitPage",
|
||||||
|
"title": "BWContest: Show Test/Submit Page"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"menus": {
|
||||||
|
"view/title": [
|
||||||
|
{
|
||||||
|
"command": "bwcontest.refreshState",
|
||||||
|
"group": "navigation",
|
||||||
|
"when": "view == bwcontest-sidebar"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"vscode:prepublish": "npm run compile",
|
"vscode:prepublish": "npm run compile",
|
||||||
|
@ -1,6 +1,15 @@
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { getNonce } from './getNonce';
|
import { getNonce } from './getNonce';
|
||||||
import { cloneAndOpenRepo } from './extension';
|
import {
|
||||||
|
RepoState,
|
||||||
|
clearCachedRepoState,
|
||||||
|
cloneOpenRepo,
|
||||||
|
cloneRepo,
|
||||||
|
openRepo,
|
||||||
|
getCachedRepoState,
|
||||||
|
refreshRepoState,
|
||||||
|
repoStateChanged
|
||||||
|
} from './teamRepoManager';
|
||||||
import { BWPanel } from './problemPanel';
|
import { BWPanel } from './problemPanel';
|
||||||
import urlJoin from 'url-join';
|
import urlJoin from 'url-join';
|
||||||
import outputPanelLog from './outputPanelLog';
|
import outputPanelLog from './outputPanelLog';
|
||||||
@ -22,12 +31,15 @@ import { startTeamStatusPolling, stopTeamStatusPolling } from './contestMonitor/
|
|||||||
export type WebviewMessageType =
|
export type WebviewMessageType =
|
||||||
| { msg: 'onLogin'; data: TeamData }
|
| { msg: 'onLogin'; data: TeamData }
|
||||||
| { msg: 'onLogout' }
|
| { msg: 'onLogout' }
|
||||||
| { msg: 'teamStatusUpdated'; data: SidebarTeamStatus | null };
|
| { msg: 'teamStatusUpdated'; data: SidebarTeamStatus | null }
|
||||||
|
| { msg: 'repoStateUpdated'; data: RepoState };
|
||||||
|
|
||||||
export type MessageType =
|
export type MessageType =
|
||||||
| { msg: 'onTestAndSubmit' }
|
| { msg: 'onTestAndSubmit' }
|
||||||
| { msg: 'onUIMount' }
|
| { msg: 'onUIMount' }
|
||||||
| { msg: 'onClone'; data: { contestId: number; teamId: number } }
|
| { msg: 'onCloneOpenRepo'; data: { contestId: number; teamId: number } }
|
||||||
|
| { msg: 'onCloneRepo'; data: { contestId: number; teamId: number } }
|
||||||
|
| { msg: 'onOpenRepo'; data: { contestId: number; teamId: number } }
|
||||||
| { msg: 'onLogin'; data: { teamName: string; password: string } }
|
| { msg: 'onLogin'; data: { teamName: string; password: string } }
|
||||||
| { msg: 'onLogout' };
|
| { msg: 'onLogout' };
|
||||||
|
|
||||||
@ -75,6 +87,22 @@ export class SidebarProvider implements vscode.WebviewViewProvider {
|
|||||||
submissionsChangedEventArgs.changedProblemIds
|
submissionsChangedEventArgs.changedProblemIds
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const currentRepoState = getCachedRepoState();
|
||||||
|
outputPanelLog.info(
|
||||||
|
'When SidebarProvider constructed, cached repo state is: ' + currentRepoState
|
||||||
|
);
|
||||||
|
this.updateRepoStatus(currentRepoState);
|
||||||
|
|
||||||
|
repoStateChanged.add((repoChangedEventArgs) => {
|
||||||
|
outputPanelLog.trace('Repo status updating from event');
|
||||||
|
|
||||||
|
if (!repoChangedEventArgs) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateRepoStatus(repoChangedEventArgs.state);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleLogin(
|
private async handleLogin(
|
||||||
@ -129,6 +157,11 @@ export class SidebarProvider implements vscode.WebviewViewProvider {
|
|||||||
'After login, cached submission list is: ' + JSON.stringify(currentSubmissionsList)
|
'After login, cached submission list is: ' + JSON.stringify(currentSubmissionsList)
|
||||||
);
|
);
|
||||||
this.updateTeamStatus(currentSubmissionsList);
|
this.updateTeamStatus(currentSubmissionsList);
|
||||||
|
|
||||||
|
const currentRepoState = getCachedRepoState();
|
||||||
|
outputPanelLog.info('After login, cached repo state is: ' + currentRepoState);
|
||||||
|
this.updateRepoStatus(currentRepoState);
|
||||||
|
refreshRepoState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleLogout(webviewPostMessage: (m: WebviewMessageType) => void) {
|
private async handleLogout(webviewPostMessage: (m: WebviewMessageType) => void) {
|
||||||
@ -189,6 +222,8 @@ export class SidebarProvider implements vscode.WebviewViewProvider {
|
|||||||
stopTeamStatusPolling();
|
stopTeamStatusPolling();
|
||||||
clearCachedContestTeamState();
|
clearCachedContestTeamState();
|
||||||
|
|
||||||
|
clearCachedRepoState();
|
||||||
|
|
||||||
this.context.globalState.update('token', undefined);
|
this.context.globalState.update('token', undefined);
|
||||||
this.context.globalState.update('teamData', undefined);
|
this.context.globalState.update('teamData', undefined);
|
||||||
}
|
}
|
||||||
@ -236,6 +271,24 @@ export class SidebarProvider implements vscode.WebviewViewProvider {
|
|||||||
this.webview.postMessage(message);
|
this.webview.postMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public updateRepoStatus(state: RepoState) {
|
||||||
|
if (this.webview == null) {
|
||||||
|
outputPanelLog.trace('Not updating sidebar repo state because webview is null');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const message: WebviewMessageType = {
|
||||||
|
msg: 'repoStateUpdated',
|
||||||
|
data: state
|
||||||
|
};
|
||||||
|
|
||||||
|
outputPanelLog.trace(
|
||||||
|
'Posting repoStateUpdated to webview with message: ' + JSON.stringify(message)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.webview.postMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
public resolveWebviewView(webviewView: vscode.WebviewView) {
|
public resolveWebviewView(webviewView: vscode.WebviewView) {
|
||||||
outputPanelLog.trace('SidebarProvider resolveWebviewView');
|
outputPanelLog.trace('SidebarProvider resolveWebviewView');
|
||||||
const webview = webviewView.webview;
|
const webview = webviewView.webview;
|
||||||
@ -273,11 +326,23 @@ export class SidebarProvider implements vscode.WebviewViewProvider {
|
|||||||
'onUIMount, currentSubmissionsList is ' + JSON.stringify(currentSubmissionsList)
|
'onUIMount, currentSubmissionsList is ' + JSON.stringify(currentSubmissionsList)
|
||||||
);
|
);
|
||||||
this.updateTeamStatus(currentSubmissionsList);
|
this.updateTeamStatus(currentSubmissionsList);
|
||||||
|
|
||||||
|
const currentRepoState = getCachedRepoState();
|
||||||
|
outputPanelLog.trace('onUIMount, currentRepoState is ' + currentRepoState);
|
||||||
|
this.updateRepoStatus(currentRepoState);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'onClone': {
|
case 'onCloneOpenRepo': {
|
||||||
cloneAndOpenRepo(m.data.contestId, m.data.teamId);
|
cloneOpenRepo(m.data.contestId, m.data.teamId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'onCloneRepo': {
|
||||||
|
cloneRepo(m.data.contestId, m.data.teamId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'onOpenRepo': {
|
||||||
|
openRepo(m.data.contestId, m.data.teamId);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'onLogin': {
|
case 'onLogin': {
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { SidebarProvider } from './SidebarProvider';
|
import { SidebarProvider } from './SidebarProvider';
|
||||||
import * as fs from 'fs-extra';
|
|
||||||
import urlJoin from 'url-join';
|
|
||||||
import git from 'isomorphic-git';
|
|
||||||
import path = require('path');
|
|
||||||
import http from 'isomorphic-git/http/node';
|
|
||||||
import outputPanelLog from './outputPanelLog';
|
import outputPanelLog from './outputPanelLog';
|
||||||
import {
|
import {
|
||||||
startTeamStatusPollingOnActivation,
|
startTeamStatusPollingOnActivation,
|
||||||
stopTeamStatusPolling,
|
stopTeamStatusPolling,
|
||||||
useFastPolling
|
useFastPolling
|
||||||
} from './contestMonitor/pollingService';
|
} from './contestMonitor/pollingService';
|
||||||
|
import {
|
||||||
|
clearCachedRepoState,
|
||||||
|
refreshRepoState,
|
||||||
|
setRepoManagerExtensionContext
|
||||||
|
} from './teamRepoManager';
|
||||||
|
import { BWPanel } from './problemPanel';
|
||||||
|
|
||||||
export interface BWContestSettings {
|
export interface BWContestSettings {
|
||||||
repoBaseUrl: string;
|
repoBaseUrl: string;
|
||||||
@ -24,93 +25,6 @@ export function extensionSettings(): BWContestSettings {
|
|||||||
return vscode.workspace.getConfiguration().get<BWContestSettings>('BWContest')!;
|
return vscode.workspace.getConfiguration().get<BWContestSettings>('BWContest')!;
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeAllWorkspaces() {
|
|
||||||
const workspaceFolders = vscode.workspace.workspaceFolders;
|
|
||||||
if (!workspaceFolders) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const removedFolders = vscode.workspace.updateWorkspaceFolders(0, workspaceFolders.length);
|
|
||||||
if (!removedFolders) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function cloneAndOpenRepo(contestId: number, teamId: number) {
|
|
||||||
const currentSettings = vscode.workspace.getConfiguration().get<BWContestSettings>('BWContest');
|
|
||||||
|
|
||||||
if (!currentSettings || currentSettings.repoBaseUrl == '') {
|
|
||||||
vscode.window.showErrorMessage('BWContest: BWContest.repoBaseURL not set');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!currentSettings || currentSettings.repoClonePath == '') {
|
|
||||||
vscode.window.showErrorMessage('BWContest: BWContest.repoClonePath not set');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!currentSettings || currentSettings.webUrl == '') {
|
|
||||||
vscode.window.showErrorMessage('BWContest: BWContest.webUrl not set');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const repoUrl = urlJoin(
|
|
||||||
currentSettings.repoBaseUrl,
|
|
||||||
contestId.toString(),
|
|
||||||
`${teamId.toString()}.git`
|
|
||||||
);
|
|
||||||
|
|
||||||
const repoName = teamId.toString();
|
|
||||||
|
|
||||||
if (!fs.existsSync(`${currentSettings.repoClonePath}/BWContest`)) {
|
|
||||||
fs.mkdirSync(`${currentSettings.repoClonePath}/BWContest`);
|
|
||||||
}
|
|
||||||
if (!fs.existsSync(`${currentSettings.repoClonePath}/BWContest/${contestId.toString()}`)) {
|
|
||||||
fs.mkdirSync(`${currentSettings.repoClonePath}/BWContest/${contestId.toString()}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const clonedRepoPath = `${
|
|
||||||
currentSettings.repoClonePath
|
|
||||||
}/BWContest/${contestId.toString()}/${repoName}`;
|
|
||||||
|
|
||||||
if (fs.existsSync(clonedRepoPath)) {
|
|
||||||
const confirm = await vscode.window.showWarningMessage(
|
|
||||||
'The repo already exists. Do you want to replace it?',
|
|
||||||
'Delete and Replace',
|
|
||||||
'Cancel'
|
|
||||||
);
|
|
||||||
if (confirm !== 'Delete and Replace') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
closeAllWorkspaces();
|
|
||||||
fs.removeSync(clonedRepoPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
const dir = path.join(currentSettings.repoClonePath, 'BWContest', contestId.toString(), repoName);
|
|
||||||
outputPanelLog.info(`Running 'git clone' to directory: ${dir}`);
|
|
||||||
try {
|
|
||||||
await git.clone({ fs, http, dir, url: repoUrl });
|
|
||||||
} catch (error) {
|
|
||||||
outputPanelLog.error(
|
|
||||||
"Failed to 'git clone'. The git server might be incorrectly configured. Error: " + error
|
|
||||||
);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
outputPanelLog.info('Closing workspaces...');
|
|
||||||
closeAllWorkspaces();
|
|
||||||
|
|
||||||
const addedFolder = vscode.workspace.updateWorkspaceFolders(
|
|
||||||
vscode.workspace.workspaceFolders?.length ?? 0,
|
|
||||||
0,
|
|
||||||
{ uri: vscode.Uri.file(clonedRepoPath), name: 'BWContest' }
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!addedFolder) {
|
|
||||||
vscode.window.showErrorMessage('BWContest: Failed to open cloned repo');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
vscode.window.showInformationMessage('BWContest: Repo cloned and opened');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function activate(context: vscode.ExtensionContext) {
|
export function activate(context: vscode.ExtensionContext) {
|
||||||
outputPanelLog.info('BWContest Extension Activated');
|
outputPanelLog.info('BWContest Extension Activated');
|
||||||
|
|
||||||
@ -133,13 +47,27 @@ export function activate(context: vscode.ExtensionContext) {
|
|||||||
|
|
||||||
fastPolling = !fastPolling;
|
fastPolling = !fastPolling;
|
||||||
useFastPolling(fastPolling);
|
useFastPolling(fastPolling);
|
||||||
|
}),
|
||||||
|
vscode.commands.registerCommand('bwcontest.showTestSubmitPage', () => {
|
||||||
|
BWPanel.show(context, extensionSettings().webUrl);
|
||||||
|
}),
|
||||||
|
vscode.commands.registerCommand('bwcontest.refreshState', () => {
|
||||||
|
refreshRepoState();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
startTeamStatusPollingOnActivation(context);
|
startTeamStatusPollingOnActivation(context);
|
||||||
|
|
||||||
|
setRepoManagerExtensionContext(context);
|
||||||
|
refreshRepoState();
|
||||||
|
|
||||||
|
vscode.workspace.onDidChangeWorkspaceFolders(() => {
|
||||||
|
refreshRepoState();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deactivate() {
|
export function deactivate() {
|
||||||
outputPanelLog.info('BWContest Extension Deactivated');
|
outputPanelLog.info('BWContest Extension Deactivated');
|
||||||
stopTeamStatusPolling();
|
stopTeamStatusPolling();
|
||||||
|
clearCachedRepoState();
|
||||||
}
|
}
|
||||||
|
318
extension/bwcontest/src/teamRepoManager.ts
Normal file
318
extension/bwcontest/src/teamRepoManager.ts
Normal file
@ -0,0 +1,318 @@
|
|||||||
|
import * as vscode from 'vscode';
|
||||||
|
import * as fs from 'fs-extra';
|
||||||
|
import urlJoin from 'url-join';
|
||||||
|
import git from 'isomorphic-git';
|
||||||
|
import path = require('path');
|
||||||
|
import http from 'isomorphic-git/http/node';
|
||||||
|
import outputPanelLog from './outputPanelLog';
|
||||||
|
import { BWContestSettings } from './extension';
|
||||||
|
import { LiteEvent } from './utilities/LiteEvent';
|
||||||
|
import { TeamData } from './sharedTypes';
|
||||||
|
import * as os from 'os';
|
||||||
|
|
||||||
|
let latestRepoState: RepoState = 'No Team';
|
||||||
|
|
||||||
|
const onRepoStateChanged = new LiteEvent<RepoChangedEventArgs>();
|
||||||
|
export const repoStateChanged = onRepoStateChanged.expose();
|
||||||
|
|
||||||
|
export type RepoState = 'No Team' | 'No Repo' | 'Repo Exists, Not Open' | 'Repo Open';
|
||||||
|
|
||||||
|
export type RepoChangedEventArgs = {
|
||||||
|
state: RepoState;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getCachedRepoState(): RepoState {
|
||||||
|
return latestRepoState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearCachedRepoState(): void {
|
||||||
|
outputPanelLog.trace(`Clearing cached repoState`);
|
||||||
|
setRepoState('No Team');
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function refreshRepoState(): Promise<void> {
|
||||||
|
outputPanelLog.trace(`Refreshing repoState`);
|
||||||
|
|
||||||
|
if (!repoManagerExtensionContext) {
|
||||||
|
outputPanelLog.trace(` -> repoState is 'No Team' because something is misconfigured`);
|
||||||
|
return setRepoState('No Team');
|
||||||
|
}
|
||||||
|
|
||||||
|
const teamData = repoManagerExtensionContext.globalState.get<TeamData>('teamData');
|
||||||
|
if (teamData === undefined) {
|
||||||
|
outputPanelLog.trace(` -> repoState is 'No Team' because no globalState for teamData`);
|
||||||
|
return setRepoState('No Team');
|
||||||
|
}
|
||||||
|
|
||||||
|
const repoPaths = getRepoPaths(teamData.contestId, teamData.teamId);
|
||||||
|
if (!repoPaths.success) {
|
||||||
|
outputPanelLog.trace(` -> repoState is 'No Team' can't calculate repo paths`);
|
||||||
|
return setRepoState('No Team');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { clonedRepoPath } = repoPaths;
|
||||||
|
|
||||||
|
outputPanelLog.trace(` -> inspecting local repoPath ${clonedRepoPath}`);
|
||||||
|
if (!fs.existsSync(clonedRepoPath)) {
|
||||||
|
outputPanelLog.trace(` -> repoState is 'No Repo', the local repo path doesn't exist at all`);
|
||||||
|
return setRepoState('No Repo');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await directoryHasGitRepo(clonedRepoPath))) {
|
||||||
|
outputPanelLog.trace(
|
||||||
|
` -> repoState is 'No Repo', the local repo path exists but does not have a git repo`
|
||||||
|
);
|
||||||
|
return setRepoState('No Repo');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vscode.workspace.workspaceFolders) {
|
||||||
|
const existingOpenFolderForRepo = vscode.workspace.workspaceFolders.filter((f) => {
|
||||||
|
const p =
|
||||||
|
os.platform() === 'win32'
|
||||||
|
? path.normalize(f.uri.path.slice(1))
|
||||||
|
: path.normalize(f.uri.path);
|
||||||
|
return p === path.normalize(clonedRepoPath);
|
||||||
|
})[0];
|
||||||
|
if (existingOpenFolderForRepo) {
|
||||||
|
outputPanelLog.trace(
|
||||||
|
` -> repoState is 'Repo Open', we found the repo path in VSCode's workspaceFolders`
|
||||||
|
);
|
||||||
|
return setRepoState('Repo Open');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const workspaceFoldersLogText = vscode.workspace.workspaceFolders
|
||||||
|
? vscode.workspace.workspaceFolders.map((f) => f.uri).join(', ')
|
||||||
|
: '(no workspaceFolders)';
|
||||||
|
outputPanelLog.trace(
|
||||||
|
` -> repoState is 'Repo Exists, Not Open', did not find repoPath (${clonedRepoPath}) in VSCode's workspaceFolders (${workspaceFoldersLogText})`
|
||||||
|
);
|
||||||
|
return setRepoState('Repo Exists, Not Open');
|
||||||
|
}
|
||||||
|
|
||||||
|
function setRepoState(state: RepoState): void {
|
||||||
|
if (state != latestRepoState) {
|
||||||
|
outputPanelLog.trace(`Detected repoState change: ${latestRepoState} -> ${state}`);
|
||||||
|
latestRepoState = state;
|
||||||
|
onRepoStateChanged.trigger({ state });
|
||||||
|
} else {
|
||||||
|
outputPanelLog.trace(`No repoState change, same value: ${state}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function cloneOpenRepo(contestId: number, teamId: number): Promise<boolean> {
|
||||||
|
const result = (await cloneRepoWorker(contestId, teamId)) && openRepoWorker(contestId, teamId);
|
||||||
|
refreshRepoState();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function cloneRepo(contestId: number, teamId: number): Promise<boolean> {
|
||||||
|
const result = await cloneRepoWorker(contestId, teamId);
|
||||||
|
refreshRepoState();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function openRepo(contestId: number, teamId: number): boolean {
|
||||||
|
const result = openRepoWorker(contestId, teamId);
|
||||||
|
refreshRepoState();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function cloneRepoWorker(contestId: number, teamId: number): Promise<boolean> {
|
||||||
|
const repoPaths = getRepoPaths(contestId, teamId);
|
||||||
|
if (!repoPaths.success) {
|
||||||
|
vscode.window.showErrorMessage('BWContest: BWContestSettings not configured');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { repoUrl, clonedRepoPath } = repoPaths;
|
||||||
|
|
||||||
|
outputPanelLog.trace(`Trying to cloneRepo`);
|
||||||
|
outputPanelLog.trace(` URL: ${repoUrl}`);
|
||||||
|
outputPanelLog.trace(` Local Directory: ${clonedRepoPath}`);
|
||||||
|
|
||||||
|
if (!fs.existsSync(clonedRepoPath)) {
|
||||||
|
outputPanelLog.trace('Local Directory does not exist, creating it');
|
||||||
|
try {
|
||||||
|
fs.mkdirSync(clonedRepoPath, { recursive: true });
|
||||||
|
} catch (error) {
|
||||||
|
vscode.window.showErrorMessage(
|
||||||
|
`BWContest: Could not create directory '${clonedRepoPath}': ${error}`
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
outputPanelLog.trace('Local Directory created, starting clone');
|
||||||
|
return await doClone(clonedRepoPath, repoUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fs.readdirSync(clonedRepoPath).length == 0) {
|
||||||
|
outputPanelLog.trace('Local Directory exists but is empty, starting clone');
|
||||||
|
return await doClone(clonedRepoPath, repoUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
outputPanelLog.trace('Local Directory exists and is non-empty');
|
||||||
|
|
||||||
|
const gitHasRemote = await directoryHasGitRepo(clonedRepoPath);
|
||||||
|
|
||||||
|
const deleteAndReplacePrompt = gitHasRemote
|
||||||
|
? 'The repository already exists, replacing it will delete local changes. Are you sure?'
|
||||||
|
: 'The repository directory exists with no git repo, deleting it will delete local changes. Are you sure?';
|
||||||
|
|
||||||
|
const confirm = await vscode.window.showWarningMessage(
|
||||||
|
deleteAndReplacePrompt,
|
||||||
|
'Delete and Replace',
|
||||||
|
'Cancel'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (confirm !== 'Delete and Replace') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
outputPanelLog.trace(`Team has chosen to delete non-empty Local Directory`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const existingItemsInDir = fs.readdirSync(clonedRepoPath, {
|
||||||
|
recursive: false,
|
||||||
|
encoding: 'utf8'
|
||||||
|
});
|
||||||
|
|
||||||
|
outputPanelLog.trace(` Removing ${existingItemsInDir.length} items`);
|
||||||
|
for (const existingItemInDir of existingItemsInDir) {
|
||||||
|
const fullPath = path.join(clonedRepoPath, existingItemInDir);
|
||||||
|
outputPanelLog.trace(` Removing ${fullPath}`);
|
||||||
|
fs.rmSync(fullPath, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
vscode.window.showErrorMessage(
|
||||||
|
`BWContest: Failed to delete contents of Local Directory '${clonedRepoPath}': ${error}`
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const itemsInDirAfterDelete = fs.readdirSync(clonedRepoPath, {
|
||||||
|
recursive: false,
|
||||||
|
encoding: 'utf8'
|
||||||
|
});
|
||||||
|
|
||||||
|
outputPanelLog.trace(
|
||||||
|
`Local Directory should now be empty, there are ${itemsInDirAfterDelete.length} item(s): ${itemsInDirAfterDelete.join(', ')}`
|
||||||
|
);
|
||||||
|
if (itemsInDirAfterDelete.length > 0) {
|
||||||
|
vscode.window.showErrorMessage(`BWContest: Failed to delete contents of Local Directory`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
vscode.window.showErrorMessage(
|
||||||
|
`BWContest: Failed to delete contents of Local Directory '${clonedRepoPath}': ${error}`
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
outputPanelLog.trace(`Local Directory is now empty, starting clone`);
|
||||||
|
return await doClone(clonedRepoPath, repoUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function directoryHasGitRepo(path: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
await git.listRemotes({ fs, dir: path });
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function doClone(targetDirectory: string, repoUrl: string): Promise<boolean> {
|
||||||
|
outputPanelLog.trace(`Running 'git clone' from url ${repoUrl} to directory: ${targetDirectory}`);
|
||||||
|
try {
|
||||||
|
await git.clone({ fs, http, dir: targetDirectory, url: repoUrl });
|
||||||
|
} catch (error) {
|
||||||
|
vscode.window.showErrorMessage(`BWContest: Failed to git clone: ${error}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
vscode.window.showInformationMessage('BWContest: Repo cloned!');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openRepoWorker(contestId: number, teamId: number): boolean {
|
||||||
|
const repoPaths = getRepoPaths(contestId, teamId);
|
||||||
|
if (!repoPaths.success) {
|
||||||
|
vscode.window.showErrorMessage('BWContest: BWContestSettings not configured');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { clonedRepoPath } = repoPaths;
|
||||||
|
|
||||||
|
if (!fs.existsSync(clonedRepoPath)) {
|
||||||
|
vscode.window.showErrorMessage('BWContest: Local repo not found, clone first.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vscode.workspace.workspaceFolders) {
|
||||||
|
const existingOpenFolderForRepo = vscode.workspace.workspaceFolders.filter((f) => {
|
||||||
|
const p =
|
||||||
|
os.platform() === 'win32'
|
||||||
|
? path.normalize(f.uri.path.slice(1))
|
||||||
|
: path.normalize(f.uri.path);
|
||||||
|
return p === path.normalize(clonedRepoPath);
|
||||||
|
})[0];
|
||||||
|
if (existingOpenFolderForRepo) {
|
||||||
|
vscode.window.showInformationMessage('BWContest: Repo is already opened');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outputPanelLog.trace(`Opening local repo from '${clonedRepoPath}'`);
|
||||||
|
const workspaceUpdateValid = vscode.workspace.updateWorkspaceFolders(
|
||||||
|
0,
|
||||||
|
vscode.workspace.workspaceFolders?.length ?? 0,
|
||||||
|
{ uri: vscode.Uri.file(clonedRepoPath), name: 'BWContest' }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!workspaceUpdateValid) {
|
||||||
|
vscode.window.showErrorMessage('BWContest: Request to open repository in VSCode failed');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shouldn't happen, or if it does the IDE will restart shortly after
|
||||||
|
vscode.window.showInformationMessage('BWContest: Team repository opened!');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRepoPaths(
|
||||||
|
contestId: number,
|
||||||
|
teamId: number
|
||||||
|
): { success: false } | { success: true; repoUrl: string; clonedRepoPath: string } {
|
||||||
|
const currentSettings = vscode.workspace.getConfiguration().get<BWContestSettings>('BWContest');
|
||||||
|
|
||||||
|
if (!currentSettings || currentSettings.repoBaseUrl == '') {
|
||||||
|
// vscode.window.showErrorMessage('BWContest: BWContest.repoBaseURL not set');
|
||||||
|
return { success: false };
|
||||||
|
}
|
||||||
|
if (!currentSettings || currentSettings.repoClonePath == '') {
|
||||||
|
// vscode.window.showErrorMessage('BWContest: BWContest.repoClonePath not set');
|
||||||
|
return { success: false };
|
||||||
|
}
|
||||||
|
if (!currentSettings || currentSettings.webUrl == '') {
|
||||||
|
// vscode.window.showErrorMessage('BWContest: BWContest.webUrl not set');
|
||||||
|
return { success: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
repoUrl: urlJoin(currentSettings.repoBaseUrl, contestId.toString(), `${teamId.toString()}.git`),
|
||||||
|
clonedRepoPath: path.join(
|
||||||
|
currentSettings.repoClonePath,
|
||||||
|
'BWContest',
|
||||||
|
contestId.toString(),
|
||||||
|
teamId.toString()
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let repoManagerExtensionContext: vscode.ExtensionContext | null;
|
||||||
|
export function setRepoManagerExtensionContext(context: vscode.ExtensionContext) {
|
||||||
|
repoManagerExtensionContext = context;
|
||||||
|
}
|
@ -7,6 +7,7 @@
|
|||||||
MessageType,
|
MessageType,
|
||||||
SidebarTeamStatus
|
SidebarTeamStatus
|
||||||
} from '../../src/SidebarProvider';
|
} from '../../src/SidebarProvider';
|
||||||
|
import type { RepoState } from '../../src/teamRepoManager';
|
||||||
|
|
||||||
let teamname: string;
|
let teamname: string;
|
||||||
let password: string;
|
let password: string;
|
||||||
@ -16,16 +17,36 @@
|
|||||||
let teamData: TeamData | null = null;
|
let teamData: TeamData | null = null;
|
||||||
let teamStatus: SidebarTeamStatus | null = null;
|
let teamStatus: SidebarTeamStatus | null = null;
|
||||||
|
|
||||||
|
let repoState: RepoState | null = null;
|
||||||
|
|
||||||
let totalProblems = 0;
|
let totalProblems = 0;
|
||||||
|
|
||||||
function postMessage(message: MessageType) {
|
function postMessage(message: MessageType) {
|
||||||
vscode.postMessage(message);
|
vscode.postMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onClone() {
|
function onCloneOpenRepo() {
|
||||||
if (teamData) {
|
if (teamData) {
|
||||||
postMessage({
|
postMessage({
|
||||||
msg: 'onClone',
|
msg: 'onCloneOpenRepo',
|
||||||
|
data: { contestId: teamData.contestId, teamId: teamData.teamId }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCloneRepo() {
|
||||||
|
if (teamData) {
|
||||||
|
postMessage({
|
||||||
|
msg: 'onCloneRepo',
|
||||||
|
data: { contestId: teamData.contestId, teamId: teamData.teamId }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onOpenRepo() {
|
||||||
|
if (teamData) {
|
||||||
|
postMessage({
|
||||||
|
msg: 'onOpenRepo',
|
||||||
data: { contestId: teamData.contestId, teamId: teamData.teamId }
|
data: { contestId: teamData.contestId, teamId: teamData.teamId }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -71,6 +92,8 @@
|
|||||||
teamStatus.incorrectProblems.length +
|
teamStatus.incorrectProblems.length +
|
||||||
teamStatus.notStartedProblems.length
|
teamStatus.notStartedProblems.length
|
||||||
: 0;
|
: 0;
|
||||||
|
} else if (m.msg === 'repoStateUpdated') {
|
||||||
|
repoState = m.data;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@ -111,10 +134,24 @@
|
|||||||
|
|
||||||
<h2 class="sidebarSectionHeader">Actions</h2>
|
<h2 class="sidebarSectionHeader">Actions</h2>
|
||||||
<div class="sidebarSection">
|
<div class="sidebarSection">
|
||||||
|
{#if repoState == 'No Team'}
|
||||||
|
<span>Team not connected, click Refresh at the top of this panel</span>
|
||||||
|
{:else if repoState == 'No Repo'}
|
||||||
<div class="buttonContainer">
|
<div class="buttonContainer">
|
||||||
<button on:click={onClone} class="sidebarButton">Clone and Open Repo</button>
|
<button on:click={onCloneOpenRepo} class="sidebarButton">Clone and Open Repo</button>
|
||||||
<button on:click={onTestAndSubmit} class="sidebarButton">Test & Submit</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
{:else if repoState == 'Repo Exists, Not Open'}
|
||||||
|
<div class="buttonContainer">
|
||||||
|
<button on:click={onOpenRepo} class="sidebarButton">Open Repo</button>
|
||||||
|
</div>
|
||||||
|
{:else if repoState == 'Repo Open'}
|
||||||
|
<div class="buttonContainer">
|
||||||
|
<button on:click={onTestAndSubmit} class="sidebarButton">Test & Submit</button>
|
||||||
|
<button on:click={onCloneRepo} class="sidebarButton">Reset Repo</button>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<span>Checking repo state...</span>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 class="sidebarSectionHeader">Problem Progress</h2>
|
<h2 class="sidebarSectionHeader">Problem Progress</h2>
|
||||||
|
Loading…
Reference in New Issue
Block a user