move conflicts module to branches crate

This commit is contained in:
Kiril Videlov 2024-07-07 22:58:48 +02:00
parent 0924b506d7
commit 6dcbb8bddb
No known key found for this signature in database
GPG Key ID: A4C733025427C471
9 changed files with 181 additions and 30 deletions

View File

@ -22,6 +22,7 @@ git2-hooks = "0.3"
url = { version = "2.5.2", features = ["serde"] }
md5 = "0.7.0"
futures = "0.3"
itertools = "0.13"
[[test]]
name="branches"
@ -35,4 +36,3 @@ gitbutler-git = { workspace = true, features = ["test-askpass-path"] }
glob = "0.3.1"
serial_test = "3.1.1"
tempfile = "3.10"
itertools = "0.13"

View File

@ -8,6 +8,7 @@ use serde::Serialize;
use super::r#virtual as vb;
use super::r#virtual::convert_to_real_branch;
use crate::conflicts::RepoConflicts;
use crate::integration::{get_workspace_head, update_gitbutler_integration};
use crate::remote::{commit_to_remote_commit, RemoteCommit};
use crate::VirtualBranchHunk;

View File

@ -0,0 +1,167 @@
// stuff to manage merge conflict state
// this is the dumbest possible way to do this, but it is a placeholder
// conflicts are stored one path per line in .git/conflicts
// merge parent is stored in .git/base_merge_parent
// 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},
path::{Path, PathBuf},
};
use anyhow::{anyhow, Context, Result};
use itertools::Itertools;
use gitbutler_core::{error::Marker, project_repository::ProjectRepo};
pub fn mark<P: AsRef<Path>, A: AsRef<[P]>>(
repository: &ProjectRepo,
paths: A,
parent: Option<git2::Oid>,
) -> Result<()> {
let paths = paths.as_ref();
if paths.is_empty() {
return Ok(());
}
let conflicts_path = repository.repo().path().join("conflicts");
// 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_ref().as_os_str().as_encoded_bytes())?;
file.write_all(b"\n")?;
}
if let Some(parent) = parent {
let merge_path = repository.repo().path().join("base_merge_parent");
// write all the file paths to a file on disk
let mut file = std::fs::File::create(merge_path)?;
file.write_all(parent.to_string().as_bytes())?;
}
Ok(())
}
pub fn merge_parent(repository: &ProjectRepo) -> Result<Option<git2::Oid>> {
let merge_path = repository.repo().path().join("base_merge_parent");
if !merge_path.exists() {
return Ok(None);
}
let file = std::fs::File::open(merge_path)?;
let reader = std::io::BufReader::new(file);
let mut lines = reader.lines();
if let Some(parent) = lines.next() {
let parent = parent?;
let parent: git2::Oid = parent.parse()?;
Ok(Some(parent))
} else {
Ok(None)
}
}
pub fn resolve<P: AsRef<Path>>(repository: &ProjectRepo, path: P) -> Result<()> {
let path = path.as_ref();
let conflicts_path = repository.repo().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_ok(PathBuf::from) {
let line = line?;
if line != path {
remaining.push(line);
}
}
// remove file
std::fs::remove_file(conflicts_path)?;
// re-write file if needed
if !remaining.is_empty() {
mark(repository, &remaining, None)?;
}
Ok(())
}
pub fn conflicting_files(repository: &ProjectRepo) -> Result<Vec<String>> {
let conflicts_path = repository.repo().path().join("conflicts");
if !conflicts_path.exists() {
return Ok(vec![]);
}
let file = std::fs::File::open(conflicts_path)?;
let reader = std::io::BufReader::new(file);
Ok(reader.lines().map_while(Result::ok).collect())
}
/// Check if `path` is conflicting in `repository`, or if `None`, check if there is any conflict.
// TODO(ST): Should this not rather check the conflicting state in the index?
pub fn is_conflicting(repository: &ProjectRepo, path: Option<&Path>) -> Result<bool> {
let conflicts_path = repository.repo().path().join("conflicts");
if !conflicts_path.exists() {
return Ok(false);
}
let file = std::fs::File::open(conflicts_path)?;
let reader = std::io::BufReader::new(file);
// TODO(ST): This shouldn't work on UTF8 strings.
let mut files = reader.lines().map_ok(PathBuf::from);
if let Some(pathname) = path {
// 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.next().transpose().map(|x| x.is_some())?)
}
}
// is this project still in a resolving conflict state?
// - could be that there are no more conflicts, but the state is not committed
pub fn is_resolving(repository: &ProjectRepo) -> bool {
repository.repo().path().join("base_merge_parent").exists()
}
pub fn clear(repository: &ProjectRepo) -> Result<()> {
let merge_path = repository.repo().path().join("base_merge_parent");
std::fs::remove_file(merge_path)?;
for file in conflicting_files(repository)? {
resolve(repository, &file)?;
}
Ok(())
}
pub trait RepoConflicts {
fn assure_unconflicted(&self) -> Result<()>;
fn assure_resolved(&self) -> Result<()>;
fn is_resolving(&self) -> bool;
}
impl RepoConflicts for ProjectRepo {
fn is_resolving(&self) -> bool {
is_resolving(self)
}
fn assure_resolved(&self) -> Result<()> {
if self.is_resolving() {
Err(anyhow!("project has active conflicts")).context(Marker::ProjectConflict)
} else {
Ok(())
}
}
fn assure_unconflicted(&self) -> Result<()> {
if is_conflicting(self, None)? {
Err(anyhow!("project has active conflicts")).context(Marker::ProjectConflict)
} else {
Ok(())
}
}
}

