Skip to content

Commit

Permalink
feat(git-fetcher): shallow clone when fetching git resource
Browse files Browse the repository at this point in the history
  • Loading branch information
kenrick95 committed Apr 13, 2022
1 parent 0a70aed commit c9c54fe
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .changeset/curly-spiders-search.md
@@ -0,0 +1,5 @@
---
"@pnpm/git-fetcher": minor
---

feat(git-fetcher): shallow clone when fetching git resource
2 changes: 2 additions & 0 deletions packages/client/src/index.ts
Expand Up @@ -18,6 +18,7 @@ export type ClientOptions = {
timeout?: number
userAgent?: string
userConfig?: Record<string, string>
gitShallowHosts?: string[]
} & ResolverFactoryOptions & AgentOptions

export default function (opts: ClientOptions) {
Expand All @@ -40,6 +41,7 @@ function createFetchers (
getCredentials: GetCredentials,
opts: {
retry?: RetryTimeoutOptions
gitShallowHosts?: string[]
}
) {
return {
Expand Down
1 change: 1 addition & 0 deletions packages/config/src/Config.ts
Expand Up @@ -140,6 +140,7 @@ export interface Config {
enableModulesDir: boolean
modulesCacheMaxAge: number
embedReadme?: boolean
gitShallowHosts?: string[]

registries: Registries
ignoreWorkspaceRootCheck: boolean
Expand Down
9 changes: 9 additions & 0 deletions packages/config/src/index.ts
Expand Up @@ -48,6 +48,7 @@ export const types = Object.assign({
'filter-prod': [String, Array],
'frozen-lockfile': Boolean,
'git-checks': Boolean,
'git-shallow-hosts': Array,
'global-bin-dir': String,
'global-dir': String,
'global-path': String,
Expand Down Expand Up @@ -174,6 +175,14 @@ export default async (
'fetch-retry-maxtimeout': 60000,
'fetch-retry-mintimeout': 10000,
'fetch-timeout': 60000,
'git-shallow-hosts': [
// Follow https://github.com/npm/git/blob/1e1dbd26bd5b87ca055defecc3679777cb480e2a/lib/clone.js#L13-L19
'github.com',
'gist.github.com',
'gitlab.com',
'bitbucket.com',
'bitbucket.org',
],
globalconfig: npmDefaults.globalconfig,
hoist: true,
'hoist-pattern': ['*'],
Expand Down
25 changes: 24 additions & 1 deletion packages/git-fetcher/src/index.ts
Expand Up @@ -3,6 +3,7 @@ import { Cafs, DeferredManifestPromise } from '@pnpm/fetcher-base'
import preparePackage from '@pnpm/prepare-package'
import rimraf from '@zkochan/rimraf'
import execa from 'execa'
import { URL } from 'url'

export default () => {
return {
Expand All @@ -15,10 +16,17 @@ export default () => {
},
opts: {
manifest?: DeferredManifestPromise
gitShallowHosts?: string[]
}
) {
const tempLocation = await cafs.tempDir()
await execGit(['clone', resolution.repo, tempLocation])
if (shouldUseShallow(resolution.repo, opts.gitShallowHosts)) {
await execGit(['init'], { cwd: tempLocation })
await execGit(['remote', 'add', 'origin', resolution.repo], { cwd: tempLocation })
await execGit(['fetch', '--depth', '1', 'origin', resolution.commit], { cwd: tempLocation })
} else {
await execGit(['clone', resolution.repo, tempLocation])
}
await execGit(['checkout', resolution.commit], { cwd: tempLocation })
await preparePackage(tempLocation)
// removing /.git to make directory integrity calculation faster
Expand All @@ -32,6 +40,21 @@ export default () => {
}
}

function shouldUseShallow (repoUrl: string, allowedHosts: string[] = []): boolean {
if (!allowedHosts) {
return false
}
try {
const { host } = new URL(repoUrl)
if (allowedHosts.includes(host)) {
return true
}
} catch (e) {
// URL might be malformed
}
return false
}

function prefixGitArgs (): string[] {
return process.platform === 'win32' ? ['-c', 'core.longpaths=true'] : []
}
Expand Down
47 changes: 47 additions & 0 deletions packages/git-fetcher/test/index.ts
Expand Up @@ -5,6 +5,20 @@ import createFetcher from '@pnpm/git-fetcher'
import { DependencyManifest } from '@pnpm/types'
import pDefer from 'p-defer'
import tempy from 'tempy'
import execa from 'execa'

jest.mock('execa', () => {
const originalModule = jest.requireActual('execa')
return {
__esModule: true,
...originalModule,
default: jest.fn(originalModule.default),
}
})

beforeEach(() => {
(execa as jest.Mock).mockClear()
})

test('fetch', async () => {
const cafsDir = tempy.directory()
Expand Down Expand Up @@ -78,3 +92,36 @@ test('fetch a big repository', async () => {
}, { manifest })
await Promise.all(Object.values(filesIndex).map(({ writeResult }) => writeResult))
})

test('still able to shallow fetch for allowed hosts', async () => {
const cafsDir = tempy.directory()
const fetch = createFetcher().git
const manifest = pDefer<DependencyManifest>()
const resolution = {
commit: 'c9b30e71d704cd30fa71f2edd1ecc7dcc4985493',
repo: 'https://github.com/kevva/is-positive.git',
type: 'git' as const,
}
const { filesIndex } = await fetch(
createCafsStore(cafsDir),
resolution,
{
manifest,
gitShallowHosts: ['github.com'],
}
)
const calls = (execa as jest.Mock).mock.calls
const expectedCalls = [
['git', ['init']],
['git', ['remote', 'add', 'origin', resolution.repo]],
['git', ['fetch', '--depth', '1', 'origin', resolution.commit]],
]
for (let i = 0; i < expectedCalls.length; i++) {
// Discard final argument as it passes temporary directory
expect(calls[i].slice(0, -1)).toEqual(expectedCalls[i])
}
expect(filesIndex['package.json']).toBeTruthy()
expect(filesIndex['package.json'].writeResult).toBeTruthy()
const name = (await manifest.promise).name
expect(name).toEqual('is-positive')
})
Expand Up @@ -22,6 +22,7 @@ export type CreateNewStoreControllerOptions = CreateResolverOptions & Pick<Confi
| 'force'
| 'nodeVersion'
| 'fetchTimeout'
| 'gitShallowHosts'
| 'httpProxy'
| 'httpsProxy'
| 'key'
Expand Down Expand Up @@ -71,6 +72,7 @@ export default async (
? (opts.networkConcurrency * 3)
: undefined
),
gitShallowHosts: opts.gitShallowHosts,
})
await fs.mkdir(opts.storeDir, { recursive: true })
return {
Expand Down

0 comments on commit c9c54fe

Please sign in to comment.