diff --git a/CHANGELOG.md b/CHANGELOG.md index 50055df..24a4957 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## next +- Added `repo.currentBranch()` method - Added `repo.describeRef(ref)` method, which returns an information object about the reference - Added `repo.isOid(value)` method to check if a value is an object ID diff --git a/README.md b/README.md index 52dd7ea..e0af469 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,19 @@ The algorithm to identify a default branch name: - `main` - `master` +#### repo.currentBranch() + +Returns the current branch name along with its commit oid. +If the repository is in a detached HEAD state, `name` will be `null`. + +```js +const currentBranch = repo.currentBranch(); +// { name: 'main', oid: '8bb6e23769902199e39ab70f2441841712cbdd62' } + +const detachedHead = repo.currentBranch(); +// { name: null, oid: '8bb6e23769902199e39ab70f2441841712cbdd62' } +``` + #### repo.isRefExists(ref) Checks if a `ref` exists. diff --git a/fixtures/base/collect-data.js b/fixtures/base/collect-data.js index 17596a4..951755f 100644 --- a/fixtures/base/collect-data.js +++ b/fixtures/base/collect-data.js @@ -4,6 +4,8 @@ import { fileURLToPath } from 'url'; import { createGitReader, parseTree } from '../../lib/index.js'; const repo = await createGitReader(fileURLToPath(new URL('_git', import.meta.url))); +const defaultBranch = await repo.defaultBranch(); +const currentBranch = await repo.currentBranch(); const commits = await repo.log(); const stat = await repo.stat(); const objects = {}; @@ -38,6 +40,8 @@ fs.writeFileSync( fileURLToPath(new URL('data.json', import.meta.url)), JSON.stringify( { + defaultBranch, + currentBranch, commits, filesLists, filesDelta, diff --git a/fixtures/base/data.json b/fixtures/base/data.json index 094ecb5..89d984f 100644 --- a/fixtures/base/data.json +++ b/fixtures/base/data.json @@ -1,4 +1,9 @@ { + "defaultBranch": "main", + "currentBranch": { + "name": "test", + "oid": "2dbee47a8d4f8d39e1168fad951b703ee05614d6" + }, "commits": [ { "oid": "2dbee47a8d4f8d39e1168fad951b703ee05614d6", diff --git a/fixtures/clean/_git/HEAD b/fixtures/clean/_git/HEAD index 786c048..b9d1410 100644 --- a/fixtures/clean/_git/HEAD +++ b/fixtures/clean/_git/HEAD @@ -1 +1 @@ -ref: refs/heads/development +ref: refs/heads/empty-branch diff --git a/src/resolve-ref.ts b/src/resolve-ref.ts index 7081ce5..66943ad 100644 --- a/src/resolve-ref.ts +++ b/src/resolve-ref.ts @@ -90,7 +90,6 @@ export async function createRefIndex(gitdir: string) { } const refInfo = await refResolver.resolve(expandedRef); - const [, scope, path] = expandedRef.match(/^([^/]+\/[^/]+)\/(.+)$/) || [ '', 'refs/heads', @@ -163,6 +162,37 @@ export async function createRefIndex(gitdir: string) { .replace(/^ref:\s*/, '') ); + const currentBranch = async function () { + const { ref, oid } = await describeRef('HEAD'); + const name = ref ? (await describeRef(ref)).name : null; + + return { + name, + oid + }; + }; + + // inspired by https://usethis.r-lib.org/reference/git-default-branch.html + const defaultBranch = async function () { + const branches = (await listBranches()) as string[]; // FIXME: remove string[] + + if (branches.length <= 1) { + return basename(branches[0]); + } + + const branchRef = + expandRef('refs/remotes/upstream/HEAD') || + expandRef('refs/remotes/origin/HEAD') || + expandRef('refs/heads/main') || + expandRef('refs/heads/master'); + + if (branchRef) { + return branchRef.endsWith('/HEAD') ? readRefContent(branchRef) : basename(branchRef); + } + + return null; + }; + return { isOid, isRefExists: (ref: string) => expandRef(ref) !== null, @@ -175,28 +205,8 @@ export async function createRefIndex(gitdir: string) { listBranches, listTags, - // inspired by https://usethis.r-lib.org/reference/git-default-branch.html - async defaultBranch() { - const branches = (await listBranches()) as string[]; // FIXME: remove string[] - - if (branches.length === 1) { - return basename(branches[0]); - } - - const branchRef = - expandRef('refs/remotes/upstream/HEAD') || - expandRef('refs/remotes/origin/HEAD') || - expandRef('refs/heads/main') || - expandRef('refs/heads/master'); - - if (branchRef) { - return branchRef.endsWith('/HEAD') - ? readRefContent(branchRef) - : basename(branchRef); - } - - return null; - }, + defaultBranch, + currentBranch, async stat() { const remotes = listRemotes(); diff --git a/test/resolve-ref.ts b/test/resolve-ref.ts index 397bb2f..e43dbb9 100644 --- a/test/resolve-ref.ts +++ b/test/resolve-ref.ts @@ -16,10 +16,11 @@ describe('resolve-ref', () => { describe('defaultBranch()', () => { const defaultBranches = { base: 'main', + detached: 'main', cruft: 'main', 'no-remotes': 'main', upstream: 'fork-main', - clean: 'development' // FIXME + clean: 'empty-branch' // FIXME }; for (const [repoName, expected] of Object.entries(defaultBranches)) { @@ -34,6 +35,28 @@ describe('resolve-ref', () => { } }); + describe('currentBranch()', () => { + const defaultBranches = { + base: { name: 'test', oid: '2dbee47a8d4f8d39e1168fad951b703ee05614d6' }, + detached: { name: null, oid: '2dbee47a8d4f8d39e1168fad951b703ee05614d6' }, + cruft: { name: 'main', oid: '7b84f676f2fbea2a3c6d83924fa63059c7bdfbe2' }, + 'no-remotes': { name: 'main', oid: '293e1ffaf7158c249fc654aec07eca555a25de09' }, + upstream: { name: 'fork-main', oid: '293e1ffaf7158c249fc654aec07eca555a25de09' }, + clean: { name: 'empty-branch', oid: null } // FIXME + }; + + for (const [repoName, expected] of Object.entries(defaultBranches)) { + (repoName === 'clean' ? it.skip : it)(repoName, async () => { + const repo = await fixtures[repoName].repo(); + const actual = await repo.currentBranch(); + + assert.deepStrictEqual(actual, expected); + + return repo.dispose(); + }); + } + }); + describe('listBranches()', () => { it('local branches', async () => { const actual = await repo.listBranches();