mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-22 17:11:43 +03:00
detect changed hunks
This commit is contained in:
parent
0a45945738
commit
c30623f15a
@ -53,6 +53,16 @@ impl Ownership {
|
||||
return self.clone();
|
||||
}
|
||||
|
||||
if self.ranges.is_empty() {
|
||||
// full ownership + partial ownership = full ownership
|
||||
return self.clone();
|
||||
}
|
||||
|
||||
if another.ranges.is_empty() {
|
||||
// partial ownership + full ownership = full ownership
|
||||
return another.clone();
|
||||
}
|
||||
|
||||
let mut ranges = self.ranges.clone();
|
||||
ranges.extend(another.ranges.clone());
|
||||
|
||||
@ -272,6 +282,8 @@ mod ownership_tests {
|
||||
"file.txt:8-15,20-25",
|
||||
"file.txt:1-10,8-15,20-25",
|
||||
),
|
||||
("file.txt:1-10", "file.txt", "file.txt"),
|
||||
("file.txt", "file.txt:1-10", "file.txt"),
|
||||
("file.txt:1-10", "file.txt:10-15", "file.txt:1-10,10-15"),
|
||||
("file.txt:5-10", "file.txt:1-5", "file.txt:1-5,5-10"),
|
||||
("file.txt:1-10", "file.txt:1-10", "file.txt:1-10"),
|
||||
|
@ -4,7 +4,7 @@ pub mod target;
|
||||
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
path, time, vec,
|
||||
ops, path, time, vec,
|
||||
};
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
@ -13,7 +13,6 @@ use serde::Serialize;
|
||||
|
||||
pub use branch::Branch;
|
||||
pub use iterator::BranchIterator as Iterator;
|
||||
use tokio::sync::Semaphore;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{gb_repository, project_repository, reader, sessions};
|
||||
@ -442,7 +441,8 @@ pub fn move_files(
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
} else {
|
||||
let owner = find_owner(&virtual_branches, ownership)
|
||||
let owner = explicit_owner(&virtual_branches, ownership)
|
||||
.or_else(|| implicit_owner(&virtual_branches, ownership))
|
||||
.context(format!("failed to find owner branch for {}", ownership))?
|
||||
.clone();
|
||||
vec![owner]
|
||||
@ -483,15 +483,72 @@ pub fn move_files(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn find_owner(stack: &[branch::Branch], needle: &branch::Ownership) -> Option<branch::Branch> {
|
||||
let explicitly_owned_by = stack.iter().find(|b| {
|
||||
b.ownership
|
||||
.iter()
|
||||
.filter(|o| !o.ranges.is_empty())
|
||||
.any(|o| o.contains(needle))
|
||||
});
|
||||
let implicitly_owned_by = stack.iter().find(|b| b.contains(needle));
|
||||
explicitly_owned_by.or(implicitly_owned_by).cloned()
|
||||
fn distance(a: &ops::RangeInclusive<usize>, b: &ops::RangeInclusive<usize>) -> usize {
|
||||
if a.start() > b.end() {
|
||||
a.start() - b.end()
|
||||
} else if b.start() > a.end() {
|
||||
b.start() - a.end()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fn ranges_intersect(
|
||||
one: &ops::RangeInclusive<usize>,
|
||||
another: &ops::RangeInclusive<usize>,
|
||||
) -> bool {
|
||||
one.contains(another.start())
|
||||
|| one.contains(another.end())
|
||||
|| another.contains(one.start())
|
||||
|| another.contains(one.end())
|
||||
}
|
||||
|
||||
fn ranges_touching(
|
||||
one: &ops::RangeInclusive<usize>,
|
||||
another: &ops::RangeInclusive<usize>,
|
||||
context: usize,
|
||||
) -> bool {
|
||||
distance(one, another) <= context || distance(another, one) <= context
|
||||
}
|
||||
|
||||
fn explicit_owner(stack: &[branch::Branch], needle: &branch::Ownership) -> Option<branch::Branch> {
|
||||
stack
|
||||
.iter()
|
||||
.find(|branch| {
|
||||
branch
|
||||
.ownership
|
||||
.iter()
|
||||
.filter(|ownership| !ownership.ranges.is_empty()) // only consider explicit ownership
|
||||
.any(|ownership| ownership.contains(needle))
|
||||
})
|
||||
.cloned()
|
||||
}
|
||||
|
||||
fn owned_by_proximity(
|
||||
stack: &[branch::Branch],
|
||||
needle: &branch::Ownership,
|
||||
) -> Option<branch::Branch> {
|
||||
stack
|
||||
.iter()
|
||||
.find(|branch| {
|
||||
branch
|
||||
.ownership
|
||||
.iter()
|
||||
.filter(|ownership| !ownership.ranges.is_empty()) // only consider explicit ownership
|
||||
.any(|ownership| {
|
||||
ownership.ranges.iter().any(|range| {
|
||||
needle
|
||||
.ranges
|
||||
.iter()
|
||||
.any(|r| ranges_touching(r, range, 6) || ranges_intersect(r, range))
|
||||
})
|
||||
})
|
||||
})
|
||||
.cloned()
|
||||
}
|
||||
|
||||
fn implicit_owner(stack: &[branch::Branch], needle: &branch::Ownership) -> Option<branch::Branch> {
|
||||
stack.iter().find(|branch| branch.contains(needle)).cloned()
|
||||
}
|
||||
|
||||
fn diff_to_hunks_by_filepath(
|
||||
@ -697,12 +754,10 @@ pub fn get_status_by_branch(
|
||||
for hunk in all_hunks {
|
||||
let hunk_ownership = Ownership::try_from(&hunk.id)?;
|
||||
|
||||
let owned_by = find_owner(&virtual_branches, &hunk_ownership);
|
||||
let owned_by = explicit_owner(&virtual_branches, &hunk_ownership)
|
||||
.or_else(|| implicit_owner(&virtual_branches, &hunk_ownership))
|
||||
.or_else(|| owned_by_proximity(&virtual_branches, &hunk_ownership));
|
||||
if let Some(branch) = owned_by {
|
||||
if hunk.file_path == "src-tauri/Cargo.lock" {
|
||||
println!("explicit branch: {:?}", branch);
|
||||
}
|
||||
|
||||
hunks_by_branch_id
|
||||
.entry(branch.id.clone())
|
||||
.or_default()
|
||||
@ -1317,6 +1372,75 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hunk_expantion() -> 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)?;
|
||||
|
||||
target::Writer::new(&gb_repo).write_default(&target::Target {
|
||||
name: "origin".to_string(),
|
||||
remote: "origin".to_string(),
|
||||
sha: repository.head().unwrap().target().unwrap(),
|
||||
behind: 0,
|
||||
})?;
|
||||
|
||||
let file_path = std::path::Path::new("test.txt");
|
||||
std::fs::write(
|
||||
std::path::Path::new(&project.path).join(file_path),
|
||||
"line1\nline2\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");
|
||||
branch::Writer::new(&gb_repo).write_selected(&Some(branch1_id.clone()))?;
|
||||
|
||||
let statuses =
|
||||
get_status_by_branch(&gb_repo, &project_repository).expect("failed to get status");
|
||||
let files_by_branch_id = statuses
|
||||
.iter()
|
||||
.map(|(branch, files)| (branch.id.clone(), files))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
assert_eq!(files_by_branch_id.len(), 2);
|
||||
assert_eq!(files_by_branch_id[&branch1_id].len(), 1);
|
||||
assert_eq!(files_by_branch_id[&branch2_id].len(), 0);
|
||||
|
||||
// even though selected branch has changed
|
||||
branch::Writer::new(&gb_repo).write_selected(&Some(branch2_id.clone()))?;
|
||||
// a slightly different hunk should still go to the same branch
|
||||
std::fs::write(
|
||||
std::path::Path::new(&project.path).join(file_path),
|
||||
"line1\nline2\nline3\n",
|
||||
)?;
|
||||
|
||||
let statuses =
|
||||
get_status_by_branch(&gb_repo, &project_repository).expect("failed to get status");
|
||||
let files_by_branch_id = statuses
|
||||
.iter()
|
||||
.map(|(branch, files)| (branch.id.clone(), files))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
assert_eq!(files_by_branch_id.len(), 2);
|
||||
assert_eq!(files_by_branch_id[&branch1_id].len(), 1);
|
||||
assert_eq!(files_by_branch_id[&branch2_id].len(), 0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_status_files_by_branch() -> Result<()> {
|
||||
let repository = test_repository()?;
|
||||
@ -1380,7 +1504,7 @@ mod tests {
|
||||
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\nline5\nline6\nline7\nline8\nline9\n",
|
||||
"line1\nline2\nline3\nline4\nline5\nline6\nline7\nline8\nline9\nline10\nline11\nline12\n",
|
||||
)?;
|
||||
commit_all(&repository)?;
|
||||
|
||||
@ -1406,7 +1530,7 @@ mod tests {
|
||||
|
||||
std::fs::write(
|
||||
std::path::Path::new(&project.path).join(file_path),
|
||||
"line0\nline1\nline2\nline3\nline4\nline5\nline6\nline7\nline8\nline9\nline10\n",
|
||||
"line0\nline1\nline2\nline3\nline4\nline5\nline6\nline7\nline8\nline9\nline10\nline11\nline12\nline13\n",
|
||||
)?;
|
||||
|
||||
let branch_writer = branch::Writer::new(&gb_repo);
|
||||
@ -1422,7 +1546,7 @@ mod tests {
|
||||
})?;
|
||||
let branch1 = branch_reader.read(&branch1_id)?;
|
||||
branch_writer.write(&branch::Branch {
|
||||
ownership: vec!["test.txt:8-12".try_into()?],
|
||||
ownership: vec!["test.txt:11-15".try_into()?],
|
||||
..branch1
|
||||
})?;
|
||||
|
||||
@ -1451,8 +1575,6 @@ mod tests {
|
||||
.map(|(branch, files)| (branch.id.clone(), files))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
println!("{:#?}", statuses);
|
||||
|
||||
assert_eq!(files_by_branch_id.len(), 2);
|
||||
assert_eq!(files_by_branch_id[&branch1_id].len(), 0);
|
||||
assert_eq!(files_by_branch_id[&branch2_id].len(), 1);
|
||||
@ -1462,7 +1584,7 @@ mod tests {
|
||||
assert_eq!(branch_reader.read(&branch1_id)?.ownership, vec![]);
|
||||
assert_eq!(
|
||||
branch_reader.read(&branch2_id)?.ownership,
|
||||
vec!["test.txt:1-5,8-12".try_into()?]
|
||||
vec!["test.txt".try_into()?]
|
||||
);
|
||||
|
||||
Ok(())
|
||||
@ -1643,7 +1765,7 @@ mod tests {
|
||||
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\nline5\nline6\nline7\nline8\nline9\n",
|
||||
"line1\nline2\nline3\nline4\nline5\nline6\nline7\nline8\nline9\nline10\nline11\nline12\nline13\n",
|
||||
)?;
|
||||
commit_all(&repository)?;
|
||||
|
||||
@ -1665,7 +1787,7 @@ mod tests {
|
||||
|
||||
std::fs::write(
|
||||
std::path::Path::new(&project.path).join(file_path),
|
||||
"line0\nline1\nline2\nline3\nline4\nline5\nline6\nline7\nline8\nline9\nline10\n",
|
||||
"line0\nline1\nline2\nline3\nline4\nline5\nline6\nline7\nline8\nline9\nline10\nline11\nline12\nline13\nline14\n",
|
||||
)?;
|
||||
|
||||
let branch1_id = create_virtual_branch(&gb_repo, "test_branch")
|
||||
@ -1698,6 +1820,8 @@ mod tests {
|
||||
.map(|(branch, files)| (branch.id.clone(), files))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
println!("{:#?}", statuses);
|
||||
|
||||
assert_eq!(files_by_branch_id.len(), 2);
|
||||
assert_eq!(files_by_branch_id[&branch1_id].len(), 1);
|
||||
assert_eq!(files_by_branch_id[&branch1_id][0].hunks.len(), 1);
|
||||
@ -1709,7 +1833,7 @@ mod tests {
|
||||
let branch_reader = branch::Reader::new(¤t_session_reader);
|
||||
assert_eq!(
|
||||
branch_reader.read(&branch1_id)?.ownership,
|
||||
vec!["test.txt:8-12".try_into()?]
|
||||
vec!["test.txt:12-16".try_into()?]
|
||||
);
|
||||
assert_eq!(
|
||||
branch_reader.read(&branch2_id)?.ownership,
|
||||
@ -1720,7 +1844,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_move_hunks_partial_implicity() -> Result<()> {
|
||||
fn test_move_hunks_partial_implicity_owned() -> Result<()> {
|
||||
let repository = test_repository()?;
|
||||
let project = projects::Project::try_from(&repository)?;
|
||||
let gb_repo_path = tempdir()?.path().to_str().unwrap().to_string();
|
||||
@ -1732,7 +1856,7 @@ mod tests {
|
||||
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\nline5\nline6\nline7\nline8\nline9\n",
|
||||
"line1\nline2\nline3\nline4\nline5\nline6\nline7\nline8\nline9\nline10\nline11\nline12\n",
|
||||
)?;
|
||||
commit_all(&repository)?;
|
||||
|
||||
@ -1754,7 +1878,7 @@ mod tests {
|
||||
|
||||
std::fs::write(
|
||||
std::path::Path::new(&project.path).join(file_path),
|
||||
"line0\nline1\nline2\nline3\nline4\nline5\nline6\nline7\nline8\nline9\nline10\n",
|
||||
"line0\nline1\nline2\nline3\nline4\nline5\nline6\nline7\nline8\nline9\nline10\nline11\nline12\nline13\n",
|
||||
)?;
|
||||
|
||||
let branch1_id = create_virtual_branch(&gb_repo, "test_branch")
|
||||
@ -1792,6 +1916,8 @@ mod tests {
|
||||
let statuses =
|
||||
get_status_by_branch(&gb_repo, &project_repository).expect("failed to get status");
|
||||
|
||||
println!("{:#?}", statuses);
|
||||
|
||||
let files_by_branch_id = statuses
|
||||
.iter()
|
||||
.map(|(branch, files)| (branch.id.clone(), files))
|
||||
|
@ -56,10 +56,7 @@ mod tests {
|
||||
|
||||
use crate::{
|
||||
gb_repository, projects, sessions, storage, users,
|
||||
virtual_branches::{
|
||||
branch,
|
||||
target::{self, writer::TargetWriter},
|
||||
},
|
||||
virtual_branches::{branch, target::writer::TargetWriter},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
@ -111,21 +108,6 @@ mod tests {
|
||||
Ok(repository)
|
||||
}
|
||||
|
||||
static mut TEST_TARGET_INDEX: usize = 0;
|
||||
|
||||
fn test_target() -> Target {
|
||||
Target {
|
||||
name: format!("target_name_{}", unsafe { TEST_TARGET_INDEX }),
|
||||
remote: format!("remote_{}", unsafe { TEST_TARGET_INDEX }),
|
||||
sha: git2::Oid::from_str(&format!(
|
||||
"0123456789abcdef0123456789abcdef0123456{}",
|
||||
unsafe { TEST_TARGET_INDEX }
|
||||
))
|
||||
.unwrap(),
|
||||
behind: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_not_found() -> Result<()> {
|
||||
let repository = test_repository()?;
|
||||
|
Loading…
Reference in New Issue
Block a user