mirror of
https://github.com/pnpm/pnpm.git
synced 2025-12-23 23:29:17 -05:00
fix(git-resolver): installing git-hosted dependency using annotated tags (#10349)
close #10335
This commit is contained in:
8
.changeset/fix-annotated-git-tags.md
Normal file
8
.changeset/fix-annotated-git-tags.md
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
"@pnpm/git-resolver": patch
|
||||
"pnpm": patch
|
||||
---
|
||||
|
||||
Fix installation of Git dependencies using annotated tags [#10335](https://github.com/pnpm/pnpm/issues/10335).
|
||||
|
||||
Previously, pnpm would store the annotated tag object's SHA in the lockfile instead of the actual commit SHA. This caused `ERR_PNPM_GIT_CHECKOUT_FAILED` errors because the checked-out commit hash didn't match the stored tag object hash.
|
||||
@@ -30,7 +30,7 @@ test('should pick remoteTarball fetcher', async () => {
|
||||
})
|
||||
|
||||
test.each([
|
||||
'https://codeload.github.com/zkochan/is-negative/tar.gz/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5',
|
||||
'https://codeload.github.com/zkochan/is-negative/tar.gz/6dcce91c268805d456b8a575b67d7febc7ae2933',
|
||||
'https://bitbucket.org/pnpmjs/git-resolver/get/87cf6a67064d2ce56e8cd20624769a5512b83ff9.tar.gz',
|
||||
'https://gitlab.com/api/v4/projects/pnpm%2Fgit-resolver/repository/archive.tar.gz',
|
||||
])('should pick gitHostedTarball fetcher', async (tarball) => {
|
||||
|
||||
@@ -267,12 +267,12 @@ test('re-adding a git repo with a different tag', async () => {
|
||||
lockfile = project.readLockfile()
|
||||
expect(lockfile.importers['.'].dependencies?.['is-negative']).toEqual({
|
||||
specifier: 'github:kevva/is-negative#1.0.1',
|
||||
version: 'https://codeload.github.com/kevva/is-negative/tar.gz/9a89df745b2ec20ae7445d3d9853ceaeef5b0b72',
|
||||
version: 'https://codeload.github.com/kevva/is-negative/tar.gz/f7dec4d66a5a56719e49b9f94a24d73f924ddeb3',
|
||||
})
|
||||
expect(lockfile.packages).toEqual(
|
||||
{
|
||||
'is-negative@https://codeload.github.com/kevva/is-negative/tar.gz/9a89df745b2ec20ae7445d3d9853ceaeef5b0b72': {
|
||||
resolution: { tarball: 'https://codeload.github.com/kevva/is-negative/tar.gz/9a89df745b2ec20ae7445d3d9853ceaeef5b0b72' },
|
||||
'is-negative@https://codeload.github.com/kevva/is-negative/tar.gz/f7dec4d66a5a56719e49b9f94a24d73f924ddeb3': {
|
||||
resolution: { tarball: 'https://codeload.github.com/kevva/is-negative/tar.gz/f7dec4d66a5a56719e49b9f94a24d73f924ddeb3' },
|
||||
version: '1.0.1',
|
||||
engines: { node: '>=0.10.0' },
|
||||
},
|
||||
@@ -348,5 +348,5 @@ test('no hash character for github subdirectory install', async () => {
|
||||
], testDefaults())
|
||||
|
||||
expect(fs.readdirSync('./node_modules/.pnpm'))
|
||||
.toContain('only-allow@https+++codeload.github.com+pnpm+only-allow+tar.gz+4d577a5a5862a43e752df37a1e8a0c71c3a0084a+path++')
|
||||
.toContain('only-allow@https+++codeload.github.com+pnpm+only-allow+tar.gz+91ab41994c6a1b7319869fa8864163c9954f56ec+path++')
|
||||
})
|
||||
|
||||
@@ -83,11 +83,11 @@ function resolveVTags (vTags: string[], range: string): string | null {
|
||||
|
||||
async function getRepoRefs (repo: string, ref: string | null): Promise<Record<string, string>> {
|
||||
const gitArgs = [repo]
|
||||
if (ref !== 'HEAD') {
|
||||
gitArgs.unshift('--refs')
|
||||
}
|
||||
if (ref) {
|
||||
gitArgs.push(ref)
|
||||
// Also request the peeled ref for annotated tags (e.g., refs/tags/v1.0.0^{})
|
||||
// This is needed because annotated tags have their own SHA, and we need the commit SHA they point to
|
||||
gitArgs.push(`${ref}^{}`)
|
||||
}
|
||||
// graceful-git by default retries 10 times, reduce to single retry
|
||||
const result = await git(['ls-remote', ...gitArgs], { retries: 1 })
|
||||
@@ -123,7 +123,8 @@ function resolveRefFromRefs (refs: { [ref: string]: string }, repo: string, ref:
|
||||
|
||||
if (!commitId) {
|
||||
// check for a partial commit
|
||||
const commits = committish ? Object.values(refs).filter((value: string) => value.startsWith(ref)) : []
|
||||
// Use Set to deduplicate since multiple refs can point to the same commit
|
||||
const commits = committish ? [...new Set(Object.values(refs).filter((value: string) => value.startsWith(ref)))] : []
|
||||
if (commits.length === 1) {
|
||||
commitId = commits[0]
|
||||
} else {
|
||||
@@ -133,7 +134,7 @@ function resolveRefFromRefs (refs: { [ref: string]: string }, repo: string, ref:
|
||||
|
||||
return commitId
|
||||
} else {
|
||||
const vTags =
|
||||
const vTags = [...new Set(
|
||||
Object.keys(refs)
|
||||
// using the same semantics of version tags as https://github.com/zkat/pacote
|
||||
.filter((key: string) => /^refs\/tags\/v?\d+\.\d+\.\d+(?:[-+].+)?(?:\^\{\})?$/.test(key))
|
||||
@@ -143,10 +144,11 @@ function resolveRefFromRefs (refs: { [ref: string]: string }, repo: string, ref:
|
||||
.replace(/\^\{\}$/, '') // accept annotated tags
|
||||
})
|
||||
.filter((key: string) => semver.valid(key, true))
|
||||
)]
|
||||
const refVTag = resolveVTags(vTags, range)
|
||||
const commitId = refVTag &&
|
||||
(refs[`refs/tags/${refVTag}^{}`] || // prefer annotated tags
|
||||
refs[`refs/tags/${refVTag}`])
|
||||
refs[`refs/tags/${refVTag}`])
|
||||
|
||||
if (!commitId) {
|
||||
throw new Error(`Could not resolve ${range} to a commit of ${repo}. Available versions are: ${vTags.join(', ')}`)
|
||||
|
||||
@@ -115,10 +115,10 @@ test('resolveFromGit() with branch relative to refs', async () => {
|
||||
test('resolveFromGit() with tag', async () => {
|
||||
const resolveResult = await resolveFromGit({ bareSpecifier: 'zkochan/is-negative#2.0.1' })
|
||||
expect(resolveResult).toStrictEqual({
|
||||
id: 'https://codeload.github.com/zkochan/is-negative/tar.gz/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5',
|
||||
id: 'https://codeload.github.com/zkochan/is-negative/tar.gz/6dcce91c268805d456b8a575b67d7febc7ae2933',
|
||||
normalizedBareSpecifier: 'github:zkochan/is-negative#2.0.1',
|
||||
resolution: {
|
||||
tarball: 'https://codeload.github.com/zkochan/is-negative/tar.gz/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5',
|
||||
tarball: 'https://codeload.github.com/zkochan/is-negative/tar.gz/6dcce91c268805d456b8a575b67d7febc7ae2933',
|
||||
},
|
||||
resolvedVia: 'git-repository',
|
||||
})
|
||||
@@ -163,10 +163,10 @@ test.skip('resolveFromGit() with strict semver (v-prefixed tag)', async () => {
|
||||
test('resolveFromGit() with range semver', async () => {
|
||||
const resolveResult = await resolveFromGit({ bareSpecifier: 'zkochan/is-negative#semver:^1.0.0' })
|
||||
expect(resolveResult).toStrictEqual({
|
||||
id: 'https://codeload.github.com/zkochan/is-negative/tar.gz/9a89df745b2ec20ae7445d3d9853ceaeef5b0b72',
|
||||
id: 'https://codeload.github.com/zkochan/is-negative/tar.gz/f7dec4d66a5a56719e49b9f94a24d73f924ddeb3',
|
||||
normalizedBareSpecifier: 'github:zkochan/is-negative#semver:^1.0.0',
|
||||
resolution: {
|
||||
tarball: 'https://codeload.github.com/zkochan/is-negative/tar.gz/9a89df745b2ec20ae7445d3d9853ceaeef5b0b72',
|
||||
tarball: 'https://codeload.github.com/zkochan/is-negative/tar.gz/f7dec4d66a5a56719e49b9f94a24d73f924ddeb3',
|
||||
},
|
||||
resolvedVia: 'git-repository',
|
||||
})
|
||||
@@ -392,10 +392,10 @@ test.skip('resolveFromGit() gitlab with tag', async () => {
|
||||
test('resolveFromGit() normalizes full url', async () => {
|
||||
const resolveResult = await resolveFromGit({ bareSpecifier: 'git+ssh://git@github.com:zkochan/is-negative.git#2.0.1' })
|
||||
expect(resolveResult).toStrictEqual({
|
||||
id: 'https://codeload.github.com/zkochan/is-negative/tar.gz/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5',
|
||||
id: 'https://codeload.github.com/zkochan/is-negative/tar.gz/6dcce91c268805d456b8a575b67d7febc7ae2933',
|
||||
normalizedBareSpecifier: 'github:zkochan/is-negative#2.0.1',
|
||||
resolution: {
|
||||
tarball: 'https://codeload.github.com/zkochan/is-negative/tar.gz/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5',
|
||||
tarball: 'https://codeload.github.com/zkochan/is-negative/tar.gz/6dcce91c268805d456b8a575b67d7febc7ae2933',
|
||||
},
|
||||
resolvedVia: 'git-repository',
|
||||
})
|
||||
@@ -404,10 +404,10 @@ test('resolveFromGit() normalizes full url', async () => {
|
||||
test('resolveFromGit() normalizes full url with port', async () => {
|
||||
const resolveResult = await resolveFromGit({ bareSpecifier: 'git+ssh://git@github.com:22:zkochan/is-negative.git#2.0.1' })
|
||||
expect(resolveResult).toStrictEqual({
|
||||
id: 'https://codeload.github.com/zkochan/is-negative/tar.gz/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5',
|
||||
id: 'https://codeload.github.com/zkochan/is-negative/tar.gz/6dcce91c268805d456b8a575b67d7febc7ae2933',
|
||||
normalizedBareSpecifier: 'github:zkochan/is-negative#2.0.1',
|
||||
resolution: {
|
||||
tarball: 'https://codeload.github.com/zkochan/is-negative/tar.gz/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5',
|
||||
tarball: 'https://codeload.github.com/zkochan/is-negative/tar.gz/6dcce91c268805d456b8a575b67d7febc7ae2933',
|
||||
},
|
||||
resolvedVia: 'git-repository',
|
||||
})
|
||||
@@ -416,10 +416,10 @@ test('resolveFromGit() normalizes full url with port', async () => {
|
||||
test('resolveFromGit() normalizes full url (alternative form)', async () => {
|
||||
const resolveResult = await resolveFromGit({ bareSpecifier: 'git+ssh://git@github.com/zkochan/is-negative.git#2.0.1' })
|
||||
expect(resolveResult).toStrictEqual({
|
||||
id: 'https://codeload.github.com/zkochan/is-negative/tar.gz/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5',
|
||||
id: 'https://codeload.github.com/zkochan/is-negative/tar.gz/6dcce91c268805d456b8a575b67d7febc7ae2933',
|
||||
normalizedBareSpecifier: 'github:zkochan/is-negative#2.0.1',
|
||||
resolution: {
|
||||
tarball: 'https://codeload.github.com/zkochan/is-negative/tar.gz/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5',
|
||||
tarball: 'https://codeload.github.com/zkochan/is-negative/tar.gz/6dcce91c268805d456b8a575b67d7febc7ae2933',
|
||||
},
|
||||
resolvedVia: 'git-repository',
|
||||
})
|
||||
@@ -428,17 +428,17 @@ test('resolveFromGit() normalizes full url (alternative form)', async () => {
|
||||
test('resolveFromGit() normalizes full url (alternative form 2)', async () => {
|
||||
const resolveResult = await resolveFromGit({ bareSpecifier: 'https://github.com/zkochan/is-negative.git#2.0.1' })
|
||||
expect(resolveResult).toStrictEqual({
|
||||
id: 'https://codeload.github.com/zkochan/is-negative/tar.gz/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5',
|
||||
id: 'https://codeload.github.com/zkochan/is-negative/tar.gz/6dcce91c268805d456b8a575b67d7febc7ae2933',
|
||||
normalizedBareSpecifier: 'github:zkochan/is-negative#2.0.1',
|
||||
resolution: {
|
||||
tarball: 'https://codeload.github.com/zkochan/is-negative/tar.gz/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5',
|
||||
tarball: 'https://codeload.github.com/zkochan/is-negative/tar.gz/6dcce91c268805d456b8a575b67d7febc7ae2933',
|
||||
},
|
||||
resolvedVia: 'git-repository',
|
||||
})
|
||||
})
|
||||
|
||||
// This test relies on implementation detail.
|
||||
// current implementation does not try git ls-remote --refs on bareSpecifier with full commit hash, this fake repo url will pass.
|
||||
// current implementation does not try git ls-remote on bareSpecifier with full commit hash, this fake repo url will pass.
|
||||
test('resolveFromGit() private repo with commit hash', async () => {
|
||||
// parseBareSpecifier will try to access the repository with --exit-code
|
||||
git.mockImplementation(() => {
|
||||
@@ -461,11 +461,6 @@ test('resolveFromGit() private repo with commit hash', async () => {
|
||||
test('resolve a private repository using the HTTPS protocol without auth token', async () => {
|
||||
jest.mocked(git).mockImplementation(async (args: string[]) => {
|
||||
expect(args).toContain('git+ssh://git@github.com/foo/bar.git')
|
||||
if (args.includes('--refs')) {
|
||||
return {
|
||||
stdout: `\n${'a'.repeat(40)}\trefs/heads/master\n`,
|
||||
}
|
||||
}
|
||||
return {
|
||||
stdout: '0'.repeat(40) + '\tHEAD',
|
||||
}
|
||||
@@ -510,13 +505,6 @@ test('resolve a private repository using the HTTPS protocol with a commit hash',
|
||||
test('resolve a private repository using the HTTPS protocol and an auth token', async () => {
|
||||
git.mockImplementation(async (args: string[]) => {
|
||||
if (!args.includes('https://0000000000000000000000000000000000000000:x-oauth-basic@github.com/foo/bar.git')) throw new Error('')
|
||||
if (args.includes('--refs')) {
|
||||
return {
|
||||
stdout: '\
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\trefs/heads/master\
|
||||
',
|
||||
}
|
||||
}
|
||||
return { stdout: '0000000000000000000000000000000000000000\tHEAD' }
|
||||
})
|
||||
mockFetchAsPrivate()
|
||||
@@ -536,14 +524,6 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\trefs/heads/master\
|
||||
test('resolve an internal repository using SSH protocol with range semver', async () => {
|
||||
git.mockImplementation(async (args: string[]) => {
|
||||
if (!args.includes('ssh://git@example.com/org/repo.git')) throw new Error('')
|
||||
if (args.includes('--refs')) {
|
||||
return {
|
||||
stdout: '\
|
||||
ed3de20970d980cf21a07fd8b8732c70d5182303\trefs/tags/v0.0.38\n\
|
||||
cba04669e621b85fbdb33371604de1a2898e68e9\trefs/tags/v0.0.39\
|
||||
',
|
||||
}
|
||||
}
|
||||
return {
|
||||
stdout: '0000000000000000000000000000000000000000\tHEAD\n\
|
||||
ed3de20970d980cf21a07fd8b8732c70d5182303\trefs/tags/v0.0.38\n\
|
||||
@@ -566,14 +546,6 @@ cba04669e621b85fbdb33371604de1a2898e68e9\trefs/tags/v0.0.39',
|
||||
test('resolve an internal repository using SSH protocol with range semver and SCP-like URL', async () => {
|
||||
git.mockImplementation(async (args: string[]) => {
|
||||
if (!args.includes('ssh://git@example.com/org/repo.git')) throw new Error('')
|
||||
if (args.includes('--refs')) {
|
||||
return {
|
||||
stdout: '\
|
||||
ed3de20970d980cf21a07fd8b8732c70d5182303\trefs/tags/v0.0.38\n\
|
||||
cba04669e621b85fbdb33371604de1a2898e68e9\trefs/tags/v0.0.39\
|
||||
',
|
||||
}
|
||||
}
|
||||
return {
|
||||
stdout: '0000000000000000000000000000000000000000\tHEAD\n\
|
||||
ed3de20970d980cf21a07fd8b8732c70d5182303\trefs/tags/v0.0.38\n\
|
||||
|
||||
Reference in New Issue
Block a user