Skip to content

Commit fcb7cd8

Browse files
authored
Merge pull request #20 from Ghvstcode/worktree-engine
Worktree engine: parallel tasks, live output, PR import, all comments
2 parents e35822b + 95c9657 commit fcb7cd8

35 files changed

Lines changed: 2750 additions & 412 deletions

src-tauri/src/engine/budget.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,13 +149,24 @@ pub fn get_weekly_usage() -> Result<(i64, String), String> {
149149
/// usage earlier in the week from falsely exhausting the budget for remaining days —
150150
/// the actual subscription resets on rolling windows, not as a rigid weekly lump sum.
151151
pub fn calculate_budget_status(config: &BudgetConfig) -> BudgetStatus {
152+
calculate_budget_status_with_reservation(config, 0)
153+
}
154+
155+
/// Calculate budget status accounting for tokens already reserved by
156+
/// in-flight tasks. Used to prevent over-commit when multiple tasks
157+
/// start concurrently.
158+
pub fn calculate_budget_status_with_reservation(
159+
config: &BudgetConfig,
160+
tokens_reserved: i64,
161+
) -> BudgetStatus {
152162
let (tokens_today, _) = get_today_usage().unwrap_or((0, "unavailable".to_string()));
153163
let (tokens_week, source) = get_weekly_usage().unwrap_or((0, "unavailable".to_string()));
154164

155165
let daily_budget = config.weekly_token_budget / 7;
156166
let max_for_sustn = daily_budget * (config.max_usage_percent as i64) / 100;
157167
let reserve = daily_budget * (config.reserve_percent as i64) / 100;
158-
let available = (max_for_sustn - tokens_today - reserve).max(0);
168+
let available =
169+
(max_for_sustn - tokens_today - reserve - tokens_reserved).max(0);
159170

160171
BudgetStatus {
161172
weekly_token_budget: config.weekly_token_budget,

src-tauri/src/engine/git.rs

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ pub struct GitResult {
88
pub error: Option<String>,
99
}
1010

11-
fn run_git(cwd: &str, args: &[&str]) -> GitResult {
11+
pub(crate) fn run_git(cwd: &str, args: &[&str]) -> GitResult {
1212
match Command::new("git").args(args).current_dir(cwd).output() {
1313
Ok(output) => {
1414
let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
@@ -257,6 +257,103 @@ pub fn detect_default_branch(cwd: &str) -> String {
257257
"main".to_string()
258258
}
259259

260+
/// Clone a repository. Runs in the parent directory of `destination`.
261+
/// Uses GIT_TERMINAL_PROMPT=0 to prevent interactive credential prompts
262+
/// from hanging the process. Skips LFS to avoid downloading large
263+
/// binary files that aren't needed for code review.
264+
pub fn clone_repo(url: &str, destination: &str) -> GitResult {
265+
let dest_path = std::path::Path::new(destination);
266+
267+
// Create parent directory if needed
268+
if let Some(parent) = dest_path.parent() {
269+
let _ = std::fs::create_dir_all(parent);
270+
}
271+
272+
match Command::new("git")
273+
.args(["clone", url, destination])
274+
.env("GIT_TERMINAL_PROMPT", "0")
275+
.env("GIT_LFS_SKIP_SMUDGE", "1")
276+
.output()
277+
{
278+
Ok(output) => {
279+
let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
280+
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
281+
if output.status.success() {
282+
GitResult {
283+
success: true,
284+
output: destination.to_string(),
285+
error: None,
286+
}
287+
} else {
288+
GitResult {
289+
success: false,
290+
output: stdout,
291+
error: Some(stderr),
292+
}
293+
}
294+
}
295+
Err(e) => GitResult {
296+
success: false,
297+
output: String::new(),
298+
error: Some(format!("Failed to execute git clone: {}", e)),
299+
},
300+
}
301+
}
302+
303+
/// Fetch a specific branch from origin and create a local branch.
304+
/// Tries the branch name first, then falls back to `pull/<number>/head`
305+
/// for fork-based PRs where the branch doesn't exist on the upstream remote.
306+
pub fn fetch_branch(cwd: &str, branch_name: &str) -> GitResult {
307+
fetch_branch_with_pr(cwd, branch_name, None)
308+
}
309+
310+
/// Fetch a branch, with an optional PR number for fork-based PR fallback.
311+
pub fn fetch_branch_with_pr(cwd: &str, branch_name: &str, pr_number: Option<u32>) -> GitResult {
312+
// Try fetching by branch name first (works for same-repo PRs)
313+
let refspec = format!(
314+
"refs/heads/{}:refs/remotes/origin/{}",
315+
branch_name, branch_name
316+
);
317+
let fetch = run_git(cwd, &["fetch", "origin", &refspec]);
318+
319+
if !fetch.success {
320+
if let Some(pr_num) = pr_number {
321+
// Fallback: fetch via GitHub's PR ref (works for fork-based PRs)
322+
println!(
323+
"[git] branch {} not found on origin, trying pull/{}/head",
324+
branch_name, pr_num
325+
);
326+
let pr_refspec = format!(
327+
"refs/pull/{}/head:refs/remotes/origin/{}",
328+
pr_num, branch_name
329+
);
330+
let pr_fetch = run_git(cwd, &["fetch", "origin", &pr_refspec]);
331+
if !pr_fetch.success {
332+
return pr_fetch;
333+
}
334+
} else {
335+
return fetch;
336+
}
337+
}
338+
339+
// Delete stale local branch if it exists (may point to wrong commit)
340+
let _ = run_git(cwd, &["branch", "-D", branch_name]);
341+
// Create local branch from the fetched ref
342+
run_git(
343+
cwd,
344+
&[
345+
"branch",
346+
branch_name,
347+
&format!("origin/{}", branch_name),
348+
],
349+
)
350+
}
351+
352+
/// Get the remote URL for origin.
353+
pub fn get_remote_url(cwd: &str) -> GitResult {
354+
run_git(cwd, &["remote", "get-url", "origin"])
355+
}
356+
260357
/// Generate a branch name for a task.
261358
pub fn task_branch_name(task_id: &str) -> String {
262359
// Use first 8 chars of UUID for readability

0 commit comments

Comments
 (0)