import $ from "@david/dax"; import { existsSync } from "@std/fs/exists"; import { join } from "@std/path"; import { Notebook } from "./notebook.ts"; export function hasGit(notebook: Notebook): boolean { return existsSync(join(notebook.root, ".git"), { isDirectory: true }); } export async function hasRemote(notebook: Notebook): Promise { if (!hasGit(notebook)) return false; const remotes = await $`git remote`.cwd(notebook.root).text(); return remotes.trim() !== ""; } export async function commit(notebook: Notebook, message: string) { if (!hasGit(notebook)) return; const changed = await $`git status --porcelain`.cwd(notebook.root).text(); if (changed.trim() === "") return; await $`git add .`.cwd(notebook.root); await $`git commit -am ${message}`.cwd(notebook.root); } export async function initRepo(path: string) { await $`git init ${path}`; } function lastSyncTime(notebook: Notebook): Temporal.Instant | undefined { try { return Deno.statSync(join(notebook.root, ".git", "FETCH_HEAD")).mtime ?.toTemporalInstant(); } catch { return; } } export async function syncNotebook(notebook: Notebook, force: boolean = false) { if (!hasGit(notebook)) return; const root = notebook.root; const status = await $`git status --porcelain`.cwd(root).text(); if (status.trim() !== "") { await commit(notebook, "Checkpointing untracked changes"); } if (!await hasRemote(notebook)) return; const last = lastSyncTime(notebook); // FIXME(bmps): Make time configurable if ( force || (last != null && (Temporal.Now.instant().since(last).total("minutes") >= 30)) ) { await $`git pull --rebase`.cwd(root); await $`git push`.cwd(root); } } export async function cloneNotebook(url: string, notebook: Notebook) { await $`git clone ${url} ${notebook.root}`; } export async function creationDateFor( root: string, relpath: string, ): Promise { const fullPath = join(root, relpath); if (!existsSync(join(root, ".git"), { isDirectory: true })) { const stat = Deno.statSync(fullPath); return (stat.birthtime ?? stat.mtime ?? new Date()).toTemporalInstant(); } const out = (await $`git --no-pager log --reverse --date=iso-local --format="%aI" -- ${relpath}` .cwd(root).env({ "TZ": "UTC" }).text()).split("\n")[0]; if (!out) { const stat = Deno.statSync(fullPath); return (stat.birthtime ?? stat.mtime ?? new Date()).toTemporalInstant(); } return Temporal.Instant.from(out); } export async function modificationDateFor( root: string, relpath: string, ): Promise { const fullPath = join(root, relpath); if (!existsSync(join(root, ".git"), { isDirectory: true })) { const stat = Deno.statSync(fullPath); return (stat.mtime ?? new Date()).toTemporalInstant(); } const out = (await $`git --no-pager log --date=iso-local --format="%aI" -- ${relpath}` .cwd(root).env({ "TZ": "UTC" }).text()).split("\n")[0]; if (!out) { const stat = Deno.statSync(fullPath); return (stat.mtime ?? new Date()).toTemporalInstant(); } return Temporal.Instant.from(out); }