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( set_base_branch(
&app.gb_repository(), &app.gb_repository(),
&app.project_repository(), &app.project_repository(),
app.user(),
&items[index].branch().parse()?, &items[index].branch().parse()?,
) )
.context("failed to set target branch")?; .context("failed to set target branch")?;

View File

@ -5,14 +5,18 @@ use serde::Serialize;
use crate::{ use crate::{
gb_repository, gb_repository,
git::{self, diff}, git::{
self,
diff::{self, Options},
},
keys, keys,
project_repository::{self, LogUntil}, project_repository::{self, LogUntil},
reader, sessions, users, reader, sessions, users,
virtual_branches::branch::Ownership,
}; };
use super::{ use super::{
branch, delete_branch, branch,
errors::{self, CreateVirtualBranchFromBranchError}, errors::{self, CreateVirtualBranchFromBranchError},
integration::GITBUTLER_INTEGRATION_REFERENCE, integration::GITBUTLER_INTEGRATION_REFERENCE,
iterator, target, BranchId, RemoteCommit, iterator, target, BranchId, RemoteCommit,
@ -51,30 +55,29 @@ pub fn get_base_branch_data(
pub fn set_base_branch( pub fn set_base_branch(
gb_repository: &gb_repository::Repository, gb_repository: &gb_repository::Repository,
project_repository: &project_repository::Repository, project_repository: &project_repository::Repository,
user: Option<&users::User>, target_branch_ref: &git::RemoteRefname,
target_branch: &git::RemoteRefname,
) -> Result<super::BaseBranch, errors::SetBaseBranchError> { ) -> Result<super::BaseBranch, errors::SetBaseBranchError> {
let repo = &project_repository.git_repository; let repo = &project_repository.git_repository;
// lookup a branch by name // 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), Ok(branch) => Ok(branch),
Err(git::Error::NotFound(_)) => Err(errors::SetBaseBranchError::BranchNotFound( Err(git::Error::NotFound(_)) => Err(errors::SetBaseBranchError::BranchNotFound(
target_branch.clone(), target_branch_ref.clone(),
)), )),
Err(error) => Err(errors::SetBaseBranchError::Other(error.into())), Err(error) => Err(errors::SetBaseBranchError::Other(error.into())),
}?; }?;
let remote_name = repo let remote_name = repo
.branch_remote_name(branch.refname().unwrap()) .branch_remote_name(target_branch.refname().unwrap())
.context(format!( .context(format!(
"failed to get remote name for branch {}", "failed to get remote name for branch {}",
branch.name().unwrap() target_branch.name().unwrap()
))?; ))?;
let remote = repo.find_remote(&remote_name).context(format!( let remote = repo.find_remote(&remote_name).context(format!(
"failed to find remote {} for branch {}", "failed to find remote {} for branch {}",
remote_name, remote_name,
branch.name().unwrap() target_branch.name().unwrap()
))?; ))?;
let remote_url = remote let remote_url = remote
.url() .url()
@ -84,34 +87,30 @@ pub fn set_base_branch(
))? ))?
.unwrap(); .unwrap();
// get a list of currently active virtual branches let target_branch_head = target_branch.peel_to_commit().context(format!(
// 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!(
"failed to peel branch {} to commit", "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_ref = repo.head().context("Failed to get HEAD reference")?;
let head_name: git::Refname = head_ref let head_name: git::Refname = head_ref
.name() .name()
.context("Failed to get HEAD reference name")?; .context("Failed to get HEAD reference name")?;
let head_oid = head_ref let head_commit = head_ref
.peel_to_commit() .peel_to_commit()
.context("Failed to peel HEAD reference to commit")? .context("Failed to peel HEAD reference to commit")?;
.id();
if head_oid != commit_oid { // calculate the commit as the merge-base between HEAD in project_repository and this target commit
// calculate the commit as the merge-base between HEAD in project_repository and this target commit let commit_oid = repo
commit_oid = repo.merge_base(head_oid, commit_oid).context(format!( .merge_base(head_commit.id(), target_branch_head.id())
.context(format!(
"Failed to calculate merge base between {} and {}", "Failed to calculate merge base between {} and {}",
head_oid, commit_oid head_commit.id(),
target_branch_head.id()
))?; ))?;
}
let target = target::Target { let target = target::Target {
branch: target_branch.clone(), branch: target_branch_ref.clone(),
remote_url: remote_url.to_string(), remote_url: remote_url.to_string(),
sha: commit_oid, sha: commit_oid,
}; };
@ -119,38 +118,83 @@ pub fn set_base_branch(
let target_writer = target::Writer::new(gb_repository); let target_writer = target::Writer::new(gb_repository);
target_writer.write_default(&target)?; target_writer.write_default(&target)?;
let current_session = gb_repository if !head_name
.get_or_create_current_session() .to_string()
.context("failed to get current session")?; .eq(&GITBUTLER_INTEGRATION_REFERENCE.to_string())
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())
{ {
let branch = create_virtual_branch_from_branch( // if there are any commits on the head branch or uncommitted changes in the working directory, we need to
gb_repository, // put them into a virtual branch
project_repository,
&head_name, let wd_diff = diff::workdir(repo, &head_commit.id(), &Options::default())?;
Some(true),
user, if !wd_diff.is_empty() || head_commit.id() != commit_oid {
) let hunks_by_filepath =
.context("failed to create virtual branch")?; super::virtual_hunks_by_filepath(&project_repository.project().path, &wd_diff);
if branch.ownership.is_empty() && branch.head == target.sha {
delete_branch(gb_repository, project_repository, &branch.id) // assign ownership to the branch
.context("failed to delete 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")?; .context("failed to open gitbutler repository")?;
let target = super::set_base_branch( super::set_base_branch(&gb_repository, &project_repository, target_branch)
&gb_repository, .map_err(Into::into)
&project_repository,
user.as_ref(),
target_branch,
)?;
Ok(target)
} }
pub async fn merge_virtual_branch_upstream( pub async fn merge_virtual_branch_upstream(

View File

@ -70,6 +70,11 @@ impl TestProject {
self.local_repository.workdir().unwrap() 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) { pub fn push(&self) {
let mut origin = self.local_repository.find_remote("origin").unwrap(); let mut origin = self.local_repository.find_remote("origin").unwrap();
origin origin
@ -161,8 +166,36 @@ impl TestProject {
self.local_repository.find_commit(oid) 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 /// takes all changes in the working directory and commits them into local
pub fn commit_all(&self, message: &str) -> git::Oid { 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"); let mut index = self.local_repository.index().expect("failed to get index");
index index
.add_all(["."], git2::IndexAddOption::DEFAULT, None) .add_all(["."], git2::IndexAddOption::DEFAULT, None)
@ -172,7 +205,7 @@ impl TestProject {
let signature = git::Signature::now("test", "test@email.com").unwrap(); let signature = git::Signature::now("test", "test@email.com").unwrap();
self.local_repository self.local_repository
.commit( .commit(
Some(&"refs/heads/master".parse().unwrap()), head.name().as_ref(),
&signature, &signature,
&signature, &signature,
message, 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] #[tokio::test]
async fn submodule() { async fn submodule() {
let Test { let Test {
@ -3467,28 +3601,6 @@ mod init {
assert_eq!(branches[0].files.len(), 1); assert_eq!(branches[0].files.len(), 1);
assert_eq!(branches[0].files[0].hunks.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 { mod squash {