eths-git
Version:
**eths-git-remote** is a decentralized Git solution designed to manage repositories on-chain. It provides two main components:
128 lines (127 loc) • 5.71 kB
JavaScript
import { stdout } from 'node:process';
import { asyncMap } from 'rxjs-async-map';
import { rxToStream, streamToStringRx } from 'rxjs-stream';
import { filter, map, mergeMap, scan } from 'rxjs/operators';
import { GitCommands } from '../types/index.js';
import { log } from "../utils/index.js";
const ONE_LINE_COMMANDS = [
GitCommands.capabilities,
GitCommands.option,
GitCommands.list,
];
// --- Git Remote Helper Main ---
export default async function GitRemoteHelper({ stdin, api }) {
const inputStream = streamToStringRx(stdin);
const commands = inputStream.pipe(
// The `line` can actually contain multiple lines, so we split them out into
// multiple pieces and recombine them again
map(line => line.split('\n')), mergeMap(lineGroup => lineGroup),
// Commands include a trailing newline which we don't need
map(line => line.trimEnd()), scan((acc, line) => {
// log('Scanning #NH7FyX', JSON.stringify({ acc, line }))
// If we emitted the last value, then we ignore all of the current lines
// and start fresh on the next "batch"
const linesWaitingToBeEmitted = acc.emit ? [] : acc.lines;
// When we hit an empty line, it's always the completion of a command
// block, so we always want to emit the lines we've been collecting.
// NOTE: We do not add the blank line onto the existing array of lines
// here, it gets dropped here.
if (line === '') {
if (linesWaitingToBeEmitted.length === 0) {
return { emit: false, lines: [] };
}
return { emit: true, lines: linesWaitingToBeEmitted };
}
// Some commands emit one line at a time and so do not get buffered
if (ONE_LINE_COMMANDS.find(command => line.startsWith(command))) {
// If we have other lines waiting for emission, something went wrong
if (linesWaitingToBeEmitted.length > 0) {
log('Got one line command with lines waiting #ompfQK', JSON.stringify({ linesWaitingToBeEmitted }));
throw new Error('Got one line command with lines waiting #evVyYv');
}
return { emit: true, lines: [line] };
}
// Otherwise, this line is part of a multi line command, so stick it
// into the "buffer" and do not emit
return { emit: false, lines: linesWaitingToBeEmitted.concat(line) };
}, { emit: false, lines: [] }), filter(acc => acc.emit), map(emitted => emitted.lines));
const capabilitiesResponse = [GitCommands.option, GitCommands.push, GitCommands.fetch]
.filter(option => {
if (option === GitCommands.option) {
return true;
}
else if (option === GitCommands.push) {
return typeof api.handlePush === 'function';
}
else if (option === GitCommands.fetch) {
return typeof api.handleFetch === 'function';
}
else {
throw new Error('Unknown option #GDhBnb');
}
})
.join('\n') + '\n\n';
// NOTE: Splitting this into 2 pipelines so typescript is happy that it
// produces a string
const output = commands.pipe(map((lines) => {
const command = lines[0];
if (command.startsWith('capabilities')) {
return { command: GitCommands.capabilities };
}
else if (command.startsWith(GitCommands.list)) {
return {
command: GitCommands.list,
forPush: command.startsWith('list for-push'),
};
}
else if (command.startsWith(GitCommands.option)) {
const [, key, value] = command.split(' ');
return { command: GitCommands.option, key, value };
}
else if (command.startsWith(GitCommands.fetch)) {
// Lines for fetch commands look like:
// fetch sha1 branchName
const refs = lines.map(line => {
const [, oid, ref] = line.split(' ');
return { oid, ref };
});
return { command: GitCommands.fetch, refs };
}
else if (command.startsWith(GitCommands.push)) {
// Lines for push commands look like this (the + means force push):
// push refs/heads/master:refs/heads/master
// push +refs/heads/master:refs/heads/master
const refs = lines.map(line => {
// Strip the leading `push ` from the line
const refsAndForce = line.slice(5);
const force = refsAndForce[0] === '+';
const refs = force ? refsAndForce.slice(1) : refsAndForce;
const [src, dst] = refs.split(':');
return { src, dst, force };
});
return { command: GitCommands.push, refs };
}
throw new Error('Unknown command #Py9QTP');
}), asyncMap(async (command) => {
if (command.command === GitCommands.capabilities) {
return capabilitiesResponse;
}
else if (command.command === GitCommands.option) {
return 'ok\n';
}
else if (command.command === GitCommands.list) {
const { forPush } = command;
return await api.list(forPush);
}
else if (command.command === GitCommands.push) {
const { refs } = command;
return await api.handlePush(refs);
}
else if (command.command === GitCommands.fetch) {
const { refs } = command;
return await api.handleFetch(refs);
}
throw new Error('Unrecognised command #e6nTnS');
}, 1));
rxToStream(output).pipe(stdout);
}