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:
Peter 2024-10-31 14:58:36 +01:00
parent 218970e57b
commit e874cbefd6
7 changed files with 296 additions and 5 deletions

View File

@ -2,6 +2,25 @@
All notable changes to the “gitea-workflow” extension will be documented in this file. 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 ## 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 Git-Repository
- no errors if not a Gitea-Repository with configured upstream - 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 - truncate Commit Titles longer than 255 characters
## 1.2.1 ## 1.2.1

View File

@ -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: 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: 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: 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 ## Requirements
- A Gitea instance with SSH access. - 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 ## Configuration
@ -70,6 +71,17 @@ Choose the repository you want to clone.
Select the folder where you want to clone the repository. 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. 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 ### Creating a Pull Request
Ensure that you are on the branch for which you want to create a Pull Request. Ensure that you are on the branch for which you want to create a Pull Request.

View File

@ -36,5 +36,14 @@
"giteaClone.selectPullRequest": "Wählen Sie einen Pull Request zum Anzeigen aus", "giteaClone.selectPullRequest": "Wählen Sie einen Pull Request zum Anzeigen aus",
"giteaClone.showOpenPullRequestsTooltip": "Offene Pull Requests anzeigen", "giteaClone.showOpenPullRequestsTooltip": "Offene Pull Requests anzeigen",
"giteaClone.branchStatusTooltip": "Aktueller Branch-Status", "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}"
} }

View File

@ -36,5 +36,14 @@
"giteaClone.selectPullRequest": "Select a pull request to view", "giteaClone.selectPullRequest": "Select a pull request to view",
"giteaClone.showOpenPullRequestsTooltip": "Show open pull requests", "giteaClone.showOpenPullRequestsTooltip": "Show open pull requests",
"giteaClone.branchStatusTooltip": "Current branch status", "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}"
} }

View File

@ -2,7 +2,7 @@
"name": "gitea-workflow", "name": "gitea-workflow",
"displayName": "Gitea Workflow", "displayName": "Gitea Workflow",
"description": "Clone from Gitea instances; Create PRs", "description": "Clone from Gitea instances; Create PRs",
"version": "1.3.0", "version": "1.4.0",
"publisher": "computerliebe", "publisher": "computerliebe",
"engines": { "engines": {
"vscode": "^1.94.0" "vscode": "^1.94.0"
@ -45,6 +45,11 @@
"command": "gitea.showOpenPullRequests", "command": "gitea.showOpenPullRequests",
"title": " Show open Pull Requests", "title": " Show open Pull Requests",
"category": "Gitea" "category": "Gitea"
},
{
"command": "gitea.createRepository",
"title": " Create Repository",
"category": "Gitea"
} }
], ],
"configuration": { "configuration": {

Binary file not shown.

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 // Funktion zum Erstellen eines Pull Requests
async function createGiteaPullRequest() { async function createGiteaPullRequest() {
// Aktuellen Workspace-Ordner abrufen // Aktuellen Workspace-Ordner abrufen
@ -959,6 +1192,10 @@ export async function activate(context: vscode.ExtensionContext) {
let showPRCommand = vscode.commands.registerCommand('gitea.showOpenPullRequests', showOpenPullRequests); let showPRCommand = vscode.commands.registerCommand('gitea.showOpenPullRequests', showOpenPullRequests);
context.subscriptions.push(showPRCommand); 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 // Statusleisten-Icon erstellen
statusBarItem = await addStatusBarIcon(); statusBarItem = await addStatusBarIcon();
if (statusBarItem) { if (statusBarItem) {