{"id":15003461,"url":"https://github.com/discoveryjs/scan-git","last_synced_at":"2026-03-02T04:31:21.750Z","repository":{"id":40893776,"uuid":"500847387","full_name":"discoveryjs/scan-git","owner":"discoveryjs","description":"A tool set for fast and efficient git scanning to capture data with focus on large repos","archived":false,"fork":false,"pushed_at":"2024-12-04T16:05:37.000Z","size":1298,"stargazers_count":6,"open_issues_count":1,"forks_count":3,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-10-30T11:46:28.000Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/discoveryjs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-06-07T13:16:10.000Z","updated_at":"2024-12-05T13:24:55.000Z","dependencies_parsed_at":"2024-09-04T00:00:57.136Z","dependency_job_id":"4f41297a-1e20-405b-9559-af67c97c4452","html_url":"https://github.com/discoveryjs/scan-git","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/discoveryjs/scan-git","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/discoveryjs%2Fscan-git","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/discoveryjs%2Fscan-git/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/discoveryjs%2Fscan-git/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/discoveryjs%2Fscan-git/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/discoveryjs","download_url":"https://codeload.github.com/discoveryjs/scan-git/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/discoveryjs%2Fscan-git/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29992302,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-02T01:47:34.672Z","status":"online","status_checked_at":"2026-03-02T02:00:07.342Z","response_time":60,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-09-24T18:58:34.384Z","updated_at":"2026-03-02T04:31:21.686Z","avatar_url":"https://github.com/discoveryjs.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @discoveryjs/scan-git\n\n[![NPM version](https://img.shields.io/npm/v/@discoveryjs/scan-git.svg)](https://www.npmjs.com/package/@discoveryjs/scan-git)\n[![Build](https://github.com/discoveryjs/scan-git/actions/workflows/build.yml/badge.svg)](https://github.com/discoveryjs/scan-git/actions/workflows/build.yml)\n[![Coverage Status](https://coveralls.io/repos/github/discoveryjs/scan-git/badge.svg?branch=main)](https://coveralls.io/github/discoveryjs/scan-git?branch=main)\n\n`@discoveryjs/scan-git` is a powerful Node.js library designed for reading and analyzing Git repositories directly from the filesystem. It provides a rich set of APIs that allow you to access Git objects, references, commits, trees, and more without the need for Git command-line tools or external dependencies.\n\nWhether you're building tools for repository analysis, visualization, or automation, `@discoveryjs/scan-git` provides a robust and efficient API to meet your Git interaction needs.\n\n**Key Features:**\n\n- **Direct Repository Access:** Interact with Git repositories by reading data directly from the `.git` directory.\n- **Comprehensive Git Object Support:** Work with both loose and packed objects, including support for large pack files over 2GB.\n- **Advanced Git Features:** Handle complex repository structures with support for cruft packs and on-disk reverse indexes.\n- **Efficient Data Retrieval:** Efficiently fetch commit histories, branches, tags, and files, even for large repositories.\n- **Flexible APIs:** Compute diffs between commits, read specific Git objects, and parse commits, trees, and annotated tags.\n\n## Usage\n\n```\nnpm install @discoveryjs/scan-git\n```\n\n## API\n\n- Git reader\n  - [`createGitReader(gitdir, options?)`](#creategitreadergitdir-options)\n  - [`reader.dispose()`](#readerdispose)\n  - Reference methods\n    - [`reader.defaultBranch()`](#readerdefaultbranch)\n    - [`reader.currentBranch()`](#readercurrentbranch)\n    - [`reader.isRefExists(ref)`](#readerisrefexistsref)\n    - [`reader.expandRef(ref)`](#readerexpandrefref)\n    - [`reader.resolveRef(ref)`](#readerresolverefref)\n    - [`reader.describeRef(ref)`](#readerdescriberefref)\n    - [`reader.isOid(value)`](#readerisoidvalue)\n    - [`reader.listRemotes()`](#readerlistremotes)\n    - [`reader.listRemoteBranches(remote, withOid?)`](#readerlistremotebranchesremote-withoid)\n    - [`reader.listBranches(withOid?)`](#readerlistbrancheswithoid)\n    - [`reader.listTags(withOid?)`](#readerlisttagswithoid)\n  - Trees (file lists) methods\n    - [`reader.treeOidFromRef(ref)`](#readertreeoidfromrefref)\n    - [`reader.listFiles(ref, filesWithHash)`](#readerlistfilesref-fileswithhash)\n    - [`reader.getPathEntry(path, ref)`](#readergetpathentrypath-ref)\n    - [`reader.getPathsEntries(paths, ref)`](#readergetpathsentriespaths-ref)\n    - [`reader.deltaFiles(nextRef, prevRef)`](#readerdeltafilesnextref-prevref)\n  - Commit methods\n    - [`reader.commitOidFromRef(ref)`](#readercommitoidfromrefref)\n    - [`reader.readCommit(ref)`](#readerreadcommitref)\n    - [`reader.log(options)`](#readerlogoptions)\n  - Misc methods\n    - [`reader.readObjectHeaderByHash(hash)`](#readerreadobjectheaderbyhashhash)\n    - [`reader.readObjectByHash(hash, cache?)`](#readerreadobjectbyhashhash-cache)\n    - [`reader.readObjectHeaderByOid(oid)`](#readerreadobjectheaderbyoidoid)\n    - [`reader.readObjectByOid(oid, cache?)`](#readerreadobjectbyoidoid-cache)\n    - [`reader.stat()`](#readerstat)\n- Utils\n  - [`isGitDir(dir)`](#isgitdirdir)\n  - [`resolveGitDir(dir)`](#resolvegitdirdir)\n  - [`parseContributor(input)`](#parsecontributorinput)\n  - [`parseTimezone(offset)`](#parsetimezoneoffset)\n  - [`parseAnnotatedTag(object)`](#parseannotatedtagobject)\n  - [`parseCommit(object)`](#parsecommitobject)\n  - [`parseTree(buffer)`](#parsetreebuffer)\n\n---\n\n### Git reader\n\n```js\nimport { createGitReader } from '@discoveryjs/scan-git';\n\nconst reader = await createGitReader('path/to/.git');\nconst commits = await reader.log({ ref: 'my-branch', depth: 10 });\n\nconsole.log(commits);\n\nawait reader.dispose();\n```\n\n#### `createGitReader(gitdir, options?)`\n\nCreates an instance of the Git reader, which provides access to most of the library's functionality:\n\n- **`gitdir`**: `string`  \n  The path to the Git repository. This can either be a directory containing a `.git` folder or a direct path to a `.git` folder (even if it has a non-standard name).\n- **`options`** _(optional)_:\n  - **`maxConcurrency`**: `number` _(default: 50)_  \n    Limits the number of concurrent file system operations\n  - **`cruftPacks`**: `'include'` | `'exclude'` | `'only'` | `boolean` _(default: `'include'`)_  \n    Defines how [cruft packs](https://git-scm.com/docs/cruft-packs) are processed:\n    - `'include'` or `true` – Process all packs\n    - `'exclude'` or `false` – Exclude cruft packs from processing\n    - `'only'` – Process only cruft packs\n\n```js\nimport { createGitReader } from '@discoveryjs/scan-git';\n\nconst reader = await createGitReader('path/to/.git');\n```\n\n#### `reader.dispose()`\n\nCleans up resources used by the reader instance, such as file handles or caches. This method should be called when the reader instance is no longer needed to ensure proper resource management and avoid memory leaks.\n\n```js\nconst reader = await createGitReader('path/to/.git');\n\n// do something with reader\n\n// Dispose of the repository instance when done\nawait reader.dispose();\n```\n\n\u003e Note: After calling `dispose()`, attempting to use the reader instance (e.g., calling methods like `log()` or `readCommit()`) will likely result in errors or undefined behavior.\n\n\u003e Note: Always ensure `dispose()` is called in applications or scripts that manage multiple repositories or long-running processes to prevent resource exhaustion.\n\n---\n\n#### Reference methods\n\nCommon parameters:\n\n- `ref`: string – a reference to an object in repository\n- `withOid`: boolean – a flag to include resolved oid for a reference\n\n##### `reader.defaultBranch()`\n\nReturns the default branch name of a repository:\n\n```js\nconst defaultBranch = await reader.defaultBranch();\n// 'main'\n```\n\nThe algorithm to identify a default branch name:\n\n- if there is only one branch, that must be the default\n- otherwise looking for specific branch names, in this order:\n  - `upstream/HEAD`\n  - `origin/HEAD`\n  - `main`\n  - `master`\n\n##### `reader.currentBranch()`\n\nReturns the current branch name along with its commit oid.\nIf the repository is in a detached HEAD state, `name` will be `null`.\n\n```js\nconst currentBranch = await reader.currentBranch();\n// { name: 'main', oid: '8bb6e23769902199e39ab70f2441841712cbdd62' }\n\nconst detachedHead = await reader.currentBranch();\n// { name: null, oid: '8bb6e23769902199e39ab70f2441841712cbdd62' }\n```\n\n##### `reader.isRefExists(ref)`\n\nChecks if a `ref` exists.\n\n```js\nconst isValidRef = reader.isRefExists('main');\n// true\n```\n\n##### `reader.expandRef(ref)`\n\nExpands a `ref` into a full form, e.g. `'main'` -\u003e `'refs/heads/main'`.\nReturns `null` if `ref` doesn't exist. For the symbolic ref names (`'HEAD'`, `'FETCH_HEAD'`, `'CHERRY_PICK_HEAD'`, `'MERGE_HEAD'` and `'ORIG_HEAD'`) returns a name without changes.\n\n```js\nconst fullPath = reader.expandRef('heads/main');\n// 'refs/heads/main'\n```\n\n##### `reader.resolveRef(ref)`\n\nResolves `ref` into oid if it exists, otherwise throws an exception.\nIn case if `ref` is oid, returns this oid back. If ref is not a full path, expands it first.\n\n```js\nconst oid = await reader.resolveRef('main');\n// '8bb6e23769902199e39ab70f2441841712cbdd62'\n```\n\n##### `reader.describeRef(ref)`\n\nReturns an info object for provided `ref`.\n\n```js\nconst info = await reader.describeRef('HEAD');\n// {\n//   path: 'HEAD',\n//   name: 'HEAD',\n//   symbolic: true,\n//   ref: 'refs/heads/test',\n//   oid: '2dbee47a8d4f8d39e1168fad951b703ee05614d6'\n// }\n```\n\n```js\nconst info = await reader.describeRef('main');\n// {\n//   path: 'refs/heads/main',\n//   name: 'main',\n//   symbolic: false,\n//   scope: 'refs/heads',\n//   namespace: 'refs',\n//   category: 'heads',\n//   remote: null,\n//   ref: null,\n//   oid: '7b84f676f2fbea2a3c6d83924fa63059c7bdfbe2'\n// }\n```\n\n```js\nconst info = await reader.describeRef('origin/HEAD');\n// {\n//   path: 'refs/remotes/origin/HEAD',\n//   name: 'HEAD',\n//   symbolic: false,\n//   scope: 'refs/remotes',\n//   namespace: 'refs',\n//   category: 'remotes',\n//   remote: 'origin',\n//   ref: 'refs/remotes/origin/main',\n//   oid: '7b84f676f2fbea2a3c6d83924fa63059c7bdfbe2'\n// }\n```\n\n##### `reader.isOid(value)`\n\nChecks if a `value` is a valid oid.\n\n```js\nreader.isOid('7b84f676f2fbea2a3c6d83924fa63059c7bdfbe2'); // true\nreader.isOid('main'); // false\n```\n\n##### `reader.listRemotes()`\n\n```js\nconst remotes = reader.listRemotes();\n// [\n//   'origin'\n// ]\n```\n\n##### `reader.listRemoteBranches(remote, withOid?)`\n\nGet a list of branches for a remote.\n\n```js\nconst originBranches = await reader.listRemoteBranches('origin');\n// [\n//   'HEAD',\n//   'main'\n// ]\n\nconst originBranches = await reader.listRemoteBranches('origin', true);\n// [\n//   { name: 'HEAD', oid: '7c2a62cdbc2ef28afaaed3b6f3aef9b581e5aa8e' }\n//   { name: 'main', oid: '56ea7a808e35df13e76fee92725a65a373a9835c' }\n// ]\n```\n\n##### `reader.listBranches(withOid?)`\n\nGet a list of local branches.\n\n```js\nconst localBranches = await reader.listBranches();\n// [\n//   'HEAD',\n//   'main'\n// ]\n\nconst localBranches = await reader.listBranches(true);\n// [\n//   { name: 'HEAD', oid: '7c2a62cdbc2ef28afaaed3b6f3aef9b581e5aa8e' }\n//   { name: 'main', oid: '56ea7a808e35df13e76fee92725a65a373a9835c' }\n// ]\n```\n\n##### `reader.listTags(withOid?)`\n\nGet a list of tags.\n\n```js\nconst tags = await reader.listTags();\n// [\n//   'v1.0.0',\n//   'some-feature'\n// ]\n\nconst tags = await reader.listTags(true);\n// [\n//   { name: 'v1.0.0', oid: '7c2a62cdbc2ef28afaaed3b6f3aef9b581e5aa8e' }\n//   { name: 'some-feature', oid: '56ea7a808e35df13e76fee92725a65a373a9835c' }\n// ]\n```\n\n---\n\n#### Trees (file lists) methods\n\n##### `reader.treeOidFromRef(ref)`\n\nResolves a Git reference (e.g., branch name, tag, commit, or SHA-1 hash) to the object ID (OID) of the corresponding tree.\n\n- **`ref`**: `string` – The reference, SHA-1 hash, or object ID to resolve.\n\nBehavior:\n\n- If the reference points to an annotated tag, the method resolves the tag to its underlying object\n- If the reference resolves to a commit, the method retrieves the tree associated with the commit\n- If the reference resolves directly to a tree, the tree OID is returned\n- Throws an error if the resolved object is not a tree, commit, or tag\n\n```js\nconst treeOid = await reader.treeOidFromRef('HEAD');\n// 'a1b2c3d4e5f6...'\n\n// Error handling\ntry {\n  const invalidTreeOid = await reader.treeOidFromRef('nonexistent-ref');\n} catch (error) {\n  console.error(error.message); // \"Object 'nonexistent-ref' must be a 'tree' but ...\"\n}\n```\n\n##### `reader.listFiles(ref, filesWithHash)`\n\nList all files in the repository at the specified commit reference.\n\n- `ref`: string (default: `'HEAD'`) – commit reference\n- `filesWithHash`: boolean (default: `false`) – specify to return blob's hashes\n\n```js\nconst headFiles = reader.listFiles(); // the same as reader.listFiles('HEAD')\n// [ 'file.ext', 'path/to/file.ext', ... ]\n\nconst headFilesWithHashes = reader.listFiles('HEAD', true);\n// [ { path: 'file.ext', hash: 'f2e492a3049...' }, ... ]\n```\n\n##### `reader.getPathEntry(path, ref)`\n\nRetrieve a tree entry (file or directory) by its path at the specified commit reference.\n\n- `path`: string - the path to the file or directory\n- `ref`: string (default: `'HEAD'`) - commit reference\n\n```js\nconst entry = await reader.getPathEntry('path/to/file.txt');\n// { isTree: false, path: 'path/to/file.txt', hash: 'a1b2c3d4e5f6...' }\n```\n\n##### `reader.getPathsEntries(paths, ref)`\n\nRetrieve a list of tree entries (files or directories) by their paths at the specified commit reference.\n\n- `paths`: string[] - an array of paths to files or directories\n- `ref`: string (default: `'HEAD'`) - commit reference\n\n```js\nconst entries = await reader.getPathsEntries([\n  'path/to/file1.txt',\n  'path/to/dir1',\n  'path/to/file2.txt'\n]);\n// [\n//   { isTree: false, path: 'path/to/file1.txt', hash: 'a1b2c3d4e5f6...' },\n//   { isTree: true, path: 'path/to/dir1', hash: 'b1c2d3e4f5g6...' },\n//   { isTree: false, path: 'path/to/file2.txt', hash: 'c1d2e3f4g5h6...' }\n// ]\n```\n\n##### `reader.deltaFiles(nextRef, prevRef)`\n\nCompute the file delta (changes) between two commit references, including added, modified, and removed files.\n\n- `nextRef`: string (default: `'HEAD'`) - commit reference for the \"next\" state\n- `prevRef`: string (optional) - commit reference for the \"previous\" state\n\n```js\nconst fileDelta = await reader.deltaFiles('HEAD', 'branch-name');\n// {\n//   add: [ { path: 'path/to/new/file.txt', hash: 'a1b2c3d4e5f6...' }, ... ],\n//   modify: [ { path: 'path/to/modified/file.txt', hash: 'f1e2d3c4b5a6...', prevHash: 'a1b2c3d4e5f6...' }, ... ],\n//   remove: [ { path: 'path/to/removed/file.txt', hash: 'a1b2c3d4e5f6...' }, ... ]\n// }\n```\n\n---\n\n#### Commit methods\n\n##### `reader.commitOidFromRef(ref)`\n\nResolves a Git reference (e.g., branch name, tag, or SHA-1 hash) to the object ID (OID) of the corresponding commit.\n\n- **`ref`**: `string` – The reference, SHA-1 hash, or object ID to resolve.\n\nBehavior:\n\n- If the reference points to an annotated tag, the method resolves the tag to its underlying commit.\n- Throws an error if the reference does not resolve to a valid commit.\n\n```js\nconst commitOid = await reader.commitOidFromRef('HEAD');\n// '7c2a62cdbc2ef28afaaed3b6f3aef9b581e5aa8e'\n\n// Error handling\ntry {\n  const invalidCommitOid = await reader.commitOidFromRef('nonexistent-ref');\n} catch (error) {\n  console.error(error.message); // \"Object 'nonexistent-ref' must be a 'commit' but ...\"\n}\n```\n\n##### `reader.readCommit(ref)`\n\nReads and resolves a commit object identified by a reference (e.g., branch name, tag, or SHA-1 hash).\n\n- **`ref`**: `string` – The reference, SHA-1 hash, or object ID of the commit.\n\n```js\nconst commit = await reader.readCommit('HEAD');\n// {\n//     oid: '7c2a62cdbc2ef28afaaed3b6f3aef9b581e5aa8e',\n//     tree: '20596d5c9e037844ae2b707a4a1cb45c72e70e7f',\n//     parent: ['8bb6e23769902199e39ab70f2441841712cbdd62'],\n//     author: { name: 'John Doe', email: 'john@example.com', timestamp: 1680390225, timezone: '+0200' },\n//     committer: { name: 'Jane Doe', email: 'jane@example.com', timestamp: 1680392225, timezone: '+0200' },\n//     message: 'Initial commit',\n//     gpgsig: '-----BEGIN PGP SIGNATURE-----...'\n// }\n```\n\n##### `reader.log(options)`\n\nReturns a list of commits in topological order, starting from the specified reference.\n\n- **`options`**: An object with the following properties:\n  - **`ref`**: `string` _(default: `'HEAD'`)_ – The reference, SHA-1 hash, or object ID to start from.\n  - **`depth`**: `number` _(default: `50`)_ – Limits the number of commits to retrieve. Pass `Infinity` to retrieve all reachable commits.\n\n```js\nconst commits = await reader.log({ ref: 'my-branch', depth: 10 });\n// [\n//     { oid: 'a1b2c3d4...', tree: '...', parent: [...], author: {...}, committer: {...}, message: '...' },\n//     { oid: 'b2c3d4e5...', tree: '...', parent: [...], author: {...}, committer: {...}, message: '...' },\n//     ...\n// ]\n```\n\nTo retrieve all commits reachable from a `ref`, set the `depth` option to `Infinity`.\n\n```js\nconst allCommits = await reader.log({ ref: 'my-branch', depth: Infinity });\nconsole.log(allCommits.length); // All reachable commits\n```\n\n---\n\n#### Misc methods\n\n##### `reader.readObjectHeaderByHash(hash)`\n\nReads and returns the header of a Git object by its hash.\n\n- **`hash`**: `Buffer` – The SHA-1 hash of the object\n\n```js\nconst hash = Buffer.from('8bb6e23769902199e39ab70f2441841712cbdd62', 'hex');\nconst header = await reader.readObjectHeaderByHash(hash);\n// { type: 'commit', length: 123 }\n```\n\n##### `reader.readObjectByHash(hash, cache?)`\n\nReads and returns the complete content of a Git object by its hash.\n\n- **`hash`**: `Buffer` – The SHA-1 hash of the object\n- **`cache`**: `boolean` _(optional)_ – Whether to use reader's caching (default: `true`)\n\n```js\nconst hash = Buffer.from('8bb6e23769902199e39ab70f2441841712cbdd62', 'hex');\nconst object = await reader.readObjectByHash(hash);\n// { type: 'blob', object: \u003cBuffer ...\u003e }\n```\n\n##### `reader.readObjectHeaderByOid(oid)`\n\nReads and returns the header of a Git object by its OID (Object ID).\n\n- **`oid`**: `string` – The Object ID of the Git object\n\n```js\nconst header = await reader.readObjectHeaderByOid('8bb6e23769902199e39ab70f2441841712cbdd62');\n// { type: 'tree', length: 45 }\n```\n\n##### `reader.readObjectByOid(oid, cache?)`\n\nReads and returns the complete content of a Git object by its OID.\n\n- **`oid`**: `string` – The Object ID of the Git object.\n- **`cache`**: `boolean` _(optional)_ – Whether to use reader's caching (default: `true`).\n\n```js\nconst object = await reader.readObjectByOid('8bb6e23769902199e39ab70f2441841712cbdd62');\n// { type: 'tree', object: \u003cBuffer ...\u003e }\n```\n\n##### `reader.stat()`\n\nRetrieves repository statistics, including refs, objects, and files.\n\n```js\nconst stats = await reader.stat();\n/*\n{\n    size: 163937,\n    refs: {\n        remotes: [\n            { remote: \"origin\", branches: [\"HEAD\", \"main\", ...] },\n            ...\n        ],\n        branches: [\"main\", \"foo\", \"bar\", ...],\n        tags: [\"tag1\", \"tag2\", ...]\n    },\n    objects: {\n        count: 322,\n        size: 145569,\n        unpackedSize: 446973,\n        unpackedRestoredSize: 755430,\n        types: [\n            { type: \"tree\", count: 23, size: 7537, unpackedSize: 8929, unpackedRestoredSize: 0 },\n            ...\n        ]\n        loose: {\n            objects: { count: 19, size: 15407, unpackedSize: 40312, unpackedRestoredSize: 0, types: [...] },\n            files: [\n                {\n                    path: \"objects/20/596d5c9e037844ae2b707a4a1cb45c72e70e7f\",\n                    size: 536,\n                    object: { oid: \"20596d5c9e037844ae2b707a4a1cb45c72e70e7f\", type: \"tree\", length: 606 }\n                },\n                ...\n            ]\n        },\n        packed:{\n            objects: { ... },\n            files: [\n                {\n                    path: \"objects/pack/pack-43bc2b9ae5b7a56ab22e849c6c1dfaa00ba72ab1.pack\",\n                    size: 130194,\n                    objects: { ... },\n                    index: {\n                        path: \"objects/pack/pack-43bc2b9ae5b7a56ab22e849c6c1dfaa00ba72ab1.idx\",\n                        size: 9556,\n                        namesBytes: 6060,\n                        offsetsBytes: 1212,\n                        largeOffsetsBytes: 0\n                    },\n                    reverseIndex: {\n                        path: \"objects/pack/pack-43bc2b9ae5b7a56ab22e849c6c1dfaa00ba72ab1.rev\",\n                        size: 1264\n                    }\n                },\n                ...\n            ]\n        }\n    },\n    files: [\n        { path: 'config', size: 123 },\n        { path: 'objects/pack/pack-a1b2c3d4.pack', size: 456789 },\n        { path: 'refs/heads/main', size: 45 }\n    ]\n}\n*/\n```\n\n---\n\n### Utils\n\n#### `isGitDir(dir)`\n\nChecks whether the specified directory is a valid Git directory. Returns `true` if the directory contains the necessary files and subdirectories to be a valid Git directory (e.g. `objects`, `refs`, `HEAD`, and `config`), `false` otherwise.\n\n- **`dir`**: `string` – The path to the directory to check.\n\n```js\nimport { isGitDir } from '@discoveryjs/scan-git';\n\nconst isValidGitDir = await isGitDir('/path/to/repo/.git');\nconsole.log(isValidGitDir); // true or false\n```\n\n#### `resolveGitDir(dir)`\n\nResolves the path to the Git directory for the specified input directory.\n\n- **`dir`**: `string` – The path to the directory to resolve.\n\nBehaviour:\n\n- If the input directory contains a `.git` subdirectory, the method resolves to its path\n- If no `.git` subdirectory is found, it resolves the input directory itself, assuming it's already the `.git` directory\n- Throws an error if the input path doesn't exist or isn't a directory\n\n```js\nimport { resolveGitDir } from '@discoveryjs/scan-git';\n\ntry {\n  const gitDir = await resolveGitDir('/path/to/repo');\n  console.log(gitDir); // '/path/to/repo/.git' or '/path/to/repo'\n} catch (error) {\n  console.error(error.message);\n}\n```\n\n#### `parseContributor(input)`\n\nParses a string representation of a Git contributor into a structured object.\n\n- **`input`**: `string` – A contributor string in the format `Name \u003cemail\u003e timestamp timezone`\n\n```js\nimport { parseContributor } from '@discoveryjs/scan-git';\n\nconst contributor = parseContributor('John Doe \u003cjohn.doe@example.com\u003e 1680390225 +0200');\n// {\n//     name: 'John Doe',\n//     email: 'john.doe@example.com',\n//     timestamp: 1680390225,\n//     timezone: '+0200'\n// }\n```\n\n#### `parseTimezone(offset)`\n\nParses a Git timezone offset string into a numeric offset in minutes.\n\n- **`offset`**: `string` – A timezone string in the format `+hhmm` or `-hhmm`.\n\n```js\nimport { parseTimezone } from '@discoveryjs/scan-git';\n\nconst timezoneOffset = parseTimezone('+0200');\nconsole.log(timezoneOffset); // 120\n```\n\n#### `parseAnnotatedTag(object)`\n\nParses a buffer representing an annotated Git tag into a structured object.\n\n- **`object`**: `Buffer` – The tag object buffer.\n\n```js\nimport { parseAnnotatedTag } from '@discoveryjs/scan-git';\n\nconst tagObject = await reader.readObjectByOid('7c2a62cdbc2ef28afaaed3b6f3aef9b581e5aa8e');\nconst tag = parseAnnotatedTag(tagObject.content);\n// {\n//     tag: 'v1.0.0',\n//     type: 'tag',\n//     object: 'a1b2c3d4e5f6g7h8i9j0',\n//     tagger: { name: 'John Doe', email: 'john@example.com', timestamp: 1680390225, timezone: '+0200' },\n//     message: 'Initial release',\n//     gpgsig: '-----BEGIN PGP SIGNATURE-----...'\n// }\n```\n\n#### `parseCommit(object)`\n\nParses a buffer representing a Git commit into a structured object.\n\n- **`object`**: `Buffer` – The commit object buffer.\n\n```js\nimport { parseCommit } from '@discoveryjs/scan-git';\n\nconst commitObject = await reader.readObjectByOid('7c2a62cdbc2ef28afaaed3b6f3aef9b581e5aa8e');\nconst commit = parseCommit(commitObject.content);\n// {\n//     tree: 'a1b2c3d4e5f6g7h8i9j0',\n//     parent: ['b2c3d4e5f6g7h8i9j0k1'],\n//     author: { name: 'John Doe', email: 'john@example.com', timestamp: 1680390225, timezone: '+0200' },\n//     committer: { name: 'John Doe', email: 'john@example.com', timestamp: 1680390225, timezone: '+0200' },\n//     message: 'Fix a critical bug',\n//     gpgsig: '-----BEGIN PGP SIGNATURE-----...'\n// }\n```\n\n#### `parseTree(buffer)`\n\nParses a buffer representing a Git tree object into a structured array of entries.\n\n- **`buffer`**: `Buffer` – The tree object buffer.\n\n```js\nimport { parseTree } from '@discoveryjs/scan-git';\n\nconst treeObject = await reader.readObjectByOid('7c2a62cdbc2ef28afaaed3b6f3aef9b581e5aa8e');\nconst tree = parseTree(treeObject.content);\n// [\n//     { isTree: true, path: 'src', hash: \u003cBuffer ...\u003e },\n//     { isTree: false, path: 'README.md', hash: \u003cBuffer ...\u003e }\n// ]\n```\n\n## Features and comparation\n\n| scan-git | isomorphic-git | Feature                                                                                                                                                                                                                   |\n| :------: | :------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n|    ✅    |       ✅       | loose refs                                                                                                                                                                                                                |\n|    ✅    |       ✅       | packed refs                                                                                                                                                                                                               |\n|    🚫    |       ✅       | [index file] \u003cbr\u003e\u003csub\u003eBoosts fetching a file list for HEAD\u003c/sub\u003e                                                                                                                                                          |\n|    ✅    |       ✅       | loose objects                                                                                                                                                                                                             |\n|    ✅    |       ✅       | packed objects (`*.pack` + `*.idx` files)                                                                                                                                                                                 |\n|    ✅    |       🚫       | [2Gb+ packs support] \u003cbr\u003e\u003csub\u003eVersion 2 `pack-*.idx` files support packs larger than 4 GiB by adding an optional table of 8-byte offset entries for large offsets\u003c/sub\u003e                                                   |\n|    ✅    |       🚫       | [On-disk reverse indexes] (`*.rev` files) \u003cbr\u003e\u003csub\u003eReverse index is boosting operations such as a seeking an object by offset or scanning objects in a pack order\u003c/sub\u003e                                                   |\n|    🚫    |       🚫       | [multi-pack-index] (MIDX) \u003cbr\u003e\u003csub\u003eStores a list of objects and their offsets into multiple packfiles, can provide O(log N) lookup time for any number of packfiles\u003c/sub\u003e                                                 |\n|    🚫    |       🚫       | [multi-pack-index reverse indexes] (RIDX) \u003cbr\u003e\u003csub\u003eSimilar to the pack-based reverse index\u003c/sub\u003e                                                                                                                          |\n|    ✅    |       🚫       | [Cruft packs] \u003cbr\u003e\u003csub\u003eA cruft pack eliminates the need for storing unreachable objects in a loose state by including the per-object mtimes in a separate file alongside a single pack containing all loose objects\u003c/sub\u003e |\n|    🚫    |       🚫       | [Pack and multi-pack bitmaps] \u003cbr\u003e\u003csub\u003eBitmaps store reachability information about the set of objects in a packfile, or a multi-pack index\u003c/sub\u003e                                                                         |\n| 🚫 (TBD) |       🚫       | [commit-graph] \u003cbr\u003e\u003csub\u003eA binary file format that creates a structured representation of Git’s commit history, optimizes some operations\u003c/sub\u003e                                                                            |\n\n[index file]: https://git-scm.com/docs/index-format\n[2gb+ packs support]: https://git-scm.com/docs/pack-format#_version_2_pack_idx_files_support_packs_larger_than_4_gib_and\n[on-disk reverse indexes]: https://github.blog/2021-03-15-highlights-from-git-2-31/\n[multi-pack-index]: https://git-scm.com/docs/multi-pack-index\n[multi-pack-index reverse indexes]: https://git-scm.com/docs/pack-format#_multi_pack_index_reverse_indexes\n[cruft packs]: https://git-scm.com/docs/cruft-packs\n[pack and multi-pack bitmaps]: https://github.blog/2021-11-15-highlights-from-git-2-34/#multi-pack-reachability-bitmaps\n[commit-graph]: https://devblogs.microsoft.com/devops/updates-to-the-git-commit-graph-feature/\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdiscoveryjs%2Fscan-git","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdiscoveryjs%2Fscan-git","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdiscoveryjs%2Fscan-git/lists"}