diff --git a/.vscode/launch.json b/.vscode/launch.json index 196629e7..f48147bf 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,6 +4,31 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { + "name": "node dist/git-stacked-rebase.js origin/master", + "program": "${workspaceFolder}/dist/git-stacked-rebase.js", + "request": "launch", + "args": [ + "origin/master" // + ], + "skipFiles": [ + "/**" // + ], + "type": "node" + }, + { + "name": "node dist/git-stacked-rebase.js origin/master --apply", + "program": "${workspaceFolder}/dist/git-stacked-rebase.js", + "request": "launch", + "args": [ + "origin/master", // + "--apply" + ], + "skipFiles": [ + "/**" // + ], + "type": "node" + }, { "name": "ts-node tests", "type": "node", diff --git a/git-stacked-rebase.ts b/git-stacked-rebase.ts index a579b9e0..0aed6be6 100755 --- a/git-stacked-rebase.ts +++ b/git-stacked-rebase.ts @@ -43,7 +43,7 @@ export type OptionsForGitStackedRebase = { /** * editor name, or a function that opens the file inside some editor. */ - editor: string | ((ctx: { filePath: string }) => Promise); + editor: string | ((ctx: { filePath: string }) => void | Promise); /** * for executing raw git commands diff --git a/humanOp.ts b/humanOp.ts new file mode 100644 index 00000000..58ebb46c --- /dev/null +++ b/humanOp.ts @@ -0,0 +1,47 @@ +/** + * initially extracted as test utils, + * but i feel like these could be used to automate things + * thru the CLI that would need to be done inside the + * interactive mode. + */ + +import fs from "fs"; + +import { RegularRebaseCommand } from "./parse-todo-of-stacked-rebase/validator"; + +type CommonArgs = { + filePath: string; // + commitSHA: string; +}; + +/** + * TODO general "HumanOp" for `appendLineAfterNthCommit` & similar utils + */ +export function humanOpAppendLineAfterNthCommit(newLine: string, { filePath, commitSHA }: CommonArgs): void { + const file = fs.readFileSync(filePath, { encoding: "utf-8" }); + const lines = file.split("\n"); + const lineIdx: number = lines.findIndex((line) => line.startsWith(`pick ${commitSHA}`)); + + console.log("commitSHA: %s, lineIdx: %s, newLine: %s", commitSHA, lineIdx, newLine); + + lines.splice(lineIdx, 0, newLine); + + fs.writeFileSync(filePath, lines.join("\n")); +} + +export function humanOpChangeCommandOfNthCommitInto( + newCommand: RegularRebaseCommand, + { commitSHA, filePath }: CommonArgs +): void { + const file = fs.readFileSync(filePath, { encoding: "utf-8" }); + const lines = file.split("\n"); + const lineIdx: number = lines.findIndex((line) => line.startsWith(`pick ${commitSHA}`)); + + console.log("commitSHA: %s, lineIdx: %s, newCommand: %s", commitSHA, lineIdx, newCommand); + + const parts = lines[lineIdx].split(" "); + parts[0] = newCommand; + lines[lineIdx] = parts.join(" "); + + fs.writeFileSync(filePath, lines.join("\n")); +} diff --git a/parse-todo-of-stacked-rebase/parseNewGoodCommands.spec.ts b/parse-todo-of-stacked-rebase/parseNewGoodCommands.spec.ts new file mode 100644 index 00000000..45995d51 --- /dev/null +++ b/parse-todo-of-stacked-rebase/parseNewGoodCommands.spec.ts @@ -0,0 +1,36 @@ +/* eslint-disable @typescript-eslint/camelcase */ + +import { gitStackedRebase } from "../git-stacked-rebase"; +import { humanOpAppendLineAfterNthCommit } from "../humanOp"; + +import { setupRepoWithStackedBranches } from "../test/setupRepo"; + +export async function parseNewGoodCommandsSpec() { + await succeeds_to_apply_after_break_or_exec(); + + async function succeeds_to_apply_after_break_or_exec() { + const { initialBranch, commitOidsInLatestStacked, dir, config } = await setupRepoWithStackedBranches(); + + const branch = initialBranch.shorthand(); + const common = { + gitDir: dir, + getGitConfig: () => config, + } as const; + + await gitStackedRebase(branch, { + ...common, + editor: ({ filePath }) => { + const commitSHA: string = commitOidsInLatestStacked[7].tostrS(); + humanOpAppendLineAfterNthCommit("break", { + filePath, + commitSHA, + }); + }, + }); + + await gitStackedRebase(branch, { + ...common, + apply: true, + }); + } +} diff --git a/parse-todo-of-stacked-rebase/parseNewGoodCommands.ts b/parse-todo-of-stacked-rebase/parseNewGoodCommands.ts index 096878a4..33c8a5ef 100644 --- a/parse-todo-of-stacked-rebase/parseNewGoodCommands.ts +++ b/parse-todo-of-stacked-rebase/parseNewGoodCommands.ts @@ -10,7 +10,11 @@ import { filenames } from "../filenames"; import { readRewrittenListNotAppliedOrAppliedOrError } from "../apply"; import { parseTodoOfStackedRebase } from "./parseTodoOfStackedRebase"; -import { GoodCommand, stackedRebaseCommands } from "./validator"; +import { + GoodCommand, // + namesOfRebaseCommandsThatWillDisappearFromCommandList, + stackedRebaseCommands, +} from "./validator"; export function parseNewGoodCommands( repo: Git.Repository, @@ -95,6 +99,13 @@ export function parseNewGoodCommands( const oldCommandAtIdx: GoodCommand = oldGoodCommands[goodCommandMinIndex]; + if (namesOfRebaseCommandsThatWillDisappearFromCommandList.includes(oldCommandAtIdx.commandName)) { + goodCommandMinIndex++; // the command should disappear, + i--; // but the commit should not be lost. + + continue; + } + if (oldCommandAtIdx.commandName in stackedRebaseCommands) { goodNewCommands.push({ ...oldCommandAtIdx, @@ -184,7 +195,10 @@ export function parseNewGoodCommands( ["stackedRebaseCommandsNew.length"]: stackedRebaseCommandsNew.length, }); - assert(stackedRebaseCommandsOld.length === stackedRebaseCommandsNew.length); + const oldCommandCount: number = stackedRebaseCommandsOld.length; + const newCommandCount: number = stackedRebaseCommandsNew.length; + + assert.equal(oldCommandCount, newCommandCount); return stackedRebaseCommandsNew; } diff --git a/parse-todo-of-stacked-rebase/validator.ts b/parse-todo-of-stacked-rebase/validator.ts index 8bfca2ec..38f84fdb 100644 --- a/parse-todo-of-stacked-rebase/validator.ts +++ b/parse-todo-of-stacked-rebase/validator.ts @@ -27,12 +27,14 @@ type Command = { parseTargets: ParseTargets; makesGitRebaseExitToPause: boolean; + willDisappearFromCommandsListInNextGitRebaseTodoFile: boolean; }; const createCommand = ( nameButNeverAlias: string, { makesGitRebaseExitToPause, + willDisappearFromCommandsListInNextGitRebaseTodoFile, parseTargets = ({ split }) => { assert( split.length >= 2, @@ -57,6 +59,11 @@ const createCommand = ( */ makesGitRebaseExitToPause: boolean; + /** + * TODO RENAME + */ + willDisappearFromCommandsListInNextGitRebaseTodoFile: boolean; + // nameOrAlias: EitherRebaseEitherCommandOrAlias, // parseTargets?: Command["parseTargets"]; maxUseCount?: number; @@ -68,23 +75,33 @@ const createCommand = ( nameButNeverAlias: nameButNeverAlias as EitherRebaseCommand, // TODO: TS parseTargets, makesGitRebaseExitToPause, + willDisappearFromCommandsListInNextGitRebaseTodoFile, }); export const regularRebaseCommands = { - pick: createCommand("pick", { makesGitRebaseExitToPause: false }), + pick: createCommand("pick", { + makesGitRebaseExitToPause: false, + willDisappearFromCommandsListInNextGitRebaseTodoFile: false, // + }), // p: standardCommand, reword: createCommand("reword", { makesGitRebaseExitToPause: false /** opens editor & then continues, w/o exiting in between */, + willDisappearFromCommandsListInNextGitRebaseTodoFile: false, }), // r: standardCommand, - edit: createCommand("edit", { makesGitRebaseExitToPause: true }), + edit: createCommand("edit", { + makesGitRebaseExitToPause: true, + willDisappearFromCommandsListInNextGitRebaseTodoFile: false, // + }), // e: standardCommand, squash: createCommand("squash", { makesGitRebaseExitToPause: false /** opens editor & then continues, w/o exiting in between */, + willDisappearFromCommandsListInNextGitRebaseTodoFile: false, }), // s: standardCommand, fixup: createCommand("fixup", { makesGitRebaseExitToPause: false /** opens editor & then continues, w/o exiting in between */, + willDisappearFromCommandsListInNextGitRebaseTodoFile: false, parseTargets: ({ split }) => { /** @@ -102,20 +119,35 @@ export const regularRebaseCommands = { // f: standardCommand, exec: createCommand("exec", { makesGitRebaseExitToPause: false, // + willDisappearFromCommandsListInNextGitRebaseTodoFile: true, parseTargets: ({ rest }) => [rest], }), // x: standardCommand, - break: createCommand("break", { makesGitRebaseExitToPause: true, parseTargets: () => null }), + break: createCommand("break", { + makesGitRebaseExitToPause: true, + willDisappearFromCommandsListInNextGitRebaseTodoFile: true, // + parseTargets: () => null, + }), // b: standardCommand, - drop: createCommand("drop", { makesGitRebaseExitToPause: false }), + drop: createCommand("drop", { + makesGitRebaseExitToPause: false, + willDisappearFromCommandsListInNextGitRebaseTodoFile: false, // TODO + }), // d: standardCommand, - label: createCommand("label", { makesGitRebaseExitToPause: false /** TODO VERIFY */ }), + label: createCommand("label", { + makesGitRebaseExitToPause: false /** TODO VERIFY */, + willDisappearFromCommandsListInNextGitRebaseTodoFile: false, // TODO VERIFY + }), // l: standardCommand, - reset: createCommand("reset", { makesGitRebaseExitToPause: false /** TODO VERIFY */ }), + reset: createCommand("reset", { + makesGitRebaseExitToPause: false /** TODO VERIFY */, + willDisappearFromCommandsListInNextGitRebaseTodoFile: false, // TODO VERIFY + }), // t: standardCommand, merge: createCommand("merge", { makesGitRebaseExitToPause: false /** TODO VERIFY */, + willDisappearFromCommandsListInNextGitRebaseTodoFile: false, // TODO VERIFY parseTargets: ({ split }) => { if (["-C", "-c"].includes(split[1])) { @@ -192,6 +224,7 @@ const branchValidator: Validator = ({ rest, reasonsIfBad: reasonsWhyInvalid }) = export const stackedRebaseCommands = { "branch-end": createCommand("branch-end", { makesGitRebaseExitToPause: false, + willDisappearFromCommandsListInNextGitRebaseTodoFile: false, maxUseCount: Infinity, isRestValid: branchValidator, @@ -199,6 +232,7 @@ export const stackedRebaseCommands = { }), "branch-end-new": createCommand("branch-end-new", { makesGitRebaseExitToPause: false, + willDisappearFromCommandsListInNextGitRebaseTodoFile: false, maxUseCount: Infinity, isRestValid: branchValidator, @@ -206,6 +240,7 @@ export const stackedRebaseCommands = { }), "branch-end-initial": createCommand("branch-end-initial", { makesGitRebaseExitToPause: false, + willDisappearFromCommandsListInNextGitRebaseTodoFile: false, maxUseCount: 1, isRestValid: branchValidator, @@ -213,6 +248,7 @@ export const stackedRebaseCommands = { }), "branch-end-last": createCommand("branch-end-last", { makesGitRebaseExitToPause: false, + willDisappearFromCommandsListInNextGitRebaseTodoFile: false, maxUseCount: 1, isRestValid: branchValidator, @@ -260,11 +296,17 @@ const allEitherRebaseCommandAliases = { export const rebaseCommandsThatMakeRebaseExitToPause: Command[] = Object.values(allEitherRebaseCommands).filter( (cmd) => cmd.makesGitRebaseExitToPause ); - export const namesOfRebaseCommandsThatMakeRebaseExitToPause: EitherRebaseCommand[] = rebaseCommandsThatMakeRebaseExitToPause.map( (cmd) => cmd.nameButNeverAlias ); +export const rebaseCommandsThatWillDisappearFromCommandList: Command[] = Object.values(allEitherRebaseCommands).filter( + (cmd) => cmd.willDisappearFromCommandsListInNextGitRebaseTodoFile +); +export const namesOfRebaseCommandsThatWillDisappearFromCommandList: EitherRebaseCommand[] = rebaseCommandsThatWillDisappearFromCommandList.map( + (cmd) => cmd.nameButNeverAlias +); + type LineNr = { /** * indexed from 0. diff --git a/test/.gitignore b/test/.gitignore index 5deb4c5a..484ec6a1 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -1,2 +1,3 @@ folders-to-delete .tmp +.tmp-* diff --git a/test/experiment.spec.ts b/test/experiment.spec.ts index 5941c977..b2b0dbf0 100755 --- a/test/experiment.spec.ts +++ b/test/experiment.spec.ts @@ -1,82 +1,21 @@ #!/usr/bin/env ts-node-dev import fs from "fs"; -import path from "path"; -import assert from "assert"; -import Git from "nodegit"; +import { setupRepoWithStackedBranches } from "./setupRepo"; import { gitStackedRebase, defaultGitCmd } from "../git-stacked-rebase"; - -import { RegularRebaseCommand } from "../parse-todo-of-stacked-rebase/validator"; -import { createExecSyncInRepo } from "../util/execSyncInRepo"; -import { configKeys } from "../configKeys"; +import { humanOpChangeCommandOfNthCommitInto } from "../humanOp"; export async function testCase() { const { - repo, // - config, - sig, + initialBranch, // dir, - } = await setupRepo(); - - const commitOidsInInitial: Git.Oid[] = []; - const initialBranch: Git.Reference = await appendCommitsTo(commitOidsInInitial, 3, repo, sig); - - const latestStackedBranch: Git.Reference = await Git.Branch.create( - repo, - "stack-latest", - await repo.getHeadCommit(), - 0 - ); - await repo.checkoutBranch(latestStackedBranch); - - const execSyncInRepo = createExecSyncInRepo(repo); - - // const read = () => execSyncInRepo("read"); - const read = () => void 0; - - read(); - - const commitOidsInLatestStacked: Git.Oid[] = []; - await appendCommitsTo(commitOidsInLatestStacked, 12, repo, sig); - - const newPartialBranches = [ - ["partial-1", 4], - ["partial-2", 6], - ["partial-3", 8], - ] as const; - - console.log("launching 1st rebase to create partial branches"); - await gitStackedRebase(initialBranch.shorthand(), { - gitDir: dir, - getGitConfig: () => config, - editor: async ({ filePath }) => { - console.log("filePath %s", filePath); - - for (const [newPartial, nthCommit] of newPartialBranches) { - await humanOpAppendLineAfterNthCommit( - filePath, - commitOidsInLatestStacked[nthCommit].tostrS(), - `branch-end-new ${newPartial}` - ); - } - - console.log("finished editor"); - - read(); - }, - }); - - console.log("looking up branches to make sure they were created successfully"); - read(); - for (const [newPartial] of newPartialBranches) { - /** - * will throw if branch does not exist - * TODO "properly" expect to not throw - */ - await Git.Branch.lookup(repo, newPartial, Git.Branch.BRANCH.LOCAL); - } + config, + commitOidsInLatestStacked, + read, + execSyncInRepo, + } = await setupRepoWithStackedBranches(); /** * @@ -92,7 +31,7 @@ export async function testCase() { editor: async ({ filePath }) => { const SHA = commitOidsInLatestStacked[nthCommit2ndRebase].tostrS(); - humanOpChangeCommandOfNthCommitInto("edit", SHA, filePath); + humanOpChangeCommandOfNthCommitInto("edit", { filePath, commitSHA: SHA }); }, }); /** @@ -133,122 +72,3 @@ export async function testCase() { apply: true, }); } - -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type -export async function setupRepo() { - const dir: string = path.join(__dirname, ".tmp"); - if (fs.existsSync(dir)) { - fs.rmdirSync(dir, { recursive: true }); - } - fs.mkdirSync(dir); - console.log("tmpdir path %s", dir); - - const foldersToDeletePath: string = path.join(__dirname, "folders-to-delete"); - fs.appendFileSync(foldersToDeletePath, dir + "\n", { encoding: "utf-8" }); - - process.chdir(dir); - console.log("chdir to tmpdir"); - - const isBare = 0; - const repo: Git.Repository = await Git.Repository.init(dir, isBare); - - const config: Git.Config = await repo.config(); - - await config.setBool(configKeys.autoApplyIfNeeded, Git.Config.MAP.FALSE); - await config.setString("user.email", "tester@test.com"); - await config.setString("user.name", "tester"); - - /** - * gpg signing in tests not possible i believe, - * at least wasn't working. - */ - await config.setBool(configKeys.gpgSign, Git.Config.MAP.FALSE); - - /** - * fixups / not implemented in libgit2. - * though, would be better if received empty/minimal config by default.. - */ - await config.setString("merge.conflictStyle", "diff3"); // zdiff3 - - const sig: Git.Signature = await Git.Signature.default(repo); - console.log("sig %s", sig); - - const inicialCommitId = "Initial commit (from setupRepo)"; - const initialCommit: Git.Oid = await fs.promises - .writeFile(inicialCommitId, inicialCommitId) // - .then(() => repo.createCommitOnHead([inicialCommitId], sig, sig, inicialCommitId)); - - return { - dir, - repo, - config, - sig, - initialCommit, - } as const; -} - -async function appendCommitsTo( - alreadyExistingCommits: Git.Oid[], - n: number, - repo: Git.Repository, // - sig: Git.Signature -): Promise { - assert(n > 0, "cannot append <= 0 commits"); - - const commits: string[] = new Array(n) - .fill(0) // - .map((_, i) => "a".charCodeAt(0) + i + alreadyExistingCommits.length) - .map((ascii) => String.fromCharCode(ascii)); - - for (const c of commits) { - const branchName: string = repo.isEmpty() ? "" : (await repo.getCurrentBranch()).shorthand(); - const cInBranch: string = c + " in " + branchName; - - const oid: Git.Oid = await fs.promises - .writeFile(c, cInBranch) // - .then(() => repo.createCommitOnHead([c], sig, sig, cInBranch)); - - alreadyExistingCommits.push(oid); - - console.log(`oid of commit "%s" in branch "%s": %s`, c, branchName, oid); - } - - return repo.getCurrentBranch(); -} - -/** - * TODO general "HumanOp" for `appendLineAfterNthCommit` & similar utils - */ -async function humanOpAppendLineAfterNthCommit( - filePath: string, // - commitSHA: string, - newLine: string -): Promise { - const file = await fs.promises.readFile(filePath, { encoding: "utf-8" }); - const lines = file.split("\n"); - const lineIdx: number = lines.findIndex((line) => line.startsWith(`pick ${commitSHA}`)); - - console.log("commitSHA: %s, lineIdx: %s, newLine: %s", commitSHA, lineIdx, newLine); - - lines.splice(lineIdx, 0, newLine); - - await fs.promises.writeFile(filePath, lines.join("\n")); -} - -function humanOpChangeCommandOfNthCommitInto( - newCommand: RegularRebaseCommand, // - commitSHA: string, - filePath: string -): void { - const file = fs.readFileSync(filePath, { encoding: "utf-8" }); - const lines = file.split("\n"); - const lineIdx: number = lines.findIndex((line) => line.startsWith(`pick ${commitSHA}`)); - - console.log("commitSHA: %s, lineIdx: %s, newCommand: %s", commitSHA, lineIdx, newCommand); - - const parts = lines[lineIdx].split(" "); - parts[0] = newCommand; - lines[lineIdx] = parts.join(" "); - - fs.writeFileSync(filePath, lines.join("\n")); -} diff --git a/test/run.ts b/test/run.ts index 7b892ea1..179d77ad 100644 --- a/test/run.ts +++ b/test/run.ts @@ -3,11 +3,16 @@ import { testCase } from "./experiment.spec"; import reducePathTC from "../reducePath.spec"; +import { parseNewGoodCommandsSpec } from "../parse-todo-of-stacked-rebase/parseNewGoodCommands.spec"; +import { sequentialResolve } from "../util/sequentialResolve"; + main(); function main() { - Promise.all([ - testCase(), // - reducePathTC(), + // TODO Promise.all + sequentialResolve([ + testCase, // + async () => reducePathTC(), + parseNewGoodCommandsSpec, ]) .then(() => process.stdout.write("\nsuccess\n\n")) .catch((e) => { diff --git a/test/setupRepo.ts b/test/setupRepo.ts new file mode 100644 index 00000000..ce132aad --- /dev/null +++ b/test/setupRepo.ts @@ -0,0 +1,209 @@ +import fs from "fs"; +import path from "path"; +import assert from "assert"; + +import Git from "nodegit"; + +import { gitStackedRebase } from "../git-stacked-rebase"; +import { configKeys } from "../configKeys"; +import { humanOpAppendLineAfterNthCommit } from "../humanOp"; + +import { createExecSyncInRepo } from "../util/execSyncInRepo"; + +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type + +type Opts = { + blockWithRead?: boolean; + commitCount?: number; +} & SetupRepoOpts; +export async function setupRepoWithStackedBranches({ + blockWithRead = false, // + commitCount = 12, + ...rest +}: Opts = {}) { + const { + repo, // + config, + sig, + dir, + } = await setupRepo(rest); + + const commitOidsInInitial: Git.Oid[] = []; + const initialBranch: Git.Reference = await appendCommitsTo(commitOidsInInitial, 3, repo, sig); + + const latestStackedBranch: Git.Reference = await Git.Branch.create( + repo, + "stack-latest", + await repo.getHeadCommit(), + 0 + ); + await repo.checkoutBranch(latestStackedBranch); + + const execSyncInRepo = createExecSyncInRepo(repo); + + const read = (): void => (blockWithRead ? void execSyncInRepo("read") : void 0); + + read(); + + const commitOidsInLatestStacked: Git.Oid[] = []; + await appendCommitsTo(commitOidsInLatestStacked, commitCount, repo, sig); + + const newPartialBranches = [ + ["partial-1", 4], + ["partial-2", 6], + ["partial-3", 8], + ] as const; + + console.log("launching 0th rebase to create partial branches"); + await gitStackedRebase(initialBranch.shorthand(), { + gitDir: dir, + getGitConfig: () => config, + editor: ({ filePath }) => { + console.log("filePath %s", filePath); + + for (const [newPartial, nthCommit] of newPartialBranches) { + humanOpAppendLineAfterNthCommit(`branch-end-new ${newPartial}`, { + filePath, + commitSHA: commitOidsInLatestStacked[nthCommit].tostrS(), + }); + } + + console.log("finished editor"); + + read(); + }, + }); + + // console.log("looking up branches to make sure they were created successfully"); + read(); + for (const [newPartial] of newPartialBranches) { + /** + * will throw if branch does not exist + * TODO "properly" expect to not throw + */ + await Git.Branch.lookup(repo, newPartial, Git.Branch.BRANCH.LOCAL); + } + + return { + repo, + config, + sig, + dir, + + commitOidsInInitial, + initialBranch, + latestStackedBranch, + execSyncInRepo, + read, + commitOidsInLatestStacked, + newPartialBranches, + } as const; +} + +type SetupRepoOpts = { + tmpdir?: boolean; +}; +export async function setupRepo({ + tmpdir = true, // +}: SetupRepoOpts = {}) { + const dir: string = createTmpdir(tmpdir); + + const foldersToDeletePath: string = path.join(__dirname, "folders-to-delete"); + if (!fs.existsSync(foldersToDeletePath)) { + fs.writeFileSync(foldersToDeletePath, ""); + } + const deletables = fs.readFileSync(foldersToDeletePath, { encoding: "utf-8" }).split("\n"); + for (const d of deletables) { + if (fs.existsSync(d)) { + fs.rmdirSync(d, { recursive: true }); + } + } + fs.appendFileSync(foldersToDeletePath, dir + "\n", { encoding: "utf-8" }); + + /** + * TODO make concurrent-safe (lol) + */ + process.chdir(dir); + console.log("chdir to tmpdir %s", dir); + + const isBare = 0; + const repo: Git.Repository = await Git.Repository.init(dir, isBare); + + const config: Git.Config = await repo.config(); + + await config.setBool(configKeys.autoApplyIfNeeded, Git.Config.MAP.FALSE); + await config.setString("user.email", "tester@test.com"); + await config.setString("user.name", "tester"); + + /** + * gpg signing in tests not possible i believe, + * at least wasn't working. + */ + await config.setBool(configKeys.gpgSign, Git.Config.MAP.FALSE); + + /** + * fixups / not implemented in libgit2. + * though, would be better if received empty/minimal config by default.. + */ + await config.setString("merge.conflictStyle", "diff3"); // zdiff3 + + const sig: Git.Signature = await Git.Signature.default(repo); + console.log("sig %s", sig); + + const inicialCommitId = "Initial commit (from setupRepo)"; + const initialCommit: Git.Oid = await fs.promises + .writeFile(inicialCommitId, inicialCommitId) // + .then(() => repo.createCommitOnHead([inicialCommitId], sig, sig, inicialCommitId)); + + console.log("initial commit %s", initialCommit.tostrS()); + + return { + dir, + repo, + config, + sig, + initialCommit, + } as const; +} + +function createTmpdir(random: boolean = true): string { + if (random) { + return fs.mkdtempSync(path.join(__dirname, ".tmp-"), { encoding: "utf-8" }); + } + + const dir = path.join(__dirname, ".tmp"); + if (fs.existsSync(dir)) { + fs.rmdirSync(dir, { recursive: true }); + } + fs.mkdirSync(dir); + return dir; +} + +async function appendCommitsTo( + alreadyExistingCommits: Git.Oid[], + n: number, + repo: Git.Repository, // + sig: Git.Signature +): Promise { + assert(n > 0, "cannot append <= 0 commits"); + + const commits: string[] = new Array(n) + .fill(0) // + .map((_, i) => "a".charCodeAt(0) + i + alreadyExistingCommits.length) + .map((ascii) => String.fromCharCode(ascii)); + + for (const c of commits) { + const branchName: string = repo.isEmpty() ? "" : (await repo.getCurrentBranch()).shorthand(); + const cInBranch: string = c + " in " + branchName; + + const oid: Git.Oid = await fs.promises + .writeFile(c, cInBranch) // + .then(() => repo.createCommitOnHead([c], sig, sig, cInBranch)); + + alreadyExistingCommits.push(oid); + + console.log(`oid of commit "%s" in branch "%s": %s`, c, branchName, oid); + } + + return repo.getCurrentBranch(); +} diff --git a/util/sequentialResolve.ts b/util/sequentialResolve.ts new file mode 100644 index 00000000..71d08a36 --- /dev/null +++ b/util/sequentialResolve.ts @@ -0,0 +1,5 @@ +export const sequentialResolve = (xs: (() => Promise)[]): Promise => + xs.reduce( + (prev, curr) => prev.then(curr), // + (Promise.resolve() as unknown) as Promise + );