From 2266d99074a7ada4cabfe4fdec4878d6031c1429 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Veres?= Date: Sun, 23 Jun 2024 09:15:49 +0200 Subject: [PATCH 1/4] feat: allow override for serialization (temporal support) --- apps/ogre-cli/source/cli.ts | 6 +- apps/ogre-demo/components/DemoRepo.tsx | 6 +- apps/ogre-demo/pages/index.tsx | 6 +- packages/ogre/package.json | 7 ++ packages/ogre/src/checkout.test.ts | 25 ++++--- packages/ogre/src/commit.test.ts | 8 +-- packages/ogre/src/merge.test.ts | 13 ++-- packages/ogre/src/repository.test.ts | 62 +++++++++--------- packages/ogre/src/repository.ts | 91 ++++++++++++++++++-------- packages/ogre/src/serialize.ts | 11 ++++ packages/ogre/src/utils.ts | 10 --- 11 files changed, 142 insertions(+), 103 deletions(-) create mode 100644 packages/ogre/src/serialize.ts diff --git a/apps/ogre-cli/source/cli.ts b/apps/ogre-cli/source/cli.ts index b141d3f..557232a 100644 --- a/apps/ogre-cli/source/cli.ts +++ b/apps/ogre-cli/source/cli.ts @@ -42,7 +42,7 @@ const run = async ({name = 'author'}: {name?: string}) => { r.data.description = 'first description'; await r.commit('initial commit', author); - r.checkout('desc-up', true); + await r.checkout('desc-up', true); r.data.description = 'some longer different description'; await r.commit('change desc', author); @@ -54,8 +54,8 @@ const run = async ({name = 'author'}: {name?: string}) => { r.data.description = 'yet another correction'; await r.commit('typo fix', author); - r.checkout('main'); - r.merge('desc-up'); + await r.checkout('main'); + await r.merge('desc-up'); const history = r.getHistory(); diff --git a/apps/ogre-demo/components/DemoRepo.tsx b/apps/ogre-demo/components/DemoRepo.tsx index 0a27e6b..47ee9fe 100644 --- a/apps/ogre-demo/components/DemoRepo.tsx +++ b/apps/ogre-demo/components/DemoRepo.tsx @@ -26,7 +26,7 @@ export const DemoRepo: React.FC = ({}) => { r.data.description = "first description"; await r.commit("initial commit", author); - r.checkout("description", true); + await r.checkout("description", true); r.data.description = "some longer different description"; await r.commit("change desc", author); @@ -38,8 +38,8 @@ export const DemoRepo: React.FC = ({}) => { r.data.description = "yet another correction"; await r.commit("typo fix", author); - r.checkout("main"); - r.merge("description"); + await r.checkout("main"); + await r.merge("description"); setRepository(r); }; diff --git a/apps/ogre-demo/pages/index.tsx b/apps/ogre-demo/pages/index.tsx index d50a1c7..4da1e48 100644 --- a/apps/ogre-demo/pages/index.tsx +++ b/apps/ogre-demo/pages/index.tsx @@ -132,7 +132,7 @@ const code = ` r.data.description = 'first description' await r.commit('initial commit', author) - r.checkout('description', true) + await r.checkout('description', true) r.data.description = 'some longer different description' await r.commit('change desc', author) @@ -144,8 +144,8 @@ const code = ` r.data.description = 'yet another correction' await r.commit('typo fix', author) - r.checkout('main') - r.merge('description') + await r.checkout('main') + await r.merge('description') return r } diff --git a/packages/ogre/package.json b/packages/ogre/package.json index 007e4a0..a1d9402 100644 --- a/packages/ogre/package.json +++ b/packages/ogre/package.json @@ -9,6 +9,13 @@ "private": false, "main": "lib/index.js", "types": "lib/index.d.ts", + "exports": { + ".": { + "source": "./src/index.ts", + "import": "./lib/index.js", + "require": "./lib/index.js" + } + }, "type": "module", "scripts": { "build": "tsc -p tsconfig.build.json", diff --git a/packages/ogre/src/checkout.test.ts b/packages/ogre/src/checkout.test.ts index 46de15f..44679a2 100644 --- a/packages/ogre/src/checkout.test.ts +++ b/packages/ogre/src/checkout.test.ts @@ -18,7 +18,7 @@ test("checkout prev commit", async (t) => { addOneStep(obj); await repo.commit("first step", testAuthor); - repo.checkout(headerDataHash); + await repo.checkout(headerDataHash); const head = repo.head(); const history = repo.getHistory(); t.equal(sumChanges(history.commits), 3, `incorrect # of changelog entries`); @@ -38,7 +38,7 @@ test("checkout new branch with simple name", async (t) => { await repo.commit("simple change", testAuthor); const ref = repo.createBranch("new_feature"); - repo.checkout("new_feature"); + await repo.checkout("new_feature"); t.equal(repo.head(), ref, "HEAD is not moved to target branch"); }); @@ -48,7 +48,7 @@ test("checkout new branch with full ref name", async (t) => { await repo.commit("simple change", testAuthor); const ref = repo.createBranch("new_feature"); - repo.checkout(ref); + await repo.checkout(ref); t.equal(repo.head(), ref, "HEAD is not moved to target branch"); }); @@ -58,7 +58,7 @@ test("checkout commit which has two refs pointing leaves HEAD detached", async ( const commit = await repo.commit("simple change", testAuthor); repo.createBranch("new_feature"); - repo.checkout(commit); + await repo.checkout(commit); t.equal(repo.ref("refs/heads/main"), commit, "main does not point to commit"); t.equal( repo.ref("refs/heads/new_feature"), @@ -75,14 +75,14 @@ test("checkout new branch moves head to new branch", async (t) => { await repo.commit("simple change", testAuthor); const ref = repo.createBranch("new_feature"); - repo.checkout("new_feature"); + await repo.checkout("new_feature"); t.equal(repo.head(), ref, "HEAD is not moved to target branch"); }); test("checkout and create new branch on empty main", async (t) => { const [repo] = await getBaseline(); - repo.checkout("new_feature", true); + await repo.checkout("new_feature", true); t.equal( repo.head(), "refs/heads/new_feature", @@ -96,7 +96,7 @@ test("checkout and create new branch with at least 1 commit", async (t) => { repo.data.name = "new name"; const commit = await repo.commit("simple change", testAuthor); - repo.checkout("new_feature", true); + await repo.checkout("new_feature", true); t.equal( repo.head(), "refs/heads/new_feature", @@ -117,7 +117,7 @@ test("replacing default branch on empty master removes main", async (t) => { // replacing default main branch by moving HEAD to new branch // is OK even on empty repo - repo.checkout("new_feature", true); + await repo.checkout("new_feature", true); const history = repo.getHistory(); t.equal( sumChanges(history?.commits), @@ -129,10 +129,7 @@ test("replacing default branch on empty master removes main", async (t) => { repo.data.description = "description changed"; await repo.commit("description changes", testAuthor); - t.throws( - () => { - repo.checkout("main"); - }, - { message: `pathspec 'main' did not match any known refs` }, - ); + await t.rejects(repo.checkout("main"), { + message: `pathspec 'main' did not match any known refs`, + }); }); diff --git a/packages/ogre/src/commit.test.ts b/packages/ogre/src/commit.test.ts index aef5632..635b74d 100644 --- a/packages/ogre/src/commit.test.ts +++ b/packages/ogre/src/commit.test.ts @@ -28,7 +28,7 @@ test("changes are available for commit if starting from empty", async (t) => { const repo = new Repository({}, {}); repo.data.name = "some data"; - const dirty = repo.status(); + const dirty = await repo.status(); t.equal( dirty.length, @@ -222,7 +222,7 @@ test("commit at detached HEAD does not affect main, but moves head", async (t) = repo.data.description = "new fancy description"; const last = await repo.commit("desc change", testAuthor); - repo.checkout(commit); + await repo.checkout(commit); t.equal(repo.head(), commit, "HEAD did not move to commit"); t.equal(repo.branch(), "HEAD", "repo is not in detached state"); t.matchOnly(v1, repo.data, "object state does not match"); @@ -243,7 +243,7 @@ test("commit at detached HEAD saved to a branch", async (t) => { const commit = await repo.commit("name change", testAuthor); repo.data.description = "new fancy description"; await repo.commit("desc change", testAuthor); - repo.checkout(commit); + await repo.checkout(commit); repo.data.description = "a different description"; const commitOnDetached = await repo.commit("msg", testAuthor); @@ -262,7 +262,7 @@ test("commit --amend changes hash on message change even in detached HEAD", asyn const commitToAmend = await repo.commit("name change", testAuthor); repo.data.description = "desc change"; const descCommit = await repo.commit("desc change", testAuthor); - repo.checkout(commitToAmend); + await repo.checkout(commitToAmend); const changedHash = await repo.commit("initial setup", testAuthor, true); t.not(changedHash, commitToAmend, "hash should have changed"); const history = repo.getHistory(); diff --git a/packages/ogre/src/merge.test.ts b/packages/ogre/src/merge.test.ts index 1d7db58..06cbb0e 100644 --- a/packages/ogre/src/merge.test.ts +++ b/packages/ogre/src/merge.test.ts @@ -8,12 +8,7 @@ test("merge with no commit fails", async (t) => { repo.createBranch("new_feature"); - t.throws( - () => { - repo.merge("new_feature"); - }, - { message: "already up to date" }, - ); + await t.rejects(repo.merge("new_feature"), { message: "already up to date" }); }); test("merge fast-forward", async (t) => { @@ -22,7 +17,7 @@ test("merge fast-forward", async (t) => { await repo.commit("simple change", testAuthor); const masterCommitCount = repo.getHistory().commits.length; - repo.checkout("new_branch", true); + await repo.checkout("new_branch", true); repo.data.name = "another name"; const minorHash = await repo.commit("minor change", testAuthor); t.equal( @@ -37,8 +32,8 @@ test("merge fast-forward", async (t) => { ); // go to destination branch - repo.checkout("main"); - const mergeHash = repo.merge("new_branch"); + await repo.checkout("main"); + const mergeHash = await repo.merge("new_branch"); const headRef = repo.head(); const refHash = repo.ref(headRef); diff --git a/packages/ogre/src/repository.test.ts b/packages/ogre/src/repository.test.ts index b54093b..3cfa026 100644 --- a/packages/ogre/src/repository.test.ts +++ b/packages/ogre/src/repository.test.ts @@ -32,7 +32,7 @@ test("diff is ok", async (t) => { const second = await repo.commit("second nested", testAuthor); - const diff = repo.diff(zeroth); + const diff = await repo.diff(zeroth); t.equal( diff.length, changeEntries, @@ -137,6 +137,7 @@ test("history", async (t) => { const obj2 = {}; const repo2 = new Repository(obj2, { history }); + await repo2.isReady(); t.matchOnlyStrict( obj, @@ -154,6 +155,7 @@ test("history", async (t) => { const history = repo.getHistory(); const r2 = new Repository({}, { history }); + await r2.isReady(); const remoteBeforeChange = r2.remote(); r2.data.name = "a different name"; @@ -228,12 +230,12 @@ test("reset", async (t) => { t.equal(h1.commits.length, 1); // do changes const changes = updateHeaderData(co); - const diff = repo.diff(hash); + const diff = await repo.diff(hash); t.equal(diff.length, changes, "wrong # of changes in diff"); // reset repo.reset("hard"); - const diff2 = repo.diff(hash); + const diff2 = await repo.diff(hash); t.equal(diff2.length, 0, "failed to reset"); }); }); @@ -247,7 +249,7 @@ test("status", async (t) => { t.test("clean repo pending change", async (t) => { const [repo] = await getBaseline({ name: "base name" }); repo.data.name = "changed name"; - const dirtyState = repo.status(); + const dirtyState = await repo.status(); t.equal( dirtyState.length, 1, @@ -257,14 +259,14 @@ test("status", async (t) => { t.test("reading status shouldn't clean observer", async (t) => { const [repo] = await getBaseline({ name: "base name" }); repo.data.name = "changed name"; - const dirtyState = repo.status(); + const dirtyState = await repo.status(); t.equal( dirtyState.length, 1, `Status doesn't contain the expected # of changes: ${JSON.stringify(dirtyState)}`, ); - const dirtyState2 = repo.status(); + const dirtyState2 = await repo.status(); t.equal( dirtyState2.length, 1, @@ -276,27 +278,27 @@ test("status", async (t) => { const [repo] = await getBaseline(); repo.data.name = "new name"; await repo.commit("baseline", testAuthor); - const cleanState = repo.status(); + const cleanState = await repo.status(); t.match(cleanState, [], "Shouldn't have pending changes"); }); t.test("after commit pending change", async (t) => { const [repo] = await getBaseline(); repo.data.name = "new name"; await repo.commit("baseline", testAuthor); - const cleanState = repo.status(); + const cleanState = await repo.status(); t.match(cleanState, [], "Shouldn't have pending changes"); repo.data.name = "changed name"; - const dirtyState = repo.status(); + const dirtyState = await repo.status(); t.equal(dirtyState.length, 1, "Status doesn't contain changes"); }); t.test("after commit pending change for rewrite array", async (t) => { const [repo] = await getBaseline(); repo.data.name = "new name"; await repo.commit("baseline", testAuthor); - const cleanState = repo.status(); + const cleanState = await repo.status(); t.match(cleanState, [], "Shouldn't have pending changes"); repo.data.nested = [{ name: "new item", uuid: "asdf" }]; - const dirtyState = repo.status(); + const dirtyState = await repo.status(); t.equal(dirtyState.length, 1, "Status doesn't contain changes"); }); t.test("change of nested array element prop", async (t) => { @@ -304,10 +306,10 @@ test("status", async (t) => { repo.data.name = "new name"; addOneStep(repo.data); await repo.commit("baseline", testAuthor); - const cleanState = repo.status(); + const cleanState = await repo.status(); t.match(cleanState, [], "Shouldn't have pending changes"); repo.data.nested[0].name = "another name which is different"; - const dirtyState = repo.status(); + const dirtyState = await repo.status(); t.equal(dirtyState?.length, 1, "Status doesn't contain changes"); }); }); @@ -315,7 +317,7 @@ test("status", async (t) => { test("apply", async (t) => { t.test("single patch", async (t) => { const [repo] = await getBaseline({ name: "base name" }); - const cleanState = repo.status(); + const cleanState = await repo.status(); t.match(cleanState, [], "Shouldn't have pending changes"); const targetState = { @@ -329,7 +331,7 @@ test("apply", async (t) => { const err = repo.apply(patches); t.match(err, undefined, "Failed to apply patch"); t.match(repo.data, targetState, "The final state does not match up"); - const dirtyState = repo.status(); + const dirtyState = await repo.status(); t.equal(dirtyState.length, 2, "Status doesn't contain changes"); t.match(dirtyState, patches, "It should have the right changes"); }); @@ -337,7 +339,7 @@ test("apply", async (t) => { t.test("patch for undefined props with workaround", async (t) => { // solution for workaround from: https://github.com/Starcounter-Jack/JSON-Patch/issues/280 const [repo] = await getBaseline(); - const cleanState = repo.status(); + const cleanState = await repo.status(); t.match(cleanState, [], "Shouldn't have pending changes"); const targetState: ComplexObject = { @@ -352,14 +354,14 @@ test("apply", async (t) => { t.equal(err, undefined, "Failed to apply patch"); t.match(repo.data, targetState, "The final state should match up"); - const dirtyState = repo.status(); + const dirtyState = await repo.status(); t.equal(dirtyState.length, 1, "Status should contain 1 change"); }); t.test("multiple patches", async (t) => { const [repo] = await getBaseline({ name: "base name" }); - const cleanState = repo.status(); + const cleanState = await repo.status(); t.match(cleanState, [], "Shouldn't have pending changes"); const targetState = { uuid: undefined, @@ -370,7 +372,7 @@ test("apply", async (t) => { const patches = compare(repo.data, targetState); const err = repo.apply(patches); t.equal(err, undefined, "Failed to apply patch"); - const dirtyState = repo.status(); + const dirtyState = await repo.status(); t.equal(dirtyState?.length, 2, "Status doesn't contain changes"); t.match(dirtyState, patches, "It should have the right changes"); t.match(repo.data, targetState, "The final state does not match up"); @@ -491,7 +493,7 @@ test("pending changes - push helpers", async (t) => { const hash1 = await repo2.commit("changed desc", testAuthor); repo2.data.uuid = "uuid1"; const hash2 = await repo2.commit("changed uuid", testAuthor); - repo2.checkout("branch2", true); + await repo2.checkout("branch2", true); const { commits } = repo2.getHistory(); const pendingCommit1 = commits.find((c) => c.hash === hash1); @@ -528,7 +530,7 @@ test("pending changes - push helpers", async (t) => { const hash1 = await repo2.commit("changed desc", testAuthor); repo2.data.uuid = "uuid1"; const hash2 = await repo2.commit("changed uuid", testAuthor); - repo2.checkout("branch2", true); + await repo2.checkout("branch2", true); repo2.data.nested = [{ name: "a", uuid: "thing" }]; const hash3 = await repo2.commit("added a thing", testAuthor); @@ -569,7 +571,7 @@ test("pending changes - push helpers", async (t) => { const hash1 = await repo2.commit("changed desc", testAuthor); repo2.data.uuid = "uuid1"; const hash2 = await repo2.commit("changed uuid", testAuthor); - repo2.checkout("branch2", true); + await repo2.checkout("branch2", true); repo2.data.nested = [{ name: "a", uuid: "thing" }]; const hash3 = await repo2.commit("added a thing", testAuthor); @@ -606,14 +608,14 @@ test("pending changes - push helpers", async (t) => { const history = repo.getHistory(); const repo2 = new Repository({}, { history }); - repo2.checkout("branch2", true); + await repo2.checkout("branch2", true); repo2.data.nested = [{ name: "a", uuid: "thing" }]; const hash3 = await repo2.commit("added a thing", testAuthor); repo2.tag("v0.2.0"); - repo2.checkout("main"); - repo2.merge("branch2"); + await repo2.checkout("main"); + await repo2.merge("branch2"); const { commits } = repo2.getHistory(); const pendingCommit3 = commits.find((c) => c.hash === hash3); @@ -644,13 +646,13 @@ test("pending changes - push helpers", async (t) => { const history = repo.getHistory(); const repo2 = new Repository({}, { history }); - repo2.checkout("branch2", true); + await repo2.checkout("branch2", true); repo2.data.nested = [{ name: "a", uuid: "thing" }]; const hash3 = await repo2.commit("added a thing", testAuthor); - repo2.checkout("main"); - repo2.merge("branch2"); + await repo2.checkout("main"); + await repo2.merge("branch2"); const { commits } = repo2.getHistory(); const pendingCommit3 = commits.find((c) => c.hash === hash3); @@ -680,7 +682,7 @@ test("pending changes - push helpers", async (t) => { const history = repo.getHistory(); const repo2 = new Repository({}, { history }); - repo2.checkout("branch2", true); + await repo2.checkout("branch2", true); repo2.data.nested = [{ name: "a", uuid: "thing" }]; const hash3 = await repo2.commit("added a thing", testAuthor); @@ -713,7 +715,7 @@ test("pending changes - push helpers", async (t) => { const history = repo.getHistory(); const repo2 = new Repository({}, { history }); - repo2.checkout("branch2", true); + await repo2.checkout("branch2", true); const pending = repo2.cherry(); diff --git a/packages/ogre/src/repository.ts b/packages/ogre/src/repository.ts index 9ce4a75..51970e2 100644 --- a/packages/ogre/src/repository.ts +++ b/packages/ogre/src/repository.ts @@ -25,13 +25,11 @@ import { localHeadPathPrefix, mapPath, mutableMapCopy, - objectToTree, REFS_HEAD_KEY, REFS_MAIN_KEY, refsAtCommit, shaishToCommit, tagToRef, - treeToObject, validateBranchName, validateRef, } from "./utils.js"; @@ -40,6 +38,8 @@ export interface RepositoryOptions { history?: History; overrides?: { calculateCommitHashFn?: (content: CommitHashContent) => Promise; + serializeObjectFn?: (obj: any) => string; + deserializeObjectFn?: (str: string) => T; }; } @@ -54,12 +54,12 @@ export interface RepositoryObject { * @param shaishFrom expression (e.g. refs (branches, tags), commitSha) * @param shaishTo expression (e.g. refs (branches, tags), commitSha) */ - diff(shaishFrom: string, shaishTo?: string): Array; + diff(shaishFrom: string, shaishTo?: string): Promise>; /** * Returns pending changes. */ - status(): Array; + status(): Promise>; /** * Applies a patch to the repository's HEAD @@ -75,13 +75,13 @@ export interface RepositoryObject { commit(message: string, author: string, amend?: boolean): Promise; - checkout(shaish: string, createBranch?: boolean): void; + checkout(shaish: string, createBranch?: boolean): Promise; logs(commits?: number): Array; createBranch(name: string): string; - merge(source: string | RepositoryObject | History): string; + merge(source: string | RepositoryObject | History): Promise; /** * Branch returns the current branch name @@ -95,7 +95,7 @@ export interface RepositoryObject { * @param mode hard - discard changes * @param shaish */ - reset(mode?: "soft" | "hard", shaish?: string): void; + reset(mode?: "soft" | "hard", shaish?: string): Promise; /** * Returns the remote references from the initialization of the repository. @@ -117,6 +117,8 @@ export class Repository { constructor(obj: Partial, options: RepositoryOptions) { this.hashFn = options.overrides?.calculateCommitHashFn; + this.serializeObjectFn = options.overrides?.serializeObjectFn; + this.deserializeObjectFn = options.overrides?.deserializeObjectFn; // FIXME: move this to refs/remote as git would do? this.remoteRefs = immutableMapCopy(options.history?.refs); this.remoteCommits = immutableArrayCopy( @@ -141,22 +143,50 @@ export class Repository this.commits = options.history?.commits ?? []; - if (options.history) { - const commit = this.commitAtHead(); - if (!commit) { - return; - } - this.moveTo(commit); + if (!options.history) { + this._isReady = true; + return; + } + + // restore history + const commit = this.commitAtHead(); + if (!commit) { + this._isReady = true; + return; } + this.moveTo(commit).then(() => { + this._isReady = true; + }); } private readonly original: T; + private _isReady = false; + + isReady(): Promise { + const self = this; + + function checkFlag(callback: (ok: boolean) => void) { + if (self._isReady === true) { + callback(true); + } else { + setTimeout(() => checkFlag(callback), 10); + } + } + + return new Promise((resolve) => { + checkFlag(resolve); + }); + } data: T; private readonly hashFn: | ((content: CommitHashContent) => Promise) | undefined; + private readonly serializeObjectFn: ((obj: any) => string) | undefined; + + private readonly deserializeObjectFn: ((str: string) => T) | undefined; + // stores the remote state upon initialization private readonly remoteRefs: | ReadonlyMap> @@ -267,8 +297,10 @@ export class Repository return this.remoteRefs; } - private moveTo(commit: Commit) { - const targetTree = treeToObject(commit.tree); + private async moveTo(commit: Commit) { + const deserializeFn = + this.deserializeObjectFn ?? (await import("./serialize.js")).treeToObject; + const targetTree = deserializeFn(commit.tree); const patchToTarget = compare(this.data, targetTree); if (!patchToTarget || patchToTarget.length < 1) { return; @@ -310,16 +342,16 @@ export class Repository // const changed = patch.reduce(applyReducer, this.data); } - reset( + async reset( mode: "soft" | "hard" | undefined = "hard", shaish: string | undefined = REFS_HEAD_KEY, - ): void { + ): Promise { if (mode === "hard") { unobserve(this.data, this.observer); } const [commit] = shaishToCommit(shaish, this.refs, this.commits); - this.moveTo(commit); + await this.moveTo(commit); const refs = refsAtCommit(this.refs, commit); // reset only moves heads and not tags @@ -350,7 +382,7 @@ export class Repository return REFS_HEAD_KEY; // detached state } - status() { + async status() { const commit = this.commitAtHead(); if (!commit) { // on root repo return the pending changes @@ -359,21 +391,23 @@ export class Repository return this.diff(commit.hash); } - diff(shaishFrom: string, shaishTo?: string): Array { + async diff(shaishFrom: string, shaishTo?: string): Promise> { const [cFrom] = shaishToCommit(shaishFrom, this.refs, this.commits); let target: T; + const deserializeFn = + this.deserializeObjectFn ?? (await import("./serialize.js")).treeToObject; if (shaishTo) { const [cTo] = shaishToCommit(shaishTo, this.refs, this.commits); - target = treeToObject(cTo.tree); + target = deserializeFn(cTo.tree); } else { target = this.data; } - const targetTree = treeToObject(cFrom.tree); + const targetTree = deserializeFn(cFrom.tree); return compare(targetTree, target); } - checkout(shaish: string, createBranch?: boolean): void { + async checkout(shaish: string, createBranch?: boolean): Promise { if (createBranch) { validateBranchName(shaish); let branchRef = brancheNameToRef(shaish); @@ -388,7 +422,7 @@ export class Repository this.refs, this.commits, ); - this.moveTo(commit); + await this.moveTo(commit); this.moveRef( REFS_HEAD_KEY, isRef && refKey !== undefined ? refKey : commit, @@ -439,7 +473,10 @@ export class Repository timestamp, }); - const treeHash = objectToTree(this.data); + const serializeFn = + this.serializeObjectFn ?? (await import("./serialize.js")).objectToTree; + + const treeHash = serializeFn(this.data); const commit = { hash: sha, message, @@ -536,7 +573,7 @@ export class Repository // endregion - merge(source: string | RepositoryObject | History): string { + async merge(source: string | RepositoryObject | History): Promise { // inspiration // http://think-like-a-git.net // also check isomorphic-git @@ -574,7 +611,7 @@ export class Repository const [isAncestor] = mapPath(this.commits, srcCommit, headCommit); if (isAncestor) { this.moveRef(this.head(), srcCommit); - this.moveTo(srcCommit); + await this.moveTo(srcCommit); return srcCommit.hash; } diff --git a/packages/ogre/src/serialize.ts b/packages/ogre/src/serialize.ts new file mode 100644 index 0000000..7f6f735 --- /dev/null +++ b/packages/ogre/src/serialize.ts @@ -0,0 +1,11 @@ +import { compressSync, decompressSync, strFromU8, strToU8 } from "fflate"; + +export function objectToTree(obj: any) { + return Buffer.from( + compressSync(strToU8(JSON.stringify(obj)), { level: 6, mem: 8 }), + ).toString("base64"); +} + +export const treeToObject = (tree: string): T => { + return JSON.parse(strFromU8(decompressSync(Buffer.from(tree, "base64")))); +}; diff --git a/packages/ogre/src/utils.ts b/packages/ogre/src/utils.ts index eb9f32e..ac0aaa0 100644 --- a/packages/ogre/src/utils.ts +++ b/packages/ogre/src/utils.ts @@ -1,7 +1,6 @@ // [RFC5322](https://www.ietf.org/rfc/rfc5322.txt) import { Commit } from "./commit.js"; import { Reference } from "./interfaces.js"; -import { compressSync, decompressSync, strFromU8, strToU8 } from "fflate"; import { validBranch, validRef } from "./ref.js"; import { deepClone, Operation } from "fast-json-patch"; import { RepositoryObject } from "./repository.js"; @@ -52,15 +51,6 @@ export const REFS_HEAD_KEY = "HEAD"; */ export const REFS_MAIN_KEY = `${localHeadPathPrefix()}main`; -export function objectToTree(obj: any) { - return Buffer.from( - compressSync(strToU8(JSON.stringify(obj)), { level: 6, mem: 8 }), - ).toString("base64"); -} - -export const treeToObject = (tree: string): T => { - return JSON.parse(strFromU8(decompressSync(Buffer.from(tree, "base64")))); -}; /** * Maps the path from a commit to another commit. * It travels backwards through parent relationships until the root state. From e577ee64907ec52323ef392b573cf5ba446a4321 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Veres?= Date: Sun, 23 Jun 2024 10:10:18 +0200 Subject: [PATCH 2/4] fix: override functions are async --- packages/ogre/src/repository.ts | 45 ++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/packages/ogre/src/repository.ts b/packages/ogre/src/repository.ts index 51970e2..835a483 100644 --- a/packages/ogre/src/repository.ts +++ b/packages/ogre/src/repository.ts @@ -38,8 +38,8 @@ export interface RepositoryOptions { history?: History; overrides?: { calculateCommitHashFn?: (content: CommitHashContent) => Promise; - serializeObjectFn?: (obj: any) => string; - deserializeObjectFn?: (str: string) => T; + serializeObjectFn?: (obj: any) => Promise; + deserializeObjectFn?: (str: string) => Promise; }; } @@ -183,9 +183,13 @@ export class Repository | ((content: CommitHashContent) => Promise) | undefined; - private readonly serializeObjectFn: ((obj: any) => string) | undefined; + private readonly serializeObjectFn: + | ((obj: any) => Promise) + | undefined; - private readonly deserializeObjectFn: ((str: string) => T) | undefined; + private readonly deserializeObjectFn: + | ((str: string) => Promise) + | undefined; // stores the remote state upon initialization private readonly remoteRefs: @@ -298,8 +302,7 @@ export class Repository } private async moveTo(commit: Commit) { - const deserializeFn = - this.deserializeObjectFn ?? (await import("./serialize.js")).treeToObject; + const deserializeFn = this.deserializeObjectFn ?? defaultDeserializeFn; const targetTree = deserializeFn(commit.tree); const patchToTarget = compare(this.data, targetTree); if (!patchToTarget || patchToTarget.length < 1) { @@ -394,15 +397,14 @@ export class Repository async diff(shaishFrom: string, shaishTo?: string): Promise> { const [cFrom] = shaishToCommit(shaishFrom, this.refs, this.commits); let target: T; - const deserializeFn = - this.deserializeObjectFn ?? (await import("./serialize.js")).treeToObject; + const deserializeFn = this.deserializeObjectFn ?? defaultDeserializeFn; if (shaishTo) { const [cTo] = shaishToCommit(shaishTo, this.refs, this.commits); - target = deserializeFn(cTo.tree); + target = await deserializeFn(cTo.tree); } else { target = this.data; } - const targetTree = deserializeFn(cFrom.tree); + const targetTree = await deserializeFn(cFrom.tree); return compare(targetTree, target); } @@ -462,8 +464,7 @@ export class Repository ? parent.changes : []; const changes = [...parentChanges, ...patch]; - const calculateCommitHash = - this.hashFn ?? (await import("./commit.js")).calculateCommitHash; + const calculateCommitHash = this.hashFn ?? defaultHashFn; const sha = await calculateCommitHash({ message, @@ -473,10 +474,9 @@ export class Repository timestamp, }); - const serializeFn = - this.serializeObjectFn ?? (await import("./serialize.js")).objectToTree; + const serializeFn = this.serializeObjectFn ?? defaultSerializeFn; - const treeHash = serializeFn(this.data); + const treeHash = await serializeFn(this.data); const commit = { hash: sha, message, @@ -682,3 +682,18 @@ export class Repository return tagRef; } } + +const defaultHashFn = async (content: CommitHashContent) => { + const fn = (await import("./commit.js")).calculateCommitHash; + return fn(content); +}; + +const defaultSerializeFn = async (obj: any) => { + const fn = (await import("./serialize.js")).objectToTree; + return fn(obj); +}; + +const defaultDeserializeFn = async (str: string): Promise => { + const fn = (await import("./serialize.js")).treeToObject; + return fn(str); +}; From 65907173f79488c38d2c2dfe998babeae0f9015c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Veres?= Date: Sun, 23 Jun 2024 10:19:16 +0200 Subject: [PATCH 3/4] chore: export utils and helpers --- packages/ogre/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ogre/package.json b/packages/ogre/package.json index a1d9402..6c4e41c 100644 --- a/packages/ogre/package.json +++ b/packages/ogre/package.json @@ -14,7 +14,8 @@ "source": "./src/index.ts", "import": "./lib/index.js", "require": "./lib/index.js" - } + }, + "./lib/*": "./lib/*.js" }, "type": "module", "scripts": { From c21308bb2f536fa48158400f732cf910d2c41ea9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Veres?= Date: Sun, 23 Jun 2024 10:26:50 +0200 Subject: [PATCH 4/4] fix: missed an await --- packages/ogre/src/repository.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ogre/src/repository.ts b/packages/ogre/src/repository.ts index 835a483..47b502d 100644 --- a/packages/ogre/src/repository.ts +++ b/packages/ogre/src/repository.ts @@ -303,7 +303,7 @@ export class Repository private async moveTo(commit: Commit) { const deserializeFn = this.deserializeObjectFn ?? defaultDeserializeFn; - const targetTree = deserializeFn(commit.tree); + const targetTree = await deserializeFn(commit.tree); const patchToTarget = compare(this.data, targetTree); if (!patchToTarget || patchToTarget.length < 1) { return;