forked from quarto-dev/quarto-cli
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcmd.ts
More file actions
234 lines (208 loc) · 6.42 KB
/
cmd.ts
File metadata and controls
234 lines (208 loc) · 6.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
/*
* cmd.ts
*
* Copyright (C) 2025 Posit Software, PBC
*/
import { Command } from "cliffy/command/mod.ts";
import { gitCmdOutput, gitCmds } from "../../../core/git.ts";
import { debug, error, info } from "../../../deno_ral/log.ts";
import { logLevel } from "../../../core/log.ts";
interface SubtreeConfig {
name: string;
prefix: string;
remoteUrl: string;
remoteBranch: string;
}
// Subtree configurations - update these with actual repositories
const SUBTREES: SubtreeConfig[] = [
{
name: "julia-engine",
prefix: "src/resources/extension-subtrees/julia-engine",
remoteUrl: "https://github.com/PumasAI/quarto-julia-engine.git",
remoteBranch: "main",
},
{
name: "orange-book",
prefix: "src/resources/extension-subtrees/orange-book",
remoteUrl: "https://github.com/quarto-ext/orange-book.git",
remoteBranch: "main",
},
];
async function findLastSplit(
quartoRoot: string,
prefix: string,
): Promise<string | null> {
try {
debug(
`Searching for last split with grep pattern: git-subtree-dir: ${prefix}$`,
);
const log = await gitCmdOutput(quartoRoot, [
"log",
`--grep=git-subtree-dir: ${prefix}$`,
"-1",
"--pretty=%b",
]);
debug(`Git log output: ${log}`);
const splitLine = log.split("\n").find((line) =>
line.startsWith("git-subtree-split:")
);
if (!splitLine) {
debug("No split line found in log output");
return null;
}
const splitCommit = splitLine.split(/\s+/)[1];
debug(`Found last split commit: ${splitCommit}`);
return splitCommit;
} catch (e) {
debug(`Error finding last split: ${e}`);
return null;
}
}
async function pullSubtree(
quartoRoot: string,
config: SubtreeConfig,
): Promise<void> {
info(`\n=== Pulling subtree: ${config.name} ===`);
info(`Prefix: ${config.prefix}`);
info(`Remote: ${config.remoteUrl}`);
info(`Branch: ${config.remoteBranch}`);
// Fetch from remote
info("Fetching...");
await gitCmds(quartoRoot, [
["fetch", config.remoteUrl, config.remoteBranch],
]);
// Check what FETCH_HEAD points to
const fetchHead = await gitCmdOutput(quartoRoot, ["rev-parse", "FETCH_HEAD"]);
debug(`FETCH_HEAD resolves to: ${fetchHead.trim()}`);
// Check if prefix directory exists
const prefixPath = `${quartoRoot}/${config.prefix}`;
let prefixExists = false;
try {
const stat = await Deno.stat(prefixPath);
prefixExists = stat.isDirectory;
debug(`Prefix directory exists: ${prefixExists} (${prefixPath})`);
} catch {
debug(`Prefix directory does not exist: ${prefixPath}`);
}
// Find last split point
let lastSplit = await findLastSplit(quartoRoot, config.prefix);
if (!lastSplit) {
info("No previous subtree found - using 'git subtree add'");
await gitCmds(quartoRoot, [
[
"subtree",
"add",
"--squash",
`--prefix=${config.prefix}`,
config.remoteUrl,
config.remoteBranch,
],
]);
lastSplit = await gitCmdOutput(quartoRoot, ["rev-parse", "FETCH_HEAD^"]);
debug(`After subtree add, lastSplit set to: ${lastSplit.trim()}`);
}
// Check for new commits
const commitRange = `${lastSplit}..FETCH_HEAD`;
debug(`Checking commit range: ${commitRange}`);
const hasNewCommits = await gitCmdOutput(quartoRoot, [
"log",
"--oneline",
commitRange,
"-1",
]);
if (!hasNewCommits.trim()) {
info("No new commits to merge");
debug(`Commit range ${commitRange} has no commits`);
if (!prefixExists) {
info("WARNING: Prefix directory doesn't exist but no new commits found!");
debug("This may indicate lastSplit was found on a different branch");
}
return;
}
debug(`Found new commits in range ${commitRange}`);
// Do the subtree pull
info("Running git subtree pull --squash...");
await gitCmds(quartoRoot, [
[
"subtree",
"pull",
"--squash",
`--prefix=${config.prefix}`,
config.remoteUrl,
config.remoteBranch,
],
]);
info("✓ Done!");
}
export const pullGitSubtreeCommand = new Command()
.name("pull-git-subtree")
.hidden()
.arguments("[name:string]")
.description(
"Pull configured git subtrees.\n\n" +
"This command pulls from configured subtree repositories " +
"using --squash, which creates two commits: a squash commit " +
"containing the subtree changes and a merge commit that " +
"integrates it into your branch.\n\n" +
"Arguments:\n" +
" [name] Name of subtree to pull (use 'all' or omit to pull all)",
)
.action(async (_options: unknown, nameArg?: string) => {
// Get quarto root directory
const quartoRoot = Deno.env.get("QUARTO_ROOT");
if (!quartoRoot) {
error(
"QUARTO_ROOT environment variable not set. This command requires a development version of Quarto.",
);
Deno.exit(1);
}
// Show current branch for debugging (only if debug logging enabled)
if (logLevel() === "DEBUG") {
try {
const currentBranch = await gitCmdOutput(quartoRoot, [
"branch",
"--show-current",
]);
debug(`Current branch: ${currentBranch.trim()}`);
} catch (e) {
debug(`Unable to determine current branch: ${e}`);
}
}
// Determine which subtrees to pull
let subtreesToPull: SubtreeConfig[];
if (!nameArg || nameArg === "all") {
// Pull all subtrees
subtreesToPull = SUBTREES;
info(`Quarto root: ${quartoRoot}`);
info(`Processing all ${SUBTREES.length} subtree(s)...`);
} else {
// Pull specific subtree by name
const config = SUBTREES.find((s) => s.name === nameArg);
if (!config) {
error(`Unknown subtree name: ${nameArg}`);
error(`Available subtrees: ${SUBTREES.map((s) => s.name).join(", ")}`);
Deno.exit(1);
}
subtreesToPull = [config];
info(`Quarto root: ${quartoRoot}`);
info(`Processing subtree: ${nameArg}`);
}
let successCount = 0;
let errorCount = 0;
for (const config of subtreesToPull) {
try {
await pullSubtree(quartoRoot, config);
successCount++;
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
error(`Failed to pull subtree ${config.name}: ${message}`);
errorCount++;
}
}
info(`\n=== Summary ===`);
info(`Success: ${successCount}`);
info(`Failed: ${errorCount}`);
if (errorCount > 0) {
Deno.exit(1);
}
});