mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-11-26 12:24:26 +03:00
Merge pull request #2226 from gitbutlerapp/add-is-default-to-branch
GB-781 Add is default to branch
This commit is contained in:
commit
d123832636
@ -373,6 +373,23 @@ impl TryFrom<Content> for String {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Content> for i64 {
|
||||
type Error = FromError;
|
||||
|
||||
fn try_from(content: Content) -> Result<Self, Self::Error> {
|
||||
Self::try_from(&content)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&Content> for i64 {
|
||||
type Error = FromError;
|
||||
|
||||
fn try_from(content: &Content) -> Result<Self, Self::Error> {
|
||||
let text: String = content.try_into()?;
|
||||
text.parse().map_err(FromError::ParseInt)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Content> for u64 {
|
||||
type Error = FromError;
|
||||
|
||||
|
@ -191,6 +191,7 @@ pub fn set_base_branch(
|
||||
)?,
|
||||
ownership,
|
||||
order: 0,
|
||||
selected_for_changes: None,
|
||||
};
|
||||
|
||||
let branch_writer =
|
||||
|
@ -40,6 +40,9 @@ pub struct Branch {
|
||||
pub ownership: Ownership,
|
||||
// order is the number by which UI should sort branches
|
||||
pub order: usize,
|
||||
// is Some(timestamp), the branch is considered a default destination for new changes.
|
||||
// if more than one branch is selected, the branch with the highest timestamp wins.
|
||||
pub selected_for_changes: Option<i64>,
|
||||
}
|
||||
|
||||
impl Branch {
|
||||
@ -56,6 +59,7 @@ pub struct BranchUpdateRequest {
|
||||
pub ownership: Option<Ownership>,
|
||||
pub order: Option<usize>,
|
||||
pub upstream: Option<String>, // just the branch name, so not refs/remotes/origin/branchA, just branchA
|
||||
pub selected_for_changes: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||
@ -63,6 +67,7 @@ pub struct BranchCreateRequest {
|
||||
pub name: Option<String>,
|
||||
pub ownership: Option<Ownership>,
|
||||
pub order: Option<usize>,
|
||||
pub selected_for_changes: Option<bool>,
|
||||
}
|
||||
|
||||
impl TryFrom<&crate::reader::Reader<'_>> for Branch {
|
||||
@ -82,6 +87,7 @@ impl TryFrom<&crate::reader::Reader<'_>> for Branch {
|
||||
"meta/created_timestamp_ms",
|
||||
"meta/updated_timestamp_ms",
|
||||
"meta/ownership",
|
||||
"meta/selected_for_changes",
|
||||
])?;
|
||||
|
||||
let id: String = results[0].clone()?.try_into()?;
|
||||
@ -162,6 +168,23 @@ impl TryFrom<&crate::reader::Reader<'_>> for Branch {
|
||||
)
|
||||
})?;
|
||||
|
||||
let selected_for_changes = match results[12].clone() {
|
||||
Ok(raw_ts) => {
|
||||
let ts = raw_ts.try_into().map_err(|e| {
|
||||
crate::reader::Error::Io(
|
||||
std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
format!("meta/selected_for_changes: {}", e),
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
})?;
|
||||
Ok(Some(ts))
|
||||
}
|
||||
Err(crate::reader::Error::NotFound) => Ok(None),
|
||||
Err(e) => Err(e),
|
||||
}?;
|
||||
|
||||
Ok(Self {
|
||||
id,
|
||||
name,
|
||||
@ -185,6 +208,7 @@ impl TryFrom<&crate::reader::Reader<'_>> for Branch {
|
||||
updated_timestamp_ms,
|
||||
ownership,
|
||||
order,
|
||||
selected_for_changes,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -79,6 +79,7 @@ mod tests {
|
||||
.parse()
|
||||
.unwrap()],
|
||||
},
|
||||
selected_for_changes: Some(1),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -105,6 +105,18 @@ impl<'writer> BranchWriter<'writer> {
|
||||
branch.ownership.to_string(),
|
||||
));
|
||||
|
||||
if let Some(selected_for_changes) = branch.selected_for_changes {
|
||||
batch.push(writer::BatchTask::Write(
|
||||
format!("branches/{}/meta/selected_for_changes", branch.id),
|
||||
selected_for_changes.to_string(),
|
||||
));
|
||||
} else {
|
||||
batch.push(writer::BatchTask::Remove(format!(
|
||||
"branches/{}/meta/selected_for_changes",
|
||||
branch.id
|
||||
)));
|
||||
}
|
||||
|
||||
self.writer.batch(&batch)?;
|
||||
|
||||
Ok(())
|
||||
@ -170,6 +182,7 @@ mod tests {
|
||||
}],
|
||||
},
|
||||
order: TEST_INDEX.load(Ordering::Relaxed),
|
||||
selected_for_changes: Some(1),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,6 +106,7 @@ mod tests {
|
||||
.unwrap(),
|
||||
ownership: branch::Ownership::default(),
|
||||
order: TEST_INDEX.load(Ordering::Relaxed),
|
||||
selected_for_changes: Some(1),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,6 +85,7 @@ mod tests {
|
||||
}],
|
||||
},
|
||||
order: TEST_INDEX.load(Ordering::Relaxed),
|
||||
selected_for_changes: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -146,6 +146,7 @@ mod tests {
|
||||
}],
|
||||
},
|
||||
order: TEST_INDEX.load(Ordering::Relaxed),
|
||||
selected_for_changes: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,6 +59,7 @@ pub struct VirtualBranch {
|
||||
pub base_current: bool, // is this vbranch based on the current base branch? if false, this needs to be manually merged with conflicts
|
||||
pub ownership: Ownership,
|
||||
pub updated_at: u128,
|
||||
pub selected_for_changes: bool,
|
||||
}
|
||||
|
||||
// this is the struct that maps to the view `Commit` type in Typescript
|
||||
@ -600,6 +601,7 @@ pub fn unapply_branch(
|
||||
// this means we can just reset to the default target tree.
|
||||
{
|
||||
target_branch.applied = false;
|
||||
target_branch.selected_for_changes = None;
|
||||
branch_writer.write(&mut target_branch)?;
|
||||
}
|
||||
|
||||
@ -642,6 +644,7 @@ pub fn unapply_branch(
|
||||
|
||||
target_branch.tree = write_tree(project_repository, &default_target, files)?;
|
||||
target_branch.applied = false;
|
||||
target_branch.selected_for_changes = None;
|
||||
branch_writer.write(&mut target_branch)?;
|
||||
}
|
||||
|
||||
@ -725,6 +728,11 @@ pub fn list_virtual_branches(
|
||||
})?;
|
||||
|
||||
let statuses = get_status_by_branch(gb_repository, project_repository)?;
|
||||
let max_selected_for_changes = statuses
|
||||
.iter()
|
||||
.filter_map(|(branch, _)| branch.selected_for_changes)
|
||||
.max()
|
||||
.unwrap_or(-1);
|
||||
for (branch, files) in &statuses {
|
||||
let file_diffs = files
|
||||
.iter()
|
||||
@ -866,6 +874,7 @@ pub fn list_virtual_branches(
|
||||
base_current,
|
||||
ownership: branch.ownership.clone(),
|
||||
updated_at: branch.updated_timestamp_ms,
|
||||
selected_for_changes: branch.selected_for_changes == Some(max_selected_for_changes),
|
||||
};
|
||||
branches.push(branch);
|
||||
}
|
||||
@ -1074,6 +1083,27 @@ pub fn create_virtual_branch(
|
||||
|
||||
let branch_writer = branch::Writer::new(gb_repository).context("failed to create writer")?;
|
||||
|
||||
let selected_for_changes = if let Some(selected_for_changes) = create.selected_for_changes {
|
||||
if selected_for_changes {
|
||||
for mut other_branch in Iterator::new(¤t_session_reader)
|
||||
.context("failed to create branch iterator")?
|
||||
.collect::<Result<Vec<branch::Branch>, reader::Error>>()
|
||||
.context("failed to read virtual branches")?
|
||||
{
|
||||
other_branch.selected_for_changes = None;
|
||||
branch_writer.write(&mut other_branch)?;
|
||||
}
|
||||
Some(chrono::Utc::now().timestamp_millis())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
(!all_virtual_branches
|
||||
.iter()
|
||||
.any(|b| b.selected_for_changes.is_some()))
|
||||
.then_some(chrono::Utc::now().timestamp_millis())
|
||||
};
|
||||
|
||||
// make space for the new branch
|
||||
for (i, branch) in all_virtual_branches.iter().enumerate() {
|
||||
let mut branch = branch.clone();
|
||||
@ -1115,6 +1145,7 @@ pub fn create_virtual_branch(
|
||||
updated_timestamp_ms: now,
|
||||
ownership: Ownership::default(),
|
||||
order,
|
||||
selected_for_changes,
|
||||
};
|
||||
|
||||
if let Some(ownership) = &create.ownership {
|
||||
@ -1380,6 +1411,24 @@ pub fn update_branch(
|
||||
branch.order = order;
|
||||
};
|
||||
|
||||
if let Some(selected_for_changes) = branch_update.selected_for_changes {
|
||||
branch.selected_for_changes = if selected_for_changes {
|
||||
for mut other_branch in Iterator::new(¤t_session_reader)
|
||||
.context("failed to create branch iterator")?
|
||||
.collect::<Result<Vec<branch::Branch>, reader::Error>>()
|
||||
.context("failed to read virtual branches")?
|
||||
.into_iter()
|
||||
.filter(|b| b.id != branch.id)
|
||||
{
|
||||
other_branch.selected_for_changes = None;
|
||||
branch_writer.write(&mut other_branch)?;
|
||||
}
|
||||
Some(chrono::Utc::now().timestamp_millis())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
};
|
||||
|
||||
branch_writer
|
||||
.write(&mut branch)
|
||||
.context("failed to write target branch")?;
|
||||
@ -1747,17 +1796,29 @@ fn get_applied_status(
|
||||
.for_each(|file_ownership| branch.ownership.put(file_ownership));
|
||||
}
|
||||
|
||||
let max_selected_for_changes = virtual_branches
|
||||
.iter()
|
||||
.filter_map(|b| b.selected_for_changes)
|
||||
.max()
|
||||
.unwrap_or(-1);
|
||||
let default_vbranch_pos = virtual_branches
|
||||
.iter()
|
||||
.position(|b| b.selected_for_changes == Some(max_selected_for_changes))
|
||||
.unwrap_or(0);
|
||||
|
||||
// put the remaining hunks into the default (first) branch
|
||||
for (filepath, hunks) in diff {
|
||||
for hunk in hunks {
|
||||
virtual_branches[0].ownership.put(&FileOwnership {
|
||||
file_path: filepath.clone(),
|
||||
hunks: vec![Hunk::from(&hunk)
|
||||
.with_timestamp(get_mtime(&mut mtimes, &filepath))
|
||||
.with_hash(diff_hash(hunk.diff.as_str()).as_str())],
|
||||
});
|
||||
virtual_branches[default_vbranch_pos]
|
||||
.ownership
|
||||
.put(&FileOwnership {
|
||||
file_path: filepath.clone(),
|
||||
hunks: vec![Hunk::from(&hunk)
|
||||
.with_timestamp(get_mtime(&mut mtimes, &filepath))
|
||||
.with_hash(diff_hash(hunk.diff.as_str()).as_str())],
|
||||
});
|
||||
hunks_by_branch_id
|
||||
.entry(virtual_branches[0].id)
|
||||
.entry(virtual_branches[default_vbranch_pos].id)
|
||||
.or_default()
|
||||
.entry(filepath.clone())
|
||||
.or_default()
|
||||
@ -3280,11 +3341,19 @@ pub fn create_virtual_branch_from_branch(
|
||||
.context("failed to peel to commit")?;
|
||||
let head_commit_tree = head_commit.tree().context("failed to find tree")?;
|
||||
|
||||
let order = Iterator::new(¤t_session_reader)
|
||||
let all_virtual_branches = Iterator::new(¤t_session_reader)
|
||||
.context("failed to create branch iterator")?
|
||||
.collect::<Result<Vec<branch::Branch>, reader::Error>>()
|
||||
.context("failed to read virtual branches")?
|
||||
.len();
|
||||
.into_iter()
|
||||
.collect::<Vec<branch::Branch>>();
|
||||
|
||||
let order = all_virtual_branches.len();
|
||||
|
||||
let selected_for_changes = (!all_virtual_branches
|
||||
.iter()
|
||||
.any(|b| b.selected_for_changes.is_some()))
|
||||
.then_some(chrono::Utc::now().timestamp_millis());
|
||||
|
||||
let now = time::UNIX_EPOCH
|
||||
.elapsed()
|
||||
@ -3356,6 +3425,7 @@ pub fn create_virtual_branch_from_branch(
|
||||
updated_timestamp_ms: now,
|
||||
ownership,
|
||||
order,
|
||||
selected_for_changes,
|
||||
};
|
||||
|
||||
let writer = branch::Writer::new(gb_repository).context("failed to create writer")?;
|
||||
|
@ -243,6 +243,7 @@ mod test {
|
||||
.unwrap(),
|
||||
ownership: branch::Ownership::default(),
|
||||
order: TEST_INDEX.load(Ordering::Relaxed),
|
||||
selected_for_changes: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5559,3 +5559,238 @@ mod create_virtual_branch_from_branch {
|
||||
assert_eq!(branches[0].commits[0].description, "branch commit");
|
||||
}
|
||||
}
|
||||
|
||||
mod selected_for_changes {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn create_virtual_branch_should_set_selected_for_changes() {
|
||||
let Test {
|
||||
project_id,
|
||||
controller,
|
||||
..
|
||||
} = Test::default();
|
||||
|
||||
controller
|
||||
.set_base_branch(&project_id, &"refs/remotes/origin/master".parse().unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// first branch should be created as default
|
||||
let b_id = controller
|
||||
.create_virtual_branch(&project_id, &branch::BranchCreateRequest::default())
|
||||
.await
|
||||
.unwrap();
|
||||
let branch = controller
|
||||
.list_virtual_branches(&project_id)
|
||||
.await
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.find(|b| b.id == b_id)
|
||||
.unwrap();
|
||||
assert!(branch.selected_for_changes);
|
||||
|
||||
// if default branch exists, new branch should not be created as default
|
||||
let b_id = controller
|
||||
.create_virtual_branch(&project_id, &branch::BranchCreateRequest::default())
|
||||
.await
|
||||
.unwrap();
|
||||
let branch = controller
|
||||
.list_virtual_branches(&project_id)
|
||||
.await
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.find(|b| b.id == b_id)
|
||||
.unwrap();
|
||||
assert!(!branch.selected_for_changes);
|
||||
|
||||
// explicitly don't make this one default
|
||||
let b_id = controller
|
||||
.create_virtual_branch(
|
||||
&project_id,
|
||||
&branch::BranchCreateRequest {
|
||||
selected_for_changes: Some(false),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let branch = controller
|
||||
.list_virtual_branches(&project_id)
|
||||
.await
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.find(|b| b.id == b_id)
|
||||
.unwrap();
|
||||
assert!(!branch.selected_for_changes);
|
||||
|
||||
// explicitly make this one default
|
||||
let b_id = controller
|
||||
.create_virtual_branch(
|
||||
&project_id,
|
||||
&branch::BranchCreateRequest {
|
||||
selected_for_changes: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let branch = controller
|
||||
.list_virtual_branches(&project_id)
|
||||
.await
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.find(|b| b.id == b_id)
|
||||
.unwrap();
|
||||
assert!(branch.selected_for_changes);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn update_virtual_branch_should_reset_selected_for_changes() {
|
||||
let Test {
|
||||
project_id,
|
||||
controller,
|
||||
..
|
||||
} = Test::default();
|
||||
|
||||
controller
|
||||
.set_base_branch(&project_id, &"refs/remotes/origin/master".parse().unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let b1_id = controller
|
||||
.create_virtual_branch(&project_id, &branch::BranchCreateRequest::default())
|
||||
.await
|
||||
.unwrap();
|
||||
let b1 = controller
|
||||
.list_virtual_branches(&project_id)
|
||||
.await
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.find(|b| b.id == b1_id)
|
||||
.unwrap();
|
||||
assert!(b1.selected_for_changes);
|
||||
|
||||
let b2_id = controller
|
||||
.create_virtual_branch(&project_id, &branch::BranchCreateRequest::default())
|
||||
.await
|
||||
.unwrap();
|
||||
let b2 = controller
|
||||
.list_virtual_branches(&project_id)
|
||||
.await
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.find(|b| b.id == b2_id)
|
||||
.unwrap();
|
||||
assert!(!b2.selected_for_changes);
|
||||
|
||||
controller
|
||||
.update_virtual_branch(
|
||||
&project_id,
|
||||
branch::BranchUpdateRequest {
|
||||
id: b2_id,
|
||||
selected_for_changes: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let b1 = controller
|
||||
.list_virtual_branches(&project_id)
|
||||
.await
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.find(|b| b.id == b1_id)
|
||||
.unwrap();
|
||||
assert!(!b1.selected_for_changes);
|
||||
|
||||
let b2 = controller
|
||||
.list_virtual_branches(&project_id)
|
||||
.await
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.find(|b| b.id == b2_id)
|
||||
.unwrap();
|
||||
assert!(b2.selected_for_changes);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn unapply_virtual_branch_should_reset_selected_for_changes() {
|
||||
let Test {
|
||||
repository,
|
||||
project_id,
|
||||
controller,
|
||||
..
|
||||
} = Test::default();
|
||||
|
||||
controller
|
||||
.set_base_branch(&project_id, &"refs/remotes/origin/master".parse().unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let b1_id = controller
|
||||
.create_virtual_branch(&project_id, &branch::BranchCreateRequest::default())
|
||||
.await
|
||||
.unwrap();
|
||||
std::fs::write(repository.path().join("file.txt"), "content").unwrap();
|
||||
|
||||
let b1 = controller
|
||||
.list_virtual_branches(&project_id)
|
||||
.await
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.find(|b| b.id == b1_id)
|
||||
.unwrap();
|
||||
assert!(b1.selected_for_changes);
|
||||
|
||||
controller
|
||||
.unapply_virtual_branch(&project_id, &b1_id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let b1 = controller
|
||||
.list_virtual_branches(&project_id)
|
||||
.await
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.find(|b| b.id == b1_id)
|
||||
.unwrap();
|
||||
assert!(!b1.selected_for_changes);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn hunks_distribution() {
|
||||
let Test {
|
||||
repository,
|
||||
project_id,
|
||||
controller,
|
||||
..
|
||||
} = Test::default();
|
||||
|
||||
controller
|
||||
.set_base_branch(&project_id, &"refs/remotes/origin/master".parse().unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
std::fs::write(repository.path().join("file.txt"), "content").unwrap();
|
||||
|
||||
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
|
||||
assert_eq!(branches[0].files.len(), 1);
|
||||
|
||||
controller
|
||||
.create_virtual_branch(
|
||||
&project_id,
|
||||
&branch::BranchCreateRequest {
|
||||
selected_for_changes: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
std::fs::write(repository.path().join("another_file.txt"), "content").unwrap();
|
||||
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
|
||||
assert_eq!(branches[0].files.len(), 1);
|
||||
assert_eq!(branches[1].files.len(), 1);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user