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
556 changes: 556 additions & 0 deletions apps/staged/src-tauri/src/branches.rs

Large diffs are not rendered by default.

11 changes: 6 additions & 5 deletions apps/staged/src-tauri/src/git/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,10 @@ pub use worktree::{
create_worktree_for_existing_branch_at_path, create_worktree_from_pr,
create_worktree_from_pr_at_path, discard_worktree_changes, fetch_pr_head_sha,
get_commits_since_base, get_full_commit_log, get_head_sha, get_parent_commit,
has_unpushed_commits, list_worktree_change_paths, list_worktrees, parse_branch_commit_line,
parse_worktree_status_paths, project_worktree_path_for, project_worktree_root_for,
remote_branch_exists, remove_worktree, reset_to_commit, set_upstream_to_origin, switch_branch,
update_branch_from_pr, worktree_path_for, BranchCommitFields, CommitInfo, UpdateFromPrResult,
WorktreeChangePaths, BRANCH_COMMIT_LOG_FORMAT,
has_unpushed_commits, list_worktree_change_paths, list_worktrees, move_worktree,
parse_branch_commit_line, parse_worktree_status_paths, project_worktree_path_for,
project_worktree_root_for, remote_branch_exists, remove_worktree, reset_to_commit,
set_upstream_to_origin, switch_branch, update_branch_from_pr, worktree_path_for,
BranchCommitFields, CommitInfo, UpdateFromPrResult, WorktreeChangePaths,
BRANCH_COMMIT_LOG_FORMAT,
};
55 changes: 55 additions & 0 deletions apps/staged/src-tauri/src/git/worktree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,28 @@ pub fn create_worktree_for_existing_branch_at_path(
Ok(worktree_path.to_path_buf())
}

/// Move an existing worktree to a new path.
///
/// Goes through `git worktree move` rather than `fs::rename` because a
/// worktree is two pointers, not one directory: the `.git` gitfile inside the
/// worktree names the repo's admin directory, and that admin directory's
/// `gitdir` file names the worktree. A rename updates neither, leaving both
/// dangling; `git worktree move` rewrites both.
pub fn move_worktree(repo: &Path, from: &Path, to: &Path) -> Result<(), GitError> {
ensure_worktree_parent_exists(to)?;
ensure_worktree_absent(to)?;

let from_str = from
.to_str()
.ok_or_else(|| GitError::InvalidPath(from.display().to_string()))?;
let to_str = to
.to_str()
.ok_or_else(|| GitError::InvalidPath(to.display().to_string()))?;

cli::run(repo, &["worktree", "move", from_str, to_str])?;
Ok(())
}

/// Remove a worktree and its associated branch.
///
/// Removes the worktree directory, git worktree reference, and the local git branch.
Expand Down Expand Up @@ -961,6 +983,39 @@ mod tests {
assert!(paths.reset_required);
}

/// The reason this goes through `git worktree move`: after the move the
/// worktree still has to be a working tree at its new path, which requires
/// both the gitfile and the repo's `gitdir` pointer to have been rewritten.
#[test]
fn move_worktree_leaves_a_working_tree_at_the_new_path() {
let repo = crate::test_utils::TempGitRepo::new();
repo.write_file("tracked.txt", "base\n");
repo.commit("base");

repo.run_git(&["branch", "feature"]);

let name = repo.path().file_name().unwrap().to_str().unwrap();
let parent = repo.path().parent().unwrap();
let from = parent.join(format!("{name}-wt-from"));
let to = parent.join(format!("{name}-wt-to"));
create_worktree_for_existing_branch_at_path(repo.path(), "feature", &from).unwrap();

move_worktree(repo.path(), &from, &to).unwrap();

assert!(!from.exists());
assert!(to.join("tracked.txt").exists());
// A dangling gitfile or gitdir pointer fails both of these.
assert!(cli::run(&to, &["status", "--porcelain"]).is_ok());
assert_eq!(
cli::run(&to, &["rev-parse", "--abbrev-ref", "HEAD"])
.unwrap()
.trim(),
"feature"
);

let _ = std::fs::remove_dir_all(&to);
}

#[test]
fn discard_worktree_changes_resets_tracked_and_removes_new_files() {
let repo = crate::test_utils::TempGitRepo::new();
Expand Down
1 change: 1 addition & 0 deletions apps/staged/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2229,6 +2229,7 @@ pub fn run() {
branches::resume_workspace,
branches::delete_branch,
branches::rename_branch,
branches::move_branch,
branches::get_blox_env,
branches::get_workspace_info,
branches::poll_workspace_status,
Expand Down
Loading