@dotinc/ogre 0.8.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/.tap/processinfo/{4bb3fbc0-322c-47fe-99ca-5a2029006dab.json → 2426d9f3-699c-4fb9-94b2-9ec9c3eac102.json} +23 -23
  2. package/.tap/processinfo/{999e3026-ccc7-47b1-bfbd-82a08fbfebb2.json → 45f9dbea-d45b-4370-ad84-bacbe0f7923b.json} +7 -7
  3. package/.tap/processinfo/{46e1fc09-045e-4621-a147-8e17ab127537.json → 6a2329ae-15e9-4041-98f8-205645f535f5.json} +7 -7
  4. package/.tap/processinfo/{9e618086-85e0-4b56-855b-6e7251bd1b83.json → 7073c462-24dd-4066-8ee7-610cc0a4cdc6.json} +23 -23
  5. package/.tap/processinfo/{0edfcd98-724a-4657-9a64-db2d38f2bc26.json → 8ce3d798-c3e7-49ba-a002-477d6aed67d1.json} +11 -11
  6. package/.tap/processinfo/a33fc811-9cba-46ee-8577-3d68cb1ae96f.json +246 -0
  7. package/.tap/processinfo/{afde0f56-0117-4d71-a370-ed7621252dcc.json → cc694d82-e679-4b5d-8cf9-decd8029a65a.json} +7 -7
  8. package/.tap/test-results/src/branch.test.ts.tap +6 -6
  9. package/.tap/test-results/src/checkout.test.ts.tap +9 -9
  10. package/.tap/test-results/src/commit.test.ts.tap +18 -18
  11. package/.tap/test-results/src/merge.test.ts.tap +2 -2
  12. package/.tap/test-results/src/repository.test.ts.tap +111 -26
  13. package/.tap/test-results/src/tag.test.ts.tap +3 -3
  14. package/.tap/test-results/src/utils.test.ts.tap +41 -8
  15. package/CHANGELOG.md +17 -0
  16. package/lib/commit.d.ts +2 -2
  17. package/lib/repository.d.ts +19 -7
  18. package/lib/repository.js +84 -5
  19. package/lib/utils.d.ts +12 -1
  20. package/lib/utils.js +40 -8
  21. package/package.json +4 -2
  22. package/src/commit.ts +9 -9
  23. package/src/repository.test.ts +359 -5
  24. package/src/repository.ts +118 -13
  25. package/src/utils.test.ts +88 -1
  26. package/src/utils.ts +48 -10
  27. package/.tap/processinfo/ae7dad2b-5163-4c3f-8d16-e25e7696c657.json +0 -242
@@ -17,21 +17,21 @@ export interface RepositoryObject<T extends {
17
17
  * @param shaishFrom expression (e.g. refs (branches, tags), commitSha)
18
18
  * @param shaishTo expression (e.g. refs (branches, tags), commitSha)
19
19
  */
20
- diff(shaishFrom: string, shaishTo?: string): Operation[];
20
+ diff(shaishFrom: string, shaishTo?: string): Array<Operation>;
21
21
  /**
22
22
  * Returns pending changes.
23
23
  */
24
- status(): Operation[];
24
+ status(): Array<Operation>;
25
25
  /**
26
26
  * Applies a patch to the repository's HEAD
27
27
  * @param patch
28
28
  */
29
- apply(patch: Operation[]): void;
29
+ apply(patch: Array<Operation>): void;
30
30
  head(): string;
31
31
  ref(reference: string): string | undefined;
32
32
  commit(message: string, author: string, amend?: boolean): Promise<string>;
33
33
  checkout(shaish: string, createBranch?: boolean): void;
34
- logs(commits?: number): Commit[];
34
+ logs(commits?: number): Array<Commit>;
35
35
  createBranch(name: string): string;
36
36
  merge(source: string | RepositoryObject<T> | History): string;
37
37
  /**
@@ -50,6 +50,13 @@ export interface RepositoryObject<T extends {
50
50
  * The returned map is a readonly of remote.
51
51
  */
52
52
  remote(): ReadonlyMap<string, Readonly<Reference>> | undefined;
53
+ /**
54
+ * Cherry returns the commits that are missing from upstream and the refs that have been moved since remote
55
+ */
56
+ cherry(): {
57
+ commits: Array<Commit>;
58
+ refs: Map<string, Reference>;
59
+ };
53
60
  }
