mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-19 07:32:22 +03:00
Merge pull request #3123 from gitbutlerapp/Virtual-branch-7
perform proper path handling and vectorized writes in conflict resolver
This commit is contained in:
commit
16140c7b4a
@ -5,15 +5,24 @@
|
||||
// conflicts are removed as they are resolved, the conflicts file is removed when there are no more conflicts
|
||||
// the merge parent file is removed when the merge is complete
|
||||
|
||||
use std::io::{BufRead, Write};
|
||||
use std::{
|
||||
io::{BufRead, IoSlice, Write},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::git;
|
||||
|
||||
use super::Repository;
|
||||
|
||||
pub fn mark(repository: &Repository, paths: &[String], parent: Option<git::Oid>) -> Result<()> {
|
||||
pub fn mark<P: AsRef<Path>, A: AsRef<[P]>>(
|
||||
repository: &Repository,
|
||||
paths: A,
|
||||
parent: Option<git::Oid>,
|
||||
) -> Result<()> {
|
||||
let paths = paths.as_ref();
|
||||
if paths.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
@ -21,8 +30,8 @@ pub fn mark(repository: &Repository, paths: &[String], parent: Option<git::Oid>)
|
||||
// write all the file paths to a file on disk
|
||||
let mut file = std::fs::File::create(conflicts_path)?;
|
||||
for path in paths {
|
||||
file.write_all(path.as_bytes()).unwrap();
|
||||
file.write_all(b"\n").unwrap();
|
||||
file.write_all(path.as_ref().as_os_str().as_encoded_bytes())?;
|
||||
file.write_all(b"\n")?;
|
||||
}
|
||||
|
||||
if let Some(parent) = parent {
|
||||
@ -53,12 +62,14 @@ pub fn merge_parent(repository: &Repository) -> Result<Option<git::Oid>> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve(repository: &Repository, path: &str) -> Result<()> {
|
||||
pub fn resolve<P: AsRef<Path>>(repository: &Repository, path: P) -> Result<()> {
|
||||
let path = path.as_ref();
|
||||
let conflicts_path = repository.git_repository.path().join("conflicts");
|
||||
let file = std::fs::File::open(conflicts_path.clone())?;
|
||||
let reader = std::io::BufReader::new(file);
|
||||
let mut remaining = Vec::new();
|
||||
for line in reader.lines().map_while(Result::ok) {
|
||||
for line in reader.lines().map_ok(PathBuf::from) {
|
||||
let line = line?;
|
||||
if line != path {
|
||||
remaining.push(line);
|
||||
}
|
||||
@ -85,7 +96,7 @@ pub fn conflicting_files(repository: &Repository) -> Result<Vec<String>> {
|
||||
Ok(reader.lines().map_while(Result::ok).collect())
|
||||
}
|
||||
|
||||
pub fn is_conflicting(repository: &Repository, path: Option<&str>) -> Result<bool> {
|
||||
pub fn is_conflicting<P: AsRef<Path>>(repository: &Repository, path: Option<P>) -> Result<bool> {
|
||||
let conflicts_path = repository.git_repository.path().join("conflicts");
|
||||
if !conflicts_path.exists() {
|
||||
return Ok(false);
|
||||
@ -93,17 +104,21 @@ pub fn is_conflicting(repository: &Repository, path: Option<&str>) -> Result<boo
|
||||
|
||||
let file = std::fs::File::open(conflicts_path)?;
|
||||
let reader = std::io::BufReader::new(file);
|
||||
let files = reader.lines().map_while(Result::ok).collect::<Vec<_>>();
|
||||
let mut files = reader.lines().map_ok(PathBuf::from);
|
||||
if let Some(pathname) = path {
|
||||
let pathname = pathname.as_ref();
|
||||
|
||||
// check if pathname is one of the lines in conflicts_path file
|
||||
for line in files {
|
||||
let line = line?;
|
||||
|
||||
if line == pathname {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
} else {
|
||||
Ok(!files.is_empty())
|
||||
Ok(files.next().transpose().map(|x| x.is_some())?)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,8 @@
|
||||
use std::{collections::HashMap, path, time, vec};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
path::{Path, PathBuf},
|
||||
time, vec,
|
||||
};
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
use std::os::unix::prelude::*;
|
||||
@ -28,12 +32,12 @@ use super::{
|
||||
branch_to_remote_branch, context, errors, target, Iterator, RemoteBranch,
|
||||
};
|
||||
|
||||
type AppliedStatuses = Vec<(branch::Branch, HashMap<path::PathBuf, Vec<diff::Hunk>>)>;
|
||||
type AppliedStatuses = Vec<(branch::Branch, HashMap<PathBuf, Vec<diff::Hunk>>)>;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("path contains invalid utf-8 characters: {0}")]
|
||||
InvalidUnicodePath(path::PathBuf),
|
||||
InvalidUnicodePath(PathBuf),
|
||||
}
|
||||
|
||||
// this struct is a mapping to the view `Branch` type in Typescript
|
||||
@ -106,7 +110,7 @@ pub struct VirtualBranchCommit {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct VirtualBranchFile {
|
||||
pub id: String,
|
||||
pub path: path::PathBuf,
|
||||
pub path: PathBuf,
|
||||
pub hunks: Vec<VirtualBranchHunk>,
|
||||
pub modified_at: u128,
|
||||
pub conflicted: bool,
|
||||
@ -128,7 +132,7 @@ pub struct VirtualBranchHunk {
|
||||
pub id: String,
|
||||
pub diff: String,
|
||||
pub modified_at: u128,
|
||||
pub file_path: path::PathBuf,
|
||||
pub file_path: PathBuf,
|
||||
pub hash: String,
|
||||
pub old_start: u32,
|
||||
pub start: u32,
|
||||
@ -497,7 +501,7 @@ pub fn unapply_ownership(
|
||||
let hunks_to_unapply = applied_statuses
|
||||
.iter()
|
||||
.map(
|
||||
|(branch, branch_files)| -> Result<Vec<(std::path::PathBuf, diff::Hunk)>> {
|
||||
|(branch, branch_files)| -> Result<Vec<(PathBuf, diff::Hunk)>> {
|
||||
let branch_files = calculate_non_commited_diffs(
|
||||
project_repository,
|
||||
branch,
|
||||
@ -595,14 +599,14 @@ pub fn reset_files(
|
||||
let repo = &project_repository.git_repository;
|
||||
let index = repo.index().context("failed to get index")?;
|
||||
for file in files {
|
||||
let entry = index.get_path(path::Path::new(file), 0);
|
||||
let entry = index.get_path(Path::new(file), 0);
|
||||
if entry.is_some() {
|
||||
repo.checkout_index_path(path::Path::new(file))
|
||||
repo.checkout_index_path(Path::new(file))
|
||||
.context("failed to checkout index")?;
|
||||
} else {
|
||||
// find the project root
|
||||
let project_root = &project_repository.project().path;
|
||||
let path = path::Path::new(file);
|
||||
let path = Path::new(file);
|
||||
//combine the project root with the file path
|
||||
let path = &project_root.join(path);
|
||||
std::fs::remove_file(path).context("failed to remove file")?;
|
||||
@ -1122,8 +1126,8 @@ pub fn calculate_non_commited_diffs(
|
||||
project_repository: &project_repository::Repository,
|
||||
branch: &branch::Branch,
|
||||
default_target: &target::Target,
|
||||
files: &HashMap<path::PathBuf, Vec<diff::Hunk>>,
|
||||
) -> Result<HashMap<path::PathBuf, Vec<diff::Hunk>>> {
|
||||
files: &HashMap<PathBuf, Vec<diff::Hunk>>,
|
||||
) -> Result<HashMap<PathBuf, Vec<diff::Hunk>>> {
|
||||
if default_target.sha == branch.head && !branch.applied {
|
||||
return Ok(files.clone());
|
||||
};
|
||||
@ -1369,7 +1373,7 @@ pub fn merge_virtual_branch_upstream(
|
||||
signing_key: Option<&keys::PrivateKey>,
|
||||
user: Option<&users::User>,
|
||||
) -> Result<(), errors::MergeVirtualBranchUpstreamError> {
|
||||
if conflicts::is_conflicting(project_repository, None)? {
|
||||
if conflicts::is_conflicting::<&Path>(project_repository, None)? {
|
||||
return Err(errors::MergeVirtualBranchUpstreamError::Conflict(
|
||||
errors::ProjectConflictError {
|
||||
project_id: project_repository.project().id,
|
||||
@ -1802,7 +1806,7 @@ fn set_ownership(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_mtime(cache: &mut HashMap<path::PathBuf, u128>, file_path: &path::PathBuf) -> u128 {
|
||||
fn get_mtime(cache: &mut HashMap<PathBuf, u128>, file_path: &PathBuf) -> u128 {
|
||||
if let Some(mtime) = cache.get(file_path) {
|
||||
*mtime
|
||||
} else {
|
||||
@ -1836,10 +1840,10 @@ fn diff_hash(diff: &str) -> String {
|
||||
}
|
||||
|
||||
pub fn virtual_hunks_by_filepath(
|
||||
project_path: &path::Path,
|
||||
diff: &HashMap<path::PathBuf, Vec<diff::Hunk>>,
|
||||
) -> HashMap<path::PathBuf, Vec<VirtualBranchHunk>> {
|
||||
let mut mtimes: HashMap<path::PathBuf, u128> = HashMap::new();
|
||||
project_path: &Path,
|
||||
diff: &HashMap<PathBuf, Vec<diff::Hunk>>,
|
||||
) -> HashMap<PathBuf, Vec<VirtualBranchHunk>> {
|
||||
let mut mtimes: HashMap<PathBuf, u128> = HashMap::new();
|
||||
diff.iter()
|
||||
.map(|(file_path, hunks)| {
|
||||
let hunks = hunks
|
||||
@ -1864,7 +1868,7 @@ pub fn virtual_hunks_by_filepath(
|
||||
.collect::<HashMap<_, _>>()
|
||||
}
|
||||
|
||||
pub type BranchStatus = HashMap<path::PathBuf, Vec<diff::Hunk>>;
|
||||
pub type BranchStatus = HashMap<PathBuf, Vec<diff::Hunk>>;
|
||||
|
||||
// list the virtual branches and their file statuses (statusi?)
|
||||
#[allow(clippy::type_complexity)]
|
||||
@ -1937,7 +1941,7 @@ fn get_non_applied_status(
|
||||
virtual_branches
|
||||
.into_iter()
|
||||
.map(
|
||||
|branch| -> Result<(branch::Branch, HashMap<path::PathBuf, Vec<diff::Hunk>>)> {
|
||||
|branch| -> Result<(branch::Branch, HashMap<PathBuf, Vec<diff::Hunk>>)> {
|
||||
if branch.applied {
|
||||
bail!("branch {} is applied", branch.name);
|
||||
}
|
||||
@ -1983,7 +1987,7 @@ fn get_applied_status(
|
||||
)
|
||||
.context("failed to diff workdir")?;
|
||||
|
||||
let mut diff: HashMap<path::PathBuf, Vec<git::diff::Hunk>> = HashMap::new();
|
||||
let mut diff: HashMap<PathBuf, Vec<git::diff::Hunk>> = HashMap::new();
|
||||
let mut skipped_files: Vec<diff::DiffFile> = Vec::new();
|
||||
for (file_path, diff_file) in diff_files {
|
||||
if diff_file.skipped {
|
||||
@ -2010,7 +2014,7 @@ fn get_applied_status(
|
||||
// - update shifted hunks
|
||||
// - remove non existent hunks
|
||||
|
||||
let mut hunks_by_branch_id: HashMap<BranchId, HashMap<path::PathBuf, Vec<diff::Hunk>>> =
|
||||
let mut hunks_by_branch_id: HashMap<BranchId, HashMap<PathBuf, Vec<diff::Hunk>>> =
|
||||
virtual_branches
|
||||
.iter()
|
||||
.map(|branch| (branch.id, HashMap::new()))
|
||||
@ -2176,7 +2180,7 @@ fn virtual_hunks_to_virtual_files(
|
||||
) -> Vec<VirtualBranchFile> {
|
||||
hunks
|
||||
.iter()
|
||||
.fold(HashMap::<path::PathBuf, Vec<_>>::new(), |mut acc, hunk| {
|
||||
.fold(HashMap::<PathBuf, Vec<_>>::new(), |mut acc, hunk| {
|
||||
acc.entry(hunk.file_path.clone())
|
||||
.or_default()
|
||||
.push(hunk.clone());
|
||||
@ -2258,7 +2262,7 @@ pub fn reset_branch(
|
||||
|
||||
fn diffs_to_virtual_files(
|
||||
project_repository: &project_repository::Repository,
|
||||
diffs: &HashMap<path::PathBuf, Vec<diff::Hunk>>,
|
||||
diffs: &HashMap<PathBuf, Vec<diff::Hunk>>,
|
||||
) -> Vec<VirtualBranchFile> {
|
||||
let hunks_by_filepath = virtual_hunks_by_filepath(&project_repository.project().path, diffs);
|
||||
virtual_hunks_to_virtual_files(
|
||||
@ -2277,7 +2281,7 @@ fn diffs_to_virtual_files(
|
||||
pub fn write_tree(
|
||||
project_repository: &project_repository::Repository,
|
||||
target: &target::Target,
|
||||
files: &HashMap<path::PathBuf, Vec<diff::Hunk>>,
|
||||
files: &HashMap<PathBuf, Vec<diff::Hunk>>,
|
||||
) -> Result<git::Oid> {
|
||||
write_tree_onto_commit(project_repository, target.sha, files)
|
||||
}
|
||||
@ -2285,7 +2289,7 @@ pub fn write_tree(
|
||||
pub fn write_tree_onto_commit(
|
||||
project_repository: &project_repository::Repository,
|
||||
commit_oid: git::Oid,
|
||||
files: &HashMap<path::PathBuf, Vec<diff::Hunk>>,
|
||||
files: &HashMap<PathBuf, Vec<diff::Hunk>>,
|
||||
) -> Result<git::Oid> {
|
||||
// read the base sha into an index
|
||||
let git_repository = &project_repository.git_repository;
|
||||
@ -2299,14 +2303,14 @@ pub fn write_tree_onto_commit(
|
||||
pub fn write_tree_onto_tree(
|
||||
project_repository: &project_repository::Repository,
|
||||
base_tree: &git::Tree,
|
||||
files: &HashMap<path::PathBuf, Vec<diff::Hunk>>,
|
||||
files: &HashMap<PathBuf, Vec<diff::Hunk>>,
|
||||
) -> Result<git::Oid> {
|
||||
let git_repository = &project_repository.git_repository;
|
||||
let mut builder = git_repository.treebuilder(Some(base_tree));
|
||||
// now update the index with content in the working directory for each file
|
||||
for (filepath, hunks) in files {
|
||||
// convert this string to a Path
|
||||
let rel_path = std::path::Path::new(&filepath);
|
||||
let rel_path = Path::new(&filepath);
|
||||
let full_path = project_repository.path().join(rel_path);
|
||||
|
||||
let is_submodule =
|
||||
@ -2503,7 +2507,7 @@ pub fn commit(
|
||||
})?;
|
||||
|
||||
let files = calculate_non_commited_diffs(project_repository, branch, &default_target, files)?;
|
||||
if conflicts::is_conflicting(project_repository, None)? {
|
||||
if conflicts::is_conflicting::<&Path>(project_repository, None)? {
|
||||
return Err(errors::CommitError::Conflicted(
|
||||
errors::ProjectConflictError {
|
||||
project_id: project_repository.project().id,
|
||||
@ -2887,7 +2891,7 @@ pub fn amend(
|
||||
branch_id: &BranchId,
|
||||
target_ownership: &Ownership,
|
||||
) -> Result<git::Oid, errors::AmendError> {
|
||||
if conflicts::is_conflicting(project_repository, None)? {
|
||||
if conflicts::is_conflicting::<&Path>(project_repository, None)? {
|
||||
return Err(errors::AmendError::Conflict(errors::ProjectConflictError {
|
||||
project_id: project_repository.project().id,
|
||||
}));
|
||||
@ -3056,7 +3060,7 @@ pub fn cherry_pick(
|
||||
branch_id: &BranchId,
|
||||
target_commit_oid: git::Oid,
|
||||
) -> Result<Option<git::Oid>, errors::CherryPickError> {
|
||||
if conflicts::is_conflicting(project_repository, None)? {
|
||||
if conflicts::is_conflicting::<&Path>(project_repository, None)? {
|
||||
return Err(errors::CherryPickError::Conflict(
|
||||
errors::ProjectConflictError {
|
||||
project_id: project_repository.project().id,
|
||||
@ -3245,7 +3249,7 @@ pub fn squash(
|
||||
branch_id: &BranchId,
|
||||
commit_oid: git::Oid,
|
||||
) -> Result<(), errors::SquashError> {
|
||||
if conflicts::is_conflicting(project_repository, None)? {
|
||||
if conflicts::is_conflicting::<&Path>(project_repository, None)? {
|
||||
return Err(errors::SquashError::Conflict(
|
||||
errors::ProjectConflictError {
|
||||
project_id: project_repository.project().id,
|
||||
@ -3437,7 +3441,7 @@ pub fn update_commit_message(
|
||||
return Err(errors::UpdateCommitMessageError::EmptyMessage);
|
||||
}
|
||||
|
||||
if conflicts::is_conflicting(project_repository, None)? {
|
||||
if conflicts::is_conflicting::<&Path>(project_repository, None)? {
|
||||
return Err(errors::UpdateCommitMessageError::Conflict(
|
||||
errors::ProjectConflictError {
|
||||
project_id: project_repository.project().id,
|
||||
|
Loading…
Reference in New Issue
Block a user