/**
* @summary Interact with Git remote
*/
class GitRemote {
constructor(gitOps, remote) {
this.gitOps = gitOps;
this.name = remote;
}
/**
* @summary Fetch the casefiles from this remote to the local repository
* @returns {Promise.<null>}
*/
fetchSharedCasefiles() {
return this.gitOps.fetchSharedCasefilesFromRemote(this.name);
}
/**
* @summary Test if a casefile references commits unknown to this remote
*
* @param {Casefile} casefile
* Casefile with bookmarks to check against this remote
* @returns {Promise.<(Array.<string> | false)>}
* Either `false` (if this remote knows all referenced commits) or an
* Array of the commits that are unknown; this result is "truthy" if
* commits are unknown and `false` if all are known
*
* @description
* This method can be used prior to {@link .share} to determine
* whether the shared casefile will reference unknown commits. If the call to
* this method returns an Array (i.e. not `false`), three options can be
* offerred to the user: cancel the sharing of the casefile, push the unknown
* commits (possibly to `refs/collaboration/referenced-commits/<COMMIT-HASH>`)
* prior to sharing the casefile, or sharing the casefile despite the missing
* referent commits. A function implementing this logic would look like:
*
* ```
* async function prepareSharing(remote, casefile, { userSelectedAction }) {
* const unshared = remote.commitsUnknown(casefile);
* if (unshared) {
* // Let the user decide action
* switch (userSelectedAction(unshared)) {
* case 'shareWithoutPush':
* return true;
* case 'cancel':
* return false;
* case 'pushAndShare':
* await remote.pushCommitRefs(...unshared);
* return true;
* }
* }
* return true;
* }
* ```
*
* The function above resolves `true` if the casefile should be shared (with
* `remote.share(casefile)`) and `false` if not.
*
* This method only functions properly on a {@link Casefile} whose `bookmarks`
* embody the {@link Bookmark} type, specifically with regard to `peg.commit`
* and `children`.
*/
async commitsUnknown(casefile) {
const commits = await this.gitOps.selectCommitsUnknownToRemote(
this.name,
reduceBookmarkForestToCommits(casefile.bookmarks)
);
return commits.length ? commits : false;
}
/**
* @summary Share a {@link Casefile} to this remote
* @param {Casefile} casefile
* @returns {Promise.<{message: string, commit: ?string}>}
*/
share(casefile) {
return this.gitOps.shareCasefile(
this.name,
casefile.path,
casefile.bookmarks,
);
}
/**
* @summary Push some commits to unique names in this remote
* @param {...string} commits
* @returns {Promise.<null>}
*
* @description
* This method gets all of *commits* to this remote, allowing bookmarks to
* reference the commits even if they are not present in any other history
* shared with the remote.
*
* This method should be used with care — and always in response to a user
* prompt — since it could result in uploading significant history to this
* remote.
*/
async pushCommitRefs(...commits) {
return this.gitOps.push(this.name, ...commits.map(
commit => ({
source: commit,
dest: `refs/collaboration/referenced-commits/${commit}`,
force: true,
})
));
}
/**
* @param {...(string | Casefile)} casefiles
* Casefiles — or full paths to casefiles — to delete from this remote
* @returns {Promise.<null>}
*/
delete(...casefiles) {
return this.gitOps.deleteCasefilePaths(
this.name,
casefiles.map(casefile => (
typeof casefile === 'string'
? casefile
: casefile.path
)),
);
}
}
function reduceBookmarkForestToCommits(bookmarks) {
bookmarks = [...bookmarks];
const commits = new Set(), bookmarksSeen = new Set();
while (bookmarks.length) {
const bookmark = bookmarks.shift();
if (bookmark?.children?.length) {
for (const child of bookmark.children) {
if (bookmarksSeen.has(child)) continue;
bookmarks.push(child);
bookmarksSeen.add(child);
}
}
const markCommit = bookmark?.peg?.commit;
if (markCommit) {
commits.add(markCommit);
}
}
return [...commits];
}
export default GitRemote;