54
61
  /**
55
62
  * A repository recording and managing the state transitions of an object
@@ -61,16 +68,21 @@ export declare class Repository<T extends {
61
68
  private readonly original;
62
69
  data: T;
63
70
  private readonly remoteRefs;
71
+ private readonly remoteCommits;
64
72
  private observer;
65
73
  private readonly refs;
66
74
  private readonly commits;
75
+ cherry(): {
76
+ commits: Array<Commit>;
77
+ refs: Map<string, Reference>;
78
+ };
67
79
  remote(): ReadonlyMap<string, Readonly<Reference>> | undefined;
68
80
  private moveTo;
69
- apply(patch: Operation[]): JsonPatchError | undefined;
81
+ apply(patch: Array<Operation>): JsonPatchError | undefined;
70
82
  reset(mode?: "soft" | "hard" | undefined, shaish?: string | undefined): void;
71
83
  branch(): string;
72
84
  status(): Operation[];
73
- diff(shaishFrom: string, shaishTo?: string): Operation[];
85
+ diff(shaishFrom: string, shaishTo?: string): Array<Operation>;
74
86
  checkout(shaish: string, createBranch?: boolean): void;
75
87
  commit(message: string, author: string, amend?: boolean): Promise<string>;
76
88
  commitAtHead(): Commit | undefined;
@@ -79,7 +91,7 @@ export declare class Repository<T extends {
79
91
  head(): string;
80
92
  private collectCommits;
81
93
  getHistory(): History;
82
- logs(numberOfCommits?: number): Commit[];
94
+ logs(numberOfCommits?: number): Array<Commit>;
83
95
  merge(source: string | RepositoryObject<T> | History): string;
84
96
  private moveRef;
85
97
  private saveNewRef;
package/lib/repository.js CHANGED
@@ -10,15 +10,16 @@ const utils_1 = require("./utils");
10
10
  */
