gitbutler/packages/tauri/tests/virtual_branches/mod.rs

2219 lines
72 KiB
Rust
Raw Normal View History

2023-10-18 18:26:54 +03:00
//TODO:
#![allow(
clippy::redundant_closure_for_method_calls,
clippy::rest_pat_in_fully_bound_structs
)]
2023-10-12 15:25:01 +03:00
use std::{fs, str::FromStr};
use gblib::{
2023-11-17 11:55:47 +03:00
error::Error,
2023-10-13 12:00:00 +03:00
git, keys,
projects::{self, ProjectId},
users,
2023-11-17 11:55:47 +03:00
virtual_branches::{branch, controller::ControllerError, errors, Controller},
2023-10-12 15:25:01 +03:00
};
use crate::{common::TestProject, paths};
struct Test {
repository: TestProject,
2023-10-13 12:00:00 +03:00
project_id: ProjectId,
2023-10-12 15:25:01 +03:00
controller: Controller,
}
impl Default for Test {
fn default() -> Self {
let data_dir = paths::data_dir();
let keys = keys::Controller::from(&data_dir);
let projects = projects::Controller::from(&data_dir);
let users = users::Controller::from(&data_dir);
let test_project = TestProject::default();
let project = projects
.add(test_project.path())
.expect("failed to add project");
Self {
repository: test_project,
project_id: project.id,
controller: Controller::new(&data_dir, &projects, &users, &keys),
}
}
}
2023-11-01 15:27:12 +03:00
mod create_commit {
use super::*;
#[tokio::test]
async fn should_lock_updated_hunks() {
let Test {
project_id,
controller,
repository,
..
} = Test::default();
controller
.set_base_branch(
&project_id,
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
)
.unwrap();
let branch_id = controller
.create_virtual_branch(&project_id, &branch::BranchCreateRequest::default())
.await
.unwrap();
{
// by default, hunks are not locked
fs::write(repository.path().join("file.txt"), "content").unwrap();
let branch = controller
.list_virtual_branches(&project_id)
.await
.unwrap()
.into_iter()
.find(|b| b.id == branch_id)
.unwrap();
assert_eq!(branch.files.len(), 1);
assert_eq!(branch.files[0].path.display().to_string(), "file.txt");
assert_eq!(branch.files[0].hunks.len(), 1);
assert!(!branch.files[0].hunks[0].locked);
}
controller
.create_commit(&project_id, &branch_id, "test", None)
.await
.unwrap();
{
// change in the comitted hunks leads to hunk locking
fs::write(repository.path().join("file.txt"), "updated content").unwrap();
let branch = controller
.list_virtual_branches(&project_id)
.await
.unwrap()
.into_iter()
.find(|b| b.id == branch_id)
.unwrap();
assert_eq!(branch.files.len(), 1);
assert_eq!(branch.files[0].path.display().to_string(), "file.txt");
assert_eq!(branch.files[0].hunks.len(), 1);
assert!(branch.files[0].hunks[0].locked);
}
}
}
2023-10-18 16:17:09 +03:00
mod references {
2023-10-18 13:20:37 +03:00
use super::*;
2023-10-18 16:17:09 +03:00
mod create_virtual_branch {
2023-10-18 13:20:37 +03:00
use super::*;
#[tokio::test]
async fn simple() {
let Test {
project_id,
controller,
2023-10-18 16:17:09 +03:00
repository,
2023-10-18 13:20:37 +03:00
..
} = Test::default();
controller
.set_base_branch(
&project_id,
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
)
.unwrap();
let branch_id = controller
.create_virtual_branch(&project_id, &branch::BranchCreateRequest::default())
2023-10-18 13:20:37 +03:00
.await
.unwrap();
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
assert_eq!(branches.len(), 1);
assert_eq!(branches[0].id, branch_id);
2023-10-18 16:17:09 +03:00
assert_eq!(branches[0].name, "Virtual branch");
let refnames = repository
.references()
.into_iter()
.filter_map(|reference| reference.name().map(|name| name.to_string()))
.collect::<Vec<_>>();
2023-10-18 18:26:54 +03:00
assert!(refnames.contains(&"refs/gitbutler/virtual-branch".to_string()));
2023-10-18 13:20:37 +03:00
}
#[tokio::test]
async fn duplicate_name() {
let Test {
project_id,
controller,
2023-10-18 16:17:09 +03:00
repository,
2023-10-18 13:20:37 +03:00
..
} = Test::default();
controller
.set_base_branch(
&project_id,
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
)
.unwrap();
let branch1_id = controller
.create_virtual_branch(
&project_id,
&gblib::virtual_branches::branch::BranchCreateRequest {
2023-10-18 13:20:37 +03:00
name: Some("name".to_string()),
..Default::default()
},
)
.await
.unwrap();
let branch2_id = controller
.create_virtual_branch(
&project_id,
&gblib::virtual_branches::branch::BranchCreateRequest {
2023-10-18 13:20:37 +03:00
name: Some("name".to_string()),
..Default::default()
},
)
.await
.unwrap();
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
assert_eq!(branches.len(), 2);
assert_eq!(branches[0].id, branch1_id);
assert_eq!(branches[0].name, "name");
assert_eq!(branches[1].id, branch2_id);
2023-10-18 16:17:09 +03:00
assert_eq!(branches[1].name, "name 1");
let refnames = repository
.references()
.into_iter()
.filter_map(|reference| reference.name().map(|name| name.to_string()))
.collect::<Vec<_>>();
assert!(refnames.contains(&"refs/gitbutler/name".to_string()));
assert!(refnames.contains(&"refs/gitbutler/name-1".to_string()));
2023-10-18 13:20:37 +03:00
}
}
2023-10-18 16:17:09 +03:00
mod update_virtual_branch {
2023-10-18 13:20:37 +03:00
use super::*;
#[tokio::test]
async fn simple() {
let Test {
project_id,
controller,
2023-10-18 16:17:09 +03:00
repository,
2023-10-18 13:20:37 +03:00
..
} = Test::default();
controller
.set_base_branch(
&project_id,
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
)
.unwrap();
let branch_id = controller
2023-10-18 16:17:09 +03:00
.create_virtual_branch(
&project_id,
2023-10-18 18:26:54 +03:00
&branch::BranchCreateRequest {
2023-10-18 16:17:09 +03:00
name: Some("name".to_string()),
..Default::default()
},
)
2023-10-18 13:20:37 +03:00
.await
.unwrap();
controller
.update_virtual_branch(
&project_id,
2023-10-18 18:26:54 +03:00
branch::BranchUpdateRequest {
2023-10-18 13:20:37 +03:00
id: branch_id,
name: Some("new name".to_string()),
..Default::default()
},
)
.await
.unwrap();
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
assert_eq!(branches.len(), 1);
assert_eq!(branches[0].id, branch_id);
assert_eq!(branches[0].name, "new name");
2023-10-18 16:17:09 +03:00
let refnames = repository
.references()
.into_iter()
.filter_map(|reference| reference.name().map(|name| name.to_string()))
.collect::<Vec<_>>();
assert!(!refnames.contains(&"refs/gitbutler/name".to_string()));
assert!(refnames.contains(&"refs/gitbutler/new-name".to_string()));
2023-10-18 13:20:37 +03:00
}
#[tokio::test]
async fn duplicate_name() {
let Test {
project_id,
controller,
2023-10-18 16:17:09 +03:00
repository,
2023-10-18 13:20:37 +03:00
..
} = Test::default();
controller
.set_base_branch(
&project_id,
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
)
.unwrap();
let branch1_id = controller
.create_virtual_branch(
&project_id,
2023-10-18 18:26:54 +03:00
&branch::BranchCreateRequest {
2023-10-18 13:20:37 +03:00
name: Some("name".to_string()),
..Default::default()
},
)
.await
.unwrap();
let branch2_id = controller
.create_virtual_branch(
&project_id,
2023-10-18 18:26:54 +03:00
&branch::BranchCreateRequest {
2023-10-18 13:20:37 +03:00
..Default::default()
},
)
.await
.unwrap();
controller
.update_virtual_branch(
&project_id,
2023-10-18 18:26:54 +03:00
branch::BranchUpdateRequest {
2023-10-18 13:20:37 +03:00
id: branch2_id,
name: Some("name".to_string()),
..Default::default()
},
)
.await
.unwrap();
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
assert_eq!(branches.len(), 2);
assert_eq!(branches[0].id, branch1_id);
assert_eq!(branches[0].name, "name");
assert_eq!(branches[1].id, branch2_id);
2023-10-18 16:17:09 +03:00
assert_eq!(branches[1].name, "name 1");
let refnames = repository
.references()
.into_iter()
.filter_map(|reference| reference.name().map(|name| name.to_string()))
.collect::<Vec<_>>();
assert!(refnames.contains(&"refs/gitbutler/name".to_string()));
assert!(refnames.contains(&"refs/gitbutler/name-1".to_string()));
}
}
mod delete_virtual_branch {
use super::*;
#[tokio::test]
async fn simple() {
let Test {
project_id,
controller,
repository,
..
} = Test::default();
controller
.set_base_branch(
&project_id,
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
)
.unwrap();
let id = controller
.create_virtual_branch(
&project_id,
2023-10-18 18:26:54 +03:00
&branch::BranchCreateRequest {
2023-10-18 16:17:09 +03:00
name: Some("name".to_string()),
..Default::default()
},
)
.await
.unwrap();
controller
.delete_virtual_branch(&project_id, &id)
.await
.unwrap();
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
assert_eq!(branches.len(), 0);
let refnames = repository
.references()
.into_iter()
.filter_map(|reference| reference.name().map(|name| name.to_string()))
.collect::<Vec<_>>();
assert!(!refnames.contains(&"refs/gitbutler/name".to_string()));
}
}
mod push_virtual_branch {
use super::*;
#[tokio::test]
async fn simple() {
let Test {
project_id,
controller,
repository,
..
} = Test::default();
controller
.set_base_branch(
&project_id,
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
)
.unwrap();
let branch1_id = controller
.create_virtual_branch(
&project_id,
2023-10-18 18:26:54 +03:00
&branch::BranchCreateRequest {
2023-10-18 16:17:09 +03:00
name: Some("name".to_string()),
..Default::default()
},
)
.await
.unwrap();
fs::write(repository.path().join("file.txt"), "content").unwrap();
controller
.create_commit(&project_id, &branch1_id, "test", None)
.await
.unwrap();
controller
.push_virtual_branch(&project_id, &branch1_id, false)
.await
.unwrap();
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
assert_eq!(branches.len(), 1);
assert_eq!(branches[0].id, branch1_id);
assert_eq!(branches[0].name, "name");
assert_eq!(
branches[0].upstream.as_ref().unwrap().name.to_string(),
"refs/remotes/origin/name"
2023-10-18 16:17:09 +03:00
);
let refnames = repository
.references()
.into_iter()
.filter_map(|reference| reference.name().map(|name| name.to_string()))
.collect::<Vec<_>>();
assert!(refnames.contains(&branches[0].upstream.clone().unwrap().name.to_string()));
2023-10-18 16:17:09 +03:00
}
#[tokio::test]
async fn duplicate_names() {
let Test {
project_id,
controller,
repository,
..
} = Test::default();
controller
.set_base_branch(
&project_id,
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
)
.unwrap();
let branch1_id = {
// create and push branch with some work
let branch1_id = controller
.create_virtual_branch(
&project_id,
2023-10-18 18:26:54 +03:00
&branch::BranchCreateRequest {
2023-10-18 16:17:09 +03:00
name: Some("name".to_string()),
..Default::default()
},
)
.await
.unwrap();
fs::write(repository.path().join("file.txt"), "content").unwrap();
controller
.create_commit(&project_id, &branch1_id, "test", None)
.await
.unwrap();
controller
.push_virtual_branch(&project_id, &branch1_id, false)
.await
.unwrap();
branch1_id
};
// rename first branch
controller
.update_virtual_branch(
&project_id,
2023-10-18 18:26:54 +03:00
branch::BranchUpdateRequest {
2023-10-18 16:17:09 +03:00
id: branch1_id,
name: Some("updated name".to_string()),
..Default::default()
},
)
.await
.unwrap();
let branch2_id = {
// create another branch with first branch's old name and push it
let branch2_id = controller
.create_virtual_branch(
&project_id,
2023-10-18 18:26:54 +03:00
&branch::BranchCreateRequest {
2023-10-18 16:17:09 +03:00
name: Some("name".to_string()),
..Default::default()
},
)
.await
.unwrap();
fs::write(repository.path().join("file.txt"), "updated content").unwrap();
controller
.create_commit(&project_id, &branch2_id, "test", None)
.await
.unwrap();
controller
.push_virtual_branch(&project_id, &branch2_id, false)
.await
.unwrap();
branch2_id
};
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
assert_eq!(branches.len(), 2);
// first branch is pushing to old ref remotely
assert_eq!(branches[0].id, branch1_id);
assert_eq!(branches[0].name, "updated name");
assert_eq!(
branches[0].upstream.as_ref().unwrap().name,
"refs/remotes/origin/name".parse().unwrap()
2023-10-18 16:17:09 +03:00
);
// new branch is pushing to new ref remotely
assert_eq!(branches[1].id, branch2_id);
2023-10-18 13:20:37 +03:00
assert_eq!(branches[1].name, "name");
2023-10-18 16:17:09 +03:00
assert_eq!(
branches[1].upstream.as_ref().unwrap().name,
"refs/remotes/origin/name-1".parse().unwrap()
2023-10-18 16:17:09 +03:00
);
let refnames = repository
.references()
.into_iter()
.filter_map(|reference| reference.name().map(|name| name.to_string()))
.collect::<Vec<_>>();
assert!(refnames.contains(&branches[0].upstream.clone().unwrap().name.to_string()));
assert!(refnames.contains(&branches[1].upstream.clone().unwrap().name.to_string()));
2023-10-18 13:20:37 +03:00
}
}
}
2023-10-12 15:25:01 +03:00
mod set_base_branch {
use super::*;
#[test]
fn success() {
let Test {
project_id,
controller,
..
} = Test::default();
controller
.set_base_branch(
&project_id,
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
)
.unwrap();
}
mod errors {
use super::*;
#[test]
fn missing() {
let Test {
project_id,
controller,
..
} = Test::default();
2023-10-12 16:45:29 +03:00
assert!(matches!(
controller.set_base_branch(
2023-10-12 15:25:01 +03:00
&project_id,
&git::RemoteBranchName::from_str("refs/remotes/origin/missing").unwrap(),
2023-10-12 16:45:29 +03:00
),
2023-11-17 11:55:47 +03:00
Err(Error::UserError { .. })
2023-10-12 16:45:29 +03:00
));
2023-10-12 15:25:01 +03:00
}
}
}
mod conflicts {
use super::*;
mod apply_virtual_branch {
2023-10-12 15:25:01 +03:00
use super::*;
#[tokio::test]
async fn deltect_conflict() {
2023-10-12 15:25:01 +03:00
let Test {
repository,
project_id,
controller,
} = Test::default();
controller
.set_base_branch(
&project_id,
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
)
.unwrap();
let branch1_id = {
let branch1_id = controller
2023-10-18 18:26:54 +03:00
.create_virtual_branch(&project_id, &branch::BranchCreateRequest::default())
.await
.unwrap();
fs::write(repository.path().join("file.txt"), "branch one").unwrap();
branch1_id
};
2023-10-12 15:25:01 +03:00
// unapply first vbranch
2023-10-12 15:25:01 +03:00
controller
.unapply_virtual_branch(&project_id, &branch1_id)
.await
.unwrap();
{
// create another vbranch that conflicts with the first one
controller
2023-10-18 18:26:54 +03:00
.create_virtual_branch(&project_id, &branch::BranchCreateRequest::default())
.await
.unwrap();
fs::write(repository.path().join("file.txt"), "branch two").unwrap();
}
{
// it should not be possible to apply the first branch
assert!(!controller
.can_apply_virtual_branch(&project_id, &branch1_id)
.unwrap());
assert!(matches!(
controller
.apply_virtual_branch(&project_id, &branch1_id)
.await,
2023-11-17 11:55:47 +03:00
Err(ControllerError::Action(
errors::ApplyBranchError::BranchConflicts(_)
))
));
}
}
#[tokio::test]
async fn rebase_commit() {
let Test {
repository,
project_id,
controller,
} = Test::default();
// make sure we have an undiscovered commit in the remote branch
{
fs::write(repository.path().join("file.txt"), "one").unwrap();
fs::write(repository.path().join("another_file.txt"), "").unwrap();
let first_commit_oid = repository.commit_all("first");
fs::write(repository.path().join("file.txt"), "two").unwrap();
repository.commit_all("second");
repository.push();
2023-10-31 10:45:53 +03:00
repository.reset_hard(Some(first_commit_oid));
}
2023-10-12 15:25:01 +03:00
controller
.set_base_branch(
&project_id,
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
)
2023-10-12 15:25:01 +03:00
.unwrap();
let branch1_id = {
// create a branch with some commited work
let branch1_id = controller
2023-10-18 18:26:54 +03:00
.create_virtual_branch(&project_id, &branch::BranchCreateRequest::default())
.await
.unwrap();
fs::write(repository.path().join("another_file.txt"), "virtual").unwrap();
controller
.create_commit(&project_id, &branch1_id, "virtual commit", None)
.await
.unwrap();
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
assert_eq!(branches.len(), 1);
assert_eq!(branches[0].id, branch1_id);
assert!(branches[0].active);
assert_eq!(branches[0].files.len(), 0);
assert_eq!(branches[0].commits.len(), 1);
branch1_id
};
{
// unapply first vbranch
controller
.unapply_virtual_branch(&project_id, &branch1_id)
.await
.unwrap();
assert_eq!(
fs::read_to_string(repository.path().join("another_file.txt")).unwrap(),
""
);
assert_eq!(
fs::read_to_string(repository.path().join("file.txt")).unwrap(),
"one"
);
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
assert_eq!(branches.len(), 1);
assert_eq!(branches[0].id, branch1_id);
assert_eq!(branches[0].files.len(), 0);
assert_eq!(branches[0].commits.len(), 1);
assert!(!branches[0].active);
}
{
// fetch remote
controller.update_base_branch(&project_id).await.unwrap();
// branch is stil unapplied
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
assert_eq!(branches.len(), 1);
assert_eq!(branches[0].id, branch1_id);
assert_eq!(branches[0].files.len(), 0);
assert_eq!(branches[0].commits.len(), 1);
assert!(!branches[0].active);
assert!(!branches[0].conflicted);
assert_eq!(
fs::read_to_string(repository.path().join("another_file.txt")).unwrap(),
""
);
assert_eq!(
fs::read_to_string(repository.path().join("file.txt")).unwrap(),
"two"
);
}
{
// apply first vbranch again
2023-10-12 15:25:01 +03:00
controller
.apply_virtual_branch(&project_id, &branch1_id)
.await
.unwrap();
// it should be rebased
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
assert_eq!(branches.len(), 1);
assert_eq!(branches[0].id, branch1_id);
assert_eq!(branches[0].files.len(), 0);
assert_eq!(branches[0].commits.len(), 1);
assert!(branches[0].active);
assert!(!branches[0].conflicted);
assert_eq!(
fs::read_to_string(repository.path().join("another_file.txt")).unwrap(),
"virtual"
);
assert_eq!(
fs::read_to_string(repository.path().join("file.txt")).unwrap(),
"two"
);
}
}
#[tokio::test]
async fn rebase_work() {
let Test {
repository,
project_id,
controller,
} = Test::default();
// make sure we have an undiscovered commit in the remote branch
{
let first_commit_oid = repository.commit_all("first");
fs::write(repository.path().join("file.txt"), "").unwrap();
repository.commit_all("second");
repository.push();
2023-10-31 10:45:53 +03:00
repository.reset_hard(Some(first_commit_oid));
}
controller
.set_base_branch(
&project_id,
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
)
.unwrap();
let branch1_id = {
// make a branch with some work
let branch1_id = controller
2023-10-18 18:26:54 +03:00
.create_virtual_branch(&project_id, &branch::BranchCreateRequest::default())
.await
.unwrap();
2023-10-16 16:30:24 +03:00
fs::write(repository.path().join("another_file.txt"), "").unwrap();
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
assert_eq!(branches.len(), 1);
assert_eq!(branches[0].id, branch1_id);
assert!(branches[0].active);
assert_eq!(branches[0].files.len(), 1);
assert_eq!(branches[0].commits.len(), 0);
branch1_id
};
{
// unapply first vbranch
controller
.unapply_virtual_branch(&project_id, &branch1_id)
.await
.unwrap();
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
assert_eq!(branches.len(), 1);
assert_eq!(branches[0].id, branch1_id);
assert_eq!(branches[0].files.len(), 1);
assert_eq!(branches[0].commits.len(), 0);
assert!(!branches[0].active);
assert!(!repository.path().join("another_file.txt").exists());
assert!(!repository.path().join("file.txt").exists());
}
{
// fetch remote
controller.update_base_branch(&project_id).await.unwrap();
// first branch is stil unapplied
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
assert_eq!(branches.len(), 1);
assert_eq!(branches[0].id, branch1_id);
assert_eq!(branches[0].files.len(), 1);
assert_eq!(branches[0].commits.len(), 0);
assert!(!branches[0].active);
assert!(!branches[0].conflicted);
assert!(!repository.path().join("another_file.txt").exists());
assert!(repository.path().join("file.txt").exists());
}
{
// apply first vbranch again
controller
.apply_virtual_branch(&project_id, &branch1_id)
.await
.unwrap();
// workdir should be rebased, and work should be restored
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
assert_eq!(branches.len(), 1);
assert_eq!(branches[0].id, branch1_id);
assert_eq!(branches[0].files.len(), 1);
assert_eq!(branches[0].commits.len(), 0);
assert!(branches[0].active);
assert!(!branches[0].conflicted);
assert!(repository.path().join("another_file.txt").exists());
assert!(repository.path().join("file.txt").exists());
}
2023-10-12 15:25:01 +03:00
}
}
2023-10-12 16:07:43 +03:00
mod update_base_branch {
2023-11-17 11:55:47 +03:00
use gblib::virtual_branches::{controller::ControllerError, errors::CommitError};
2023-10-12 16:07:43 +03:00
use super::*;
#[tokio::test]
async fn detect_resolve_conflict() {
2023-10-12 16:07:43 +03:00
let Test {
repository,
project_id,
controller,
} = Test::default();
// make sure we have an undiscovered commit in the remote branch
{
fs::write(repository.path().join("file.txt"), "first").unwrap();
let first_commit_oid = repository.commit_all("first");
fs::write(repository.path().join("file.txt"), "second").unwrap();
repository.commit_all("second");
repository.push();
2023-10-31 10:45:53 +03:00
repository.reset_hard(Some(first_commit_oid));
}
2023-10-12 16:07:43 +03:00
controller
.set_base_branch(
&project_id,
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
)
.unwrap();
let branch1_id = {
// make a branch that conflicts with the remote branch, but doesn't know about it yet
let branch1_id = controller
2023-10-18 18:26:54 +03:00
.create_virtual_branch(&project_id, &branch::BranchCreateRequest::default())
.await
.unwrap();
fs::write(repository.path().join("file.txt"), "conflict").unwrap();
2023-10-12 16:07:43 +03:00
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
assert_eq!(branches.len(), 1);
assert_eq!(branches[0].id, branch1_id);
assert!(branches[0].active);
branch1_id
};
2023-10-12 16:07:43 +03:00
{
// fetch remote
controller.update_base_branch(&project_id).await.unwrap();
// there is a conflict now, so the branch should be inactive
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
assert_eq!(branches.len(), 1);
assert_eq!(branches[0].id, branch1_id);
assert!(!branches[0].active);
}
{
// when we apply conflicted branch, it has conflict
controller
.apply_virtual_branch(&project_id, &branch1_id)
.await
.unwrap();
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
assert_eq!(branches.len(), 1);
assert_eq!(branches[0].id, branch1_id);
assert!(branches[0].active);
assert!(branches[0].conflicted);
// and the conflict markers are in the file
assert_eq!(
fs::read_to_string(repository.path().join("file.txt")).unwrap(),
"<<<<<<< ours\nconflict\n=======\nsecond\n>>>>>>> theirs\n"
);
}
{
// can't commit conflicts
assert!(matches!(
controller
.create_commit(&project_id, &branch1_id, "commit conflicts", None)
.await,
2023-11-17 11:55:47 +03:00
Err(ControllerError::Action(CommitError::Conflicted(_)))
2023-10-12 16:07:43 +03:00
));
}
{
// fixing the conflict removes conflicted mark
fs::write(repository.path().join("file.txt"), "resolved").unwrap();
2023-10-12 16:14:02 +03:00
let commit_oid = controller
2023-10-12 16:07:43 +03:00
.create_commit(&project_id, &branch1_id, "resolution", None)
.await
.unwrap();
2023-10-12 16:14:02 +03:00
let commit = repository.find_commit(commit_oid).unwrap();
assert_eq!(commit.parent_count(), 2);
2023-10-12 16:07:43 +03:00
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
assert_eq!(branches.len(), 1);
assert_eq!(branches[0].id, branch1_id);
assert!(branches[0].active);
assert!(!branches[0].conflicted);
}
}
}
2023-10-12 15:25:01 +03:00
}
2023-10-18 14:23:55 +03:00
2023-10-23 16:51:31 +03:00
mod reset_virtual_branch {
2023-11-17 11:55:47 +03:00
use gblib::virtual_branches::{controller::ControllerError, errors::ResetBranchError};
2023-10-18 14:23:55 +03:00
use super::*;
#[tokio::test]
async fn to_head() {
let Test {
repository,
project_id,
controller,
} = Test::default();
controller
.set_base_branch(
&project_id,
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
)
.unwrap();
let branch1_id = controller
2023-10-18 18:26:54 +03:00
.create_virtual_branch(&project_id, &branch::BranchCreateRequest::default())
2023-10-18 14:23:55 +03:00
.await
.unwrap();
let oid = {
fs::write(repository.path().join("file.txt"), "content").unwrap();
// commit changes
let oid = controller
.create_commit(&project_id, &branch1_id, "commit", None)
.await
.unwrap();
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
assert_eq!(branches.len(), 1);
assert_eq!(branches[0].id, branch1_id);
assert_eq!(branches[0].commits.len(), 1);
assert_eq!(branches[0].commits[0].id, oid);
assert_eq!(branches[0].files.len(), 0);
assert_eq!(
fs::read_to_string(repository.path().join("file.txt")).unwrap(),
"content"
);
oid
};
{
// reset changes to head
controller
.reset_virtual_branch(&project_id, &branch1_id, oid)
.await
.unwrap();
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
assert_eq!(branches.len(), 1);
assert_eq!(branches[0].id, branch1_id);
assert_eq!(branches[0].commits.len(), 1);
assert_eq!(branches[0].commits[0].id, oid);
assert_eq!(branches[0].files.len(), 0);
assert_eq!(
fs::read_to_string(repository.path().join("file.txt")).unwrap(),
"content"
);
}
}
#[tokio::test]
async fn to_target() {
let Test {
repository,
project_id,
controller,
} = Test::default();
let base_branch = controller
.set_base_branch(
&project_id,
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
)
.unwrap();
let branch1_id = controller
2023-10-18 18:26:54 +03:00
.create_virtual_branch(&project_id, &branch::BranchCreateRequest::default())
2023-10-18 14:23:55 +03:00
.await
.unwrap();
{
fs::write(repository.path().join("file.txt"), "content").unwrap();
// commit changes
let oid = controller
.create_commit(&project_id, &branch1_id, "commit", None)
.await
.unwrap();
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
assert_eq!(branches.len(), 1);
assert_eq!(branches[0].id, branch1_id);
assert_eq!(branches[0].commits.len(), 1);
assert_eq!(branches[0].commits[0].id, oid);
assert_eq!(branches[0].files.len(), 0);
assert_eq!(
fs::read_to_string(repository.path().join("file.txt")).unwrap(),
"content"
);
}
{
// reset changes to head
controller
.reset_virtual_branch(&project_id, &branch1_id, base_branch.base_sha)
.await
.unwrap();
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
assert_eq!(branches.len(), 1);
assert_eq!(branches[0].id, branch1_id);
assert_eq!(branches[0].commits.len(), 0);
assert_eq!(branches[0].files.len(), 1);
assert_eq!(
fs::read_to_string(repository.path().join("file.txt")).unwrap(),
"content"
);
}
}
#[tokio::test]
async fn to_commit() {
let Test {
repository,
project_id,
controller,
} = Test::default();
controller
.set_base_branch(
&project_id,
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
)
.unwrap();
let branch1_id = controller
2023-10-18 18:26:54 +03:00
.create_virtual_branch(&project_id, &branch::BranchCreateRequest::default())
2023-10-18 14:23:55 +03:00
.await
.unwrap();
let first_commit_oid = {
// commit some changes
fs::write(repository.path().join("file.txt"), "content").unwrap();
let oid = controller
.create_commit(&project_id, &branch1_id, "commit", None)
.await
.unwrap();
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
assert_eq!(branches.len(), 1);
assert_eq!(branches[0].id, branch1_id);
assert_eq!(branches[0].commits.len(), 1);
assert_eq!(branches[0].commits[0].id, oid);
assert_eq!(branches[0].files.len(), 0);
assert_eq!(
fs::read_to_string(repository.path().join("file.txt")).unwrap(),
"content"
);
oid
};
{
// commit some more
fs::write(repository.path().join("file.txt"), "more content").unwrap();
let second_commit_oid = controller
.create_commit(&project_id, &branch1_id, "commit", None)
.await
.unwrap();
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
assert_eq!(branches.len(), 1);
assert_eq!(branches[0].id, branch1_id);
assert_eq!(branches[0].commits.len(), 2);
assert_eq!(branches[0].commits[0].id, second_commit_oid);
assert_eq!(branches[0].commits[1].id, first_commit_oid);
assert_eq!(branches[0].files.len(), 0);
assert_eq!(
fs::read_to_string(repository.path().join("file.txt")).unwrap(),
"more content"
);
}
{
// reset changes to the first commit
controller
.reset_virtual_branch(&project_id, &branch1_id, first_commit_oid)
.await
.unwrap();
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
assert_eq!(branches.len(), 1);
assert_eq!(branches[0].id, branch1_id);
assert_eq!(branches[0].commits.len(), 1);
assert_eq!(branches[0].commits[0].id, first_commit_oid);
assert_eq!(branches[0].files.len(), 1);
assert_eq!(
fs::read_to_string(repository.path().join("file.txt")).unwrap(),
"more content"
);
}
}
#[tokio::test]
async fn to_non_existing() {
let Test {
repository,
project_id,
controller,
} = Test::default();
controller
.set_base_branch(
&project_id,
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
)
.unwrap();
let branch1_id = controller
2023-10-18 18:26:54 +03:00
.create_virtual_branch(&project_id, &branch::BranchCreateRequest::default())
2023-10-18 14:23:55 +03:00
.await
.unwrap();
{
fs::write(repository.path().join("file.txt"), "content").unwrap();
// commit changes
let oid = controller
.create_commit(&project_id, &branch1_id, "commit", None)
.await
.unwrap();
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
assert_eq!(branches.len(), 1);
assert_eq!(branches[0].id, branch1_id);
assert_eq!(branches[0].commits.len(), 1);
assert_eq!(branches[0].commits[0].id, oid);
assert_eq!(branches[0].files.len(), 0);
assert_eq!(
fs::read_to_string(repository.path().join("file.txt")).unwrap(),
"content"
);
oid
};
assert!(matches!(
controller
.reset_virtual_branch(
&project_id,
&branch1_id,
"fe14df8c66b73c6276f7bb26102ad91da680afcb".parse().unwrap()
)
.await,
2023-11-17 11:55:47 +03:00
Err(ControllerError::Action(
ResetBranchError::CommitNotFoundInBranch(_)
))
2023-10-18 14:23:55 +03:00
));
}
}
mod upstream {
use super::*;
#[tokio::test]
async fn detect_upstream_commits() {
let Test {
repository,
project_id,
controller,
} = Test::default();
controller
.set_base_branch(
&project_id,
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
)
.unwrap();
let branch1_id = controller
.create_virtual_branch(&project_id, &branch::BranchCreateRequest::default())
.await
.unwrap();
let oid1 = {
// create first commit
fs::write(repository.path().join("file.txt"), "content").unwrap();
controller
.create_commit(&project_id, &branch1_id, "commit", None)
.await
.unwrap()
};
let oid2 = {
// create second commit
fs::write(repository.path().join("file.txt"), "content2").unwrap();
controller
.create_commit(&project_id, &branch1_id, "commit", None)
.await
.unwrap()
};
// push
controller
.push_virtual_branch(&project_id, &branch1_id, false)
.await
.unwrap();
let oid3 = {
// create third commit
fs::write(repository.path().join("file.txt"), "content3").unwrap();
controller
.create_commit(&project_id, &branch1_id, "commit", None)
.await
.unwrap()
};
{
// should correctly detect pushed commits
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
assert_eq!(branches.len(), 1);
assert_eq!(branches[0].id, branch1_id);
assert_eq!(branches[0].commits.len(), 3);
assert_eq!(branches[0].commits[0].id, oid3);
assert!(!branches[0].commits[0].is_remote);
assert_eq!(branches[0].commits[1].id, oid2);
assert!(branches[0].commits[1].is_remote);
assert_eq!(branches[0].commits[2].id, oid1);
assert!(branches[0].commits[2].is_remote);
}
}
2023-10-24 14:49:06 +03:00
#[tokio::test]
async fn detect_integrated_commits() {
let Test {
repository,
project_id,
controller,
} = Test::default();
controller
.set_base_branch(
&project_id,
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
)
.unwrap();
let branch1_id = controller
.create_virtual_branch(&project_id, &branch::BranchCreateRequest::default())
.await
.unwrap();
let oid1 = {
// create first commit
fs::write(repository.path().join("file.txt"), "content").unwrap();
controller
.create_commit(&project_id, &branch1_id, "commit", None)
.await
.unwrap()
};
let oid2 = {
// create second commit
fs::write(repository.path().join("file.txt"), "content2").unwrap();
controller
.create_commit(&project_id, &branch1_id, "commit", None)
.await
.unwrap()
};
// push
controller
.push_virtual_branch(&project_id, &branch1_id, false)
.await
.unwrap();
{
// merge branch upstream
2023-10-24 14:57:43 +03:00
let branch = controller
.list_virtual_branches(&project_id)
.await
.unwrap()
.into_iter()
.find(|b| b.id == branch1_id)
.unwrap();
2023-10-24 14:49:06 +03:00
repository.merge(&branch.upstream.as_ref().unwrap().name);
2023-10-24 15:10:06 +03:00
repository.fetch();
2023-10-24 14:49:06 +03:00
}
let oid3 = {
// create third commit
fs::write(repository.path().join("file.txt"), "content3").unwrap();
controller
.create_commit(&project_id, &branch1_id, "commit", None)
.await
.unwrap()
};
{
// should correctly detect pushed commits
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
assert_eq!(branches.len(), 1);
assert_eq!(branches[0].id, branch1_id);
assert_eq!(branches[0].commits.len(), 3);
assert_eq!(branches[0].commits[0].id, oid3);
assert!(!branches[0].commits[0].is_integrated);
assert_eq!(branches[0].commits[1].id, oid2);
assert!(branches[0].commits[1].is_integrated);
assert_eq!(branches[0].commits[2].id, oid1);
assert!(branches[0].commits[2].is_integrated);
}
}
}
2023-10-23 16:51:31 +03:00
mod cherry_pick {
use super::*;
mod cleanly {
use super::*;
#[tokio::test]
async fn applied() {
let Test {
repository,
project_id,
controller,
} = Test::default();
controller
.set_base_branch(
&project_id,
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
)
.unwrap();
let branch_id = controller
.create_virtual_branch(&project_id, &branch::BranchCreateRequest::default())
.await
.unwrap();
2023-10-31 10:45:53 +03:00
let commit_one = {
fs::write(repository.path().join("file.txt"), "content").unwrap();
controller
.create_commit(&project_id, &branch_id, "commit", None)
.await
.unwrap()
};
let commit_two = {
fs::write(repository.path().join("file.txt"), "content two").unwrap();
controller
.create_commit(&project_id, &branch_id, "commit", None)
.await
.unwrap()
};
controller
.push_virtual_branch(&project_id, &branch_id, false)
.await
.unwrap();
controller
.reset_virtual_branch(&project_id, &branch_id, commit_one)
.await
.unwrap();
repository.reset_hard(None);
assert_eq!(
fs::read_to_string(repository.path().join("file.txt")).unwrap(),
"content"
);
2023-10-23 16:51:31 +03:00
let cherry_picked_commit_oid = controller
2023-10-31 10:45:53 +03:00
.cherry_pick(&project_id, &branch_id, commit_two)
2023-10-23 16:51:31 +03:00
.await
.unwrap();
assert!(cherry_picked_commit_oid.is_some());
assert!(repository.path().join("file.txt").exists());
2023-10-31 10:45:53 +03:00
assert_eq!(
fs::read_to_string(repository.path().join("file.txt")).unwrap(),
"content two"
);
2023-10-23 16:51:31 +03:00
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
assert_eq!(branches.len(), 1);
assert_eq!(branches[0].id, branch_id);
assert!(branches[0].active);
2023-10-31 10:45:53 +03:00
assert_eq!(branches[0].commits.len(), 2);
2023-10-23 16:51:31 +03:00
assert_eq!(branches[0].commits[0].id, cherry_picked_commit_oid.unwrap());
2023-10-31 10:45:53 +03:00
assert_eq!(branches[0].commits[1].id, commit_one);
}
#[tokio::test]
async fn to_different_branch() {
let Test {
repository,
project_id,
controller,
} = Test::default();
controller
.set_base_branch(
&project_id,
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
)
.unwrap();
let branch_id = controller
.create_virtual_branch(&project_id, &branch::BranchCreateRequest::default())
.await
.unwrap();
let commit_one = {
fs::write(repository.path().join("file.txt"), "content").unwrap();
controller
.create_commit(&project_id, &branch_id, "commit", None)
.await
.unwrap()
};
let commit_two = {
fs::write(repository.path().join("file_two.txt"), "content two").unwrap();
controller
.create_commit(&project_id, &branch_id, "commit", None)
.await
.unwrap()
};
controller
.push_virtual_branch(&project_id, &branch_id, false)
.await
.unwrap();
controller
.reset_virtual_branch(&project_id, &branch_id, commit_one)
.await
.unwrap();
repository.reset_hard(None);
assert_eq!(
fs::read_to_string(repository.path().join("file.txt")).unwrap(),
"content"
);
assert!(!repository.path().join("file_two.txt").exists());
let branch_two_id = controller
.create_virtual_branch(&project_id, &branch::BranchCreateRequest::default())
.await
.unwrap();
let cherry_picked_commit_oid = controller
.cherry_pick(&project_id, &branch_two_id, commit_two)
.await
.unwrap();
assert!(cherry_picked_commit_oid.is_some());
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
assert!(repository.path().join("file_two.txt").exists());
assert_eq!(
fs::read_to_string(repository.path().join("file_two.txt")).unwrap(),
"content two"
);
assert_eq!(branches.len(), 2);
assert_eq!(branches[0].id, branch_id);
assert!(!branches[0].active);
assert_eq!(branches[0].commits.len(), 1);
assert_eq!(branches[0].commits[0].id, commit_one);
assert_eq!(branches[1].id, branch_two_id);
assert!(branches[1].active);
assert_eq!(branches[1].commits.len(), 1);
assert_eq!(branches[1].commits[0].id, cherry_picked_commit_oid.unwrap());
2023-10-23 16:51:31 +03:00
}
#[tokio::test]
async fn non_applied() {
let Test {
repository,
project_id,
controller,
} = Test::default();
controller
.set_base_branch(
&project_id,
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
)
.unwrap();
let branch_id = controller
.create_virtual_branch(&project_id, &branch::BranchCreateRequest::default())
.await
.unwrap();
let commit_one_oid = {
fs::write(repository.path().join("file.txt"), "content").unwrap();
controller
.create_commit(&project_id, &branch_id, "commit", None)
.await
.unwrap()
};
{
fs::write(repository.path().join("file_two.txt"), "content two").unwrap();
controller
.create_commit(&project_id, &branch_id, "commit", None)
.await
.unwrap()
};
let commit_three_oid = {
fs::write(repository.path().join("file_three.txt"), "content three").unwrap();
controller
.create_commit(&project_id, &branch_id, "commit", None)
.await
.unwrap()
};
controller
.reset_virtual_branch(&project_id, &branch_id, commit_one_oid)
.await
.unwrap();
controller
.unapply_virtual_branch(&project_id, &branch_id)
.await
.unwrap();
assert!(matches!(
controller
.cherry_pick(&project_id, &branch_id, commit_three_oid)
.await,
2023-11-17 11:55:47 +03:00
Err(ControllerError::Action(errors::CherryPickError::NotApplied))
2023-10-23 16:51:31 +03:00
));
}
}
mod with_conflicts {
use super::*;
#[tokio::test]
async fn applied() {
let Test {
repository,
project_id,
controller,
} = Test::default();
controller
.set_base_branch(
&project_id,
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
)
.unwrap();
let branch_id = controller
.create_virtual_branch(&project_id, &branch::BranchCreateRequest::default())
.await
.unwrap();
2023-10-31 10:45:53 +03:00
let commit_one = {
fs::write(repository.path().join("file.txt"), "content").unwrap();
controller
2023-10-31 13:52:11 +03:00
.create_commit(&project_id, &branch_id, "commit one", None)
2023-10-31 10:45:53 +03:00
.await
.unwrap()
};
2023-10-31 13:52:11 +03:00
{
2023-10-31 10:45:53 +03:00
fs::write(repository.path().join("file_two.txt"), "content two").unwrap();
controller
2023-10-31 13:52:11 +03:00
.create_commit(&project_id, &branch_id, "commit two", None)
.await
.unwrap()
};
let commit_three = {
fs::write(repository.path().join("file_three.txt"), "content three").unwrap();
controller
.create_commit(&project_id, &branch_id, "commit three", None)
2023-10-31 10:45:53 +03:00
.await
.unwrap()
};
controller
.push_virtual_branch(&project_id, &branch_id, false)
.await
.unwrap();
controller
.reset_virtual_branch(&project_id, &branch_id, commit_one)
.await
.unwrap();
repository.reset_hard(None);
assert_eq!(
fs::read_to_string(repository.path().join("file.txt")).unwrap(),
"content"
);
2023-10-31 13:52:11 +03:00
assert!(!repository.path().join("file_two.txt").exists());
assert!(!repository.path().join("file_three.txt").exists());
2023-10-31 10:45:53 +03:00
2023-10-23 16:51:31 +03:00
// introduce conflict with the remote commit
2023-10-31 13:52:11 +03:00
fs::write(repository.path().join("file_three.txt"), "conflict").unwrap();
2023-10-23 16:51:31 +03:00
{
// cherry picking leads to conflict
let cherry_picked_commit_oid = controller
2023-10-31 13:52:11 +03:00
.cherry_pick(&project_id, &branch_id, commit_three)
2023-10-23 16:51:31 +03:00
.await
.unwrap();
assert!(cherry_picked_commit_oid.is_none());
assert_eq!(
2023-10-31 13:52:11 +03:00
fs::read_to_string(repository.path().join("file_three.txt")).unwrap(),
"<<<<<<< ours\nconflict\n=======\ncontent three\n>>>>>>> theirs\n"
2023-10-23 16:51:31 +03:00
);
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
assert_eq!(branches.len(), 1);
assert_eq!(branches[0].id, branch_id);
assert!(branches[0].active);
assert!(branches[0].conflicted);
assert_eq!(branches[0].files.len(), 1);
assert!(branches[0].files[0].conflicted);
2023-10-31 10:45:53 +03:00
assert_eq!(branches[0].commits.len(), 1);
2023-10-23 16:51:31 +03:00
}
{
// conflict can be resolved
2023-10-31 13:52:11 +03:00
fs::write(repository.path().join("file_three.txt"), "resolved").unwrap();
2023-10-23 16:51:31 +03:00
let commited_oid = controller
.create_commit(&project_id, &branch_id, "resolution", None)
.await
.unwrap();
let commit = repository.find_commit(commited_oid).unwrap();
assert_eq!(commit.parent_count(), 2);
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
assert_eq!(branches.len(), 1);
assert_eq!(branches[0].id, branch_id);
assert!(branches[0].active);
2023-10-31 13:52:11 +03:00
assert!(branches[0].requires_force);
2023-10-23 16:51:31 +03:00
assert!(!branches[0].conflicted);
2023-10-31 13:52:11 +03:00
assert_eq!(branches[0].commits.len(), 2);
2023-10-23 16:51:31 +03:00
// resolution commit is there
assert_eq!(branches[0].commits[0].id, commited_oid);
2023-10-31 13:52:11 +03:00
assert_eq!(branches[0].commits[1].id, commit_one);
2023-10-23 16:51:31 +03:00
}
}
#[tokio::test]
async fn non_applied() {
let Test {
repository,
project_id,
controller,
} = Test::default();
let commit_oid = {
let first = repository.commit_all("commit");
fs::write(repository.path().join("file.txt"), "content").unwrap();
let second = repository.commit_all("commit");
repository.push();
2023-10-31 10:45:53 +03:00
repository.reset_hard(Some(first));
2023-10-23 16:51:31 +03:00
second
};
controller
.set_base_branch(
&project_id,
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
)
.unwrap();
let branch_id = controller
.create_virtual_branch(&project_id, &branch::BranchCreateRequest::default())
.await
.unwrap();
// introduce conflict with the remote commit
fs::write(repository.path().join("file.txt"), "conflict").unwrap();
controller
.unapply_virtual_branch(&project_id, &branch_id)
.await
.unwrap();
assert!(matches!(
controller
.cherry_pick(&project_id, &branch_id, commit_oid)
.await,
2023-11-17 11:55:47 +03:00
Err(ControllerError::Action(errors::CherryPickError::NotApplied))
2023-10-23 16:51:31 +03:00
));
}
}
}
2023-11-01 15:45:13 +03:00
mod amend {
use super::*;
#[tokio::test]
async fn to_default_target() {
let Test {
repository,
project_id,
controller,
..
} = Test::default();
2023-11-01 16:44:47 +03:00
controller
.set_base_branch(
&project_id,
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
)
.unwrap();
2023-11-01 15:45:13 +03:00
let branch_id = controller
.create_virtual_branch(&project_id, &branch::BranchCreateRequest::default())
.await
.unwrap();
2023-11-01 16:44:47 +03:00
// amend without head commit
fs::write(repository.path().join("file2.txt"), "content").unwrap();
let to_amend: branch::Ownership = "file2.txt:1-2".parse().unwrap();
2023-11-17 11:55:47 +03:00
assert!(matches!(
2023-11-01 16:44:47 +03:00
controller
.amend(&project_id, &branch_id, &to_amend)
.await
2023-11-17 11:55:47 +03:00
.unwrap_err(),
ControllerError::Action(errors::AmendError::BranchHasNoCommits)
));
}
#[tokio::test]
async fn non_locked_hunk() {
let Test {
repository,
project_id,
controller,
..
} = Test::default();
controller
.set_base_branch(
&project_id,
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
)
.unwrap();
let branch_id = controller
.create_virtual_branch(&project_id, &branch::BranchCreateRequest::default())
.await
.unwrap();
{
// create commit
fs::write(repository.path().join("file.txt"), "content").unwrap();
controller
.create_commit(&project_id, &branch_id, "commit one", None)
.await
2023-11-01 16:44:47 +03:00
.unwrap();
let branch = controller
.list_virtual_branches(&project_id)
2023-11-01 16:44:47 +03:00
.await
.unwrap()
.into_iter()
.find(|b| b.id == branch_id)
2023-11-01 16:44:47 +03:00
.unwrap();
assert_eq!(branch.commits.len(), 1);
assert_eq!(branch.files.len(), 0);
assert_eq!(branch.commits[0].files.len(), 1);
};
2023-11-01 16:44:47 +03:00
{
// amend another hunk
fs::write(repository.path().join("file2.txt"), "content2").unwrap();
let to_amend: branch::Ownership = "file2.txt:1-2".parse().unwrap();
2023-11-01 16:44:47 +03:00
controller
.amend(&project_id, &branch_id, &to_amend)
.await
.unwrap();
let branch = controller
.list_virtual_branches(&project_id)
.await
.unwrap()
.into_iter()
.find(|b| b.id == branch_id)
.unwrap();
assert_eq!(branch.commits.len(), 1);
assert_eq!(branch.files.len(), 0);
assert_eq!(branch.commits[0].files.len(), 2);
2023-11-01 16:44:47 +03:00
}
}
2023-11-01 16:44:47 +03:00
#[tokio::test]
async fn locked_hunk() {
let Test {
repository,
project_id,
controller,
..
} = Test::default();
controller
.set_base_branch(
&project_id,
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
)
.unwrap();
let branch_id = controller
.create_virtual_branch(&project_id, &branch::BranchCreateRequest::default())
.await
.unwrap();
2023-11-01 16:44:47 +03:00
{
// create commit
fs::write(repository.path().join("file.txt"), "content").unwrap();
2023-11-01 16:44:47 +03:00
controller
.create_commit(&project_id, &branch_id, "commit one", None)
.await
2023-11-01 16:44:47 +03:00
.unwrap();
let branch = controller
.list_virtual_branches(&project_id)
.await
.unwrap()
.into_iter()
.find(|b| b.id == branch_id)
.unwrap();
assert_eq!(branch.commits.len(), 1);
assert_eq!(branch.files.len(), 0);
assert_eq!(branch.commits[0].files.len(), 1);
assert_eq!(
branch.commits[0].files[0].hunks[0].diff,
"@@ -0,0 +1 @@\n+content\n\\ No newline at end of file\n"
);
};
{
// amend another hunk
fs::write(repository.path().join("file.txt"), "more content").unwrap();
let to_amend: branch::Ownership = "file.txt:1-2".parse().unwrap();
controller
.amend(&project_id, &branch_id, &to_amend)
2023-11-01 16:44:47 +03:00
.await
.unwrap();
let branch = controller
.list_virtual_branches(&project_id)
.await
.unwrap()
.into_iter()
.find(|b| b.id == branch_id)
.unwrap();
2023-11-01 16:44:47 +03:00
assert_eq!(branch.commits.len(), 1);
assert_eq!(branch.files.len(), 0);
assert_eq!(branch.commits[0].files.len(), 1);
assert_eq!(
branch.commits[0].files[0].hunks[0].diff,
"@@ -0,0 +1 @@\n+more content\n\\ No newline at end of file\n"
);
}
}
2023-11-01 16:44:47 +03:00
#[tokio::test]
async fn non_existing_ownership() {
let Test {
repository,
project_id,
controller,
..
} = Test::default();
2023-11-01 16:44:47 +03:00
controller
.set_base_branch(
&project_id,
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
)
.unwrap();
2023-11-01 15:45:13 +03:00
let branch_id = controller
.create_virtual_branch(&project_id, &branch::BranchCreateRequest::default())
.await
.unwrap();
2023-11-01 16:44:47 +03:00
{
// create commit
fs::write(repository.path().join("file.txt"), "content").unwrap();
2023-11-01 16:44:47 +03:00
controller
.create_commit(&project_id, &branch_id, "commit one", None)
.await
2023-11-01 16:44:47 +03:00
.unwrap();
let branch = controller
.list_virtual_branches(&project_id)
2023-11-01 16:44:47 +03:00
.await
.unwrap()
.into_iter()
.find(|b| b.id == branch_id)
2023-11-01 16:44:47 +03:00
.unwrap();
assert_eq!(branch.commits.len(), 1);
assert_eq!(branch.files.len(), 0);
assert_eq!(branch.commits[0].files.len(), 1);
};
2023-11-01 16:44:47 +03:00
{
// amend non existing hunk
let to_amend: branch::Ownership = "file2.txt:1-2".parse().unwrap();
2023-11-17 11:55:47 +03:00
assert!(matches!(
2023-11-01 16:44:47 +03:00
controller
.amend(&project_id, &branch_id, &to_amend)
.await
2023-11-17 11:55:47 +03:00
.unwrap_err(),
ControllerError::Action(errors::AmendError::TargetOwnerhshipNotFound(_))
));
2023-11-01 15:45:13 +03:00
}
}
}
2023-11-03 16:30:30 +03:00
mod init {
use super::*;
#[tokio::test]
async fn dirty() {
let Test {
repository,
project_id,
controller,
..
} = Test::default();
fs::write(repository.path().join("file.txt"), "content").unwrap();
controller
.set_base_branch(
&project_id,
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
)
.unwrap();
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
assert_eq!(branches.len(), 1);
assert_eq!(branches[0].files.len(), 1);
assert_eq!(branches[0].files[0].hunks.len(), 1);
}
}
2023-11-09 15:00:14 +03:00
mod squash {
use super::*;
#[tokio::test]
async fn head() {
let Test {
repository,
project_id,
controller,
..
} = Test::default();
controller
.set_base_branch(
&project_id,
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
)
.unwrap();
let branch_id = controller
.create_virtual_branch(&project_id, &branch::BranchCreateRequest::default())
.await
.unwrap();
let commit_one_oid = {
fs::write(repository.path().join("file one.txt"), "").unwrap();
controller
.create_commit(&project_id, &branch_id, "commit one", None)
.await
.unwrap()
};
let commit_two_oid = {
fs::write(repository.path().join("file two.txt"), "").unwrap();
controller
.create_commit(&project_id, &branch_id, "commit two", None)
.await
.unwrap()
};
let commit_three_oid = {
fs::write(repository.path().join("file three.txt"), "").unwrap();
controller
.create_commit(&project_id, &branch_id, "commit three", None)
.await
.unwrap()
};
controller
.squash(&project_id, &branch_id, commit_three_oid)
.await
.unwrap();
let branch = controller
.list_virtual_branches(&project_id)
.await
.unwrap()
.into_iter()
.find(|b| b.id == branch_id)
.unwrap();
assert_eq!(branch.commits.len(), 2);
assert_ne!(branch.commits[0].id, commit_three_oid);
assert_ne!(branch.commits[0].id, commit_two_oid);
assert_eq!(branch.commits[0].description, "commit two\ncommit three");
2023-11-09 15:00:14 +03:00
assert_eq!(branch.commits[0].files.len(), 2);
assert_eq!(branch.commits[1].id, commit_one_oid);
}
#[tokio::test]
async fn middle() {
let Test {
repository,
project_id,
controller,
..
} = Test::default();
controller
.set_base_branch(
&project_id,
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
)
.unwrap();
let branch_id = controller
.create_virtual_branch(&project_id, &branch::BranchCreateRequest::default())
.await
.unwrap();
let commit_one_oid = {
fs::write(repository.path().join("file one.txt"), "").unwrap();
controller
.create_commit(&project_id, &branch_id, "commit one", None)
.await
.unwrap()
};
let commit_two_oid = {
fs::write(repository.path().join("file two.txt"), "").unwrap();
controller
.create_commit(&project_id, &branch_id, "commit two", None)
.await
.unwrap()
};
let commit_three_oid = {
fs::write(repository.path().join("file three.txt"), "").unwrap();
controller
.create_commit(&project_id, &branch_id, "commit three", None)
.await
.unwrap()
};
controller
.squash(&project_id, &branch_id, commit_two_oid)
.await
.unwrap();
let branch = controller
.list_virtual_branches(&project_id)
.await
.unwrap()
.into_iter()
.find(|b| b.id == branch_id)
.unwrap();
assert_eq!(branch.commits.len(), 2);
assert_ne!(branch.commits[0].id, commit_three_oid);
assert_ne!(branch.commits[0].id, commit_two_oid);
assert_ne!(branch.commits[0].id, commit_one_oid);
assert_eq!(branch.commits[0].description, "commit three");
assert_eq!(branch.commits[0].files.len(), 1);
assert_ne!(branch.commits[1].id, commit_three_oid);
assert_ne!(branch.commits[1].id, commit_two_oid);
assert_ne!(branch.commits[1].id, commit_one_oid);
assert_eq!(branch.commits[1].description, "commit one\ncommit two");
2023-11-09 15:00:14 +03:00
assert_eq!(branch.commits[1].files.len(), 2);
}
#[tokio::test]
async fn root() {
let Test {
repository,
project_id,
controller,
..
} = Test::default();
controller
.set_base_branch(
&project_id,
&git::RemoteBranchName::from_str("refs/remotes/origin/master").unwrap(),
)
.unwrap();
let branch_id = controller
.create_virtual_branch(&project_id, &branch::BranchCreateRequest::default())
.await
.unwrap();
let commit_one_oid = {
fs::write(repository.path().join("file one.txt"), "").unwrap();
controller
.create_commit(&project_id, &branch_id, "commit one", None)
.await
.unwrap()
};
2023-11-17 11:55:47 +03:00
assert!(matches!(
2023-11-09 15:00:14 +03:00
controller
.squash(&project_id, &branch_id, commit_one_oid)
.await
2023-11-17 11:55:47 +03:00
.unwrap_err(),
ControllerError::Action(errors::SquashError::CantSquashRootCommit)
));
2023-11-09 15:00:14 +03:00
}
}