gristlabs_grist-core/app/server/lib/HashUtil.ts
Paul Fitzpatrick 87f2fd15fb (core) add more detail to /compare endpoint
Summary:
 * Extends `/api/docs/docId1/compare/docId2` endpoint with a `detail=1` option to include details of what changed in the document content.
 * Adds an `/api/docs/docId/compare?left=HASH&right=HASH` endpoint for comparing two versions of a single document. This is needed to implement the extension to `/api/docs/docId1/compare/docId2`.
 * Adds a `HashUtil` class to allow hash aliases like `HEAD` and `HEAD~`.

Everything is a bit crude:
 * Changes are expressed as ActionSummary objects, which aren't fully fleshed out.
 * Extra data about formula columns is inserted in an inflexible way.

This is extracted and cleaned up from https://phab.getgrist.com/D2600.

Test Plan: added tests

Reviewers: dsagal

Reviewed By: dsagal

Differential Revision: https://phab.getgrist.com/D2614
2020-09-18 16:31:29 -04:00

47 lines
1.5 KiB
TypeScript

import { DocState } from 'app/common/UserAPI';
/**
*
* Helper class to support a small subset of git-style references for state hashes:
* HEAD = the most recent state
* [HASH]^1 = the parent of [HASH]
* [HASH]~1 = the parent of [HASH]
* [HASH]~2 = the grandparent of [HASH]
* [HASH]^1^1 = the grandparent of [HASH]
* [HASH]~3 = the great grandparent of [HASH]
* For git, where commits have multiple parents, "~" refers to the first parent,
* and "^1" also refers to the first parent. For grist, there are only first parents
* (unless/until we start tracking history across merges).
*
*/
export class HashUtil {
/**
* To construct, provide a list of states, most recent first.
*/
constructor(private _state: DocState[]) {}
/**
* Find the named hash in the list of states, allowing for aliases.
* Returns an index into the list of states provided in constructor.
*/
public hashToOffset(hash: string): number {
const parts = hash.split(/([~^][0-9]*)/);
hash = parts.shift() || '';
let offset = hash === 'HEAD' ? 0 : this._state.findIndex(state => state.h === hash);
if (offset < 0) { throw new Error('Cannot read hash'); }
for (const part of parts) {
if (part === '^' || part === '^1') {
offset++;
} else if (part.startsWith('~')) {
offset += parseInt(part.slice(1) || '1', 10);
} else if (part === '') {
// pass
} else {
throw new Error('cannot parse hash');
}
}
return offset;
}
}