From 87a0f9b7a3b75f9efe1cbfdad212c48906d28626 Mon Sep 17 00:00:00 2001 From: faza Date: Tue, 27 Oct 2020 17:35:42 +0530 Subject: [PATCH 1/8] Fix getOidByRef method and cleanup codebase --- index.js | 2552 +++++++++--------------------- package-lock.json | 207 +-- package.json | 3 +- src/api/cloneFromArweave.js | 113 -- src/api/fetchFromArweave.js | 127 -- src/api/pushToArweave.js | 107 -- src/commands/cloneFromArweave.js | 109 -- src/commands/fetchFromArweave.js | 160 -- src/commands/pushToArweave.js | 253 --- src/index.js | 12 - src/utils/arweave.js | 294 ++-- src/utils/graphql.js | 77 - 12 files changed, 890 insertions(+), 3124 deletions(-) delete mode 100644 src/api/cloneFromArweave.js delete mode 100644 src/api/fetchFromArweave.js delete mode 100644 src/api/pushToArweave.js delete mode 100644 src/commands/cloneFromArweave.js delete mode 100644 src/commands/fetchFromArweave.js delete mode 100644 src/commands/pushToArweave.js delete mode 100644 src/utils/graphql.js diff --git a/index.js b/index.js index 19fbb0cb8..2df82fc26 100644 --- a/index.js +++ b/index.js @@ -5,8 +5,6 @@ import pako from 'pako'; import ignore from 'ignore'; import pify from 'pify'; import cleanGitRef from 'clean-git-ref'; -import { readContract, selectWeightedPstHolder } from 'smartweave'; -import axios from 'axios'; import diff3Merge from 'diff3'; /** @@ -7603,619 +7601,298 @@ async function clone({ } } -const graphQlEndpoint = 'https://arweave.net/graphql'; - -const getOidByRef = async (arweave, remoteURI, ref) => { - const { repoOwnerAddress, repoName } = parseArgitRemoteURI(remoteURI); - const { data } = await axios({ - url: graphQlEndpoint, - method: 'post', - data: { - query: ` - query { - transactions( - owners: ["${repoOwnerAddress}"] - tags: [ - { name: "Repo", values: ["${repoName}"] } - { name: "ref", values: ["${ref}"] } - { name: "Type", values: ["update-ref"] } - { name: "App-Name", values: ["dgit"] } - ] - first: 1 - ) { - edges { - node { - id - } - } - } - }`, - }, - }); - - const edges = data.data.transactions.edges; - - if (edges.length === 0) { - return '0000000000000000000000000000000000000000' - } - - const id = edges[0].node.id; - return await arweave.transactions.getData(id, { - decode: true, - string: true, - }) -}; +// @ts-check -const getTransactionIdByObjectId = async (remoteURI, oid) => { - const { repoOwnerAddress, repoName } = parseArgitRemoteURI(remoteURI); - const { data } = await axios({ - url: graphQlEndpoint, - method: 'post', - data: { - query: ` - query { - transactions( - owners: ["${repoOwnerAddress}"] - tags: [ - { name: "oid", values: ["${oid}"] } - { name: "Repo", values: ["${repoName}"] } - { name: "Type", values: ["push-git-object"] } - { name: "App-Name", values: ["dgit"] } - ] - first: 1 - ) { - edges { - node { - id - } - } - } - }`, - }, - }); +/** + * Create a new commit + * + * @param {Object} args + * @param {FsClient} args.fs - a file system implementation + * @param {SignCallback} [args.onSign] - a PGP signing implementation + * @param {string} [args.dir] - The [working tree](dir-vs-gitdir.md) directory path + * @param {string} [args.gitdir=join(dir,'.git')] - [required] The [git directory](dir-vs-gitdir.md) path + * @param {string} args.message - The commit message to use. + * @param {Object} [args.author] - The details about the author. + * @param {string} [args.author.name] - Default is `user.name` config. + * @param {string} [args.author.email] - Default is `user.email` config. + * @param {number} [args.author.timestamp=Math.floor(Date.now()/1000)] - Set the author timestamp field. This is the integer number of seconds since the Unix epoch (1970-01-01 00:00:00). + * @param {number} [args.author.timezoneOffset] - Set the author timezone offset field. This is the difference, in minutes, from the current timezone to UTC. Default is `(new Date()).getTimezoneOffset()`. + * @param {Object} [args.committer = author] - The details about the commit committer, in the same format as the author parameter. If not specified, the author details are used. + * @param {string} [args.committer.name] - Default is `user.name` config. + * @param {string} [args.committer.email] - Default is `user.email` config. + * @param {number} [args.committer.timestamp=Math.floor(Date.now()/1000)] - Set the committer timestamp field. This is the integer number of seconds since the Unix epoch (1970-01-01 00:00:00). + * @param {number} [args.committer.timezoneOffset] - Set the committer timezone offset field. This is the difference, in minutes, from the current timezone to UTC. Default is `(new Date()).getTimezoneOffset()`. + * @param {string} [args.signingKey] - Sign the tag object using this private PGP key. + * @param {boolean} [args.dryRun = false] - If true, simulates making a commit so you can test whether it would succeed. Implies `noUpdateBranch`. + * @param {boolean} [args.noUpdateBranch = false] - If true, does not update the branch pointer after creating the commit. + * @param {string} [args.ref] - The fully expanded name of the branch to commit to. Default is the current branch pointed to by HEAD. (TODO: fix it so it can expand branch names without throwing if the branch doesn't exist yet.) + * @param {string[]} [args.parent] - The SHA-1 object ids of the commits to use as parents. If not specified, the commit pointed to by `ref` is used. + * @param {string} [args.tree] - The SHA-1 object id of the tree to use. If not specified, a new tree object is created from the current git index. + * + * @returns {Promise} Resolves successfully with the SHA-1 object id of the newly created commit. + * + * @example + * let sha = await git.commit({ + * fs, + * dir: '/tutorial', + * author: { + * name: 'Mr. Test', + * email: 'mrtest@example.com', + * }, + * message: 'Added the a.txt file' + * }) + * console.log(sha) + * + */ +async function commit({ + fs: _fs, + onSign, + dir, + gitdir = join(dir, '.git'), + message, + author: _author, + committer: _committer, + signingKey, + dryRun = false, + noUpdateBranch = false, + ref, + parent, + tree, +}) { + try { + assertParameter('fs', _fs); + assertParameter('message', message); + if (signingKey) { + assertParameter('onSign', onSign); + } + const fs = new FileSystem(_fs); + const cache = {}; - const edges = data.data.transactions.edges; - return edges[0].node.id -}; + const author = await normalizeAuthorObject({ fs, gitdir, author: _author }); + if (!author) throw new MissingNameError('author') -// prettier-ignore -const argitRemoteURIRegex = '^dgit:\/\/([a-zA-Z0-9-_]{43})\/([A-Za-z0-9_.-]*)'; -const contractId = 'N9Vfr_3Rw95111UJ6eaT7scGZzDCd2zzpja890758Qc'; + const committer = await normalizeCommitterObject({ + fs, + gitdir, + author, + committer: _committer, + }); + if (!committer) throw new MissingNameError('committer') -const repoQuery = remoteURI => { - const { repoOwnerAddress, repoName } = parseArgitRemoteURI(remoteURI); - return { - op: 'and', - expr1: { - op: 'and', - expr1: { - op: 'equals', - expr1: 'App-Name', - expr2: 'dgit', - }, - expr2: { - op: 'equals', - expr1: 'from', - expr2: repoOwnerAddress, - }, - }, - expr2: { op: 'equals', expr1: 'Repo', expr2: repoName }, + return await _commit({ + fs, + cache, + onSign, + gitdir, + message, + author, + committer, + signingKey, + dryRun, + noUpdateBranch, + ref, + parent, + tree, + }) + } catch (err) { + err.caller = 'git.commit'; + throw err } -}; - -function parseArgitRemoteURI(remoteURI) { - const matchGroups = remoteURI.match(argitRemoteURIRegex); - const repoOwnerAddress = matchGroups[1]; - const repoName = matchGroups[2]; - - return { repoOwnerAddress, repoName } } -function addTransactionTags(tx, repo, txType) { - tx.addTag('Repo', repo); - tx.addTag('Type', txType); - tx.addTag('Content-Type', 'application/json'); - tx.addTag('App-Name', 'dgit'); - tx.addTag('version', '0.0.1'); - tx.addTag('Unix-Time', Math.round(new Date().getTime() / 1000)); // Add Unix timestamp - return tx -} - -async function updateRef(arweave, wallet, remoteURI, name, ref) { - const { repoName } = parseArgitRemoteURI(remoteURI); - let tx = await arweave.createTransaction({ data: ref }, wallet); - tx = addTransactionTags(tx, repoName, 'update-ref'); - tx.addTag('ref', name); +// @ts-check - await arweave.transactions.sign(tx, wallet); // Sign transaction - arweave.transactions.post(tx); // Post transaction +/** + * Get the name of the branch currently pointed to by .git/HEAD + * + * @param {Object} args + * @param {FsClient} args.fs - a file system implementation + * @param {string} [args.dir] - The [working tree](dir-vs-gitdir.md) directory path + * @param {string} [args.gitdir=join(dir,'.git')] - [required] The [git directory](dir-vs-gitdir.md) path + * @param {boolean} [args.fullname = false] - Return the full path (e.g. "refs/heads/main") instead of the abbreviated form. + * @param {boolean} [args.test = false] - If the current branch doesn't actually exist (such as right after git init) then return `undefined`. + * + * @returns {Promise} The name of the current branch or undefined if the HEAD is detached. + * + * @example + * // Get the current branch name + * let branch = await git.currentBranch({ + * fs, + * dir: '/tutorial', + * fullname: false + * }) + * console.log(branch) + * + */ +async function currentBranch({ + fs, + dir, + gitdir = join(dir, '.git'), + fullname = false, + test = false, +}) { + try { + assertParameter('fs', fs); + assertParameter('gitdir', gitdir); + return await _currentBranch({ + fs: new FileSystem(fs), + gitdir, + fullname, + test, + }) + } catch (err) { + err.caller = 'git.currentBranch'; + throw err + } } -async function pushPackfile( - arweave, - wallet, - remoteURI, - oldoid, - oid, - packfile -) { - const { repoName } = parseArgitRemoteURI(remoteURI); - - let tx = await arweave.createTransaction({ data: packfile.packfile }, wallet); - tx = addTransactionTags(tx, repoName, 'send-pack'); - tx.addTag('oid', oid); - tx.addTag('oldoid', oldoid); - tx.addTag('filename', packfile.filename); - - await arweave.transactions.sign(tx, wallet); - let uploader = await arweave.transactions.getUploader(tx); +// @ts-check - while (!uploader.isComplete) { - await uploader.uploadChunk(); - console.log( - `${uploader.pctComplete}% complete, ${uploader.uploadedChunks}/${uploader.totalChunks}` - ); +/** + * @param {Object} args + * @param {import('../models/FileSystem.js').FileSystem} args.fs + * @param {string} args.gitdir + * @param {string} args.ref + * + * @returns {Promise} + */ +async function _deleteBranch({ fs, gitdir, ref }) { + const exist = await GitRefManager.exists({ fs, gitdir, ref }); + if (!exist) { + throw new NotFoundError(ref) } - // Send fee to PST holders - const contractState = await readContract(arweave, contractId); - const holder = selectWeightedPstHolder(contractState.balances); - // send a fee. You should inform the user about this fee and amount. - const pstTx = await arweave.createTransaction( - { target: holder, quantity: arweave.ar.arToWinston('0.01') }, - wallet - ); - pstTx.addTag('App-Name', 'dgit'); - pstTx.addTag('version', '0.0.1'); - - await arweave.transactions.sign(pstTx, wallet); - await arweave.transactions.post(pstTx); -} - -async function fetchPackfiles(arweave, remoteURI) { - const query = { - op: 'and', - expr1: repoQuery(remoteURI), - expr2: { op: 'equals', expr1: 'Type', expr2: 'send-pack' }, - }; - const txids = await arweave.arql(query); - const packfiles = await Promise.all( - txids.map(async txid => { - const tx = await arweave.transactions.get(txid); - let filename = ''; - tx.get('tags').forEach(tag => { - const key = tag.get('name', { decode: true, string: true }); - const value = tag.get('value', { decode: true, string: true }); - if (key === 'filename') filename = value; - }); - const data = await arweave.transactions.getData(txid, { decode: true }); - return { data, filename } - }) - ); - return packfiles -} - -async function fetchGitObject(arweave, remoteURI, oid) { - const id = await getTransactionIdByObjectId(remoteURI, oid); - return await arweave.transactions.getData(id, { decode: true }) -} + const fullRef = await GitRefManager.expand({ fs, gitdir, ref }); + const currentRef = await _currentBranch({ fs, gitdir, fullname: true }); + if (fullRef === currentRef) { + // detach HEAD + const value = await GitRefManager.resolve({ fs, gitdir, ref: fullRef }); + await GitRefManager.writeRef({ fs, gitdir, ref: 'HEAD', value }); + } -async function fetchGitObjects(arweave, remoteURI) { - const query = { - op: 'and', - expr1: repoQuery(remoteURI), - expr2: { op: 'equals', expr1: 'Type', expr2: 'push-git-object' }, - }; - const txids = await arweave.arql(query); - const objects = await Promise.all( - txids.map(async txid => { - const tx = await arweave.transactions.get(txid); - let oid = ''; - tx.get('tags').forEach(tag => { - const key = tag.get('name', { decode: true, string: true }); - const value = tag.get('value', { decode: true, string: true }); - if (key === 'oid') oid = value; - }); - const data = await arweave.transactions.getData(txid, { decode: true }); - return { data, oid } - }) - ); - return objects + // Delete a specified branch + await GitRefManager.deleteRef({ fs, gitdir, ref: fullRef }); } -async function getRefsOnArweave(arweave, remoteURI) { - const refs = new Map(); - const query = { - op: 'and', - expr1: repoQuery(remoteURI), - expr2: { op: 'equals', expr1: 'Type', expr2: 'update-ref' }, - }; - const txids = await arweave.arql(query); - const tx_rows = await Promise.all( - txids.map(async txid => { - let ref = {}; - const tx = await arweave.transactions.get(txid); - tx.get('tags').forEach(tag => { - const key = tag.get('name', { decode: true, string: true }); - const value = tag.get('value', { decode: true, string: true }); - if (key === 'Unix-Time') ref.unixTime = value; - else if (key === 'ref') ref.name = value; - }); - - ref.oid = await arweave.transactions.getData(txid, { - decode: true, - string: true, - }); +// @ts-check - return ref +/** + * Delete a local branch + * + * > Note: This only deletes loose branches - it should be fixed in the future to delete packed branches as well. + * + * @param {Object} args + * @param {FsClient} args.fs - a file system implementation + * @param {string} [args.dir] - The [working tree](dir-vs-gitdir.md) directory path + * @param {string} [args.gitdir=join(dir,'.git')] - [required] The [git directory](dir-vs-gitdir.md) path + * @param {string} args.ref - The branch to delete + * + * @returns {Promise} Resolves successfully when filesystem operations are complete + * + * @example + * await git.deleteBranch({ fs, dir: '/tutorial', ref: 'local-branch' }) + * console.log('done') + * + */ +async function deleteBranch({ + fs, + dir, + gitdir = join(dir, '.git'), + ref, +}) { + try { + assertParameter('fs', fs); + assertParameter('ref', ref); + return await _deleteBranch({ + fs: new FileSystem(fs), + gitdir, + ref, }) - ); - - // descending order - tx_rows.sort((a, b) => { - Number(b.unixTime) - Number(a.unixTime); - }); - - tx_rows.forEach(ref => { - if (!refs.has(ref.name)) refs.set(ref.name, ref.oid); - }); - - return refs + } catch (err) { + err.caller = 'git.deleteBranch'; + throw err + } } -var Arweave = /*#__PURE__*/Object.freeze({ - __proto__: null, - parseArgitRemoteURI: parseArgitRemoteURI, - updateRef: updateRef, - pushPackfile: pushPackfile, - fetchPackfiles: fetchPackfiles, - fetchGitObject: fetchGitObject, - fetchGitObjects: fetchGitObjects, - getRefsOnArweave: getRefsOnArweave -}); - // @ts-check /** + * Delete a local ref * - * @typedef {object} FetchResult - The object returned has the following schema: - * @property {string | null} defaultBranch - The branch that is cloned if no branch is specified - * @property {string | null} fetchHead - The SHA-1 object id of the fetched head commit - * @property {string | null} fetchHeadDescription - a textual description of the branch that was fetched - * @property {Object} [headers] - The HTTP response headers returned by the git server - * @property {string[]} [pruned] - A list of branches that were pruned, if you provided the `prune` parameter + * @param {Object} args + * @param {FsClient} args.fs - a file system implementation + * @param {string} [args.dir] - The [working tree](dir-vs-gitdir.md) directory path + * @param {string} [args.gitdir=join(dir,'.git')] - [required] The [git directory](dir-vs-gitdir.md) path + * @param {string} args.ref - The ref to delete * - */ - -/** - * @param {object} args - * @param {import('../models/FileSystem.js').FileSystem} args.fs - * @param {import { fetch } from '../../index.d'; -HttpClient} args.http - * @param {ProgressCallback} [args.onProgress] - * @param {MessageCallback} [args.onMessage] - * @param {AuthCallback} [args.onAuth] - * @param {AuthFailureCallback} [args.onAuthFailure] - * @param {AuthSuccessCallback} [args.onAuthSuccess] - * @param {string} args.gitdir - * @param {string|void} [args.url] - * @param {string} [args.corsProxy] - * @param {string} [args.ref] - * @param {string} [args.remoteRef] - * @param {string} [args.remote] - * @param {boolean} [args.singleBranch = false] - * @param {boolean} [args.tags = false] - * @param {number} [args.depth] - * @param {Date} [args.since] - * @param {string[]} [args.exclude = []] - * @param {boolean} [args.relative = false] - * @param {Object} [args.headers] - * @param {boolean} [args.prune] - * @param {boolean} [args.pruneTags] - * @param {Arweave} [args.arweave] + * @returns {Promise} Resolves successfully when filesystem operations are complete + * + * @example + * await git.deleteRef({ fs, dir: '/tutorial', ref: 'refs/tags/test-tag' }) + * console.log('done') * - * @returns {Promise} - * @see FetchResult */ -async function _fetchFromArweave({ - fs, - http, - onProgress, - onMessage, - onAuth, - onAuthSuccess, - onAuthFailure, - gitdir, - ref: _ref, - remoteRef: _remoteRef, - remote: _remote, - url: _url, - corsProxy, - depth = null, - since = null, - exclude = [], - relative = false, - tags = false, - singleBranch = false, - headers = {}, - prune = false, - pruneTags = false, - arweave, -}) { - const ref = _ref || (await _currentBranch({ fs, gitdir, test: true })); - const config = await GitConfigManager.get({ fs, gitdir }); - // Figure out what remote to use. - const remote = - _remote || (ref && (await config.get(`branch.${ref}.remote`))) || 'origin'; - // Lookup the URL for the given remote. - const url = _url || (await config.get(`remote.${remote}.url`)); - if (typeof url === 'undefined') { - throw new MissingParameterError('remote OR url') - } - // Figure out what remote ref to use. - const remoteRef = - _remoteRef || - (ref && (await config.get(`branch.${ref}.merge`))) || - _ref || - 'master'; - - if (corsProxy === undefined) { - corsProxy = await config.get('http.corsProxy'); - } - - const remoteRefs = await getRefsOnArweave(arweave, url); - // For the special case of an empty repository with no refs, return null. - if (remoteRefs.size === 0) { - return { - defaultBranch: null, - fetchHead: null, - fetchHeadDescription: null, - } - } - - // Figure out the SHA for the requested ref - const { oid, fullref } = GitRefManager.resolveAgainstMap({ - ref: remoteRef, - map: remoteRefs, - }); - - const symrefs = new Map(); - await GitRefManager.updateRemoteRefs({ - fs, - gitdir, - remote, - refs: remoteRefs, - symrefs, - tags, - prune, - }); - - const objects = await fetchGitObjects(arweave, url); - - // Write objects - await Promise.all( - objects.map(async object => { - const subdirectory = object.oid.substring(0, 2); - const filename = object.oid.substring(2); - const objectPath = `objects/${subdirectory}/${filename}`; - const fullpath = join(gitdir, objectPath); - await fs.write(fullpath, object.data); - }) - ); - - const noun = fullref.startsWith('refs/tags') ? 'tag' : 'branch'; - return { - defaultBranch: fullref, - fetchHead: oid, - fetchHeadDescription: `${noun} '${abbreviateRef(fullref)}' of ${url}`, +async function deleteRef({ fs, dir, gitdir = join(dir, '.git'), ref }) { + try { + assertParameter('fs', fs); + assertParameter('ref', ref); + await GitRefManager.deleteRef({ fs: new FileSystem(fs), gitdir, ref }); + } catch (err) { + err.caller = 'git.deleteRef'; + throw err } } // @ts-check + /** - * @param {object} args + * @param {Object} args * @param {import('../models/FileSystem.js').FileSystem} args.fs - * @param {object} args.cache - * @param {HttpClient} args.http - * @param {ProgressCallback} [args.onProgress] - * @param {MessageCallback} [args.onMessage] - * @param {AuthCallback} [args.onAuth] - * @param {AuthFailureCallback} [args.onAuthFailure] - * @param {AuthSuccessCallback} [args.onAuthSuccess] - * @param {string} [args.dir] * @param {string} args.gitdir - * @param {string} args.url - * @param {string} args.corsProxy - * @param {string} args.ref - * @param {boolean} args.singleBranch - * @param {boolean} args.noCheckout - * @param {boolean} args.noTags * @param {string} args.remote - * @param {number} args.depth - * @param {Date} args.since - * @param {string[]} args.exclude - * @param {boolean} args.relative - * @param {Object} args.headers - * @param {Arweave} args.arweave - * - * @returns {Promise} Resolves successfully when clone completes * + * @returns {Promise} */ -async function _cloneFromArweave({ - fs, - cache, - http, - onProgress, - onMessage, - onAuth, - onAuthSuccess, - onAuthFailure, - dir, - gitdir, - url, - corsProxy, - ref, - remote, - depth, - since, - exclude, - relative, - singleBranch, - noCheckout, - noTags, - headers, - arweave, -}) { - const { repoName } = parseArgitRemoteURI(url); - dir = dir !== '.' ? dir : repoName; - gitdir = join(dir, '.git'); - await _init({ fs, gitdir }); - await _addRemote({ fs, gitdir, remote, url, force: false }); - if (corsProxy) { - const config = await GitConfigManager.get({ fs, gitdir }); - await config.set(`http.corsProxy`, corsProxy); - await GitConfigManager.save({ fs, gitdir, config }); - } - const { defaultBranch, fetchHead } = await _fetchFromArweave({ - fs, - http, - onProgress, - onMessage, - onAuth, - onAuthSuccess, - onAuthFailure, - gitdir, - ref, - remote, - depth, - since, - exclude, - relative, - singleBranch, - headers, - tags: !noTags, - arweave, - }); - if (fetchHead === null) return - ref = ref || defaultBranch; - ref = ref.replace('refs/heads/', ''); - // Checkout that branch - await _checkout({ - fs, - cache, - onProgress, - dir, - gitdir, - ref, - remote, - noCheckout, - }); +async function _deleteRemote({ fs, gitdir, remote }) { + const config = await GitConfigManager.get({ fs, gitdir }); + await config.deleteSection('remote', remote); + await GitConfigManager.save({ fs, gitdir, config }); } // @ts-check /** - * Clone a repository + * Removes the local config entry for a given remote * - * @param {object} args + * @param {Object} args * @param {FsClient} args.fs - a file system implementation - * @param {HttpClient} args.http - an HTTP client - * @param {ProgressCallback} [args.onProgress] - optional progress event callback - * @param {MessageCallback} [args.onMessage] - optional message event callback - * @param {AuthCallback} [args.onAuth] - optional auth fill callback - * @param {AuthFailureCallback} [args.onAuthFailure] - optional auth rejected callback - * @param {AuthSuccessCallback} [args.onAuthSuccess] - optional auth approved callback - * @param {string} args.dir - The [working tree](dir-vs-gitdir.md) directory path + * @param {string} [args.dir] - The [working tree](dir-vs-gitdir.md) directory path * @param {string} [args.gitdir=join(dir,'.git')] - [required] The [git directory](dir-vs-gitdir.md) path - * @param {string} args.url - The URL of the remote repository - * @param {string} [args.corsProxy] - Optional [CORS proxy](https://www.npmjs.com/%40isomorphic-git/cors-proxy). Value is stored in the git config file for that repo. - * @param {string} [args.ref] - Which branch to checkout. By default this is the designated "main branch" of the repository. - * @param {boolean} [args.singleBranch = false] - Instead of the default behavior of fetching all the branches, only fetch a single branch. - * @param {boolean} [args.noCheckout = false] - If true, clone will only fetch the repo, not check out a branch. Skipping checkout can save a lot of time normally spent writing files to disk. - * @param {boolean} [args.noTags = false] - By default clone will fetch all tags. `noTags` disables that behavior. - * @param {string} [args.remote = 'origin'] - What to name the remote that is created. - * @param {number} [args.depth] - Integer. Determines how much of the git repository's history to retrieve - * @param {Date} [args.since] - Only fetch commits created after the given date. Mutually exclusive with `depth`. - * @param {string[]} [args.exclude = []] - A list of branches or tags. Instructs the remote server not to send us any commits reachable from these refs. - * @param {boolean} [args.relative = false] - Changes the meaning of `depth` to be measured from the current shallow depth rather than from the branch tip. - * @param {Object} [args.headers = {}] - Additional headers to include in HTTP requests, similar to git's `extraHeader` config - * @param {Arweave} [args.arweave] + * @param {string} args.remote - The name of the remote to delete * - * @returns {Promise} Resolves successfully when clone completes + * @returns {Promise} Resolves successfully when filesystem operations are complete * * @example - * await git.clone({ - * fs, - * http, - * dir: '/tutorial', - * corsProxy: 'https://cors.isomorphic-git.org', - * url: 'https://github.com/isomorphic-git/isomorphic-git', - * singleBranch: true, - * depth: 1 - * }) + * await git.deleteRemote({ fs, dir: '/tutorial', remote: 'upstream' }) * console.log('done') * */ -async function cloneFromArweave({ +async function deleteRemote({ fs, - http, - onProgress, - onMessage, - onAuth, - onAuthSuccess, - onAuthFailure, dir, gitdir = join(dir, '.git'), - url, - corsProxy = undefined, - ref = undefined, - remote = 'origin', - depth = undefined, - since = undefined, - exclude = [], - relative = false, - singleBranch = false, - noCheckout = false, - noTags = false, - headers = {}, - arweave, + remote, }) { try { assertParameter('fs', fs); - // assertParameter('http', http) - assertParameter('gitdir', gitdir); - if (!noCheckout) { - assertParameter('dir', dir); - } - assertParameter('url', url); - - return await _cloneFromArweave({ + assertParameter('remote', remote); + return await _deleteRemote({ fs: new FileSystem(fs), - cache: {}, - http, - onProgress, - onMessage, - onAuth, - onAuthSuccess, - onAuthFailure, - dir, gitdir, - url, - corsProxy, - ref, remote, - depth, - since, - exclude, - relative, - singleBranch, - noCheckout, - noTags, - headers, - arweave, }) } catch (err) { - err.caller = 'git.cloneFromArweave'; + err.caller = 'git.deleteRemote'; throw err } } @@ -8223,423 +7900,127 @@ async function cloneFromArweave({ // @ts-check /** - * Create a new commit + * Delete a local tag ref * * @param {Object} args - * @param {FsClient} args.fs - a file system implementation - * @param {SignCallback} [args.onSign] - a PGP signing implementation - * @param {string} [args.dir] - The [working tree](dir-vs-gitdir.md) directory path - * @param {string} [args.gitdir=join(dir,'.git')] - [required] The [git directory](dir-vs-gitdir.md) path - * @param {string} args.message - The commit message to use. - * @param {Object} [args.author] - The details about the author. - * @param {string} [args.author.name] - Default is `user.name` config. - * @param {string} [args.author.email] - Default is `user.email` config. - * @param {number} [args.author.timestamp=Math.floor(Date.now()/1000)] - Set the author timestamp field. This is the integer number of seconds since the Unix epoch (1970-01-01 00:00:00). - * @param {number} [args.author.timezoneOffset] - Set the author timezone offset field. This is the difference, in minutes, from the current timezone to UTC. Default is `(new Date()).getTimezoneOffset()`. - * @param {Object} [args.committer = author] - The details about the commit committer, in the same format as the author parameter. If not specified, the author details are used. - * @param {string} [args.committer.name] - Default is `user.name` config. - * @param {string} [args.committer.email] - Default is `user.email` config. - * @param {number} [args.committer.timestamp=Math.floor(Date.now()/1000)] - Set the committer timestamp field. This is the integer number of seconds since the Unix epoch (1970-01-01 00:00:00). - * @param {number} [args.committer.timezoneOffset] - Set the committer timezone offset field. This is the difference, in minutes, from the current timezone to UTC. Default is `(new Date()).getTimezoneOffset()`. - * @param {string} [args.signingKey] - Sign the tag object using this private PGP key. - * @param {boolean} [args.dryRun = false] - If true, simulates making a commit so you can test whether it would succeed. Implies `noUpdateBranch`. - * @param {boolean} [args.noUpdateBranch = false] - If true, does not update the branch pointer after creating the commit. - * @param {string} [args.ref] - The fully expanded name of the branch to commit to. Default is the current branch pointed to by HEAD. (TODO: fix it so it can expand branch names without throwing if the branch doesn't exist yet.) - * @param {string[]} [args.parent] - The SHA-1 object ids of the commits to use as parents. If not specified, the commit pointed to by `ref` is used. - * @param {string} [args.tree] - The SHA-1 object id of the tree to use. If not specified, a new tree object is created from the current git index. + * @param {import('../models/FileSystem.js').FileSystem} args.fs + * @param {string} args.gitdir + * @param {string} args.ref - The tag to delete * - * @returns {Promise} Resolves successfully with the SHA-1 object id of the newly created commit. + * @returns {Promise} Resolves successfully when filesystem operations are complete * * @example - * let sha = await git.commit({ - * fs, - * dir: '/tutorial', - * author: { - * name: 'Mr. Test', - * email: 'mrtest@example.com', - * }, - * message: 'Added the a.txt file' - * }) - * console.log(sha) + * await git.deleteTag({ dir: '$input((/))', ref: '$input((test-tag))' }) + * console.log('done') * */ -async function commit({ - fs: _fs, - onSign, - dir, - gitdir = join(dir, '.git'), - message, - author: _author, - committer: _committer, - signingKey, - dryRun = false, - noUpdateBranch = false, - ref, - parent, - tree, -}) { - try { - assertParameter('fs', _fs); - assertParameter('message', message); - if (signingKey) { - assertParameter('onSign', onSign); - } - const fs = new FileSystem(_fs); - const cache = {}; - - const author = await normalizeAuthorObject({ fs, gitdir, author: _author }); - if (!author) throw new MissingNameError('author') - - const committer = await normalizeCommitterObject({ - fs, - gitdir, - author, - committer: _committer, - }); - if (!committer) throw new MissingNameError('committer') - - return await _commit({ - fs, - cache, - onSign, - gitdir, - message, - author, - committer, - signingKey, - dryRun, - noUpdateBranch, - ref, - parent, - tree, - }) - } catch (err) { - err.caller = 'git.commit'; - throw err - } +async function _deleteTag({ fs, gitdir, ref }) { + ref = ref.startsWith('refs/tags/') ? ref : `refs/tags/${ref}`; + await GitRefManager.deleteRef({ fs, gitdir, ref }); } // @ts-check /** - * Get the name of the branch currently pointed to by .git/HEAD + * Delete a local tag ref * * @param {Object} args * @param {FsClient} args.fs - a file system implementation * @param {string} [args.dir] - The [working tree](dir-vs-gitdir.md) directory path * @param {string} [args.gitdir=join(dir,'.git')] - [required] The [git directory](dir-vs-gitdir.md) path - * @param {boolean} [args.fullname = false] - Return the full path (e.g. "refs/heads/main") instead of the abbreviated form. - * @param {boolean} [args.test = false] - If the current branch doesn't actually exist (such as right after git init) then return `undefined`. + * @param {string} args.ref - The tag to delete * - * @returns {Promise} The name of the current branch or undefined if the HEAD is detached. + * @returns {Promise} Resolves successfully when filesystem operations are complete * * @example - * // Get the current branch name - * let branch = await git.currentBranch({ - * fs, - * dir: '/tutorial', - * fullname: false - * }) - * console.log(branch) + * await git.deleteTag({ fs, dir: '/tutorial', ref: 'test-tag' }) + * console.log('done') * */ -async function currentBranch({ - fs, - dir, - gitdir = join(dir, '.git'), - fullname = false, - test = false, -}) { +async function deleteTag({ fs, dir, gitdir = join(dir, '.git'), ref }) { try { assertParameter('fs', fs); - assertParameter('gitdir', gitdir); - return await _currentBranch({ + assertParameter('ref', ref); + return await _deleteTag({ fs: new FileSystem(fs), gitdir, - fullname, - test, + ref, }) } catch (err) { - err.caller = 'git.currentBranch'; + err.caller = 'git.deleteTag'; throw err } } -// @ts-check +async function expandOidLoose({ fs, gitdir, oid: short }) { + const prefix = short.slice(0, 2); + const objectsSuffixes = await fs.readdir(`${gitdir}/objects/${prefix}`); + return objectsSuffixes + .map(suffix => `${prefix}${suffix}`) + .filter(_oid => _oid.startsWith(short)) +} -/** - * @param {Object} args - * @param {import('../models/FileSystem.js').FileSystem} args.fs - * @param {string} args.gitdir - * @param {string} args.ref - * - * @returns {Promise} - */ -async function _deleteBranch({ fs, gitdir, ref }) { - const exist = await GitRefManager.exists({ fs, gitdir, ref }); - if (!exist) { - throw new NotFoundError(ref) +async function expandOidPacked({ + fs, + gitdir, + oid: short, + getExternalRefDelta, +}) { + // Iterate through all the .pack files + const results = []; + let list = await fs.readdir(join(gitdir, 'objects/pack')); + list = list.filter(x => x.endsWith('.idx')); + for (const filename of list) { + const indexFile = `${gitdir}/objects/pack/${filename}`; + const p = await readPackIndex({ + fs, + filename: indexFile, + getExternalRefDelta, + }); + if (p.error) throw new InternalError(p.error) + // Search through the list of oids in the packfile + for (const oid of p.offsets.keys()) { + if (oid.startsWith(short)) results.push(oid); + } } + return results +} - const fullRef = await GitRefManager.expand({ fs, gitdir, ref }); - const currentRef = await _currentBranch({ fs, gitdir, fullname: true }); - if (fullRef === currentRef) { - // detach HEAD - const value = await GitRefManager.resolve({ fs, gitdir, ref: fullRef }); - await GitRefManager.writeRef({ fs, gitdir, ref: 'HEAD', value }); - } +async function _expandOid({ fs, gitdir, oid: short }) { + // Curry the current read method so that the packfile un-deltification + // process can acquire external ref-deltas. + const getExternalRefDelta = oid => _readObject({ fs, gitdir, oid }); - // Delete a specified branch - await GitRefManager.deleteRef({ fs, gitdir, ref: fullRef }); + const results1 = await expandOidLoose({ fs, gitdir, oid: short }); + const results2 = await expandOidPacked({ + fs, + gitdir, + oid: short, + getExternalRefDelta, + }); + const results = results1.concat(results2); + + if (results.length === 1) { + return results[0] + } + if (results.length > 1) { + throw new AmbiguousError('oids', short, results) + } + throw new NotFoundError(`an object matching "${short}"`) } // @ts-check /** - * Delete a local branch - * - * > Note: This only deletes loose branches - it should be fixed in the future to delete packed branches as well. + * Expand and resolve a short oid into a full oid * * @param {Object} args * @param {FsClient} args.fs - a file system implementation * @param {string} [args.dir] - The [working tree](dir-vs-gitdir.md) directory path * @param {string} [args.gitdir=join(dir,'.git')] - [required] The [git directory](dir-vs-gitdir.md) path - * @param {string} args.ref - The branch to delete + * @param {string} args.oid - The shortened oid prefix to expand (like "0414d2a") * - * @returns {Promise} Resolves successfully when filesystem operations are complete - * - * @example - * await git.deleteBranch({ fs, dir: '/tutorial', ref: 'local-branch' }) - * console.log('done') - * - */ -async function deleteBranch({ - fs, - dir, - gitdir = join(dir, '.git'), - ref, -}) { - try { - assertParameter('fs', fs); - assertParameter('ref', ref); - return await _deleteBranch({ - fs: new FileSystem(fs), - gitdir, - ref, - }) - } catch (err) { - err.caller = 'git.deleteBranch'; - throw err - } -} - -// @ts-check - -/** - * Delete a local ref - * - * @param {Object} args - * @param {FsClient} args.fs - a file system implementation - * @param {string} [args.dir] - The [working tree](dir-vs-gitdir.md) directory path - * @param {string} [args.gitdir=join(dir,'.git')] - [required] The [git directory](dir-vs-gitdir.md) path - * @param {string} args.ref - The ref to delete - * - * @returns {Promise} Resolves successfully when filesystem operations are complete - * - * @example - * await git.deleteRef({ fs, dir: '/tutorial', ref: 'refs/tags/test-tag' }) - * console.log('done') - * - */ -async function deleteRef({ fs, dir, gitdir = join(dir, '.git'), ref }) { - try { - assertParameter('fs', fs); - assertParameter('ref', ref); - await GitRefManager.deleteRef({ fs: new FileSystem(fs), gitdir, ref }); - } catch (err) { - err.caller = 'git.deleteRef'; - throw err - } -} - -// @ts-check - -/** - * @param {Object} args - * @param {import('../models/FileSystem.js').FileSystem} args.fs - * @param {string} args.gitdir - * @param {string} args.remote - * - * @returns {Promise} - */ -async function _deleteRemote({ fs, gitdir, remote }) { - const config = await GitConfigManager.get({ fs, gitdir }); - await config.deleteSection('remote', remote); - await GitConfigManager.save({ fs, gitdir, config }); -} - -// @ts-check - -/** - * Removes the local config entry for a given remote - * - * @param {Object} args - * @param {FsClient} args.fs - a file system implementation - * @param {string} [args.dir] - The [working tree](dir-vs-gitdir.md) directory path - * @param {string} [args.gitdir=join(dir,'.git')] - [required] The [git directory](dir-vs-gitdir.md) path - * @param {string} args.remote - The name of the remote to delete - * - * @returns {Promise} Resolves successfully when filesystem operations are complete - * - * @example - * await git.deleteRemote({ fs, dir: '/tutorial', remote: 'upstream' }) - * console.log('done') - * - */ -async function deleteRemote({ - fs, - dir, - gitdir = join(dir, '.git'), - remote, -}) { - try { - assertParameter('fs', fs); - assertParameter('remote', remote); - return await _deleteRemote({ - fs: new FileSystem(fs), - gitdir, - remote, - }) - } catch (err) { - err.caller = 'git.deleteRemote'; - throw err - } -} - -// @ts-check - -/** - * Delete a local tag ref - * - * @param {Object} args - * @param {import('../models/FileSystem.js').FileSystem} args.fs - * @param {string} args.gitdir - * @param {string} args.ref - The tag to delete - * - * @returns {Promise} Resolves successfully when filesystem operations are complete - * - * @example - * await git.deleteTag({ dir: '$input((/))', ref: '$input((test-tag))' }) - * console.log('done') - * - */ -async function _deleteTag({ fs, gitdir, ref }) { - ref = ref.startsWith('refs/tags/') ? ref : `refs/tags/${ref}`; - await GitRefManager.deleteRef({ fs, gitdir, ref }); -} - -// @ts-check - -/** - * Delete a local tag ref - * - * @param {Object} args - * @param {FsClient} args.fs - a file system implementation - * @param {string} [args.dir] - The [working tree](dir-vs-gitdir.md) directory path - * @param {string} [args.gitdir=join(dir,'.git')] - [required] The [git directory](dir-vs-gitdir.md) path - * @param {string} args.ref - The tag to delete - * - * @returns {Promise} Resolves successfully when filesystem operations are complete - * - * @example - * await git.deleteTag({ fs, dir: '/tutorial', ref: 'test-tag' }) - * console.log('done') - * - */ -async function deleteTag({ fs, dir, gitdir = join(dir, '.git'), ref }) { - try { - assertParameter('fs', fs); - assertParameter('ref', ref); - return await _deleteTag({ - fs: new FileSystem(fs), - gitdir, - ref, - }) - } catch (err) { - err.caller = 'git.deleteTag'; - throw err - } -} - -async function expandOidLoose({ fs, gitdir, oid: short }) { - const prefix = short.slice(0, 2); - const objectsSuffixes = await fs.readdir(`${gitdir}/objects/${prefix}`); - return objectsSuffixes - .map(suffix => `${prefix}${suffix}`) - .filter(_oid => _oid.startsWith(short)) -} - -async function expandOidPacked({ - fs, - gitdir, - oid: short, - getExternalRefDelta, -}) { - // Iterate through all the .pack files - const results = []; - let list = await fs.readdir(join(gitdir, 'objects/pack')); - list = list.filter(x => x.endsWith('.idx')); - for (const filename of list) { - const indexFile = `${gitdir}/objects/pack/${filename}`; - const p = await readPackIndex({ - fs, - filename: indexFile, - getExternalRefDelta, - }); - if (p.error) throw new InternalError(p.error) - // Search through the list of oids in the packfile - for (const oid of p.offsets.keys()) { - if (oid.startsWith(short)) results.push(oid); - } - } - return results -} - -async function _expandOid({ fs, gitdir, oid: short }) { - // Curry the current read method so that the packfile un-deltification - // process can acquire external ref-deltas. - const getExternalRefDelta = oid => _readObject({ fs, gitdir, oid }); - - const results1 = await expandOidLoose({ fs, gitdir, oid: short }); - const results2 = await expandOidPacked({ - fs, - gitdir, - oid: short, - getExternalRefDelta, - }); - const results = results1.concat(results2); - - if (results.length === 1) { - return results[0] - } - if (results.length > 1) { - throw new AmbiguousError('oids', short, results) - } - throw new NotFoundError(`an object matching "${short}"`) -} - -// @ts-check - -/** - * Expand and resolve a short oid into a full oid - * - * @param {Object} args - * @param {FsClient} args.fs - a file system implementation - * @param {string} [args.dir] - The [working tree](dir-vs-gitdir.md) directory path - * @param {string} [args.gitdir=join(dir,'.git')] - [required] The [git directory](dir-vs-gitdir.md) path - * @param {string} args.oid - The shortened oid prefix to expand (like "0414d2a") - * - * @returns {Promise} Resolves successfully with the full oid (like "0414d2a286d7bbc7a4a326a61c1f9f888a8ab87f") + * @returns {Promise} Resolves successfully with the full oid (like "0414d2a286d7bbc7a4a326a61c1f9f888a8ab87f") * * @example * let oid = await git.expandOid({ fs, dir: '/tutorial', oid: '0414d2a'}) @@ -9502,129 +8883,7 @@ async function fetch({ // @ts-check /** - * - * @typedef {object} FetchResult - The object returned has the following schema: - * @property {string | null} defaultBranch - The branch that is cloned if no branch is specified - * @property {string | null} fetchHead - The SHA-1 object id of the fetched head commit - * @property {string | null} fetchHeadDescription - a textual description of the branch that was fetched - * @property {Object} [headers] - The HTTP response headers returned by the git server - * @property {string[]} [pruned] - A list of branches that were pruned, if you provided the `prune` parameter - * - */ - -/** - * Fetch commits from a remote repository - * - * @param {object} args - * @param {FsClient} args.fs - a file system client - * @param {HttpClient} args.http - an HTTP client - * @param {ProgressCallback} [args.onProgress] - optional progress event callback - * @param {MessageCallback} [args.onMessage] - optional message event callback - * @param {AuthCallback} [args.onAuth] - optional auth fill callback - * @param {AuthFailureCallback} [args.onAuthFailure] - optional auth rejected callback - * @param {AuthSuccessCallback} [args.onAuthSuccess] - optional auth approved callback - * @param {string} [args.dir] - The [working tree](dir-vs-gitdir.md) directory path - * @param {string} [args.gitdir=join(dir,'.git')] - [required] The [git directory](dir-vs-gitdir.md) path - * @param {string} [args.url] - The URL of the remote repository. The default is the value set in the git config for that remote. - * @param {string} [args.remote] - If URL is not specified, determines which remote to use. - * @param {boolean} [args.singleBranch = false] - Instead of the default behavior of fetching all the branches, only fetch a single branch. - * @param {string} [args.ref] - Which branch to fetch if `singleBranch` is true. By default this is the current branch or the remote's default branch. - * @param {string} [args.remoteRef] - The name of the branch on the remote to fetch if `singleBranch` is true. By default this is the configured remote tracking branch. - * @param {boolean} [args.tags = false] - Also fetch tags - * @param {number} [args.depth] - Integer. Determines how much of the git repository's history to retrieve - * @param {boolean} [args.relative = false] - Changes the meaning of `depth` to be measured from the current shallow depth rather than from the branch tip. - * @param {Date} [args.since] - Only fetch commits created after the given date. Mutually exclusive with `depth`. - * @param {string[]} [args.exclude = []] - A list of branches or tags. Instructs the remote server not to send us any commits reachable from these refs. - * @param {boolean} [args.prune] - Delete local remote-tracking branches that are not present on the remote - * @param {boolean} [args.pruneTags] - Prune local tags that don’t exist on the remote, and force-update those tags that differ - * @param {string} [args.corsProxy] - Optional [CORS proxy](https://www.npmjs.com/%40isomorphic-git/cors-proxy). Overrides value in repo config. - * @param {Object} [args.headers] - Additional headers to include in HTTP requests, similar to git's `extraHeader` config - * @param {Arweave} [args.arweave] - * - * @returns {Promise} Resolves successfully when fetch completes - * @see FetchResult - * - * @example - * let result = await git.fetch({ - * fs, - * http, - * dir: '/tutorial', - * corsProxy: 'https://cors.isomorphic-git.org', - * url: 'https://github.com/isomorphic-git/isomorphic-git', - * ref: 'main', - * depth: 1, - * singleBranch: true, - * tags: false - * }) - * console.log(result) - * - */ -async function fetchFromArweave({ - fs, - http, - onProgress, - onMessage, - onAuth, - onAuthSuccess, - onAuthFailure, - dir, - gitdir = join(dir, '.git'), - ref, - remote, - remoteRef, - url, - corsProxy, - depth = null, - since = null, - exclude = [], - relative = false, - tags = false, - singleBranch = false, - headers = {}, - prune = false, - pruneTags = false, - arweave, -}) { - try { - assertParameter('fs', fs); - // assertParameter('http', http) - assertParameter('gitdir', gitdir); - - return await _fetchFromArweave({ - fs: new FileSystem(fs), - http, - onProgress, - onMessage, - onAuth, - onAuthSuccess, - onAuthFailure, - gitdir, - ref, - remote, - remoteRef, - url, - corsProxy, - depth, - since, - exclude, - relative, - tags, - singleBranch, - headers, - prune, - pruneTags, - arweave, - }) - } catch (err) { - err.caller = 'git.fetchFromArweave'; - throw err - } -} - -// @ts-check - -/** - * Find the merge base for a set of commits + * Find the merge base for a set of commits * * @param {object} args * @param {FsClient} args.fs - a file system client @@ -11234,647 +10493,94 @@ async function _pack({ fs, dir, gitdir = join(dir, '.git'), oids }) { * * @typedef {Object} PackObjectsResult The packObjects command returns an object with two properties: * @property {string} filename - The suggested filename for the packfile if you want to save it to disk somewhere. It includes the packfile SHA. - * @property {Uint8Array} [packfile] - The packfile contents. Not present if `write` parameter was true, in which case the packfile was written straight to disk. - */ - -/** - * @param {object} args - * @param {import('../models/FileSystem.js').FileSystem} args.fs - * @param {string} args.gitdir - * @param {string[]} args.oids - * @param {boolean} args.write - * - * @returns {Promise} - * @see PackObjectsResult - */ -async function _packObjects({ fs, gitdir, oids, write }) { - const buffers = await _pack({ fs, gitdir, oids }); - const packfile = Buffer.from(await collect(buffers)); - const packfileSha = packfile.slice(-20).toString('hex'); - const filename = `pack-${packfileSha}.pack`; - if (write) { - await fs.write(join(gitdir, `objects/pack/${filename}`), packfile); - return { filename } - } - return { - filename, - packfile: new Uint8Array(packfile), - } -} - -// @ts-check - -/** - * - * @typedef {Object} PackObjectsResult The packObjects command returns an object with two properties: - * @property {string} filename - The suggested filename for the packfile if you want to save it to disk somewhere. It includes the packfile SHA. - * @property {Uint8Array} [packfile] - The packfile contents. Not present if `write` parameter was true, in which case the packfile was written straight to disk. - */ - -/** - * Create a packfile from an array of SHA-1 object ids - * - * @param {object} args - * @param {FsClient} args.fs - a file system client - * @param {string} [args.dir] - The [working tree](dir-vs-gitdir.md) directory path - * @param {string} [args.gitdir=join(dir, '.git')] - [required] The [git directory](dir-vs-gitdir.md) path - * @param {string[]} args.oids - An array of SHA-1 object ids to be included in the packfile - * @param {boolean} [args.write = false] - Whether to save the packfile to disk or not - * - * @returns {Promise} Resolves successfully when the packfile is ready with the filename and buffer - * @see PackObjectsResult - * - * @example - * // Create a packfile containing only an empty tree - * let { packfile } = await git.packObjects({ - * fs, - * dir: '/tutorial', - * oids: ['4b825dc642cb6eb9a060e54bf8d69288fbee4904'] - * }) - * console.log(packfile) - * - */ -async function packObjects({ - fs, - dir, - gitdir = join(dir, '.git'), - oids, - write = false, -}) { - try { - assertParameter('fs', fs); - assertParameter('gitdir', gitdir); - assertParameter('oids', oids); - - return await _packObjects({ - fs: new FileSystem(fs), - gitdir, - oids, - write, - }) - } catch (err) { - err.caller = 'git.packObjects'; - throw err - } -} - -// @ts-check - -/** - * Fetch and merge commits from a remote repository - * - * @param {object} args - * @param {FsClient} args.fs - a file system client - * @param {HttpClient} args.http - an HTTP client - * @param {ProgressCallback} [args.onProgress] - optional progress event callback - * @param {MessageCallback} [args.onMessage] - optional message event callback - * @param {AuthCallback} [args.onAuth] - optional auth fill callback - * @param {AuthFailureCallback} [args.onAuthFailure] - optional auth rejected callback - * @param {AuthSuccessCallback} [args.onAuthSuccess] - optional auth approved callback - * @param {string} args.dir] - The [working tree](dir-vs-gitdir.md) directory path - * @param {string} [args.gitdir=join(dir,'.git')] - [required] The [git directory](dir-vs-gitdir.md) path - * @param {string} [args.ref] - Which branch to merge into. By default this is the currently checked out branch. - * @param {string} [args.url] - (Added in 1.1.0) The URL of the remote repository. The default is the value set in the git config for that remote. - * @param {string} [args.remote] - (Added in 1.1.0) If URL is not specified, determines which remote to use. - * @param {string} [args.remoteRef] - (Added in 1.1.0) The name of the branch on the remote to fetch. By default this is the configured remote tracking branch. - * @param {string} [args.corsProxy] - Optional [CORS proxy](https://www.npmjs.com/%40isomorphic-git/cors-proxy). Overrides value in repo config. - * @param {boolean} [args.singleBranch = false] - Instead of the default behavior of fetching all the branches, only fetch a single branch. - * @param {boolean} [args.fastForwardOnly = false] - Only perform simple fast-forward merges. (Don't create merge commits.) - * @param {Object} [args.headers] - Additional headers to include in HTTP requests, similar to git's `extraHeader` config - * @param {Object} [args.author] - The details about the author. - * @param {string} [args.author.name] - Default is `user.name` config. - * @param {string} [args.author.email] - Default is `user.email` config. - * @param {number} [args.author.timestamp=Math.floor(Date.now()/1000)] - Set the author timestamp field. This is the integer number of seconds since the Unix epoch (1970-01-01 00:00:00). - * @param {number} [args.author.timezoneOffset] - Set the author timezone offset field. This is the difference, in minutes, from the current timezone to UTC. Default is `(new Date()).getTimezoneOffset()`. - * @param {Object} [args.committer = author] - The details about the commit committer, in the same format as the author parameter. If not specified, the author details are used. - * @param {string} [args.committer.name] - Default is `user.name` config. - * @param {string} [args.committer.email] - Default is `user.email` config. - * @param {number} [args.committer.timestamp=Math.floor(Date.now()/1000)] - Set the committer timestamp field. This is the integer number of seconds since the Unix epoch (1970-01-01 00:00:00). - * @param {number} [args.committer.timezoneOffset] - Set the committer timezone offset field. This is the difference, in minutes, from the current timezone to UTC. Default is `(new Date()).getTimezoneOffset()`. - * @param {string} [args.signingKey] - passed to [commit](commit.md) when creating a merge commit - * - * @returns {Promise} Resolves successfully when pull operation completes - * - * @example - * await git.pull({ - * fs, - * http, - * dir: '/tutorial', - * ref: 'main', - * singleBranch: true - * }) - * console.log('done') - * - */ -async function pull({ - fs: _fs, - http, - onProgress, - onMessage, - onAuth, - onAuthSuccess, - onAuthFailure, - dir, - gitdir = join(dir, '.git'), - ref, - url, - remote, - remoteRef, - fastForwardOnly = false, - corsProxy, - singleBranch, - headers = {}, - author: _author, - committer: _committer, - signingKey, -}) { - try { - assertParameter('fs', _fs); - assertParameter('gitdir', gitdir); - - const fs = new FileSystem(_fs); - - const author = await normalizeAuthorObject({ fs, gitdir, author: _author }); - if (!author) throw new MissingNameError('author') - - const committer = await normalizeCommitterObject({ - fs, - gitdir, - author, - committer: _committer, - }); - if (!committer) throw new MissingNameError('committer') - - return await _pull({ - fs, - cache: {}, - http, - onProgress, - onMessage, - onAuth, - onAuthSuccess, - onAuthFailure, - dir, - gitdir, - ref, - url, - remote, - remoteRef, - fastForwardOnly, - corsProxy, - singleBranch, - headers, - author, - committer, - signingKey, - }) - } catch (err) { - err.caller = 'git.pull'; - throw err - } -} - -/** - * @param {object} args - * @param {import('../models/FileSystem.js').FileSystem} args.fs - * @param {string} [args.dir] - * @param {string} args.gitdir - * @param {Iterable} args.start - * @param {Iterable} args.finish - * @returns {Promise>} - */ -async function listCommitsAndTags({ - fs, - dir, - gitdir = join(dir, '.git'), - start, - finish, -}) { - const shallows = await GitShallowManager.read({ fs, gitdir }); - const startingSet = new Set(); - const finishingSet = new Set(); - for (const ref of start) { - startingSet.add(await GitRefManager.resolve({ fs, gitdir, ref })); - } - for (const ref of finish) { - // We may not have these refs locally so we must try/catch - try { - const oid = await GitRefManager.resolve({ fs, gitdir, ref }); - finishingSet.add(oid); - } catch (err) {} - } - const visited = new Set(); - // Because git commits are named by their hash, there is no - // way to construct a cycle. Therefore we won't worry about - // setting a default recursion limit. - async function walk(oid) { - visited.add(oid); - const { type, object } = await _readObject({ fs, gitdir, oid }); - // Recursively resolve annotated tags - if (type === 'tag') { - const tag = GitAnnotatedTag.from(object); - const commit = tag.headers().object; - return walk(commit) - } - if (type !== 'commit') { - throw new ObjectTypeError(oid, type, 'commit') - } - if (!shallows.has(oid)) { - const commit = GitCommit.from(object); - const parents = commit.headers().parent; - for (oid of parents) { - if (!finishingSet.has(oid) && !visited.has(oid)) { - await walk(oid); - } - } - } - } - // Let's go walking! - for (const oid of startingSet) { - await walk(oid); - } - return visited -} - -/** - * @param {object} args - * @param {import('../models/FileSystem.js').FileSystem} args.fs - * @param {string} [args.dir] - * @param {string} args.gitdir - * @param {Iterable} args.oids - * @returns {Promise>} - */ -async function listObjects({ - fs, - dir, - gitdir = join(dir, '.git'), - oids, -}) { - const visited = new Set(); - // We don't do the purest simplest recursion, because we can - // avoid reading Blob objects entirely since the Tree objects - // tell us which oids are Blobs and which are Trees. - async function walk(oid) { - if (visited.has(oid)) return - visited.add(oid); - const { type, object } = await _readObject({ fs, gitdir, oid }); - if (type === 'tag') { - const tag = GitAnnotatedTag.from(object); - const obj = tag.headers().object; - await walk(obj); - } else if (type === 'commit') { - const commit = GitCommit.from(object); - const tree = commit.headers().tree; - await walk(tree); - } else if (type === 'tree') { - const tree = GitTree.from(object); - for (const entry of tree) { - // add blobs to the set - // skip over submodules whose type is 'commit' - if (entry.type === 'blob') { - visited.add(entry.oid); - } - // recurse for trees - if (entry.type === 'tree') { - await walk(entry.oid); - } - } - } - } - // Let's go walking! - for (const oid of oids) { - await walk(oid); - } - return visited -} - -async function parseReceivePackResponse(packfile) { - /** @type PushResult */ - const result = {}; - let response = ''; - const read = GitPktLine.streamReader(packfile); - let line = await read(); - while (line !== true) { - if (line !== null) response += line.toString('utf8') + '\n'; - line = await read(); - } - - const lines = response.toString('utf8').split('\n'); - // We're expecting "unpack {unpack-result}" - line = lines.shift(); - if (!line.startsWith('unpack ')) { - throw new ParseError('unpack ok" or "unpack [error message]', line) - } - result.ok = line === 'unpack ok'; - if (!result.ok) { - result.error = line.slice('unpack '.length); - } - result.refs = {}; - for (const line of lines) { - if (line.trim() === '') continue - const status = line.slice(0, 2); - const refAndMessage = line.slice(3); - let space = refAndMessage.indexOf(' '); - if (space === -1) space = refAndMessage.length; - const ref = refAndMessage.slice(0, space); - const error = refAndMessage.slice(space + 1); - result.refs[ref] = { - ok: status === 'ok', - error, - }; - } - return result -} - -async function writeReceivePackRequest({ - capabilities = [], - triplets = [], -}) { - const packstream = []; - let capsFirstLine = `\x00 ${capabilities.join(' ')}`; - for (const trip of triplets) { - packstream.push( - GitPktLine.encode( - `${trip.oldoid} ${trip.oid} ${trip.fullRef}${capsFirstLine}\n` - ) - ); - capsFirstLine = ''; - } - packstream.push(GitPktLine.flush()); - return packstream -} - -// @ts-check - -/** - * @param {object} args - * @param {import('../models/FileSystem.js').FileSystem} args.fs - * @param {HttpClient} args.http - * @param {ProgressCallback} [args.onProgress] - * @param {MessageCallback} [args.onMessage] - * @param {AuthCallback} [args.onAuth] - * @param {AuthFailureCallback} [args.onAuthFailure] - * @param {AuthSuccessCallback} [args.onAuthSuccess] - * @param {string} args.gitdir - * @param {string} [args.ref] - * @param {string} [args.remoteRef] - * @param {string} [args.remote] - * @param {boolean} [args.force = false] - * @param {boolean} [args.delete = false] - * @param {string} [args.url] - * @param {string} [args.corsProxy] - * @param {Object} [args.headers] - * - * @returns {Promise} - */ -async function _push({ - fs, - http, - onProgress, - onMessage, - onAuth, - onAuthSuccess, - onAuthFailure, - gitdir, - ref: _ref, - remoteRef: _remoteRef, - remote, - url: _url, - force = false, - delete: _delete = false, - corsProxy, - headers = {}, -}) { - const ref = _ref || (await _currentBranch({ fs, gitdir })); - if (typeof ref === 'undefined') { - throw new MissingParameterError('ref') - } - const config = await GitConfigManager.get({ fs, gitdir }); - // Figure out what remote to use. - remote = - remote || - (await config.get(`branch.${ref}.pushRemote`)) || - (await config.get('remote.pushDefault')) || - (await config.get(`branch.${ref}.remote`)) || - 'origin'; - // Lookup the URL for the given remote. - const url = - _url || - (await config.get(`remote.${remote}.pushurl`)) || - (await config.get(`remote.${remote}.url`)); - if (typeof url === 'undefined') { - throw new MissingParameterError('remote OR url') - } - // Figure out what remote ref to use. - const remoteRef = _remoteRef || (await config.get(`branch.${ref}.merge`)); - if (typeof url === 'undefined') { - throw new MissingParameterError('remoteRef') - } - - if (corsProxy === undefined) { - corsProxy = await config.get('http.corsProxy'); - } - - const fullRef = await GitRefManager.expand({ fs, gitdir, ref }); - const oid = _delete - ? '0000000000000000000000000000000000000000' - : await GitRefManager.resolve({ fs, gitdir, ref: fullRef }); - - /** @type typeof import("../managers/GitRemoteHTTP").GitRemoteHTTP */ - const GitRemoteHTTP = GitRemoteManager.getRemoteHelperFor({ url }); - const httpRemote = await GitRemoteHTTP.discover({ - http, - onAuth, - onAuthSuccess, - onAuthFailure, - corsProxy, - service: 'git-receive-pack', - url, - headers, - }); - const auth = httpRemote.auth; // hack to get new credentials from CredentialManager API - let fullRemoteRef; - if (!remoteRef) { - fullRemoteRef = fullRef; - } else { - try { - fullRemoteRef = await GitRefManager.expandAgainstMap({ - ref: remoteRef, - map: httpRemote.refs, - }); - } catch (err) { - if (err instanceof NotFoundError) { - // The remote reference doesn't exist yet. - // If it is fully specified, use that value. Otherwise, treat it as a branch. - fullRemoteRef = remoteRef.startsWith('refs/') - ? remoteRef - : `refs/heads/${remoteRef}`; - } else { - throw err - } - } - } - const oldoid = - httpRemote.refs.get(fullRemoteRef) || - '0000000000000000000000000000000000000000'; - - // Remotes can always accept thin-packs UNLESS they specify the 'no-thin' capability - const thinPack = !httpRemote.capabilities.has('no-thin'); - - let objects = new Set(); - if (!_delete) { - const finish = [...httpRemote.refs.values()]; - let skipObjects = new Set(); - - // If remote branch is present, look for a common merge base. - if (oldoid !== '0000000000000000000000000000000000000000') { - // trick to speed up common force push scenarios - const mergebase = await _findMergeBase({ - fs, - gitdir, - oids: [oid, oldoid], - }); - for (const oid of mergebase) finish.push(oid); - if (thinPack) { - skipObjects = await listObjects({ fs, gitdir, oids: mergebase }); - } - } - - // If remote does not have the commit, figure out the objects to send - if (!finish.includes(oid)) { - const commits = await listCommitsAndTags({ - fs, - gitdir, - start: [oid], - finish, - }); - objects = await listObjects({ fs, gitdir, oids: commits }); - } - - if (thinPack) { - // If there's a default branch for the remote lets skip those objects too. - // Since this is an optional optimization, we just catch and continue if there is - // an error (because we can't find a default branch, or can't find a commit, etc) - try { - // Sadly, the discovery phase with 'forPush' doesn't return symrefs, so we have to - // rely on existing ones. - const ref = await GitRefManager.resolve({ - fs, - gitdir, - ref: `refs/remotes/${remote}/HEAD`, - depth: 2, - }); - const { oid } = await GitRefManager.resolveAgainstMap({ - ref: ref.replace(`refs/remotes/${remote}/`, ''), - fullref: ref, - map: httpRemote.refs, - }); - const oids = [oid]; - for (const oid of await listObjects({ fs, gitdir, oids })) { - skipObjects.add(oid); - } - } catch (e) {} - - // Remove objects that we know the remote already has - for (const oid of skipObjects) { - objects.delete(oid); - } - } - - if (!force) { - // Is it a tag that already exists? - if ( - fullRef.startsWith('refs/tags') && - oldoid !== '0000000000000000000000000000000000000000' - ) { - throw new PushRejectedError('tag-exists') - } - // Is it a non-fast-forward commit? - if ( - oid !== '0000000000000000000000000000000000000000' && - oldoid !== '0000000000000000000000000000000000000000' && - !(await _isDescendent({ fs, gitdir, oid, ancestor: oldoid, depth: -1 })) - ) { - throw new PushRejectedError('not-fast-forward') - } - } - } - // We can only safely use capabilities that the server also understands. - // For instance, AWS CodeCommit aborts a push if you include the `agent`!!! - const capabilities = filterCapabilities( - [...httpRemote.capabilities], - ['report-status', 'side-band-64k', `agent=${pkg.agent}`] - ); - const packstream1 = await writeReceivePackRequest({ - capabilities, - triplets: [{ oldoid, oid, fullRef: fullRemoteRef }], - }); - const packstream2 = _delete - ? [] - : await _pack({ - fs, - gitdir, - oids: [...objects], - }); - const res = await GitRemoteHTTP.connect({ - http, - onProgress, - corsProxy, - service: 'git-receive-pack', - url, - auth, - headers, - body: [...packstream1, ...packstream2], - }); - const { packfile, progress } = await GitSideBand.demux(res.body); - if (onMessage) { - const lines = splitLines(progress); - forAwait(lines, async line => { - await onMessage(line); - }); - } - // Parse the response! - const result = await parseReceivePackResponse(packfile); - if (res.headers) { - result.headers = res.headers; - } - - // Update the local copy of the remote ref - if (remote && result.ok && result.refs[fullRemoteRef].ok) { - // TODO: I think this should actually be using a refspec transform rather than assuming 'refs/remotes/{remote}' - const ref = `refs/remotes/${remote}/${fullRemoteRef.replace( - 'refs/heads', - '' - )}`; - if (_delete) { - await GitRefManager.deleteRef({ fs, gitdir, ref }); - } else { - await GitRefManager.writeRef({ fs, gitdir, ref, value: oid }); - } + * @property {Uint8Array} [packfile] - The packfile contents. Not present if `write` parameter was true, in which case the packfile was written straight to disk. + */ + +/** + * @param {object} args + * @param {import('../models/FileSystem.js').FileSystem} args.fs + * @param {string} args.gitdir + * @param {string[]} args.oids + * @param {boolean} args.write + * + * @returns {Promise} + * @see PackObjectsResult + */ +async function _packObjects({ fs, gitdir, oids, write }) { + const buffers = await _pack({ fs, gitdir, oids }); + const packfile = Buffer.from(await collect(buffers)); + const packfileSha = packfile.slice(-20).toString('hex'); + const filename = `pack-${packfileSha}.pack`; + if (write) { + await fs.write(join(gitdir, `objects/pack/${filename}`), packfile); + return { filename } } - if (result.ok && Object.values(result.refs).every(result => result.ok)) { - return result - } else { - const prettyDetails = Object.entries(result.refs) - .filter(([k, v]) => !v.ok) - .map(([k, v]) => `\n - ${k}: ${v.error}`) - .join(''); - throw new GitPushError(prettyDetails, result) + return { + filename, + packfile: new Uint8Array(packfile), } } // @ts-check /** - * Push a branch or tag * - * The push command returns an object that describes the result of the attempted push operation. - * *Notes:* If there were no errors, then there will be no `errors` property. There can be a mix of `ok` messages and `errors` messages. + * @typedef {Object} PackObjectsResult The packObjects command returns an object with two properties: + * @property {string} filename - The suggested filename for the packfile if you want to save it to disk somewhere. It includes the packfile SHA. + * @property {Uint8Array} [packfile] - The packfile contents. Not present if `write` parameter was true, in which case the packfile was written straight to disk. + */ + +/** + * Create a packfile from an array of SHA-1 object ids * - * | param | type [= default] | description | - * | ------ | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | - * | ok | Array\ | The first item is "unpack" if the overall operation was successful. The remaining items are the names of refs that were updated successfully. | - * | errors | Array\ | If the overall operation threw and error, the first item will be "unpack {Overall error message}". The remaining items are individual refs that failed to be updated in the format "{ref name} {error message}". | + * @param {object} args + * @param {FsClient} args.fs - a file system client + * @param {string} [args.dir] - The [working tree](dir-vs-gitdir.md) directory path + * @param {string} [args.gitdir=join(dir, '.git')] - [required] The [git directory](dir-vs-gitdir.md) path + * @param {string[]} args.oids - An array of SHA-1 object ids to be included in the packfile + * @param {boolean} [args.write = false] - Whether to save the packfile to disk or not + * + * @returns {Promise} Resolves successfully when the packfile is ready with the filename and buffer + * @see PackObjectsResult + * + * @example + * // Create a packfile containing only an empty tree + * let { packfile } = await git.packObjects({ + * fs, + * dir: '/tutorial', + * oids: ['4b825dc642cb6eb9a060e54bf8d69288fbee4904'] + * }) + * console.log(packfile) + * + */ +async function packObjects({ + fs, + dir, + gitdir = join(dir, '.git'), + oids, + write = false, +}) { + try { + assertParameter('fs', fs); + assertParameter('gitdir', gitdir); + assertParameter('oids', oids); + + return await _packObjects({ + fs: new FileSystem(fs), + gitdir, + oids, + write, + }) + } catch (err) { + err.caller = 'git.packObjects'; + throw err + } +} + +// @ts-check + +/** + * Fetch and merge commits from a remote repository * * @param {object} args * @param {FsClient} args.fs - a file system client @@ -11884,79 +10590,277 @@ async function _push({ * @param {AuthCallback} [args.onAuth] - optional auth fill callback * @param {AuthFailureCallback} [args.onAuthFailure] - optional auth rejected callback * @param {AuthSuccessCallback} [args.onAuthSuccess] - optional auth approved callback - * @param {string} [args.dir] - The [working tree](dir-vs-gitdir.md) directory path + * @param {string} args.dir] - The [working tree](dir-vs-gitdir.md) directory path * @param {string} [args.gitdir=join(dir,'.git')] - [required] The [git directory](dir-vs-gitdir.md) path - * @param {string} [args.ref] - Which branch to push. By default this is the currently checked out branch. - * @param {string} [args.url] - The URL of the remote repository. The default is the value set in the git config for that remote. - * @param {string} [args.remote] - If URL is not specified, determines which remote to use. - * @param {string} [args.remoteRef] - The name of the receiving branch on the remote. By default this is the configured remote tracking branch. - * @param {boolean} [args.force = false] - If true, behaves the same as `git push --force` - * @param {boolean} [args.delete = false] - If true, delete the remote ref + * @param {string} [args.ref] - Which branch to merge into. By default this is the currently checked out branch. + * @param {string} [args.url] - (Added in 1.1.0) The URL of the remote repository. The default is the value set in the git config for that remote. + * @param {string} [args.remote] - (Added in 1.1.0) If URL is not specified, determines which remote to use. + * @param {string} [args.remoteRef] - (Added in 1.1.0) The name of the branch on the remote to fetch. By default this is the configured remote tracking branch. * @param {string} [args.corsProxy] - Optional [CORS proxy](https://www.npmjs.com/%40isomorphic-git/cors-proxy). Overrides value in repo config. + * @param {boolean} [args.singleBranch = false] - Instead of the default behavior of fetching all the branches, only fetch a single branch. + * @param {boolean} [args.fastForwardOnly = false] - Only perform simple fast-forward merges. (Don't create merge commits.) * @param {Object} [args.headers] - Additional headers to include in HTTP requests, similar to git's `extraHeader` config + * @param {Object} [args.author] - The details about the author. + * @param {string} [args.author.name] - Default is `user.name` config. + * @param {string} [args.author.email] - Default is `user.email` config. + * @param {number} [args.author.timestamp=Math.floor(Date.now()/1000)] - Set the author timestamp field. This is the integer number of seconds since the Unix epoch (1970-01-01 00:00:00). + * @param {number} [args.author.timezoneOffset] - Set the author timezone offset field. This is the difference, in minutes, from the current timezone to UTC. Default is `(new Date()).getTimezoneOffset()`. + * @param {Object} [args.committer = author] - The details about the commit committer, in the same format as the author parameter. If not specified, the author details are used. + * @param {string} [args.committer.name] - Default is `user.name` config. + * @param {string} [args.committer.email] - Default is `user.email` config. + * @param {number} [args.committer.timestamp=Math.floor(Date.now()/1000)] - Set the committer timestamp field. This is the integer number of seconds since the Unix epoch (1970-01-01 00:00:00). + * @param {number} [args.committer.timezoneOffset] - Set the committer timezone offset field. This is the difference, in minutes, from the current timezone to UTC. Default is `(new Date()).getTimezoneOffset()`. + * @param {string} [args.signingKey] - passed to [commit](commit.md) when creating a merge commit * - * @returns {Promise} Resolves successfully when push completes with a detailed description of the operation from the server. - * @see PushResult - * @see RefUpdateStatus + * @returns {Promise} Resolves successfully when pull operation completes * * @example - * let pushResult = await git.push({ + * await git.pull({ * fs, * http, * dir: '/tutorial', - * remote: 'origin', * ref: 'main', - * onAuth: () => ({ username: process.env.GITHUB_TOKEN }), + * singleBranch: true * }) - * console.log(pushResult) + * console.log('done') * */ -async function push({ +async function pull({ + fs: _fs, + http, + onProgress, + onMessage, + onAuth, + onAuthSuccess, + onAuthFailure, + dir, + gitdir = join(dir, '.git'), + ref, + url, + remote, + remoteRef, + fastForwardOnly = false, + corsProxy, + singleBranch, + headers = {}, + author: _author, + committer: _committer, + signingKey, +}) { + try { + assertParameter('fs', _fs); + assertParameter('gitdir', gitdir); + + const fs = new FileSystem(_fs); + + const author = await normalizeAuthorObject({ fs, gitdir, author: _author }); + if (!author) throw new MissingNameError('author') + + const committer = await normalizeCommitterObject({ + fs, + gitdir, + author, + committer: _committer, + }); + if (!committer) throw new MissingNameError('committer') + + return await _pull({ + fs, + cache: {}, + http, + onProgress, + onMessage, + onAuth, + onAuthSuccess, + onAuthFailure, + dir, + gitdir, + ref, + url, + remote, + remoteRef, + fastForwardOnly, + corsProxy, + singleBranch, + headers, + author, + committer, + signingKey, + }) + } catch (err) { + err.caller = 'git.pull'; + throw err + } +} + +/** + * @param {object} args + * @param {import('../models/FileSystem.js').FileSystem} args.fs + * @param {string} [args.dir] + * @param {string} args.gitdir + * @param {Iterable} args.start + * @param {Iterable} args.finish + * @returns {Promise>} + */ +async function listCommitsAndTags({ + fs, + dir, + gitdir = join(dir, '.git'), + start, + finish, +}) { + const shallows = await GitShallowManager.read({ fs, gitdir }); + const startingSet = new Set(); + const finishingSet = new Set(); + for (const ref of start) { + startingSet.add(await GitRefManager.resolve({ fs, gitdir, ref })); + } + for (const ref of finish) { + // We may not have these refs locally so we must try/catch + try { + const oid = await GitRefManager.resolve({ fs, gitdir, ref }); + finishingSet.add(oid); + } catch (err) {} + } + const visited = new Set(); + // Because git commits are named by their hash, there is no + // way to construct a cycle. Therefore we won't worry about + // setting a default recursion limit. + async function walk(oid) { + visited.add(oid); + const { type, object } = await _readObject({ fs, gitdir, oid }); + // Recursively resolve annotated tags + if (type === 'tag') { + const tag = GitAnnotatedTag.from(object); + const commit = tag.headers().object; + return walk(commit) + } + if (type !== 'commit') { + throw new ObjectTypeError(oid, type, 'commit') + } + if (!shallows.has(oid)) { + const commit = GitCommit.from(object); + const parents = commit.headers().parent; + for (oid of parents) { + if (!finishingSet.has(oid) && !visited.has(oid)) { + await walk(oid); + } + } + } + } + // Let's go walking! + for (const oid of startingSet) { + await walk(oid); + } + return visited +} + +/** + * @param {object} args + * @param {import('../models/FileSystem.js').FileSystem} args.fs + * @param {string} [args.dir] + * @param {string} args.gitdir + * @param {Iterable} args.oids + * @returns {Promise>} + */ +async function listObjects({ fs, - http, - onProgress, - onMessage, - onAuth, - onAuthSuccess, - onAuthFailure, dir, gitdir = join(dir, '.git'), - ref, - remoteRef, - remote = 'origin', - url, - force = false, - delete: _delete = false, - corsProxy, - headers = {}, + oids, }) { - try { - assertParameter('fs', fs); - assertParameter('http', http); - assertParameter('gitdir', gitdir); + const visited = new Set(); + // We don't do the purest simplest recursion, because we can + // avoid reading Blob objects entirely since the Tree objects + // tell us which oids are Blobs and which are Trees. + async function walk(oid) { + if (visited.has(oid)) return + visited.add(oid); + const { type, object } = await _readObject({ fs, gitdir, oid }); + if (type === 'tag') { + const tag = GitAnnotatedTag.from(object); + const obj = tag.headers().object; + await walk(obj); + } else if (type === 'commit') { + const commit = GitCommit.from(object); + const tree = commit.headers().tree; + await walk(tree); + } else if (type === 'tree') { + const tree = GitTree.from(object); + for (const entry of tree) { + // add blobs to the set + // skip over submodules whose type is 'commit' + if (entry.type === 'blob') { + visited.add(entry.oid); + } + // recurse for trees + if (entry.type === 'tree') { + await walk(entry.oid); + } + } + } + } + // Let's go walking! + for (const oid of oids) { + await walk(oid); + } + return visited +} - return await _push({ - fs: new FileSystem(fs), - http, - onProgress, - onMessage, - onAuth, - onAuthSuccess, - onAuthFailure, - gitdir, - ref, - remoteRef, - remote, - url, - force, - delete: _delete, - corsProxy, - headers, - }) - } catch (err) { - err.caller = 'git.push'; - throw err +async function parseReceivePackResponse(packfile) { + /** @type PushResult */ + const result = {}; + let response = ''; + const read = GitPktLine.streamReader(packfile); + let line = await read(); + while (line !== true) { + if (line !== null) response += line.toString('utf8') + '\n'; + line = await read(); + } + + const lines = response.toString('utf8').split('\n'); + // We're expecting "unpack {unpack-result}" + line = lines.shift(); + if (!line.startsWith('unpack ')) { + throw new ParseError('unpack ok" or "unpack [error message]', line) + } + result.ok = line === 'unpack ok'; + if (!result.ok) { + result.error = line.slice('unpack '.length); + } + result.refs = {}; + for (const line of lines) { + if (line.trim() === '') continue + const status = line.slice(0, 2); + const refAndMessage = line.slice(3); + let space = refAndMessage.indexOf(' '); + if (space === -1) space = refAndMessage.length; + const ref = refAndMessage.slice(0, space); + const error = refAndMessage.slice(space + 1); + result.refs[ref] = { + ok: status === 'ok', + error, + }; + } + return result +} + +async function writeReceivePackRequest({ + capabilities = [], + triplets = [], +}) { + const packstream = []; + let capsFirstLine = `\x00 ${capabilities.join(' ')}`; + for (const trip of triplets) { + packstream.push( + GitPktLine.encode( + `${trip.oldoid} ${trip.oid} ${trip.fullRef}${capsFirstLine}\n` + ) + ); + capsFirstLine = ''; } + packstream.push(GitPktLine.flush()); + return packstream } // @ts-check @@ -11964,9 +10868,7 @@ async function push({ /** * @param {object} args * @param {import('../models/FileSystem.js').FileSystem} args.fs - * @param {import { pushToArweave } from '../../index.d'; -HttpClient}import { getOidByRef } from '../utils/graphql'; - args.http + * @param {HttpClient} args.http * @param {ProgressCallback} [args.onProgress] * @param {MessageCallback} [args.onMessage] * @param {AuthCallback} [args.onAuth] @@ -11981,12 +10883,10 @@ HttpClient}import { getOidByRef } from '../utils/graphql'; * @param {string} [args.url] * @param {string} [args.corsProxy] * @param {Object} [args.headers] - * @param {Arweave} [args.arweave] - * @param {ArweaveWallet} [args.wallet] * * @returns {Promise} */ -async function _pushToArweave({ +async function _push({ fs, http, onProgress, @@ -12003,8 +10903,6 @@ async function _pushToArweave({ delete: _delete = false, corsProxy, headers = {}, - arweave, - wallet, }) { const ref = _ref || (await _currentBranch({ fs, gitdir })); if (typeof ref === 'undefined') { @@ -12041,12 +10939,19 @@ async function _pushToArweave({ ? '0000000000000000000000000000000000000000' : await GitRefManager.resolve({ fs, gitdir, ref: fullRef }); - // const arweave = Arweave.init({ - // host: 'arweave.net', - // port: 443, - // protocol: 'https', - // }) - + /** @type typeof import("../managers/GitRemoteHTTP").GitRemoteHTTP */ + const GitRemoteHTTP = GitRemoteManager.getRemoteHelperFor({ url }); + const httpRemote = await GitRemoteHTTP.discover({ + http, + onAuth, + onAuthSuccess, + onAuthFailure, + corsProxy, + service: 'git-receive-pack', + url, + headers, + }); + const auth = httpRemote.auth; // hack to get new credentials from CredentialManager API let fullRemoteRef; if (!remoteRef) { fullRemoteRef = fullRef; @@ -12054,7 +10959,7 @@ async function _pushToArweave({ try { fullRemoteRef = await GitRefManager.expandAgainstMap({ ref: remoteRef, - map: await getRefsOnArweave(arweave, url), + map: httpRemote.refs, }); } catch (err) { if (err instanceof NotFoundError) { @@ -12068,14 +10973,16 @@ async function _pushToArweave({ } } } + const oldoid = + httpRemote.refs.get(fullRemoteRef) || + '0000000000000000000000000000000000000000'; - const oldoid = await getOidByRef(arweave, url, fullRemoteRef); + // Remotes can always accept thin-packs UNLESS they specify the 'no-thin' capability + const thinPack = !httpRemote.capabilities.has('no-thin'); let objects = new Set(); if (!_delete) { - // const finish = [...httpRemote.refs.values()] - // all refs on the remote - const finish = []; + const finish = [...httpRemote.refs.values()]; let skipObjects = new Set(); // If remote branch is present, look for a common merge base. @@ -12087,8 +10994,9 @@ async function _pushToArweave({ oids: [oid, oldoid], }); for (const oid of mergebase) finish.push(oid); - // thinpack - skipObjects = await listObjects({ fs, gitdir, oids: mergebase }); + if (thinPack) { + skipObjects = await listObjects({ fs, gitdir, oids: mergebase }); + } } // If remote does not have the commit, figure out the objects to send @@ -12102,33 +11010,34 @@ async function _pushToArweave({ objects = await listObjects({ fs, gitdir, oids: commits }); } - //thinpack - // If there's a default branch for the remote lets skip those objects too. - // Since this is an optional optimization, we just catch and continue if there is - // an error (because we can't find a default branch, or can't find a commit, etc) - try { - // Sadly, the discovery phase with 'forPush' doesn't return symrefs, so we have to - // rely on existing ones. - const ref = await GitRefManager.resolve({ - fs, - gitdir, - ref: `refs/remotes/${remote}/HEAD`, - depth: 2, - }); - const { oid } = await GitRefManager.resolveAgainstMap({ - ref: ref.replace(`refs/remotes/${remote}/`, ''), - fullref: ref, - map: new Map(), - }); - const oids = [oid]; - for (const oid of await listObjects({ fs, gitdir, oids })) { - skipObjects.add(oid); - } - } catch (e) {} + if (thinPack) { + // If there's a default branch for the remote lets skip those objects too. + // Since this is an optional optimization, we just catch and continue if there is + // an error (because we can't find a default branch, or can't find a commit, etc) + try { + // Sadly, the discovery phase with 'forPush' doesn't return symrefs, so we have to + // rely on existing ones. + const ref = await GitRefManager.resolve({ + fs, + gitdir, + ref: `refs/remotes/${remote}/HEAD`, + depth: 2, + }); + const { oid } = await GitRefManager.resolveAgainstMap({ + ref: ref.replace(`refs/remotes/${remote}/`, ''), + fullref: ref, + map: httpRemote.refs, + }); + const oids = [oid]; + for (const oid of await listObjects({ fs, gitdir, oids })) { + skipObjects.add(oid); + } + } catch (e) {} - // Remove objects that we know the remote already has - for (const oid of skipObjects) { - objects.delete(oid); + // Remove objects that we know the remote already has + for (const oid of skipObjects) { + objects.delete(oid); + } } if (!force) { @@ -12149,23 +11058,48 @@ async function _pushToArweave({ } } } - - const packfile = _delete - ? {} - : await _packObjects({ + // We can only safely use capabilities that the server also understands. + // For instance, AWS CodeCommit aborts a push if you include the `agent`!!! + const capabilities = filterCapabilities( + [...httpRemote.capabilities], + ['report-status', 'side-band-64k', `agent=${pkg.agent}`] + ); + const packstream1 = await writeReceivePackRequest({ + capabilities, + triplets: [{ oldoid, oid, fullRef: fullRemoteRef }], + }); + const packstream2 = _delete + ? [] + : await _pack({ fs, gitdir, oids: [...objects], - write: false, }); - - if (objects.size !== 0) - await pushPackfile(arweave, wallet, url, oldoid, oid, packfile); - - await updateRef(arweave, wallet, url, fullRemoteRef, oid); + const res = await GitRemoteHTTP.connect({ + http, + onProgress, + corsProxy, + service: 'git-receive-pack', + url, + auth, + headers, + body: [...packstream1, ...packstream2], + }); + const { packfile, progress } = await GitSideBand.demux(res.body); + if (onMessage) { + const lines = splitLines(progress); + forAwait(lines, async line => { + await onMessage(line); + }); + } + // Parse the response! + const result = await parseReceivePackResponse(packfile); + if (res.headers) { + result.headers = res.headers; + } // Update the local copy of the remote ref - if (remote) { + if (remote && result.ok && result.refs[fullRemoteRef].ok) { // TODO: I think this should actually be using a refspec transform rather than assuming 'refs/remotes/{remote}' const ref = `refs/remotes/${remote}/${fullRemoteRef.replace( 'refs/heads', @@ -12177,15 +11111,15 @@ async function _pushToArweave({ await GitRefManager.writeRef({ fs, gitdir, ref, value: oid }); } } - // if (result.ok && Object.values(result.refs).every(result => result.ok)) { - // return result - // } else { - // const prettyDetails = Object.entries(result.refs) - // .filter(([k, v]) => !v.ok) - // .map(([k, v]) => `\n - ${k}: ${v.error}`) - // .join('') - // throw new GitPushError(prettyDetails, result) - // } + if (result.ok && Object.values(result.refs).every(result => result.ok)) { + return result + } else { + const prettyDetails = Object.entries(result.refs) + .filter(([k, v]) => !v.ok) + .map(([k, v]) => `\n - ${k}: ${v.error}`) + .join(''); + throw new GitPushError(prettyDetails, result) + } } // @ts-check @@ -12219,8 +11153,6 @@ async function _pushToArweave({ * @param {boolean} [args.delete = false] - If true, delete the remote ref * @param {string} [args.corsProxy] - Optional [CORS proxy](https://www.npmjs.com/%40isomorphic-git/cors-proxy). Overrides value in repo config. * @param {Object} [args.headers] - Additional headers to include in HTTP requests, similar to git's `extraHeader` config - * @param {Arweave} [args.arweave] - * @param {ArweaveWallet} [args.wallet] * * @returns {Promise} Resolves successfully when push completes with a detailed description of the operation from the server. * @see PushResult @@ -12238,7 +11170,7 @@ async function _pushToArweave({ * console.log(pushResult) * */ -async function pushToArweave({ +async function push({ fs, http, onProgress, @@ -12256,15 +11188,13 @@ async function pushToArweave({ delete: _delete = false, corsProxy, headers = {}, - arweave, - wallet, }) { try { assertParameter('fs', fs); - // assertParameter('http', http) + assertParameter('http', http); assertParameter('gitdir', gitdir); - return await _pushToArweave({ + return await _push({ fs: new FileSystem(fs), http, onProgress, @@ -12281,11 +11211,9 @@ async function pushToArweave({ delete: _delete, corsProxy, headers, - arweave, - wallet, }) } catch (err) { - err.caller = 'git.pushToArweave'; + err.caller = 'git.push'; throw err } } @@ -14600,7 +13528,6 @@ async function writeTree({ fs, dir, gitdir = join(dir, '.git'), tree }) { // default export var index = { Errors, - Arweave, STAGE, TREE, WORKDIR, @@ -14611,7 +13538,6 @@ var index = { branch, checkout, clone, - cloneFromArweave, commit, getConfig, getConfigAll, @@ -14625,7 +13551,6 @@ var index = { expandRef, fastForward, fetch, - fetchFromArweave, findMergeBase, findRoot, getRemoteInfo, @@ -14645,7 +13570,6 @@ var index = { packObjects, pull, push, - pushToArweave, readBlob, readCommit, readNote, @@ -14671,4 +13595,4 @@ var index = { }; export default index; -export { Arweave, Errors, STAGE, TREE, WORKDIR, add, addNote, addRemote, annotatedTag, branch, checkout, clone, cloneFromArweave, commit, currentBranch, deleteBranch, deleteRef, deleteRemote, deleteTag, expandOid, expandRef, fastForward, fetch, fetchFromArweave, findMergeBase, findRoot, getConfig, getConfigAll, getRemoteInfo, getRemoteInfo2, hashBlob, indexPack, init, isDescendent, listBranches, listFiles, listNotes, listRemotes, listServerRefs, listTags, log, merge, packObjects, pull, push, pushToArweave, readBlob, readCommit, readNote, readObject, readTag, readTree, remove, removeNote, renameBranch, resetIndex, resolveRef, setConfig, status, statusMatrix, tag, version, walk, writeBlob, writeCommit, writeObject, writeRef, writeTag, writeTree }; +export { Errors, STAGE, TREE, WORKDIR, add, addNote, addRemote, annotatedTag, branch, checkout, clone, commit, currentBranch, deleteBranch, deleteRef, deleteRemote, deleteTag, expandOid, expandRef, fastForward, fetch, findMergeBase, findRoot, getConfig, getConfigAll, getRemoteInfo, getRemoteInfo2, hashBlob, indexPack, init, isDescendent, listBranches, listFiles, listNotes, listRemotes, listServerRefs, listTags, log, merge, packObjects, pull, push, readBlob, readCommit, readNote, readObject, readTag, readTree, remove, removeNote, renameBranch, resetIndex, resolveRef, setConfig, status, statusMatrix, tag, version, walk, writeBlob, writeCommit, writeObject, writeRef, writeTag, writeTree }; diff --git a/package-lock.json b/package-lock.json index 5f3ab77be..4f3fd64b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1956,12 +1956,8 @@ "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" - }, - "@types/debug": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz", - "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==" + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true }, "@types/estree": { "version": "0.0.42", @@ -6085,6 +6081,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, "requires": { "ms": "^2.1.1" } @@ -6098,7 +6095,8 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true }, "decamelize-keys": { "version": "1.1.0", @@ -6695,7 +6693,8 @@ "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true }, "emojis-list": { "version": "2.1.0", @@ -13814,7 +13813,8 @@ "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true }, "mute-stream": { "version": "0.0.8", @@ -18768,15 +18768,6 @@ "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", "dev": true }, - "promises-tho": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/promises-tho/-/promises-tho-1.1.0.tgz", - "integrity": "sha512-sr6lVl1T5TbV5TdnwNRunCPWRWcSNs+iiaTzz0A+Hfnup1muqLG90pcLovsdrSdOhD4qGkTFLXyIt027PjAUZA==", - "requires": { - "@types/debug": "4.1.5", - "debug": "4.1.1" - } - }, "promisify-call": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/promisify-call/-/promisify-call-2.0.4.tgz", @@ -19860,7 +19851,8 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true }, "require-main-filename": { "version": "1.0.1", @@ -20822,7 +20814,8 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true }, "set-value": { "version": "2.0.0", @@ -20978,177 +20971,6 @@ "dev": true, "optional": true }, - "smartweave": { - "version": "https://arweave.net/AM-u4X2po-3Tx7fma3lRonCfLwrjI42IALwDL_YFXBs", - "integrity": "sha512-UDE9SM31otNaRPAd+W9oJmeur6Y0pS3vC95K03NRFYeo99rQSa4VgqHpCEnCb+iq3LzT5WyHoIuJYTmXrPoCBw==", - "requires": { - "arweave": "^1.7.1", - "promises-tho": "^1.1.0", - "yargs": "^15.3.1" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" - }, - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" - }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" - }, - "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - } - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } - }, "smtp-connection": { "version": "2.12.0", "resolved": "https://registry.npmjs.org/smtp-connection/-/smtp-connection-2.12.0.tgz", @@ -24259,7 +24081,8 @@ "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true }, "windows-release": { "version": "3.2.0", diff --git a/package.json b/package.json index 7a09a2df3..acd350c06 100644 --- a/package.json +++ b/package.json @@ -63,8 +63,7 @@ "pify": "^4.0.1", "readable-stream": "^3.4.0", "sha.js": "^2.4.9", - "simple-get": "^3.0.2", - "smartweave": "https://arweave.net/AM-u4X2po-3Tx7fma3lRonCfLwrjI42IALwDL_YFXBs" + "simple-get": "^3.0.2" }, "devDependencies": { "@babel/core": "7.6.0", diff --git a/src/api/cloneFromArweave.js b/src/api/cloneFromArweave.js deleted file mode 100644 index b98f617d8..000000000 --- a/src/api/cloneFromArweave.js +++ /dev/null @@ -1,113 +0,0 @@ -// @ts-check -import '../typedefs.js' - -import { _cloneFromArweave } from '../commands/cloneFromArweave.js' -import { FileSystem } from '../models/FileSystem.js' -import { assertParameter } from '../utils/assertParameter.js' -import { join } from '../utils/join.js' - -/** - * Clone a repository - * - * @param {object} args - * @param {FsClient} args.fs - a file system implementation - * @param {HttpClient} args.http - an HTTP client - * @param {ProgressCallback} [args.onProgress] - optional progress event callback - * @param {MessageCallback} [args.onMessage] - optional message event callback - * @param {AuthCallback} [args.onAuth] - optional auth fill callback - * @param {AuthFailureCallback} [args.onAuthFailure] - optional auth rejected callback - * @param {AuthSuccessCallback} [args.onAuthSuccess] - optional auth approved callback - * @param {string} args.dir - The [working tree](dir-vs-gitdir.md) directory path - * @param {string} [args.gitdir=join(dir,'.git')] - [required] The [git directory](dir-vs-gitdir.md) path - * @param {string} args.url - The URL of the remote repository - * @param {string} [args.corsProxy] - Optional [CORS proxy](https://www.npmjs.com/%40isomorphic-git/cors-proxy). Value is stored in the git config file for that repo. - * @param {string} [args.ref] - Which branch to checkout. By default this is the designated "main branch" of the repository. - * @param {boolean} [args.singleBranch = false] - Instead of the default behavior of fetching all the branches, only fetch a single branch. - * @param {boolean} [args.noCheckout = false] - If true, clone will only fetch the repo, not check out a branch. Skipping checkout can save a lot of time normally spent writing files to disk. - * @param {boolean} [args.noTags = false] - By default clone will fetch all tags. `noTags` disables that behavior. - * @param {string} [args.remote = 'origin'] - What to name the remote that is created. - * @param {number} [args.depth] - Integer. Determines how much of the git repository's history to retrieve - * @param {Date} [args.since] - Only fetch commits created after the given date. Mutually exclusive with `depth`. - * @param {string[]} [args.exclude = []] - A list of branches or tags. Instructs the remote server not to send us any commits reachable from these refs. - * @param {boolean} [args.relative = false] - Changes the meaning of `depth` to be measured from the current shallow depth rather than from the branch tip. - * @param {Object} [args.headers = {}] - Additional headers to include in HTTP requests, similar to git's `extraHeader` config - * @param {Arweave} [args.arweave] - * - * @returns {Promise} Resolves successfully when clone completes - * - * @example - * await git.clone({ - * fs, - * http, - * dir: '/tutorial', - * corsProxy: 'https://cors.isomorphic-git.org', - * url: 'https://github.com/isomorphic-git/isomorphic-git', - * singleBranch: true, - * depth: 1 - * }) - * console.log('done') - * - */ -export async function cloneFromArweave({ - fs, - http, - onProgress, - onMessage, - onAuth, - onAuthSuccess, - onAuthFailure, - dir, - gitdir = join(dir, '.git'), - url, - corsProxy = undefined, - ref = undefined, - remote = 'origin', - depth = undefined, - since = undefined, - exclude = [], - relative = false, - singleBranch = false, - noCheckout = false, - noTags = false, - headers = {}, - arweave, -}) { - try { - assertParameter('fs', fs) - // assertParameter('http', http) - assertParameter('gitdir', gitdir) - if (!noCheckout) { - assertParameter('dir', dir) - } - assertParameter('url', url) - - return await _cloneFromArweave({ - fs: new FileSystem(fs), - cache: {}, - http, - onProgress, - onMessage, - onAuth, - onAuthSuccess, - onAuthFailure, - dir, - gitdir, - url, - corsProxy, - ref, - remote, - depth, - since, - exclude, - relative, - singleBranch, - noCheckout, - noTags, - headers, - arweave, - }) - } catch (err) { - err.caller = 'git.cloneFromArweave' - throw err - } -} diff --git a/src/api/fetchFromArweave.js b/src/api/fetchFromArweave.js deleted file mode 100644 index cd6a6fda1..000000000 --- a/src/api/fetchFromArweave.js +++ /dev/null @@ -1,127 +0,0 @@ -// @ts-check -import '../typedefs.js' - -import { _fetchFromArweave } from '../commands/fetchFromArweave.js' -import { FileSystem } from '../models/FileSystem.js' -import { assertParameter } from '../utils/assertParameter.js' -import { join } from '../utils/join.js' - -/** - * - * @typedef {object} FetchResult - The object returned has the following schema: - * @property {string | null} defaultBranch - The branch that is cloned if no branch is specified - * @property {string | null} fetchHead - The SHA-1 object id of the fetched head commit - * @property {string | null} fetchHeadDescription - a textual description of the branch that was fetched - * @property {Object} [headers] - The HTTP response headers returned by the git server - * @property {string[]} [pruned] - A list of branches that were pruned, if you provided the `prune` parameter - * - */ - -/** - * Fetch commits from a remote repository - * - * @param {object} args - * @param {FsClient} args.fs - a file system client - * @param {HttpClient} args.http - an HTTP client - * @param {ProgressCallback} [args.onProgress] - optional progress event callback - * @param {MessageCallback} [args.onMessage] - optional message event callback - * @param {AuthCallback} [args.onAuth] - optional auth fill callback - * @param {AuthFailureCallback} [args.onAuthFailure] - optional auth rejected callback - * @param {AuthSuccessCallback} [args.onAuthSuccess] - optional auth approved callback - * @param {string} [args.dir] - The [working tree](dir-vs-gitdir.md) directory path - * @param {string} [args.gitdir=join(dir,'.git')] - [required] The [git directory](dir-vs-gitdir.md) path - * @param {string} [args.url] - The URL of the remote repository. The default is the value set in the git config for that remote. - * @param {string} [args.remote] - If URL is not specified, determines which remote to use. - * @param {boolean} [args.singleBranch = false] - Instead of the default behavior of fetching all the branches, only fetch a single branch. - * @param {string} [args.ref] - Which branch to fetch if `singleBranch` is true. By default this is the current branch or the remote's default branch. - * @param {string} [args.remoteRef] - The name of the branch on the remote to fetch if `singleBranch` is true. By default this is the configured remote tracking branch. - * @param {boolean} [args.tags = false] - Also fetch tags - * @param {number} [args.depth] - Integer. Determines how much of the git repository's history to retrieve - * @param {boolean} [args.relative = false] - Changes the meaning of `depth` to be measured from the current shallow depth rather than from the branch tip. - * @param {Date} [args.since] - Only fetch commits created after the given date. Mutually exclusive with `depth`. - * @param {string[]} [args.exclude = []] - A list of branches or tags. Instructs the remote server not to send us any commits reachable from these refs. - * @param {boolean} [args.prune] - Delete local remote-tracking branches that are not present on the remote - * @param {boolean} [args.pruneTags] - Prune local tags that don’t exist on the remote, and force-update those tags that differ - * @param {string} [args.corsProxy] - Optional [CORS proxy](https://www.npmjs.com/%40isomorphic-git/cors-proxy). Overrides value in repo config. - * @param {Object} [args.headers] - Additional headers to include in HTTP requests, similar to git's `extraHeader` config - * @param {Arweave} [args.arweave] - * - * @returns {Promise} Resolves successfully when fetch completes - * @see FetchResult - * - * @example - * let result = await git.fetch({ - * fs, - * http, - * dir: '/tutorial', - * corsProxy: 'https://cors.isomorphic-git.org', - * url: 'https://github.com/isomorphic-git/isomorphic-git', - * ref: 'main', - * depth: 1, - * singleBranch: true, - * tags: false - * }) - * console.log(result) - * - */ -export async function fetchFromArweave({ - fs, - http, - onProgress, - onMessage, - onAuth, - onAuthSuccess, - onAuthFailure, - dir, - gitdir = join(dir, '.git'), - ref, - remote, - remoteRef, - url, - corsProxy, - depth = null, - since = null, - exclude = [], - relative = false, - tags = false, - singleBranch = false, - headers = {}, - prune = false, - pruneTags = false, - arweave, -}) { - try { - assertParameter('fs', fs) - // assertParameter('http', http) - assertParameter('gitdir', gitdir) - - return await _fetchFromArweave({ - fs: new FileSystem(fs), - http, - onProgress, - onMessage, - onAuth, - onAuthSuccess, - onAuthFailure, - gitdir, - ref, - remote, - remoteRef, - url, - corsProxy, - depth, - since, - exclude, - relative, - tags, - singleBranch, - headers, - prune, - pruneTags, - arweave, - }) - } catch (err) { - err.caller = 'git.fetchFromArweave' - throw err - } -} diff --git a/src/api/pushToArweave.js b/src/api/pushToArweave.js deleted file mode 100644 index 89e0be49b..000000000 --- a/src/api/pushToArweave.js +++ /dev/null @@ -1,107 +0,0 @@ -// @ts-check -import '../typedefs.js' - -import { _pushToArweave } from '../commands/pushToArweave.js' -import { FileSystem } from '../models/FileSystem.js' -import { assertParameter } from '../utils/assertParameter.js' -import { join } from '../utils/join.js' - -/** - * Push a branch or tag - * - * The push command returns an object that describes the result of the attempted push operation. - * *Notes:* If there were no errors, then there will be no `errors` property. There can be a mix of `ok` messages and `errors` messages. - * - * | param | type [= default] | description | - * | ------ | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | - * | ok | Array\ | The first item is "unpack" if the overall operation was successful. The remaining items are the names of refs that were updated successfully. | - * | errors | Array\ | If the overall operation threw and error, the first item will be "unpack {Overall error message}". The remaining items are individual refs that failed to be updated in the format "{ref name} {error message}". | - * - * @param {object} args - * @param {FsClient} args.fs - a file system client - * @param {HttpClient} args.http - an HTTP client - * @param {ProgressCallback} [args.onProgress] - optional progress event callback - * @param {MessageCallback} [args.onMessage] - optional message event callback - * @param {AuthCallback} [args.onAuth] - optional auth fill callback - * @param {AuthFailureCallback} [args.onAuthFailure] - optional auth rejected callback - * @param {AuthSuccessCallback} [args.onAuthSuccess] - optional auth approved callback - * @param {string} [args.dir] - The [working tree](dir-vs-gitdir.md) directory path - * @param {string} [args.gitdir=join(dir,'.git')] - [required] The [git directory](dir-vs-gitdir.md) path - * @param {string} [args.ref] - Which branch to push. By default this is the currently checked out branch. - * @param {string} [args.url] - The URL of the remote repository. The default is the value set in the git config for that remote. - * @param {string} [args.remote] - If URL is not specified, determines which remote to use. - * @param {string} [args.remoteRef] - The name of the receiving branch on the remote. By default this is the configured remote tracking branch. - * @param {boolean} [args.force = false] - If true, behaves the same as `git push --force` - * @param {boolean} [args.delete = false] - If true, delete the remote ref - * @param {string} [args.corsProxy] - Optional [CORS proxy](https://www.npmjs.com/%40isomorphic-git/cors-proxy). Overrides value in repo config. - * @param {Object} [args.headers] - Additional headers to include in HTTP requests, similar to git's `extraHeader` config - * @param {Arweave} [args.arweave] - * @param {ArweaveWallet} [args.wallet] - * - * @returns {Promise} Resolves successfully when push completes with a detailed description of the operation from the server. - * @see PushResult - * @see RefUpdateStatus - * - * @example - * let pushResult = await git.push({ - * fs, - * http, - * dir: '/tutorial', - * remote: 'origin', - * ref: 'main', - * onAuth: () => ({ username: process.env.GITHUB_TOKEN }), - * }) - * console.log(pushResult) - * - */ -export async function pushToArweave({ - fs, - http, - onProgress, - onMessage, - onAuth, - onAuthSuccess, - onAuthFailure, - dir, - gitdir = join(dir, '.git'), - ref, - remoteRef, - remote = 'origin', - url, - force = false, - delete: _delete = false, - corsProxy, - headers = {}, - arweave, - wallet, -}) { - try { - assertParameter('fs', fs) - // assertParameter('http', http) - assertParameter('gitdir', gitdir) - - return await _pushToArweave({ - fs: new FileSystem(fs), - http, - onProgress, - onMessage, - onAuth, - onAuthSuccess, - onAuthFailure, - gitdir, - ref, - remoteRef, - remote, - url, - force, - delete: _delete, - corsProxy, - headers, - arweave, - wallet, - }) - } catch (err) { - err.caller = 'git.pushToArweave' - throw err - } -} diff --git a/src/commands/cloneFromArweave.js b/src/commands/cloneFromArweave.js deleted file mode 100644 index 1b011f9e4..000000000 --- a/src/commands/cloneFromArweave.js +++ /dev/null @@ -1,109 +0,0 @@ -// @ts-check -import '../typedefs.js' - -import { _addRemote } from '../commands/addRemote.js' -import { _checkout } from '../commands/checkout.js' -import { _fetchFromArweave } from '../commands/fetchFromArweave.js' -import { _init } from '../commands/init.js' -import { GitConfigManager } from '../managers/GitConfigManager.js' -import { parseArgitRemoteURI } from '../utils/arweave' -import { join } from '../utils/join' -/** - * @param {object} args - * @param {import('../models/FileSystem.js').FileSystem} args.fs - * @param {object} args.cache - * @param {HttpClient} args.http - * @param {ProgressCallback} [args.onProgress] - * @param {MessageCallback} [args.onMessage] - * @param {AuthCallback} [args.onAuth] - * @param {AuthFailureCallback} [args.onAuthFailure] - * @param {AuthSuccessCallback} [args.onAuthSuccess] - * @param {string} [args.dir] - * @param {string} args.gitdir - * @param {string} args.url - * @param {string} args.corsProxy - * @param {string} args.ref - * @param {boolean} args.singleBranch - * @param {boolean} args.noCheckout - * @param {boolean} args.noTags - * @param {string} args.remote - * @param {number} args.depth - * @param {Date} args.since - * @param {string[]} args.exclude - * @param {boolean} args.relative - * @param {Object} args.headers - * @param {Arweave} args.arweave - * - * @returns {Promise} Resolves successfully when clone completes - * - */ -export async function _cloneFromArweave({ - fs, - cache, - http, - onProgress, - onMessage, - onAuth, - onAuthSuccess, - onAuthFailure, - dir, - gitdir, - url, - corsProxy, - ref, - remote, - depth, - since, - exclude, - relative, - singleBranch, - noCheckout, - noTags, - headers, - arweave, -}) { - const { repoName } = parseArgitRemoteURI(url) - dir = dir !== '.' ? dir : repoName - gitdir = join(dir, '.git') - await _init({ fs, gitdir }) - await _addRemote({ fs, gitdir, remote, url, force: false }) - if (corsProxy) { - const config = await GitConfigManager.get({ fs, gitdir }) - await config.set(`http.corsProxy`, corsProxy) - await GitConfigManager.save({ fs, gitdir, config }) - } - const { defaultBranch, fetchHead } = await _fetchFromArweave({ - fs, - http, - onProgress, - onMessage, - onAuth, - onAuthSuccess, - onAuthFailure, - gitdir, - ref, - remote, - depth, - since, - exclude, - relative, - singleBranch, - headers, - tags: !noTags, - arweave, - }) - if (fetchHead === null) return - ref = ref || defaultBranch - ref = ref.replace('refs/heads/', '') - // Checkout that branch - await _checkout({ - fs, - cache, - onProgress, - dir, - gitdir, - ref, - remote, - noCheckout, - }) -} diff --git a/src/commands/fetchFromArweave.js b/src/commands/fetchFromArweave.js deleted file mode 100644 index 5b1026cd6..000000000 --- a/src/commands/fetchFromArweave.js +++ /dev/null @@ -1,160 +0,0 @@ -// @ts-check -import '../typedefs.js' - -import { _currentBranch } from '../commands/currentBranch.js' -import { MissingParameterError } from '../errors/MissingParameterError.js' -import { RemoteCapabilityError } from '../errors/RemoteCapabilityError.js' -import { GitConfigManager } from '../managers/GitConfigManager.js' -import { GitRefManager } from '../managers/GitRefManager.js' -import { GitRemoteManager } from '../managers/GitRemoteManager.js' -import { GitShallowManager } from '../managers/GitShallowManager.js' -import { GitCommit } from '../models/GitCommit.js' -import { GitPackIndex } from '../models/GitPackIndex.js' -import { hasObject } from '../storage/hasObject.js' -import { _readObject as readObject } from '../storage/readObject.js' -import { abbreviateRef } from '../utils/abbreviateRef.js' -import { collect } from '../utils/collect.js' -import { emptyPackfile } from '../utils/emptyPackfile.js' -import { filterCapabilities } from '../utils/filterCapabilities.js' -import { forAwait } from '../utils/forAwait.js' -import { join } from '../utils/join.js' -import { pkg } from '../utils/pkg.js' -import { splitLines } from '../utils/splitLines.js' -import { parseUploadPackResponse } from '../wire/parseUploadPackResponse.js' -import { writeUploadPackRequest } from '../wire/writeUploadPackRequest.js' -import { getRefsOnArweave, fetchGitObjects } from '../utils/arweave.js' - -/** - * - * @typedef {object} FetchResult - The object returned has the following schema: - * @property {string | null} defaultBranch - The branch that is cloned if no branch is specified - * @property {string | null} fetchHead - The SHA-1 object id of the fetched head commit - * @property {string | null} fetchHeadDescription - a textual description of the branch that was fetched - * @property {Object} [headers] - The HTTP response headers returned by the git server - * @property {string[]} [pruned] - A list of branches that were pruned, if you provided the `prune` parameter - * - */ - -/** - * @param {object} args - * @param {import('../models/FileSystem.js').FileSystem} args.fs - * @param {import { fetch } from '../../index.d'; -HttpClient} args.http - * @param {ProgressCallback} [args.onProgress] - * @param {MessageCallback} [args.onMessage] - * @param {AuthCallback} [args.onAuth] - * @param {AuthFailureCallback} [args.onAuthFailure] - * @param {AuthSuccessCallback} [args.onAuthSuccess] - * @param {string} args.gitdir - * @param {string|void} [args.url] - * @param {string} [args.corsProxy] - * @param {string} [args.ref] - * @param {string} [args.remoteRef] - * @param {string} [args.remote] - * @param {boolean} [args.singleBranch = false] - * @param {boolean} [args.tags = false] - * @param {number} [args.depth] - * @param {Date} [args.since] - * @param {string[]} [args.exclude = []] - * @param {boolean} [args.relative = false] - * @param {Object} [args.headers] - * @param {boolean} [args.prune] - * @param {boolean} [args.pruneTags] - * @param {Arweave} [args.arweave] - * - * @returns {Promise} - * @see FetchResult - */ -export async function _fetchFromArweave({ - fs, - http, - onProgress, - onMessage, - onAuth, - onAuthSuccess, - onAuthFailure, - gitdir, - ref: _ref, - remoteRef: _remoteRef, - remote: _remote, - url: _url, - corsProxy, - depth = null, - since = null, - exclude = [], - relative = false, - tags = false, - singleBranch = false, - headers = {}, - prune = false, - pruneTags = false, - arweave, -}) { - const ref = _ref || (await _currentBranch({ fs, gitdir, test: true })) - const config = await GitConfigManager.get({ fs, gitdir }) - // Figure out what remote to use. - const remote = - _remote || (ref && (await config.get(`branch.${ref}.remote`))) || 'origin' - // Lookup the URL for the given remote. - const url = _url || (await config.get(`remote.${remote}.url`)) - if (typeof url === 'undefined') { - throw new MissingParameterError('remote OR url') - } - // Figure out what remote ref to use. - const remoteRef = - _remoteRef || - (ref && (await config.get(`branch.${ref}.merge`))) || - _ref || - 'master' - - if (corsProxy === undefined) { - corsProxy = await config.get('http.corsProxy') - } - - const remoteRefs = await getRefsOnArweave(arweave, url) - // For the special case of an empty repository with no refs, return null. - if (remoteRefs.size === 0) { - return { - defaultBranch: null, - fetchHead: null, - fetchHeadDescription: null, - } - } - - // Figure out the SHA for the requested ref - const { oid, fullref } = GitRefManager.resolveAgainstMap({ - ref: remoteRef, - map: remoteRefs, - }) - - const symrefs = new Map() - await GitRefManager.updateRemoteRefs({ - fs, - gitdir, - remote, - refs: remoteRefs, - symrefs, - tags, - prune, - }) - - const objects = await fetchGitObjects(arweave, url) - - // Write objects - await Promise.all( - objects.map(async object => { - const subdirectory = object.oid.substring(0, 2) - const filename = object.oid.substring(2) - const objectPath = `objects/${subdirectory}/${filename}` - const fullpath = join(gitdir, objectPath) - await fs.write(fullpath, object.data) - }) - ) - - const noun = fullref.startsWith('refs/tags') ? 'tag' : 'branch' - return { - defaultBranch: fullref, - fetchHead: oid, - fetchHeadDescription: `${noun} '${abbreviateRef(fullref)}' of ${url}`, - } -} diff --git a/src/commands/pushToArweave.js b/src/commands/pushToArweave.js deleted file mode 100644 index bf08d8900..000000000 --- a/src/commands/pushToArweave.js +++ /dev/null @@ -1,253 +0,0 @@ -// @ts-check -import '../typedefs.js' - -import { _currentBranch } from '../commands/currentBranch.js' -import { _findMergeBase } from '../commands/findMergeBase.js' -import { _isDescendent } from '../commands/isDescendent.js' -import { listCommitsAndTags } from '../commands/listCommitsAndTags.js' -import { listObjects } from '../commands/listObjects.js' -import { _pack } from '../commands/pack.js' -import { GitPushError } from '../errors/GitPushError.js' -import { MissingParameterError } from '../errors/MissingParameterError.js' -import { NotFoundError } from '../errors/NotFoundError.js' -import { PushRejectedError } from '../errors/PushRejectedError.js' -import { GitConfigManager } from '../managers/GitConfigManager.js' -import { GitRefManager } from '../managers/GitRefManager.js' -import { GitRemoteManager } from '../managers/GitRemoteManager.js' -import { GitSideBand } from '../models/GitSideBand.js' -import { filterCapabilities } from '../utils/filterCapabilities.js' -import { forAwait } from '../utils/forAwait.js' -import { pkg } from '../utils/pkg.js' -import { splitLines } from '../utils/splitLines.js' -import { parseReceivePackResponse } from '../wire/parseReceivePackResponse.js' -import { writeReceivePackRequest } from '../wire/writeReceivePackRequest.js' -import { _packObjects } from './packObjects.js' -import { getRefsOnArweave, pushPackfile, updateRef } from '../utils/arweave.js' -import { getOidByRef } from '../utils/graphql.js' - -/** - * @param {object} args - * @param {import('../models/FileSystem.js').FileSystem} args.fs - * @param {import { pushToArweave } from '../../index.d'; -HttpClient}import { getOidByRef } from '../utils/graphql'; - args.http - * @param {ProgressCallback} [args.onProgress] - * @param {MessageCallback} [args.onMessage] - * @param {AuthCallback} [args.onAuth] - * @param {AuthFailureCallback} [args.onAuthFailure] - * @param {AuthSuccessCallback} [args.onAuthSuccess] - * @param {string} args.gitdir - * @param {string} [args.ref] - * @param {string} [args.remoteRef] - * @param {string} [args.remote] - * @param {boolean} [args.force = false] - * @param {boolean} [args.delete = false] - * @param {string} [args.url] - * @param {string} [args.corsProxy] - * @param {Object} [args.headers] - * @param {Arweave} [args.arweave] - * @param {ArweaveWallet} [args.wallet] - * - * @returns {Promise} - */ -export async function _pushToArweave({ - fs, - http, - onProgress, - onMessage, - onAuth, - onAuthSuccess, - onAuthFailure, - gitdir, - ref: _ref, - remoteRef: _remoteRef, - remote, - url: _url, - force = false, - delete: _delete = false, - corsProxy, - headers = {}, - arweave, - wallet, -}) { - const ref = _ref || (await _currentBranch({ fs, gitdir })) - if (typeof ref === 'undefined') { - throw new MissingParameterError('ref') - } - const config = await GitConfigManager.get({ fs, gitdir }) - // Figure out what remote to use. - remote = - remote || - (await config.get(`branch.${ref}.pushRemote`)) || - (await config.get('remote.pushDefault')) || - (await config.get(`branch.${ref}.remote`)) || - 'origin' - // Lookup the URL for the given remote. - const url = - _url || - (await config.get(`remote.${remote}.pushurl`)) || - (await config.get(`remote.${remote}.url`)) - if (typeof url === 'undefined') { - throw new MissingParameterError('remote OR url') - } - // Figure out what remote ref to use. - const remoteRef = _remoteRef || (await config.get(`branch.${ref}.merge`)) - if (typeof url === 'undefined') { - throw new MissingParameterError('remoteRef') - } - - if (corsProxy === undefined) { - corsProxy = await config.get('http.corsProxy') - } - - const fullRef = await GitRefManager.expand({ fs, gitdir, ref }) - const oid = _delete - ? '0000000000000000000000000000000000000000' - : await GitRefManager.resolve({ fs, gitdir, ref: fullRef }) - - // const arweave = Arweave.init({ - // host: 'arweave.net', - // port: 443, - // protocol: 'https', - // }) - - let fullRemoteRef - if (!remoteRef) { - fullRemoteRef = fullRef - } else { - try { - fullRemoteRef = await GitRefManager.expandAgainstMap({ - ref: remoteRef, - map: await getRefsOnArweave(arweave, url), - }) - } catch (err) { - if (err instanceof NotFoundError) { - // The remote reference doesn't exist yet. - // If it is fully specified, use that value. Otherwise, treat it as a branch. - fullRemoteRef = remoteRef.startsWith('refs/') - ? remoteRef - : `refs/heads/${remoteRef}` - } else { - throw err - } - } - } - - const oldoid = await getOidByRef(arweave, url, fullRemoteRef) - - let objects = new Set() - if (!_delete) { - // const finish = [...httpRemote.refs.values()] - // all refs on the remote - const finish = [] - let skipObjects = new Set() - - // If remote branch is present, look for a common merge base. - if (oldoid !== '0000000000000000000000000000000000000000') { - // trick to speed up common force push scenarios - const mergebase = await _findMergeBase({ - fs, - gitdir, - oids: [oid, oldoid], - }) - for (const oid of mergebase) finish.push(oid) - // thinpack - skipObjects = await listObjects({ fs, gitdir, oids: mergebase }) - } - - // If remote does not have the commit, figure out the objects to send - if (!finish.includes(oid)) { - const commits = await listCommitsAndTags({ - fs, - gitdir, - start: [oid], - finish, - }) - objects = await listObjects({ fs, gitdir, oids: commits }) - } - - //thinpack - // If there's a default branch for the remote lets skip those objects too. - // Since this is an optional optimization, we just catch and continue if there is - // an error (because we can't find a default branch, or can't find a commit, etc) - try { - // Sadly, the discovery phase with 'forPush' doesn't return symrefs, so we have to - // rely on existing ones. - const ref = await GitRefManager.resolve({ - fs, - gitdir, - ref: `refs/remotes/${remote}/HEAD`, - depth: 2, - }) - const { oid } = await GitRefManager.resolveAgainstMap({ - ref: ref.replace(`refs/remotes/${remote}/`, ''), - fullref: ref, - map: new Map(), - }) - const oids = [oid] - for (const oid of await listObjects({ fs, gitdir, oids })) { - skipObjects.add(oid) - } - } catch (e) {} - - // Remove objects that we know the remote already has - for (const oid of skipObjects) { - objects.delete(oid) - } - - if (!force) { - // Is it a tag that already exists? - if ( - fullRef.startsWith('refs/tags') && - oldoid !== '0000000000000000000000000000000000000000' - ) { - throw new PushRejectedError('tag-exists') - } - // Is it a non-fast-forward commit? - if ( - oid !== '0000000000000000000000000000000000000000' && - oldoid !== '0000000000000000000000000000000000000000' && - !(await _isDescendent({ fs, gitdir, oid, ancestor: oldoid, depth: -1 })) - ) { - throw new PushRejectedError('not-fast-forward') - } - } - } - - const packfile = _delete - ? {} - : await _packObjects({ - fs, - gitdir, - oids: [...objects], - write: false, - }) - - if (objects.size !== 0) - await pushPackfile(arweave, wallet, url, oldoid, oid, packfile) - - await updateRef(arweave, wallet, url, fullRemoteRef, oid) - - // Update the local copy of the remote ref - if (remote) { - // TODO: I think this should actually be using a refspec transform rather than assuming 'refs/remotes/{remote}' - const ref = `refs/remotes/${remote}/${fullRemoteRef.replace( - 'refs/heads', - '' - )}` - if (_delete) { - await GitRefManager.deleteRef({ fs, gitdir, ref }) - } else { - await GitRefManager.writeRef({ fs, gitdir, ref, value: oid }) - } - } - // if (result.ok && Object.values(result.refs).every(result => result.ok)) { - // return result - // } else { - // const prettyDetails = Object.entries(result.refs) - // .filter(([k, v]) => !v.ok) - // .map(([k, v]) => `\n - ${k}: ${v.error}`) - // .join('') - // throw new GitPushError(prettyDetails, result) - // } -} diff --git a/src/index.js b/src/index.js index 5ff8d1b4e..6148f77de 100644 --- a/src/index.js +++ b/src/index.js @@ -10,7 +10,6 @@ import { annotatedTag } from './api/annotatedTag.js' import { branch } from './api/branch.js' import { checkout } from './api/checkout.js' import { clone } from './api/clone.js' -import { cloneFromArweave } from './api/cloneFromArweave.js' import { commit } from './api/commit.js' import { currentBranch } from './api/currentBranch.js' import { deleteBranch } from './api/deleteBranch.js' @@ -21,7 +20,6 @@ import { expandOid } from './api/expandOid.js' import { expandRef } from './api/expandRef.js' import { fastForward } from './api/fastForward.js' import { fetch } from './api/fetch.js' -import { fetchFromArweave } from './api/fetchFromArweave.js' import { findMergeBase } from './api/findMergeBase.js' import { findRoot } from './api/findRoot.js' import { getConfig } from './api/getConfig.js' @@ -43,7 +41,6 @@ import { merge } from './api/merge.js' import { packObjects } from './api/packObjects.js' import { pull } from './api/pull.js' import { push } from './api/push.js' -import { pushToArweave } from './api/pushToArweave.js' import { readBlob } from './api/readBlob.js' import { readCommit } from './api/readCommit.js' import { readNote } from './api/readNote.js' @@ -68,12 +65,10 @@ import { writeRef } from './api/writeRef.js' import { writeTag } from './api/writeTag.js' import { writeTree } from './api/writeTree.js' import * as Errors from './errors/index.js' -import * as Arweave from './utils/arweave.js' // named exports export { Errors, - Arweave, STAGE, TREE, WORKDIR, @@ -84,7 +79,6 @@ export { branch, checkout, clone, - cloneFromArweave, commit, getConfig, getConfigAll, @@ -98,7 +92,6 @@ export { expandRef, fastForward, fetch, - fetchFromArweave, findMergeBase, findRoot, getRemoteInfo, @@ -118,7 +111,6 @@ export { packObjects, pull, push, - pushToArweave, readBlob, readCommit, readNote, @@ -146,7 +138,6 @@ export { // default export export default { Errors, - Arweave, STAGE, TREE, WORKDIR, @@ -157,7 +148,6 @@ export default { branch, checkout, clone, - cloneFromArweave, commit, getConfig, getConfigAll, @@ -171,7 +161,6 @@ export default { expandRef, fastForward, fetch, - fetchFromArweave, findMergeBase, findRoot, getRemoteInfo, @@ -191,7 +180,6 @@ export default { packObjects, pull, push, - pushToArweave, readBlob, readCommit, readNote, diff --git a/src/utils/arweave.js b/src/utils/arweave.js index d8dcc1156..0c8734671 100644 --- a/src/utils/arweave.js +++ b/src/utils/arweave.js @@ -1,30 +1,9 @@ -import * as smartweave from 'smartweave' -import { getTransactionIdByObjectId } from './graphql' +import axios from 'axios' + +const graphQlEndpoint = 'https://arweave.net/graphql' // prettier-ignore const argitRemoteURIRegex = '^dgit:\/\/([a-zA-Z0-9-_]{43})\/([A-Za-z0-9_.-]*)' -const contractId = 'N9Vfr_3Rw95111UJ6eaT7scGZzDCd2zzpja890758Qc' - -const repoQuery = remoteURI => { - const { repoOwnerAddress, repoName } = parseArgitRemoteURI(remoteURI) - return { - op: 'and', - expr1: { - op: 'and', - expr1: { - op: 'equals', - expr1: 'App-Name', - expr2: 'dgit', - }, - expr2: { - op: 'equals', - expr1: 'from', - expr2: repoOwnerAddress, - }, - }, - expr2: { op: 'equals', expr1: 'Repo', expr2: repoName }, - } -} export function parseArgitRemoteURI(remoteURI) { const matchGroups = remoteURI.match(argitRemoteURIRegex) @@ -34,154 +13,153 @@ export function parseArgitRemoteURI(remoteURI) { return { repoOwnerAddress, repoName } } -function addTransactionTags(tx, repo, txType) { - tx.addTag('Repo', repo) - tx.addTag('Type', txType) - tx.addTag('Content-Type', 'application/json') - tx.addTag('App-Name', 'dgit') - tx.addTag('version', '0.0.1') - tx.addTag('Unix-Time', Math.round(new Date().getTime() / 1000)) // Add Unix timestamp - return tx +export async function fetchGitObject(arweave, remoteURI, oid) { + const id = await getTransactionIdByObjectId(remoteURI, oid) + return await arweave.transactions.getData(id, { decode: true }) } -export async function updateRef(arweave, wallet, remoteURI, name, ref) { - const { repoName } = parseArgitRemoteURI(remoteURI) - let tx = await arweave.createTransaction({ data: ref }, wallet) - tx = addTransactionTags(tx, repoName, 'update-ref') - tx.addTag('ref', name) - - await arweave.transactions.sign(tx, wallet) // Sign transaction - arweave.transactions.post(tx) // Post transaction +const getTagValue = (tagName, tags) => { + for (const tag of tags) { + if (tag.name === tagName) { + return tag.value + } + } } -export async function pushPackfile( - arweave, - wallet, - remoteURI, - oldoid, - oid, - packfile -) { - const { repoName } = parseArgitRemoteURI(remoteURI) - - let tx = await arweave.createTransaction({ data: packfile.packfile }, wallet) - tx = addTransactionTags(tx, repoName, 'send-pack') - tx.addTag('oid', oid) - tx.addTag('oldoid', oldoid) - tx.addTag('filename', packfile.filename) - - await arweave.transactions.sign(tx, wallet) - let uploader = await arweave.transactions.getUploader(tx) - - while (!uploader.isComplete) { - await uploader.uploadChunk() - console.log( - `${uploader.pctComplete}% complete, ${uploader.uploadedChunks}/${uploader.totalChunks}` - ) +export const getOidByRef = async (arweave, remoteURI, ref) => { + const { repoOwnerAddress, repoName } = parseArgitRemoteURI(remoteURI) + const { data } = await axios({ + url: graphQlEndpoint, + method: 'post', + data: { + query: ` + query { + transactions( + owners: ["${repoOwnerAddress}"] + tags: [ + { name: "Repo", values: ["${repoName}"] } + { name: "version", values: ["0.0.2"] } + { name: "ref", values: ["${ref}"] } + { name: "Type", values: ["update-ref"] } + { name: "App-Name", values: ["dgit"] } + ] + first: 10 + ) { + edges { + node { + id + tags { + name + value + } + block { + height + } + } + } + } + }`, + }, + }) + + const edges = data.data.transactions.edges + if (edges.length === 0) { + return '0000000000000000000000000000000000000000' } - // Send fee to PST holders - const contractState = await smartweave.readContract(arweave, contractId) - const holder = smartweave.selectWeightedPstHolder(contractState.balances) - // send a fee. You should inform the user about this fee and amount. - const pstTx = await arweave.createTransaction( - { target: holder, quantity: arweave.ar.arToWinston('0.01') }, - wallet - ) - pstTx.addTag('App-Name', 'dgit') - pstTx.addTag('version', '0.0.1') - - await arweave.transactions.sign(pstTx, wallet) - await arweave.transactions.post(pstTx) -} + edges.sort((a, b) => { + if ((b.node.block.height - a.node.block.height) < 50) { + const bUnixTime = Number(getTagValue("Unix-Time", b.node.tags)) + const aUnixTime = Number(getTagValue("Unix-Time", a.node.tags)) + return bUnixTime - aUnixTime + } + return 0 + }) -export async function fetchPackfiles(arweave, remoteURI) { - const query = { - op: 'and', - expr1: repoQuery(remoteURI), - expr2: { op: 'equals', expr1: 'Type', expr2: 'send-pack' }, - } - const txids = await arweave.arql(query) - const packfiles = await Promise.all( - txids.map(async txid => { - const tx = await arweave.transactions.get(txid) - let filename = '' - tx.get('tags').forEach(tag => { - const key = tag.get('name', { decode: true, string: true }) - const value = tag.get('value', { decode: true, string: true }) - if (key === 'filename') filename = value - }) - const data = await arweave.transactions.getData(txid, { decode: true }) - return { data, filename } - }) - ) - return packfiles + const id = edges[0].node.id + return await arweave.transactions.getData(id, { + decode: true, + string: true, + }) } -export async function fetchGitObject(arweave, remoteURI, oid) { - const id = await getTransactionIdByObjectId(remoteURI, oid) - return await arweave.transactions.getData(id, { decode: true }) -} +export const getAllRefs = async (arweave, remoteURI) => { + let refs = new Set(); + let refOidObj = {}; + const { repoOwnerAddress, repoName } = parseArgitRemoteURI(remoteURI); + const { data } = await axios({ + url: graphQlEndpoint, + method: "post", + data: { + query: ` + query { + transactions( + owners: ["${repoOwnerAddress}"] + tags: [ + { name: "Repo", values: ["${repoName}"] } + { name: "Type", values: ["update-ref"] } + { name: "App-Name", values: ["dgit"] } + ] + ) { + edges { + node { + tags { + name + value + } + } + } + } + }`, + }, + }); + + const edges = data.data.transactions.edges; -export async function fetchGitObjects(arweave, remoteURI) { - const query = { - op: 'and', - expr1: repoQuery(remoteURI), - expr2: { op: 'equals', expr1: 'Type', expr2: 'push-git-object' }, + for (const edge of edges) { + for (const tag of edge.node.tags) { + if (tag.name === "ref") { + refs.add(tag.value); + break + } + } } - const txids = await arweave.arql(query) - const objects = await Promise.all( - txids.map(async txid => { - const tx = await arweave.transactions.get(txid) - let oid = '' - tx.get('tags').forEach(tag => { - const key = tag.get('name', { decode: true, string: true }) - const value = tag.get('value', { decode: true, string: true }) - if (key === 'oid') oid = value - }) - const data = await arweave.transactions.getData(txid, { decode: true }) - return { data, oid } - }) - ) - return objects -} -export async function getRefsOnArweave(arweave, remoteURI) { - const refs = new Map() - const query = { - op: 'and', - expr1: repoQuery(remoteURI), - expr2: { op: 'equals', expr1: 'Type', expr2: 'update-ref' }, + for (const ref of refs) { + refOidObj[ref] = await getOidByRef(arweave, remoteURI, ref); } - const txids = await arweave.arql(query) - const tx_rows = await Promise.all( - txids.map(async txid => { - let ref = {} - const tx = await arweave.transactions.get(txid) - tx.get('tags').forEach(tag => { - const key = tag.get('name', { decode: true, string: true }) - const value = tag.get('value', { decode: true, string: true }) - if (key === 'Unix-Time') ref.unixTime = value - else if (key === 'ref') ref.name = value - }) - - ref.oid = await arweave.transactions.getData(txid, { - decode: true, - string: true, - }) - - return ref - }) - ) - - // descending order - tx_rows.sort((a, b) => { - Number(b.unixTime) - Number(a.unixTime) - }) - tx_rows.forEach(ref => { - if (!refs.has(ref.name)) refs.set(ref.name, ref.oid) + return refOidObj; +}; + +export const getTransactionIdByObjectId = async (remoteURI, oid) => { + const { repoOwnerAddress, repoName } = parseArgitRemoteURI(remoteURI) + const { data } = await axios({ + url: graphQlEndpoint, + method: 'post', + data: { + query: ` + query { + transactions( + owners: ["${repoOwnerAddress}"] + tags: [ + { name: "oid", values: ["${oid}"] } + { name: "Repo", values: ["${repoName}"] } + { name: "Type", values: ["push-git-object"] } + { name: "App-Name", values: ["dgit"] } + ] + first: 1 + ) { + edges { + node { + id + } + } + } + }`, + }, }) - return refs + const edges = data.data.transactions.edges + return edges[0].node.id } diff --git a/src/utils/graphql.js b/src/utils/graphql.js deleted file mode 100644 index 984c3e9e1..000000000 --- a/src/utils/graphql.js +++ /dev/null @@ -1,77 +0,0 @@ -import axios from 'axios' -import { parseArgitRemoteURI } from './arweave' - -const graphQlEndpoint = 'https://arweave.net/graphql' - -export const getOidByRef = async (arweave, remoteURI, ref) => { - const { repoOwnerAddress, repoName } = parseArgitRemoteURI(remoteURI) - const { data } = await axios({ - url: graphQlEndpoint, - method: 'post', - data: { - query: ` - query { - transactions( - owners: ["${repoOwnerAddress}"] - tags: [ - { name: "Repo", values: ["${repoName}"] } - { name: "ref", values: ["${ref}"] } - { name: "Type", values: ["update-ref"] } - { name: "App-Name", values: ["dgit"] } - ] - first: 1 - ) { - edges { - node { - id - } - } - } - }`, - }, - }) - - const edges = data.data.transactions.edges - - if (edges.length === 0) { - return '0000000000000000000000000000000000000000' - } - - const id = edges[0].node.id - return await arweave.transactions.getData(id, { - decode: true, - string: true, - }) -} - -export const getTransactionIdByObjectId = async (remoteURI, oid) => { - const { repoOwnerAddress, repoName } = parseArgitRemoteURI(remoteURI) - const { data } = await axios({ - url: graphQlEndpoint, - method: 'post', - data: { - query: ` - query { - transactions( - owners: ["${repoOwnerAddress}"] - tags: [ - { name: "oid", values: ["${oid}"] } - { name: "Repo", values: ["${repoName}"] } - { name: "Type", values: ["push-git-object"] } - { name: "App-Name", values: ["dgit"] } - ] - first: 1 - ) { - edges { - node { - id - } - } - } - }`, - }, - }) - - const edges = data.data.transactions.edges - return edges[0].node.id -} From cc6aeef054b4d5398a35b80a17a2326bea1426e0 Mon Sep 17 00:00:00 2001 From: faza Date: Fri, 30 Oct 2020 02:41:41 +0530 Subject: [PATCH 2/8] Changes related to gitopia --- src/utils/arweave.js | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/utils/arweave.js b/src/utils/arweave.js index 0c8734671..e8f70f87a 100644 --- a/src/utils/arweave.js +++ b/src/utils/arweave.js @@ -3,7 +3,7 @@ import axios from 'axios' const graphQlEndpoint = 'https://arweave.net/graphql' // prettier-ignore -const argitRemoteURIRegex = '^dgit:\/\/([a-zA-Z0-9-_]{43})\/([A-Za-z0-9_.-]*)' +const argitRemoteURIRegex = '^gitopia:\/\/([a-zA-Z0-9-_]{43})\/([A-Za-z0-9_.-]*)' export function parseArgitRemoteURI(remoteURI) { const matchGroups = remoteURI.match(argitRemoteURIRegex) @@ -38,10 +38,10 @@ export const getOidByRef = async (arweave, remoteURI, ref) => { owners: ["${repoOwnerAddress}"] tags: [ { name: "Repo", values: ["${repoName}"] } - { name: "version", values: ["0.0.2"] } - { name: "ref", values: ["${ref}"] } + { name: "Version", values: ["0.0.2"] } + { name: "Ref", values: ["${ref}"] } { name: "Type", values: ["update-ref"] } - { name: "App-Name", values: ["dgit"] } + { name: "App-Name", values: ["gitopia"] } ] first: 10 ) { @@ -97,8 +97,9 @@ export const getAllRefs = async (arweave, remoteURI) => { owners: ["${repoOwnerAddress}"] tags: [ { name: "Repo", values: ["${repoName}"] } + { name: "Version", values: ["0.0.2"] } { name: "Type", values: ["update-ref"] } - { name: "App-Name", values: ["dgit"] } + { name: "App-Name", values: ["gitopia"] } ] ) { edges { @@ -118,7 +119,7 @@ export const getAllRefs = async (arweave, remoteURI) => { for (const edge of edges) { for (const tag of edge.node.tags) { - if (tag.name === "ref") { + if (tag.name === "Ref") { refs.add(tag.value); break } @@ -143,10 +144,11 @@ export const getTransactionIdByObjectId = async (remoteURI, oid) => { transactions( owners: ["${repoOwnerAddress}"] tags: [ - { name: "oid", values: ["${oid}"] } + { name: "Oid", values: ["${oid}"] } + { name: "Version", values: ["0.0.2"] } { name: "Repo", values: ["${repoName}"] } - { name: "Type", values: ["push-git-object"] } - { name: "App-Name", values: ["dgit"] } + { name: "Type", values: ["git-object"] } + { name: "App-Name", values: ["gitopia"] } ] first: 1 ) { From 385c0fde13210d7c48e5073874cf61909ca6cbd2 Mon Sep 17 00:00:00 2001 From: faza Date: Fri, 30 Oct 2020 12:58:37 +0530 Subject: [PATCH 3/8] Return numCommits along with ref oid --- src/utils/arweave.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/utils/arweave.js b/src/utils/arweave.js index e8f70f87a..9d2ba80d0 100644 --- a/src/utils/arweave.js +++ b/src/utils/arweave.js @@ -64,7 +64,10 @@ export const getOidByRef = async (arweave, remoteURI, ref) => { const edges = data.data.transactions.edges if (edges.length === 0) { - return '0000000000000000000000000000000000000000' + return { + oid: '0000000000000000000000000000000000000000', + numCommits: 0 + } } edges.sort((a, b) => { @@ -77,10 +80,12 @@ export const getOidByRef = async (arweave, remoteURI, ref) => { }) const id = edges[0].node.id - return await arweave.transactions.getData(id, { + const data = await arweave.transactions.getData(id, { decode: true, string: true, }) + + return JSON.parse(data) } export const getAllRefs = async (arweave, remoteURI) => { From df6b4e0c9bf09831295ab82551b3ab56074902e8 Mon Sep 17 00:00:00 2001 From: faza Date: Fri, 30 Oct 2020 14:32:08 +0530 Subject: [PATCH 4/8] Fix variable error --- src/utils/arweave.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/arweave.js b/src/utils/arweave.js index 9d2ba80d0..c49b85865 100644 --- a/src/utils/arweave.js +++ b/src/utils/arweave.js @@ -80,12 +80,12 @@ export const getOidByRef = async (arweave, remoteURI, ref) => { }) const id = edges[0].node.id - const data = await arweave.transactions.getData(id, { + const response = await arweave.transactions.getData(id, { decode: true, string: true, }) - return JSON.parse(data) + return JSON.parse(response) } export const getAllRefs = async (arweave, remoteURI) => { From 1d8453c8f246786d56a71306d11e1c82f4e97b02 Mon Sep 17 00:00:00 2001 From: faza Date: Fri, 30 Oct 2020 16:05:40 +0530 Subject: [PATCH 5/8] Fix getAllRefs method --- src/utils/arweave.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/utils/arweave.js b/src/utils/arweave.js index c49b85865..5d354740b 100644 --- a/src/utils/arweave.js +++ b/src/utils/arweave.js @@ -65,7 +65,7 @@ export const getOidByRef = async (arweave, remoteURI, ref) => { const edges = data.data.transactions.edges if (edges.length === 0) { return { - oid: '0000000000000000000000000000000000000000', + oid: null, numCommits: 0 } } @@ -132,7 +132,8 @@ export const getAllRefs = async (arweave, remoteURI) => { } for (const ref of refs) { - refOidObj[ref] = await getOidByRef(arweave, remoteURI, ref); + const { oid } = await getOidByRef(arweave, remoteURI, ref); + refOidObj[ref] = oid } return refOidObj; From a0a05f823721eaef80c3a00105b72ac61de8b336 Mon Sep 17 00:00:00 2001 From: faza Date: Fri, 30 Oct 2020 19:23:21 +0530 Subject: [PATCH 6/8] Optimize graphql query --- src/utils/arweave.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/arweave.js b/src/utils/arweave.js index 5d354740b..936958cf3 100644 --- a/src/utils/arweave.js +++ b/src/utils/arweave.js @@ -37,10 +37,10 @@ export const getOidByRef = async (arweave, remoteURI, ref) => { transactions( owners: ["${repoOwnerAddress}"] tags: [ + { name: "Type", values: ["update-ref"] } { name: "Repo", values: ["${repoName}"] } { name: "Version", values: ["0.0.2"] } { name: "Ref", values: ["${ref}"] } - { name: "Type", values: ["update-ref"] } { name: "App-Name", values: ["gitopia"] } ] first: 10 @@ -101,9 +101,9 @@ export const getAllRefs = async (arweave, remoteURI) => { transactions( owners: ["${repoOwnerAddress}"] tags: [ + { name: "Type", values: ["update-ref"] } { name: "Repo", values: ["${repoName}"] } { name: "Version", values: ["0.0.2"] } - { name: "Type", values: ["update-ref"] } { name: "App-Name", values: ["gitopia"] } ] ) { From 8ee801623becc27517555e09bf5ab0ad90f90733 Mon Sep 17 00:00:00 2001 From: faza Date: Mon, 2 Nov 2020 01:51:45 +0530 Subject: [PATCH 7/8] Fix app name --- src/utils/arweave.js | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/utils/arweave.js b/src/utils/arweave.js index 936958cf3..6dffd70bb 100644 --- a/src/utils/arweave.js +++ b/src/utils/arweave.js @@ -41,7 +41,7 @@ export const getOidByRef = async (arweave, remoteURI, ref) => { { name: "Repo", values: ["${repoName}"] } { name: "Version", values: ["0.0.2"] } { name: "Ref", values: ["${ref}"] } - { name: "App-Name", values: ["gitopia"] } + { name: "App-Name", values: ["Gitopia"] } ] first: 10 ) { @@ -66,14 +66,14 @@ export const getOidByRef = async (arweave, remoteURI, ref) => { if (edges.length === 0) { return { oid: null, - numCommits: 0 + numCommits: 0, } } edges.sort((a, b) => { - if ((b.node.block.height - a.node.block.height) < 50) { - const bUnixTime = Number(getTagValue("Unix-Time", b.node.tags)) - const aUnixTime = Number(getTagValue("Unix-Time", a.node.tags)) + if (b.node.block.height - a.node.block.height < 50) { + const bUnixTime = Number(getTagValue('Unix-Time', b.node.tags)) + const aUnixTime = Number(getTagValue('Unix-Time', a.node.tags)) return bUnixTime - aUnixTime } return 0 @@ -89,22 +89,23 @@ export const getOidByRef = async (arweave, remoteURI, ref) => { } export const getAllRefs = async (arweave, remoteURI) => { - let refs = new Set(); - let refOidObj = {}; - const { repoOwnerAddress, repoName } = parseArgitRemoteURI(remoteURI); + let refs = new Set() + let refOidObj = {} + const { repoOwnerAddress, repoName } = parseArgitRemoteURI(remoteURI) const { data } = await axios({ url: graphQlEndpoint, - method: "post", + method: 'post', data: { query: ` query { transactions( + first: 2147483647 owners: ["${repoOwnerAddress}"] tags: [ { name: "Type", values: ["update-ref"] } { name: "Repo", values: ["${repoName}"] } { name: "Version", values: ["0.0.2"] } - { name: "App-Name", values: ["gitopia"] } + { name: "App-Name", values: ["Gitopia"] } ] ) { edges { @@ -118,26 +119,26 @@ export const getAllRefs = async (arweave, remoteURI) => { } }`, }, - }); + }) - const edges = data.data.transactions.edges; + const edges = data.data.transactions.edges for (const edge of edges) { for (const tag of edge.node.tags) { - if (tag.name === "Ref") { - refs.add(tag.value); + if (tag.name === 'Ref') { + refs.add(tag.value) break } } } for (const ref of refs) { - const { oid } = await getOidByRef(arweave, remoteURI, ref); + const { oid } = await getOidByRef(arweave, remoteURI, ref) refOidObj[ref] = oid } - return refOidObj; -}; + return refOidObj +} export const getTransactionIdByObjectId = async (remoteURI, oid) => { const { repoOwnerAddress, repoName } = parseArgitRemoteURI(remoteURI) @@ -154,7 +155,7 @@ export const getTransactionIdByObjectId = async (remoteURI, oid) => { { name: "Version", values: ["0.0.2"] } { name: "Repo", values: ["${repoName}"] } { name: "Type", values: ["git-object"] } - { name: "App-Name", values: ["gitopia"] } + { name: "App-Name", values: ["Gitopia"] } ] first: 1 ) { From 0af59698c0c489d89994d92331165ff465b5ae39 Mon Sep 17 00:00:00 2001 From: faza Date: Fri, 4 Dec 2020 17:23:08 +0530 Subject: [PATCH 8/8] Fix getOidByRef method --- src/utils/arweave.js | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/utils/arweave.js b/src/utils/arweave.js index 6dffd70bb..5c44f4329 100644 --- a/src/utils/arweave.js +++ b/src/utils/arweave.js @@ -43,18 +43,11 @@ export const getOidByRef = async (arweave, remoteURI, ref) => { { name: "Ref", values: ["${ref}"] } { name: "App-Name", values: ["Gitopia"] } ] - first: 10 + first: 1 ) { edges { node { id - tags { - name - value - } - block { - height - } } } } @@ -70,15 +63,6 @@ export const getOidByRef = async (arweave, remoteURI, ref) => { } } - edges.sort((a, b) => { - if (b.node.block.height - a.node.block.height < 50) { - const bUnixTime = Number(getTagValue('Unix-Time', b.node.tags)) - const aUnixTime = Number(getTagValue('Unix-Time', a.node.tags)) - return bUnixTime - aUnixTime - } - return 0 - }) - const id = edges[0].node.id const response = await arweave.transactions.getData(id, { decode: true,