Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions __tests__/git.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ describe('ref', () => {
case 'git show -s --pretty=%D':
result = 'HEAD, tag: 8.0.0';
break;
case 'git for-each-ref --format=%(refname) --points-at HEAD refs/tags/':
result = 'refs/tags/8.0.0';
break;
}
return Promise.resolve({
stdout: result,
Expand All @@ -144,6 +147,9 @@ describe('ref', () => {
case 'git show -s --pretty=%D':
result = 'grafted, HEAD, tag: 8.0.0';
break;
case 'git for-each-ref --format=%(refname) --points-at HEAD refs/tags/':
result = 'refs/tags/8.0.0';
break;
}
return Promise.resolve({
stdout: result,
Expand Down Expand Up @@ -408,6 +414,81 @@ describe('ref', () => {
const ref = await Git.ref();
expect(ref).toEqual('refs/remotes/unusual-format');
});

it('returns mocked detached tag ref when commit also has branch decorations', async () => {
vi.spyOn(Exec, 'getExecOutput').mockImplementation((cmd, args): Promise<ExecOutput> => {
const fullCmd = `${cmd} ${args?.join(' ')}`;
let result = '';
switch (fullCmd) {
case 'git branch --show-current':
result = '';
break;
case 'git show -s --pretty=%D':
result = 'HEAD, tag: v8.0.0, origin/release-branch';
break;
case 'git for-each-ref --format=%(refname) --points-at HEAD refs/tags/':
result = 'refs/tags/v8.0.0';
break;
}
return Promise.resolve({
stdout: result,
stderr: '',
exitCode: 0
});
});
const ref = await Git.ref();
expect(ref).toEqual('refs/tags/v8.0.0');
});

it('returns mocked detached tag ref (shallow clone) when commit also has branch decorations', async () => {
vi.spyOn(Exec, 'getExecOutput').mockImplementation((cmd, args): Promise<ExecOutput> => {
const fullCmd = `${cmd} ${args?.join(' ')}`;
let result = '';
switch (fullCmd) {
case 'git branch --show-current':
result = '';
break;
case 'git show -s --pretty=%D':
result = 'grafted, HEAD, tag: v8.0.0, origin/release-branch';
break;
case 'git for-each-ref --format=%(refname) --points-at HEAD refs/tags/':
result = 'refs/tags/v8.0.0';
break;
}
return Promise.resolve({
stdout: result,
stderr: '',
exitCode: 0
});
});
const ref = await Git.ref();
expect(ref).toEqual('refs/tags/v8.0.0');
});

it('returns mocked detached tag ref when tag name contains a comma', async () => {
vi.spyOn(Exec, 'getExecOutput').mockImplementation((cmd, args): Promise<ExecOutput> => {
const fullCmd = `${cmd} ${args?.join(' ')}`;
let result = '';
switch (fullCmd) {
case 'git branch --show-current':
result = '';
break;
case 'git show -s --pretty=%D':
result = 'HEAD, tag: release,with-comma, origin/release-branch';
break;
case 'git for-each-ref --format=%(refname) --points-at HEAD refs/tags/':
result = 'refs/tags/release,with-comma';
break;
}
return Promise.resolve({
stdout: result,
stderr: '',
exitCode: 0
});
});
const ref = await Git.ref();
expect(ref).toEqual('refs/tags/release,with-comma');
});
});

describe('fullCommit', () => {
Expand Down
24 changes: 23 additions & 1 deletion src/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export class Git {

// Tag refs are formatted as "tag: <tagname>"
if (ref.startsWith('tag: ')) {
return `refs/tags/${ref.split(':')[1].trim()}`;
return await Git.findDetachedTagRef(ref, res);
}

// Pull request merge refs are formatted as "pull/<number>/<state>"
Expand Down Expand Up @@ -201,6 +201,28 @@ export class Git {
throw new Error(`Cannot infer ref from detached HEAD`);
}

private static async findDetachedTagRef(tagDecoration: string, originalRef: string): Promise<string> {
const tagRefs = await Git.exec(['for-each-ref', '--format=%(refname)', '--points-at', 'HEAD', 'refs/tags/']);
const refs = tagRefs
.split('\n')
.map(tagRef => tagRef.trim())
.filter(tagRef => tagRef.length > 0)
.sort((a, b) => b.length - a.length);

for (const tagRef of refs) {
const decoration = `tag: ${tagRef.slice('refs/tags/'.length)}`;
if (tagDecoration === decoration || tagDecoration.startsWith(`${decoration}, `)) {
return tagRef;
}
}

if (refs.length === 1) {
return refs[0];
}

throw new Error(`Cannot find detached tag ref in "${originalRef}"`);
}

private static async findContainingRef(scope: string): Promise<string | undefined> {
const refs = await Git.exec(['for-each-ref', '--format=%(refname)', '--contains', 'HEAD', '--sort=-committerdate', scope]);

Expand Down
Loading