File size: 4,685 Bytes
d90101d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
use std::path::PathBuf;
use std::process::Command;

use anyhow::{Context, Result, bail};
use bstr::ByteSlice;
use forge_domain::TitleFormat;

use crate::title_display::TitleDisplayExt;

pub struct Sandbox<'a> {
    dir: &'a str,
}

impl<'a> Sandbox<'a> {
    pub fn new(dir: &'a str) -> Self {
        Self { dir }
    }

    /// Handles worktree creation and returns the path to the worktree directory
    pub fn create(&self) -> Result<PathBuf> {
        let worktree_name = self.dir;
        // First check if we're in a git repository
        let git_check = Command::new("git")
            .args(["rev-parse", "--is-inside-work-tree"])
            .output()
            .context("Failed to check if current directory is a git repository")?;

        if !git_check.status.success() {
            bail!(
                "Current directory is not inside a git repository. Worktree creation requires a git repository."
            );
        }

        // Get the git root directory
        let git_root_output = Command::new("git")
            .args(["rev-parse", "--show-toplevel"])
            .output()
            .context("Failed to get git root directory")?;

        if !git_root_output.status.success() {
            bail!("Failed to determine git repository root");
        }

        let git_root = String::from_utf8(git_root_output.stdout)
            .context("Git root path contains invalid UTF-8")?
            .trim()
            .to_string();

        let git_root_path = PathBuf::from(&git_root);

        // Get the parent directory of the git root
        let parent_dir = git_root_path.parent().context(
            "Git repository is at filesystem root - cannot create worktree in parent directory",
        )?;

        // Create the worktree path in the parent directory
        let worktree_path = parent_dir.join(worktree_name);

        // Check if worktree already exists
        if worktree_path.exists() {
            // Check if it's already a git worktree by checking if it has a .git file
            // (worktree marker)
            let git_file = worktree_path.join(".git");
            if git_file.exists() {
                let worktree_check = Command::new("git")
                    .args(["rev-parse", "--is-inside-work-tree"])
                    .current_dir(&worktree_path)
                    .output()
                    .context("Failed to check if target directory is a git worktree")?;

                if worktree_check.status.success() {
                    println!(
                        "{}",
                        TitleFormat::info("Worktree [Reused]")
                            .sub_title(worktree_path.display().to_string())
                            .display()
                    );
                    return worktree_path
                        .canonicalize()
                        .context("Failed to canonicalize worktree path");
                }
            }

            bail!(
                "Directory '{}' already exists but is not a git worktree. Please remove it or choose a different name.",
                worktree_path.display()
            );
        }

        // Check if branch already exists
        let branch_check = Command::new("git")
            .args([
                "rev-parse",
                "--verify",
                &format!("refs/heads/{worktree_name}"),
            ])
            .current_dir(&git_root_path)
            .output()
            .context("Failed to check if branch exists")?;

        let branch_exists = branch_check.status.success();

        // Create the worktree
        let mut worktree_cmd = Command::new("git");
        worktree_cmd.args(["worktree", "add"]);

        if !branch_exists {
            // Create new branch from current HEAD
            worktree_cmd.args(["-b", worktree_name]);
        }

        worktree_cmd.args([worktree_path.to_str().unwrap()]);

        if branch_exists {
            worktree_cmd.arg(worktree_name);
        }

        let worktree_output = worktree_cmd
            .current_dir(&git_root_path)
            .output()
            .context("Failed to create git worktree")?;

        if !worktree_output.status.success() {
            let stderr = worktree_output.stderr.to_str_lossy();
            bail!("Failed to create git worktree: {stderr}");
        }

        println!(
            "{}",
            TitleFormat::info("Worktree [Created]")
                .sub_title(worktree_path.display().to_string())
                .display()
        );

        // Return the canonicalized path
        worktree_path
            .canonicalize()
            .context("Failed to canonicalize worktree path")
    }
}