Add functionality for persistent branches

This commit is contained in:
2021-12-23 18:33:14 +01:00
parent 70eac10eaa
commit 27586b5ff0
6 changed files with 297 additions and 30 deletions

View File

@@ -5,6 +5,8 @@ use git2::{Cred, RemoteCallbacks, Repository};
use crate::output::*;
const WORKTREE_CONFIG_FILE_NAME: &str = "grm.toml";
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum RemoteType {
@@ -16,6 +18,7 @@ pub enum RemoteType {
pub enum WorktreeRemoveFailureReason {
Changes(String),
Error(String),
NotMerged(String),
}
pub enum GitPushDefaultSetting {
@@ -39,6 +42,37 @@ impl RepoError {
}
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct WorktreeRootConfig {
pub persistent_branches: Option<Vec<String>>,
}
pub fn read_worktree_root_config(worktree_root: &Path) -> Result<Option<WorktreeRootConfig>, String> {
let path = worktree_root.join(WORKTREE_CONFIG_FILE_NAME);
let content = match std::fs::read_to_string(&path) {
Ok(s) => s,
Err(e) => {
match e.kind() {
std::io::ErrorKind::NotFound => return Ok(None),
_ => return Err(format!("Error reading configuration file \"{}\": {}", path.display(), e)),
}
}
};
let config: WorktreeRootConfig = match toml::from_str(&content) {
Ok(c) => c,
Err(e) => {
return Err(format!(
"Error parsing configuration file \"{}\": {}",
path.display(), e
))
}
};
Ok(Some(config))
}
impl std::error::Error for RepoError {}
impl std::fmt::Display for RepoError {
@@ -237,10 +271,9 @@ impl Repo {
// name exists
let branch = self
.find_local_branch(
&head
head
.shorthand()
.expect("Branch name is not valid utf-8")
.to_string(),
.expect("Branch name is not valid utf-8"),
)
.unwrap();
Ok(branch)
@@ -642,6 +675,7 @@ impl Repo {
name: &str,
worktree_dir: &Path,
force: bool,
worktree_config: &Option<WorktreeRootConfig>,
) -> Result<(), WorktreeRemoveFailureReason> {
if !worktree_dir.exists() {
return Err(WorktreeRemoveFailureReason::Error(format!(
@@ -684,25 +718,55 @@ impl Repo {
)));
}
match branch.upstream() {
Ok(remote_branch) => {
let (ahead, behind) = worktree_repo
.graph_ahead_behind(&branch, &remote_branch)
.unwrap();
let mut is_merged_into_persistent_branch = false;
let mut has_persistent_branches = false;
if let Some(config) = worktree_config {
if let Some(branches) = &config.persistent_branches {
has_persistent_branches = true;
for persistent_branch in branches {
let persistent_branch = worktree_repo
.find_local_branch(persistent_branch)
.map_err(WorktreeRemoveFailureReason::Error)?;
if (ahead, behind) != (0, 0) {
let (ahead, _behind) = worktree_repo
.graph_ahead_behind(&branch, &persistent_branch)
.unwrap();
if ahead == 0 {
is_merged_into_persistent_branch = true;
}
}
}
}
if has_persistent_branches && !is_merged_into_persistent_branch {
return Err(WorktreeRemoveFailureReason::NotMerged(format!(
"Branch {} is not merged into any persistent branches",
name
)));
}
if !has_persistent_branches {
match branch.upstream() {
Ok(remote_branch) => {
let (ahead, behind) = worktree_repo
.graph_ahead_behind(&branch, &remote_branch)
.unwrap();
if (ahead, behind) != (0, 0) {
return Err(WorktreeRemoveFailureReason::Changes(format!(
"Branch {} is not in line with remote branch",
name
)));
}
}
Err(_) => {
return Err(WorktreeRemoveFailureReason::Changes(format!(
"Branch {} is not in line with remote branch",
"No remote tracking branch for branch {} found",
name
)));
}
}
Err(_) => {
return Err(WorktreeRemoveFailureReason::Changes(format!(
"No remote tracking branch for branch {} found",
name
)));
}
}
}
@@ -737,13 +801,22 @@ impl Repo {
.name()
.map_err(|error| format!("Failed getting default branch name: {}", error))?;
let config = read_worktree_root_config(directory)?;
for worktree in worktrees
.iter()
.filter(|worktree| *worktree != &default_branch_name)
.filter(|worktree| match &config {
None => true,
Some(config) => match &config.persistent_branches {
None => true,
Some(branches) => !branches.contains(worktree),
},
})
{
let repo_dir = &directory.join(&worktree);
if repo_dir.exists() {
match self.remove_worktree(worktree, repo_dir, false) {
match self.remove_worktree(worktree, repo_dir, false, &config) {
Ok(_) => print_success(&format!("Worktree {} deleted", &worktree)),
Err(error) => match error {
WorktreeRemoveFailureReason::Changes(changes) => {
@@ -753,6 +826,10 @@ impl Repo {
));
continue;
}
WorktreeRemoveFailureReason::NotMerged(message) => {
warnings.push(message);
continue;
}
WorktreeRemoveFailureReason::Error(error) => {
return Err(error);
}
@@ -773,14 +850,13 @@ impl Repo {
let mut unmanaged_worktrees = Vec::new();
for entry in std::fs::read_dir(&directory).map_err(|error| error.to_string())? {
let dirname = crate::path_as_string(
&entry
entry
.map_err(|error| error.to_string())?
.path()
.strip_prefix(&directory)
// that unwrap() is safe as each entry is
// guaranteed to be a subentry of &directory
.unwrap()
.to_path_buf(),
.unwrap(),
);
let default_branch = self
@@ -794,6 +870,9 @@ impl Repo {
if dirname == crate::GIT_MAIN_WORKTREE_DIRECTORY {
continue;
}
if dirname == WORKTREE_CONFIG_FILE_NAME {
continue;
}
if dirname == default_branch_name {
continue;
}