-
Notifications
You must be signed in to change notification settings - Fork 510
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New crate for calculating hunk dependencies
- Loading branch information
Showing
16 changed files
with
951 additions
and
12 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
[package] | ||
name = "gitbutler-hunk-dependency" | ||
version = "0.0.0" | ||
edition = "2021" | ||
authors = ["GitButler <[email protected]>"] | ||
publish = false | ||
|
||
[dependencies] | ||
anyhow = "1.0.86" | ||
git2.workspace = true | ||
gix = { workspace = true, features = [] } | ||
gitbutler-diff.workspace = true | ||
gitbutler-reference.workspace = true | ||
gitbutler-serde.workspace = true | ||
gitbutler-stack.workspace = true | ||
gitbutler-id.workspace = true | ||
itertools = "0.13" | ||
serde = { workspace = true, features = ["std"] } | ||
bstr.workspace = true | ||
tokio.workspace = true | ||
uuid = { workspace = true, features = ["v4", "fast-rng"] } | ||
|
||
[[test]] | ||
name = "blame" | ||
path = "tests/mod.rs" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
use anyhow::{anyhow, Context}; | ||
|
||
#[derive(Debug, PartialEq, Clone)] | ||
pub struct InputDiff { | ||
pub old_start: i32, | ||
pub old_lines: i32, | ||
pub new_start: i32, | ||
pub new_lines: i32, | ||
} | ||
|
||
impl InputDiff { | ||
pub fn net_lines(&self) -> i32 { | ||
self.new_lines - self.old_lines | ||
} | ||
} | ||
|
||
fn count_context_lines<I, S>(iter: I) -> i32 | ||
where | ||
I: Iterator<Item = S>, | ||
S: AsRef<str>, | ||
{ | ||
iter.take_while(|line| { | ||
let line_ref = line.as_ref(); // Convert to &str | ||
!line_ref.starts_with('-') && !line_ref.starts_with('+') | ||
}) | ||
.fold(0i32, |acc, _| acc + 1) | ||
} | ||
|
||
impl TryFrom<String> for InputDiff { | ||
fn try_from(value: String) -> Result<Self, anyhow::Error> { | ||
parse_diff(value) | ||
} | ||
|
||
type Error = anyhow::Error; | ||
} | ||
|
||
impl TryFrom<&str> for InputDiff { | ||
fn try_from(value: &str) -> Result<Self, anyhow::Error> { | ||
parse_diff(value) | ||
} | ||
|
||
type Error = anyhow::Error; | ||
} | ||
|
||
fn parse_diff(value: impl AsRef<str>) -> Result<InputDiff, anyhow::Error> { | ||
let value = value.as_ref(); | ||
let header = value.lines().next().context("No header found")?; | ||
if !header.starts_with("@@") { | ||
return Err(anyhow!("Malformed undiff")); | ||
} | ||
let parts: Vec<&str> = header.split_whitespace().collect(); | ||
let (old_start, old_lines) = parse_header(parts[1]); | ||
let (new_start, new_lines) = parse_header(parts[2]); | ||
let head_context_lines = count_context_lines(value.lines().skip(1).take(3)); | ||
let tail_context_lines = count_context_lines(value.rsplit_terminator('\n').take(3)); | ||
let context_lines = head_context_lines + tail_context_lines; | ||
|
||
Ok(InputDiff { | ||
old_start: old_start + head_context_lines, | ||
old_lines: old_lines - context_lines, | ||
new_start: new_start + head_context_lines, | ||
new_lines: new_lines - context_lines, | ||
}) | ||
} | ||
|
||
fn parse_header(hunk_info: &str) -> (i32, i32) { | ||
let hunk_info = hunk_info.trim_start_matches(&['-', '+'][..]); // Remove the leading '-' or '+' | ||
let parts: Vec<&str> = hunk_info.split(',').collect(); | ||
let start = parts[0].parse().unwrap(); | ||
let lines = if parts.len() > 1 { | ||
parts[1].parse().unwrap() | ||
} else { | ||
1 | ||
}; | ||
(start, lines) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
use gitbutler_stack::StackId; | ||
|
||
#[derive(Debug, PartialEq, Clone)] | ||
pub struct HunkRange { | ||
pub stack_id: StackId, | ||
pub commit_id: git2::Oid, | ||
pub start: i32, | ||
pub lines: i32, | ||
pub line_shift: i32, | ||
} | ||
|
||
impl HunkRange { | ||
fn end(&self) -> i32 { | ||
self.start + self.lines - 1 | ||
} | ||
|
||
pub fn intersects(&self, start: i32, lines: i32) -> bool { | ||
self.end() >= start && self.start < start + lines | ||
} | ||
|
||
pub fn contains(&self, start: i32, lines: i32) -> bool { | ||
start > self.start && start + lines <= self.end() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
pub mod diff; | ||
pub(crate) mod hunk; | ||
pub mod locks; | ||
pub(crate) mod path; | ||
pub mod stack; | ||
pub mod workspace; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
use std::{collections::HashMap, path::PathBuf}; | ||
|
||
use gitbutler_diff::{Hunk, HunkHash}; | ||
use gitbutler_stack::StackId; | ||
use itertools::Itertools; | ||
use serde::Serialize; | ||
|
||
use crate::workspace::{InputStack, WorkspaceRanges}; | ||
|
||
// Type defined in gitbutler-branch-actions and can't be imported here. | ||
type BranchStatus = HashMap<PathBuf, Vec<gitbutler_diff::GitHunk>>; | ||
|
||
// A hunk is locked when it depends on changes in commits that are in your | ||
// workspace. A hunk can be locked to more than one branch if it overlaps | ||
// with more than one committed hunk. | ||
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Copy)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct HunkLock { | ||
// TODO: Rename this stack_id. | ||
pub branch_id: StackId, | ||
#[serde(with = "gitbutler_serde::oid")] | ||
pub commit_id: git2::Oid, | ||
} | ||
|
||
pub struct HunkDependencyOptions<'a> { | ||
// Uncommitted changes in workspace. | ||
pub workdir: &'a BranchStatus, | ||
|
||
/// A nested map of committed diffs per stack, commit, and path. | ||
pub stacks: Vec<InputStack>, | ||
} | ||
|
||
/// Returns a map from hunk hash to hunk locks. | ||
/// | ||
/// To understand if any uncommitted changes depend on (intersect) any existing | ||
/// changes we first transform the branch specific line numbers to global ones, | ||
/// then look for places they intersect. | ||
/// | ||
/// TODO: Change terminology to talk about dependencies instead of locks. | ||
pub fn compute_hunk_locks( | ||
options: HunkDependencyOptions, | ||
) -> anyhow::Result<HashMap<HunkHash, Vec<HunkLock>>> { | ||
let HunkDependencyOptions { workdir, stacks } = options; | ||
|
||
// Transforms local line numbers to global line numbers. | ||
let workspace_ranges = WorkspaceRanges::new(stacks); | ||
|
||
let mut result = HashMap::new(); | ||
|
||
for (path, workspace_hunks) in workdir { | ||
for hunk in workspace_hunks { | ||
let hunk_dependencies = | ||
workspace_ranges.intersection(path, hunk.old_start as i32, hunk.old_lines as i32); | ||
let hash = Hunk::hash_diff(&hunk.diff_lines); | ||
let locks = hunk_dependencies | ||
.iter() | ||
.map(|dependency| HunkLock { | ||
commit_id: dependency.commit_id, | ||
branch_id: dependency.stack_id, | ||
}) | ||
.collect_vec(); | ||
|
||
if !locks.is_empty() { | ||
result.insert(hash, locks); | ||
} | ||
} | ||
} | ||
Ok(result) | ||
} |
Oops, something went wrong.