Skip to content

Commit 522d202

Browse files
authored
feat: Dynamic commit hash length (#5)
Query short commit hash from git instead of using custom `toShortHash()` to shorten it. This way the length of the commit hash is dynamic instead of always being 7 chars long. Now e.g. git's `core.abbrev` config option affects the length of the short hash. Also query short commit message (the _subject_ of the message) from git instead of using `toShortCommitMessage()`.
1 parent edabe1c commit 522d202

File tree

6 files changed

+50
-50
lines changed

6 files changed

+50
-50
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## Unreleased
9+
10+
- Dynamic hash length
11+
812
## 1.0.0 (2025-01-02)
913

1014
- Initial release

src/GitFacade.ts

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import simpleGit, { SimpleGit } from 'simple-git'
2-
import { BranchName, Commit, CommitHash } from './types'
2+
import { BranchName, Commit, ShortCommitHash } from './types'
33

44
export const NO_UPSTREAM = 'NO_UPSTREAM'
55

@@ -15,6 +15,11 @@ export class GitFacade {
1515
this.git.cwd(path)
1616
}
1717

18+
async getStagedFiles(): Promise<string[]> {
19+
const diff = await this.git.diff(['--name-only', '--cached']);
20+
return diff.split('\n').filter((line) => line !== '')
21+
}
22+
1823
async getCurrentBranch(): Promise<BranchName> {
1924
const branchSummary = await this.git.branch()
2025
return branchSummary.current
@@ -36,38 +41,48 @@ export class GitFacade {
3641
}
3742

3843
async getFeatureBranchCommits(featureBranch: BranchName, mainBranch: BranchName): Promise<readonly Commit[]> {
39-
return (await this.git.log([featureBranch, '--not', mainBranch])).all
44+
return this.queryCommits(featureBranch, '--not', mainBranch)
4045
}
4146

4247
async getMainBranchCommits(mainBranch: BranchName): Promise<readonly Commit[]> {
43-
return (await this.git.log([mainBranch])).all
48+
return await this.queryCommits(mainBranch)
4449
}
45-
50+
4651
async getCommitsNotInUpstream(): Promise<readonly Commit[] | typeof NO_UPSTREAM> {
4752
try {
48-
return (await this.git.log(['@{u}..'])).all
53+
return (await this.queryCommits('@{u}..'))
4954
} catch (e: any) {
5055
if (e.message?.includes('no upstream configured')) {
5156
return NO_UPSTREAM
5257
}
5358
throw e
5459
}
5560
}
56-
57-
async getStagedFiles(): Promise<string[]> {
58-
const diff = await this.git.diff(['--name-only', '--cached']);
59-
return diff.split('\n').filter((line) => line !== '')
61+
62+
async getLatestFixedCommit(): Promise<Commit> {
63+
return (await this.queryCommits('-g', '-1', '--grep-reflog=rebase \(fixup\)'))[0]
64+
}
65+
66+
private async queryCommits(...args: string[]): Promise<Commit[]> {
67+
const lines = (await this.git.raw(['log', ...args, '--format=%h %s'])).trim().split('\n')
68+
return lines.map((line) => {
69+
const separatorIndex = line.indexOf(' ')
70+
const hash = line.slice(0, separatorIndex)
71+
const subject = line.slice(separatorIndex + 1)
72+
return { hash, subject }
73+
})
6074
}
6175

62-
async commitFixup(hash: CommitHash): Promise<void> {
76+
77+
async commitFixup(hash: ShortCommitHash): Promise<void> {
6378
await this.git.commit('', undefined, { '--fixup': hash })
6479
}
6580

66-
async rebaseFixupCommit(hash: CommitHash): Promise<boolean> {
81+
async rebaseFixupCommit(hash: ShortCommitHash): Promise<boolean> {
6782
try {
6883
await this.git.env({
6984
...process.env,
70-
GIT_SEQUENCE_EDITOR: 'true' // Bypass the interactive editor
85+
GIT_SEQUENCE_EDITOR: 'true' // bypass the interactive editor
7186
}).rebase(['--autosquash', '--autostash', '-i', `${hash}~`])
7287
return false
7388
} catch (e: any) {
@@ -77,8 +92,4 @@ export class GitFacade {
7792
throw e
7893
}
7994
}
80-
81-
async getLatestFixedCommit(): Promise<CommitHash> {
82-
return await this.git.raw(['log', '-g', '-1', '--grep-reflog=rebase \(fixup\)', '--pretty=format:%h'])
83-
}
8495
}

src/extension.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import * as vscode from 'vscode'
22
import { GitFacade, NO_UPSTREAM } from './GitFacade'
33
import { Commit } from './types'
4-
import { toShortCommitMessage, toShortHash } from './utils'
54

65
export const activate = (context: vscode.ExtensionContext): void => {
76

@@ -42,11 +41,11 @@ export const activate = (context: vscode.ExtensionContext): void => {
4241
const commitsNotInUpstream = await git.getCommitsNotInUpstream()
4342
const commitChoices = selectableCommits.map((commit: Commit) => {
4443
const isInUpstream = (commitsNotInUpstream !== NO_UPSTREAM) && (commitsNotInUpstream.find((upstreamCommit) => upstreamCommit.hash === commit.hash) === undefined)
45-
const shortMessage = toShortCommitMessage(commit.message)
44+
const messageSubject = commit.subject
4645
return {
47-
label: `${isInUpstream ? '$(cloud)' : '$(git-commit)'} ${shortMessage}`,
46+
label: `${isInUpstream ? '$(cloud)' : '$(git-commit)'} ${messageSubject}`,
4847
hash: commit.hash,
49-
shortMessage,
48+
messageSubject,
5049
isInUpstream: isInUpstream
5150
}
5251
})
@@ -62,12 +61,12 @@ export const activate = (context: vscode.ExtensionContext): void => {
6261
await git.commitFixup(selectedCommit.hash)
6362
const isMergeConflict = await git.rebaseFixupCommit(selectedCommit.hash)
6463
if (isMergeConflict) {
65-
writeToOutputChannel(`Merge conflict while fixing ${toShortHash(selectedCommit.hash)}`)
64+
writeToOutputChannel(`Merge conflict while fixing ${selectedCommit.hash}`)
6665
vscode.window.showErrorMessage('Merge conflict\n\nResolve the conflict manually, then use Git: Commit (Amend) to commit the changes.\n', { modal: true })
6766
return
6867
}
69-
const fixedCommitHash = await git.getLatestFixedCommit()
70-
const successMessage = `Fixed: ${toShortHash(selectedCommit.hash)}->${toShortHash(fixedCommitHash)} - ${selectedCommit.shortMessage}`
68+
const fixedCommit = await git.getLatestFixedCommit()
69+
const successMessage = `Fixed: ${selectedCommit.hash}->${fixedCommit.hash} - ${selectedCommit.messageSubject}`
7170
writeToOutputChannel(successMessage)
7271
vscode.window.showInformationMessage(successMessage)
7372
}

src/types.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
export type CommitHash = string
1+
export type ShortCommitHash = string
22

33
export type BranchName = string
44

55
export interface Commit {
6-
hash: CommitHash
7-
message: string
6+
hash: ShortCommitHash
7+
subject: string
88
}

src/utils.ts

Lines changed: 0 additions & 14 deletions
This file was deleted.

test/GitFacade.test.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import * as os from 'node:os'
33
import * as path from 'node:path'
44
import simpleGit, { SimpleGit } from 'simple-git'
55
import { GitFacade } from '../src/GitFacade'
6-
import { toShortHash } from '../src/utils'
76

87
describe('GitFacade', () => {
98

@@ -37,9 +36,9 @@ describe('GitFacade', () => {
3736
git = simpleGit(repoDirectory)
3837
await git.init(['-b', 'main'])
3938
await configureGit()
40-
await createCommit('lorem\n\n', 'commit 1')
41-
await createCommit('lorem\n\n\n\nipsum\n\n', 'commit 2')
42-
await createCommit('lorem\n\n\n\nipsum\n\n\n\ndolor', 'commit 3')
39+
await createCommit('lorem\n\n', 'subject 1')
40+
await createCommit('lorem\n\n\n\nipsum\n\n', 'subject 2')
41+
await createCommit('lorem\n\n\n\nipsum\n\n\n\ndolor', 'subject 3\n\nbody test')
4342
facade = new GitFacade()
4443
facade.updateWorkingDirectory(repoDirectory)
4544
})
@@ -52,19 +51,20 @@ describe('GitFacade', () => {
5251
await facade.commitFixup(fixableCommit.hash)
5352
const isMergeConflict = await facade.rebaseFixupCommit(fixableCommit.hash)
5453
expect(isMergeConflict).toBe(false)
55-
const fixedCommitHash = await facade.getLatestFixedCommit()
54+
const fixedCommit = await facade.getLatestFixedCommit()
5655
const commitsAfter = await facade.getMainBranchCommits('main')
57-
expect(toShortHash(commitsAfter[FIXABLE_COMMIT_INDEX].hash)).toEqual(fixedCommitHash)
58-
const fixedCommitDiff = await git.raw(['diff', '-U0', `${fixedCommitHash}~`, `${fixedCommitHash}`])
56+
expect(commitsAfter[FIXABLE_COMMIT_INDEX].hash).toEqual(fixedCommit.hash)
57+
const fixedCommitDiff = await git.raw(['diff', '-U0', `${fixedCommit.hash}~`, `${fixedCommit.hash}`])
5958
expect(fixedCommitDiff).toContain('+\n+\n+foobar\n+\n')
60-
const fixupCommitDiff = await git.raw(['diff', '-U0', `${fixableCommit.hash}`, `${fixedCommitHash}`])
59+
const fixupCommitDiff = await git.raw(['diff', '-U0', `${fixableCommit.hash}`, `${fixedCommit.hash}`])
6160
expect(fixupCommitDiff).toContain('-ipsum\n+foobar\n')
6261
})
6362

6463
it('getMainBranchCommits()', async () => {
6564
const commits = await facade.getMainBranchCommits('main')
6665
expect(commits).toHaveLength(3)
67-
expect(commits.map((c) => c.hash.length)).toEqual([40, 40, 40])
68-
expect(commits.map((c) => c.message)).toEqual(['commit 3', 'commit 2', 'commit 1'])
66+
const expectedLength = (await git.raw(['rev-parse', '--short', 'HEAD'])).trim().length
67+
expect(commits.map((c) => c.hash.length)).toEqual([expectedLength, expectedLength, expectedLength])
68+
expect(commits.map((c) => c.subject)).toEqual(['subject 3', 'subject 2', 'subject 1'])
6969
})
7070
})

0 commit comments

Comments
 (0)