mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-23 01:22:12 +03:00
Merge status (#554)
* show merge status of virtual branches * add merge conflict list and tests and fix uncommitted work
This commit is contained in:
parent
c090837b17
commit
8cc1d7b3f5
@ -54,12 +54,15 @@ impl TryFrom<&dyn crate::reader::Reader> for Branch {
|
|||||||
format!("meta/name: {}", e),
|
format!("meta/name: {}", e),
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
let applied = reader.read_bool("meta/applied").map_err(|e| {
|
let applied = reader
|
||||||
crate::reader::Error::IOError(std::io::Error::new(
|
.read_bool("meta/applied")
|
||||||
std::io::ErrorKind::Other,
|
.map_err(|e| {
|
||||||
format!("meta/applied: {}", e),
|
crate::reader::Error::IOError(std::io::Error::new(
|
||||||
))
|
std::io::ErrorKind::Other,
|
||||||
})?;
|
format!("meta/applied: {}", e),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.or(Ok(false))?;
|
||||||
|
|
||||||
let order = match reader.read_usize("meta/order") {
|
let order = match reader.read_usize("meta/order") {
|
||||||
Ok(order) => Ok(order),
|
Ok(order) => Ok(order),
|
||||||
|
@ -26,6 +26,8 @@ pub struct VirtualBranch {
|
|||||||
pub active: bool,
|
pub active: bool,
|
||||||
pub files: Vec<VirtualBranchFile>,
|
pub files: Vec<VirtualBranchFile>,
|
||||||
pub commits: Vec<VirtualBranchCommit>,
|
pub commits: Vec<VirtualBranchCommit>,
|
||||||
|
pub mergeable: bool,
|
||||||
|
pub merge_conflicts: Vec<String>,
|
||||||
pub order: usize,
|
pub order: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,6 +75,8 @@ pub struct RemoteBranch {
|
|||||||
behind: u32,
|
behind: u32,
|
||||||
upstream: String,
|
upstream: String,
|
||||||
authors: Vec<String>,
|
authors: Vec<String>,
|
||||||
|
mergeable: bool,
|
||||||
|
merge_conflicts: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_branch(
|
pub fn apply_branch(
|
||||||
@ -88,11 +92,7 @@ pub fn apply_branch(
|
|||||||
|
|
||||||
let repo = &project_repository.git_repository;
|
let repo = &project_repository.git_repository;
|
||||||
|
|
||||||
let mut index = repo.index()?;
|
let wd_tree = get_wd_tree(repo)?;
|
||||||
index.add_all(["*"], git2::IndexAddOption::DEFAULT, None)?;
|
|
||||||
let tree_id = index.write_tree().unwrap();
|
|
||||||
// get tree object from our current working directory state
|
|
||||||
let wd_tree = repo.find_tree(tree_id).unwrap();
|
|
||||||
|
|
||||||
let target_reader = target::Reader::new(¤t_session_reader);
|
let target_reader = target::Reader::new(¤t_session_reader);
|
||||||
let default_target = match target_reader.read_default() {
|
let default_target = match target_reader.read_default() {
|
||||||
@ -247,15 +247,49 @@ pub fn remote_branches(
|
|||||||
}
|
}
|
||||||
.context("failed to read default target")?;
|
.context("failed to read default target")?;
|
||||||
|
|
||||||
let main_oid = default_target.sha;
|
|
||||||
|
|
||||||
let current_time = time::SystemTime::now();
|
let current_time = time::SystemTime::now();
|
||||||
let too_old = time::Duration::from_secs(86_400 * 90); // 90 days (3 months) is too old
|
let too_old = time::Duration::from_secs(86_400 * 90); // 90 days (3 months) is too old
|
||||||
|
|
||||||
let repo = &project_repository.git_repository;
|
let repo = &project_repository.git_repository;
|
||||||
|
|
||||||
|
let main_oid = default_target.sha;
|
||||||
|
let target_commit = repo.find_commit(main_oid).ok().unwrap();
|
||||||
|
|
||||||
|
let wd_tree = get_wd_tree(&repo)?;
|
||||||
|
|
||||||
let mut branches: Vec<RemoteBranch> = Vec::new();
|
let mut branches: Vec<RemoteBranch> = Vec::new();
|
||||||
|
let mut most_recent_branches: Vec<(git2::Branch, u64)> = Vec::new();
|
||||||
|
|
||||||
for branch in repo.branches(Some(git2::BranchType::Remote))? {
|
for branch in repo.branches(Some(git2::BranchType::Remote))? {
|
||||||
let (branch, _) = branch?;
|
let (branch, _) = branch?;
|
||||||
|
match branch.get().target() {
|
||||||
|
Some(branch_oid) => {
|
||||||
|
// get the branch ref
|
||||||
|
let branch_commit = repo.find_commit(branch_oid).ok().unwrap();
|
||||||
|
let branch_time = branch_commit.time();
|
||||||
|
let seconds = branch_time.seconds().try_into().unwrap();
|
||||||
|
let branch_time = time::UNIX_EPOCH + time::Duration::from_secs(seconds);
|
||||||
|
let duration = current_time.duration_since(branch_time).unwrap();
|
||||||
|
if duration > too_old {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
most_recent_branches.push((branch, seconds));
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// take the most recent 20 branches
|
||||||
|
most_recent_branches.sort_by(|a, b| b.1.cmp(&a.1)); // Sort by timestamp in descending order.
|
||||||
|
let sorted_branches: Vec<git2::Branch> = most_recent_branches
|
||||||
|
.into_iter()
|
||||||
|
.map(|(branch, _)| branch)
|
||||||
|
.collect();
|
||||||
|
let top_branches = sorted_branches.into_iter().take(20).collect::<Vec<_>>(); // Take the first 20 entries.
|
||||||
|
|
||||||
|
for branch in &top_branches {
|
||||||
let branch_name = branch.get().name().unwrap();
|
let branch_name = branch.get().name().unwrap();
|
||||||
let upstream_branch = branch.upstream();
|
let upstream_branch = branch.upstream();
|
||||||
match branch.get().target() {
|
match branch.get().target() {
|
||||||
@ -263,16 +297,6 @@ pub fn remote_branches(
|
|||||||
// get the branch ref
|
// get the branch ref
|
||||||
let branch_commit = repo.find_commit(branch_oid).ok().unwrap();
|
let branch_commit = repo.find_commit(branch_oid).ok().unwrap();
|
||||||
|
|
||||||
// figure out if the last commit on this branch is too old to consider
|
|
||||||
let branch_time = branch_commit.time();
|
|
||||||
// convert git::Time to SystemTime
|
|
||||||
let branch_time = time::UNIX_EPOCH
|
|
||||||
+ time::Duration::from_secs(branch_time.seconds().try_into().unwrap());
|
|
||||||
let duration = current_time.duration_since(branch_time).unwrap();
|
|
||||||
if duration > too_old {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut revwalk = repo.revwalk().unwrap();
|
let mut revwalk = repo.revwalk().unwrap();
|
||||||
revwalk.set_sorting(git2::Sort::TOPOLOGICAL).unwrap();
|
revwalk.set_sorting(git2::Sort::TOPOLOGICAL).unwrap();
|
||||||
revwalk.push(main_oid).unwrap();
|
revwalk.push(main_oid).unwrap();
|
||||||
@ -327,6 +351,12 @@ pub fn remote_branches(
|
|||||||
Err(_) => "".to_string(),
|
Err(_) => "".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let base_tree = find_base_tree(repo, &branch_commit, &target_commit)?;
|
||||||
|
let branch_tree = branch_commit.tree()?;
|
||||||
|
let (mergeable, merge_conflicts) =
|
||||||
|
check_mergeable(&repo, &base_tree, &branch_tree, &wd_tree)?;
|
||||||
|
println!("mergeable: {} {}", branch_name, mergeable);
|
||||||
|
|
||||||
branches.push(RemoteBranch {
|
branches.push(RemoteBranch {
|
||||||
sha: branch_oid.to_string(),
|
sha: branch_oid.to_string(),
|
||||||
branch: branch_name.to_string(),
|
branch: branch_name.to_string(),
|
||||||
@ -338,6 +368,8 @@ pub fn remote_branches(
|
|||||||
behind: count_behind,
|
behind: count_behind,
|
||||||
upstream: upstream_branch_name,
|
upstream: upstream_branch_name,
|
||||||
authors: authors.into_iter().collect(),
|
authors: authors.into_iter().collect(),
|
||||||
|
mergeable,
|
||||||
|
merge_conflicts,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
@ -353,6 +385,8 @@ pub fn remote_branches(
|
|||||||
behind: 0,
|
behind: 0,
|
||||||
upstream: "".to_string(),
|
upstream: "".to_string(),
|
||||||
authors: vec![],
|
authors: vec![],
|
||||||
|
mergeable: false,
|
||||||
|
merge_conflicts: vec![],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -360,6 +394,65 @@ pub fn remote_branches(
|
|||||||
Ok(branches)
|
Ok(branches)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_wd_tree(repo: &git2::Repository) -> Result<git2::Tree> {
|
||||||
|
let mut index = repo.index()?;
|
||||||
|
index.add_all(&["*"], git2::IndexAddOption::DEFAULT, None)?;
|
||||||
|
let oid = index.write_tree()?;
|
||||||
|
let tree = repo.find_tree(oid)?;
|
||||||
|
Ok(tree)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_base_tree<'a>(
|
||||||
|
repo: &'a git2::Repository,
|
||||||
|
branch_commit: &'a git2::Commit<'a>,
|
||||||
|
target_commit: &'a git2::Commit<'a>,
|
||||||
|
) -> Result<git2::Tree<'a>> {
|
||||||
|
// find merge base between target_commit and branch_commit
|
||||||
|
let merge_base = repo
|
||||||
|
.merge_base(target_commit.id(), branch_commit.id())
|
||||||
|
.context("failed to find merge base")?;
|
||||||
|
// turn oid into a commit
|
||||||
|
let merge_base_commit = repo
|
||||||
|
.find_commit(merge_base)
|
||||||
|
.context("failed to find merge base commit")?;
|
||||||
|
let base_tree = merge_base_commit
|
||||||
|
.tree()
|
||||||
|
.context("failed to get base tree object")?;
|
||||||
|
Ok(base_tree.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_mergeable(
|
||||||
|
repo: &git2::Repository,
|
||||||
|
base_tree: &git2::Tree,
|
||||||
|
branch_tree: &git2::Tree,
|
||||||
|
wd_tree: &git2::Tree,
|
||||||
|
) -> Result<(bool, Vec<String>)> {
|
||||||
|
let mut merge_conflicts = Vec::new();
|
||||||
|
let merge_options = git2::MergeOptions::new();
|
||||||
|
let merge_index = repo
|
||||||
|
.merge_trees(&base_tree, &wd_tree, &branch_tree, Some(&merge_options))
|
||||||
|
.unwrap();
|
||||||
|
let mergeable = !merge_index.has_conflicts();
|
||||||
|
if merge_index.has_conflicts() {
|
||||||
|
let conflicts = merge_index.conflicts()?;
|
||||||
|
for conflict in conflicts {
|
||||||
|
if let Ok(path) = conflict {
|
||||||
|
if let Some(their) = path.their {
|
||||||
|
let path = std::str::from_utf8(&their.path)?.to_string();
|
||||||
|
merge_conflicts.push(path);
|
||||||
|
} else if let Some(ours) = path.our {
|
||||||
|
let path = std::str::from_utf8(&ours.path)?.to_string();
|
||||||
|
merge_conflicts.push(path);
|
||||||
|
} else if let Some(anc) = path.ancestor {
|
||||||
|
let path = std::str::from_utf8(&anc.path)?.to_string();
|
||||||
|
merge_conflicts.push(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok((mergeable, merge_conflicts))
|
||||||
|
}
|
||||||
|
|
||||||
// just for debugging for now
|
// just for debugging for now
|
||||||
fn _print_diff(diff: &git2::Diff) -> Result<()> {
|
fn _print_diff(diff: &git2::Diff) -> Result<()> {
|
||||||
diff.print(git2::DiffFormat::Patch, |_delta, _hunk, line| {
|
diff.print(git2::DiffFormat::Patch, |_delta, _hunk, line| {
|
||||||
@ -402,6 +495,9 @@ pub fn list_virtual_branches(
|
|||||||
|
|
||||||
let statuses = get_status_by_branch(gb_repository, project_repository)?;
|
let statuses = get_status_by_branch(gb_repository, project_repository)?;
|
||||||
|
|
||||||
|
let repo = &project_repository.git_repository;
|
||||||
|
let wd_tree = get_wd_tree(repo)?;
|
||||||
|
|
||||||
for branch in &virtual_branches {
|
for branch in &virtual_branches {
|
||||||
let branch_statuses = statuses.clone();
|
let branch_statuses = statuses.clone();
|
||||||
let mut files: Vec<VirtualBranchFile> = vec![];
|
let mut files: Vec<VirtualBranchFile> = vec![];
|
||||||
@ -473,6 +569,24 @@ pub fn list_virtual_branches(
|
|||||||
commits.push(commit);
|
commits.push(commit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut mergeable = true;
|
||||||
|
let mut merge_conflicts = vec![];
|
||||||
|
if !branch.applied {
|
||||||
|
let target_commit = repo
|
||||||
|
.find_commit(default_target.sha)
|
||||||
|
.context("failed to find target commit")?;
|
||||||
|
let branch_commit = repo
|
||||||
|
.find_commit(branch.head)
|
||||||
|
.context("failed to find branch commit")?;
|
||||||
|
let base_tree = find_base_tree(repo, &branch_commit, &target_commit)?;
|
||||||
|
// determine if this tree is mergeable
|
||||||
|
let branch_tree = repo
|
||||||
|
.find_tree(branch.tree)
|
||||||
|
.context("failed to find branch tree")?;
|
||||||
|
(mergeable, merge_conflicts) =
|
||||||
|
check_mergeable(&repo, &base_tree, &branch_tree, &wd_tree)?;
|
||||||
|
}
|
||||||
|
|
||||||
let branch = VirtualBranch {
|
let branch = VirtualBranch {
|
||||||
id: branch.id.to_string(),
|
id: branch.id.to_string(),
|
||||||
name: branch.name.to_string(),
|
name: branch.name.to_string(),
|
||||||
@ -480,6 +594,8 @@ pub fn list_virtual_branches(
|
|||||||
files: vfiles,
|
files: vfiles,
|
||||||
order: branch.order,
|
order: branch.order,
|
||||||
commits,
|
commits,
|
||||||
|
mergeable,
|
||||||
|
merge_conflicts,
|
||||||
};
|
};
|
||||||
branches.push(branch);
|
branches.push(branch);
|
||||||
}
|
}
|
||||||
@ -1013,11 +1129,7 @@ pub fn update_branch_target(
|
|||||||
|
|
||||||
// ok, target has changed, so now we need to merge it into our current work and update our branches
|
// ok, target has changed, so now we need to merge it into our current work and update our branches
|
||||||
// first, pull the current state of the working directory into the index
|
// first, pull the current state of the working directory into the index
|
||||||
let mut index = repo.index()?;
|
let wd_tree = get_wd_tree(repo)?;
|
||||||
index.add_all(["*"], git2::IndexAddOption::DEFAULT, None)?;
|
|
||||||
let tree_id = index.write_tree().unwrap();
|
|
||||||
// get tree object from our current working directory state
|
|
||||||
let wd_tree = repo.find_tree(tree_id).unwrap();
|
|
||||||
|
|
||||||
let current_session = gb_repository
|
let current_session = gb_repository
|
||||||
.get_or_create_current_session()
|
.get_or_create_current_session()
|
||||||
@ -2519,4 +2631,175 @@ mod tests {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_detect_mergeable_branch() -> Result<()> {
|
||||||
|
let repository = test_repository()?;
|
||||||
|
let project = projects::Project::try_from(&repository)?;
|
||||||
|
let gb_repo_path = tempdir()?.path().to_str().unwrap().to_string();
|
||||||
|
let storage = storage::Storage::from_path(tempdir()?.path());
|
||||||
|
let user_store = users::Storage::new(storage.clone());
|
||||||
|
let project_store = projects::Storage::new(storage);
|
||||||
|
project_store.add_project(&project)?;
|
||||||
|
let gb_repo = gb_repository::Repository::open(
|
||||||
|
gb_repo_path,
|
||||||
|
project.id.clone(),
|
||||||
|
project_store,
|
||||||
|
user_store,
|
||||||
|
)?;
|
||||||
|
let project_repository = project_repository::Repository::open(&project)?;
|
||||||
|
|
||||||
|
// create a commit and set the target
|
||||||
|
let file_path = std::path::Path::new("test.txt");
|
||||||
|
std::fs::write(
|
||||||
|
std::path::Path::new(&project.path).join(file_path),
|
||||||
|
"line1\nline2\nline3\nline4\n",
|
||||||
|
)?;
|
||||||
|
commit_all(&repository)?;
|
||||||
|
|
||||||
|
target::Writer::new(&gb_repo).write_default(&target::Target {
|
||||||
|
name: "origin/master".to_string(),
|
||||||
|
remote: "origin".to_string(),
|
||||||
|
sha: repository.head().unwrap().target().unwrap(),
|
||||||
|
behind: 0,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
std::fs::write(
|
||||||
|
std::path::Path::new(&project.path).join(file_path),
|
||||||
|
"line1\nline2\nline3\nline4\nbranch1\n",
|
||||||
|
)?;
|
||||||
|
let file_path4 = std::path::Path::new("test4.txt");
|
||||||
|
std::fs::write(
|
||||||
|
std::path::Path::new(&project.path).join(file_path4),
|
||||||
|
"line5\nline6\n",
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let branch1_id = create_virtual_branch(&gb_repo, "test_branch")
|
||||||
|
.expect("failed to create virtual branch");
|
||||||
|
let branch2_id = create_virtual_branch(&gb_repo, "test_branch2")
|
||||||
|
.expect("failed to create virtual branch");
|
||||||
|
|
||||||
|
let current_session = gb_repo.get_or_create_current_session()?;
|
||||||
|
let current_session_reader = sessions::Reader::open(&gb_repo, ¤t_session)?;
|
||||||
|
let branch_reader = branch::Reader::new(¤t_session_reader);
|
||||||
|
let branch_writer = branch::Writer::new(&gb_repo);
|
||||||
|
let branch1 = branch_reader.read(&branch1_id)?;
|
||||||
|
branch_writer.write(&Branch {
|
||||||
|
ownership: Ownership {
|
||||||
|
files: vec!["test.txt".try_into()?, "test4.txt".try_into()?],
|
||||||
|
},
|
||||||
|
..branch1
|
||||||
|
})?;
|
||||||
|
|
||||||
|
move_files(&gb_repo, &branch2_id, &vec!["test4.txt".try_into()?])
|
||||||
|
.expect("failed to move hunks");
|
||||||
|
|
||||||
|
// unapply both branches and create some conflicting ones
|
||||||
|
unapply_branch(&gb_repo, &project_repository, &branch1_id)?;
|
||||||
|
unapply_branch(&gb_repo, &project_repository, &branch2_id)?;
|
||||||
|
|
||||||
|
// create an upstream remote conflicting commit
|
||||||
|
std::fs::write(
|
||||||
|
std::path::Path::new(&project.path).join(file_path),
|
||||||
|
"line1\nline2\nline3\nline4\nupstream\n",
|
||||||
|
)?;
|
||||||
|
commit_all(&repository)?;
|
||||||
|
let up_target = repository.head().unwrap().target().unwrap();
|
||||||
|
repository.reference(
|
||||||
|
"refs/remotes/origin/remote_branch",
|
||||||
|
up_target,
|
||||||
|
true,
|
||||||
|
"update target",
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// revert content and write a mergeable branch
|
||||||
|
std::fs::write(
|
||||||
|
std::path::Path::new(&project.path).join(file_path),
|
||||||
|
"line1\nline2\nline3\nline4\n",
|
||||||
|
)?;
|
||||||
|
let file_path3 = std::path::Path::new("test3.txt");
|
||||||
|
std::fs::write(
|
||||||
|
std::path::Path::new(&project.path).join(file_path3),
|
||||||
|
"file3\n",
|
||||||
|
)?;
|
||||||
|
commit_all(&repository)?;
|
||||||
|
let up_target = repository.head().unwrap().target().unwrap();
|
||||||
|
repository.reference(
|
||||||
|
"refs/remotes/origin/remote_branch2",
|
||||||
|
up_target,
|
||||||
|
true,
|
||||||
|
"update target",
|
||||||
|
)?;
|
||||||
|
// remove file_path3
|
||||||
|
std::fs::remove_file(std::path::Path::new(&project.path).join(file_path3))?;
|
||||||
|
|
||||||
|
// create branches that conflict with our earlier branches
|
||||||
|
let branch3_id = create_virtual_branch(&gb_repo, "test_branch3")
|
||||||
|
.expect("failed to create virtual branch");
|
||||||
|
let branch4_id = create_virtual_branch(&gb_repo, "test_branch4")
|
||||||
|
.expect("failed to create virtual branch");
|
||||||
|
|
||||||
|
// branch3 conflicts with branch1 and remote_branch
|
||||||
|
std::fs::write(
|
||||||
|
std::path::Path::new(&project.path).join(file_path),
|
||||||
|
"line1\nline2\nline3\nline4\nbranch3\n",
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// branch4 conflicts with branch2
|
||||||
|
let file_path2 = std::path::Path::new("test2.txt");
|
||||||
|
std::fs::write(
|
||||||
|
std::path::Path::new(&project.path).join(file_path2),
|
||||||
|
"line1\nline2\nline3\nline4\nbranch4\n",
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let branch3 = branch_reader.read(&branch3_id)?;
|
||||||
|
branch_writer.write(&Branch {
|
||||||
|
ownership: Ownership {
|
||||||
|
files: vec!["test.txt".try_into()?],
|
||||||
|
},
|
||||||
|
..branch3
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let branch4 = branch_reader.read(&branch4_id)?;
|
||||||
|
branch_writer.write(&Branch {
|
||||||
|
ownership: Ownership {
|
||||||
|
files: vec!["test2.txt".try_into()?],
|
||||||
|
},
|
||||||
|
..branch4
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let branches = list_virtual_branches(&gb_repo, &project_repository)?;
|
||||||
|
assert_eq!(branches.len(), 4);
|
||||||
|
|
||||||
|
let branch1 = &branches.iter().find(|b| b.id == branch1_id).unwrap();
|
||||||
|
assert_eq!(branch1.active, false);
|
||||||
|
assert_eq!(branch1.mergeable, false);
|
||||||
|
assert_eq!(branch1.merge_conflicts.len(), 1);
|
||||||
|
assert_eq!(branch1.merge_conflicts.first().unwrap(), "test.txt");
|
||||||
|
|
||||||
|
let branch2 = &branches.iter().find(|b| b.id == branch2_id).unwrap();
|
||||||
|
assert_eq!(branch2.active, false);
|
||||||
|
assert_eq!(branch2.mergeable, true);
|
||||||
|
assert_eq!(branch2.merge_conflicts.len(), 0);
|
||||||
|
|
||||||
|
let remotes = remote_branches(&gb_repo, &project_repository)?;
|
||||||
|
let remote1 = &remotes
|
||||||
|
.iter()
|
||||||
|
.find(|b| b.branch == "refs/remotes/origin/remote_branch")
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(remote1.mergeable, false);
|
||||||
|
assert_eq!(remote1.ahead, 1);
|
||||||
|
assert_eq!(remote1.merge_conflicts.len(), 1);
|
||||||
|
assert_eq!(remote1.merge_conflicts.first().unwrap(), "test.txt");
|
||||||
|
|
||||||
|
let remote2 = &remotes
|
||||||
|
.iter()
|
||||||
|
.find(|b| b.branch == "refs/remotes/origin/remote_branch2")
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(remote2.mergeable, true);
|
||||||
|
assert_eq!(remote2.ahead, 2);
|
||||||
|
assert_eq!(remote2.merge_conflicts.len(), 0);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,13 +61,20 @@
|
|||||||
<div class="flex flex-col gap-y-2">
|
<div class="flex flex-col gap-y-2">
|
||||||
{#each branches as branch (branch.id)}
|
{#each branches as branch (branch.id)}
|
||||||
<div class="rounded-lg p-2" title={branch.name}>
|
<div class="rounded-lg p-2" title={branch.name}>
|
||||||
<Checkbox
|
<div class="flex flex-row justify-between">
|
||||||
on:change={() => toggleBranch(branch.id, branch.active)}
|
<div>
|
||||||
bind:checked={branch.active}
|
<Checkbox
|
||||||
/>
|
on:change={() => toggleBranch(branch.id, branch.active)}
|
||||||
<span class="ml-2 cursor-pointer">
|
bind:checked={branch.active}
|
||||||
{branch.name}
|
/>
|
||||||
</span>
|
<span class="ml-2 cursor-pointer">
|
||||||
|
{branch.name}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{#if !branch.active}
|
||||||
|
<div class={branch.mergeable ? 'text-green-500' : 'text-red-500'}>●</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
@ -80,6 +87,7 @@
|
|||||||
{branch.branch.replace('refs/remotes/', '')}
|
{branch.branch.replace('refs/remotes/', '')}
|
||||||
</div>
|
</div>
|
||||||
<div>{branch.ahead}/{branch.behind}</div>
|
<div>{branch.ahead}/{branch.behind}</div>
|
||||||
|
<div class={branch.mergeable ? 'text-green-500' : 'text-red-500'}>●</div>
|
||||||
</div>
|
</div>
|
||||||
{#if branch.lastCommitTs > 0}
|
{#if branch.lastCommitTs > 0}
|
||||||
<div class="flex flex-row justify-between">
|
<div class="flex flex-row justify-between">
|
||||||
|
@ -27,6 +27,8 @@ export class Branch extends DndItem {
|
|||||||
files!: File[];
|
files!: File[];
|
||||||
commits!: Commit[];
|
commits!: Commit[];
|
||||||
description!: string;
|
description!: string;
|
||||||
|
mergeable!: boolean;
|
||||||
|
mergeConflicts!: string[];
|
||||||
order!: number;
|
order!: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,6 +43,8 @@ export type BranchData = {
|
|||||||
behind: number;
|
behind: number;
|
||||||
upstream: string;
|
upstream: string;
|
||||||
authors: string[];
|
authors: string[];
|
||||||
|
mergeable: boolean;
|
||||||
|
mergeConflicts: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Commit {
|
export class Commit {
|
||||||
|
Loading…
Reference in New Issue
Block a user