UNPKG

git-documentdb

Version:

Offline-first database that syncs with Git

279 lines 11.5 kB
"use strict"; /* eslint-disable no-await-in-loop */ /** * GitDocumentDB * Copyright (c) Hidekazu Kubota * * This source code is licensed under the Mozilla Public License Version 2.0 * found in the LICENSE file in the root directory of gitDDB source tree. */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.syncWorker = void 0; const isomorphic_git_1 = __importDefault(require("isomorphic-git")); const fs_extra_1 = __importDefault(require("fs-extra")); const const_1 = require("../const"); const utils_1 = require("../utils"); const push_worker_1 = require("./push_worker"); const worker_utils_1 = require("./worker_utils"); const _3way_merge_1 = require("./3way_merge"); const remote_engine_1 = require("./remote_engine"); const error_1 = require("../error"); /** * sync_worker * * @throws {@link Err.NoMergeBaseFoundError} * @throws {@link Err.ThreeWayMergeError} * @throws {@link Err.CannotDeleteDataError} * * @throws # Errors from fetch, pushWorker * @throws - {@link RemoteErr.InvalidGitRemoteError} * @throws - {@link RemoteErr.InvalidURLFormatError} * @throws - {@link RemoteErr.NetworkError} * @throws - {@link RemoteErr.HTTPError401AuthorizationRequired} * @throws - {@link RemoteErr.HTTPError404NotFound} * @throws - {@link RemoteErr.CannotConnectError} * @throws - {@link RemoteErr.HttpProtocolRequiredError} * @throws - {@link RemoteErr.InvalidRepositoryURLError} * @throws - {@link RemoteErr.InvalidSSHKeyPathError} * @throws - {@link RemoteErr.InvalidAuthenticationTypeError} * * @throws # Errors from pushWorker * @throws - {@link RemoteErr.HTTPError403Forbidden} * @throws - {@link RemoteErr.UnfetchedCommitExistsError} * * @throws # Errors from merge * @throws - {@link Err.InvalidConflictStateError} * @throws - {@link Err.CannotDeleteDataError} * @throws ## Errors from getMergedDocument * @throws - {@link Err.InvalidDocTypeError} * @throws - {@link Err.InvalidConflictResolutionStrategyError} * @throws ## Errors from writeBlobToFile * @throws - {@link Err.CannotCreateDirectoryError} * @throws ## Errors from getFatDocFromData, getFatDocFromReadBlobResult * @throws - {@link Err.InvalidJsonObjectError} * * @throws # Errors from getChanges * @throws - {@link Err.InvalidJsonObjectError} * * @internal */ // eslint-disable-next-line complexity async function syncWorker(gitDDB, sync, taskMetadata) { const syncOptions = sync.options; sync.eventHandlers.start.forEach(listener => { listener.func({ ...taskMetadata, collectionPath: listener.collectionPath }, sync.currentRetries()); }); /** * Fetch */ await remote_engine_1.RemoteEngine[sync.engine] .fetch(gitDDB.workingDir, syncOptions, sync.remoteName, gitDDB.defaultBranch, gitDDB.defaultBranch, gitDDB.tsLogger) .catch(err => { throw (0, remote_engine_1.wrappingRemoteEngineError)(err); }); /** * Calc distance */ const oldCommitOid = await isomorphic_git_1.default.resolveRef({ fs: fs_extra_1.default, dir: gitDDB.workingDir, ref: 'HEAD', }); const oldRemoteCommitOid = await isomorphic_git_1.default.resolveRef({ fs: fs_extra_1.default, dir: gitDDB.workingDir, ref: `refs/remotes/${sync.remoteName}/${gitDDB.defaultBranch}`, }); const [baseCommitOid] = await isomorphic_git_1.default.findMergeBase({ fs: fs_extra_1.default, dir: gitDDB.workingDir, oids: [oldCommitOid, oldRemoteCommitOid], }); const distance = await (0, worker_utils_1.calcDistance)(baseCommitOid, oldCommitOid, oldRemoteCommitOid); // ahead: 0, behind 0 => Nothing to do: Local does not have new commits. Remote has not pushed new commits. // ahead: 0, behind 1 => Fast-forward merge : Local does not have new commits. Remote has pushed new commits. // ahead: 1, behind 0 => Push : Local has new commits. Remote has not pushed new commits. // ahead: 1, behind 1 => Merge, may resolve conflict and push: Local has new commits. Remote has pushed new commits. if (distance.ahead === undefined || distance.behind === undefined) { throw new error_1.Err.NoMergeBaseFoundError(); } else if (distance.ahead === 0 && distance.behind === 0) { return { action: 'nop' }; } else if (distance.ahead === 0 && distance.behind > 0) { /** * Fast forward */ await isomorphic_git_1.default.writeRef({ fs: fs_extra_1.default, dir: gitDDB.workingDir, ref: 'refs/heads/' + gitDDB.defaultBranch, value: oldRemoteCommitOid, force: true, }); const newCommitOid = oldRemoteCommitOid; const localChanges = await (0, worker_utils_1.getAndWriteLocalChanges)(gitDDB.workingDir, oldCommitOid, newCommitOid, gitDDB.serializeFormat); const syncResultFastForwardMerge = { action: 'fast-forward merge', changes: { local: localChanges, }, }; if (syncOptions.includeCommits) { // Get list of commits which has been merged to local const commitsFromRemote = await (0, worker_utils_1.getCommitLogs)(gitDDB.workingDir, oldRemoteCommitOid, oldCommitOid); syncResultFastForwardMerge.commits = { local: commitsFromRemote, }; } return syncResultFastForwardMerge; } else if (distance.ahead > 0 && distance.behind === 0) { /** * Push */ return await (0, push_worker_1.pushWorker)(gitDDB, sync, taskMetadata, true).catch(err => { throw err; }); } /** * Merge (distance.ahead > 0 && distance.behind > 0) */ const [mergedTreeOid, localChanges, remoteChanges, acceptedConflicts] = await (0, _3way_merge_1.merge)(gitDDB, sync, baseCommitOid, oldCommitOid, oldRemoteCommitOid); if (acceptedConflicts.length === 0) { const mergeCommitOid = await isomorphic_git_1.default.commit({ fs: fs_extra_1.default, dir: gitDDB.workingDir, author: gitDDB.author, committer: gitDDB.committer, parent: [oldCommitOid, oldRemoteCommitOid], message: 'merge', tree: mergedTreeOid, }); let localCommits; // Get list of commits which has been added to local if (syncOptions.includeCommits) { const mergeCommit = await isomorphic_git_1.default.readCommit({ fs: fs_extra_1.default, dir: gitDDB.workingDir, oid: mergeCommitOid, }); const commitsFromRemote = await (0, worker_utils_1.getCommitLogs)(gitDDB.workingDir, oldRemoteCommitOid, baseCommitOid); // Add merge commit localCommits = [...commitsFromRemote, (0, utils_1.normalizeCommit)(mergeCommit)]; } // Need push because it is merged normally. const syncResultPush = (await (0, push_worker_1.pushWorker)(gitDDB, sync, taskMetadata, true, true).catch((err) => { return err; })); if (syncResultPush instanceof Error) { const syncResultMergeAndPushError = { action: 'merge and push error', changes: { local: localChanges, }, error: syncResultPush, }; if (localCommits) { syncResultMergeAndPushError.commits = { local: localCommits, }; } return syncResultMergeAndPushError; } const syncResultMergeAndPush = { action: 'merge and push', changes: { local: localChanges, // remote: syncResultPush.changes.remote, remote: remoteChanges, }, }; if (localCommits) { syncResultMergeAndPush.commits = { local: localCommits, remote: syncResultPush.commits.remote, }; } return syncResultMergeAndPush; } /** * Conflict * https://git-scm.com/docs/git-merge#_true_merge */ acceptedConflicts.sort((a, b) => { return a.fatDoc.name === b.fatDoc.name ? 0 : a.fatDoc.name > b.fatDoc.name ? 1 : -1; }); // console.log(acceptedConflicts); let commitMessage = 'resolve: '; acceptedConflicts.forEach(conflict => { // e.g.) put-ours: myID commitMessage += `${conflict.fatDoc.name}(${conflict.operation},${conflict.fatDoc.fileOid.substr(0, const_1.SHORT_SHA_LENGTH)},${conflict.strategy}), `; }); if (commitMessage.endsWith(', ')) { commitMessage = commitMessage.slice(0, -2); } const mergeCommitOid = await isomorphic_git_1.default.commit({ fs: fs_extra_1.default, dir: gitDDB.workingDir, author: gitDDB.author, committer: gitDDB.committer, parent: [oldCommitOid, oldRemoteCommitOid], message: commitMessage, tree: mergedTreeOid, }); // const localChanges = await getChanges(gitDDB.workingDir, oldCommitOid, mergeCommitOid); // Get list of commits which has been added to local let localCommits; if (syncOptions.includeCommits) { const commitsFromRemote = await (0, worker_utils_1.getCommitLogs)(gitDDB.workingDir, oldRemoteCommitOid, baseCommitOid); const overwriteCommit = await isomorphic_git_1.default.readCommit({ fs: fs_extra_1.default, dir: gitDDB.workingDir, oid: mergeCommitOid, }); // Add merge commit localCommits = [...commitsFromRemote, (0, utils_1.normalizeCommit)(overwriteCommit)]; } // Push const syncResultPush = (await (0, push_worker_1.pushWorker)(gitDDB, sync, taskMetadata, true, true).catch((err) => { return err; })); if (syncResultPush instanceof Error) { const syncResultResolveConflictsAndPushError = { action: 'resolve conflicts and push error', conflicts: acceptedConflicts, changes: { local: localChanges, }, error: syncResultPush, }; if (localCommits) { syncResultResolveConflictsAndPushError.commits = { local: localCommits, }; } return syncResultResolveConflictsAndPushError; } const syncResultResolveConflictsAndPush = { action: 'resolve conflicts and push', conflicts: acceptedConflicts, changes: { local: localChanges, // remote: syncResultPush.changes.remote, remote: remoteChanges, }, }; if (localCommits) { syncResultResolveConflictsAndPush.commits = { local: localCommits, remote: syncResultPush.commits.remote, }; } return syncResultResolveConflictsAndPush; } exports.syncWorker = syncWorker; //# sourceMappingURL=sync_worker.js.map