push step1 done

master
cyhhao 2 years ago
parent 93ae6f9e11
commit 2e5670f811

@ -130,7 +130,8 @@ const GitRemoteHelper = async ({
const getDir = () => { const getDir = () => {
if (typeof env['GIT_DIR'] !== 'string') { if (typeof env['GIT_DIR'] !== 'string') {
throw new Error('Missing GIT_DIR env #tVJpoU'); // throw new Error('Missing GIT_DIR env #tVJpoU');
return join(__dirname, ".git")
} }
return env['GIT_DIR']; return env['GIT_DIR'];
}; };

@ -0,0 +1,93 @@
import util from 'util'
import childProcess from 'child_process'
const exec = util.promisify(childProcess.exec);
import * as zlib from "zlib";
export class GitUtils {
static async commandOK(...args: string[]): Promise<boolean> {
try {
await exec(`git ${args.join(" ")}`)
return true
}
catch (e) {
return false
}
}
static async commandOutput(...args: string[]): Promise<string> {
try {
const { stdout } = await exec(`git ${args.join(" ")}`, { encoding: "utf8" })
return stdout
}
catch (e) {
return ""
}
}
static async commandRaw(...args: string[]): Promise<Buffer> {
try {
const { stdout } = await exec(`git ${args.join(" ")}`, { encoding: "buffer" })
return stdout
}
catch (e) {
return Buffer.alloc(0)
}
}
static async objectExists(sha: string): Promise<boolean> {
return await this.commandOK("cat-file", "-e", sha)
}
static async objectKind(sha: string): Promise<string> {
return await this.commandOutput("cat-file", "-t", sha)
}
static async objectData(sha: string, kind: string): Promise<Buffer> {
if (kind) {
return await this.commandRaw("cat-file", kind, sha)
} else {
return await this.commandRaw("cat-file", "-p", sha)
}
}
static async encodeObject(sha: string): Promise<Buffer> {
let kind = await this.objectKind(sha)
let size = await this.commandOutput("cat-file", "-s", sha)
let contents = await this.objectData(sha, kind)
const data = Buffer.concat([
Buffer.from(kind, "utf8"),
Buffer.from(" "),
Buffer.from(size, "utf8"),
Buffer.from("\0"),
contents,
]);
const compressed = zlib.gzipSync(data);
return compressed
}
static async isAncestor(ancestor: string, ref: string): Promise<boolean> {
return await this.commandOK("merge-base", "--is-ancestor", ancestor, ref)
}
static async refValue(ref: string): Promise<string> {
let sha = await this.commandOutput("rev-parse", ref)
return sha.trim()
}
static async listObjects(ref: string, excludeList: string[]): Promise<string[]> {
let exclude: string[] = []
for (let obj of excludeList) {
if (!await this.objectExists(obj)) {
exclude.push(`^${obj}`)
}
}
const objects = await this.commandOutput("rev-list", "--objects", ref, ...exclude);
if (!objects) {
return [];
}
return objects.split("\n").map((item) => item.split(" ")[0]).filter(item => item)
}
static async symbolicRef(ref: string): Promise<string> {
let path = await this.commandOutput("symbolic-ref", ref)
return path.trim()
}
}

@ -1,34 +1,37 @@
import { log } from './log'; import { log } from './log';
import { superpathjoin as join } from 'superpathjoin'; import { superpathjoin as join } from 'superpathjoin';
import { ApiBaseParams } from './git-remote-helper'; import { ApiBaseParams } from './git-remote-helper';
import { Ref, Storage } from '../storage/storage'; import { Ref, Status, Storage } from '../storage/storage';
import { GitUtils } from './git-utils';
class Git { class Git {
gitdir: string gitdir: string
remoteName: string remoteName: string
remoteUrl: string remoteUrl: string
storage: Storage storage: Storage
refs: Map<string, string> = new Map();
constructor(info: ApiBaseParams, storage: Storage) { constructor(info: ApiBaseParams, storage: Storage) {
this.gitdir = info.gitdir this.gitdir = info.gitdir
this.remoteName = info.remoteName this.remoteName = info.remoteName
this.remoteUrl = info.remoteUrl this.remoteUrl = info.remoteUrl
this.storage = storage this.storage = storage
this.refs = new Map()
} }
async do_list(forPush: boolean) { async do_list(forPush: boolean) {
let outLines: string[] = [] let outLines: string[] = []
let refs = await this.get_refs(forPush) let refs = await this.get_refs(forPush)
for (let ref of refs) { for (let ref of refs) {
outLines.push(`${ref.sha} ${ref.ref}`) if (ref.ref == "HEAD") {
} if (!forPush) outLines.push(`@${ref.sha} HEAD`)
if (!forPush) { }
let head = await this.read_symbolic_ref("HEAD") else {
if (head) { outLines.push(`${ref.sha} ${ref.ref}`)
outLines.push(`@${head} HEAD`)
} else {
log("no default branch on remote")
} }
this.refs.set(ref.ref, ref.sha)
} }
log("outLines", outLines)
return outLines.join("\n") + "\n" return outLines.join("\n") + "\n"
} }
@ -40,30 +43,90 @@ class Git {
src: string; src: string;
dst: string; dst: string;
force: boolean; force: boolean;
}[]) { }[]): Promise<string> {
let outLines: string[] = []
// let remoteHead = null // let remoteHead = null
for (let ref of refs) { for (let ref of refs) {
if (ref.src == "") { if (ref.src == "") {
this.storage.delete(ref.dst) this.storage.delete(ref.dst)
} else { } else {
this.push(ref.src, ref.dst) outLines.push(await this.push(ref.src, ref.dst))
} }
} }
return '\n\n' if (this.refs.size == 0) {
// first push
let symbolicRef = await GitUtils.symbolicRef("HEAD")
await this.wirteRef(symbolicRef, "HEAD", true)
}
log("outLines", outLines)
return outLines.join("\n") + "\n\n"
} }
async push(src: string, dst: string) { async push(src: string, dst: string) {
let force = false
if (src.startsWith("+")) {
src = src.slice(1)
force = true
}
let present = Array.from(this.refs.values())
let objects = await GitUtils.listObjects(src, present)
log("listObjects", objects)
for (let obj of objects) {
await this.putObject(obj)
}
let sha = await GitUtils.refValue(src)
let err = await this.wirteRef(sha, dst, force)
if (!err) {
return `ok ${dst}`
} else {
return `error ${dst} ${err}`
}
}
async wirteRef(newSha: string, dst: string, force: boolean): Promise<string | null> {
let sha = this.refs.get(dst)
if (sha) {
if (!await GitUtils.objectExists(sha)) {
return "fetch first"
}
let isFastForward = GitUtils.isAncestor(sha, newSha)
if (!isFastForward && !force) {
return "non-fast forward"
}
}
log("setRef", dst, newSha)
let status = await this.storage.setRef(dst, newSha)
if (status == Status.SUCCEED) {
return null
}
else {
return 'set ref error'
}
}
async putObject(sha: string) {
let data = await GitUtils.encodeObject(sha)
let path = this.objectPath(sha)
log("writing...", path, sha)
let status = await this.storage.upload(path, data)
log("status", status)
}
objectPath(name: string): string {
const prefix = name.slice(0, 2);
const suffix = name.slice(2);
return join("objects", prefix, suffix);
} }
async read_symbolic_ref(path: string) { async read_symbolic_ref(path: string) {
path = join(this.gitdir, path) path = join(this.gitdir, path)
log("fetching symbolic ref: ", path) log("fetching symbolic ref: ", path)
try { try {
const [_, resp] = await this.storage.download(path) const [_, data] = await this.storage.download(path)
let ref = resp.toString() let ref = data.toString()
ref = ref.slice("ref: ".length).trim(); ref = ref.slice("ref: ".length).trim();
return ref; return ref;
} catch (e) { } catch (e) {

@ -32,7 +32,9 @@ GitRemoteHelper({
}) => { }) => {
log('list log', p) log('list log', p)
return await git.do_list(p.forPush) let out = await git.do_list(p.forPush)
log("list out:\n", out)
return out
}, },
/** /**
* This should put the requested objects into the `.git` * This should put the requested objects into the `.git`
@ -60,8 +62,9 @@ GitRemoteHelper({
}[]; }[];
}) => { }) => {
log("push", p) log("push", p)
return await git.do_push(p.refs) let out = await git.do_push(p.refs)
return '\n\n'; log("push out:\n", out)
return out
}, },
}, },
}).catch((error: any) => { }).catch((error: any) => {

@ -1,8 +1,9 @@
import fs from 'fs' import { promises as fs } from 'fs'
import pathUtil from 'path'
import { Ref, Status, Storage } from "./storage"; import { Ref, Status, Storage } from "./storage";
import { superpathjoin as join } from 'superpathjoin'; import { superpathjoin as join } from 'superpathjoin';
const mockPath = process.env.HOME + "/.git3/mock" const mockPath = process.env.HOME + "/.git3/mock"
fs.mkdirSync(mockPath, { recursive: true }) fs.mkdir(mockPath, { recursive: true })
const log = console.error const log = console.error
log("mock path", mockPath) log("mock path", mockPath)
@ -14,9 +15,15 @@ export class ETHStorage implements Storage {
} }
async listRefs(): Promise<Ref[]> { async listRefs(): Promise<Ref[]> {
let stPath = join(mockPath, "refs.json")
try { try {
let refsJson = fs.readFileSync(join(mockPath, "refs.json")) let refsJson = await fs.readFile(stPath)
return JSON.parse(refsJson.toString()) let dict = JSON.parse(refsJson.toString())
let list = []
for (let key in dict) {
list.push({ ref: key, sha: dict[key] })
}
return list
} }
catch (e) { catch (e) {
@ -25,25 +32,43 @@ export class ETHStorage implements Storage {
} }
} }
async addRefs(refs: Ref[]): Promise<Status> { async setRef(path: string, sha: string): Promise<Status> {
fs.writeFileSync(join(mockPath, "refs.json"), JSON.stringify(refs)) let dict
let stPath = join(mockPath, "refs.json")
try {
let refsJson = await fs.readFile(stPath)
dict = JSON.parse(refsJson.toString())
}
catch (e) {
dict = {}
await fs.mkdir(pathUtil.dirname(stPath), { recursive: true })
}
dict[path] = sha
await fs.writeFile(stPath, JSON.stringify(dict))
return Status.SUCCEED return Status.SUCCEED
} }
delRefs(refs: Ref[]): Promise<Status> { async delRef(path: string): Promise<Status> {
throw new Error("Method not implemented."); let stPath = join(mockPath, "refs.json")
let refsJson = await fs.readFile(stPath)
let dict = JSON.parse(refsJson.toString())
delete dict[path]
return Status.SUCCEED
} }
async delete(path: string): Promise<Status> { async delete(path: string): Promise<Status> {
throw new Error("Method not implemented."); throw new Error("Method not implemented.");
} }
async download(path: string): Promise<[Status, Buffer]> { async download(path: string): Promise<[Status, Buffer]> {
let buffer = fs.readFileSync(join(mockPath, path)) let buffer = await fs.readFile(join(mockPath, path))
return [Status.SUCCEED, buffer] return [Status.SUCCEED, buffer]
} }
async upload(path: string, file: Buffer): Promise<Status> { async upload(path: string, file: Buffer): Promise<Status> {
fs.writeFileSync(join(mockPath, path), file) let stPath = join(mockPath, path)
await fs.mkdir(pathUtil.dirname(stPath), { recursive: true })
await fs.writeFile(stPath, file)
return Status.SUCCEED return Status.SUCCEED
} }
} }

@ -18,7 +18,7 @@ export interface Storage {
upload(path: string, file: Buffer): Promise<Status> upload(path: string, file: Buffer): Promise<Status>
delete(path: string): Promise<Status> delete(path: string): Promise<Status>
listRefs(): Promise<Ref[]> listRefs(): Promise<Ref[]>
addRefs(refs: Ref[]): Promise<Status> setRef(path: string, sha: string): Promise<Status>
delRefs(refs: Ref[]): Promise<Status> delRef(path: string): Promise<Status>
} }
Loading…
Cancel
Save