@dotinc/ogre 0.1.2 → 0.2.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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,20 @@
1
+ # v0.2.0 (Sun Mar 13 2022)
2
+
3
+ #### 🚀 Enhancement
4
+
5
+ - Add visualization [#4](https://github.com/dotindustries/ogre/pull/4) ([@nadilas](https://github.com/nadilas))
6
+ - feat: add OgreGraph visualization ([@nadilas](https://github.com/nadilas))
7
+
8
+ #### ⚠️ Pushed to `main`
9
+
10
+ - chore: remove old dev deps ([@nadilas](https://github.com/nadilas))
11
+
12
+ #### Authors: 1
13
+
14
+ - [@nadilas](https://github.com/nadilas)
15
+
16
+ ---
17
+
1
18
  # v0.1.2 (Fri Mar 11 2022)
2
19
 
3
20
  #### 🐛 Bug Fix
package/lib/commit.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Change } from './interfaces';
2
2
  export interface Commit {
3
3
  hash: string;
4
+ tree: string;
4
5
  message: string | undefined;
5
6
  author: string;
6
7
  parent: string | undefined;
@@ -8,11 +9,11 @@ export interface Commit {
8
9
  timestamp: Date;
9
10
  to: number;
10
11
  }
11
- export interface HashContent {
12
+ export interface CommitHashContent {
12
13
  message: string;
13
14
  author: string;
14
15
  parentRef: string | undefined;
15
16
  changes: Change[];
16
17
  timestamp: Date;
17
18
  }
18
- export declare function calculateHash(content: HashContent): Promise<string>;
19
+ export declare function calculateCommitHash(content: CommitHashContent): Promise<string>;
package/lib/commit.js CHANGED
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.calculateHash = void 0;
3
+ exports.calculateCommitHash = void 0;
4
4
  const hash_1 = require("./hash");
5
- function calculateHash(content) {
5
+ function calculateCommitHash(content) {
6
6
  return (0, hash_1.digest)(content);
7
7
  }
8
- exports.calculateHash = calculateHash;
8
+ exports.calculateCommitHash = calculateCommitHash;
package/lib/hash.d.ts CHANGED
File without changes
package/lib/hash.js CHANGED
File without changes
package/lib/index.d.ts CHANGED
@@ -3,3 +3,4 @@ export * from './repository';
3
3
  export { Commit } from './commit';
4
4
  export * from './ref';
5
5
  export * from './size';
6
+ export * from './utils';
package/lib/index.js CHANGED
@@ -5,3 +5,4 @@ tslib_1.__exportStar(require("./interfaces"), exports);
5
5
  tslib_1.__exportStar(require("./repository"), exports);
6
6
  tslib_1.__exportStar(require("./ref"), exports);
7
7
  tslib_1.__exportStar(require("./size"), exports);
8
+ tslib_1.__exportStar(require("./utils"), exports);
File without changes
package/lib/interfaces.js CHANGED
File without changes
package/lib/ref.d.ts CHANGED
File without changes
package/lib/ref.js CHANGED
File without changes
@@ -1,5 +1,7 @@
1
1
  import { History } from './interfaces';
2
2
  import { Commit } from './commit';
3
+ export declare const REFS_HEAD_KEY = "HEAD";
4
+ export declare const REFS_MAIN_KEY: string;
3
5
  export interface RepositoryOptions<T> {
4
6
  history?: History;
5
7
  }
@@ -22,6 +24,11 @@ export interface RespositoryObjectType {
22
24
  * A repository recording and managing the state transitions of an object
23
25
  */
24
26
  export declare const Repository: RespositoryObjectType;
27
+ export declare const createHeadRefValue: (refKey: string) => string;
28
+ export declare const isTagRef: (refKey: string) => boolean;
29
+ export declare const cleanRefValue: (ref: string) => string;
30
+ export declare const brancheNameToRef: (name: string) => string;
31
+ export declare const validateBranchName: (name: string) => void;
25
32
  /**
26
33
  * Prints the underlying changelog of a repository
27
34
  * @param repository
package/lib/repository.js CHANGED
@@ -1,11 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.printChangeLog = exports.Repository = void 0;
3
+ exports.printChangeLog = exports.validateBranchName = exports.brancheNameToRef = exports.cleanRefValue = exports.isTagRef = exports.createHeadRefValue = exports.Repository = exports.REFS_MAIN_KEY = exports.REFS_HEAD_KEY = void 0;
4
4
  const commit_1 = require("./commit");
5
5
  const ref_1 = require("./ref");
6
- const REFS_HEAD = 'HEAD';
7
- const REFS_MAIN = 'refs/heads/main';
8
- const refPrefix = 'ref: ';
6
+ const hash_1 = require("./hash");
7
+ const tagRefPathPrefix = 'refs/tags/';
8
+ const headsRefPathPrefix = 'refs/heads/';
9
+ const headValueRefPrefix = 'ref: ';
10
+ exports.REFS_HEAD_KEY = 'HEAD';
11
+ exports.REFS_MAIN_KEY = `${headsRefPathPrefix}main`;
9
12
  /**
10
13
  * A repository recording and managing the state transitions of an object
11
14
  */
@@ -13,9 +16,9 @@ exports.Repository = function (obj, options) {
13
16
  var _a, _b, _c, _d;
14
17
  let savedLength;
15
18
  let version = 0;
16
- const refs = (_b = (_a = options.history) === null || _a === void 0 ? void 0 : _a.refs) !== null && _b !== void 0 ? _b : new Map([[REFS_HEAD, {
17
- name: REFS_HEAD,
18
- value: `ref: ${REFS_MAIN}`
19
+ const refs = (_b = (_a = options.history) === null || _a === void 0 ? void 0 : _a.refs) !== null && _b !== void 0 ? _b : new Map([[exports.REFS_HEAD_KEY, {
20
+ name: exports.REFS_HEAD_KEY,
21
+ value: `ref: ${exports.REFS_MAIN_KEY}`
19
22
  }]]);
20
23
  const changeLog = [];
21
24
  const targets = [];
@@ -105,28 +108,28 @@ exports.Repository = function (obj, options) {
105
108
  this.data = new Proxy(obj, handler);
106
109
  // region Read state read
107
110
  this.head = () => {
108
- const ref = refs.get(REFS_HEAD);
111
+ const ref = refs.get(exports.REFS_HEAD_KEY);
109
112
  if (!ref) {
110
113
  throw new Error(`unreachable: HEAD is not present`);
111
114
  }
112
- return cleanRefValue(ref.value);
115
+ return (0, exports.cleanRefValue)(ref.value);
113
116
  };
114
117
  this.ref = (reference) => {
115
118
  var _a;
116
119
  const ref = (_a = refs.get(reference)) === null || _a === void 0 ? void 0 : _a.value;
117
- return ref ? cleanRefValue(ref) : undefined;
120
+ return ref ? (0, exports.cleanRefValue)(ref) : undefined;
118
121
  };
119
122
  this.branch = () => {
120
- const currentHeadRef = refs.get(REFS_HEAD);
123
+ const currentHeadRef = refs.get(exports.REFS_HEAD_KEY);
121
124
  if (!currentHeadRef) {
122
125
  throw new Error('unreachable: ref HEAD not available');
123
126
  }
124
- if (currentHeadRef.value.includes(refPrefix)) {
125
- const refName = cleanRefValue(currentHeadRef.value);
127
+ if (currentHeadRef.value.includes(headValueRefPrefix)) {
128
+ const refName = (0, exports.cleanRefValue)(currentHeadRef.value);
126
129
  if (refs.has(refName))
127
130
  return getLastItem(refName);
128
131
  }
129
- return REFS_HEAD; // detached state
132
+ return exports.REFS_HEAD_KEY; // detached state
130
133
  };
131
134
  // endregion
132
135
  // region History functions
@@ -176,7 +179,7 @@ exports.Repository = function (obj, options) {
176
179
  };
177
180
  // region Commit lookups
178
181
  const commitAtHead = () => {
179
- return commitAtHeadIn(REFS_HEAD, refs, commits);
182
+ return commitAtRefIn(exports.REFS_HEAD_KEY, refs, commits);
180
183
  };
181
184
  const mustCommitAtHead = () => {
182
185
  const commitHead = commitAtHead();
@@ -202,13 +205,14 @@ exports.Repository = function (obj, options) {
202
205
  }
203
206
  const timestamp = new Date();
204
207
  const changes = [...changesSinceLastCommit];
205
- const sha = await (0, commit_1.calculateHash)({
208
+ const sha = await (0, commit_1.calculateCommitHash)({
206
209
  message,
207
210
  author,
208
211
  changes,
209
212
  parentRef: parent === null || parent === void 0 ? void 0 : parent.hash,
210
213
  timestamp
211
214
  });
215
+ const treeHash = await (0, hash_1.digest)(obj);
212
216
  const commit = {
213
217
  hash: sha,
214
218
  message,
@@ -216,7 +220,8 @@ exports.Repository = function (obj, options) {
216
220
  changes: changes,
217
221
  parent: parent === null || parent === void 0 ? void 0 : parent.hash,
218
222
  timestamp,
219
- to: version
223
+ to: version,
224
+ tree: treeHash
220
225
  };
221
226
  if (amend) {
222
227
  const idx = commits.findIndex(c => c === parent);
@@ -230,30 +235,30 @@ exports.Repository = function (obj, options) {
230
235
  }
231
236
  else {
232
237
  // move detached HEAD to new commit
233
- moveRef(REFS_HEAD, commit);
238
+ moveRef(exports.REFS_HEAD_KEY, commit);
234
239
  }
235
240
  return sha;
236
241
  };
237
242
  // region Graph manipulation
238
243
  this.checkout = (shaish, createBranch = false) => {
239
244
  if (createBranch) {
240
- validateBranchName(shaish);
241
- let branchRef = brancheNameToRef(shaish);
245
+ (0, exports.validateBranchName)(shaish);
246
+ let branchRef = (0, exports.brancheNameToRef)(shaish);
242
247
  const commit = commitAtHead();
243
248
  if (commit) {
244
249
  moveRef(branchRef, commit);
245
250
  }
246
- moveRef(REFS_HEAD, branchRef);
251
+ moveRef(exports.REFS_HEAD_KEY, branchRef);
247
252
  }
248
253
  else {
249
254
  const [commit, isRef, refKey] = shaishToCommit(shaish, refs, commits);
250
255
  rebuildChangeLog(commit);
251
- moveRef(REFS_HEAD, isRef && refKey !== undefined ? refKey : commit);
256
+ moveRef(exports.REFS_HEAD_KEY, isRef && refKey !== undefined ? refKey : commit);
252
257
  }
253
258
  };
254
259
  this.createBranch = (name) => {
255
- validateBranchName(name);
256
- const refName = brancheNameToRef(name);
260
+ (0, exports.validateBranchName)(name);
261
+ const refName = (0, exports.brancheNameToRef)(name);
257
262
  const headCommit = commitAtHead();
258
263
  if (!headCommit) {
259
264
  const headRef = this.head();
@@ -267,7 +272,7 @@ exports.Repository = function (obj, options) {
267
272
  };
268
273
  const moveRef = (refName, value) => {
269
274
  let ref = refs.get(refName);
270
- const val = typeof value === 'string' ? `${refPrefix}${value}` : value.hash;
275
+ const val = typeof value === 'string' ? (0, exports.createHeadRefValue)(value) : value.hash;
271
276
  if (!ref) {
272
277
  ref = { name: getLastItem(refName), value: val };
273
278
  }
@@ -283,7 +288,7 @@ exports.Repository = function (obj, options) {
283
288
  // for fancier merge tree
284
289
  // https://github.com/isomorphic-git/isomorphic-git/blob/a623133345a5d8b6bb7a8352ea9702ce425d8266/src/utils/mergeTree.js#L33
285
290
  if (typeof source !== 'string') {
286
- // const srcHead = commitAtHeadIn(REFS_HEAD, src.refs, src.commits)
291
+ // const srcHead = commitAtRefIn(REFS_HEAD, src.refs, src.commits)
287
292
  throw new Error(`fatal: source type (${source instanceof exports.Repository ? 'Repository' : 'History'}) not implemented`);
288
293
  }
289
294
  const [srcCommit] = shaishToCommit(source, refs, commits);
@@ -331,15 +336,6 @@ exports.Repository = function (obj, options) {
331
336
  }
332
337
  };
333
338
  const getLastItem = (thePath) => thePath.substring(thePath.lastIndexOf('/') + 1);
334
- const cleanRefValue = (ref) => ref.replace(refPrefix, '');
335
- const brancheNameToRef = (name) => {
336
- return `refs/heads/${name}`;
337
- };
338
- const validateBranchName = (name) => {
339
- if (!(0, ref_1.validBranch)(name)) {
340
- throw new Error(`invalid ref name`);
341
- }
342
- };
343
339
  /**
344
340
  * Traverses the commit tree backwards and reassembles the changelog
345
341
  * @param commit
@@ -370,14 +366,14 @@ const mapPath = (from, to, commits) => {
370
366
  * @param references
371
367
  * @param commitsList
372
368
  */
373
- const commitAtHeadIn = (ref, references, commitsList) => {
369
+ const commitAtRefIn = (ref, references, commitsList) => {
374
370
  const reference = references.get(ref);
375
371
  if (!reference) {
376
372
  throw new Error(`unreachable: '${ref}' is not present`);
377
373
  }
378
374
  let commitHash;
379
- if (reference.value.includes(refPrefix)) {
380
- const refKey = cleanRefValue(reference.value);
375
+ if (reference.value.includes(headValueRefPrefix)) {
376
+ const refKey = (0, exports.cleanRefValue)(reference.value);
381
377
  const targetRef = references.get(refKey);
382
378
  if (!targetRef) {
383
379
  // target branch may not have been saved yet
@@ -412,9 +408,9 @@ const shaishToCommit = (shaish, references, commitsList) => {
412
408
  isRef = true;
413
409
  refKey = name;
414
410
  sha = ref.value;
415
- if (sha.includes(refPrefix)) {
416
- const cleanedRef = cleanRefValue(sha);
417
- const c = commitAtHeadIn(cleanedRef, references, commitsList);
411
+ if (sha.includes(headValueRefPrefix)) {
412
+ const cleanedRef = (0, exports.cleanRefValue)(sha);
413
+ const c = commitAtRefIn(cleanedRef, references, commitsList);
418
414
  if (!c) {
419
415
  throw new Error(`${cleanedRef} points to non-existing commit`);
420
416
  }
@@ -434,6 +430,24 @@ const shaishToCommit = (shaish, references, commitsList) => {
434
430
  }
435
431
  return [found[0], isRef, refKey];
436
432
  };
433
+ const createHeadRefValue = (refKey) => {
434
+ return `${headValueRefPrefix}${refKey}`;
435
+ };
436
+ exports.createHeadRefValue = createHeadRefValue;
437
+ const isTagRef = (refKey) => refKey.indexOf(tagRefPathPrefix) > -1;
438
+ exports.isTagRef = isTagRef;
439
+ const cleanRefValue = (ref) => ref.replace(headValueRefPrefix, '');
440
+ exports.cleanRefValue = cleanRefValue;
441
+ const brancheNameToRef = (name) => {
442
+ return `${headsRefPathPrefix}${name}`;
443
+ };
444
+ exports.brancheNameToRef = brancheNameToRef;
445
+ const validateBranchName = (name) => {
446
+ if (!(0, ref_1.validBranch)(name)) {
447
+ throw new Error(`invalid ref name`);
448
+ }
449
+ };
450
+ exports.validateBranchName = validateBranchName;
437
451
  /**
438
452
  * Prints the underlying changelog of a repository
439
453
  * @param repository
@@ -442,7 +456,7 @@ const printChangeLog = (repository) => {
442
456
  console.log('----------------------------------------------------------');
443
457
  console.log(`Changelog at ${repository.head()}`);
444
458
  const history = repository.getHistory();
445
- const head = commitAtHeadIn(repository.head(), history.refs, history.commits);
459
+ const head = commitAtRefIn(repository.head(), history.refs, history.commits);
446
460
  if (!head) {
447
461
  throw new Error(`fatal: HEAD is not defined`);
448
462
  }
package/lib/size.d.ts CHANGED
File without changes
package/lib/size.js CHANGED
File without changes
File without changes
package/lib/test.utils.js CHANGED
File without changes
package/lib/utils.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare const cleanAuthor: (author: string) => [name: string, email: string];
package/lib/utils.js ADDED
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.cleanAuthor = void 0;
4
+ // [RFC5322](https://www.ietf.org/rfc/rfc5322.txt)
5
+ 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])+)\])/;
6
+ const cleanAuthor = (author) => {
7
+ if (author === '') {
8
+ throw new Error(`author not provided`);
9
+ }
10
+ // author name <email>
11
+ let strings = author.split(' <');
12
+ if (strings.length > 1) {
13
+ return [strings[0], strings[1].replace('>', '')];
14
+ }
15
+ // author name @handle
16
+ strings = author.split(' @');
17
+ if (strings.length > 1) {
18
+ return [strings[0], `@${strings[1]}`];
19
+ }
20
+ // email@domain.com
21
+ if (emailRegex.test(author)) {
22
+ return ['', author];
23
+ }
24
+ // unrecognized format
25
+ return [author, ''];
26
+ };
27
+ exports.cleanAuthor = cleanAuthor;
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.1.2",
7
+ "version": "0.2.0",
8
8
  "description": "Git-like repository for in-memory object versioning",
9
9
  "main": "lib/index.js",
10
10
  "types": "lib/index.d.ts",
@@ -36,11 +36,8 @@
36
36
  "@types/node": "^17.0.21",
37
37
  "@types/uuid": "^8.3.4",
38
38
  "ava": "^4.0.1",
39
- "coveralls": "^3.1.1",
40
39
  "nyc": "^15.1.0",
41
40
  "ts-node": "^10.7.0",
42
- "tslib": "^2.3.1",
43
- "turbo": "^1.1.5",
44
41
  "typescript": "^4.5.5",
45
42
  "uuid": "^8.3.2"
46
43
  },
@@ -48,5 +45,5 @@
48
45
  "registry": "https://registry.npmjs.org/",
49
46
  "access": "public"
50
47
  },
51
- "gitHead": "494bd45f7a75618df120338e797aa552a7f4a4b4"
48
+ "gitHead": "fb4343e4c5f6cc2b87eb9204a643b6cd632ada69"
52
49
  }
@@ -32,6 +32,16 @@ test('no commit without changes after recent commit', async t => {
32
32
  return await repo.commit('baseline', testAuthor)
33
33
  }, { message: 'no changes to commit' })
34
34
  })
35
+
36
+ test('treeHash of commit is matching content', async t => {
37
+ const [repo] = await getBaseline()
38
+ repo.data.name = 'new name'
39
+ await repo.commit('baseline', testAuthor)
40
+ const {commits} = repo.getHistory()
41
+ t.is(commits.length, 1)
42
+ t.not(commits[0].tree, '', 'tree hash mismatch')
43
+ })
44
+
35
45
  test('no commit --amend without commit', async t => {
36
46
  const [repo] = await getBaseline()
37
47
  repo.data.name = 'new name'
package/src/commit.ts CHANGED
@@ -10,6 +10,7 @@ export interface Commit {
10
10
  // - author commit timestamp with timezone
11
11
  // - commit message
12
12
  hash: string
13
+ tree: string
13
14
 
14
15
  message: string | undefined
15
16
  author: string
@@ -32,7 +33,7 @@ export interface Commit {
32
33
  to: number;
33
34
  }
34
35
 
35
- export interface HashContent {
36
+ export interface CommitHashContent {
36
37
  message: string
37
38
  author: string
38
39
  parentRef: string | undefined
@@ -40,6 +41,6 @@ export interface HashContent {
40
41
  timestamp: Date
41
42
  }
42
43
 
43
- export function calculateHash(content: HashContent) {
44
+ export function calculateCommitHash(content: CommitHashContent) {
44
45
  return digest(content)
45
46
  }
package/src/index.ts CHANGED
@@ -3,3 +3,4 @@ export * from './repository'
3
3
  export {Commit} from './commit'
4
4
  export * from './ref'
5
5
  export * from './size'
6
+ export * from './utils'
@@ -24,3 +24,15 @@ test('reconstruction', async t => {
24
24
  t.is(history2.commits.length, 2, 'incorrect # of commits')
25
25
  t.is(sumChanges(history2.commits), changeEntries, 'incorrect # of changelog entries')
26
26
  })
27
+
28
+ test('history contains HEAD ref', async t => {
29
+ const [repo] = await getBaseline()
30
+
31
+ t.is(repo.head(), 'refs/heads/main')
32
+
33
+ const history = repo.getHistory()
34
+ let headRef = history.refs.get('HEAD')
35
+ t.not(headRef, undefined)
36
+ t.is(headRef!.name, 'HEAD')
37
+ t.is(headRef!.value, 'ref: refs/heads/main')
38
+ })
package/src/repository.ts CHANGED
@@ -1,10 +1,15 @@
1
1
  import { Change, History, Reference } from './interfaces'
2
- import { calculateHash, Commit } from './commit'
2
+ import { calculateCommitHash, Commit } from './commit'
3
3
  import { validBranch } from './ref'
4
+ import {digest} from './hash'
5
+
6
+ const tagRefPathPrefix = 'refs/tags/'
7
+ const headsRefPathPrefix = 'refs/heads/'
8
+ const headValueRefPrefix = 'ref: '
9
+
10
+ export const REFS_HEAD_KEY = 'HEAD'
11
+ export const REFS_MAIN_KEY = `${headsRefPathPrefix}main`
4
12
 
5
- const REFS_HEAD = 'HEAD'
6
- const REFS_MAIN = 'refs/heads/main'
7
- const refPrefix = 'ref: '
8
13
 
9
14
  export interface RepositoryOptions<T> {
10
15
  history?: History
@@ -49,9 +54,9 @@ export const Repository = function <T extends { [k: PropertyKey]: any }>(
49
54
  ) {
50
55
  let savedLength: number | undefined
51
56
  let version = 0
52
- const refs: Map<string, Reference> = options.history?.refs ?? new Map<string, Reference>([[REFS_HEAD, {
53
- name: REFS_HEAD,
54
- value: `ref: ${REFS_MAIN}`
57
+ const refs: Map<string, Reference> = options.history?.refs ?? new Map<string, Reference>([[REFS_HEAD_KEY, {
58
+ name: REFS_HEAD_KEY,
59
+ value: `ref: ${REFS_MAIN_KEY}`
55
60
  }]])
56
61
  const changeLog: Change[] = []
57
62
  const targets: any[] = []
@@ -149,7 +154,7 @@ export const Repository = function <T extends { [k: PropertyKey]: any }>(
149
154
 
150
155
  // region Read state read
151
156
  this.head = () => {
152
- const ref = refs.get(REFS_HEAD)
157
+ const ref = refs.get(REFS_HEAD_KEY)
153
158
  if (!ref) {
154
159
  throw new Error(`unreachable: HEAD is not present`)
155
160
  }
@@ -160,18 +165,18 @@ export const Repository = function <T extends { [k: PropertyKey]: any }>(
160
165
  return ref ? cleanRefValue(ref) : undefined
161
166
  }
162
167
  this.branch = () => {
163
- const currentHeadRef = refs.get(REFS_HEAD)
168
+ const currentHeadRef = refs.get(REFS_HEAD_KEY)
164
169
  if (!currentHeadRef) {
165
170
  throw new Error('unreachable: ref HEAD not available')
166
171
  }
167
172
 
168
- if (currentHeadRef.value.includes(refPrefix)) {
173
+ if (currentHeadRef.value.includes(headValueRefPrefix)) {
169
174
  const refName = cleanRefValue(currentHeadRef.value)
170
175
  if (refs.has(refName))
171
176
  return getLastItem(refName)
172
177
  }
173
178
 
174
- return REFS_HEAD // detached state
179
+ return REFS_HEAD_KEY // detached state
175
180
  }
176
181
  // endregion
177
182
 
@@ -226,7 +231,7 @@ export const Repository = function <T extends { [k: PropertyKey]: any }>(
226
231
 
227
232
  // region Commit lookups
228
233
  const commitAtHead = () => {
229
- return commitAtHeadIn(REFS_HEAD, refs, commits)
234
+ return commitAtRefIn(REFS_HEAD_KEY, refs, commits)
230
235
  }
231
236
  const mustCommitAtHead = () => {
232
237
  const commitHead = commitAtHead()
@@ -254,13 +259,14 @@ export const Repository = function <T extends { [k: PropertyKey]: any }>(
254
259
 
255
260
  const timestamp = new Date()
256
261
  const changes = [...changesSinceLastCommit]
257
- const sha = await calculateHash({
262
+ const sha = await calculateCommitHash({
258
263
  message,
259
264
  author,
260
265
  changes,
261
266
  parentRef: parent?.hash,
262
267
  timestamp
263
268
  })
269
+ const treeHash = await digest(obj)
264
270
 
265
271
  const commit = {
266
272
  hash: sha,
@@ -269,7 +275,8 @@ export const Repository = function <T extends { [k: PropertyKey]: any }>(
269
275
  changes: changes,
270
276
  parent: parent?.hash,
271
277
  timestamp,
272
- to: version
278
+ to: version,
279
+ tree: treeHash
273
280
  }
274
281
  if (amend) {
275
282
  const idx = commits.findIndex(c => c === parent)
@@ -283,7 +290,7 @@ export const Repository = function <T extends { [k: PropertyKey]: any }>(
283
290
  moveRef(headRef, commit)
284
291
  } else {
285
292
  // move detached HEAD to new commit
286
- moveRef(REFS_HEAD, commit)
293
+ moveRef(REFS_HEAD_KEY, commit)
287
294
  }
288
295
 
289
296
  return sha
@@ -298,11 +305,11 @@ export const Repository = function <T extends { [k: PropertyKey]: any }>(
298
305
  if (commit) {
299
306
  moveRef(branchRef, commit)
300
307
  }
301
- moveRef(REFS_HEAD, branchRef)
308
+ moveRef(REFS_HEAD_KEY, branchRef)
302
309
  } else {
303
310
  const [commit, isRef, refKey] = shaishToCommit(shaish, refs, commits)
304
311
  rebuildChangeLog(commit)
305
- moveRef(REFS_HEAD, isRef && refKey !== undefined ? refKey : commit)
312
+ moveRef(REFS_HEAD_KEY, isRef && refKey !== undefined ? refKey : commit)
306
313
  }
307
314
  }
308
315
  this.createBranch = (name) => {
@@ -321,7 +328,7 @@ export const Repository = function <T extends { [k: PropertyKey]: any }>(
321
328
  }
322
329
  const moveRef = (refName: string, value: string | Commit) => {
323
330
  let ref = refs.get(refName)
324
- const val = typeof value === 'string' ? `${refPrefix}${value}` : value.hash
331
+ const val = typeof value === 'string' ? createHeadRefValue(value) : value.hash
325
332
  if (!ref) {
326
333
  ref = { name: getLastItem(refName), value: val }
327
334
  } else {
@@ -337,7 +344,7 @@ export const Repository = function <T extends { [k: PropertyKey]: any }>(
337
344
  // https://github.com/isomorphic-git/isomorphic-git/blob/a623133345a5d8b6bb7a8352ea9702ce425d8266/src/utils/mergeTree.js#L33
338
345
 
339
346
  if (typeof source !== 'string') {
340
- // const srcHead = commitAtHeadIn(REFS_HEAD, src.refs, src.commits)
347
+ // const srcHead = commitAtRefIn(REFS_HEAD, src.refs, src.commits)
341
348
  throw new Error(`fatal: source type (${source instanceof Repository ? 'Repository' : 'History'}) not implemented`)
342
349
  }
343
350
 
@@ -392,15 +399,6 @@ export const Repository = function <T extends { [k: PropertyKey]: any }>(
392
399
  } as any as RespositoryObjectType
393
400
 
394
401
  const getLastItem = (thePath: string) => thePath.substring(thePath.lastIndexOf('/') + 1)
395
- const cleanRefValue = (ref: string) => ref.replace(refPrefix, '')
396
- const brancheNameToRef = (name: string) => {
397
- return `refs/heads/${name}`
398
- }
399
- const validateBranchName = (name: string) => {
400
- if (!validBranch(name)) {
401
- throw new Error(`invalid ref name`)
402
- }
403
- }
404
402
 
405
403
  /**
406
404
  * Traverses the commit tree backwards and reassembles the changelog
@@ -434,13 +432,13 @@ const mapPath = (from: Commit, to: Commit, commits: Commit[]): [isAncestor: bool
434
432
  * @param references
435
433
  * @param commitsList
436
434
  */
437
- const commitAtHeadIn = (ref: string, references: Map<string, Reference>, commitsList: Commit[]) => {
435
+ const commitAtRefIn = (ref: string, references: Map<string, Reference>, commitsList: Commit[]) => {
438
436
  const reference = references.get(ref)
439
437
  if (!reference) {
440
438
  throw new Error(`unreachable: '${ref}' is not present`)
441
439
  }
442
440
  let commitHash
443
- if (reference.value.includes(refPrefix)) {
441
+ if (reference.value.includes(headValueRefPrefix)) {
444
442
  const refKey = cleanRefValue(reference.value)
445
443
  const targetRef = references.get(refKey)
446
444
  if (!targetRef) {
@@ -477,9 +475,9 @@ const shaishToCommit = (shaish: string, references: Map<string, Reference>, comm
477
475
  isRef = true
478
476
  refKey = name
479
477
  sha = ref.value
480
- if (sha.includes(refPrefix)) {
478
+ if (sha.includes(headValueRefPrefix)) {
481
479
  const cleanedRef = cleanRefValue(sha)
482
- const c = commitAtHeadIn(cleanedRef, references, commitsList)
480
+ const c = commitAtRefIn(cleanedRef, references, commitsList)
483
481
  if (!c) {
484
482
  throw new Error(`${cleanedRef} points to non-existing commit`)
485
483
  }
@@ -500,6 +498,24 @@ const shaishToCommit = (shaish: string, references: Map<string, Reference>, comm
500
498
  return [found[0], isRef, refKey]
501
499
  }
502
500
 
501
+ export const createHeadRefValue = (refKey: string) => {
502
+ return `${headValueRefPrefix}${refKey}`
503
+ }
504
+
505
+ export const isTagRef = (refKey: string) => refKey.indexOf(tagRefPathPrefix) > -1
506
+
507
+ export const cleanRefValue = (ref: string) => ref.replace(headValueRefPrefix, '')
508
+
509
+ export const brancheNameToRef = (name: string) => {
510
+ return `${headsRefPathPrefix}${name}`
511
+ }
512
+
513
+ export const validateBranchName = (name: string) => {
514
+ if (!validBranch(name)) {
515
+ throw new Error(`invalid ref name`)
516
+ }
517
+ }
518
+
503
519
  /**
504
520
  * Prints the underlying changelog of a repository
505
521
  * @param repository
@@ -508,7 +524,7 @@ export const printChangeLog = <T>(repository: RepositoryObject<T>) => {
508
524
  console.log('----------------------------------------------------------')
509
525
  console.log(`Changelog at ${repository.head()}`)
510
526
  const history = repository.getHistory()
511
- const head = commitAtHeadIn(repository.head(), history.refs, history.commits)
527
+ const head = commitAtRefIn(repository.head(), history.refs, history.commits)
512
528
  if (!head) {
513
529
  throw new Error(`fatal: HEAD is not defined`)
514
530
  }
@@ -0,0 +1,44 @@
1
+ import test from 'ava'
2
+ import {cleanAuthor} from './utils'
3
+
4
+ test('author <email@domain.info>', t => {
5
+ const [name, email] = cleanAuthor('author <email@domain.info>')
6
+ t.is(name, 'author')
7
+ t.is(email, 'email@domain.info')
8
+ })
9
+
10
+ test('author with space <email@domain.info>', t => {
11
+ const [name, email] = cleanAuthor('author with space <email@domain.info>')
12
+ t.is(name, 'author with space')
13
+ t.is(email, 'email@domain.info')
14
+ })
15
+
16
+ test('author @handle', t => {
17
+ const [name, email] = cleanAuthor('author @handle')
18
+ t.is(name, 'author')
19
+ t.is(email, '@handle')
20
+ })
21
+
22
+ test('author with space @handle', t => {
23
+ const [name, email] = cleanAuthor('author with space @handle')
24
+ t.is(name, 'author with space')
25
+ t.is(email, '@handle')
26
+ })
27
+
28
+ test('email@domain.info', t => {
29
+ const [name, email] = cleanAuthor('email@domain.info')
30
+ t.is(name, '')
31
+ t.is(email, 'email@domain.info')
32
+ })
33
+
34
+ test('@handle', t => {
35
+ const [name, email] = cleanAuthor('@handle')
36
+ t.is(name, '@handle')
37
+ t.is(email, '')
38
+ })
39
+
40
+ test('empty author', t => {
41
+ t.throws(() => {
42
+ cleanAuthor('')
43
+ }, {message: 'author not provided'})
44
+ })
package/src/utils.ts ADDED
@@ -0,0 +1,24 @@
1
+ // [RFC5322](https://www.ietf.org/rfc/rfc5322.txt)
2
+ 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])+)\])/
3
+
4
+ export const cleanAuthor = (author: string): [name: string, email: string] => {
5
+ if (author === '') {
6
+ throw new Error(`author not provided`)
7
+ }
8
+ // author name <email>
9
+ let strings = author.split(' <')
10
+ if (strings.length > 1) {
11
+ return [strings[0], strings[1].replace('>', '')]
12
+ }
13
+ // author name @handle
14
+ strings = author.split(' @')
15
+ if (strings.length > 1) {
16
+ return [strings[0], `@${strings[1]}`]
17
+ }
18
+ // email@domain.com
19
+ if (emailRegex.test(author)) {
20
+ return ['', author]
21
+ }
22
+ // unrecognized format
23
+ return [author, '']
24
+ }