From 1b8ba79600e5b00fb9cb5981287fa612d0c2f2cf Mon Sep 17 00:00:00 2001 From: LongYinan Date: Fri, 23 Aug 2024 18:10:47 +0800 Subject: [PATCH] feat: implment blob and related features --- .github/workflows/CI.yml | 42 +- __test__/repo.spec.mjs | 69 ++- index.d.ts | 1021 +++++++++++++++++++------------------- index.js | 686 ++++++++++++------------- src/blob.rs | 52 ++ src/lib.rs | 1 + src/object.rs | 17 +- src/tree.rs | 116 ++++- 8 files changed, 1068 insertions(+), 936 deletions(-) create mode 100644 src/blob.rs diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 1495278..fba387c 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -2,20 +2,20 @@ name: CI env: DEBUG: napi:* APP_NAME: simple-git - MACOSX_DEPLOYMENT_TARGET: '10.13' + MACOSX_DEPLOYMENT_TARGET: "10.13" permissions: contents: write id-token: write -'on': +"on": push: branches: - main tags-ignore: - - '**' + - "**" paths-ignore: - - '**/*.md' + - "**/*.md" - LICENSE - - '**/*.gitignore' + - "**/*.gitignore" - .editorconfig - docs/** pull_request: null @@ -120,10 +120,10 @@ jobs: uses: actions/cache@v4 with: path: | - ~/.cargo/registry - ~/.cargo/git - .cargo-cache - target + ~/.cargo/registry + ~/.cargo/git + .cargo-cache + target key: ${{ matrix.settings.target }}-cargo-cache - name: Setup toolchain run: ${{ matrix.settings.setup }} @@ -140,7 +140,7 @@ jobs: if: ${{ matrix.settings.docker }} with: image: ${{ matrix.settings.docker }} - options: '-v ${{ github.workspace }}/.cargo-cache/git:/usr/local/cargo/git -v ${{ github.workspace }}/.cargo-cache/registry:/usr/local/cargo/registry -v ${{ github.workspace }}:/build -w /build' + options: "-v ${{ github.workspace }}/.cargo-cache/git:/usr/local/cargo/git -v ${{ github.workspace }}/.cargo-cache/registry:/usr/local/cargo/registry -v ${{ github.workspace }}:/build -w /build" run: ${{ matrix.settings.build }} - name: Build run: ${{ matrix.settings.build }} @@ -166,10 +166,10 @@ jobs: RUSTUP_IO_THREADS: 1 with: operating_system: freebsd - version: '14.0' + version: "14.1" memory: 8G cpu_count: 3 - environment_variables: 'DEBUG RUSTUP_IO_THREADS' + environment_variables: "DEBUG RUSTUP_IO_THREADS" shell: bash run: | sudo pkg install -y -f curl node libnghttp2 npm perl5 @@ -206,17 +206,17 @@ jobs: matrix: settings: - host: macos-latest - target: 'x86_64-apple-darwin' - architecture: 'x64' + target: "x86_64-apple-darwin" + architecture: "x64" - host: macos-latest - target: 'aarch64-apple-darwin' - architecture: 'arm64' + target: "aarch64-apple-darwin" + architecture: "arm64" - host: windows-latest target: x86_64-pc-windows-msvc - architecture: 'x64' + architecture: "x64" node: - - '18' - - '20' + - "18" + - "20" runs-on: ${{ matrix.settings.host }} steps: - uses: actions/checkout@v4 @@ -248,8 +248,8 @@ jobs: fail-fast: false matrix: node: - - '18' - - '20' + - "18" + - "20" runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/__test__/repo.spec.mjs b/__test__/repo.spec.mjs index 2cec1bb..2cd4cae 100644 --- a/__test__/repo.spec.mjs +++ b/__test__/repo.spec.mjs @@ -1,47 +1,66 @@ -import { execSync } from 'child_process' -import { join } from 'path' -import { fileURLToPath } from 'url' +import { readFile } from "node:fs/promises"; +import { execSync } from "node:child_process"; +import { join } from "node:path"; +import { fileURLToPath } from "node:url"; -import test from 'ava' +import test from "ava"; -const __dirname = join(fileURLToPath(import.meta.url), '..') +const __dirname = join(fileURLToPath(import.meta.url), ".."); -import { Repository } from '../index.js' +import { Repository } from "../index.js"; -const workDir = join(__dirname, '..') +const workDir = join(__dirname, ".."); test.beforeEach((t) => { - t.context.repo = new Repository(workDir) -}) + t.context.repo = new Repository(workDir); +}); -test('Date should be equal with cli', (t) => { - const { repo } = t.context +test("Date should be equal with cli", (t) => { + const { repo } = t.context; if (process.env.CI) { - t.notThrows(() => repo.getFileLatestModifiedDate(join('src', 'lib.rs'))) + t.notThrows(() => repo.getFileLatestModifiedDate(join("src", "lib.rs"))); } else { t.deepEqual( new Date( - execSync('git log -1 --format=%cd --date=iso src/lib.rs', { + execSync("git log -1 --format=%cd --date=iso src/lib.rs", { cwd: workDir, }) - .toString('utf8') - .trim() + .toString("utf8") + .trim(), ).valueOf(), - repo.getFileLatestModifiedDate(join('src', 'lib.rs')) - ) + repo.getFileLatestModifiedDate(join("src", "lib.rs")), + ); } -}) +}); -test('Should be able to resolve head', (t) => { - const { repo } = t.context +test("Should be able to resolve head", (t) => { + const { repo } = t.context; t.is( repo.head().target(), process.env.CI ? process.env.GITHUB_SHA - : execSync('git rev-parse HEAD', { + : execSync("git rev-parse HEAD", { cwd: workDir, }) - .toString('utf8') - .trim() - ) -}) + .toString("utf8") + .trim(), + ); +}); + +test("Should be able to get blob content", async (t) => { + if (process.platform === "win32") { + t.pass("Skip test on windows"); + return; + } + const { repo } = t.context; + const blob = repo + .head() + .peelToTree() + .getPath("__test__/repo.spec.mjs") + .toObject(repo) + .peelToBlob(); + t.deepEqual( + await readFile(join(__dirname, "repo.spec.mjs"), "utf8"), + Buffer.from(blob.content()).toString("utf8"), + ); +}); diff --git a/index.d.ts b/index.d.ts index 83f0ac5..9a74c02 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,6 +1,271 @@ -/* auto-generated by NAPI-RS */ +/* tslint:disable */ /* eslint-disable */ -export class Commit { + +/* auto-generated by NAPI-RS */ + +export const enum DiffFlags { + /** + * File(s) treated as binary data. + * 1 << 0 + */ + Binary = 1, + /** + * File(s) treated as text data. + * 1 << 1 + */ + NotBinary = 2, + /** + * `id` value is known correct. + * 1 << 2 + */ + ValidId = 4, + /** + * File exists at this side of the delta. + * 1 << 3 + */ + Exists = 8 +} +/** Valid modes for index and tree entries. */ +export const enum FileMode { + /** Unreadable */ + Unreadable = 0, + /** Tree */ + Tree = 1, + /** Blob */ + Blob = 2, + /** Group writable blob. Obsolete mode kept for compatibility reasons */ + BlobGroupWritable = 3, + /** Blob executable */ + BlobExecutable = 4, + /** Link */ + Link = 5, + /** Commit */ + Commit = 6 +} +export const enum Delta { + /** No changes */ + Unmodified = 0, + /** Entry does not exist in old version */ + Added = 1, + /** Entry does not exist in new version */ + Deleted = 2, + /** Entry content changed between old and new */ + Modified = 3, + /** Entry was renamed between old and new */ + Renamed = 4, + /** Entry was copied from another old entry */ + Copied = 5, + /** Entry is ignored item in workdir */ + Ignored = 6, + /** Entry is untracked item in workdir */ + Untracked = 7, + /** Type of entry changed between old and new */ + Typechange = 8, + /** Entry is unreadable */ + Unreadable = 9, + /** Entry in the index is conflicted */ + Conflicted = 10 +} +export interface DiffOptions { + /** + * When generating output, include the names of unmodified files if they + * are included in the `Diff`. Normally these are skipped in the formats + * that list files (e.g. name-only, name-status, raw). Even with this these + * will not be included in the patch format. + */ + showUnmodified?: boolean +} +export const enum ObjectType { + /** Any kind of git object */ + Any = 0, + /** An object which corresponds to a git commit */ + Commit = 1, + /** An object which corresponds to a git tree */ + Tree = 2, + /** An object which corresponds to a git blob */ + Blob = 3, + /** An object which corresponds to a git tag */ + Tag = 4 +} +/** An enumeration of all possible kinds of references. */ +export const enum ReferenceType { + /** A reference which points at an object id. */ + Direct = 0, + /** A reference which points at another reference. */ + Symbolic = 1, + Unknown = 2 +} +/** An enumeration of the possible directions for a remote. */ +export const enum Direction { + /** Data will be fetched (read) from this remote. */ + Fetch = 0, + /** Data will be pushed (written) to this remote. */ + Push = 1 +} +/** Configuration for how pruning is done on a fetch */ +export const enum FetchPrune { + /** Use the setting from the configuration */ + Unspecified = 0, + /** Force pruning on */ + On = 1, + /** Force pruning off */ + Off = 2 +} +/** Automatic tag following options. */ +export const enum AutotagOption { + /** Use the setting from the remote's configuration */ + Unspecified = 0, + /** Ask the server for tags pointing to objects we're already downloading */ + Auto = 1, + /** Don't ask for any tags beyond the refspecs */ + None = 2, + /** Ask for all the tags */ + All = 3 +} +/** + * Remote redirection settings; whether redirects to another host are + * permitted. + * + * By default, git will follow a redirect on the initial request + * (`/info/refs`), but not subsequent requests. + */ +export const enum RemoteRedirect { + /** Do not follow any off-site redirects at any stage of the fetch or push. */ + None = 0, + /** + * Allow off-site redirects only upon the initial request. This is the + * default. + */ + Initial = 1, + /** Allow redirects at any stage in the fetch or push. */ + All = 2 +} +/** Types of credentials that can be requested by a credential callback. */ +export const enum CredentialType { + /** 1 << 0 */ + UserPassPlaintext = 1, + /** 1 << 1 */ + SshKey = 2, + /** 1 << 6 */ + SshMemory = 64, + /** 1 << 2 */ + SshCustom = 4, + /** 1 << 3 */ + Default = 8, + /** 1 << 4 */ + SshInteractive = 16, + /** 1 << 5 */ + Username = 32 +} +export interface CredInfo { + credType: CredentialType + url: string + username: string +} +export const enum RemoteUpdateFlags { + UpdateFetchHead = 1, + ReportUnchanged = 2 +} +export interface Progress { + totalObjects: number + indexedObjects: number + receivedObjects: number + localObjects: number + totalDeltas: number + indexedDeltas: number + receivedBytes: number +} +export interface PushTransferProgress { + current: number + total: number + bytes: number +} +/** Check whether a cred_type contains another credential type. */ +export function credTypeContains(credType: CredentialType, another: CredentialType): boolean +export const enum RepositoryState { + Clean = 0, + Merge = 1, + Revert = 2, + RevertSequence = 3, + CherryPick = 4, + CherryPickSequence = 5, + Bisect = 6, + Rebase = 7, + RebaseInteractive = 8, + RebaseMerge = 9, + ApplyMailbox = 10, + ApplyMailboxOrRebase = 11 +} +export const enum RepositoryOpenFlags { + /** Only open the specified path; don't walk upward searching. */ + NoSearch = 0, + /** Search across filesystem boundaries. */ + CrossFS = 1, + /** Force opening as bare repository, and defer loading its config. */ + Bare = 2, + /** Don't try appending `/.git` to the specified repository path. */ + NoDotGit = 3, + /** Respect environment variables like `$GIT_DIR`. */ + FromEnv = 4 +} +export const enum CloneLocal { + /** + * Auto-detect (default) + * + * Here libgit2 will bypass the git-aware transport for local paths, but + * use a normal fetch for `file://` URLs. + */ + Auto = 0, + /** Bypass the git-aware transport even for `file://` URLs. */ + Local = 1, + /** Never bypass the git-aware transport */ + None = 2, + /** Bypass the git-aware transport, but don't try to use hardlinks. */ + NoLinks = 3 +} +/** Orderings that may be specified for Revwalk iteration. */ +export const enum Sort { + /** + * Sort the repository contents in no particular ordering. + * + * This sorting is arbitrary, implementation-specific, and subject to + * change at any time. This is the default sorting for new walkers. + */ + None = 0, + /** + * Sort the repository contents in topological order (children before + * parents). + * + * This sorting mode can be combined with time sorting. + * 1 << 0 + */ + Topological = 1, + /** + * Sort the repository contents by commit time. + * + * This sorting mode can be combined with topological sorting. + * 1 << 1 + */ + Time = 2, + /** + * Iterate through the repository contents in reverse order. + * + * This sorting mode can be combined with any others. + * 1 << 2 + */ + Reverse = 4 +} +export declare class Blob { + /** Get the id (SHA1) of a repository blob */ + id(): string + /** Determine if the blob content is most certainly binary or not. */ + isBinary(): boolean + /** Get the content of this blob. */ + content(): Uint8Array + /** Get the size in bytes of the contents of this blob. */ + size(): bigint +} +export declare class Commit { /** Get the id (SHA1) of a repository object */ id(): string /** @@ -139,62 +404,11 @@ export class Commit { /** Casts this Commit to be usable as an `Object` */ asObject(): GitObject } - -export class Cred { - /** - * Create a "default" credential usable for Negotiate mechanisms like NTLM - * or Kerberos authentication. - */ - constructor() - /** - * Create a new ssh key credential object used for querying an ssh-agent. - * - * The username specified is the username to authenticate. - */ - static sshKeyFromAgent(username: string): Cred - /** Create a new passphrase-protected ssh key credential object. */ - static sshKey(username: string, publickey: string | undefined | null, privatekey: string, passphrase?: string | undefined | null): Cred - /** Create a new ssh key credential object reading the keys from memory. */ - static sshKeyFromMemory(username: string, publickey: string | undefined | null, privatekey: string, passphrase?: string | undefined | null): Cred - /** Create a new plain-text username and password credential object. */ - static userpassPlaintext(username: string, password: string): Cred - /** - * Create a credential to specify a username. - * - * This is used with ssh authentication to query for the username if none is - * specified in the URL. - */ - static username(username: string): Cred - /** Check whether a credential object contains username information. */ - hasUsername(): boolean - /** Return the type of credentials that this object represents. */ - credtype(): CredentialType -} - /** An iterator over the diffs in a delta */ -export class Deltas { +export declare class Deltas { [Symbol.iterator](): Iterator } - -export class Diff { - /** - * Merge one diff into another. - * - * This merges items from the "from" list into the "self" list. The - * resulting diff will have all items that appear in either list. - * If an item appears in both lists, then it will be "merged" to appear - * as if the old version was from the "onto" list and the new version - * is from the "from" list (with the exception that if the item has a - * pending DELETE in the middle, then it will show as deleted). - */ - merge(diff: Diff): void - /** Returns an iterator over the deltas in this diff. */ - deltas(): Deltas - /** Check if deltas are sorted case sensitively or insensitively. */ - isSortedIcase(): boolean -} - -export class DiffDelta { +export declare class DiffDelta { /** * Returns the flags on the delta. * @@ -220,8 +434,7 @@ export class DiffDelta { */ newFile(): DiffFile } - -export class DiffFile { +export declare class DiffFile { /** * Returns the Oid of this item. * @@ -247,46 +460,24 @@ export class DiffFile { /** Returns file mode. */ mode(): FileMode } - -export class FetchOptions { - constructor() - /** Set the callbacks to use for the fetch operation. */ - remoteCallback(callback: RemoteCallbacks): this - /** Set the proxy options to use for the fetch operation. */ - proxyOptions(options: ProxyOptions): this - /** Set whether to perform a prune after the fetch. */ - prune(prune: FetchPrune): this - /** - * Set whether to write the results to FETCH_HEAD. - * - * Defaults to `true`. - */ - updateFetchhead(update: boolean): this - /** - * Set fetch depth, a value less or equal to 0 is interpreted as pull - * everything (effectively the same as not declaring a limit depth). - */ - depth(depth: number): this - /** - * Set how to behave regarding tags on the remote, such as auto-downloading - * tags for objects we're downloading or downloading all of them. - * - * The default is to auto-follow tags. - */ - downloadTags(opt: AutotagOption): this +export declare class Diff { /** - * Set remote redirection settings; whether redirects to another host are - * permitted. + * Merge one diff into another. * - * By default, git will follow a redirect on the initial request - * (`/info/refs`), but not subsequent requests. + * This merges items from the "from" list into the "self" list. The + * resulting diff will have all items that appear in either list. + * If an item appears in both lists, then it will be "merged" to appear + * as if the old version was from the "onto" list and the new version + * is from the "from" list (with the exception that if the item has a + * pending DELETE in the middle, then it will show as deleted). */ - followRedirects(opt: RemoteRedirect): this - /** Set extra headers for this fetch operation. */ - customHeaders(headers: Array): this + merge(diff: Diff): void + /** Returns an iterator over the deltas in this diff. */ + deltas(): Deltas + /** Check if deltas are sorted case sensitively or insensitively. */ + isSortedIcase(): boolean } - -export class GitObject { +export declare class GitObject { /** Get the id (SHA1) of a repository object */ id(): string /** Get the type of the object. */ @@ -299,25 +490,10 @@ export class GitObject { * referenced object is no longer a tag). */ peel(kind: ObjectType): GitObject + /** Recursively peel an object until a blob is found */ + peelToBlob(): Blob } - -export class ProxyOptions { - constructor() - /** - * Try to auto-detect the proxy from the git configuration. - * - * Note that this will override `url` specified before. - */ - auto(): this - /** - * Specify the exact URL of the proxy to use. - * - * Note that this will override `auto` specified before. - */ - url(url: string): this -} - -export class Reference { +export declare class Reference { /** * Ensure the reference name is well-formed. * @@ -410,8 +586,7 @@ export class Reference { */ rename(newName: string, force: boolean, msg: string): Reference } - -export class Remote { +export declare class Remote { /** Ensure the remote name is well-formed. */ static isValidName(name: string): boolean /** @@ -466,8 +641,7 @@ export class Remote { /** Update the tips to the new state */ updateTips(updateFetchhead: RemoteUpdateFlags, downloadTags: AutotagOption, callbacks?: RemoteCallbacks | undefined | null, msg?: string | undefined | null): void } - -export class RemoteCallbacks { +export declare class RemoteCallbacks { constructor() /** * The callback through which to fetch credentials if required. @@ -502,39 +676,89 @@ export class RemoteCallbacks { /** The callback through which progress of push transfer is monitored */ pushTransferProgress(callback: (current: number, total: number, bytes: number) => void): this } - -export class RepoBuilder { +export declare class FetchOptions { constructor() + /** Set the callbacks to use for the fetch operation. */ + remoteCallback(callback: RemoteCallbacks): this + /** Set the proxy options to use for the fetch operation. */ + proxyOptions(options: ProxyOptions): this + /** Set whether to perform a prune after the fetch. */ + prune(prune: FetchPrune): this /** - * Indicate whether the repository will be cloned as a bare repository or - * not. + * Set whether to write the results to FETCH_HEAD. + * + * Defaults to `true`. */ - bare(bare: boolean): this + updateFetchhead(update: boolean): this /** - * Specify the name of the branch to check out after the clone. + * Set fetch depth, a value less or equal to 0 is interpreted as pull + * everything (effectively the same as not declaring a limit depth). + */ + depth(depth: number): this + /** + * Set how to behave regarding tags on the remote, such as auto-downloading + * tags for objects we're downloading or downloading all of them. * - * If not specified, the remote's default branch will be used. + * The default is to auto-follow tags. */ - branch(branch: string): this + downloadTags(opt: AutotagOption): this /** - * Configures options for bypassing the git-aware transport on clone. + * Set remote redirection settings; whether redirects to another host are + * permitted. * - * Bypassing it means that instead of a fetch libgit2 will copy the object - * database directory instead of figuring out what it needs, which is - * faster. If possible, it will hardlink the files to save space. + * By default, git will follow a redirect on the initial request + * (`/info/refs`), but not subsequent requests. */ - cloneLocal(cloneLocal: CloneLocal): this + followRedirects(opt: RemoteRedirect): this + /** Set extra headers for this fetch operation. */ + customHeaders(headers: Array): this +} +export declare class ProxyOptions { + constructor() /** - * Options which control the fetch, including callbacks. + * Try to auto-detect the proxy from the git configuration. * - * The callbacks are used for reporting fetch progress, and for acquiring - * credentials in the event they are needed. + * Note that this will override `url` specified before. + */ + auto(): this + /** + * Specify the exact URL of the proxy to use. + * + * Note that this will override `auto` specified before. + */ + url(url: string): this +} +export declare class Cred { + /** + * Create a "default" credential usable for Negotiate mechanisms like NTLM + * or Kerberos authentication. + */ + constructor() + /** + * Create a new ssh key credential object used for querying an ssh-agent. + * + * The username specified is the username to authenticate. + */ + static sshKeyFromAgent(username: string): Cred + /** Create a new passphrase-protected ssh key credential object. */ + static sshKey(username: string, publickey: string | undefined | null, privatekey: string, passphrase?: string | undefined | null): Cred + /** Create a new ssh key credential object reading the keys from memory. */ + static sshKeyFromMemory(username: string, publickey: string | undefined | null, privatekey: string, passphrase?: string | undefined | null): Cred + /** Create a new plain-text username and password credential object. */ + static userpassPlaintext(username: string, password: string): Cred + /** + * Create a credential to specify a username. + * + * This is used with ssh authentication to query for the username if none is + * specified in the URL. */ - fetchOptions(fetchOptions: FetchOptions): this - clone(url: string, path: string): Repository + static username(username: string): Cred + /** Check whether a credential object contains username information. */ + hasUsername(): boolean + /** Return the type of credentials that this object represents. */ + credtype(): CredentialType } - -export class Repository { +export declare class Repository { static init(p: string): Repository /** * Find and open an existing repository, with additional options. @@ -822,8 +1046,37 @@ export class Repository { getFileLatestModifiedDate(filepath: string): number getFileLatestModifiedDateAsync(filepath: string, signal?: AbortSignal | undefined | null): Promise } - -export class RevWalk { +export declare class RepoBuilder { + constructor() + /** + * Indicate whether the repository will be cloned as a bare repository or + * not. + */ + bare(bare: boolean): this + /** + * Specify the name of the branch to check out after the clone. + * + * If not specified, the remote's default branch will be used. + */ + branch(branch: string): this + /** + * Configures options for bypassing the git-aware transport on clone. + * + * Bypassing it means that instead of a fetch libgit2 will copy the object + * database directory instead of figuring out what it needs, which is + * faster. If possible, it will hardlink the files to save space. + */ + cloneLocal(cloneLocal: CloneLocal): this + /** + * Options which control the fetch, including callbacks. + * + * The callbacks are used for reporting fetch progress, and for acquiring + * credentials in the event they are needed. + */ + fetchOptions(fetchOptions: FetchOptions): this + clone(url: string, path: string): Repository +} +export declare class RevWalk { [Symbol.iterator](): Iterator /** * Reset a revwalk to allow re-configuring it. @@ -876,411 +1129,143 @@ export class RevWalk { * `` is in the form accepted by `revparse_single`. The left-hand * commit will be hidden and the right-hand commit pushed. */ - pushRange(range: string): this - /** - * Push the OID pointed to by a reference - * - * The reference must point to a commitish. - */ - pushRef(reference: string): this - /** Mark a commit as not of interest to this revwalk. */ - hide(oid: string): this - /** - * Hide the repository's HEAD - * - * For more information, see `hide`. - */ - hideHead(): this - /** - * Hide matching references. - * - * The OIDs pointed to by the references that match the given glob pattern - * and their ancestors will be hidden from the output on the revision walk. - * - * A leading 'refs/' is implied if not present as well as a trailing `/ \ - * *` if the glob lacks '?', ' \ *' or '['. - * - * Any references matching this glob which do not point to a commitish - * will be ignored. - */ - hideGlob(glob: string): this - /** - * Hide the OID pointed to by a reference. - * - * The reference must point to a commitish. - */ - hideRef(reference: string): this -} - -/** - * A Signature is used to indicate authorship of various actions throughout the - * library. - * - * Signatures contain a name, email, and timestamp. All fields can be specified - * with `new` while the `now` constructor omits the timestamp. The - * [`Repository::signature`] method can be used to create a default signature - * with name and email values read from the configuration. - * - * [`Repository::signature`]: struct.Repository.html#method.signature - */ -export class Signature { - /** - * Create a new action signature with a timestamp of 'now'. - * - * See `new` for more information - */ - static now(name: string, email: string): Signature - /** - * Create a new action signature. - * - * The `time` specified is in seconds since the epoch, and the `offset` is - * the time zone offset in minutes. - * - * Returns error if either `name` or `email` contain angle brackets. - */ - constructor(name: string, email: string, time: number) - /** - * Gets the name on the signature. - * - * Returns `None` if the name is not valid utf-8 - */ - name(): string | null - /** - * Gets the email on the signature. - * - * Returns `None` if the email is not valid utf-8 - */ - email(): string | null - /** Return the time, in seconds, from epoch */ - when(): number -} - -export class Tag { - /** - * Determine whether a tag name is valid, meaning that (when prefixed with refs/tags/) that - * it is a valid reference name, and that any additional tag name restrictions are imposed - * (eg, it cannot start with a -). - */ - static isValidName(name: string): boolean - /** Get the id (SHA1) of a repository object */ - id(): string - /** - * Get the message of a tag - * - * Returns None if there is no message or if it is not valid utf8 - */ - message(): string | null - /** - * Get the message of a tag - * - * Returns None if there is no message - */ - messageBytes(): Buffer | null - /** - * Get the name of a tag - * - * Returns None if it is not valid utf8 - */ - name(): string | null - /** Get the name of a tag */ - nameBytes(): Buffer - /** Recursively peel a tag until a non tag git_object is found */ - peel(): GitObject -} - -export class Tree { - /** Get the id (SHA1) of a repository object */ - id(): string - /** Get the number of entries listed in a tree. */ - len(): bigint - /** Return `true` if there is not entry */ - isEmpty(): boolean - /** Returns an iterator over the entries in this tree. */ - iter(): TreeIter -} - -export class TreeEntry { - /** Get the id of the object pointed by the entry */ - id(): string - /** Get the name of a tree entry */ - name(): string - /** Get the filename of a tree entry */ - nameBytes(): Buffer -} - -export class TreeIter { - [Symbol.iterator](): Iterator -} - -/** Automatic tag following options. */ -export const enum AutotagOption { - /** Use the setting from the remote's configuration */ - Unspecified = 0, - /** Ask the server for tags pointing to objects we're already downloading */ - Auto = 1, - /** Don't ask for any tags beyond the refspecs */ - None = 2, - /** Ask for all the tags */ - All = 3 -} - -export const enum CloneLocal { - /** - * Auto-detect (default) - * - * Here libgit2 will bypass the git-aware transport for local paths, but - * use a normal fetch for `file://` URLs. - */ - Auto = 0, - /** Bypass the git-aware transport even for `file://` URLs. */ - Local = 1, - /** Never bypass the git-aware transport */ - None = 2, - /** Bypass the git-aware transport, but don't try to use hardlinks. */ - NoLinks = 3 -} - -/** Types of credentials that can be requested by a credential callback. */ -export const enum CredentialType { - /** 1 << 0 */ - UserPassPlaintext = 1, - /** 1 << 1 */ - SshKey = 2, - /** 1 << 6 */ - SshMemory = 64, - /** 1 << 2 */ - SshCustom = 4, - /** 1 << 3 */ - Default = 8, - /** 1 << 4 */ - SshInteractive = 16, - /** 1 << 5 */ - Username = 32 -} - -export interface CredInfo { - credType: CredentialType - url: string - username: string -} - -/** Check whether a cred_type contains another credential type. */ -export function credTypeContains(credType: CredentialType, another: CredentialType): boolean - -export const enum Delta { - /** No changes */ - Unmodified = 0, - /** Entry does not exist in old version */ - Added = 1, - /** Entry does not exist in new version */ - Deleted = 2, - /** Entry content changed between old and new */ - Modified = 3, - /** Entry was renamed between old and new */ - Renamed = 4, - /** Entry was copied from another old entry */ - Copied = 5, - /** Entry is ignored item in workdir */ - Ignored = 6, - /** Entry is untracked item in workdir */ - Untracked = 7, - /** Type of entry changed between old and new */ - Typechange = 8, - /** Entry is unreadable */ - Unreadable = 9, - /** Entry in the index is conflicted */ - Conflicted = 10 -} - -export const enum DiffFlags { - /** - * File(s) treated as binary data. - * 1 << 0 - */ - Binary = 1, + pushRange(range: string): this /** - * File(s) treated as text data. - * 1 << 1 + * Push the OID pointed to by a reference + * + * The reference must point to a commitish. */ - NotBinary = 2, + pushRef(reference: string): this + /** Mark a commit as not of interest to this revwalk. */ + hide(oid: string): this /** - * `id` value is known correct. - * 1 << 2 + * Hide the repository's HEAD + * + * For more information, see `hide`. */ - ValidId = 4, + hideHead(): this /** - * File exists at this side of the delta. - * 1 << 3 + * Hide matching references. + * + * The OIDs pointed to by the references that match the given glob pattern + * and their ancestors will be hidden from the output on the revision walk. + * + * A leading 'refs/' is implied if not present as well as a trailing `/ \ + * *` if the glob lacks '?', ' \ *' or '['. + * + * Any references matching this glob which do not point to a commitish + * will be ignored. */ - Exists = 8 -} - -export interface DiffOptions { + hideGlob(glob: string): this /** - * When generating output, include the names of unmodified files if they - * are included in the `Diff`. Normally these are skipped in the formats - * that list files (e.g. name-only, name-status, raw). Even with this these - * will not be included in the patch format. + * Hide the OID pointed to by a reference. + * + * The reference must point to a commitish. */ - showUnmodified?: boolean -} - -/** An enumeration of the possible directions for a remote. */ -export const enum Direction { - /** Data will be fetched (read) from this remote. */ - Fetch = 0, - /** Data will be pushed (written) to this remote. */ - Push = 1 -} - -/** Configuration for how pruning is done on a fetch */ -export const enum FetchPrune { - /** Use the setting from the configuration */ - Unspecified = 0, - /** Force pruning on */ - On = 1, - /** Force pruning off */ - Off = 2 -} - -/** Valid modes for index and tree entries. */ -export const enum FileMode { - /** Unreadable */ - Unreadable = 0, - /** Tree */ - Tree = 1, - /** Blob */ - Blob = 2, - /** Group writable blob. Obsolete mode kept for compatibility reasons */ - BlobGroupWritable = 3, - /** Blob executable */ - BlobExecutable = 4, - /** Link */ - Link = 5, - /** Commit */ - Commit = 6 -} - -export const enum ObjectType { - /** Any kind of git object */ - Any = 0, - /** An object which corresponds to a git commit */ - Commit = 1, - /** An object which corresponds to a git tree */ - Tree = 2, - /** An object which corresponds to a git blob */ - Blob = 3, - /** An object which corresponds to a git tag */ - Tag = 4 -} - -export interface Progress { - totalObjects: number - indexedObjects: number - receivedObjects: number - localObjects: number - totalDeltas: number - indexedDeltas: number - receivedBytes: number -} - -export interface PushTransferProgress { - current: number - total: number - bytes: number -} - -/** An enumeration of all possible kinds of references. */ -export const enum ReferenceType { - /** A reference which points at an object id. */ - Direct = 0, - /** A reference which points at another reference. */ - Symbolic = 1, - Unknown = 2 + hideRef(reference: string): this } - /** - * Remote redirection settings; whether redirects to another host are - * permitted. + * A Signature is used to indicate authorship of various actions throughout the + * library. * - * By default, git will follow a redirect on the initial request - * (`/info/refs`), but not subsequent requests. + * Signatures contain a name, email, and timestamp. All fields can be specified + * with `new` while the `now` constructor omits the timestamp. The + * [`Repository::signature`] method can be used to create a default signature + * with name and email values read from the configuration. + * + * [`Repository::signature`]: struct.Repository.html#method.signature */ -export const enum RemoteRedirect { - /** Do not follow any off-site redirects at any stage of the fetch or push. */ - None = 0, +export declare class Signature { /** - * Allow off-site redirects only upon the initial request. This is the - * default. + * Create a new action signature with a timestamp of 'now'. + * + * See `new` for more information */ - Initial = 1, - /** Allow redirects at any stage in the fetch or push. */ - All = 2 -} - -export const enum RemoteUpdateFlags { - UpdateFetchHead = 1, - ReportUnchanged = 2 -} - -export const enum RepositoryOpenFlags { - /** Only open the specified path; don't walk upward searching. */ - NoSearch = 0, - /** Search across filesystem boundaries. */ - CrossFS = 1, - /** Force opening as bare repository, and defer loading its config. */ - Bare = 2, - /** Don't try appending `/.git` to the specified repository path. */ - NoDotGit = 3, - /** Respect environment variables like `$GIT_DIR`. */ - FromEnv = 4 -} - -export const enum RepositoryState { - Clean = 0, - Merge = 1, - Revert = 2, - RevertSequence = 3, - CherryPick = 4, - CherryPickSequence = 5, - Bisect = 6, - Rebase = 7, - RebaseInteractive = 8, - RebaseMerge = 9, - ApplyMailbox = 10, - ApplyMailboxOrRebase = 11 -} - -/** Orderings that may be specified for Revwalk iteration. */ -export const enum Sort { + static now(name: string, email: string): Signature /** - * Sort the repository contents in no particular ordering. + * Create a new action signature. * - * This sorting is arbitrary, implementation-specific, and subject to - * change at any time. This is the default sorting for new walkers. + * The `time` specified is in seconds since the epoch, and the `offset` is + * the time zone offset in minutes. + * + * Returns error if either `name` or `email` contain angle brackets. */ - None = 0, + constructor(name: string, email: string, time: number) /** - * Sort the repository contents in topological order (children before - * parents). + * Gets the name on the signature. * - * This sorting mode can be combined with time sorting. - * 1 << 0 + * Returns `None` if the name is not valid utf-8 */ - Topological = 1, + name(): string | null /** - * Sort the repository contents by commit time. + * Gets the email on the signature. * - * This sorting mode can be combined with topological sorting. - * 1 << 1 + * Returns `None` if the email is not valid utf-8 */ - Time = 2, + email(): string | null + /** Return the time, in seconds, from epoch */ + when(): number +} +export declare class Tag { /** - * Iterate through the repository contents in reverse order. + * Determine whether a tag name is valid, meaning that (when prefixed with refs/tags/) that + * it is a valid reference name, and that any additional tag name restrictions are imposed + * (eg, it cannot start with a -). + */ + static isValidName(name: string): boolean + /** Get the id (SHA1) of a repository object */ + id(): string + /** + * Get the message of a tag * - * This sorting mode can be combined with any others. - * 1 << 2 + * Returns None if there is no message or if it is not valid utf8 */ - Reverse = 4 + message(): string | null + /** + * Get the message of a tag + * + * Returns None if there is no message + */ + messageBytes(): Buffer | null + /** + * Get the name of a tag + * + * Returns None if it is not valid utf8 + */ + name(): string | null + /** Get the name of a tag */ + nameBytes(): Buffer + /** Recursively peel a tag until a non tag git_object is found */ + peel(): GitObject +} +export declare class Tree { + /** Get the id (SHA1) of a repository object */ + id(): string + /** Get the number of entries listed in a tree. */ + len(): bigint + /** Return `true` if there is not entry */ + isEmpty(): boolean + /** Returns an iterator over the entries in this tree. */ + iter(): TreeIter + /** Lookup a tree entry by SHA value */ + getId(id: string): TreeEntry | null + /** Lookup a tree entry by its position in the tree */ + get(index: number): TreeEntry | null + /** Lookup a tree entry by its filename */ + getName(name: string): TreeEntry | null + /** Lookup a tree entry by its filename */ + getPath(name: string): TreeEntry | null +} +export declare class TreeIter { + [Symbol.iterator](): Iterator +} +export declare class TreeEntry { + /** Get the id of the object pointed by the entry */ + id(): string + /** Get the name of a tree entry */ + name(): string + /** Get the filename of a tree entry */ + nameBytes(): Uint8Array + /** Convert a tree entry to the object it points to. */ + toObject(repo: Repository): GitObject } - diff --git a/index.js b/index.js index 4e93a98..9fb4773 100644 --- a/index.js +++ b/index.js @@ -1,399 +1,351 @@ -// prettier-ignore +/* tslint:disable */ /* eslint-disable */ -/* auto-generated by NAPI-RS */ +/* prettier-ignore */ -const { readFileSync } = require('fs') +/* auto-generated by NAPI-RS */ -let nativeBinding = null -const loadErrors = [] +const { existsSync, readFileSync } = require('fs') +const { join } = require('path') -const isMusl = () => { - let musl = false - if (process.platform === 'linux') { - musl = isMuslFromFilesystem() - if (musl === null) { - musl = isMuslFromReport() - } - if (musl === null) { - musl = isMuslFromChildProcess() - } - } - return musl -} +const { platform, arch } = process -const isFileMusl = (f) => f.includes('libc.musl-') || f.includes('ld-musl-') - -const isMuslFromFilesystem = () => { - try { - return readFileSync('/usr/bin/ldd', 'utf-8').includes('musl') - } catch { - return null - } -} +let nativeBinding = null +let localFileExisted = false +let loadError = null -const isMuslFromReport = () => { - const report = typeof process.report.getReport === 'function' ? process.report.getReport() : null - if (!report) { - return null - } - if (report.header && report.header.glibcVersionRuntime) { - return false - } - if (Array.isArray(report.sharedObjects)) { - if (report.sharedObjects.some(isFileMusl)) { +function isMusl() { + // For Node 10 + if (!process.report || typeof process.report.getReport !== 'function') { + try { + const lddPath = require('child_process').execSync('which ldd').toString().trim() + return readFileSync(lddPath, 'utf8').includes('musl') + } catch (e) { return true } - } - return false -} - -const isMuslFromChildProcess = () => { - try { - return require('child_process').execSync('ldd --version', { encoding: 'utf8' }).includes('musl') - } catch (e) { - // If we reach this case, we don't know if the system is musl or not, so is better to just fallback to false - return false + } else { + const { glibcVersionRuntime } = process.report.getReport().header + return !glibcVersionRuntime } } -function requireNative() { - if (process.platform === 'android') { - if (process.arch === 'arm64') { - try { - return require('./simple-git.android-arm64.node') - } catch (e) { - loadErrors.push(e) - } - try { - return require('@napi-rs/simple-git-android-arm64') - } catch (e) { - loadErrors.push(e) - } - - } else if (process.arch === 'arm') { - try { - return require('./simple-git.android-arm-eabi.node') - } catch (e) { - loadErrors.push(e) - } - try { - return require('@napi-rs/simple-git-android-arm-eabi') - } catch (e) { - loadErrors.push(e) - } - - } else { - loadErrors.push(new Error(`Unsupported architecture on Android ${process.arch}`)) - } - } else if (process.platform === 'win32') { - if (process.arch === 'x64') { - try { - return require('./simple-git.win32-x64-msvc.node') - } catch (e) { - loadErrors.push(e) - } - try { - return require('@napi-rs/simple-git-win32-x64-msvc') - } catch (e) { - loadErrors.push(e) - } - - } else if (process.arch === 'ia32') { - try { - return require('./simple-git.win32-ia32-msvc.node') - } catch (e) { - loadErrors.push(e) - } - try { - return require('@napi-rs/simple-git-win32-ia32-msvc') - } catch (e) { - loadErrors.push(e) - } - - } else if (process.arch === 'arm64') { - try { - return require('./simple-git.win32-arm64-msvc.node') - } catch (e) { - loadErrors.push(e) - } - try { - return require('@napi-rs/simple-git-win32-arm64-msvc') - } catch (e) { - loadErrors.push(e) - } - - } else { - loadErrors.push(new Error(`Unsupported architecture on Windows: ${process.arch}`)) - } - } else if (process.platform === 'darwin') { - try { - return require('./simple-git.darwin-universal.node') - } catch (e) { - loadErrors.push(e) - } - try { - return require('@napi-rs/simple-git-darwin-universal') - } catch (e) { - loadErrors.push(e) - } - - if (process.arch === 'x64') { - try { - return require('./simple-git.darwin-x64.node') - } catch (e) { - loadErrors.push(e) - } - try { - return require('@napi-rs/simple-git-darwin-x64') - } catch (e) { - loadErrors.push(e) - } - - } else if (process.arch === 'arm64') { - try { - return require('./simple-git.darwin-arm64.node') - } catch (e) { - loadErrors.push(e) - } - try { - return require('@napi-rs/simple-git-darwin-arm64') - } catch (e) { - loadErrors.push(e) - } - - } else { - loadErrors.push(new Error(`Unsupported architecture on macOS: ${process.arch}`)) - } - } else if (process.platform === 'freebsd') { - if (process.arch === 'x64') { - try { - return require('./simple-git.freebsd-x64.node') - } catch (e) { - loadErrors.push(e) - } - try { - return require('@napi-rs/simple-git-freebsd-x64') - } catch (e) { - loadErrors.push(e) - } - - } else if (process.arch === 'arm64') { - try { - return require('./simple-git.freebsd-arm64.node') - } catch (e) { - loadErrors.push(e) - } - try { - return require('@napi-rs/simple-git-freebsd-arm64') - } catch (e) { - loadErrors.push(e) - } - - } else { - loadErrors.push(new Error(`Unsupported architecture on FreeBSD: ${process.arch}`)) - } - } else if (process.platform === 'linux') { - if (process.arch === 'x64') { - if (isMusl()) { +switch (platform) { + case 'android': + switch (arch) { + case 'arm64': + localFileExisted = existsSync(join(__dirname, 'simple-git.android-arm64.node')) try { - return require('./simple-git.linux-x64-musl.node') - } catch (e) { - loadErrors.push(e) - } - try { - return require('@napi-rs/simple-git-linux-x64-musl') - } catch (e) { - loadErrors.push(e) - } - - } else { + if (localFileExisted) { + nativeBinding = require('./simple-git.android-arm64.node') + } else { + nativeBinding = require('@napi-rs/simple-git-android-arm64') + } + } catch (e) { + loadError = e + } + break + case 'arm': + localFileExisted = existsSync(join(__dirname, 'simple-git.android-arm-eabi.node')) try { - return require('./simple-git.linux-x64-gnu.node') - } catch (e) { - loadErrors.push(e) - } - try { - return require('@napi-rs/simple-git-linux-x64-gnu') - } catch (e) { - loadErrors.push(e) - } - - } - } else if (process.arch === 'arm64') { - if (isMusl()) { + if (localFileExisted) { + nativeBinding = require('./simple-git.android-arm-eabi.node') + } else { + nativeBinding = require('@napi-rs/simple-git-android-arm-eabi') + } + } catch (e) { + loadError = e + } + break + default: + throw new Error(`Unsupported architecture on Android ${arch}`) + } + break + case 'win32': + switch (arch) { + case 'x64': + localFileExisted = existsSync( + join(__dirname, 'simple-git.win32-x64-msvc.node') + ) try { - return require('./simple-git.linux-arm64-musl.node') - } catch (e) { - loadErrors.push(e) - } - try { - return require('@napi-rs/simple-git-linux-arm64-musl') - } catch (e) { - loadErrors.push(e) - } - - } else { + if (localFileExisted) { + nativeBinding = require('./simple-git.win32-x64-msvc.node') + } else { + nativeBinding = require('@napi-rs/simple-git-win32-x64-msvc') + } + } catch (e) { + loadError = e + } + break + case 'ia32': + localFileExisted = existsSync( + join(__dirname, 'simple-git.win32-ia32-msvc.node') + ) try { - return require('./simple-git.linux-arm64-gnu.node') - } catch (e) { - loadErrors.push(e) - } - try { - return require('@napi-rs/simple-git-linux-arm64-gnu') - } catch (e) { - loadErrors.push(e) - } - - } - } else if (process.arch === 'arm') { - if (isMusl()) { + if (localFileExisted) { + nativeBinding = require('./simple-git.win32-ia32-msvc.node') + } else { + nativeBinding = require('@napi-rs/simple-git-win32-ia32-msvc') + } + } catch (e) { + loadError = e + } + break + case 'arm64': + localFileExisted = existsSync( + join(__dirname, 'simple-git.win32-arm64-msvc.node') + ) try { - return require('./simple-git.linux-arm-musleabihf.node') - } catch (e) { - loadErrors.push(e) - } - try { - return require('@napi-rs/simple-git-linux-arm-musleabihf') - } catch (e) { - loadErrors.push(e) - } - + if (localFileExisted) { + nativeBinding = require('./simple-git.win32-arm64-msvc.node') + } else { + nativeBinding = require('@napi-rs/simple-git-win32-arm64-msvc') + } + } catch (e) { + loadError = e + } + break + default: + throw new Error(`Unsupported architecture on Windows: ${arch}`) + } + break + case 'darwin': + localFileExisted = existsSync(join(__dirname, 'simple-git.darwin-universal.node')) + try { + if (localFileExisted) { + nativeBinding = require('./simple-git.darwin-universal.node') } else { - try { - return require('./simple-git.linux-arm-gnueabihf.node') - } catch (e) { - loadErrors.push(e) - } - try { - return require('@napi-rs/simple-git-linux-arm-gnueabihf') - } catch (e) { - loadErrors.push(e) + nativeBinding = require('@napi-rs/simple-git-darwin-universal') } - - } - } else if (process.arch === 'riscv64') { - if (isMusl()) { + break + } catch {} + switch (arch) { + case 'x64': + localFileExisted = existsSync(join(__dirname, 'simple-git.darwin-x64.node')) try { - return require('./simple-git.linux-riscv64-musl.node') - } catch (e) { - loadErrors.push(e) - } - try { - return require('@napi-rs/simple-git-linux-riscv64-musl') - } catch (e) { - loadErrors.push(e) - } - - } else { + if (localFileExisted) { + nativeBinding = require('./simple-git.darwin-x64.node') + } else { + nativeBinding = require('@napi-rs/simple-git-darwin-x64') + } + } catch (e) { + loadError = e + } + break + case 'arm64': + localFileExisted = existsSync( + join(__dirname, 'simple-git.darwin-arm64.node') + ) try { - return require('./simple-git.linux-riscv64-gnu.node') - } catch (e) { - loadErrors.push(e) - } - try { - return require('@napi-rs/simple-git-linux-riscv64-gnu') - } catch (e) { - loadErrors.push(e) - } - - } - } else if (process.arch === 'ppc64') { - try { - return require('./simple-git.linux-ppc64-gnu.node') - } catch (e) { - loadErrors.push(e) - } - try { - return require('@napi-rs/simple-git-linux-ppc64-gnu') - } catch (e) { - loadErrors.push(e) - } - - } else if (process.arch === 's390x') { - try { - return require('./simple-git.linux-s390x-gnu.node') - } catch (e) { - loadErrors.push(e) - } - try { - return require('@napi-rs/simple-git-linux-s390x-gnu') - } catch (e) { - loadErrors.push(e) - } - - } else { - loadErrors.push(new Error(`Unsupported architecture on Linux: ${process.arch}`)) + if (localFileExisted) { + nativeBinding = require('./simple-git.darwin-arm64.node') + } else { + nativeBinding = require('@napi-rs/simple-git-darwin-arm64') + } + } catch (e) { + loadError = e + } + break + default: + throw new Error(`Unsupported architecture on macOS: ${arch}`) } - } else { - loadErrors.push(new Error(`Unsupported OS: ${process.platform}, architecture: ${process.arch}`)) - } -} - -nativeBinding = requireNative() - -if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) { - try { - nativeBinding = require('./simple-git.wasi.cjs') - } catch (err) { - if (process.env.NAPI_RS_FORCE_WASI) { - console.error(err) + break + case 'freebsd': + if (arch !== 'x64') { + throw new Error(`Unsupported architecture on FreeBSD: ${arch}`) } - } - if (!nativeBinding) { + localFileExisted = existsSync(join(__dirname, 'simple-git.freebsd-x64.node')) try { - nativeBinding = require('@napi-rs/simple-git-wasm32-wasi') - } catch (err) { - if (process.env.NAPI_RS_FORCE_WASI) { - console.error(err) + if (localFileExisted) { + nativeBinding = require('./simple-git.freebsd-x64.node') + } else { + nativeBinding = require('@napi-rs/simple-git-freebsd-x64') } + } catch (e) { + loadError = e } - } + break + case 'linux': + switch (arch) { + case 'x64': + if (isMusl()) { + localFileExisted = existsSync( + join(__dirname, 'simple-git.linux-x64-musl.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./simple-git.linux-x64-musl.node') + } else { + nativeBinding = require('@napi-rs/simple-git-linux-x64-musl') + } + } catch (e) { + loadError = e + } + } else { + localFileExisted = existsSync( + join(__dirname, 'simple-git.linux-x64-gnu.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./simple-git.linux-x64-gnu.node') + } else { + nativeBinding = require('@napi-rs/simple-git-linux-x64-gnu') + } + } catch (e) { + loadError = e + } + } + break + case 'arm64': + if (isMusl()) { + localFileExisted = existsSync( + join(__dirname, 'simple-git.linux-arm64-musl.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./simple-git.linux-arm64-musl.node') + } else { + nativeBinding = require('@napi-rs/simple-git-linux-arm64-musl') + } + } catch (e) { + loadError = e + } + } else { + localFileExisted = existsSync( + join(__dirname, 'simple-git.linux-arm64-gnu.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./simple-git.linux-arm64-gnu.node') + } else { + nativeBinding = require('@napi-rs/simple-git-linux-arm64-gnu') + } + } catch (e) { + loadError = e + } + } + break + case 'arm': + if (isMusl()) { + localFileExisted = existsSync( + join(__dirname, 'simple-git.linux-arm-musleabihf.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./simple-git.linux-arm-musleabihf.node') + } else { + nativeBinding = require('@napi-rs/simple-git-linux-arm-musleabihf') + } + } catch (e) { + loadError = e + } + } else { + localFileExisted = existsSync( + join(__dirname, 'simple-git.linux-arm-gnueabihf.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./simple-git.linux-arm-gnueabihf.node') + } else { + nativeBinding = require('@napi-rs/simple-git-linux-arm-gnueabihf') + } + } catch (e) { + loadError = e + } + } + break + case 'riscv64': + if (isMusl()) { + localFileExisted = existsSync( + join(__dirname, 'simple-git.linux-riscv64-musl.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./simple-git.linux-riscv64-musl.node') + } else { + nativeBinding = require('@napi-rs/simple-git-linux-riscv64-musl') + } + } catch (e) { + loadError = e + } + } else { + localFileExisted = existsSync( + join(__dirname, 'simple-git.linux-riscv64-gnu.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./simple-git.linux-riscv64-gnu.node') + } else { + nativeBinding = require('@napi-rs/simple-git-linux-riscv64-gnu') + } + } catch (e) { + loadError = e + } + } + break + case 's390x': + localFileExisted = existsSync( + join(__dirname, 'simple-git.linux-s390x-gnu.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./simple-git.linux-s390x-gnu.node') + } else { + nativeBinding = require('@napi-rs/simple-git-linux-s390x-gnu') + } + } catch (e) { + loadError = e + } + break + default: + throw new Error(`Unsupported architecture on Linux: ${arch}`) + } + break + default: + throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`) } if (!nativeBinding) { - if (loadErrors.length > 0) { - // TODO Link to documentation with potential fixes - // - The package owner could build/publish bindings for this arch - // - The user may need to bundle the correct files - // - The user may need to re-install node_modules to get new packages - throw new Error('Failed to load native binding', { cause: loadErrors }) + if (loadError) { + throw loadError } throw new Error(`Failed to load native binding`) } -module.exports.Commit = nativeBinding.Commit -module.exports.Cred = nativeBinding.Cred -module.exports.Deltas = nativeBinding.Deltas -module.exports.Diff = nativeBinding.Diff -module.exports.DiffDelta = nativeBinding.DiffDelta -module.exports.DiffFile = nativeBinding.DiffFile -module.exports.FetchOptions = nativeBinding.FetchOptions -module.exports.GitObject = nativeBinding.GitObject -module.exports.ProxyOptions = nativeBinding.ProxyOptions -module.exports.Reference = nativeBinding.Reference -module.exports.Remote = nativeBinding.Remote -module.exports.RemoteCallbacks = nativeBinding.RemoteCallbacks -module.exports.RepoBuilder = nativeBinding.RepoBuilder -module.exports.Repository = nativeBinding.Repository -module.exports.RevWalk = nativeBinding.RevWalk -module.exports.Signature = nativeBinding.Signature -module.exports.Tag = nativeBinding.Tag -module.exports.Tree = nativeBinding.Tree -module.exports.TreeEntry = nativeBinding.TreeEntry -module.exports.TreeIter = nativeBinding.TreeIter -module.exports.AutotagOption = nativeBinding.AutotagOption -module.exports.CloneLocal = nativeBinding.CloneLocal -module.exports.CredentialType = nativeBinding.CredentialType -module.exports.credTypeContains = nativeBinding.credTypeContains -module.exports.Delta = nativeBinding.Delta -module.exports.DiffFlags = nativeBinding.DiffFlags -module.exports.Direction = nativeBinding.Direction -module.exports.FetchPrune = nativeBinding.FetchPrune -module.exports.FileMode = nativeBinding.FileMode -module.exports.ObjectType = nativeBinding.ObjectType -module.exports.ReferenceType = nativeBinding.ReferenceType -module.exports.RemoteRedirect = nativeBinding.RemoteRedirect -module.exports.RemoteUpdateFlags = nativeBinding.RemoteUpdateFlags -module.exports.RepositoryOpenFlags = nativeBinding.RepositoryOpenFlags -module.exports.RepositoryState = nativeBinding.RepositoryState -module.exports.Sort = nativeBinding.Sort +const { Blob, Commit, DiffFlags, FileMode, Deltas, DiffDelta, Delta, DiffFile, Diff, ObjectType, GitObject, Reference, ReferenceType, Direction, FetchPrune, AutotagOption, RemoteRedirect, CredentialType, RemoteUpdateFlags, Remote, RemoteCallbacks, FetchOptions, ProxyOptions, Cred, credTypeContains, RepositoryState, RepositoryOpenFlags, Repository, RepoBuilder, CloneLocal, Sort, RevWalk, Signature, Tag, Tree, TreeIter, TreeEntry } = nativeBinding + +module.exports.Blob = Blob +module.exports.Commit = Commit +module.exports.DiffFlags = DiffFlags +module.exports.FileMode = FileMode +module.exports.Deltas = Deltas +module.exports.DiffDelta = DiffDelta +module.exports.Delta = Delta +module.exports.DiffFile = DiffFile +module.exports.Diff = Diff +module.exports.ObjectType = ObjectType +module.exports.GitObject = GitObject +module.exports.Reference = Reference +module.exports.ReferenceType = ReferenceType +module.exports.Direction = Direction +module.exports.FetchPrune = FetchPrune +module.exports.AutotagOption = AutotagOption +module.exports.RemoteRedirect = RemoteRedirect +module.exports.CredentialType = CredentialType +module.exports.RemoteUpdateFlags = RemoteUpdateFlags +module.exports.Remote = Remote +module.exports.RemoteCallbacks = RemoteCallbacks +module.exports.FetchOptions = FetchOptions +module.exports.ProxyOptions = ProxyOptions +module.exports.Cred = Cred +module.exports.credTypeContains = credTypeContains +module.exports.RepositoryState = RepositoryState +module.exports.RepositoryOpenFlags = RepositoryOpenFlags +module.exports.Repository = Repository +module.exports.RepoBuilder = RepoBuilder +module.exports.CloneLocal = CloneLocal +module.exports.Sort = Sort +module.exports.RevWalk = RevWalk +module.exports.Signature = Signature +module.exports.Tag = Tag +module.exports.Tree = Tree +module.exports.TreeIter = TreeIter +module.exports.TreeEntry = TreeEntry diff --git a/src/blob.rs b/src/blob.rs new file mode 100644 index 0000000..38edf95 --- /dev/null +++ b/src/blob.rs @@ -0,0 +1,52 @@ +use std::ops::Deref; + +use napi::bindgen_prelude::{SharedReference, Uint8Array}; +use napi_derive::napi; + +use crate::object::GitObject; + +pub(crate) enum BlobParent { + GitObject(SharedReference>), +} + +impl Deref for BlobParent { + type Target = git2::Blob<'static>; + + fn deref(&self) -> &git2::Blob<'static> { + match self { + BlobParent::GitObject(parent) => parent.deref(), + } + } +} + +#[napi] +pub struct Blob { + pub(crate) inner: BlobParent, +} + +#[napi] +impl Blob { + #[napi] + /// Get the id (SHA1) of a repository blob + pub fn id(&self) -> String { + self.inner.id().to_string() + } + + #[napi] + /// Determine if the blob content is most certainly binary or not. + pub fn is_binary(&self) -> bool { + self.inner.is_binary() + } + + #[napi] + /// Get the content of this blob. + pub fn content(&self) -> Uint8Array { + self.inner.content().to_vec().into() + } + + #[napi] + /// Get the size in bytes of the contents of this blob. + pub fn size(&self) -> u64 { + self.inner.size() as u64 + } +} diff --git a/src/lib.rs b/src/lib.rs index 9e72767..1a786c1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ #![deny(clippy::all)] +pub mod blob; pub mod commit; pub mod deltas; pub mod diff; diff --git a/src/object.rs b/src/object.rs index 71f8f2a..d72d6eb 100644 --- a/src/object.rs +++ b/src/object.rs @@ -3,7 +3,11 @@ use std::ops::Deref; use napi::bindgen_prelude::*; use napi_derive::napi; -use crate::{error::IntoNapiError, repo::Repository}; +use crate::{ + blob::{Blob, BlobParent}, + error::IntoNapiError, + repo::Repository, +}; #[napi] pub enum ObjectType { @@ -89,4 +93,15 @@ impl GitObject { inner: ObjectParent::Object(self.inner.peel(kind.into()).convert("Peel object failed")?), }) } + + #[napi] + /// Recursively peel an object until a blob is found + pub fn peel_to_blob(&self, env: Env, self_ref: Reference) -> Result { + let blob = self_ref.share_with(env, |obj| { + obj.inner.peel_to_blob().convert_without_message() + })?; + Ok(Blob { + inner: BlobParent::GitObject(blob), + }) + } } diff --git a/src/tree.rs b/src/tree.rs index 1488ad9..53f4484 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -1,8 +1,17 @@ use std::ops::Deref; +use std::path::Path; -use napi::bindgen_prelude::{Buffer, Env, Error, Generator, Reference, Result, SharedReference}; +use napi::bindgen_prelude::{ + Env, Error, Generator, Reference, Result, SharedReference, Uint8Array, +}; use napi_derive::napi; +use crate::{ + error::IntoNapiError, + object::{GitObject, ObjectParent}, + repo::Repository, +}; + pub(crate) enum TreeParent { Repository(SharedReference>), Reference(SharedReference>), @@ -49,6 +58,76 @@ impl Tree { inner: this_ref.share_with(env, |tree| Ok(tree.inner().iter()))?, }) } + + #[napi] + /// Lookup a tree entry by SHA value + pub fn get_id(&self, this_ref: Reference, env: Env, id: String) -> Option { + let reference = this_ref + .share_with(env, |tree| { + if let Some(entry) = tree + .inner() + .get_id(git2::Oid::from_str(&id).convert_without_message()?) + { + Ok(entry) + } else { + Err(Error::new(napi::Status::InvalidArg, "Tree entry not found")) + } + }) + .ok()?; + Some(TreeEntry { + inner: TreeEntryInner::Ref(reference), + }) + } + + #[napi] + /// Lookup a tree entry by its position in the tree + pub fn get(&self, this_ref: Reference, env: Env, index: u32) -> Option { + let reference = this_ref + .share_with(env, |tree| { + if let Some(entry) = tree.inner().get(index as usize) { + Ok(entry) + } else { + Err(Error::new(napi::Status::InvalidArg, "Tree entry not found")) + } + }) + .ok()?; + Some(TreeEntry { + inner: TreeEntryInner::Ref(reference), + }) + } + + #[napi] + /// Lookup a tree entry by its filename + pub fn get_name(&self, this_ref: Reference, env: Env, name: String) -> Option { + let reference = this_ref + .share_with(env, |tree| { + if let Some(entry) = tree.inner().get_name(&name) { + Ok(entry) + } else { + Err(Error::new(napi::Status::InvalidArg, "Tree entry not found")) + } + }) + .ok()?; + Some(TreeEntry { + inner: TreeEntryInner::Ref(reference), + }) + } + + #[napi] + /// Lookup a tree entry by its filename + pub fn get_path(&self, this_ref: Reference, env: Env, name: String) -> Option { + let reference = this_ref + .share_with(env, |tree| { + tree + .inner() + .get_path(Path::new(&name)) + .convert_without_message() + }) + .ok()?; + Some(TreeEntry { + inner: TreeEntryInner::Ref(reference), + }) + } } impl<'a> AsRef> for Tree { @@ -73,13 +152,31 @@ impl Generator for TreeIter { type Next = (); fn next(&mut self, _value: Option<()>) -> Option { - self.inner.next().map(|e| TreeEntry { inner: e }) + self.inner.next().map(|e| TreeEntry { + inner: TreeEntryInner::Owned(e), + }) } } +pub(crate) enum TreeEntryInner { + Owned(git2::TreeEntry<'static>), + Ref(SharedReference>), +} + #[napi] pub struct TreeEntry { - pub(crate) inner: git2::TreeEntry<'static>, + pub(crate) inner: TreeEntryInner, +} + +impl Deref for TreeEntryInner { + type Target = git2::TreeEntry<'static>; + + fn deref(&self) -> &Self::Target { + match &self { + TreeEntryInner::Owned(entry) => entry, + TreeEntryInner::Ref(entry) => entry.deref(), + } + } } #[napi] @@ -101,7 +198,18 @@ impl TreeEntry { #[napi] /// Get the filename of a tree entry - pub fn name_bytes(&self) -> Buffer { + pub fn name_bytes(&self) -> Uint8Array { self.inner.name_bytes().to_vec().into() } + + #[napi] + /// Convert a tree entry to the object it points to. + pub fn to_object(&self, env: Env, repo: Reference) -> Result { + let object = repo.share_with(env, |repo| { + self.inner.to_object(&repo.inner).convert_without_message() + })?; + Ok(GitObject { + inner: ObjectParent::Repository(object), + }) + } }