203 lines
8.0 KiB
Rust
203 lines
8.0 KiB
Rust
use std::path::Path;
|
|
|
|
use super::output::*;
|
|
use super::repo;
|
|
|
|
pub const GIT_MAIN_WORKTREE_DIRECTORY: &str = ".git-main-working-tree";
|
|
|
|
// The logic about the base branch and the tracking branch is as follows:
|
|
//
|
|
// * If a branch with the same name does not exist and no track is given, use the default
|
|
// branch
|
|
//
|
|
// * If a branch with the same name exists and no track is given, use that
|
|
//
|
|
// * If a branch with the same name does not exist and track is given, use the
|
|
// local branch that tracks that branch
|
|
//
|
|
// * If a branch with the same name exists and track is given, use the locally
|
|
// existing branch. If the locally existing branch is not the local branch to
|
|
// the remote tracking branch, issue a warning
|
|
pub fn add_worktree(
|
|
directory: &Path,
|
|
name: &str,
|
|
subdirectory: Option<&Path>,
|
|
track: Option<(&str, &str)>,
|
|
no_track: bool,
|
|
) -> Result<(), String> {
|
|
let repo = repo::RepoHandle::open(directory, true).map_err(|error| match error.kind {
|
|
repo::RepoErrorKind::NotFound => {
|
|
String::from("Current directory does not contain a worktree setup")
|
|
}
|
|
_ => format!("Error opening repo: {}", error),
|
|
})?;
|
|
|
|
let config = repo::read_worktree_root_config(directory)?;
|
|
|
|
if repo.find_worktree(name).is_ok() {
|
|
return Err(format!("Worktree {} already exists", &name));
|
|
}
|
|
|
|
let path = match subdirectory {
|
|
Some(dir) => directory.join(dir).join(name),
|
|
None => directory.join(Path::new(name)),
|
|
};
|
|
|
|
let mut remote_branch_exists = false;
|
|
|
|
let mut target_branch = match repo.find_local_branch(name) {
|
|
Ok(branchref) => {
|
|
if !no_track {
|
|
if let Some((remote_name, remote_branch_name)) = track {
|
|
let remote_branch = repo.find_remote_branch(remote_name, remote_branch_name);
|
|
if let Ok(remote_branch) = remote_branch {
|
|
remote_branch_exists = true;
|
|
if let Ok(local_upstream_branch) = branchref.upstream() {
|
|
if remote_branch.name()? != local_upstream_branch.name()? {
|
|
print_warning(&format!(
|
|
"You specified a tracking branch ({}/{}) for an existing branch ({}), but \
|
|
it differs from the current upstream ({}). Will keep current upstream"
|
|
, remote_name, remote_branch_name, branchref.name()?, local_upstream_branch.name()?))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
branchref
|
|
}
|
|
Err(_) => {
|
|
let default_checkout = || repo.default_branch()?.to_commit();
|
|
|
|
let checkout_commit;
|
|
|
|
if no_track {
|
|
checkout_commit = default_checkout()?;
|
|
} else {
|
|
match track {
|
|
Some((remote_name, remote_branch_name)) => {
|
|
let remote_branch =
|
|
repo.find_remote_branch(remote_name, remote_branch_name);
|
|
match remote_branch {
|
|
Ok(branch) => {
|
|
remote_branch_exists = true;
|
|
checkout_commit = branch.to_commit()?;
|
|
}
|
|
Err(_) => {
|
|
remote_branch_exists = false;
|
|
checkout_commit = default_checkout()?;
|
|
}
|
|
}
|
|
}
|
|
None => match &config {
|
|
None => checkout_commit = default_checkout()?,
|
|
Some(config) => match &config.track {
|
|
None => checkout_commit = default_checkout()?,
|
|
Some(track_config) => {
|
|
if track_config.default {
|
|
let remote_branch =
|
|
repo.find_remote_branch(&track_config.default_remote, name);
|
|
match remote_branch {
|
|
Ok(branch) => {
|
|
remote_branch_exists = true;
|
|
checkout_commit = branch.to_commit()?;
|
|
}
|
|
Err(_) => {
|
|
checkout_commit = default_checkout()?;
|
|
}
|
|
}
|
|
} else {
|
|
checkout_commit = default_checkout()?;
|
|
}
|
|
}
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
repo.create_branch(name, &checkout_commit)?
|
|
}
|
|
};
|
|
|
|
fn push(
|
|
remote: &mut repo::RemoteHandle,
|
|
branch_name: &str,
|
|
remote_branch_name: &str,
|
|
repo: &repo::RepoHandle,
|
|
) -> Result<(), String> {
|
|
if !remote.is_pushable()? {
|
|
return Err(format!(
|
|
"Cannot push to non-pushable remote {}",
|
|
remote.url()
|
|
));
|
|
}
|
|
remote.push(branch_name, remote_branch_name, repo)
|
|
}
|
|
|
|
if !no_track {
|
|
if let Some((remote_name, remote_branch_name)) = track {
|
|
if remote_branch_exists {
|
|
target_branch.set_upstream(remote_name, remote_branch_name)?;
|
|
} else {
|
|
let mut remote = repo
|
|
.find_remote(remote_name)
|
|
.map_err(|error| format!("Error getting remote {}: {}", remote_name, error))?
|
|
.ok_or_else(|| format!("Remote {} not found", remote_name))?;
|
|
|
|
push(
|
|
&mut remote,
|
|
&target_branch.name()?,
|
|
remote_branch_name,
|
|
&repo,
|
|
)?;
|
|
|
|
target_branch.set_upstream(remote_name, remote_branch_name)?;
|
|
}
|
|
} else if let Some(config) = config {
|
|
if let Some(track_config) = config.track {
|
|
if track_config.default {
|
|
let remote_name = track_config.default_remote;
|
|
if remote_branch_exists {
|
|
target_branch.set_upstream(&remote_name, name)?;
|
|
} else {
|
|
let remote_branch_name = match track_config.default_remote_prefix {
|
|
Some(prefix) => {
|
|
format!("{}{}{}", &prefix, super::BRANCH_NAMESPACE_SEPARATOR, &name)
|
|
}
|
|
None => name.to_string(),
|
|
};
|
|
|
|
let mut remote = repo
|
|
.find_remote(&remote_name)
|
|
.map_err(|error| {
|
|
format!("Error getting remote {}: {}", remote_name, error)
|
|
})?
|
|
.ok_or_else(|| format!("Remote {} not found", remote_name))?;
|
|
|
|
if !remote.is_pushable()? {
|
|
return Err(format!(
|
|
"Cannot push to non-pushable remote {}",
|
|
remote.url()
|
|
));
|
|
}
|
|
push(
|
|
&mut remote,
|
|
&target_branch.name()?,
|
|
&remote_branch_name,
|
|
&repo,
|
|
)?;
|
|
|
|
target_branch.set_upstream(&remote_name, &remote_branch_name)?;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(subdirectory) = subdirectory {
|
|
std::fs::create_dir_all(subdirectory).map_err(|error| error.to_string())?;
|
|
}
|
|
repo.new_worktree(name, &path, &target_branch)?;
|
|
|
|
Ok(())
|
|
}
|