From 6822001ae814dc2b3acf64971d83ab707733b975 Mon Sep 17 00:00:00 2001 From: Peter Date: Sun, 20 Oct 2024 20:17:35 +0200 Subject: [PATCH 1/2] * feat(extension.ts): add support for creating a pull request in Gitea * feat(extension.ts): add status bar icon for creating a pull request * feat(extension.ts): implement function to create a Gitea pull request * feat(extension.ts): implement function to add status bar icon --- gitea-git-clone/package-lock.json | 136 +++++++++++++++++++++++++++++- gitea-git-clone/package.json | 8 +- gitea-git-clone/src/extension.ts | 70 +++++++++++++++ 3 files changed, 212 insertions(+), 2 deletions(-) diff --git a/gitea-git-clone/package-lock.json b/gitea-git-clone/package-lock.json index a66612a..07c397e 100644 --- a/gitea-git-clone/package-lock.json +++ b/gitea-git-clone/package-lock.json @@ -8,7 +8,8 @@ "name": "gitea-git-clone", "version": "0.0.1", "dependencies": { - "axios": "^1.7.7" + "axios": "^1.7.7", + "open": "^10.1.0" }, "devDependencies": { "@types/mocha": "^10.0.8", @@ -1117,6 +1118,21 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/c8": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/c8/-/c8-9.1.0.tgz", @@ -1466,6 +1482,46 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -2232,6 +2288,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2262,6 +2333,24 @@ "node": ">=0.10.0" } }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-interactive": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", @@ -2316,6 +2405,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -2899,6 +3003,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/open": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", + "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -3382,6 +3504,18 @@ "node": ">=0.10.0" } }, + "node_modules/run-applescript": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", + "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", diff --git a/gitea-git-clone/package.json b/gitea-git-clone/package.json index aaea421..4b517a5 100644 --- a/gitea-git-clone/package.json +++ b/gitea-git-clone/package.json @@ -13,6 +13,11 @@ "main": "./dist/extension.js", "contributes": { "commands": [ + { + "command": "gitea.createPullRequest", + "title": "Gitea: Create Pull Request", + "category": "Gitea" + }, { "command": "gitea.authenticate", "title": "Gitea: Authenticate" @@ -67,6 +72,7 @@ "webpack-cli": "^5.1.4" }, "dependencies": { - "axios": "^1.7.7" + "axios": "^1.7.7", + "open": "^10.1.0" } } diff --git a/gitea-git-clone/src/extension.ts b/gitea-git-clone/src/extension.ts index 657f881..abb31fe 100644 --- a/gitea-git-clone/src/extension.ts +++ b/gitea-git-clone/src/extension.ts @@ -2,6 +2,68 @@ import * as vscode from 'vscode'; import axios from 'axios'; import { exec } from 'child_process'; +// Funktion zum Abrufen des Gitea Repositories und Erstellen eines Pull Requests +async function createGiteaPullRequest() { + const instanceUrl = vscode.workspace.getConfiguration().get('gitea.instanceUrl'); + const token = vscode.workspace.getConfiguration().get('gitea.personalAccessToken'); + + if (!instanceUrl || !token) { + vscode.window.showErrorMessage('Gitea URL oder Token ist nicht konfiguriert.'); + return; + } + + // Aktuellen Git-Ordner abrufen + const currentWorkspaceFolder = vscode.workspace.workspaceFolders?.[0]?.uri?.fsPath; + if (!currentWorkspaceFolder) { + vscode.window.showErrorMessage('Kein gültiges Git-Repository im aktuellen Workspace gefunden.'); + return; + } + + // Git-Repository-Informationen abrufen + exec(`git config --get remote.origin.url`, { cwd: currentWorkspaceFolder }, async (error, stdout, stderr) => { + if (error || !stdout) { + vscode.window.showErrorMessage('Konnte die Git-Remote-URL nicht abrufen.'); + return; + } + + let repoUrl = stdout.trim(); + + // URL umwandeln, wenn sie im SSH-Format vorliegt (git@gitea.com:owner/repo.git) + if (repoUrl.startsWith('git@')) { + // SSH-URL in HTTP(S)-URL umwandeln + const parts = repoUrl.split(':'); + const domain = parts[0].replace('git@', ''); + const path = parts[1].replace('.git', ''); + repoUrl = `https://${domain}/${path}`; + } else { + // Falls es eine HTTPS-URL ist, einfach .git entfernen + repoUrl = repoUrl.replace('.git', ''); + } + + // URL für Pull Request Seite generieren + const pullRequestUrl = `${repoUrl}/pulls/new`; + + try { + // Verwende die VSCode API, um die URL im Browser zu öffnen + await vscode.env.openExternal(vscode.Uri.parse(pullRequestUrl)); + vscode.window.showInformationMessage('Pull-Request-Seite im Browser geöffnet.'); + } catch (err: unknown) { + vscode.window.showErrorMessage('Fehler beim Öffnen der Pull-Request-Seite.'); + console.error('Fehler beim Öffnen des Browsers:', err); + } + }); +} + +// Funktion zum Hinzufügen des Statusbar-Icons +function addStatusBarIcon() { + const statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left); + statusBar.text = `$(git-pull-request) Create PR`; + statusBar.tooltip = 'Create a Pull Request in Gitea'; + statusBar.command = 'gitea.createPullRequest'; + statusBar.show(); + return statusBar; +} + // Funktion zur Authentifizierung bei Gitea mit dem Personal Access Token (PAT) async function authenticateGitea() { const instanceUrl = vscode.workspace.getConfiguration().get('gitea.instanceUrl'); @@ -161,6 +223,14 @@ export function activate(context: vscode.ExtensionContext) { // Befehl zum Klonen eines Repositories let cloneCommand = vscode.commands.registerCommand('gitea.cloneRepository', cloneGiteaRepository); context.subscriptions.push(cloneCommand); + + // Registriere den Befehl zum Erstellen eines Pull Requests + let pullRequestCommand = vscode.commands.registerCommand('gitea.createPullRequest', createGiteaPullRequest); + context.subscriptions.push(pullRequestCommand); + + // Statusbar-Icon hinzufügen + const statusBar = addStatusBarIcon(); + context.subscriptions.push(statusBar); } // Deaktivierungsfunktion des Plugins From b5f132db7cb52e9328823c3b21d9d7ace17674de Mon Sep 17 00:00:00 2001 From: Peter Schuemann Date: Mon, 21 Oct 2024 01:22:28 +0200 Subject: [PATCH 2/2] * refactor(extension.ts): remove unused code and comments * feat(extension.ts): add helper functions to get last commit and current branch --- gitea-git-clone/src/extension.ts | 191 ++++++++++++------------------- 1 file changed, 76 insertions(+), 115 deletions(-) diff --git a/gitea-git-clone/src/extension.ts b/gitea-git-clone/src/extension.ts index abb31fe..7cd57ef 100644 --- a/gitea-git-clone/src/extension.ts +++ b/gitea-git-clone/src/extension.ts @@ -2,7 +2,7 @@ import * as vscode from 'vscode'; import axios from 'axios'; import { exec } from 'child_process'; -// Funktion zum Abrufen des Gitea Repositories und Erstellen eines Pull Requests +// Funktion zum Erstellen eines Pull Requests async function createGiteaPullRequest() { const instanceUrl = vscode.workspace.getConfiguration().get('gitea.instanceUrl'); const token = vscode.workspace.getConfiguration().get('gitea.personalAccessToken'); @@ -30,30 +30,95 @@ async function createGiteaPullRequest() { // URL umwandeln, wenn sie im SSH-Format vorliegt (git@gitea.com:owner/repo.git) if (repoUrl.startsWith('git@')) { - // SSH-URL in HTTP(S)-URL umwandeln const parts = repoUrl.split(':'); const domain = parts[0].replace('git@', ''); const path = parts[1].replace('.git', ''); repoUrl = `https://${domain}/${path}`; } else { - // Falls es eine HTTPS-URL ist, einfach .git entfernen repoUrl = repoUrl.replace('.git', ''); } - // URL für Pull Request Seite generieren - const pullRequestUrl = `${repoUrl}/pulls/new`; + // Extract repository owner and name from the URL + const [owner, repo] = repoUrl.split('/').slice(-2); try { - // Verwende die VSCode API, um die URL im Browser zu öffnen - await vscode.env.openExternal(vscode.Uri.parse(pullRequestUrl)); - vscode.window.showInformationMessage('Pull-Request-Seite im Browser geöffnet.'); + // Den letzten Commit und den Branch abrufen + const { title, body } = await getLastCommit(currentWorkspaceFolder); + const branch = await getCurrentBranch(currentWorkspaceFolder); + + // Debugging-Ausgabe zur Überprüfung + console.log('Branch:', branch); + console.log('Title:', title); + console.log('Body:', body); + + if (!branch) { + vscode.window.showErrorMessage('Branch konnte nicht ermittelt werden.'); + return; + } + + // API-Request-Daten vorbereiten + const prData = { + title: title, // Der letzte Commit als Titel + body: body || '', // Commit-Kommentare als Body + head: branch, // Der aktuelle Branch als "head" + base: 'main' // Standardmäßig auf 'main' als Basisbranch + }; + + const config = { + method: 'post', + maxBodyLength: Infinity, + url: `${instanceUrl}/api/v1/repos/${owner}/${repo}/pulls`, + headers: { + 'Content-Type': 'application/json', + 'Authorization': `token ${token}` + }, + data: JSON.stringify(prData) + }; + + // API-Request zum Erstellen des Pull Requests + const response = await axios.request(config); + + const prUrl = response.data.html_url; + + // Öffne die URL des erstellten PRs im Browser + vscode.env.openExternal(vscode.Uri.parse(prUrl)); + vscode.window.showInformationMessage('Pull-Request erfolgreich erstellt und im Browser geöffnet.'); } catch (err: unknown) { - vscode.window.showErrorMessage('Fehler beim Öffnen der Pull-Request-Seite.'); - console.error('Fehler beim Öffnen des Browsers:', err); + vscode.window.showErrorMessage('Fehler beim Erstellen des Pull Requests.'); + console.error('Fehler beim Erstellen des PRs:', err); } }); } +// Hilfsfunktion, um den letzten Commit zu ermitteln +async function getLastCommit(folderPath: string): Promise<{ title: string, body: string }> { + return new Promise((resolve, reject) => { + exec(`git log -1 --pretty=format:"%s%n%b"`, { cwd: folderPath }, (error, stdout) => { + if (error) { + reject('Fehler beim Abrufen des letzten Commits.'); + } else { + const output = stdout.split('\n'); + const title = output[0]; // Commit-Message als Titel + const body = output.slice(1).join('\n'); // Commit-Kommentar als Body + resolve({ title: title.trim(), body: body.trim() }); + } + }); + }); +} + +// Hilfsfunktion, um den aktuellen Branch zu ermitteln +async function getCurrentBranch(folderPath: string): Promise { + return new Promise((resolve, reject) => { + exec(`git rev-parse --abbrev-ref HEAD`, { cwd: folderPath }, (error, stdout) => { + if (error) { + reject('Fehler beim Abrufen des Branches.'); + } else { + resolve(stdout.trim()); + } + }); + }); +} + // Funktion zum Hinzufügen des Statusbar-Icons function addStatusBarIcon() { const statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left); @@ -110,106 +175,6 @@ async function configureGitea() { } } -// Funktion zum Abrufen der Repositories des Benutzers von Gitea -async function getGiteaRepositories(): Promise { - const instanceUrl = vscode.workspace.getConfiguration().get('gitea.instanceUrl'); - const token = vscode.workspace.getConfiguration().get('gitea.personalAccessToken'); - - if (!instanceUrl || !token) { - vscode.window.showErrorMessage('Gitea URL oder Token ist nicht konfiguriert.'); - return []; - } - - try { - const response = await axios.get(`${instanceUrl}/api/v1/user/repos`, { - headers: { - 'Authorization': `token ${token}` - } - }); - - if (response.status === 200) { - return response.data; - } else { - vscode.window.showErrorMessage('Fehler beim Abrufen der Repositories.'); - return []; - } - } catch (error) { - vscode.window.showErrorMessage('Fehler bei der Verbindung mit der Gitea API.'); - console.error(error); - return []; - } -} - -// Funktion zum Klonen eines Repositories via SSH -async function cloneGiteaRepository() { - const repos = await getGiteaRepositories(); - - if (repos.length === 0) { - vscode.window.showInformationMessage('Keine Repositories gefunden.'); - return; - } - - const repoNames = repos.map(repo => repo.full_name); - const selectedRepo = await vscode.window.showQuickPick(repoNames, { - placeHolder: 'Wähle ein Repository zum Klonen aus' - }); - - if (!selectedRepo) { - vscode.window.showInformationMessage('Kein Repository ausgewählt.'); - return; - } - - const repo = repos.find(r => r.full_name === selectedRepo); - - if (repo && repo.ssh_url) { - const folderUri = await vscode.window.showOpenDialog({ - canSelectFiles: false, - canSelectFolders: true, - canSelectMany: false, - openLabel: 'Ordner zum Klonen auswählen' - }); - - if (folderUri && folderUri[0]) { - const folderPath = folderUri[0].fsPath; - - vscode.window.withProgress({ - location: vscode.ProgressLocation.Notification, - title: `Cloning ${selectedRepo}`, - cancellable: false - }, async (progress, token) => { - progress.report({ message: 'Klonvorgang läuft...', increment: 0 }); - - return new Promise((resolve, reject) => { - exec(`git clone ${repo.ssh_url} ${folderPath}`, (error, stdout, stderr) => { - if (error) { - vscode.window.showErrorMessage('Fehler beim Klonen des Repositories.'); - console.error(stderr); - reject(error); - } else { - progress.report({ message: 'Repository geklont.', increment: 100 }); - vscode.window.showInformationMessage(`Repository ${selectedRepo} erfolgreich geklont.`); - - // Öffne das geklonte Repository im VSCode - try { - vscode.commands.executeCommand('vscode.openFolder', vscode.Uri.file(folderPath), true) - .then(() => resolve()); - } catch (err: unknown) { - vscode.window.showErrorMessage('Fehler beim Öffnen des geklonten Repositories.'); - console.error(err); - reject(err); - } - } - }); - }); - }); - } else { - vscode.window.showInformationMessage('Kein Zielordner ausgewählt.'); - } - } else { - vscode.window.showErrorMessage('Konnte die SSH-Klon-URL nicht finden.'); - } -} - // Aktivierungsfunktion des Plugins export function activate(context: vscode.ExtensionContext) { // Registriert den Befehl zur Authentifizierung @@ -220,11 +185,7 @@ export function activate(context: vscode.ExtensionContext) { let configCommand = vscode.commands.registerCommand('gitea.configure', configureGitea); context.subscriptions.push(configCommand); - // Befehl zum Klonen eines Repositories - let cloneCommand = vscode.commands.registerCommand('gitea.cloneRepository', cloneGiteaRepository); - context.subscriptions.push(cloneCommand); - - // Registriere den Befehl zum Erstellen eines Pull Requests + // Registriere den Befehl zum Erstellen eines Pull Requests let pullRequestCommand = vscode.commands.registerCommand('gitea.createPullRequest', createGiteaPullRequest); context.subscriptions.push(pullRequestCommand);