feat(gitea-git-clone): add support for creating repositories in Gitea

* Added:
* - Users can now create new repositories in a selected Gitea instance directly from VSCode. Options include:
* - Selecting the Gitea instance (if multiple are configured).
* - Choosing the owner (personal account or organization).
* - Setting the repository visibility (private by default, or public).
* - Selecting a `.gitignore` template (if available via API).
* - The repository is initialized with an MIT license.
* - After creation, the new repository is automatically cloned and opened in VSCode.
* Changed:
* - Updated localization files to include new messages related to repository creation.
*
* Fixed:
* - Improved error handling and messages throughout the extension.
This commit is contained in:
2024-10-31 14:58:36 +01:00
parent 218970e57b
commit e874cbefd6
7 changed files with 296 additions and 5 deletions

View File

@ -88,6 +88,239 @@ async function isGitRepository(folderPath: string): Promise<boolean> {
});
}
// Helper function to slugify the repository name
function slugify(text: string): string {
return text
.toString()
.toLowerCase()
.trim()
.replace(/[\s\W-]+/g, '-'); // Replace spaces and non-word characters with a dash
}
// UPDATED FUNCTION: Creating a Repository in Gitea
async function createGiteaRepository() {
const instances = getGiteaInstances();
if (instances.length === 0) {
vscode.window.showErrorMessage(localize('giteaClone.configMissing', 'No Gitea instances are configured.'));
return;
}
// User selects a Gitea instance
const instanceNames = instances.map(instance => instance.name);
const selectedInstanceName = await vscode.window.showQuickPick(instanceNames, {
placeHolder: localize('giteaClone.selectInstance', 'Select a Gitea instance to create the repository in')
});
if (!selectedInstanceName) {
vscode.window.showInformationMessage(localize('giteaClone.noInstanceSelected', 'No Gitea instance selected.'));
return;
}
const selectedInstance = instances.find(instance => instance.name === selectedInstanceName);
if (!selectedInstance) {
vscode.window.showErrorMessage(localize('giteaClone.invalidInstance', 'Invalid Gitea instance selected.'));
return;
}
const instanceUrl = selectedInstance.url;
const token = selectedInstance.token;
try {
// Fetch user organizations
const organizations = await getUserOrganizations(instanceUrl, token);
const ownerChoices = ['(Personal)'].concat(organizations.map(org => org.username));
// User selects the owner (personal account or organization)
const selectedOwner = await vscode.window.showQuickPick(ownerChoices, {
placeHolder: localize('giteaClone.selectOwner', 'Select the owner for the repository')
});
if (selectedOwner === undefined) {
vscode.window.showInformationMessage(localize('giteaClone.noOwnerSelected', 'No owner selected.'));
return;
}
const owner = selectedOwner === '(Personal)' ? null : selectedOwner;
// User inputs the repository name
const repoNameInput = await vscode.window.showInputBox({
prompt: localize('giteaClone.enterRepoName', 'Enter the name of the new repository'),
placeHolder: 'my-new-repo'
});
if (!repoNameInput) {
vscode.window.showInformationMessage(localize('giteaClone.noRepoName', 'No repository name provided.'));
return;
}
// Sanitize and slugify the repository name
const repoName = slugify(repoNameInput);
if (!repoName) {
vscode.window.showErrorMessage(localize('giteaClone.invalidRepoName', 'Invalid repository name.'));
return;
}
// User selects the visibility
const visibilityChoice = await vscode.window.showQuickPick(['Private', 'Public'], {
placeHolder: localize('giteaClone.selectVisibility', 'Select the visibility of the repository'),
canPickMany: false
});
const isPrivate = visibilityChoice !== 'Public'; // Default to Private if undefined
// Set the .gitignore template to 'VisualStudioCode' by default
const gitignoreTemplate = 'VisualStudioCode';
// Repository creation data
const repoData = {
name: repoName, // Slugified name
repo_name: repoNameInput, // Original name for display purposes
private: isPrivate,
auto_init: true,
license: 'MIT',
gitignores: gitignoreTemplate
};
const apiUrl = owner
? `${instanceUrl}/api/v1/orgs/${owner}/repos`
: `${instanceUrl}/api/v1/user/repos`;
const response = await axios.post(apiUrl, repoData, {
headers: {
'Authorization': `token ${token}`,
'Content-Type': 'application/json'
}
});
const repo = response.data;
vscode.window.showInformationMessage(localize('giteaClone.repoCreated', `Repository '${repoNameInput}' created successfully.`));
// Clone the repository
await cloneRepository(repo.ssh_url, repoName);
} catch (err: any) {
let errMessage = '';
if (axios.isAxiosError(err)) {
const axiosError = err as AxiosError;
if (axiosError.response && axiosError.response.data) {
const data = axiosError.response.data as GiteaErrorResponse;
if (typeof data === 'string') {
errMessage = data;
} else if (data.message) {
errMessage = data.message;
}
} else if (axiosError.message) {
errMessage = axiosError.message;
}
} else {
errMessage = err.message;
}
let errorMessage = localize('giteaClone.createRepoError', `Error creating repository: ${errMessage}`);
vscode.window.showErrorMessage(errorMessage);
console.error('Error creating repository:', err);
}
}
// Hilfsfunktion zum Abrufen der Benutzerorganisationen
async function getUserOrganizations(instanceUrl: string, token: string): Promise<any[]> {
try {
const response = await axios.get(`${instanceUrl}/api/v1/user/orgs`, {
headers: {
'Authorization': `token ${token}`
}
});
if (response.status === 200) {
return response.data;
} else {
return [];
}
} catch (err) {
console.error('Error fetching user organizations:', err);
return [];
}
}
// Hilfsfunktion zum Abrufen der verfügbaren .gitignore-Templates
async function getGitignoreTemplates(instanceUrl: string, token: string): Promise<string[]> {
try {
const response = await axios.get(`${instanceUrl}/api/v1/gitignores`, {
headers: {
'Authorization': `token ${token}`
}
});
if (response.status === 200) {
return response.data;
} else {
return [];
}
} catch (err) {
console.error('Error fetching gitignore templates:', err);
return [];
}
}
// Hilfsfunktion zum Klonen eines Repositories
async function cloneRepository(sshUrl: string, repoName: string) {
const folderUri = await vscode.window.showOpenDialog({
canSelectFiles: false,
canSelectFolders: true,
canSelectMany: false,
openLabel: localize('giteaClone.selectFolder', 'Select folder to clone into')
});
if (folderUri && folderUri[0]) {
const folderPath = folderUri[0].fsPath;
const targetPath = path.join(folderPath, repoName);
// Überprüfen, ob das Zielverzeichnis bereits existiert
if (fs.existsSync(targetPath)) {
vscode.window.showErrorMessage(localize('giteaClone.targetExists', `The target directory "${targetPath}" already exists.`));
return;
}
vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: localize('giteaClone.cloningRepo', `Cloning ${repoName}`),
cancellable: false
}, async (progress, token) => {
progress.report({ message: localize('giteaClone.cloningProgress', 'Cloning in progress...'), increment: 0 });
return new Promise<void>((resolve, reject) => {
exec(`git clone ${sshUrl} "${targetPath}"`, (error, stdout, stderr) => {
if (error) {
vscode.window.showErrorMessage(localize('giteaClone.cloneError', 'Error cloning the repository.'));
console.error(stderr);
reject(error);
} else {
progress.report({ message: localize('giteaClone.cloneComplete', 'Repository cloned successfully.'), increment: 100 });
vscode.window.showInformationMessage(localize('giteaClone.cloneSuccess', `Repository ${repoName} cloned successfully.`));
// Öffne das geklonte Repository im VSCode
try {
vscode.commands.executeCommand('vscode.openFolder', vscode.Uri.file(targetPath), true)
.then(() => resolve());
} catch (err: unknown) {
vscode.window.showErrorMessage(localize('giteaClone.openRepoError', 'Error opening the cloned repository.'));
console.error(err);
reject(err);
}
}
});
});
});
} else {
vscode.window.showInformationMessage(localize('giteaClone.noFolderSelected', 'No target folder selected.'));
}
}
// Funktion zum Erstellen eines Pull Requests
async function createGiteaPullRequest() {
// Aktuellen Workspace-Ordner abrufen
@ -959,6 +1192,10 @@ export async function activate(context: vscode.ExtensionContext) {
let showPRCommand = vscode.commands.registerCommand('gitea.showOpenPullRequests', showOpenPullRequests);
context.subscriptions.push(showPRCommand);
// NEUER BEFEHL zum Erstellen eines Repositories
let createRepoCommand = vscode.commands.registerCommand('gitea.createRepository', createGiteaRepository);
context.subscriptions.push(createRepoCommand);
// Statusleisten-Icon erstellen
statusBarItem = await addStatusBarIcon();
if (statusBarItem) {