Skip to content

Instantly share code, notes, and snippets.

@mizchi
Last active August 16, 2024 08:22
Show Gist options
  • Save mizchi/aaf5c8f306e7d1817133c85a154858af to your computer and use it in GitHub Desktop.
Save mizchi/aaf5c8f306e7d1817133c85a154858af to your computer and use it in GitHub Desktop.
/*
USAGE: deno run -A git-aware-cp.ts [srcs...] dest
EXAMPLE: deno run -A git-aware-cp.ts *.ts lib.ts examples /tmp/out
backupper (main)🔥 🦕
$ tree ./examples
./examples
├── ignored-dir
│ ├── a
│ ├── examples
│ │ ├── newdir
│ │ │ └── xxx
│ │ └── y
│ ├── find-git.ts
│ ├── git-copy.ts
│ ├── lib.ts
│ ├── main.ts
│ └── main_test.ts
├── nested
├── newdir
│ └── xxx
├── other-git
│ ├── hello
│ └── more-git
│ └── stuff
├── out.log
├── x
└── y
8 directories, 14 files
backupper (main)🔥 🦕
$ git ls-files examples
examples/.gitignore
examples/other-git
examples/y
backupper (main)🔥 🦕
$ deno run -A lib.ts *.ts lib.ts examples /tmp/out
[COPY] find-git.ts /tmp/out/find-git.ts
[COPY] git-copy.ts /tmp/out/git-copy.ts
[COPY] lib.ts /tmp/out/lib.ts
[COPY] main.ts /tmp/out/main.ts
[COPY] main_test.ts /tmp/out/main_test.ts
[COPY] examples/newdir/xxx /tmp/out/examples/newdir/xxx
[COPY] examples/.gitignore /tmp/out/examples/.gitignore
[COPY] examples/y /tmp/out/examples/y
[WARING] ignored by git roots [ "examples/other-git" ]
*/
import * as path from "jsr:@std/path@1.0.2";
import { $ } from "jsr:@david/dax@0.40.0";
const DEBUG = Deno.env.get("DEBUG") === "true";
$.setPrintCommand(DEBUG);
const debug = DEBUG ? console.log : () => { };
// list files in git
export async function listGitLsFiles(baseDir: string, src: string) {
const files = await $`git ls-files ${src}`.lines();
return files.map((f: string) => path.join(baseDir, f));
}
// check target is includable in git
export async function isPathGitIncluded(relpath: string) {
const r = await $`git check-ignore ${relpath}`.noThrow().stdout("piped");
return r.code === 0;
}
export async function isGitRoot(dir: string): Promise<boolean> {
try {
const _stat = await Deno.stat(dir); // throw if not exists
const gitDir = await Deno.stat(path.join(dir, ".git"));
return gitDir.isDirectory;
} catch {
return false;
}
}
export async function listCopyFiles(root: string, fpath: string, files: Set<string>, gitRoots: Set<string>): Promise<void> {
// if path is ignored by git, skip
if (await isPathGitIncluded(fpath)) {
debug(`[SKIP] ${fpath} is ignored by git`);
return;
}
const stat = await Deno.stat(fpath);
if (stat.isFile) {
files.add(fpath);
return;
}
// recursively list files
if (stat.isDirectory) {
for await (const entry of Deno.readDir(fpath)) {
const childPath = path.join(fpath, entry.name);
if (await isGitRoot(childPath)) {
gitRoots.add(childPath);
continue;
}
await listCopyFiles(root, childPath, files, gitRoots);
}
}
}
// console.log(`[INFO] Copying files to ${dest}`);
async function normalizePath(root: string, fpath: string) {
if (path.isAbsolute(fpath)) {
return fpath;
}
return path.join(root, fpath);
}
if (import.meta.main) {
const root = path.resolve(Deno.cwd());
const srcs = Deno.args.slice(0, -1);
const dest = Deno.args.at(-1)!;
const files = new Set<string>();
const gitRoots = new Set<string>();
debug(`[INFO] Listing files in ${srcs}`, root);
for (const src of srcs) {
await listCopyFiles(root, src, files, gitRoots);
}
// ensure dest is directory
try {
await Deno.mkdir(dest, { recursive: true });
} catch (err) {
console.error(`[ERROR] Failed to create directory ${dest}`);
console.error(err);
Deno.exit(1);
}
for (const src of files) {
const srcFullPath = await normalizePath(root, src);
const outFullPath = await normalizePath(root, path.join(dest, src));
const outDir = path.dirname(outFullPath);
try {
await Deno.mkdir(outDir, { recursive: true });
} catch (err) {
console.error(`[ERROR] Failed to create directory ${outDir}`);
console.error(err);
Deno.exit(1);
}
await Deno.copyFile(srcFullPath, outFullPath);
console.log(`[COPY] ${src} ${outFullPath}`);
}
// report ignored git roots
if (gitRoots.size > 0) {
console.log("[WARING] ignored by git roots", [...gitRoots]);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment