Skip to main content

worktree_io/git/
mod.rs

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