Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions electron/git/GitRepo.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,22 @@ describe("GitRepo", () => {
expect(log[0].timezoneOffset).toBe(new Date().getTimezoneOffset());
});

it("reports UTC as a timezone offset of 0, not -0", async () => {
await writeFile(dir, "a.subtext", "A");
await repo.commit({
canonicalNoteSlugs: ["a"],
aliases: [],
arbitraryFiles: [],
flushPins: false,
}, AUTHOR);
git(dir, "commit", "--quiet", "--amend", "--no-edit",
"--date=1700000000 +0000");

const [commit] = await repo.log({ limit: 1, offset: 0 });

expect(Object.is(commit.timezoneOffset, 0)).toBe(true);
});

it("pages through the history", async () => {
await save("a", "1");
await save("a", "2");
Expand Down
3 changes: 2 additions & 1 deletion electron/git/GitRepo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ function toTimezoneOffset(zone: string): number {
const match = /^([+-])(\d{2})(\d{2})$/.exec(zone ?? "");
if (!match) return 0;
const minutes = Number(match[2]) * 60 + Number(match[3]);
return match[1] === "+" ? -minutes : minutes;
// 0 - x, not -x: UTC must come out as 0, not -0.
return match[1] === "+" ? 0 - minutes : minutes;
}

/** Splits NUL-separated output into its non-empty fields. */
Expand Down
28 changes: 28 additions & 0 deletions electron/git/GitSync.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,34 @@ describe("GitSync", { timeout: 30_000 }, () => {
});
});

it("retries a rejected push when git prints no hints", async () => {
/*
With --porcelain, the "[rejected]" verdict goes to stdout; stderr
only carries the hints, which git config can switch off.
*/
const sync = makeSync(a, {
repo: new GitRepo({
dir: a,
gitPath: GIT,
extraConfig: [
"advice.pushUpdateRejected=false",
"advice.pushFetchFirst=false",
"advice.pushNonFFCurrent=false",
],
}),
});
await sync.start();
await sync.setAutoSync(true);
await pushFromB("from-b.subtext", "B");

await saveOnA(sync, "from-a", "A");
await sync.whenIdle();

expect(git(remote, "log", "-1", "--format=%s", "main"))
.toBe("create: from-a");
expect(sync.getStatus().unpushedCommits).toBe(0);
});

it("waits while the editor has unsaved changes", async () => {
let dirty = true;
const sync = makeSync(a, { isEditorDirty: () => dirty });
Expand Down
2 changes: 1 addition & 1 deletion electron/git/classifyError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const OFFLINE = [
export default function classifyError(error: unknown): NetworkFailure {
if (!(error instanceof GitError)) return "other";
if (error.killed) return "offline";
const text = error.stderr;
const text = `${error.stderr}\n${error.stdout}`;
if (AUTH.some((pattern) => pattern.test(text))) return "auth";
if (REJECTED.some((pattern) => pattern.test(text))) return "rejected";
if (OFFLINE.some((pattern) => pattern.test(text))) return "offline";
Expand Down
16 changes: 15 additions & 1 deletion electron/git/runGit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ export class GitError extends Error {
readonly args: string[];
readonly exitCode: number | null;
readonly stderr: string;
/*
Some verdicts only appear here: `push --porcelain` reports a rejected
ref on stdout, and stderr gets nothing but hints, which git config
can switch off.
*/
readonly stdout: string;
/** Killed for running over its time limit or by an abort. */
readonly killed: boolean;

Expand All @@ -46,6 +52,7 @@ export class GitError extends Error {
exitCode: number | null,
stderr: string,
killed: boolean,
stdout = "",
) {
const summary = stderr.trim().split("\n").slice(-3).join("\n");
super(
Expand All @@ -56,6 +63,7 @@ export class GitError extends Error {
this.args = args;
this.exitCode = exitCode;
this.stderr = stderr;
this.stdout = stdout;
this.killed = killed;
}
}
Expand Down Expand Up @@ -186,7 +194,13 @@ export function createGitRunner(options: {
const stderrText = Buffer.concat(stderr).toString("utf8");
const okCodes = runOptions.okCodes ?? [0];
if (killed || code === null || !okCodes.includes(code)) {
reject(new GitError(args, code, stderrText, killed));
reject(new GitError(
args,
code,
stderrText,
killed,
Buffer.concat(stdout).toString("utf8"),
));
return;
}
resolve({ code, stdout: Buffer.concat(stdout), stderr: stderrText });
Expand Down
Loading