Skip to content

Commit 2261de4

Browse files
authored
Merge pull request #1585 from Byron/merge
octopus-merge (part 2: blob-merge)
2 parents 20f9b3f + eb37dc3 commit 2261de4

35 files changed

+4428
-17
lines changed

Cargo.lock

+23
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ members = [
243243
"gix-object",
244244
"gix-glob",
245245
"gix-diff",
246+
"gix-merge",
246247
"gix-date",
247248
"gix-traverse",
248249
"gix-dir",

README.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -130,10 +130,11 @@ is usable to some extent.
130130
* [gix-submodule](https://github.com/Byron/gitoxide/blob/main/crate-status.md#gix-submodule)
131131
* [gix-status](https://github.com/Byron/gitoxide/blob/main/crate-status.md#gix-status)
132132
* [gix-worktree-state](https://github.com/Byron/gitoxide/blob/main/crate-status.md#gix-worktree-state)
133-
* `gitoxide-core`
134-
* **very early** _(possibly without any documentation and many rough edges)_
135133
* [gix-date](https://github.com/Byron/gitoxide/blob/main/crate-status.md#gix-date)
136134
* [gix-dir](https://github.com/Byron/gitoxide/blob/main/crate-status.md#gix-dir)
135+
* `gitoxide-core`
136+
* **very early** _(possibly without any documentation and many rough edges)_
137+
* [gix-merge](https://github.com/Byron/gitoxide/blob/main/crate-status.md#gix-merge)
137138
* **idea** _(just a name placeholder)_
138139
* [gix-note](https://github.com/Byron/gitoxide/blob/main/crate-status.md#gix-note)
139140
* [gix-fetchhead](https://github.com/Byron/gitoxide/blob/main/crate-status.md#gix-fetchhead)

crate-status.md

+18-1
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,9 @@ The top-level crate that acts as hub to all functionality provided by the `gix-*
196196
* [x] probe capabilities
197197
* [x] symlink creation and removal
198198
* [x] file snapshots
199+
* [ ] **BString Interner with Arena-Backing and arbitrary value association**
200+
- probably based on [`internment`](https://docs.rs/internment/latest/internment/struct.Arena.html#),
201+
but needs `bumpalo` support to avoid item allocations/boxing, and avoid internal `Mutex`. (key type is pointer based).
199202

200203
### gix-fs
201204
* [x] probe capabilities
@@ -215,6 +218,7 @@ The top-level crate that acts as hub to all functionality provided by the `gix-*
215218
* [x] [name validation][tagname-validation]
216219
* [x] transform borrowed to owned objects
217220
* [x] edit trees efficiently and write changes back
221+
- [ ] See if `gix-fs::InternedMap` improves performance.
218222
* [x] API documentation
219223
* [ ] Some examples
220224

@@ -320,11 +324,24 @@ Check out the [performance discussion][gix-diff-performance] as well.
320324
* [x] prepare invocation of external diff program
321325
- [ ] pass meta-info
322326
* [ ] working with hunks of data
327+
* [ ] diff-heuristics match Git perfectly
323328
* [x] API documentation
324329
* [ ] Examples
325-
330+
326331
[gix-diff-performance]: https://github.com/Byron/gitoxide/discussions/74
327332

333+
### gix-merge
334+
335+
* [x] three-way merge analysis of blobs with choice of how to resolve conflicts
336+
- [ ] choose how to resolve conflicts on the data-structure
337+
- [ ] produce a new blob based on data-structure containing possible resolutions
338+
- [x] `merge` style
339+
- [x] `diff3` style
340+
- [x] `zdiff` style
341+
* [ ] diff-heuristics match Git perfectly
342+
* [x] API documentation
343+
* [ ] Examples
344+
328345
### gix-traverse
329346

330347
Check out the [performance discussion][gix-traverse-performance] as well.

gix-attributes/src/state.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ impl<'a> ValueRef<'a> {
2323
}
2424

2525
/// Access and conversions
26-
impl ValueRef<'_> {
26+
impl<'a> ValueRef<'a> {
2727
/// Access this value as byte string.
28-
pub fn as_bstr(&self) -> &BStr {
28+
pub fn as_bstr(&self) -> &'a BStr {
2929
self.0.as_bytes().as_bstr()
3030
}
3131

gix-diff/src/blob/pipeline.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ pub struct WorktreeRoots {
2222
pub new_root: Option<PathBuf>,
2323
}
2424

25+
/// Access
2526
impl WorktreeRoots {
2627
/// Return the root path for the given `kind`
2728
pub fn by_kind(&self, kind: ResourceKind) -> Option<&Path> {
@@ -30,6 +31,11 @@ impl WorktreeRoots {
3031
ResourceKind::NewOrDestination => self.new_root.as_deref(),
3132
}
3233
}
34+
35+
/// Return `true` if all worktree roots are unset.
36+
pub fn is_unset(&self) -> bool {
37+
self.new_root.is_none() && self.old_root.is_none()
38+
}
3339
}
3440

3541
/// Data as part of an [Outcome].
@@ -184,6 +190,8 @@ impl Pipeline {
184190
/// Access
185191
impl Pipeline {
186192
/// Return all drivers that this instance was initialized with.
193+
///
194+
/// They are sorted by [`name`](Driver::name) to support binary searches.
187195
pub fn drivers(&self) -> &[super::Driver] {
188196
&self.drivers
189197
}
@@ -445,7 +453,7 @@ impl Pipeline {
445453
}
446454
}
447455
.map_err(|err| {
448-
convert_to_diffable::Error::CreateTempfile {
456+
convert_to_diffable::Error::StreamCopy {
449457
source: err,
450458
rela_path: rela_path.to_owned(),
451459
}
@@ -533,6 +541,8 @@ impl Driver {
533541
pub fn prepare_binary_to_text_cmd(&self, path: &Path) -> Option<std::process::Command> {
534542
let command: &BStr = self.binary_to_text_command.as_ref()?.as_ref();
535543
let cmd = gix_command::prepare(gix_path::from_bstr(command).into_owned())
544+
// TODO: Add support for an actual Context, validate it *can* match Git
545+
.with_context(Default::default())
536546
.with_shell()
537547
.stdin(Stdio::null())
538548
.stdout(Stdio::piped())

gix-diff/src/blob/platform.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ pub mod prepare_diff {
184184

185185
use crate::blob::platform::Resource;
186186

187-
/// The kind of operation that was performed during the [`diff`](super::Platform::prepare_diff()) operation.
187+
/// The kind of operation that should be performed based on the configuration of the resources involved in the diff.
188188
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
189189
pub enum Operation<'a> {
190190
/// The [internal diff algorithm](imara_diff::diff) should be called with the provided arguments.
@@ -383,6 +383,7 @@ impl Platform {
383383
///
384384
/// If one of the resources is binary, the operation reports an error as such resources don't make their data available
385385
/// which is required for the external diff to run.
386+
// TODO: fix this - the diff shouldn't fail if binary (or large) files are used, just copy them into tempfiles.
386387
pub fn prepare_diff_command(
387388
&self,
388389
diff_command: BString,

gix-filter/src/eol/convert_to_git.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,10 @@ pub(crate) mod function {
5757
/// Return `true` if `buf` was written or `false` if nothing had to be done.
5858
/// Depending on the state in `buf`, `index_object` is called to write the version of `src` as stored in the index
5959
/// into the buffer and if it is a blob, or return `Ok(None)` if no such object exists.
60-
/// If renormalization is desired, let it return `Ok(None)` at all times to not let it have any influence over the
61-
/// outcome of this function.
60+
///
61+
/// *If renormalization is desired*, let it return `Ok(None)` at all times to not let it have any influence over the
62+
/// outcome of this function. Otherwise, it will check if the in-index buffer already has newlines that it would now
63+
/// want to change, and avoid doing so as what's in Git should be what's desired (except for when *renormalizing*).
6264
/// If `round_trip_check` is not `None`, round-tripping will be validated and handled accordingly.
6365
pub fn convert_to_git(
6466
src: &[u8],

gix-filter/src/pipeline/convert.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ impl Pipeline {
9191
self.options.eol_config,
9292
)?;
9393

94-
let mut in_buffer = false;
94+
let mut in_src_buffer = false;
9595
// this is just an approximation, but it's as good as it gets without reading the actual input.
9696
let would_convert_eol = eol::convert_to_git(
9797
b"\r\n",
@@ -119,13 +119,13 @@ impl Pipeline {
119119
}
120120
self.bufs.clear();
121121
read.read_to_end(&mut self.bufs.src)?;
122-
in_buffer = true;
122+
in_src_buffer = true;
123123
}
124124
}
125-
if !in_buffer && (apply_ident_filter || encoding.is_some() || would_convert_eol) {
125+
if !in_src_buffer && (apply_ident_filter || encoding.is_some() || would_convert_eol) {
126126
self.bufs.clear();
127127
src.read_to_end(&mut self.bufs.src)?;
128-
in_buffer = true;
128+
in_src_buffer = true;
129129
}
130130

131131
if let Some(encoding) = encoding {
@@ -158,7 +158,7 @@ impl Pipeline {
158158
if apply_ident_filter && ident::undo(&self.bufs.src, &mut self.bufs.dest)? {
159159
self.bufs.swap();
160160
}
161-
Ok(if in_buffer {
161+
Ok(if in_src_buffer {
162162
ToGitOutcome::Buffer(&self.bufs.src)
163163
} else {
164164
ToGitOutcome::Unchanged(src)

gix-merge/Cargo.toml

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
[package]
2+
name = "gix-merge"
3+
version = "0.0.0"
4+
repository = "https://github.com/Byron/gitoxide"
5+
license = "MIT OR Apache-2.0"
6+
description = "A crate of the gitoxide project implementing merge algorithms"
7+
authors = ["Sebastian Thiel <[email protected]>"]
8+
edition = "2021"
9+
rust-version = "1.65"
10+
11+
[lints]
12+
workspace = true
13+
14+
[lib]
15+
doctest = false
16+
17+
[features]
18+
default = ["blob"]
19+
## Enable diffing of blobs using imara-diff, which also allows for a generic rewrite tracking implementation.
20+
blob = ["dep:imara-diff", "dep:gix-filter", "dep:gix-worktree", "dep:gix-path", "dep:gix-fs", "dep:gix-command", "dep:gix-tempfile", "dep:gix-trace", "dep:gix-quote"]
21+
## Data structures implement `serde::Serialize` and `serde::Deserialize`.
22+
serde = ["dep:serde", "gix-hash/serde", "gix-object/serde"]
23+
24+
[dependencies]
25+
gix-hash = { version = "^0.14.2", path = "../gix-hash" }
26+
gix-object = { version = "^0.44.0", path = "../gix-object" }
27+
gix-filter = { version = "^0.13.0", path = "../gix-filter", optional = true }
28+
gix-worktree = { version = "^0.36.0", path = "../gix-worktree", default-features = false, features = ["attributes"], optional = true }
29+
gix-command = { version = "^0.3.9", path = "../gix-command", optional = true }
30+
gix-path = { version = "^0.10.11", path = "../gix-path", optional = true }
31+
gix-fs = { version = "^0.11.3", path = "../gix-fs", optional = true }
32+
gix-tempfile = { version = "^14.0.0", path = "../gix-tempfile", optional = true }
33+
gix-trace = { version = "^0.1.10", path = "../gix-trace", optional = true }
34+
gix-quote = { version = "^0.4.12", path = "../gix-quote", optional = true }
35+
36+
thiserror = "1.0.63"
37+
imara-diff = { version = "0.1.7", optional = true }
38+
bstr = { version = "1.5.0", default-features = false }
39+
serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"] }
40+
41+
document-features = { version = "0.2.0", optional = true }
42+
43+
[dev-dependencies]
44+
gix-testtools = { path = "../tests/tools" }
45+
pretty_assertions = "1.4.0"
46+
47+
[package.metadata.docs.rs]
48+
all-features = true
49+
features = ["document-features"]

gix-merge/LICENSE-APACHE

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../LICENSE-APACHE

gix-merge/LICENSE-MIT

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../LICENSE-MIT
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/// What to do when having to pick a side to resolve a conflict.
2+
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
3+
pub enum ResolveWith {
4+
/// Chose the ancestor to resolve a conflict.
5+
Ancestor,
6+
/// Chose our side to resolve a conflict.
7+
Ours,
8+
/// Chose their side to resolve a conflict.
9+
Theirs,
10+
}
11+
12+
/// Tell the caller of [`merge()`](function::merge) which side was picked.
13+
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
14+
pub enum Pick {
15+
/// Chose the ancestor.
16+
Ancestor,
17+
/// Chose our side.
18+
Ours,
19+
/// Chose their side.
20+
Theirs,
21+
}
22+
23+
pub(super) mod function {
24+
use crate::blob::builtin_driver::binary::{Pick, ResolveWith};
25+
use crate::blob::Resolution;
26+
27+
/// As this algorithm doesn't look at the actual data, it returns a choice solely based on logic.
28+
///
29+
/// It always results in a conflict with `current` being picked unless `on_conflict` is not `None`.
30+
pub fn merge(on_conflict: Option<ResolveWith>) -> (Pick, Resolution) {
31+
match on_conflict {
32+
None => (Pick::Ours, Resolution::Conflict),
33+
Some(resolve) => (
34+
match resolve {
35+
ResolveWith::Ours => Pick::Ours,
36+
ResolveWith::Theirs => Pick::Theirs,
37+
ResolveWith::Ancestor => Pick::Ancestor,
38+
},
39+
Resolution::Complete,
40+
),
41+
}
42+
}
43+
}
+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
use crate::blob::BuiltinDriver;
2+
3+
impl BuiltinDriver {
4+
/// Return the name of this instance.
5+
pub fn as_str(&self) -> &str {
6+
match self {
7+
BuiltinDriver::Text => "text",
8+
BuiltinDriver::Binary => "binary",
9+
BuiltinDriver::Union => "union",
10+
}
11+
}
12+
13+
/// Get all available built-in drivers.
14+
pub fn all() -> &'static [Self] {
15+
&[BuiltinDriver::Text, BuiltinDriver::Binary, BuiltinDriver::Union]
16+
}
17+
18+
/// Try to match one of our variants to `name`, case-sensitive, and return its instance.
19+
pub fn by_name(name: &str) -> Option<Self> {
20+
Self::all().iter().find(|variant| variant.as_str() == name).copied()
21+
}
22+
}
23+
24+
///
25+
pub mod binary;
26+
pub use binary::function::merge as binary;
27+
28+
///
29+
pub mod text;
30+
pub use text::function::merge as text;

0 commit comments

Comments
 (0)