From e6532e3149713ff2558c8865880d630b15268cf5 Mon Sep 17 00:00:00 2001 From: Teo Gebhard Date: Sun, 19 Jan 2025 19:45:57 +0200 Subject: [PATCH 1/7] getStagedFiles() --- src/GitFacade.ts | 18 +++++++++++++----- src/extension.ts | 4 ++-- test/GitFacade.test.ts | 8 ++++---- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/GitFacade.ts b/src/GitFacade.ts index cee36fc..4b4bc20 100644 --- a/src/GitFacade.ts +++ b/src/GitFacade.ts @@ -3,6 +3,15 @@ import { BranchName, Commit, ShortCommitHash } from './types' export const NO_UPSTREAM = 'NO_UPSTREAM' +const toLines = (output: string): string[] => { + const trimmed = output.replace(/\n$/, '') + if (trimmed !== '') { + return trimmed.split('\n') + } else { + return [] + } +} + export class GitFacade { private readonly git: SimpleGit @@ -15,11 +24,6 @@ export class GitFacade { this.git.cwd(path) } - async hasStagedFiles(): Promise { - const diff = await this.git.diff(['--name-only', '--cached']) - return (diff.trim() !== '') - } - async getCurrentBranch(): Promise { const branchSummary = await this.git.branch() return branchSummary.current @@ -92,4 +96,8 @@ export class GitFacade { throw e } } + + async getStagedFiles(): Promise { + return toLines(await this.git.diff(['--cached', '--name-only'])) + } } diff --git a/src/extension.ts b/src/extension.ts index 4b05fc0..2f4d770 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -21,8 +21,8 @@ export const activate = (context: vscode.ExtensionContext): void => { } git.updateWorkingDirectory(workspaceFolder) - const hasStagedFiles = await git.hasStagedFiles() - if (!hasStagedFiles) { + const stagedFiles = await git.getStagedFiles() + if (stagedFiles.length === 0) { vscode.window.showErrorMessage('No staged files') return } diff --git a/test/GitFacade.test.ts b/test/GitFacade.test.ts index 4236434..bcb58b6 100644 --- a/test/GitFacade.test.ts +++ b/test/GitFacade.test.ts @@ -68,11 +68,11 @@ describe('GitFacade', () => { expect(commits.map((c) => c.subject)).toEqual(['subject 3', 'subject 2', 'subject 1']) }) - it('hasStagedFiles()', async () => { - expect(await facade.hasStagedFiles()).toBe(false) + it('getStagedFiles()', async () => { + expect(await facade.getStagedFiles()).toEqual([]) await modifyFileAndStageChanges('foobar') - expect(await facade.hasStagedFiles()).toBe(true) + expect(await facade.getStagedFiles()).toEqual(['file.txt']) await git.commit('-') - expect(await facade.hasStagedFiles()).toBe(false) + expect(await facade.getStagedFiles()).toEqual([]) }) }) From 039fe784e8f2bdc34d4b05209e4fe0acac8af93d Mon Sep 17 00:00:00 2001 From: Teo Gebhard Date: Sun, 19 Jan 2025 19:46:40 +0200 Subject: [PATCH 2/7] use toLines() in queryCommits() --- src/GitFacade.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GitFacade.ts b/src/GitFacade.ts index 4b4bc20..41796f5 100644 --- a/src/GitFacade.ts +++ b/src/GitFacade.ts @@ -68,7 +68,7 @@ export class GitFacade { } private async queryCommits(...args: string[]): Promise { - const lines = (await this.git.raw(['log', ...args, '--format=%h %s'])).trim().split('\n') + const lines = toLines(await this.git.raw(['log', ...args, '--format=%h %s'])) return lines.map((line) => { const separatorIndex = line.indexOf(' ') const hash = line.slice(0, separatorIndex) From 18fd22dd0c33668365c1083f4e4b4affadd8d844 Mon Sep 17 00:00:00 2001 From: Teo Gebhard Date: Sun, 19 Jan 2025 19:46:46 +0200 Subject: [PATCH 3/7] style --- src/GitFacade.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/GitFacade.ts b/src/GitFacade.ts index 41796f5..9911465 100644 --- a/src/GitFacade.ts +++ b/src/GitFacade.ts @@ -77,7 +77,6 @@ export class GitFacade { }) } - async commitFixup(hash: ShortCommitHash): Promise { await this.git.commit('', undefined, { '--fixup': hash }) } From a8143bfcd0d5349389bfe52df2dfbae725f46b17 Mon Sep 17 00:00:00 2001 From: Teo Gebhard Date: Sun, 19 Jan 2025 19:47:58 +0200 Subject: [PATCH 4/7] unit test for getFeatureBranchCommits() --- test/GitFacade.test.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/GitFacade.test.ts b/test/GitFacade.test.ts index bcb58b6..8462ba8 100644 --- a/test/GitFacade.test.ts +++ b/test/GitFacade.test.ts @@ -68,6 +68,14 @@ describe('GitFacade', () => { expect(commits.map((c) => c.subject)).toEqual(['subject 3', 'subject 2', 'subject 1']) }) + it('getFeatureBranchCommits()', async () => { + const branchName = `feature-${Date.now()}` + await git.checkoutLocalBranch(branchName) + expect(await facade.getFeatureBranchCommits(branchName, 'main')).toHaveLength(0) + await createCommit('foo', 'bar') + expect(await facade.getFeatureBranchCommits(branchName, 'main')).toHaveLength(1) + }) + it('getStagedFiles()', async () => { expect(await facade.getStagedFiles()).toEqual([]) await modifyFileAndStageChanges('foobar') From 6fbdbc8d43b2c608cde20a4cff47de937b3fcdd0 Mon Sep 17 00:00:00 2001 From: Teo Gebhard Date: Sun, 19 Jan 2025 19:48:26 +0200 Subject: [PATCH 5/7] getModifiedFiles() --- src/GitFacade.ts | 4 ++++ test/GitFacade.test.ts | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/src/GitFacade.ts b/src/GitFacade.ts index 9911465..46d9c14 100644 --- a/src/GitFacade.ts +++ b/src/GitFacade.ts @@ -99,4 +99,8 @@ export class GitFacade { async getStagedFiles(): Promise { return toLines(await this.git.diff(['--cached', '--name-only'])) } + + async getModifiedFiles(hash: ShortCommitHash): Promise { + return toLines(await this.git.raw(['show', '--name-only', '--pretty=format:', hash])) + } } diff --git a/test/GitFacade.test.ts b/test/GitFacade.test.ts index 8462ba8..8363fab 100644 --- a/test/GitFacade.test.ts +++ b/test/GitFacade.test.ts @@ -83,4 +83,9 @@ describe('GitFacade', () => { await git.commit('-') expect(await facade.getStagedFiles()).toEqual([]) }) + + it('getModifiedFiles()', async () => { + const hash = (await git.raw(['rev-parse', '--short', 'HEAD'])).trim() + expect(await facade.getModifiedFiles(hash)).toEqual(['file.txt']) + }) }) From 0ded8e7e1cfeb43a1ff06927d01807a6e463d77a Mon Sep 17 00:00:00 2001 From: Teo Gebhard Date: Sun, 19 Jan 2025 20:23:12 +0200 Subject: [PATCH 6/7] highlight matching commits --- images/commit-dark-highlighted.svg | 4 +++ images/commit-dark-upstream-highlighted.svg | 5 ++++ images/commit-dark-upstream.svg | 4 +++ images/commit-dark.svg | 3 ++ images/commit-light-highlighted.svg | 4 +++ images/commit-light-upstream-highlighted.svg | 5 ++++ images/commit-light-upstream.svg | 4 +++ images/commit-light.svg | 3 ++ src/extension.ts | 31 ++++++++++++++++---- 9 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 images/commit-dark-highlighted.svg create mode 100644 images/commit-dark-upstream-highlighted.svg create mode 100644 images/commit-dark-upstream.svg create mode 100644 images/commit-dark.svg create mode 100644 images/commit-light-highlighted.svg create mode 100644 images/commit-light-upstream-highlighted.svg create mode 100644 images/commit-light-upstream.svg create mode 100644 images/commit-light.svg diff --git a/images/commit-dark-highlighted.svg b/images/commit-dark-highlighted.svg new file mode 100644 index 0000000..94f0dd2 --- /dev/null +++ b/images/commit-dark-highlighted.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/images/commit-dark-upstream-highlighted.svg b/images/commit-dark-upstream-highlighted.svg new file mode 100644 index 0000000..91b19a8 --- /dev/null +++ b/images/commit-dark-upstream-highlighted.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/images/commit-dark-upstream.svg b/images/commit-dark-upstream.svg new file mode 100644 index 0000000..d9ba8d9 --- /dev/null +++ b/images/commit-dark-upstream.svg @@ -0,0 +1,4 @@ + + + + diff --git a/images/commit-dark.svg b/images/commit-dark.svg new file mode 100644 index 0000000..e53384a --- /dev/null +++ b/images/commit-dark.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/images/commit-light-highlighted.svg b/images/commit-light-highlighted.svg new file mode 100644 index 0000000..03471c3 --- /dev/null +++ b/images/commit-light-highlighted.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/images/commit-light-upstream-highlighted.svg b/images/commit-light-upstream-highlighted.svg new file mode 100644 index 0000000..4abda1d --- /dev/null +++ b/images/commit-light-upstream-highlighted.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/images/commit-light-upstream.svg b/images/commit-light-upstream.svg new file mode 100644 index 0000000..685d698 --- /dev/null +++ b/images/commit-light-upstream.svg @@ -0,0 +1,4 @@ + + + + diff --git a/images/commit-light.svg b/images/commit-light.svg new file mode 100644 index 0000000..b3bf4b6 --- /dev/null +++ b/images/commit-light.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index 2f4d770..ba30968 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,6 +1,5 @@ import * as vscode from 'vscode' import { GitFacade, NO_UPSTREAM } from './GitFacade' -import { Commit } from './types' export const activate = (context: vscode.ExtensionContext): void => { @@ -11,6 +10,25 @@ export const activate = (context: vscode.ExtensionContext): void => { outputChannel.appendLine(`${new Date().toISOString()} ${message}`) } + const getCommitIcon = (isInUpstream: boolean, isHighlighted: boolean): vscode.IconPath => { + const getThemeIconUri = (theme: 'light' | 'dark'): vscode.Uri => { + const fileNameParts: string[] = [] + fileNameParts.push('commit') + fileNameParts.push(theme) + if (isInUpstream) { + fileNameParts.push('upstream') + } + if (isHighlighted) { + fileNameParts.push('highlighted') + } + return vscode.Uri.joinPath(context.extensionUri, 'images', `${fileNameParts.join('-')}.svg`) + } + return { + light: getThemeIconUri('light'), + dark: getThemeIconUri('dark') + } + } + const disposable = vscode.commands.registerCommand('git-fixup.amendStagedChanges', async () => { try { @@ -39,16 +57,19 @@ export const activate = (context: vscode.ExtensionContext): void => { } const commitsNotInUpstream = await git.getCommitsNotInUpstream() - const commitChoices = selectableCommits.map((commit: Commit) => { + const commitChoices = await Promise.all((selectableCommits.map(async (commit) => { const isInUpstream = (commitsNotInUpstream !== NO_UPSTREAM) && (commitsNotInUpstream.find((upstreamCommit) => upstreamCommit.hash === commit.hash) === undefined) + const modifiedFiles = await git.getModifiedFiles(commit.hash) + const isHighlighted = modifiedFiles.some((filePath) => stagedFiles.includes(filePath)) const messageSubject = commit.subject return { - label: `${isInUpstream ? '$(cloud)' : '$(git-commit)'} ${messageSubject}`, + iconPath: getCommitIcon(isInUpstream, isHighlighted), + label: ` ${messageSubject}`, hash: commit.hash, messageSubject, - isInUpstream: isInUpstream + isInUpstream } - }) + }))) const selectedCommit = await vscode.window.showQuickPick(commitChoices, { placeHolder: 'Select a Git commit to fix up' }) if (selectedCommit !== undefined) { From b88d1d12bd8e93127bee9b5ed37c0b6282943b3e Mon Sep 17 00:00:00 2001 From: Teo Gebhard Date: Sun, 19 Jan 2025 20:23:18 +0200 Subject: [PATCH 7/7] changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a60ea9f..5c106f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +- Highlight commits that match staged files + ## 1.1.0 (2025-01-05) - Dynamic hash length