11
11
  class Repository {
12
12
  constructor(obj, options) {
13
+ var _a, _b, _c, _d, _e, _f;
13
14
  // FIXME: move this to refs/remote as git would do?
14
- var _a, _b, _c, _d, _e;
15
15
  this.remoteRefs = (0, utils_1.immutableMapCopy)((_a = options.history) === null || _a === void 0 ? void 0 : _a.refs);
16
+ this.remoteCommits = (0, utils_1.immutableArrayCopy)((_b = options.history) === null || _b === void 0 ? void 0 : _b.commits, (c) => c.hash);
16
17
  this.original = (0, fast_json_patch_1.deepClone)(obj);
17
18
  // store js ref, so obj can still be modified without going through repo.data
18
19
  this.data = obj;
19
20
  this.observer = (0, fast_json_patch_1.observe)(obj);
20
- this.refs = ((_b = options.history) === null || _b === void 0 ? void 0 : _b.refs)
21
- ? (0, utils_1.mutableMapCopy)((_c = options.history) === null || _c === void 0 ? void 0 : _c.refs)
21
+ this.refs = ((_c = options.history) === null || _c === void 0 ? void 0 : _c.refs)
22
+ ? (0, utils_1.mutableMapCopy)((_d = options.history) === null || _d === void 0 ? void 0 : _d.refs)
22
23
  : new Map([
23
24
  [
24
25
  utils_1.REFS_HEAD_KEY,
@@ -28,7 +29,7 @@ class Repository {
28
29
  },
29
30
  ],
30
31
  ]);
31
- this.commits = (_e = (_d = options.history) === null || _d === void 0 ? void 0 : _d.commits) !== null && _e !== void 0 ? _e : [];
32
+ this.commits = (_f = (_e = options.history) === null || _e === void 0 ? void 0 : _e.commits) !== null && _f !== void 0 ? _f : [];
32
33
  if (options.history) {
33
34
  const commit = this.commitAtHead();
34
35
  if (!commit) {
@@ -37,6 +38,84 @@ class Repository {
37
38
  this.moveTo(commit);
38
39
  }
39
40
  }
41
+ cherry() {
42
+ var _a;
43
+ const commits = [];
44
+ const refs = new Map();
45
+ const collectedHashes = [];
46
+ const shouldExclude = (hash) => { var _a; return ((_a = this.remoteCommits) === null || _a === void 0 ? void 0 : _a.includes(hash)) || collectedHashes.includes(hash); };
47
+ const collect = (c) => {
48
+ // we can't include remote state in the pending report
49
+ if (shouldExclude(c.hash)) {
50
+ return false;
51
+ }
52
+ commits.push(c);
53
+ collectedHashes.push(c.hash);
54
+ return true;
55
+ };
56
+ // collect ref updates and commits that are not present on the remote
57
+ for (const [key, ref] of this.refs) {
58
+ if (key === utils_1.REFS_HEAD_KEY) {
59
+ continue;
60
+ }
61
+ const remote = (_a = this.remoteRefs) === null || _a === void 0 ? void 0 : _a.get(key);
62
+ if (!remote) {
63
+ // if we have no remote pair, we need to sync the ref
64
+ refs.set(key, ref);
65
+ const localCommit = this.commits.find((c) => c.hash === ref.value);
66
+ // if ref is not pointing to a commit move on
67
+ if (!localCommit) {
68
+ continue;
69
+ }
70
+ // map all commits to root
71
+ const [isAncestor, path] = (0, utils_1.mapPath)(this.commits, localCommit, undefined);
72
+ if (isAncestor) {
73
+ for (let i = 0; i < path.length; i++) {
74
+ const commit = path[i];
75
+ collect({
76
+ hash: commit.hash,
77
+ author: commit.author,
78
+ changes: commit.changes,
79
+ message: commit.message,
80
+ parent: commit.parent,
81
+ timestamp: commit.timestamp,
82
+ tree: commit.tree,
83
+ });
84
+ }
85
+ }
86
+ continue;
87
+ }
88
+ if (remote.value === ref.value) {
89
+ // early exit if remote is the same
90
+ continue;
91
+ }
92
+ // local and remote refs differ
93
+ refs.set(key, ref);
94
+ const localCommit = this.commits.find((c) => c.hash === ref.value);
95
+ // if ref is not pointing to a commit move on
96
+ if (!localCommit) {
97
+ continue;
98
+ }
99
+ // FIXME: do we have to have the remote ref as a commit locally?
100
+ const remoteCommit = this.commits.find((c) => c.hash === remote.value);
101
+ const [isAncestor, path] = (0, utils_1.mapPath)(this.commits, localCommit, remoteCommit);
102
+ if (isAncestor) {
103
+ for (let i = 0; i < path.length; i++) {
104
+ const commit = path[i];
105
+ collect({
106
+ hash: commit.hash,
107
+ author: commit.author,
108
+ changes: commit.changes,
109
+ message: commit.message,
110
+ parent: commit.parent,
111
+ timestamp: commit.timestamp,
112
+ tree: commit.tree,
113
+ });
114
+ }
115
+ }
116
+ }
117
+ return { commits, refs };
118
+ }
40
119
  remote() {
41
120
  return this.remoteRefs;
42
121
  }
@@ -289,7 +368,7 @@ class Repository {
289
368
  // *---*
290
369
  // \
291
370
  // *---*---* (master, foo)
292
- const [isAncestor] = (0, utils_1.mapPath)(headCommit, srcCommit, this.commits);
371
+ const [isAncestor] = (0, utils_1.mapPath)(this.commits, srcCommit, headCommit);
293
372
  if (isAncestor) {
294
373
  this.moveRef(this.head(), srcCommit);
295
374
  this.moveTo(srcCommit);
package/lib/utils.d.ts CHANGED
@@ -18,7 +18,17 @@ export declare const REFS_HEAD_KEY = "HEAD";
18
18
  */
19
19
  export declare const REFS_MAIN_KEY: string;
20
20
  export declare const treeToObject: <T = any>(tree: string) => T;
21
- export declare const mapPath: (from: Commit, to: Commit, commits: Commit[]) => [isAncestor: boolean];
21
+ /**
22
+ * Maps the path from a commit to another commit.
23
+ * It travels backwards through parent relationships until the root state.
24
+ * It returns a boolean whether the to commit parameter is a direct ancestor
25
+ * of the from commit and returns the path of commits between them.
26
+ *
27
+ * @param commits
28
+ * @param from the higher commit to start from
29
+ * @param to to lower commit to arrive at
30
+ */
31
+ export declare const mapPath: (commits: Array<Commit>, from: Commit, to?: Commit) => [isAncestor: boolean, path: Commit[]];
22
32
  /**
23
33
  * Returns the commit to which the provided ref is pointing
24
34
  * @param ref - needs to be in key format, e.g. refs/heads/... or refs/tags/...
@@ -56,3 +66,4 @@ export declare const printChange: (chg: Operation) => void;
56
66
  export declare const getLastRefPathElement: (thePath: string) => string;
57
67
  export declare const immutableMapCopy: <T extends object>(map: Map<string, T> | undefined) => ReadonlyMap<string, Readonly<T>> | undefined;
58
68
  export declare const mutableMapCopy: <T extends object>(map: Map<string, T>) => Map<string, T>;
69
+ export declare const immutableArrayCopy: <T, R extends unknown>(arr: T[] | undefined, fn?: (obj: T) => Readonly<R>) => readonly Readonly<R>[] | undefined;
package/lib/utils.js CHANGED
@@ -1,8 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.mutableMapCopy = exports.immutableMapCopy = exports.getLastRefPathElement = exports.printChange = exports.printChangeLog = exports.validateRef = exports.validateBranchName = exports.tagToRef = exports.brancheNameToRef = exports.cleanRefValue = exports.isTagRef = exports.createHeadRefValue = exports.shaishToCommit = exports.refsAtCommit = exports.commitAtRefIn = exports.mapPath = exports.treeToObject = exports.REFS_MAIN_KEY = exports.REFS_HEAD_KEY = exports.remoteTagPathPrefix = exports.localTagPathPrefix = exports.remoteHeadPathPrefix = exports.localHeadPathPrefix = exports.headValueRefPrefix = exports.headsRefPathPrefix = exports.tagRefPathPrefix = exports.remoteRefPrefix = exports.localRefPrefix = exports.cleanAuthor = void 0;
3
+ exports.immutableArrayCopy = exports.mutableMapCopy = exports.immutableMapCopy = exports.getLastRefPathElement = exports.printChange = exports.printChangeLog = exports.validateRef = exports.validateBranchName = exports.tagToRef = exports.brancheNameToRef = exports.cleanRefValue = exports.isTagRef = exports.createHeadRefValue = exports.shaishToCommit = exports.refsAtCommit = exports.commitAtRefIn = exports.mapPath = exports.treeToObject = exports.REFS_MAIN_KEY = exports.REFS_HEAD_KEY = exports.remoteTagPathPrefix = exports.localTagPathPrefix = exports.remoteHeadPathPrefix = exports.localHeadPathPrefix = exports.headValueRefPrefix = exports.headsRefPathPrefix = exports.tagRefPathPrefix = exports.remoteRefPrefix = exports.localRefPrefix = exports.cleanAuthor = void 0;
4
4
  const fflate_1 = require("fflate");
5
5
  const ref_1 = require("./ref");
6
+ const fast_json_patch_1 = require("fast-json-patch");
6
7
  const emailRegex = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/;
7
8
  const cleanAuthor = (author) => {
8
9
  if (author === "") {
@@ -48,15 +49,34 @@ const treeToObject = (tree) => {
48
49
  return JSON.parse((0, fflate_1.strFromU8)((0, fflate_1.decompressSync)(Buffer.from(tree, "base64"))));
49
50
  };
50
51
  exports.treeToObject = treeToObject;
51
- const mapPath = (from, to, commits) => {
52
- let c = to;
53
- while (c !== undefined) {
54
- c = commits.find((parent) => parent.hash === (c === null || c === void 0 ? void 0 : c.parent));
55
- if ((c === null || c === void 0 ? void 0 : c.hash) === from.hash) {
56
- return [true];
52
+ /**
53
+ * Maps the path from a commit to another commit.
54
+ * It travels backwards through parent relationships until the root state.
55
+ * It returns a boolean whether the to commit parameter is a direct ancestor
56
+ * of the from commit and returns the path of commits between them.
57
+ *
58
+ * @param commits
59
+ * @param from the higher commit to start from
60
+ * @param to to lower commit to arrive at
61
+ */
62
+ const mapPath = (commits, from, to) => {
63
+ // early exit for first commit
64
+ if (from.parent === undefined && to === undefined)
65
+ return [true, [from]];
66
+ const path = [];
67
+ let parent = from;
68
+ while (parent !== undefined) {
69
+ const child = parent;
70
+ parent = commits.find((gp) => gp.hash === (parent === null || parent === void 0 ? void 0 : parent.parent));
71
+ const atTarget = (parent === null || parent === void 0 ? void 0 : parent.hash) === (to === null || to === void 0 ? void 0 : to.hash);
72
+ path.push(child);
73
+ if (atTarget) {
74
+ if (parent)
75
+ path.push(parent);
76
+ return [true, path];
57
77
  }
58
78
  }
59
- return [false];
79
+ return [false, []];
60
80
  };
61
81
  exports.mapPath = mapPath;
62
82
  /**
@@ -225,3 +245,15 @@ const mutableMapCopy = (map) => {
225
245
  return m;
226
246
  };
227
247
  exports.mutableMapCopy = mutableMapCopy;
248
+ const immutableArrayCopy = (arr, fn = (o) => typeof o === "object" ? (0, fast_json_patch_1.deepClone)(o) : o) => {
249
+ if (!arr) {
250
+ return undefined;
251
+ }
252
+ const a = [];
253
+ for (let i = 0; i < arr.length; i++) {
254
+ const o = arr[i];
255
+ a.push(fn(o));
256
+ }
257
+ return a;
258
+ };
259
+ exports.immutableArrayCopy = immutableArrayCopy;
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "type": "git",
5
5
  "url": "https://github.com/dotindustries/ogre.git"
6
6
  },
7
- "version": "0.8.1",
7
+ "version": "0.9.0",
8
8
  "description": "Git-like repository for in-memory object versioning",
9
9
  "private": false,
10
10
  "main": "lib/index.js",
@@ -12,6 +12,8 @@
12
12
  "scripts": {
13
13
  "build": "tsc -p tsconfig.build.json",
14
14
  "test": "nyc --reporter=lcov tap --coverage-report=none --allow-incomplete-coverage",
15
+ "test:cov": "tap",
16
+ "test:covhtml": "tap --coverage-report=lcov",
15
17
  "test:node": "node --import tsx --test src/**/*.test.ts",
16
18
  "coverage:html": "nyc report --reporter=html"
17
19
  },
@@ -37,5 +39,5 @@
37
39
  "registry": "https://registry.npmjs.org/",
38
40
  "access": "public"
39
41
  },
40
- "gitHead": "82800df2bbac9520914c093cdd47356ce5fae00a"
42
+ "gitHead": "a254347f76edf34818a313cfd85a98b73cc25004"
41
43
  }
package/src/commit.ts CHANGED
@@ -2,13 +2,13 @@ import { digest } from "./hash";
2
2
  import { Operation } from "fast-json-patch";
3
3
 
4
4
  export interface Commit {
5
- /*The hash of the commit. Is an sha256 of:
6
- - tree object reference (changes?)
7
- - parent object reference (parent hash)
8
- - author
9
- - author commit timestamp with timezone
10
- - commit message
11
- */
5
+ /*The hash of the commit. Is a sha256 of:
6
+ - tree object reference (changes?)
7
+ - parent object reference (parent hash)
8
+ - author
9
+ - author commit timestamp with timezone
10
+ - commit message
11
+ */
12
12
  hash: string;
13
13
  tree: string;
14
14
 
@@ -19,7 +19,7 @@ export interface Commit {
19
19
  parent: string | undefined;
20
20
 
21
21
  // The diff of this commit from the parent
22
- changes: Operation[];
22
+ changes: Array<Operation>;
23
23
 
24
24
  // Commit timestamp with timezone
25
25
  timestamp: Date;
@@ -29,7 +29,7 @@ export interface CommitHashContent {
29
29
  message: string;
30
30
  author: string;
31
31
  parentRef: string | undefined;
32
- changes: Operation[];
32
+ changes: Array<Operation>;
33
33
  timestamp: Date;
34
34
  }
35
35