diff --git a/gitea-git-clone/CHANGELOG.md b/gitea-git-clone/CHANGELOG.md index 75edcb4..c3553bb 100644 --- a/gitea-git-clone/CHANGELOG.md +++ b/gitea-git-clone/CHANGELOG.md @@ -2,6 +2,25 @@ All notable changes to the “gitea-workflow” extension will be documented in this file. +## 1.4.0 + +### Added + +- **Create Repositories**: 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. +- **Automatic Cloning**: After creation, the new repository is automatically cloned and opened in VSCode. + +### Changed + +- **Localization**: Updated localization files to include new messages related to repository creation. + +### Fixed + +- **Minor Bug Fixes**: Improved error handling and messages throughout the extension. ## 1.3 @@ -34,7 +53,7 @@ All notable changes to the “gitea-workflow” extension will be documented in - no errors if not a Git-Repository - no errors if not a Gitea-Repository with configured upstream -- no Statusbar icons if not Git/Gitea-Repository +- no Status bar icons if not Git/Gitea-Repository - truncate Commit Titles longer than 255 characters ## 1.2.1 diff --git a/gitea-git-clone/README.md b/gitea-git-clone/README.md index 9af4dfc..9a496fa 100644 --- a/gitea-git-clone/README.md +++ b/gitea-git-clone/README.md @@ -19,11 +19,12 @@ A Visual Studio Code extension that allows you to manage your Gitea repositories - `Gitea: Clone Repository` → Clone a repository from a selected Gitea instance via SSH. - `Gitea: Create Pull Request` → Create a pull request for the current repository. - `Gitea: Show Open Pull Requests` → Show open pull requests for the current repository and link to them. +- `Gitea: Create Repository` → Create a new repository in a selected Gitea instance. ## Requirements - A Gitea instance with SSH access. -- A Personal Access Token (PAT) from your Gitea instance(s) for authentication. +- A Personal Access Token (PAT) from your Gitea instance(s) with necessary permissions (e.g., `repo`, `admin:org`). ## Configuration @@ -70,6 +71,17 @@ Choose the repository you want to clone. Select the folder where you want to clone the repository. The repository will be cloned via SSH and automatically opened in a new VSCode window. +### Creating a Repository + +Run `Gitea: Create Repository` from the command palette. +Select the Gitea instance where you want to create the repository. +Choose the owner (your personal account or an organization you belong to). +Enter the name of the new repository. +The repository name will be sanitized and formatted to meet Gitea's requirements. +Select the visibility of the repository (Private or Public). +The repository will be created with an MIT license and initialized with a `.gitignore` template for Visual Studio Code. +After creation, the repository will be cloned, and you can start working immediately. + ### Creating a Pull Request Ensure that you are on the branch for which you want to create a Pull Request. diff --git a/gitea-git-clone/i18n/package.nls.de.json b/gitea-git-clone/i18n/package.nls.de.json index 339fcf6..d90b40e 100644 --- a/gitea-git-clone/i18n/package.nls.de.json +++ b/gitea-git-clone/i18n/package.nls.de.json @@ -36,5 +36,14 @@ "giteaClone.selectPullRequest": "Wählen Sie einen Pull Request zum Anzeigen aus", "giteaClone.showOpenPullRequestsTooltip": "Offene Pull Requests anzeigen", "giteaClone.branchStatusTooltip": "Aktueller Branch-Status", - "giteaClone.buildStatusTooltip": "Build-Status des aktuellen Commits" + "giteaClone.buildStatusTooltip": "Build-Status des aktuellen Commits", + "giteaClone.createRepository": "Repository erstellen", + "giteaClone.selectOwner": "Wählen Sie den Besitzer für das Repository aus", + "giteaClone.noOwnerSelected": "Kein Besitzer ausgewählt.", + "giteaClone.enterRepoName": "Geben Sie den Namen des neuen Repositories ein", + "giteaClone.noRepoName": "Kein Repository-Name angegeben.", + "giteaClone.selectVisibility": "Wählen Sie die Sichtbarkeit des Repositories", + "giteaClone.selectGitignore": ".gitignore-Template auswählen (optional)", + "giteaClone.repoCreated": "Repository '{0}' erfolgreich erstellt.", + "giteaClone.createRepoError": "Fehler beim Erstellen des Repositories: {0}" } diff --git a/gitea-git-clone/i18n/package.nls.json b/gitea-git-clone/i18n/package.nls.json index 83fe2ec..9a41f19 100644 --- a/gitea-git-clone/i18n/package.nls.json +++ b/gitea-git-clone/i18n/package.nls.json @@ -36,5 +36,14 @@ "giteaClone.selectPullRequest": "Select a pull request to view", "giteaClone.showOpenPullRequestsTooltip": "Show open pull requests", "giteaClone.branchStatusTooltip": "Current branch status", - "giteaClone.buildStatusTooltip": "Build status of the current commit" + "giteaClone.buildStatusTooltip": "Build status of the current commit", + "giteaClone.createRepository": "Create Repository", + "giteaClone.selectOwner": "Select the owner for the repository", + "giteaClone.noOwnerSelected": "No owner selected.", + "giteaClone.enterRepoName": "Enter the name of the new repository", + "giteaClone.noRepoName": "No repository name provided.", + "giteaClone.selectVisibility": "Select the visibility of the repository", + "giteaClone.selectGitignore": "Select a .gitignore template (optional)", + "giteaClone.repoCreated": "Repository '{0}' created successfully.", + "giteaClone.createRepoError": "Error creating repository: {0}" } diff --git a/gitea-git-clone/package.json b/gitea-git-clone/package.json index a345cb8..8bfd1fe 100644 --- a/gitea-git-clone/package.json +++ b/gitea-git-clone/package.json @@ -2,7 +2,7 @@ "name": "gitea-workflow", "displayName": "Gitea Workflow", "description": "Clone from Gitea instances; Create PRs", - "version": "1.3.0", + "version": "1.4.0", "publisher": "computerliebe", "engines": { "vscode": "^1.94.0" @@ -45,6 +45,11 @@ "command": "gitea.showOpenPullRequests", "title": " Show open Pull Requests", "category": "Gitea" + }, + { + "command": "gitea.createRepository", + "title": " Create Repository", + "category": "Gitea" } ], "configuration": { diff --git a/gitea-git-clone/releases/gitea-workflow-1.4.0.vsix b/gitea-git-clone/releases/gitea-workflow-1.4.0.vsix new file mode 100644 index 0000000..1272ca5 Binary files /dev/null and b/gitea-git-clone/releases/gitea-workflow-1.4.0.vsix differ diff --git a/gitea-git-clone/src/extension.ts b/gitea-git-clone/src/extension.ts index 7c836ad..61037d3 100644 --- a/gitea-git-clone/src/extension.ts +++ b/gitea-git-clone/src/extension.ts @@ -88,6 +88,239 @@ async function isGitRepository(folderPath: string): Promise { }); } +// 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 { + 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 { + 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((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) {