test more init test cases

This commit is contained in:
Nikita Galaiko 2023-12-05 15:18:34 +01:00 committed by GitButler
parent ef9aa747a4
commit 65fd1c9ac3
5 changed files with 269 additions and 87 deletions

View File

@ -40,7 +40,6 @@ impl super::RunCommand for Setup {
set_base_branch(
&app.gb_repository(),
&app.project_repository(),
app.user(),
&items[index].branch().parse()?,
)
.context("failed to set target branch")?;

View File

@ -5,14 +5,18 @@ use serde::Serialize;
use crate::{
gb_repository,
git::{self, diff},
git::{
self,
diff::{self, Options},
},
keys,
project_repository::{self, LogUntil},
reader, sessions, users,
virtual_branches::branch::Ownership,
};
use super::{
branch, delete_branch,
branch,
errors::{self, CreateVirtualBranchFromBranchError},
integration::GITBUTLER_INTEGRATION_REFERENCE,
iterator, target, BranchId, RemoteCommit,
@ -51,30 +55,29 @@ pub fn get_base_branch_data(
pub fn set_base_branch(
gb_repository: &gb_repository::Repository,
project_repository: &project_repository::Repository,
user: Option<&users::User>,
target_branch: &git::RemoteRefname,
target_branch_ref: &git::RemoteRefname,
) -> Result<super::BaseBranch, errors::SetBaseBranchError> {
let repo = &project_repository.git_repository;
// lookup a branch by name
let branch = match repo.find_branch(&target_branch.clone().into()) {
let target_branch = match repo.find_branch(&target_branch_ref.clone().into()) {
Ok(branch) => Ok(branch),
Err(git::Error::NotFound(_)) => Err(errors::SetBaseBranchError::BranchNotFound(
target_branch.clone(),
target_branch_ref.clone(),
)),
Err(error) => Err(errors::SetBaseBranchError::Other(error.into())),
}?;
let remote_name = repo
.branch_remote_name(branch.refname().unwrap())
.branch_remote_name(target_branch.refname().unwrap())
.context(format!(
"failed to get remote name for branch {}",
branch.name().unwrap()
target_branch.name().unwrap()
))?;
let remote = repo.find_remote(&remote_name).context(format!(
"failed to find remote {} for branch {}",
remote_name,
branch.name().unwrap()
target_branch.name().unwrap()
))?;
let remote_url = remote
.url()
@ -84,34 +87,30 @@ pub fn set_base_branch(
))?
.unwrap();
// get a list of currently active virtual branches
// if there are no applied virtual branches, calculate the sha as the merge-base between HEAD in project_repository and this target commit
let commit = branch.peel_to_commit().context(format!(
let target_branch_head = target_branch.peel_to_commit().context(format!(
"failed to peel branch {} to commit",
branch.name().unwrap()
target_branch.name().unwrap()
))?;
let mut commit_oid = commit.id();
let head_ref = repo.head().context("Failed to get HEAD reference")?;
let head_name: git::Refname = head_ref
.name()
.context("Failed to get HEAD reference name")?;
let head_oid = head_ref
let head_commit = head_ref
.peel_to_commit()
.context("Failed to peel HEAD reference to commit")?
.id();
.context("Failed to peel HEAD reference to commit")?;
if head_oid != commit_oid {
// calculate the commit as the merge-base between HEAD in project_repository and this target commit
commit_oid = repo.merge_base(head_oid, commit_oid).context(format!(
// calculate the commit as the merge-base between HEAD in project_repository and this target commit
let commit_oid = repo
.merge_base(head_commit.id(), target_branch_head.id())
.context(format!(
"Failed to calculate merge base between {} and {}",
head_oid, commit_oid
head_commit.id(),
target_branch_head.id()
))?;
}
let target = target::Target {
branch: target_branch.clone(),
branch: target_branch_ref.clone(),
remote_url: remote_url.to_string(),
sha: commit_oid,
};
@ -119,38 +118,83 @@ pub fn set_base_branch(
let target_writer = target::Writer::new(gb_repository);
target_writer.write_default(&target)?;
let current_session = gb_repository
.get_or_create_current_session()
.context("failed to get current session")?;
let current_session_reader = sessions::Reader::open(gb_repository, &current_session)
.context("failed to open current session for reading")?;
let virtual_branches = iterator::BranchIterator::new(&current_session_reader)
.context("failed to create branch iterator")?
.collect::<Result<Vec<branch::Branch>, reader::Error>>()
.context("failed to read virtual branches")?;
let active_virtual_branches = virtual_branches
.iter()
.filter(|branch| branch.applied)
.collect::<Vec<_>>();
if active_virtual_branches.is_empty()
&& !head_name
.to_string()
.eq(&GITBUTLER_INTEGRATION_REFERENCE.to_string())
if !head_name
.to_string()
.eq(&GITBUTLER_INTEGRATION_REFERENCE.to_string())
{
let branch = create_virtual_branch_from_branch(
gb_repository,
project_repository,
&head_name,
Some(true),
user,
)
.context("failed to create virtual branch")?;
if branch.ownership.is_empty() && branch.head == target.sha {
delete_branch(gb_repository, project_repository, &branch.id)
.context("failed to delete branch")?;
// if there are any commits on the head branch or uncommitted changes in the working directory, we need to
// put them into a virtual branch
let wd_diff = diff::workdir(repo, &head_commit.id(), &Options::default())?;
if !wd_diff.is_empty() || head_commit.id() != commit_oid {
let hunks_by_filepath =
super::virtual_hunks_by_filepath(&project_repository.project().path, &wd_diff);
// assign ownership to the branch
let ownership = hunks_by_filepath.values().flatten().fold(
Ownership::default(),
|mut ownership, hunk| {
ownership.put(
&format!("{}:{}", hunk.file_path.display(), hunk.id)
.parse()
.unwrap(),
);
ownership
},
);
let now_ms = time::UNIX_EPOCH
.elapsed()
.context("failed to get elapsed time")?
.as_millis();
let (upstream, upstream_head) = if let git::Refname::Local(head_name) = &head_name {
let upstream_name = target_branch_ref.with_branch(head_name.branch());
if upstream_name.eq(target_branch_ref) {
(None, None)
} else {
match repo.find_reference(&git::Refname::from(&upstream_name)) {
Ok(upstream) => {
let head = upstream
.peel_to_commit()
.map(|commit| commit.id())
.context(format!(
"failed to peel upstream {} to commit",
upstream.name().unwrap()
))?;
Ok((Some(upstream_name), Some(head)))
}
Err(git::Error::NotFound(_)) => Ok((None, None)),
Err(error) => Err(error),
}
.context(format!("failed to find upstream for {}", head_name))?
}
} else {
(None, None)
};
let branch = branch::Branch {
id: BranchId::generate(),
name: head_name.to_string().replace("refs/heads/", ""),
notes: String::new(),
applied: true,
upstream,
upstream_head,
created_timestamp_ms: now_ms,
updated_timestamp_ms: now_ms,
head: head_commit.id(),
tree: super::write_tree_onto_commit(
project_repository,
head_commit.id(),
&wd_diff,
)?,
ownership,
order: 0,
};
let branch_writer = branch::Writer::new(gb_repository);
branch_writer.write(&branch)?;
}
}

View File

@ -565,14 +565,8 @@ impl ControllerInner {
)
.context("failed to open gitbutler repository")?;
let target = super::set_base_branch(
&gb_repository,
&project_repository,
user.as_ref(),
target_branch,
)?;
Ok(target)
super::set_base_branch(&gb_repository, &project_repository, target_branch)
.map_err(Into::into)
}
pub async fn merge_virtual_branch_upstream(

View File

@ -70,6 +70,11 @@ impl TestProject {
self.local_repository.workdir().unwrap()
}
pub fn push_branch(&self, branch: &git::LocalRefname) {
let mut origin = self.local_repository.find_remote("origin").unwrap();
origin.push(&[&format!("{branch}:{branch}")], None).unwrap();
}
pub fn push(&self) {
let mut origin = self.local_repository.find_remote("origin").unwrap();
origin
@ -161,8 +166,36 @@ impl TestProject {
self.local_repository.find_commit(oid)
}
pub fn checkout(&self, branch: git::LocalRefname) {
let branch: git::Refname = branch.into();
let tree = match self.local_repository.find_branch(&branch) {
Ok(branch) => branch.peel_to_tree(),
Err(git::Error::NotFound(_)) => {
let head_commit = self
.local_repository
.head()
.unwrap()
.peel_to_commit()
.unwrap();
self.local_repository
.reference(&branch, head_commit.id(), false, "new branch")
.unwrap();
head_commit.tree()
}
Err(error) => Err(error),
}
.unwrap();
self.local_repository.set_head(&branch).unwrap();
self.local_repository
.checkout_tree(&tree)
.force()
.checkout()
.unwrap();
}
/// takes all changes in the working directory and commits them into local
pub fn commit_all(&self, message: &str) -> git::Oid {
let head = self.local_repository.head().unwrap();
let mut index = self.local_repository.index().expect("failed to get index");
index
.add_all(["."], git2::IndexAddOption::DEFAULT, None)
@ -172,7 +205,7 @@ impl TestProject {
let signature = git::Signature::now("test", "test@email.com").unwrap();
self.local_repository
.commit(
Some(&"refs/heads/master".parse().unwrap()),
head.name().as_ref(),
&signature,
&signature,
message,

View File

@ -3440,6 +3440,140 @@ mod init {
}
}
#[tokio::test]
async fn dirty_non_target() {
// a situation when you initialize project while being on the local verison of the master
// that has uncommited changes.
let Test {
repository,
project_id,
controller,
..
} = Test::default();
repository.checkout("refs/heads/some-feature".parse().unwrap());
fs::write(repository.path().join("file.txt"), "content").unwrap();
controller
.set_base_branch(&project_id, &"refs/remotes/origin/master".parse().unwrap())
.await
.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);
assert!(branches[0].upstream.is_none());
assert_eq!(branches[0].name, "some-feature");
}
#[tokio::test]
async fn dirty_target() {
// a situation when you initialize project while being on the local verison of the master
// that has uncommited changes.
let Test {
repository,
project_id,
controller,
..
} = Test::default();
fs::write(repository.path().join("file.txt"), "content").unwrap();
controller
.set_base_branch(&project_id, &"refs/remotes/origin/master".parse().unwrap())
.await
.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);
assert!(branches[0].upstream.is_none());
assert_eq!(branches[0].name, "master");
}
#[tokio::test]
async fn commit_on_non_target_local() {
let Test {
repository,
project_id,
controller,
..
} = Test::default();
repository.checkout("refs/heads/some-feature".parse().unwrap());
fs::write(repository.path().join("file.txt"), "content").unwrap();
repository.commit_all("commit on target");
controller
.set_base_branch(&project_id, &"refs/remotes/origin/master".parse().unwrap())
.await
.unwrap();
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
dbg!(&branches);
assert_eq!(branches.len(), 1);
assert!(branches[0].files.is_empty());
assert_eq!(branches[0].commits.len(), 1);
assert!(branches[0].upstream.is_none());
assert_eq!(branches[0].name, "some-feature");
}
#[tokio::test]
async fn commit_on_non_target_remote() {
let Test {
repository,
project_id,
controller,
..
} = Test::default();
repository.checkout("refs/heads/some-feature".parse().unwrap());
fs::write(repository.path().join("file.txt"), "content").unwrap();
repository.commit_all("commit on target");
repository.push_branch(&"refs/heads/some-feature".parse().unwrap());
controller
.set_base_branch(&project_id, &"refs/remotes/origin/master".parse().unwrap())
.await
.unwrap();
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
dbg!(&branches);
assert_eq!(branches.len(), 1);
assert!(branches[0].files.is_empty());
assert_eq!(branches[0].commits.len(), 1);
assert!(branches[0].upstream.is_some());
assert_eq!(branches[0].name, "some-feature");
}
#[tokio::test]
async fn commit_on_target() {
let Test {
repository,
project_id,
controller,
..
} = Test::default();
fs::write(repository.path().join("file.txt"), "content").unwrap();
repository.commit_all("commit on target");
controller
.set_base_branch(&project_id, &"refs/remotes/origin/master".parse().unwrap())
.await
.unwrap();
let branches = controller.list_virtual_branches(&project_id).await.unwrap();
assert_eq!(branches.len(), 1);
assert!(branches[0].files.is_empty());
assert_eq!(branches[0].commits.len(), 1);
assert!(branches[0].upstream.is_none());
assert_eq!(branches[0].name, "master");
}
#[tokio::test]
async fn submodule() {
let Test {
@ -3467,28 +3601,6 @@ mod init {
assert_eq!(branches[0].files.len(), 1);
assert_eq!(branches[0].files[0].hunks.len(), 1);
}
#[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, &"refs/remotes/origin/master".parse().unwrap())
.await
.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);
}
}
mod squash {