diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2f6ab4c..f4482d8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,22 +24,3 @@ jobs: run: npm run test - name: Run packaging smoke test run: npx @vscode/vsce package - - old-version-support: - runs-on: ubuntu-latest - steps: - - name: Install Git 2.25.1 - run: | - echo "deb http://archive.ubuntu.com/ubuntu focal-updates main universe" | sudo tee /etc/apt/sources.list.d/focal.list - sudo apt-get update - sudo apt-get install --allow-downgrades -y git=1:2.25.1-1ubuntu3.13 git-man=1:2.25.1-1ubuntu3.13 - - name: Checkout code - uses: actions/checkout@v4 - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - - name: Install dependencies - run: npm ci - - name: Run unit tests - run: npm run test diff --git a/package.json b/package.json index deb7a78..522abf3 100644 --- a/package.json +++ b/package.json @@ -18,11 +18,16 @@ "vscode": "^1.96.0" }, "main": "./out/extension.js", + "enabledApiProposals": ["contribSourceControlHistoryItemMenu"], "contributes": { "commands": [ { "command": "git-fixup.amendStagedChanges", "title": "Git Fixup: Amend Staged Changes" + }, + { + "command": "git-fixup.amendStagedChangesToHistoryItem", + "title": "Amend Staged Changes" } ], "keybindings": [ @@ -32,7 +37,22 @@ "mac": "cmd+alt+shift+f", "when": "editorTextFocus" } - ] + ], + "menus": { + "scm/historyItem/context": [ + { + "command": "git-fixup.amendStagedChangesToHistoryItem", + "when": "scmProvider == git", + "group": "1_actions" + } + ], + "commandPalette": [ + { + "command": "git-fixup.amendStagedChangesToHistoryItem", + "when": "false" + } + ] + } }, "scripts": { "vscode:prepublish": "npm run compile", diff --git a/src/GitFacade.ts b/src/GitFacade.ts index 2aff7c0..cc02f55 100644 --- a/src/GitFacade.ts +++ b/src/GitFacade.ts @@ -63,11 +63,20 @@ export class GitFacade { } } + async isCommitInUptream(hash: string): Promise { + const result = await this.git.raw(['branch', '-r', '--contains', hash]) + return (result.trim() !== '') + } + async getLatestFixedCommit(): Promise { // old Git versions (earlier than v2.26) include "-i" in the message return (await this.queryCommits('--grep-reflog=rebase (fixup)', '--grep-reflog=rebase -i (fixup)', '--walk-reflogs', '-1'))[0] } + async getCommit(hash: string): Promise { + return (await this.queryCommits(hash, '-1'))[0] + } + private async queryCommits(...args: string[]): Promise { const lines = toLines(await this.git.raw(['log', ...args, '--format=%h %s'])) return lines.map((line) => { diff --git a/src/extension.ts b/src/extension.ts index ba30968..e58163d 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,5 +1,6 @@ import * as vscode from 'vscode' import { GitFacade, NO_UPSTREAM } from './GitFacade' +import { Commit } from './types' export const activate = (context: vscode.ExtensionContext): void => { @@ -29,6 +30,26 @@ export const activate = (context: vscode.ExtensionContext): void => { } } + const amendStagedChangesToCommit = async (selectedCommit: Commit & { isInUpstream: boolean }) => { + if (selectedCommit.isInUpstream) { + const confirm = await vscode.window.showInformationMessage('The commit has been pushed to the upstream. Do you want to proceed?', 'Yes', 'No') + if (confirm !== 'Yes') { + return + } + } + await git.commitFixup(selectedCommit.hash) + const isMergeConflict = await git.rebaseFixupCommit(selectedCommit.hash) + if (isMergeConflict) { + writeToOutputChannel(`Merge conflict while fixing ${selectedCommit.hash}`) + vscode.window.showErrorMessage('Merge conflict\n\nResolve the conflict manually, then use Git: Commit (Amend) to commit the changes.\n', { modal: true }) + return + } + const fixedCommit = await git.getLatestFixedCommit() + const successMessage = `Fixed: ${selectedCommit.hash}->${fixedCommit.hash} - ${selectedCommit.subject}` + writeToOutputChannel(successMessage) + vscode.window.showInformationMessage(successMessage) + } + const disposable = vscode.commands.registerCommand('git-fixup.amendStagedChanges', async () => { try { @@ -66,30 +87,14 @@ export const activate = (context: vscode.ExtensionContext): void => { iconPath: getCommitIcon(isInUpstream, isHighlighted), label: ` ${messageSubject}`, hash: commit.hash, - messageSubject, + subject: commit.subject, isInUpstream } }))) const selectedCommit = await vscode.window.showQuickPick(commitChoices, { placeHolder: 'Select a Git commit to fix up' }) if (selectedCommit !== undefined) { - if (selectedCommit.isInUpstream) { - const confirm = await vscode.window.showInformationMessage('The commit has been pushed to the upstream. Do you want to proceed?', 'Yes', 'No') - if (confirm !== 'Yes') { - return - } - } - await git.commitFixup(selectedCommit.hash) - const isMergeConflict = await git.rebaseFixupCommit(selectedCommit.hash) - if (isMergeConflict) { - writeToOutputChannel(`Merge conflict while fixing ${selectedCommit.hash}`) - vscode.window.showErrorMessage('Merge conflict\n\nResolve the conflict manually, then use Git: Commit (Amend) to commit the changes.\n', { modal: true }) - return - } - const fixedCommit = await git.getLatestFixedCommit() - const successMessage = `Fixed: ${selectedCommit.hash}->${fixedCommit.hash} - ${selectedCommit.messageSubject}` - writeToOutputChannel(successMessage) - vscode.window.showInformationMessage(successMessage) + await amendStagedChangesToCommit(selectedCommit) } } catch (error) { vscode.window.showErrorMessage(error.message) @@ -97,4 +102,27 @@ export const activate = (context: vscode.ExtensionContext): void => { }) context.subscriptions.push(disposable, outputChannel) + + context.subscriptions.push( + // TODO explicit types for the arguments (most likely 1st is ISCMProvider and 2nd is ISCMHistoryItem) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + vscode.commands.registerCommand('git-fixup.amendStagedChangesToHistoryItem', async (_: any, historyItem: any) => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + git.updateWorkingDirectory(vscode.workspace.workspaceFolders![0]!.uri.fsPath) + const stagedFiles = await git.getStagedFiles() + if (stagedFiles.length === 0) { + vscode.window.showErrorMessage('No staged files') + return + } + const hash = historyItem.id + try { + const commit = await git.getCommit(hash) + const isInUpstream = await git.isCommitInUptream(hash) + await amendStagedChangesToCommit({ ...commit, isInUpstream }) + } catch (error) { + vscode.window.showErrorMessage(error.message) + } + }), + outputChannel + ) }