diff --git a/docs/ko/reference/Remote/Methods/fetch.md b/docs/ko/reference/Remote/Methods/fetch.md index fdadfd4..0616635 100644 --- a/docs/ko/reference/Remote/Methods/fetch.md +++ b/docs/ko/reference/Remote/Methods/fetch.md @@ -89,10 +89,7 @@ class Remote {
  • depthnumber
    -

    - 가져올 커밋의 깊이를 설정해요. - 0 이하의 값이면 모든 커밋을 가져와요(제한 없음). -

    +

    fetch 깊이로, 각 원격 브랜치의 최신 커밋부터 가져올 커밋 수를 설정해요. 0보다 크면 얕은 리포지토리가 만들어져요(Repository.isShallow() 참고). 0 이하의 값이면 모든 커밋을 가져와요(사실상 깊이 제한을 선언하지 않은 것과 같아요). 이미 얕은 리포지토리를 전체 리포지토리로 바꾸지는 않으므로, 그럴 때는 unshallow를 사용해요. 로컬 전송은 얕은 fetch를 지원하지 않아서 로컬 경로나 file:// URL에서는 depth가 실패해요.

  • downloadTagsAutotagOption @@ -148,6 +145,11 @@ class Remote { - Off : 정리를 강제 비활성화

  • +
  • + unshallowboolean +
    +

    git fetch --unshallow처럼 얕은 리포지토리에 없는 히스토리를 가져와요. 원격 리포지토리도 얕은 리포지토리라면 원격에 있는 만큼만 가져와요. true이면 depth는 무시돼요.

    +
  • diff --git a/docs/ko/reference/Repository/cloneRepository.md b/docs/ko/reference/Repository/cloneRepository.md index a289100..9840764 100644 --- a/docs/ko/reference/Repository/cloneRepository.md +++ b/docs/ko/reference/Repository/cloneRepository.md @@ -120,7 +120,7 @@ function cloneRepository(
  • depthnumber
    -

    클론할 커밋 깊이를 설정해요. 0 이하의 값이면 전체 이력을 클론해요.

    +

    fetch 깊이로, 각 원격 브랜치의 최신 커밋부터 가져올 커밋 수를 설정해요. 0보다 크면 얕은 리포지토리가 만들어져요(Repository.isShallow() 참고). 0 이하의 값이면 모든 커밋을 가져와요(사실상 깊이 제한을 선언하지 않은 것과 같아요). 이미 얕은 리포지토리를 전체 리포지토리로 바꾸지는 않으므로, 그럴 때는 unshallow를 사용해요. 로컬 전송은 얕은 fetch를 지원하지 않아서 로컬 경로나 file:// URL에서는 depth가 실패해요.

  • downloadTagsAutotagOption @@ -187,6 +187,11 @@ function cloneRepository( - Off : 삭제하지 않음

  • +
  • + unshallowboolean +
    +

    git fetch --unshallow처럼 얕은 리포지토리에 없는 히스토리를 가져와요. 원격 리포지토리도 얕은 리포지토리라면 원격에 있는 만큼만 가져와요. true이면 depth는 무시돼요.

    +
  • @@ -256,3 +261,18 @@ const repo = await cloneRepository('git@github.com:toss/es-git', '.', { }, }); ``` + +최신 커밋만 가져오는 얕은 클론을 해요. + +```ts +import { cloneRepository } from 'es-git'; + +const repo = await cloneRepository('https://github.com/toss/es-git', '/path/to/clone', { + fetch: { depth: 1 }, +}); +console.log(repo.isShallow()); // true + +// 나머지 히스토리는 나중에 가져와요. +await repo.getRemote('origin').fetch([], { fetch: { unshallow: true } }); +console.log(repo.isShallow()); // false +``` diff --git a/docs/ko/reference/Submodule/Methods/clone.md b/docs/ko/reference/Submodule/Methods/clone.md index 31e4d9a..76cb0db 100644 --- a/docs/ko/reference/Submodule/Methods/clone.md +++ b/docs/ko/reference/Submodule/Methods/clone.md @@ -178,7 +178,7 @@ class Submodule {
  • depthnumber
    -

    fetch 깊이 설정. 0 이하 값은 모든 항목을 가져오라는 의미로 해석돼요(사실상 깊이 제한을 선언하지 않은 것과 같아요).

    +

    fetch 깊이로, 각 원격 브랜치의 최신 커밋부터 가져올 커밋 수를 설정해요. 0보다 크면 얕은 리포지토리가 만들어져요(Repository.isShallow() 참고). 0 이하의 값이면 모든 커밋을 가져와요(사실상 깊이 제한을 선언하지 않은 것과 같아요). 이미 얕은 리포지토리를 전체 리포지토리로 바꾸지는 않으므로, 그럴 때는 unshallow를 사용해요. 로컬 전송은 얕은 fetch를 지원하지 않아서 로컬 경로나 file:// URL에서는 depth가 실패해요.

  • downloadTagsAutotagOption @@ -215,6 +215,11 @@ class Submodule {

    fetch 이후 prune 수행 여부 설정

    - Unspecified : 구성의 설정을 사용
    - On : prune을 강제로 켬
    - Off : prune을 강제로 끔

  • +
  • + unshallowboolean +
    +

    git fetch --unshallow처럼 얕은 리포지토리에 없는 히스토리를 가져와요. 원격 리포지토리도 얕은 리포지토리라면 원격에 있는 만큼만 가져와요. true이면 depth는 무시돼요.

    +
  • diff --git a/docs/ko/reference/Submodule/Methods/update.md b/docs/ko/reference/Submodule/Methods/update.md index 9a61e1b..f44ca6f 100644 --- a/docs/ko/reference/Submodule/Methods/update.md +++ b/docs/ko/reference/Submodule/Methods/update.md @@ -184,7 +184,7 @@ class Submodule {
  • depthnumber
    -

    fetch 깊이 설정. 0 이하의 값은 모든 것을 가져오라는 의미로 해석돼요(사실상 깊이 제한을 선언하지 않는 것과 동일해요).

    +

    fetch 깊이로, 각 원격 브랜치의 최신 커밋부터 가져올 커밋 수를 설정해요. 0보다 크면 얕은 리포지토리가 만들어져요(Repository.isShallow() 참고). 0 이하의 값이면 모든 커밋을 가져와요(사실상 깊이 제한을 선언하지 않은 것과 같아요). 이미 얕은 리포지토리를 전체 리포지토리로 바꾸지는 않으므로, 그럴 때는 unshallow를 사용해요. 로컬 전송은 얕은 fetch를 지원하지 않아서 로컬 경로나 file:// URL에서는 depth가 실패해요.

  • downloadTagsAutotagOption @@ -221,6 +221,11 @@ class Submodule {

    fetch 후 prune을 수행할지 여부 설정

    - Unspecified : 구성의 설정을 사용.
    - On : prune을 강제로 켬.
    - Off : prune을 강제로 끔

  • +
  • + unshallowboolean +
    +

    git fetch --unshallow처럼 얕은 리포지토리에 없는 히스토리를 가져와요. 원격 리포지토리도 얕은 리포지토리라면 원격에 있는 만큼만 가져와요. true이면 depth는 무시돼요.

    +
  • diff --git a/docs/ko/usage/repository.md b/docs/ko/usage/repository.md index da93773..1f5a36f 100644 --- a/docs/ko/usage/repository.md +++ b/docs/ko/usage/repository.md @@ -83,3 +83,33 @@ const repo = await cloneRepository('https://github.com//', '.', { }, }); ``` + +### 얕은 클론하기 + +히스토리의 최근 일부만 클론하려면 `fetch` 옵션에 `depth`를 설정하세요. `depth`가 `1`이면 각 브랜치의 최신 커밋만 +가져와요. 리포지토리가 얕은 리포지토리인지는 [`isShallow()`](../reference/Repository/Methods/isShallow.md)로 확인할 수 있어요. + +```ts +import { cloneRepository } from 'es-git'; + +const repo = await cloneRepository('https://github.com/toss/es-git', '/path/to/clone', { + fetch: { depth: 1 }, +}); +console.log(repo.isShallow()); // true +``` + +::: warning +얕은 클론은 `https://`, `git://`, SSH 같은 네트워크 프로토콜에서만 동작해요. libgit2의 로컬 전송은 얕은 fetch를 +지원하지 않기 때문에, 로컬 경로나 `file://` URL에서 `depth`를 설정해 클론하면 실패해요. +::: + +나머지 히스토리를 나중에 가져오려면 `unshallow: true`로 리모트에서 fetch하세요. `git fetch --unshallow`와 같아요. + +```ts +const remote = repo.getRemote('origin'); +await remote.fetch([], { fetch: { unshallow: true } }); +console.log(repo.isShallow()); // false +``` + +더 큰 `depth`로 fetch하면 히스토리를 그만큼 더 가져와요. `depth: 0`으로 fetch하거나 `depth`를 지정하지 않으면 얕은 +리포지토리가 전체 리포지토리로 바뀌지 않아요. diff --git a/docs/reference/Remote/Methods/fetch.md b/docs/reference/Remote/Methods/fetch.md index da7589c..19af7b2 100644 --- a/docs/reference/Remote/Methods/fetch.md +++ b/docs/reference/Remote/Methods/fetch.md @@ -88,7 +88,7 @@ class Remote {
  • depthnumber
    -

    Set fetch depth, a value less or equal to 0 is interpreted as pull everything (effectively the same as not declaring a limit depth).

    +

    Set fetch depth, the number of commits to fetch from the tip of each remote branch. A depth greater than 0 creates a shallow repository (see Repository.isShallow()). A value less or equal to 0 is interpreted as pull everything (effectively the same as not declaring a limit depth). This does not unshallow an existing shallow repository; use unshallow for that. Shallow fetches are not supported by the local transport, so depth fails for local paths and file:// URLs.

  • downloadTagsAutotagOption @@ -125,6 +125,11 @@ class Remote {

    Set whether to perform a prune after the fetch.

    - Unspecified : Use the setting from the configuration.
    - On : Force pruning on.
    - Off : Force pruning off

  • +
  • + unshallowboolean +
    +

    Fetch the history missing from a shallow repository, like git fetch --unshallow. If the remote repository is itself shallow, fetch as much history as it has. When true, depth is ignored.

    +
  • diff --git a/docs/reference/Repository/cloneRepository.md b/docs/reference/Repository/cloneRepository.md index 134a57e..aedca74 100644 --- a/docs/reference/Repository/cloneRepository.md +++ b/docs/reference/Repository/cloneRepository.md @@ -103,7 +103,7 @@ function cloneRepository(
  • depthnumber
    -

    Set fetch depth, a value less or equal to 0 is interpreted as pull everything (effectively the same as not declaring a limit depth).

    +

    Set fetch depth, the number of commits to fetch from the tip of each remote branch. A depth greater than 0 creates a shallow repository (see Repository.isShallow()). A value less or equal to 0 is interpreted as pull everything (effectively the same as not declaring a limit depth). This does not unshallow an existing shallow repository; use unshallow for that. Shallow fetches are not supported by the local transport, so depth fails for local paths and file:// URLs.

  • downloadTagsAutotagOption @@ -140,6 +140,11 @@ function cloneRepository(

    Set whether to perform a prune after the fetch.

    - Unspecified : Use the setting from the configuration.
    - On : Force pruning on.
    - Off : Force pruning off

  • +
  • + unshallowboolean +
    +

    Fetch the history missing from a shallow repository, like git fetch --unshallow. If the remote repository is itself shallow, fetch as much history as it has. When true, depth is ignored.

    +
  • @@ -203,4 +208,19 @@ const repo = await cloneRepository('git@github.com:toss/es-git', '.', { }, }, }); +``` + +Shallow clone repository with only the latest commit. + +```ts +import { cloneRepository } from 'es-git'; + +const repo = await cloneRepository('https://github.com/toss/es-git', '/path/to/clone', { + fetch: { depth: 1 }, +}); +console.log(repo.isShallow()); // true + +// Fetch the rest of the history later. +await repo.getRemote('origin').fetch([], { fetch: { unshallow: true } }); +console.log(repo.isShallow()); // false ``` \ No newline at end of file diff --git a/docs/reference/Submodule/Methods/clone.md b/docs/reference/Submodule/Methods/clone.md index ac4bd02..2be192b 100644 --- a/docs/reference/Submodule/Methods/clone.md +++ b/docs/reference/Submodule/Methods/clone.md @@ -178,7 +178,7 @@ class Submodule {
  • depthnumber
    -

    Set fetch depth, a value less or equal to 0 is interpreted as pull everything (effectively the same as not declaring a limit depth).

    +

    Set fetch depth, the number of commits to fetch from the tip of each remote branch. A depth greater than 0 creates a shallow repository (see Repository.isShallow()). A value less or equal to 0 is interpreted as pull everything (effectively the same as not declaring a limit depth). This does not unshallow an existing shallow repository; use unshallow for that. Shallow fetches are not supported by the local transport, so depth fails for local paths and file:// URLs.

  • downloadTagsAutotagOption @@ -215,12 +215,17 @@ class Submodule {

    Set whether to perform a prune after the fetch.

    - Unspecified : Use the setting from the configuration.
    - On : Force pruning on.
    - Off : Force pruning off

  • +
  • + unshallowboolean +
    +

    Fetch the history missing from a shallow repository, like git fetch --unshallow. If the remote repository is itself shallow, fetch as much history as it has. When true, depth is ignored.

    +
  • - signalnull | AbortSignal + signalAbortSignal | null

    Optional AbortSignal to cancel the operation.

  • diff --git a/docs/reference/Submodule/Methods/update.md b/docs/reference/Submodule/Methods/update.md index c62db13..b7737eb 100644 --- a/docs/reference/Submodule/Methods/update.md +++ b/docs/reference/Submodule/Methods/update.md @@ -187,7 +187,7 @@ class Submodule {
  • depthnumber
    -

    Set fetch depth, a value less or equal to 0 is interpreted as pull everything (effectively the same as not declaring a limit depth).

    +

    Set fetch depth, the number of commits to fetch from the tip of each remote branch. A depth greater than 0 creates a shallow repository (see Repository.isShallow()). A value less or equal to 0 is interpreted as pull everything (effectively the same as not declaring a limit depth). This does not unshallow an existing shallow repository; use unshallow for that. Shallow fetches are not supported by the local transport, so depth fails for local paths and file:// URLs.

  • downloadTagsAutotagOption @@ -224,12 +224,17 @@ class Submodule {

    Set whether to perform a prune after the fetch.

    - Unspecified : Use the setting from the configuration.
    - On : Force pruning on.
    - Off : Force pruning off

  • +
  • + unshallowboolean +
    +

    Fetch the history missing from a shallow repository, like git fetch --unshallow. If the remote repository is itself shallow, fetch as much history as it has. When true, depth is ignored.

    +
  • - signalnull | AbortSignal + signalAbortSignal | null

    Optional AbortSignal to cancel the operation.

  • diff --git a/docs/usage/repository.md b/docs/usage/repository.md index 31d15b8..1b7f397 100644 --- a/docs/usage/repository.md +++ b/docs/usage/repository.md @@ -82,3 +82,35 @@ const repo = await cloneRepository('https://github.com//', '.', { }, }); ``` + +### Shallow Clone + +To clone only the latest part of the history, set `depth` in the `fetch` option. A depth of `1` fetches only the latest +commit of each branch. Use [`isShallow()`](../reference/Repository/Methods/isShallow.md) to check whether a repository is +shallow. + +```ts +import { cloneRepository } from 'es-git'; + +const repo = await cloneRepository('https://github.com/toss/es-git', '/path/to/clone', { + fetch: { depth: 1 }, +}); +console.log(repo.isShallow()); // true +``` + +::: warning +Shallow clones need a network protocol such as `https://`, `git://` or SSH. Cloning from a local path or a `file://` URL +with `depth` fails, because libgit2's local transport does not support shallow fetches. +::: + +To fetch the rest of the history later, fetch from the remote with `unshallow: true`. This works like +`git fetch --unshallow`. + +```ts +const remote = repo.getRemote('origin'); +await remote.fetch([], { fetch: { unshallow: true } }); +console.log(repo.isShallow()); // false +``` + +Fetching with a larger `depth` deepens the history instead. Fetching with `depth: 0` or without `depth` does not unshallow +a shallow repository. diff --git a/index.d.ts b/index.d.ts index 8f692c0..6fe3af1 100644 --- a/index.d.ts +++ b/index.d.ts @@ -7132,6 +7132,21 @@ export interface CherrypickOptions { * }, * }); * ``` + * + * Shallow clone repository with only the latest commit. + * + * ```ts + * import { cloneRepository } from 'es-git'; + * + * const repo = await cloneRepository('https://github.com/toss/es-git', '/path/to/clone', { + * fetch: { depth: 1 }, + * }); + * console.log(repo.isShallow()); // true + * + * // Fetch the rest of the history later. + * await repo.getRemote('origin').fetch([], { fetch: { unshallow: true } }); + * console.log(repo.isShallow()); // false + * ``` */ export declare function cloneRepository(url: string, path: string, options?: RepositoryCloneOptions | undefined | null, signal?: AbortSignal | undefined | null): Promise @@ -7780,10 +7795,26 @@ export interface FetchOptions { /** Set whether to perform a prune after the fetch. */ prune?: FetchPrune /** - * Set fetch depth, a value less or equal to 0 is interpreted as pull - * everything (effectively the same as not declaring a limit depth). + * Set fetch depth, the number of commits to fetch from the tip of each + * remote branch. A depth greater than 0 creates a shallow repository + * (see `Repository.isShallow()`). + * + * A value less or equal to 0 is interpreted as pull everything + * (effectively the same as not declaring a limit depth). This does not + * unshallow an existing shallow repository; use `unshallow` for that. + * + * Shallow fetches are not supported by the local transport, so `depth` + * fails for local paths and `file://` URLs. */ depth?: number + /** + * Fetch the history missing from a shallow repository, like + * `git fetch --unshallow`. If the remote repository is itself shallow, + * fetch as much history as it has. + * + * When `true`, `depth` is ignored. + */ + unshallow?: boolean /** * Set how to behave regarding tags on the remote, such as auto-downloading * tags for objects we're downloading or downloading all of them. diff --git a/src/remote.rs b/src/remote.rs index 115ef31..92f5e50 100644 --- a/src/remote.rs +++ b/src/remote.rs @@ -322,9 +322,23 @@ pub struct FetchOptions { pub proxy: Option, /// Set whether to perform a prune after the fetch. pub prune: Option, - /// Set fetch depth, a value less or equal to 0 is interpreted as pull - /// everything (effectively the same as not declaring a limit depth). + /// Set fetch depth, the number of commits to fetch from the tip of each + /// remote branch. A depth greater than 0 creates a shallow repository + /// (see `Repository.isShallow()`). + /// + /// A value less or equal to 0 is interpreted as pull everything + /// (effectively the same as not declaring a limit depth). This does not + /// unshallow an existing shallow repository; use `unshallow` for that. + /// + /// Shallow fetches are not supported by the local transport, so `depth` + /// fails for local paths and `file://` URLs. pub depth: Option, + /// Fetch the history missing from a shallow repository, like + /// `git fetch --unshallow`. If the remote repository is itself shallow, + /// fetch as much history as it has. + /// + /// When `true`, `depth` is ignored. + pub unshallow: Option, /// Set how to behave regarding tags on the remote, such as auto-downloading /// tags for objects we're downloading or downloading all of them. /// @@ -427,7 +441,10 @@ impl<'a> FetchOptions { if let Some(prune) = self.prune { fetch.prune(prune.into()); } - if let Some(depth) = self.depth { + if self.unshallow == Some(true) { + // libgit2's `GIT_FETCH_DEPTH_UNSHALLOW` + fetch.depth(i32::MAX); + } else if let Some(depth) = self.depth { fetch.depth(depth); } if let Some(download_tags) = self.download_tags { diff --git a/src/repository.rs b/src/repository.rs index cb25b10..d744e19 100644 --- a/src/repository.rs +++ b/src/repository.rs @@ -853,6 +853,21 @@ impl Task for CloneRepositoryTask { /// }, /// }); /// ``` +/// +/// Shallow clone repository with only the latest commit. +/// +/// ```ts +/// import { cloneRepository } from 'es-git'; +/// +/// const repo = await cloneRepository('https://github.com/toss/es-git', '/path/to/clone', { +/// fetch: { depth: 1 }, +/// }); +/// console.log(repo.isShallow()); // true +/// +/// // Fetch the rest of the history later. +/// await repo.getRemote('origin').fetch([], { fetch: { unshallow: true } }); +/// console.log(repo.isShallow()); // false +/// ``` pub fn clone_repository( url: String, path: String, diff --git a/tests/git-server.ts b/tests/git-server.ts new file mode 100644 index 0000000..25b738f --- /dev/null +++ b/tests/git-server.ts @@ -0,0 +1,86 @@ +import { type ChildProcess, spawn, spawnSync } from 'node:child_process'; +import http from 'node:http'; +import type { AddressInfo } from 'node:net'; + +export const hasGit = spawnSync('git', ['--version']).status === 0; + +/** + * Serve git repositories under `root` over the smart HTTP protocol, using `git http-backend`. + * + * libgit2's local transport (local paths and `file://` URLs) does not support shallow fetches, + * so tests for shallow clones need a real protocol. + */ +export async function serveGitRepositories(root: string) { + const children = new Set(); + const server = http.createServer((req, res) => { + const url = new URL(req.url ?? '/', 'http://localhost'); + const child = spawn('git', ['http-backend'], { + env: { + ...process.env, + GIT_PROJECT_ROOT: root, + GIT_HTTP_EXPORT_ALL: '1', + PATH_INFO: url.pathname, + QUERY_STRING: url.search.slice(1), + REQUEST_METHOD: req.method, + CONTENT_TYPE: req.headers['content-type'] ?? '', + }, + }); + children.add(child); + child.on('close', () => children.delete(child)); + child.on('error', error => { + res.destroy(error); + }); + // Ignore EPIPE when the backend exits before reading the whole request; 'error' above handles failures. + child.stdin.on('error', () => {}); + req.pipe(child.stdin); + + // The CGI response is headers, a blank line, then the (binary) body. + let head: Buffer | null = Buffer.alloc(0); + child.stdout.on('data', (chunk: Buffer) => { + if (head == null) { + res.write(chunk); + return; + } + head = Buffer.concat([head, chunk]); + const end = head.indexOf('\r\n\r\n'); + if (end === -1) { + return; + } + let status = 200; + for (const line of head.subarray(0, end).toString('utf8').split('\r\n')) { + const separator = line.indexOf(':'); + const name = line.slice(0, separator).trim(); + const value = line.slice(separator + 1).trim(); + if (name.toLowerCase() === 'status') { + status = Number.parseInt(value, 10); + } else { + res.setHeader(name, value); + } + } + res.writeHead(status); + res.write(head.subarray(end + 4)); + head = null; + }); + child.stdout.on('end', () => { + if (head != null) { + res.destroy(new Error('git http-backend exited without a response')); + return; + } + res.end(); + }); + }); + + await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)); + const { port } = server.address() as AddressInfo; + + return { + url: `http://127.0.0.1:${port}`, + close: async () => { + for (const child of children) { + child.kill(); + } + server.closeAllConnections(); + await new Promise(resolve => server.close(resolve)); + }, + }; +} diff --git a/tests/remote.spec.ts b/tests/remote.spec.ts index 6cfc31c..d8b1b8c 100644 --- a/tests/remote.spec.ts +++ b/tests/remote.spec.ts @@ -2,6 +2,7 @@ import { describe, expect, it, vi } from 'vitest'; import { cloneRepository, initRepository, openRepository } from '../index'; import { isTarget } from './env'; import { useFixture } from './fixtures'; +import { hasGit, serveGitRepositories } from './git-server'; import { makeTmpDir } from './tmp'; describe('remote', () => { @@ -43,6 +44,26 @@ describe('remote', () => { await remote.fetch(['main']); }); + it('unshallow shallow repository', { skip: !hasGit }, async () => { + const root = await useFixture('commits'); + const server = await serveGitRepositories(root); + try { + const p = await makeTmpDir('clone'); + const repo = await cloneRepository(`${server.url}/.git`, p, { fetch: { depth: 1 } }); + expect(repo.isShallow()).toBe(true); + + await repo.getRemote('origin').fetch([], { fetch: { unshallow: true } }); + expect(repo.isShallow()).toBe(false); + const revwalk = repo.revwalk(); + revwalk.pushHead(); + expect(revwalk.next()).toEqual('a01e9888e46729ef4aa68953ba19b02a7a64eb82'); + expect(revwalk.next()).toEqual('b33e0101b828225f77eeff4dfa31259dcf379002'); + expect(revwalk.next()).toBeNull(); + } finally { + await server.close(); + } + }); + it('get remote default branch', { skip: isTarget('linux', undefined, 'gnu') }, async () => { const p = await makeTmpDir('clone'); const repo = await cloneRepository('https://github.com/seokju-na/dummy-repo', p); diff --git a/tests/repository.spec.ts b/tests/repository.spec.ts index 1ddd508..6574b68 100644 --- a/tests/repository.spec.ts +++ b/tests/repository.spec.ts @@ -4,6 +4,7 @@ import { describe, expect, it, vi } from 'vitest'; import { cloneRepository, initRepository, openRepository } from '../index'; import { isTarget } from './env'; import { useFixture } from './fixtures'; +import { hasGit, serveGitRepositories } from './git-server'; import { makeTmpDir } from './tmp'; describe('Repository', () => { @@ -51,6 +52,30 @@ describe('Repository', () => { await expect(fs.readFile(path.join(p, 'first'), 'utf8')).resolves.toEqual(expect.stringContaining('first')); }); + it('shallow clone', { skip: !hasGit }, async () => { + const root = await useFixture('commits'); + const server = await serveGitRepositories(root); + try { + const p = await makeTmpDir('clone'); + const repo = await cloneRepository(`${server.url}/.git`, p, { fetch: { depth: 1 } }); + expect(repo.isShallow()).toBe(true); + const revwalk = repo.revwalk(); + revwalk.pushHead(); + expect(revwalk.next()).toEqual('a01e9888e46729ef4aa68953ba19b02a7a64eb82'); + expect(revwalk.next()).toBeNull(); + } finally { + await server.close(); + } + }); + + it('shallow clone is not supported from local path', async () => { + const localPath = await useFixture('commits'); + const p = await makeTmpDir('clone'); + await expect(cloneRepository(localPath, p, { fetch: { depth: 1 } })).rejects.toThrowError( + /shallow fetch is not supported by the local transport/ + ); + }); + it('clone from remote', { skip: isTarget('linux', undefined, 'gnu') }, async () => { const p = await makeTmpDir('clone'); const repo = await cloneRepository('https://github.com/seokju-na/dummy-repo', p);