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
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/GitFacade.ts b/src/GitFacade.ts
index cee36fc..46d9c14 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
@@ -64,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)
@@ -73,7 +77,6 @@ export class GitFacade {
})
}
-
async commitFixup(hash: ShortCommitHash): Promise {
await this.git.commit('', undefined, { '--fixup': hash })
}
@@ -92,4 +95,12 @@ export class GitFacade {
throw e
}
}
+
+ 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/src/extension.ts b/src/extension.ts
index 4b05fc0..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 {
@@ -21,8 +39,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
}
@@ -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) {
diff --git a/test/GitFacade.test.ts b/test/GitFacade.test.ts
index 4236434..8363fab 100644
--- a/test/GitFacade.test.ts
+++ b/test/GitFacade.test.ts
@@ -68,11 +68,24 @@ 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('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')
- 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([])
+ })
+
+ it('getModifiedFiles()', async () => {
+ const hash = (await git.raw(['rev-parse', '--short', 'HEAD'])).trim()
+ expect(await facade.getModifiedFiles(hash)).toEqual(['file.txt'])
})
})