Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/package/src/util/git.ts
6450 views
1
/*
2
* git.ts
3
*
4
* Copyright (C) 2020-2024 Posit Software, PBC
5
*
6
*/
7
import { join } from "../../../src/deno_ral/path.ts";
8
import { info } from "../../../src/deno_ral/log.ts";
9
import * as colors from "fmt/colors";
10
11
export interface Repo {
12
dir: string;
13
checkout(commit: string): Promise<void>;
14
}
15
16
// Provides a utility to clone a repo and call a provided function in the context of that
17
// repo. The function receives a repo interface which allows some basic repo operations as
18
// well as provides a full path to the repo.
19
export async function withRepo(
20
workingDir: string,
21
repoUrl: string,
22
fn: (repo: Repo) => Promise<void>,
23
) {
24
// clone the repo
25
const repoDir = await clone(workingDir, repoUrl);
26
const repoPath = join(workingDir, repoDir);
27
28
const repo = {
29
dir: repoPath,
30
checkout: (commit: string) => {
31
return checkout(repoPath, commit);
32
},
33
};
34
await fn(repo);
35
try {
36
Deno.removeSync(repoPath, { recursive: true });
37
} catch (_err) {
38
info(`Folder not deleted by deno: ${repoPath}`);
39
}
40
}
41
42
async function checkout(dir: string, commit: string) {
43
info(`Checking out ${commit}`);
44
const gitCmd: string[] = [];
45
gitCmd.push("git");
46
gitCmd.push("checkout");
47
gitCmd.push(commit);
48
49
const p = Deno.run({
50
cmd: gitCmd,
51
cwd: dir,
52
});
53
54
const status = await p.status();
55
if (status.code !== 0) {
56
throw Error("Failed to checkout");
57
}
58
}
59
60
async function clone(workingDir: string, url: string) {
61
info(`Cloning ${url}`);
62
const gitCmd: string[] = [];
63
gitCmd.push("git");
64
gitCmd.push("clone");
65
gitCmd.push(url);
66
67
const p = Deno.run({
68
cmd: gitCmd,
69
cwd: workingDir,
70
stderr: "piped",
71
});
72
73
const status = await p.status();
74
if (status.code !== 0) {
75
throw Error("Failed to clone repo");
76
}
77
78
const output = await p.stderrOutput();
79
if (output) {
80
// Try to read the git clone output
81
const outputTxt = new TextDecoder().decode(output);
82
83
// Forward the output
84
info(outputTxt);
85
86
// Find the directory that we cloned into and return that
87
const match = outputTxt.match(/^Cloning into '(.*)'\.\.\.$/m);
88
if (match) {
89
return match[1];
90
} else {
91
throw Error("Failed to determine cloned repo directory");
92
}
93
} else {
94
throw Error("No output from git clone");
95
}
96
}
97
98
99
export async function applyGitPatches(patches: string[]) {
100
if (!patches) return undefined;
101
info(`Applying Git patches...`);
102
for (const patch of patches) {
103
info(` - patch ${colors.blue(patch)}`);
104
const gitCmd: string[] = [];
105
gitCmd.push("git");
106
gitCmd.push("apply");
107
gitCmd.push(patch);
108
// this helps with newline handling in patch
109
gitCmd.push("--ignore-space-change");
110
// this helps debug when patch goes wrong
111
// https://git-scm.com/docs/git-apply#Documentation/git-apply.txt---reject
112
gitCmd.push("--reject");
113
const p = Deno.run({
114
cmd: gitCmd,
115
stderr: "piped",
116
});
117
const status = await p.status();
118
if (status.code !== 0) {
119
throw Error(`Failed to apply patch: '${patch}'`);
120
}
121
}
122
}
123