Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions crates/okena-git/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
uuid = { version = "1.10", features = ["v4"] }
log = "0.4"

[dev-dependencies]
tempfile = "3"
64 changes: 64 additions & 0 deletions crates/okena-git/src/repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -983,6 +983,70 @@ mod tests {
assert_paths_eq(&proj, &expected);
}

// ─── get_repo_root worktree / monorepo tests ───────────────────────

/// Helper: initialise a throwaway git repo with one commit so worktrees can
/// be created from it.
fn init_temp_repo() -> (tempfile::TempDir, PathBuf) {
let tmp = tempfile::tempdir().expect("create temp dir");
let repo = tmp.path().to_path_buf();
let r = |args: &[&str]| {
std::process::Command::new("git")
.args(args)
.current_dir(&repo)
.env("GIT_AUTHOR_NAME", "test")
.env("GIT_AUTHOR_EMAIL", "test@test")
.env("GIT_COMMITTER_NAME", "test")
.env("GIT_COMMITTER_EMAIL", "test@test")
.output()
.expect("git command failed")
};
r(&["init", "-b", "main"]);
std::fs::write(repo.join("file.txt"), "x").unwrap();
r(&["add", "."]);
r(&["-c", "commit.gpgsign=false", "commit", "-m", "init"]);
(tmp, repo)
}

#[test]
fn get_repo_root_returns_toplevel_for_subdirectory() {
let (_tmp, repo) = init_temp_repo();
let sub = repo.join("packages").join("app");
std::fs::create_dir_all(&sub).unwrap();

let root = get_repo_root(&sub).expect("should resolve repo root");
assert_eq!(root, repo.canonicalize().unwrap());
}

#[test]
fn get_repo_root_resolves_worktree_root_not_subdir() {
let (_tmp, repo) = init_temp_repo();
// Create a worktree on a new branch
let wt_path = repo.parent().unwrap().join("my-worktree");
let status = std::process::Command::new("git")
.args([
"-C",
repo.to_str().unwrap(),
"worktree",
"add",
wt_path.to_str().unwrap(),
"-b",
"wt-branch",
])
.output()
.expect("git worktree add");
assert!(status.status.success(), "worktree add failed");

// Create a nested subdirectory inside the worktree (monorepo subproject)
let nested = wt_path.join("packages").join("app");
std::fs::create_dir_all(&nested).unwrap();

// get_repo_root from the nested subdir should return the worktree root,
// NOT the main repo — this is the path `git worktree remove` needs.
let root = get_repo_root(&nested).expect("should resolve worktree root");
assert_eq!(root, wt_path.canonicalize().unwrap());
}

// ─── CI check parsing tests ────────────────────────────────────────

#[test]
Expand Down
8 changes: 5 additions & 3 deletions crates/okena-workspace/src/actions/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -659,9 +659,11 @@ impl Workspace {
let project_hooks = project.hooks.clone();
let project_name = project.name.clone();
let project_path = project.path.clone();
// Use the stored worktree path (repo-level checkout), falling back to project path
// for backwards compatibility with worktrees created before monorepo support
let worktree_path = std::path::PathBuf::from(&project_path);
// For monorepos the project path is a subdirectory inside the worktree checkout.
// Resolve the actual worktree root via git so `git worktree remove` gets the right path.
let project_pathbuf = std::path::PathBuf::from(&project_path);
let worktree_path = okena_git::get_repo_root(&project_pathbuf)
.unwrap_or(project_pathbuf);
Comment thread
JanTvrdik marked this conversation as resolved.

// Resolve branch BEFORE removal (git worktree remove deletes the checkout)
let branch = okena_git::get_current_branch(&worktree_path).unwrap_or_default();
Expand Down
Loading