View File

@ -13,10 +13,12 @@ use gitbutler_core::virtual_branches::{
};
use gitbutler_core::{
git::CommitExt,
project_repository::{self, conflicts, LogUntil},
project_repository::{self, LogUntil},
virtual_branches::branch::BranchCreateRequest,
};
use crate::conflicts;
const WORKSPACE_HEAD: &str = "Workspace Head";
pub fn get_integration_commiter<'a>() -> Result<git2::Signature<'a>> {

View File

@ -14,3 +14,5 @@ pub mod integration;
pub mod files;
pub mod remote;
pub mod conflicts;

View File

@ -21,6 +21,7 @@ use gitbutler_core::virtual_branches::Author;
use hex::ToHex;
use serde::{Deserialize, Serialize};
use crate::conflicts::{self, RepoConflicts};
use crate::integration::{get_integration_commiter, get_workspace_head};
use crate::remote::{branch_to_remote_branch, RemoteBranch};
use gitbutler_core::error::Code;
@ -46,7 +47,7 @@ use gitbutler_core::{
diff::{self},
Refname, RemoteRefname,
},
project_repository::{self, conflicts, LogUntil},
project_repository::{self, LogUntil},
};
type AppliedStatuses = Vec<(branch::Branch, BranchStatus)>;

View File

@ -1,5 +1,5 @@
mod config;
pub mod conflicts;
// pub mod conflicts;
mod repository;
pub use config::Config;

View File

@ -6,7 +6,8 @@ use std::{
use anyhow::{anyhow, Context, Result};
use super::conflicts;
// use super::conflicts;
use crate::git::RepositoryExt;
use crate::{
askpass,
git::{self, Url},
@ -15,7 +16,6 @@ use crate::{
virtual_branches::{Branch, BranchId},
};
use crate::{error::Code, git::CommitHeadersV2};
use crate::{error::Marker, git::RepositoryExt};
pub struct ProjectRepo {
git_repository: git2::Repository,
@ -140,32 +140,9 @@ pub trait RepoActions {
fn git_index_size(&self) -> Result<usize>;
fn config(&self) -> super::Config;
fn path(&self) -> &path::Path;
fn assure_unconflicted(&self) -> Result<()>;
fn assure_resolved(&self) -> Result<()>;
fn is_resolving(&self) -> bool;
}
impl RepoActions for ProjectRepo {
fn is_resolving(&self) -> bool {
conflicts::is_resolving(self)
}
fn assure_resolved(&self) -> Result<()> {
if self.is_resolving() {
Err(anyhow!("project has active conflicts")).context(Marker::ProjectConflict)
} else {
Ok(())
}
}
fn assure_unconflicted(&self) -> Result<()> {
if conflicts::is_conflicting(self, None)? {
Err(anyhow!("project has active conflicts")).context(Marker::ProjectConflict)
} else {
Ok(())
}
}
fn path(&self) -> &path::Path {
path::Path::new(&self.project.path)
}

View File

@ -1,7 +1,8 @@
use anyhow::{Context, Result};
use gitbutler_branch::conflicts;
use gitbutler_core::{
git,
project_repository::{self, conflicts, RepoActions},
project_repository::{self, RepoActions},
projects::{self, ProjectId},
virtual_branches::BranchId,
};