Skip to main content

worktree_io/git/
mod.rs

1mod branch;
2mod clone;
3mod local_branch;
4mod prune;
5mod remote;
6
7pub use branch::{branch_exists_remote, detect_default_branch};
8pub use clone::{bare_clone, git_fetch};
9pub use local_branch::{branch_exists_local, detect_local_default_branch};
10pub use prune::git_worktree_prune;
11pub use remote::get_remote_url;
12
13use anyhow::{bail, Context, Result};
14use std::path::Path;
15use std::process::Command;
16
17/// Build a `git` [`Command`] with worktree-related environment variables unset.
18///
19/// When the process runs inside a git worktree hook, `GIT_DIR`, `GIT_WORK_TREE`,
20/// and `GIT_INDEX_FILE` are set by git itself and would override the `-C <dir>`
21/// flag, causing child git commands to operate on the wrong repository.
22/// Clearing them here ensures `-C dir` is always honoured.
23pub(super) fn git_cmd() -> Command {
24    let mut cmd = Command::new("git");
25    cmd.env_remove("GIT_DIR")
26        .env_remove("GIT_WORK_TREE")
27        .env_remove("GIT_INDEX_FILE");
28    cmd
29}
30
31/// Create a worktree from a local (non-bare) repository without referencing a remote.
32///
33/// When `branch_exists` is false a new branch is created from HEAD.
34/// When `branch_exists` is true the existing local branch is checked out.
35///
36/// # Errors
37///
38/// Returns an error if the git command fails to spawn or exits non-zero.
39pub fn create_local_worktree(
40    repo: &Path,
41    dest: &Path,
42    branch: &str,
43    branch_exists: bool,
44) -> Result<()> {
45    let mut cmd = git_cmd();
46    cmd.args(["-C"]).arg(repo).arg("worktree").arg("add");
47
48    if branch_exists {
49        cmd.arg(dest).arg(branch);
50    } else {
51        // Create new branch from HEAD (no origin/ reference needed)
52        cmd.arg(dest).arg("-b").arg(branch);
53    }
54
55    let status = cmd.status().context("Failed to run `git worktree add`")?;
56
57    if !status.success() {
58        bail!("git worktree add failed for branch {branch}"); // LLVM_COV_EXCL_LINE
59    }
60    Ok(())
61}
62
63/// Create a worktree inside a bare clone.
64///
65/// When `branch_exists` is false a new branch is created from `origin/<base_branch>`.
66/// When `branch_exists` is true the existing branch is checked out.
67///
68/// # Errors
69///
70/// Returns an error if the git command fails to spawn or exits non-zero.
71pub fn create_worktree(
72    bare: &Path,
73    dest: &Path,
74    branch: &str,
75    base_branch: &str,
76    branch_exists: bool,
77) -> Result<()> {
78    let mut cmd = git_cmd();
79    cmd.args(["-C"]).arg(bare).arg("worktree").arg("add");
80
81    if branch_exists {
82        cmd.arg(dest).arg(branch);
83    } else {
84        cmd.arg(dest)
85            .arg("-b")
86            .arg(branch)
87            .arg(format!("origin/{base_branch}"));
88    }
89
90    let status = cmd.status().context("Failed to run `git worktree add`")?;
91
92    if !status.success() {
93        bail!("git worktree add failed for branch {branch}"); // LLVM_COV_EXCL_LINE
94    }
95    Ok(())
96}