mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2025-01-04 07:25:44 +03:00
test more init test cases
This commit is contained in:
parent
ef9aa747a4
commit
65fd1c9ac3
@ -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")?;
|
||||||
|
@ -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, ¤t_session)
|
|
||||||
.context("failed to open current session for reading")?;
|
|
||||||
|
|
||||||
let virtual_branches = iterator::BranchIterator::new(¤t_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)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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(
|
||||||
|
@ -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,
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user