gix-merge-0.2.0/.cargo_vcs_info.json 0000644 00000000147 00000000001 0012721 0 ustar {
"git": {
"sha1": "beb0ea8c4ff94c64b7773772a9d388ccb403f3c1"
},
"path_in_vcs": "gix-merge"
} gix-merge-0.2.0/CHANGELOG.md 0000644 0000000 0000000 00000051344 10461020230 0013327 0 ustar 0000000 0000000 # Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 0.2.0 (2024-12-22)
### Bug Fixes
- don't incorrectly mark as auto-resolved conflict if there was none.
Previously it was possible to mark perfectly working content merges as conflicting
if we would choose 'our' or 'their' resolution.
- when binary merges are performed, adjust the returned resolution to indicate auto-resolution.
Previously it wasn't possible to detect auto-resolution, even though it can be assumed if a
resolution mode was provided.
### New Features (BREAKING)
- replace `tree::Options::allow_lossy_resolution` with `*::tree_conflicts`.
That way it's possible to steer how to resolve tree-related conflicts while
making it possible to detect that a conflict happened.
### Bug Fixes (BREAKING)
- assure that `tree::apply_index_entries()` cannot unknowingly leave unconflicted *and* conflicted entries.
This is a massive footgun currently where incorrect usage is very likely while causing all kinds of mistakes.
### Commit Statistics
- 12 commits contributed to the release over the course of 28 calendar days.
- 28 days passed between releases.
- 4 commits were understood as [conventional](https://www.conventionalcommits.org).
- 0 issues like '(#ID)' were seen in commit messages
### Commit Details
view details
* **Uncategorized**
- Release gix-date v0.9.3, gix-object v0.46.1, gix-command v0.4.0, gix-filter v0.16.0, gix-fs v0.12.1, gix-traverse v0.43.1, gix-worktree-stream v0.18.0, gix-archive v0.18.0, gix-ref v0.49.1, gix-prompt v0.9.0, gix-url v0.28.2, gix-credentials v0.26.0, gix-diff v0.49.0, gix-dir v0.11.0, gix-revision v0.31.1, gix-merge v0.2.0, gix-pack v0.56.0, gix-odb v0.66.0, gix-shallow v0.1.0, gix-packetline v0.18.2, gix-transport v0.44.0, gix-protocol v0.47.0, gix-status v0.16.0, gix-worktree-state v0.16.0, gix v0.69.0, gitoxide-core v0.44.0, gitoxide v0.40.0, safety bump 16 crates ([`c1ba571`](https://github.com/GitoxideLabs/gitoxide/commit/c1ba5719132227410abefeb54e3032b015233e94))
- Update changelogs prior to release ([`7ea8582`](https://github.com/GitoxideLabs/gitoxide/commit/7ea85821c6999e3e6cf50a2a009904e9c38642a4))
- Merge pull request #1705 from GitoxideLabs/merge ([`520c832`](https://github.com/GitoxideLabs/gitoxide/commit/520c832cfcfb34eb7617be55ebe2719ab35595fd))
- Adapt to changes in `gix-diff` related to not tracking empty blobs anymore. ([`f53cec5`](https://github.com/GitoxideLabs/gitoxide/commit/f53cec5b2ce8aa6eeb4a3016511bbb1ac25fa2f7))
- Don't incorrectly mark as auto-resolved conflict if there was none. ([`da585db`](https://github.com/GitoxideLabs/gitoxide/commit/da585db16bae9f44e6300f31b0f784c356d5bd3f))
- Assure that `tree::apply_index_entries()` cannot unknowingly leave unconflicted *and* conflicted entries. ([`3e94b58`](https://github.com/GitoxideLabs/gitoxide/commit/3e94b58f00334392da89fb2772034efe06d3a741))
- Implement support for resolving irreconcilable tree conflicts with 'ours' or 'ancestor' ([`e487cca`](https://github.com/GitoxideLabs/gitoxide/commit/e487cca78d8e6c5b51d2614daf05c98e1469ee69))
- Merge pull request #1708 from EliahKagan/run-ci/mode ([`34efe03`](https://github.com/GitoxideLabs/gitoxide/commit/34efe03fdab97bbf5603a7ea605f37096ff1736a))
- Add missing executable bits on fixture scripts ([`ed757ea`](https://github.com/GitoxideLabs/gitoxide/commit/ed757ea0f4f80968d80c5d9d75ba49f031ee77fc))
- Replace `tree::Options::allow_lossy_resolution` with `*::tree_conflicts`. ([`1c3ba81`](https://github.com/GitoxideLabs/gitoxide/commit/1c3ba812bd3df5991a457b68a962aa1fd87fa915))
- When binary merges are performed, adjust the returned resolution to indicate auto-resolution. ([`a57192c`](https://github.com/GitoxideLabs/gitoxide/commit/a57192c0418aab2e3cd2ddb7b7a951cd3aaeb58f))
- Merge pull request #1701 from GitoxideLabs/release ([`e8b3b41`](https://github.com/GitoxideLabs/gitoxide/commit/e8b3b41dd79b8f4567670b1f89dd8867b6134e9e))
## 0.1.0 (2024-11-24)
### New Features
- provide a way to record and apply index changes.
These changes will then be applicable to an index that is created
from the written tree editor.
- when blob-merging, clarify if something would have conflicted.
- add `Conflict::is_unresolved()` as utility to see if multiple of them are considered unresolved.
- respect the `conflict-marker-size` attribute as well.
- add `tree()` and `commit()` merge support, en par with `merge-ORT` as far as tests go.
Note that this judgement of quality is based on a limited amount of partially complex
test, but it's likely that in practice there will be deviations of sorts.
Also, given the complexity of the implementation it is definitely under-tested,
but with that it's mostly en par with Git, unfortunatly.
On the bright side, some of the tests are very taxing and I'd hope this
means something for real-world quality.
- add `blob::PlatformRef::id_by_pick()` to more efficiently pick merge results.
This works by either selecting a possibly unchanged and not even loaded resource,
instead of always loading it to provide a buffer, in case the user doesn't
actually want a buffer.
Note that this also alters `buffer_by_pick()` to enforce handling of the 'buffer-too-large'
option.
### Other
- Fix code fences in gix-merge `ConflictStyle` and `Driver`
They are not Rust code (they are text with conflict markers and a
shell command, respectively) and they are not intended as doctests,
but the absence of anything on the opening line caused them to be
taken as doctests, so `cargo test --workspace --doc` would fail
with parsing errors.
(Doctests for all crates have not always been run automatically on
CI, so this was not caught when these documentation comments were
introduced in #1585.)
### New Features (BREAKING)
- Add more modes for checking for unresolved conflicts.
They aim at making it possible to know if a conflict happened that was
automatically resolved.
- add `commit::virtual_merge_base()` to produce the single merge-base to use.
This allows more flexibility in conjunction with tree-merging, as
commits as input aren't required.
This is breaking as it changes the return value of `commit()`.
- `Repository::merge_trees()` now has a fully-wrapped outcome.
That way, more attached types are used for greater convenience.
- Don't fail on big files during blob-merge, but turn them into binary merges.
Binary merges are mere choices of which side to pick, which works well for big files
as well. Git doesn't define this well during its own merges, so there is some room here.
### Bug Fixes (BREAKING)
- Adjust blob-merge baseline to also test the reverse of each operation
This also fixes an issue with blob merge computations.
It's breaking because the marker-size was reduced to `u8`.
- prefer to receive borrowed `gix_command::Context` when it's just passed on.
That way, the clone occours only when needed, without forcing the caller
to pre-emptively clone each time it's called.
### Commit Statistics
- 29 commits contributed to the release.
- 13 commits were understood as [conventional](https://www.conventionalcommits.org).
- 0 issues like '(#ID)' were seen in commit messages
### Commit Details
view details
* **Uncategorized**
- Release gix-glob v0.17.1, gix-command v0.3.11, gix-filter v0.15.0, gix-chunk v0.4.10, gix-commitgraph v0.25.1, gix-revwalk v0.17.0, gix-traverse v0.43.0, gix-worktree-stream v0.17.0, gix-archive v0.17.0, gix-config-value v0.14.10, gix-lock v15.0.1, gix-ref v0.49.0, gix-sec v0.10.10, gix-config v0.42.0, gix-prompt v0.8.9, gix-url v0.28.1, gix-credentials v0.25.1, gix-ignore v0.12.1, gix-bitmap v0.2.13, gix-index v0.37.0, gix-worktree v0.38.0, gix-diff v0.48.0, gix-discover v0.37.0, gix-pathspec v0.8.1, gix-dir v0.10.0, gix-mailmap v0.25.1, gix-revision v0.31.0, gix-merge v0.1.0, gix-negotiate v0.17.0, gix-pack v0.55.0, gix-odb v0.65.0, gix-packetline v0.18.1, gix-transport v0.43.1, gix-protocol v0.46.1, gix-refspec v0.27.0, gix-status v0.15.0, gix-submodule v0.16.0, gix-worktree-state v0.15.0, gix v0.68.0, gix-fsck v0.8.0, gitoxide-core v0.43.0, gitoxide v0.39.0 ([`4000197`](https://github.com/GitoxideLabs/gitoxide/commit/4000197ecc8cf1a5d79361620e4c114f86476703))
- Release gix-date v0.9.2, gix-actor v0.33.1, gix-hash v0.15.1, gix-features v0.39.1, gix-validate v0.9.2, gix-object v0.46.0, gix-path v0.10.13, gix-quote v0.4.14, gix-attributes v0.23.1, gix-packetline-blocking v0.18.1, gix-filter v0.15.0, gix-chunk v0.4.10, gix-commitgraph v0.25.1, gix-revwalk v0.17.0, gix-traverse v0.43.0, gix-worktree-stream v0.17.0, gix-archive v0.17.0, gix-config-value v0.14.10, gix-lock v15.0.1, gix-ref v0.49.0, gix-config v0.42.0, gix-prompt v0.8.9, gix-url v0.28.1, gix-credentials v0.25.1, gix-bitmap v0.2.13, gix-index v0.37.0, gix-worktree v0.38.0, gix-diff v0.48.0, gix-discover v0.37.0, gix-pathspec v0.8.1, gix-dir v0.10.0, gix-mailmap v0.25.1, gix-revision v0.31.0, gix-merge v0.1.0, gix-negotiate v0.17.0, gix-pack v0.55.0, gix-odb v0.65.0, gix-packetline v0.18.1, gix-transport v0.43.1, gix-protocol v0.46.1, gix-refspec v0.27.0, gix-status v0.15.0, gix-submodule v0.16.0, gix-worktree-state v0.15.0, gix v0.68.0, gix-fsck v0.8.0, gitoxide-core v0.43.0, gitoxide v0.39.0, safety bump 25 crates ([`8ce4912`](https://github.com/GitoxideLabs/gitoxide/commit/8ce49129a75e21346ceedf7d5f87fa3a34b024e1))
- Prepare changelogs prior to release ([`bc9d994`](https://github.com/GitoxideLabs/gitoxide/commit/bc9d9943e8499a76fc47a05b63ac5c684187d1ae))
- Merge pull request #1661 from GitoxideLabs/merge ([`0b7abfb`](https://github.com/GitoxideLabs/gitoxide/commit/0b7abfbdebe8c5ab30b89499a70dd7727de41184))
- Provide a way to record and apply index changes. ([`3ee8b62`](https://github.com/GitoxideLabs/gitoxide/commit/3ee8b62dd025d6fdb0d9929dec7a561fa576f545))
- Add more modes for checking for unresolved conflicts. ([`aff76f2`](https://github.com/GitoxideLabs/gitoxide/commit/aff76f291a52fc6806944d72d249a8bd1b804c39))
- When blob-merging, clarify if something would have conflicted. ([`09213bc`](https://github.com/GitoxideLabs/gitoxide/commit/09213bc1b2aa725af1571dff040415772e844c3a))
- Merge pull request #1662 from paolobarbolini/thiserror-v2 ([`7a40648`](https://github.com/GitoxideLabs/gitoxide/commit/7a406481b072728cec089d7c05364f9dbba335a2))
- Upgrade thiserror to v2.0.0 ([`0f0e4fe`](https://github.com/GitoxideLabs/gitoxide/commit/0f0e4fe121932a8a6302cf950b3caa4c8608fb61))
- Merge pull request #1658 from GitoxideLabs/merge ([`905e5b4`](https://github.com/GitoxideLabs/gitoxide/commit/905e5b42a6163f92edef8fab82d97aeb6f17cf06))
- Add `commit::virtual_merge_base()` to produce the single merge-base to use. ([`9d43b75`](https://github.com/GitoxideLabs/gitoxide/commit/9d43b753a225482645b22e4151bf7dc192c8c082))
- Merge pull request #1654 from EliahKagan/doctest-workspace ([`1411289`](https://github.com/GitoxideLabs/gitoxide/commit/141128942c26bd63fc6855e5137b98f8da814446))
- Fix code fences in gix-merge `ConflictStyle` and `Driver` ([`2fdbcfe`](https://github.com/GitoxideLabs/gitoxide/commit/2fdbcfe17cdcc480e320582d7c6b48f8b615bf3b))
- Merge pull request #1651 from GitoxideLabs/merge ([`a876533`](https://github.com/GitoxideLabs/gitoxide/commit/a8765330fc16997dee275866b18a128dec1c5d55))
- `Repository::merge_trees()` now has a fully-wrapped outcome. ([`1d2262f`](https://github.com/GitoxideLabs/gitoxide/commit/1d2262f2ca416e3c22f9601e7eab11f3372b2128))
- Add `Conflict::is_unresolved()` as utility to see if multiple of them are considered unresolved. ([`9e106c4`](https://github.com/GitoxideLabs/gitoxide/commit/9e106c4ab7ceea091cd8baef99a480739bd53c9d))
- Remove a TODO that turned out to be unnecessary. ([`5b428a9`](https://github.com/GitoxideLabs/gitoxide/commit/5b428a9e931b19622ae76c25bfb1fe882744cd1f))
- Merge pull request #1652 from EliahKagan/run-ci/chmod ([`8e99eba`](https://github.com/GitoxideLabs/gitoxide/commit/8e99eba2a284b35b5e9bcb97e47bfbbafc3df5d1))
- Update tree-baseline archive ([`ab45415`](https://github.com/GitoxideLabs/gitoxide/commit/ab45415a0119cbdcbd2fcbe8c7b6e2580432f705))
- Set +x in index in added-file-changed-content-and-mode ([`6faf11a`](https://github.com/GitoxideLabs/gitoxide/commit/6faf11a0e0916fdf03e4532d28e40674c369bf9b))
- Set +x in index in same-rename-different-mode baseline ([`041bdde`](https://github.com/GitoxideLabs/gitoxide/commit/041bddecacb23b4925bcd9bcdc4a86aec2ea719d))
- Merge pull request #1618 from GitoxideLabs/merge ([`3fb989b`](https://github.com/GitoxideLabs/gitoxide/commit/3fb989be21c739bbfeac93953c1685e7c6cd2106))
- Respect the `conflict-marker-size` attribute as well. ([`bd91d6a`](https://github.com/GitoxideLabs/gitoxide/commit/bd91d6ae97d1981a2366136040407590f64fdad4))
- Add `tree()` and `commit()` merge support, en par with `merge-ORT` as far as tests go. ([`4b1764c`](https://github.com/GitoxideLabs/gitoxide/commit/4b1764ca9148e08ae9f11bca68e0689b12bc8c80))
- Adjust blob-merge baseline to also test the reverse of each operation ([`de1cfb6`](https://github.com/GitoxideLabs/gitoxide/commit/de1cfb6a9caf5ac086c6411824835c75e888e2d7))
- Add `blob::PlatformRef::id_by_pick()` to more efficiently pick merge results. ([`dd99991`](https://github.com/GitoxideLabs/gitoxide/commit/dd99991ec2bfb07ef571769abc32f1b35122d5ca))
- Don't fail on big files during blob-merge, but turn them into binary merges. ([`c1cf08c`](https://github.com/GitoxideLabs/gitoxide/commit/c1cf08cc47f98abda017544b1791ab3b4463cc77))
- Prefer to receive borrowed `gix_command::Context` when it's just passed on. ([`78a5355`](https://github.com/GitoxideLabs/gitoxide/commit/78a535572643a0657348aea3c2fed0123505f7fe))
- Merge pull request #1642 from GitoxideLabs/new-release ([`db5c9cf`](https://github.com/GitoxideLabs/gitoxide/commit/db5c9cfce93713b4b3e249cff1f8cc1ef146f470))
## v0.0.0 (2024-10-22)
### Other
- Update gitoxide repository URLs
This updates `Byron/gitoxide` URLs to `GitoxideLabs/gitoxide` in:
- Markdown documentation, except changelogs and other such files
where such changes should not be made.
- Documentation comments (in .rs files).
- Manifest (.toml) files, for the value of the `repository` key.
- The comments appearing at the top of a sample hook that contains
a repository URL as an example.
When making these changes, I also allowed my editor to remove
trailing whitespace in any lines in files already being edited
(since, in this case, there was no disadvantage to allowing this).
The gitoxide repository URL changed when the repository was moved
into the recently created GitHub organization `GitoxideLabs`, as
detailed in #1406. Please note that, although I believe updating
the URLs to their new canonical values is useful, this is not
needed to fix any broken links, since `Byron/gitoxide` URLs
redirect (and hopefully will always redirect) to the coresponding
`GitoxideLabs/gitoxide` URLs.
While this change should not break any URLs, some affected URLs
were already broken. This updates them, but they are still broken.
They will be fixed in a subsequent commit.
This also does not update `Byron/gitoxide` URLs in test fixtures
or test cases, nor in the `Makefile`. (It may make sense to change
some of those too, but it is not really a documentation change.)
### Commit Statistics
- 15 commits contributed to the release over the course of 26 calendar days.
- 1 commit was understood as [conventional](https://www.conventionalcommits.org).
- 0 issues like '(#ID)' were seen in commit messages
### Commit Details
view details
* **Uncategorized**
- Release gix-merge v0.0.0, gix-negotiate v0.16.0, gix-pack v0.54.0, gix-odb v0.64.0, gix-packetline v0.18.0, gix-transport v0.43.0, gix-protocol v0.46.0, gix-revision v0.30.0, gix-refspec v0.26.0, gix-status v0.14.0, gix-submodule v0.15.0, gix-worktree-state v0.14.0, gix v0.67.0, gix-fsck v0.7.0, gitoxide-core v0.42.0, gitoxide v0.38.0 ([`f1364dc`](https://github.com/GitoxideLabs/gitoxide/commit/f1364dcb8aa66e3d8730e38445b045c5b63c56e6))
- Add new changelog for gix-merge ([`fa3e260`](https://github.com/GitoxideLabs/gitoxide/commit/fa3e2600d7e39011f1d7f410249ebd0426a348a8))
- Release gix-date v0.9.1, gix-utils v0.1.13, gix-actor v0.33.0, gix-hash v0.15.0, gix-trace v0.1.11, gix-features v0.39.0, gix-hashtable v0.6.0, gix-validate v0.9.1, gix-object v0.45.0, gix-path v0.10.12, gix-glob v0.17.0, gix-quote v0.4.13, gix-attributes v0.23.0, gix-command v0.3.10, gix-packetline-blocking v0.18.0, gix-filter v0.14.0, gix-fs v0.12.0, gix-chunk v0.4.9, gix-commitgraph v0.25.0, gix-revwalk v0.16.0, gix-traverse v0.42.0, gix-worktree-stream v0.16.0, gix-archive v0.16.0, gix-config-value v0.14.9, gix-tempfile v15.0.0, gix-lock v15.0.0, gix-ref v0.48.0, gix-sec v0.10.9, gix-config v0.41.0, gix-prompt v0.8.8, gix-url v0.28.0, gix-credentials v0.25.0, gix-ignore v0.12.0, gix-bitmap v0.2.12, gix-index v0.36.0, gix-worktree v0.37.0, gix-diff v0.47.0, gix-discover v0.36.0, gix-pathspec v0.8.0, gix-dir v0.9.0, gix-mailmap v0.25.0, gix-merge v0.0.0, gix-negotiate v0.16.0, gix-pack v0.54.0, gix-odb v0.64.0, gix-packetline v0.18.0, gix-transport v0.43.0, gix-protocol v0.46.0, gix-revision v0.30.0, gix-refspec v0.26.0, gix-status v0.14.0, gix-submodule v0.15.0, gix-worktree-state v0.14.0, gix v0.67.0, gix-fsck v0.7.0, gitoxide-core v0.42.0, gitoxide v0.38.0, safety bump 41 crates ([`3f7e8ee`](https://github.com/GitoxideLabs/gitoxide/commit/3f7e8ee2c5107aec009eada1a05af7941da9cb4d))
- Merge pull request #1624 from EliahKagan/update-repo-url ([`795962b`](https://github.com/GitoxideLabs/gitoxide/commit/795962b107d86f58b1f7c75006da256d19cc80ad))
- Update gitoxide repository URLs ([`64ff0a7`](https://github.com/GitoxideLabs/gitoxide/commit/64ff0a77062d35add1a2dd422bb61075647d1a36))
- Merge pull request #1612 from Byron/merge ([`37c1e4c`](https://github.com/GitoxideLabs/gitoxide/commit/37c1e4c919382c9d213bd5ca299ed659d63ab45d))
- Add some performance traces for blob-merges ([`b25fe4d`](https://github.com/GitoxideLabs/gitoxide/commit/b25fe4d052250ddace9a118b2247537b2a7fa09e))
- Merge pull request #1611 from Byron/merge ([`5ffccd2`](https://github.com/GitoxideLabs/gitoxide/commit/5ffccd2f08d70576347e3ae17a66ca5a60f1d81c))
- Make `blob::Platform::filter_mode` public. ([`b26eb26`](https://github.com/GitoxideLabs/gitoxide/commit/b26eb2618c1764f699530e251ddef4e6cf456ddd))
- Merge pull request #1585 from Byron/merge ([`2261de4`](https://github.com/GitoxideLabs/gitoxide/commit/2261de470aeb77be080f9e423e1513bde85d9cc0))
- Add platform tests and implementation ([`eb37dc3`](https://github.com/GitoxideLabs/gitoxide/commit/eb37dc36d8c42f5a7714c641244ce4a13111b0a1))
- Add all relevant tests for the merge processing pipeline ([`a6f3e30`](https://github.com/GitoxideLabs/gitoxide/commit/a6f3e30017343c01ba61c49fe74ffc69e443a33c))
- Implement `text` and `binary` merge algorithms, also with baseline tests for correctness. ([`0762846`](https://github.com/GitoxideLabs/gitoxide/commit/07628465a0a3f047ec809d287c9a4567b4acd607))
- Sketch the entire API surface to capture all parts of blob-merges ([`9efa09f`](https://github.com/GitoxideLabs/gitoxide/commit/9efa09f10d042dc4d5db4edf4589594450a30b31))
- Add the `gix-merge` crate for capturing merge algorithms ([`65efcb7`](https://github.com/GitoxideLabs/gitoxide/commit/65efcb7624031ae478056fbb49a07ef176f0c96b))
gix-merge-0.2.0/Cargo.toml 0000644 00000010662 00000000001 0010722 0 ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
rust-version = "1.65"
name = "gix-merge"
version = "0.2.0"
authors = ["Sebastian Thiel "]
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "A crate of the gitoxide project implementing merge algorithms"
readme = false
license = "MIT OR Apache-2.0"
repository = "https://github.com/GitoxideLabs/gitoxide"
[package.metadata.docs.rs]
all-features = true
features = ["document-features"]
[lib]
name = "gix_merge"
path = "src/lib.rs"
doctest = false
[[test]]
name = "merge"
path = "tests/merge/main.rs"
[dependencies.bstr]
version = "1.5.0"
default-features = false
[dependencies.document-features]
version = "0.2.0"
optional = true
[dependencies.gix-command]
version = "^0.4.0"
[dependencies.gix-diff]
version = "^0.49.0"
features = ["blob"]
default-features = false
[dependencies.gix-filter]
version = "^0.16.0"
[dependencies.gix-fs]
version = "^0.12.1"
[dependencies.gix-hash]
version = "^0.15.1"
[dependencies.gix-index]
version = "^0.37.0"
[dependencies.gix-object]
version = "^0.46.1"
[dependencies.gix-path]
version = "^0.10.13"
[dependencies.gix-quote]
version = "^0.4.14"
[dependencies.gix-revision]
version = "^0.31.1"
features = ["merge_base"]
default-features = false
[dependencies.gix-revwalk]
version = "^0.17.0"
[dependencies.gix-tempfile]
version = "^15.0.0"
[dependencies.gix-trace]
version = "^0.1.11"
[dependencies.gix-worktree]
version = "^0.38.0"
features = ["attributes"]
default-features = false
[dependencies.imara-diff]
version = "0.1.7"
[dependencies.serde]
version = "1.0.114"
features = ["derive"]
optional = true
default-features = false
[dependencies.thiserror]
version = "2.0.0"
[dev-dependencies.gix-utils]
version = "^0.1.12"
[dev-dependencies.pretty_assertions]
version = "1.4.0"
[dev-dependencies.termtree]
version = "0.5.1"
[features]
serde = [
"dep:serde",
"gix-hash/serde",
"gix-object/serde",
]
[lints.clippy]
bool_to_int_with_if = "allow"
borrow_as_ptr = "allow"
cast_lossless = "allow"
cast_possible_truncation = "allow"
cast_possible_wrap = "allow"
cast_precision_loss = "allow"
cast_sign_loss = "allow"
checked_conversions = "allow"
copy_iterator = "allow"
default_trait_access = "allow"
doc_markdown = "allow"
empty_docs = "allow"
enum_glob_use = "allow"
explicit_deref_methods = "allow"
explicit_into_iter_loop = "allow"
explicit_iter_loop = "allow"
filter_map_next = "allow"
fn_params_excessive_bools = "allow"
from_iter_instead_of_collect = "allow"
if_not_else = "allow"
ignored_unit_patterns = "allow"
implicit_clone = "allow"
inconsistent_struct_constructor = "allow"
inefficient_to_string = "allow"
inline_always = "allow"
items_after_statements = "allow"
iter_not_returning_iterator = "allow"
iter_without_into_iter = "allow"
manual_assert = "allow"
manual_is_variant_and = "allow"
manual_let_else = "allow"
manual_string_new = "allow"
many_single_char_names = "allow"
match_bool = "allow"
match_same_arms = "allow"
match_wild_err_arm = "allow"
match_wildcard_for_single_variants = "allow"
missing_errors_doc = "allow"
missing_panics_doc = "allow"
module_name_repetitions = "allow"
must_use_candidate = "allow"
mut_mut = "allow"
naive_bytecount = "allow"
needless_for_each = "allow"
needless_pass_by_value = "allow"
needless_raw_string_hashes = "allow"
no_effect_underscore_binding = "allow"
option_option = "allow"
range_plus_one = "allow"
redundant_else = "allow"
return_self_not_must_use = "allow"
should_panic_without_expect = "allow"
similar_names = "allow"
single_match_else = "allow"
stable_sort_primitive = "allow"
struct_excessive_bools = "allow"
struct_field_names = "allow"
too_long_first_doc_paragraph = "allow"
too_many_lines = "allow"
transmute_ptr_to_ptr = "allow"
trivially_copy_pass_by_ref = "allow"
unnecessary_join = "allow"
unnecessary_wraps = "allow"
unreadable_literal = "allow"
unused_self = "allow"
used_underscore_binding = "allow"
wildcard_imports = "allow"
[lints.clippy.pedantic]
level = "warn"
priority = -1
[lints.rust]
gix-merge-0.2.0/Cargo.toml.orig 0000644 0000000 0000000 00000004027 10461020230 0014401 0 ustar 0000000 0000000 [package]
name = "gix-merge"
version = "0.2.0"
repository = "https://github.com/GitoxideLabs/gitoxide"
license = "MIT OR Apache-2.0"
description = "A crate of the gitoxide project implementing merge algorithms"
authors = ["Sebastian Thiel "]
edition = "2021"
rust-version = "1.65"
[lints]
workspace = true
[lib]
doctest = false
[features]
## Data structures implement `serde::Serialize` and `serde::Deserialize`.
serde = ["dep:serde", "gix-hash/serde", "gix-object/serde"]
[dependencies]
gix-hash = { version = "^0.15.1", path = "../gix-hash" }
gix-object = { version = "^0.46.1", path = "../gix-object" }
gix-filter = { version = "^0.16.0", path = "../gix-filter" }
gix-worktree = { version = "^0.38.0", path = "../gix-worktree", default-features = false, features = ["attributes"] }
gix-command = { version = "^0.4.0", path = "../gix-command" }
gix-path = { version = "^0.10.13", path = "../gix-path" }
gix-fs = { version = "^0.12.1", path = "../gix-fs" }
gix-tempfile = { version = "^15.0.0", path = "../gix-tempfile" }
gix-trace = { version = "^0.1.11", path = "../gix-trace" }
gix-quote = { version = "^0.4.14", path = "../gix-quote" }
gix-revision = { version = "^0.31.1", path = "../gix-revision", default-features = false, features = ["merge_base"] }
gix-revwalk = { version = "^0.17.0", path = "../gix-revwalk" }
gix-diff = { version = "^0.49.0", path = "../gix-diff", default-features = false, features = ["blob"] }
gix-index = { version = "^0.37.0", path = "../gix-index" }
thiserror = "2.0.0"
imara-diff = { version = "0.1.7" }
bstr = { version = "1.5.0", default-features = false }
serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"] }
document-features = { version = "0.2.0", optional = true }
[dev-dependencies]
gix-testtools = { path = "../tests/tools" }
gix-odb = { path = "../gix-odb" }
gix-utils = { version = "^0.1.12", path = "../gix-utils" }
termtree = "0.5.1"
pretty_assertions = "1.4.0"
[package.metadata.docs.rs]
all-features = true
features = ["document-features"]
gix-merge-0.2.0/LICENSE-APACHE 0000644 0000000 0000000 00000024746 10461020230 0013450 0 ustar 0000000 0000000
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
gix-merge-0.2.0/LICENSE-MIT 0000644 0000000 0000000 00000001777 10461020230 0013157 0 ustar 0000000 0000000 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
gix-merge-0.2.0/src/blob/builtin_driver/binary.rs 0000644 0000000 0000000 00000003171 10461020230 0020071 0 ustar 0000000 0000000 /// What to do when having to pick a side to resolve a conflict.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum ResolveWith {
/// Chose the ancestor to resolve a conflict.
Ancestor,
/// Chose our side to resolve a conflict.
Ours,
/// Chose their side to resolve a conflict.
Theirs,
}
/// Tell the caller of [`merge()`](function::merge) which side was picked.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum Pick {
/// Chose the ancestor.
Ancestor,
/// Chose our side.
Ours,
/// Chose their side.
Theirs,
}
pub(super) mod function {
use crate::blob::builtin_driver::binary::{Pick, ResolveWith};
use crate::blob::Resolution;
/// As this algorithm doesn't look at the actual data, it returns a choice solely based on logic.
/// This also means that the caller has to assure this only gets called if the input *doesn't* match.
///
/// It always results in a conflict with `current` being picked unless `on_conflict` is not `None`,
/// which is when we always return [`Resolution::CompleteWithAutoResolvedConflict`].
pub fn merge(on_conflict: Option) -> (Pick, Resolution) {
match on_conflict {
None => (Pick::Ours, Resolution::Conflict),
Some(resolve) => (
match resolve {
ResolveWith::Ours => Pick::Ours,
ResolveWith::Theirs => Pick::Theirs,
ResolveWith::Ancestor => Pick::Ancestor,
},
Resolution::CompleteWithAutoResolvedConflict,
),
}
}
}
gix-merge-0.2.0/src/blob/builtin_driver/mod.rs 0000644 0000000 0000000 00000001462 10461020230 0017365 0 ustar 0000000 0000000 use crate::blob::BuiltinDriver;
impl BuiltinDriver {
/// Return the name of this instance.
pub fn as_str(&self) -> &str {
match self {
BuiltinDriver::Text => "text",
BuiltinDriver::Binary => "binary",
BuiltinDriver::Union => "union",
}
}
/// Get all available built-in drivers.
pub fn all() -> &'static [Self] {
&[BuiltinDriver::Text, BuiltinDriver::Binary, BuiltinDriver::Union]
}
/// Try to match one of our variants to `name`, case-sensitive, and return its instance.
pub fn by_name(name: &str) -> Option {
Self::all().iter().find(|variant| variant.as_str() == name).copied()
}
}
///
pub mod binary;
pub use binary::function::merge as binary;
///
pub mod text;
pub use text::function::merge as text;
gix-merge-0.2.0/src/blob/builtin_driver/text/function.rs 0000644 0000000 0000000 00000031043 10461020230 0021415 0 ustar 0000000 0000000 use crate::blob::builtin_driver::text::utils::{
assure_ends_with_nl, contains_lines, detect_line_ending, detect_line_ending_or_nl, fill_ancestor,
hunks_differ_in_diff3, take_intersecting, tokens, write_ancestor, write_conflict_marker, write_hunks,
zealously_contract_hunks, CollectHunks, Hunk, Side,
};
use crate::blob::builtin_driver::text::{Conflict, ConflictStyle, Labels, Options};
use crate::blob::Resolution;
use std::ops::Range;
/// Merge `current` and `other` with `ancestor` as base according to `opts`.
///
/// Use `labels` to annotate conflict sections.
///
/// `input` is for reusing memory for lists of tokens, but note that it grows indefinitely
/// while tokens for `current`, `ancestor` and `other` are added.
/// Place the merged result in `out` (cleared before use) and return the resolution.
///
/// # Important
///
/// *The caller* is responsible for clearing `input`, otherwise tokens will accumulate.
/// This idea is to save time if the input is known to be very similar.
#[allow(clippy::too_many_arguments)]
pub fn merge<'a>(
out: &mut Vec,
input: &mut imara_diff::intern::InternedInput<&'a [u8]>,
Labels {
ancestor: ancestor_label,
current: current_label,
other: other_label,
}: Labels<'_>,
current: &'a [u8],
ancestor: &'a [u8],
other: &'a [u8],
Options {
diff_algorithm,
conflict,
}: Options,
) -> Resolution {
out.clear();
input.update_before(tokens(ancestor));
input.update_after(tokens(current));
let hunks = imara_diff::diff(
diff_algorithm,
input,
CollectHunks {
side: Side::Current,
hunks: Default::default(),
},
);
let current_tokens = std::mem::take(&mut input.after);
input.update_after(tokens(other));
let mut hunks = imara_diff::diff(
diff_algorithm,
input,
CollectHunks {
side: Side::Other,
hunks,
},
);
if hunks.is_empty() {
write_ancestor(input, 0, input.before.len(), out);
return Resolution::Complete;
}
hunks.sort_by(|a, b| a.before.start.cmp(&b.before.start));
let mut hunks = hunks.into_iter().peekable();
let mut intersecting = Vec::new();
let mut ancestor_integrated_until = 0;
let mut resolution = Resolution::Complete;
let mut current_hunks = Vec::with_capacity(2);
while take_intersecting(&mut hunks, &mut current_hunks, &mut intersecting).is_some() {
if intersecting.is_empty() {
let hunk = current_hunks.pop().expect("always pushed during intersection check");
write_ancestor(input, ancestor_integrated_until, hunk.before.start as usize, out);
ancestor_integrated_until = hunk.before.end;
write_hunks(std::slice::from_ref(&hunk), input, ¤t_tokens, out);
continue;
}
let filled_hunks_side = current_hunks.first().expect("at least one hunk").side;
{
let filled_hunks_range = before_range_from_hunks(¤t_hunks);
let intersecting_range = before_range_from_hunks(&intersecting);
let extended_range = filled_hunks_range.start..intersecting_range.end.max(filled_hunks_range.end);
fill_ancestor(&extended_range, &mut current_hunks);
fill_ancestor(&extended_range, &mut intersecting);
}
match conflict {
Conflict::Keep { style, marker_size } => {
let marker_size = marker_size.get();
let (hunks_front_and_back, num_hunks_front) = match style {
ConflictStyle::Merge | ConflictStyle::ZealousDiff3 => {
zealously_contract_hunks(&mut current_hunks, &mut intersecting, input, ¤t_tokens)
}
ConflictStyle::Diff3 => (Vec::new(), 0),
};
let (our_hunks, their_hunks) = match filled_hunks_side {
Side::Current => (¤t_hunks, &intersecting),
Side::Other => (&intersecting, ¤t_hunks),
Side::Ancestor => {
unreachable!("initial hunks are never ancestors")
}
};
let (front_hunks, back_hunks) = hunks_front_and_back.split_at(num_hunks_front);
let first_hunk = first_hunk(front_hunks, our_hunks, their_hunks, back_hunks);
let last_hunk = last_hunk(front_hunks, our_hunks, their_hunks, back_hunks);
write_ancestor(input, ancestor_integrated_until, first_hunk.before.start as usize, out);
write_hunks(front_hunks, input, ¤t_tokens, out);
if their_hunks.is_empty() {
write_hunks(our_hunks, input, ¤t_tokens, out);
} else if our_hunks.is_empty() {
write_hunks(their_hunks, input, ¤t_tokens, out);
} else {
// DEVIATION: this makes tests (mostly) pass, but probably is very different from what Git does.
let hunk_storage;
let nl = detect_line_ending(
if front_hunks.is_empty() {
hunk_storage = Hunk {
before: ancestor_integrated_until..first_hunk.before.start,
after: Default::default(),
side: Side::Ancestor,
};
std::slice::from_ref(&hunk_storage)
} else {
front_hunks
},
input,
¤t_tokens,
)
.or_else(|| detect_line_ending(our_hunks, input, ¤t_tokens))
.unwrap_or(b"\n".into());
match style {
ConflictStyle::Merge => {
if contains_lines(our_hunks) || contains_lines(their_hunks) {
resolution = Resolution::Conflict;
write_conflict_marker(out, b'<', current_label, marker_size, nl);
write_hunks(our_hunks, input, ¤t_tokens, out);
write_conflict_marker(out, b'=', None, marker_size, nl);
write_hunks(their_hunks, input, ¤t_tokens, out);
write_conflict_marker(out, b'>', other_label, marker_size, nl);
}
}
ConflictStyle::Diff3 | ConflictStyle::ZealousDiff3 => {
if contains_lines(our_hunks) || contains_lines(their_hunks) {
if hunks_differ_in_diff3(style, our_hunks, their_hunks, input, ¤t_tokens) {
resolution = Resolution::Conflict;
write_conflict_marker(out, b'<', current_label, marker_size, nl);
write_hunks(our_hunks, input, ¤t_tokens, out);
let ancestor_hunk = Hunk {
before: first_hunk.before.start..last_hunk.before.end,
after: Default::default(),
side: Side::Ancestor,
};
let ancestor_hunk = std::slice::from_ref(&ancestor_hunk);
let ancestor_nl = detect_line_ending_or_nl(ancestor_hunk, input, ¤t_tokens);
write_conflict_marker(out, b'|', ancestor_label, marker_size, ancestor_nl);
write_hunks(ancestor_hunk, input, ¤t_tokens, out);
write_conflict_marker(out, b'=', None, marker_size, nl);
write_hunks(their_hunks, input, ¤t_tokens, out);
write_conflict_marker(out, b'>', other_label, marker_size, nl);
} else {
write_hunks(our_hunks, input, ¤t_tokens, out);
}
}
}
}
}
write_hunks(back_hunks, input, ¤t_tokens, out);
ancestor_integrated_until = last_hunk.before.end;
}
Conflict::ResolveWithOurs | Conflict::ResolveWithTheirs => {
let (our_hunks, their_hunks) = match filled_hunks_side {
Side::Current => (¤t_hunks, &intersecting),
Side::Other => (&intersecting, ¤t_hunks),
Side::Ancestor => {
unreachable!("initial hunks are never ancestors")
}
};
if hunks_differ_in_diff3(ConflictStyle::Diff3, our_hunks, their_hunks, input, ¤t_tokens) {
resolution = Resolution::CompleteWithAutoResolvedConflict;
}
let hunks_to_write = if conflict == Conflict::ResolveWithOurs {
our_hunks
} else {
their_hunks
};
if let Some(first_hunk) = hunks_to_write.first() {
write_ancestor(input, ancestor_integrated_until, first_hunk.before.start as usize, out);
}
write_hunks(hunks_to_write, input, ¤t_tokens, out);
if let Some(last_hunk) = hunks_to_write.last() {
ancestor_integrated_until = last_hunk.before.end;
}
}
Conflict::ResolveWithUnion => {
let (hunks_front_and_back, num_hunks_front) =
zealously_contract_hunks(&mut current_hunks, &mut intersecting, input, ¤t_tokens);
let (our_hunks, their_hunks) = match filled_hunks_side {
Side::Current => (¤t_hunks, &intersecting),
Side::Other => (&intersecting, ¤t_hunks),
Side::Ancestor => {
unreachable!("initial hunks are never ancestors")
}
};
if hunks_differ_in_diff3(ConflictStyle::Diff3, our_hunks, their_hunks, input, ¤t_tokens) {
resolution = Resolution::CompleteWithAutoResolvedConflict;
}
let (front_hunks, back_hunks) = hunks_front_and_back.split_at(num_hunks_front);
let first_hunk = first_hunk(front_hunks, our_hunks, their_hunks, back_hunks);
write_ancestor(input, ancestor_integrated_until, first_hunk.before.start as usize, out);
write_hunks(front_hunks, input, ¤t_tokens, out);
assure_ends_with_nl(out, detect_line_ending_or_nl(front_hunks, input, ¤t_tokens));
write_hunks(our_hunks, input, ¤t_tokens, out);
assure_ends_with_nl(out, detect_line_ending_or_nl(our_hunks, input, ¤t_tokens));
write_hunks(their_hunks, input, ¤t_tokens, out);
if !back_hunks.is_empty() {
assure_ends_with_nl(out, detect_line_ending_or_nl(their_hunks, input, ¤t_tokens));
}
write_hunks(back_hunks, input, ¤t_tokens, out);
let last_hunk = last_hunk(front_hunks, our_hunks, their_hunks, back_hunks);
ancestor_integrated_until = last_hunk.before.end;
}
}
}
write_ancestor(input, ancestor_integrated_until, input.before.len(), out);
resolution
}
fn first_hunk<'a>(front: &'a [Hunk], ours: &'a [Hunk], theirs: &'a [Hunk], back: &'a [Hunk]) -> &'a Hunk {
front
.first()
.or(ours.first())
.or(theirs.first())
.or(back.first())
.expect("at least one hunk - we aborted if there are none anywhere")
}
/// Note that last-hunk could be [`first_hunk()`], so the hunk must only be used accordingly.
fn last_hunk<'a>(front: &'a [Hunk], ours: &'a [Hunk], theirs: &'a [Hunk], back: &'a [Hunk]) -> &'a Hunk {
back.last()
.or(theirs.last())
.or(ours.last())
.or(front.last())
.expect("at least one hunk - we aborted if there are none anywhere")
}
fn before_range_from_hunks(hunks: &[Hunk]) -> Range {
hunks
.first()
.zip(hunks.last())
.map(|(f, l)| f.before.start..l.before.end)
.expect("at least one entry")
}
gix-merge-0.2.0/src/blob/builtin_driver/text/mod.rs 0000644 0000000 0000000 00000010046 10461020230 0020347 0 ustar 0000000 0000000 use bstr::BStr;
use std::num::NonZeroU8;
/// The way the built-in [text driver](crate::blob::BuiltinDriver::Text) will express
/// merge conflicts in the resulting file.
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum ConflictStyle {
/// Only show the zealously minified conflicting lines of the local changes and the incoming (other) changes,
/// hiding the base version entirely.
///
/// ```text
/// line1-changed-by-both
/// <<<<<<< local
/// line2-to-be-changed-in-incoming
/// =======
/// line2-changed
/// >>>>>>> incoming
/// ```
#[default]
Merge,
/// Show non-minimized hunks of local changes, the base, and the incoming (other) changes.
///
/// This mode does not hide any information.
///
/// ```text
/// <<<<<<< local
/// line1-changed-by-both
/// line2-to-be-changed-in-incoming
/// ||||||| 9a8d80c
/// line1-to-be-changed-by-both
/// line2-to-be-changed-in-incoming
/// =======
/// line1-changed-by-both
/// line2-changed
/// >>>>>>> incoming
/// ```
Diff3,
/// Like [`Diff3](Self::Diff3), but will show *minimized* hunks of local change and the incoming (other) changes,
/// as well as non-minimized hunks of the base.
///
/// ```text
/// line1-changed-by-both
/// <<<<<<< local
/// line2-to-be-changed-in-incoming
/// ||||||| 9a8d80c
/// line1-to-be-changed-by-both
/// line2-to-be-changed-in-incoming
/// =======
/// line2-changed
/// >>>>>>> incoming
/// ```
ZealousDiff3,
}
/// The set of labels to annotate conflict markers with.
///
/// That way it becomes clearer where the content of conflicts are originating from.
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq)]
pub struct Labels<'a> {
/// The label for the common *ancestor*.
pub ancestor: Option<&'a BStr>,
/// The label for the *current* (or *our*) side.
pub current: Option<&'a BStr>,
/// The label for the *other* (or *their*) side.
pub other: Option<&'a BStr>,
}
/// Options for the builtin [text driver](crate::blob::BuiltinDriver::Text).
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Options {
/// Determine of the diff will be performed.
/// Defaults to [`imara_diff::Algorithm::Myers`].
pub diff_algorithm: imara_diff::Algorithm,
/// Decide what to do to automatically resolve conflicts, or to keep them.
pub conflict: Conflict,
}
impl Default for Options {
fn default() -> Self {
Options {
conflict: Default::default(),
diff_algorithm: imara_diff::Algorithm::Myers,
}
}
}
/// What to do to resolve a conflict.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum Conflict {
/// Keep the conflict by marking it in the source file.
Keep {
/// How to visualize conflicts in merged files.
style: ConflictStyle,
/// The amount of markers to draw, defaults to 7, i.e. `<<<<<<<`
marker_size: NonZeroU8,
},
/// Chose our side to resolve a conflict.
ResolveWithOurs,
/// Chose their side to resolve a conflict.
ResolveWithTheirs,
/// Place our and their lines one after another, in any order
ResolveWithUnion,
}
impl Conflict {
/// The amount of conflict marker characters to print by default.
// TODO: use NonZeroU8::new().unwrap() here once the MSRV supports it.
pub const DEFAULT_MARKER_SIZE: u8 = 7;
/// The amount of conflict markers to print if this instance contains them, or `None` otherwise
pub fn marker_size(&self) -> Option {
match self {
Conflict::Keep { marker_size, .. } => Some(marker_size.get()),
Conflict::ResolveWithOurs | Conflict::ResolveWithTheirs | Conflict::ResolveWithUnion => None,
}
}
}
impl Default for Conflict {
fn default() -> Self {
Conflict::Keep {
style: Default::default(),
marker_size: Conflict::DEFAULT_MARKER_SIZE.try_into().unwrap(),
}
}
}
pub(super) mod function;
mod utils;
gix-merge-0.2.0/src/blob/builtin_driver/text/utils.rs 0000644 0000000 0000000 00000042515 10461020230 0020736 0 ustar 0000000 0000000 use crate::blob::builtin_driver::text::ConflictStyle;
use bstr::{BStr, ByteSlice, ByteVec};
use std::iter::Peekable;
use std::ops::Range;
/// Used only when `diff3` is the conflict style as `zdiff3` automatically reduces hunks into nothing.
/// Here we check if all hunks are the same.
pub fn hunks_differ_in_diff3(
style: ConflictStyle,
a: &[Hunk],
b: &[Hunk],
input: &imara_diff::intern::InternedInput<&[u8]>,
current_tokens: &[imara_diff::intern::Token],
) -> bool {
if style != ConflictStyle::Diff3 {
return true;
}
let tokens_for_hunk = |hunk: &Hunk| -> &[imara_diff::intern::Token] {
&tokens_for_side(hunk.side, input, current_tokens)[hunk.after.start as usize..hunk.after.end as usize]
};
a.iter()
.flat_map(tokens_for_hunk)
.ne(b.iter().flat_map(tokens_for_hunk))
}
pub fn contains_lines(hunks: &[Hunk]) -> bool {
hunks.iter().any(|h| !h.after.is_empty())
}
/// ## Deviation
///
/// This implementation definitely isn't the same as in Git, primarily because it seemed impossible
/// to understand what's going on there without investing more time than it seemed worth.
pub fn detect_line_ending(
hunks: &[Hunk],
input: &mut imara_diff::intern::InternedInput<&[u8]>,
current_tokens: &[imara_diff::intern::Token],
) -> Option<&'static BStr> {
fn is_eol_crlf(
hunks: &[Hunk],
input: &mut imara_diff::intern::InternedInput<&[u8]>,
current_tokens: &[imara_diff::intern::Token],
) -> Option {
let (range, side) = hunks.iter().rev().find_map(|h| {
(!h.after.is_empty())
.then_some((&h.after, h.side))
.or((!h.before.is_empty()).then_some((&h.before, Side::Ancestor)))
})?;
let tokens = tokens_for_side(side, input, current_tokens);
{
let last_line = tokens
.get(range.end as usize - 1)
.map(|token| &input.interner[*token])?;
if last_line.last() == Some(&b'\n') {
return last_line.get(last_line.len().checked_sub(2)?).map(|c| *c == b'\r');
}
}
let second_to_last_line = tokens
.get(range.end.checked_sub(2)? as usize)
.map(|token| &input.interner[*token])?;
second_to_last_line
.get(second_to_last_line.len().checked_sub(2)?)
.map(|c| *c == b'\r')
}
is_eol_crlf(hunks, input, current_tokens).map(|is_crlf| if is_crlf { b"\r\n".into() } else { b"\n".into() })
}
pub fn detect_line_ending_or_nl(
hunks: &[Hunk],
input: &mut imara_diff::intern::InternedInput<&[u8]>,
current_tokens: &[imara_diff::intern::Token],
) -> &'static BStr {
detect_line_ending(hunks, input, current_tokens).unwrap_or(b"\n".into())
}
fn tokens_for_side<'a>(
side: Side,
input: &'a imara_diff::intern::InternedInput<&[u8]>,
current_tokens: &'a [imara_diff::intern::Token],
) -> &'a [imara_diff::intern::Token] {
match side {
Side::Current => current_tokens,
Side::Other => &input.after,
Side::Ancestor => &input.before,
}
}
pub fn assure_ends_with_nl(out: &mut Vec, nl: &BStr) {
if !out.is_empty() && !out.ends_with(b"\n") {
out.push_str(nl);
}
}
pub fn write_conflict_marker(out: &mut Vec, marker: u8, label: Option<&BStr>, marker_size: u8, nl: &BStr) {
assure_ends_with_nl(out, nl);
out.extend(std::iter::repeat(marker).take(marker_size as usize));
if let Some(label) = label {
out.push(b' ');
out.extend_from_slice(label);
}
out.push_str(nl);
}
pub fn write_ancestor(input: &imara_diff::intern::InternedInput<&[u8]>, from: u32, to: usize, out: &mut Vec) {
if to < from as usize {
return;
}
if let Some(tokens) = input.before.get(from as usize..to) {
write_tokens(&input.interner, tokens, out);
}
}
/// Look at all hunks in `in_out` and fill in the ancestor in the range of `ancestor_range`.
/// This is all based on knowing the ranges are sequences of tokens.
pub fn fill_ancestor(Range { start, end }: &Range, in_out: &mut Vec) {
fn is_nonzero(num: &u32) -> bool {
*num > 0
}
if in_out.is_empty() {
return;
}
let first = &in_out[0];
let mut first_idx = 0;
if let Some(lines_to_add) = first.before.start.checked_sub(*start).filter(is_nonzero) {
in_out.insert(0, ancestor_hunk(*start, lines_to_add));
first_idx += 1;
}
let mut added_hunks = false;
for (idx, next_idx) in (first_idx..in_out.len()).map(|idx| (idx, idx + 1)) {
let Some(next_hunk) = in_out.get(next_idx) else { break };
let hunk = &in_out[idx];
if let Some(lines_to_add) = next_hunk.before.start.checked_sub(hunk.before.end).filter(is_nonzero) {
in_out.push(ancestor_hunk(hunk.before.end, lines_to_add));
added_hunks = true;
}
}
let in_out_len = in_out.len();
if added_hunks {
in_out[first_idx..in_out_len].sort_by_key(|hunk| hunk.before.start);
}
let last = &in_out[in_out_len - 1];
if let Some(lines_to_add) = end.checked_sub(last.before.end).filter(is_nonzero) {
in_out.push(ancestor_hunk(last.before.end, lines_to_add));
}
}
fn ancestor_hunk(start: u32, num_lines: u32) -> Hunk {
let range = start..start + num_lines;
Hunk {
before: range.clone(),
after: range,
side: Side::Ancestor,
}
}
/// Reduce the area of `a_hunks` and the hunks in `b_hunks` so that only those lines that are
/// actually different remain. Note that we have to compare the resolved values, not only the tokens,
/// so `current_tokens` is expected to be known to the `input` (and its `interner`).
/// Hunks from all input arrays maybe removed in the process from the front and back, in case they
/// are entirely equal to what's in `hunk`. Note also that `a_hunks` and `b_hunks` are treated to be consecutive,
/// so [`fill_ancestor()`] must have been called beforehand, and are assumed to covert the same space in the
/// ancestor buffer.
/// Use `mode` to determine how hunks may be handled.
///
/// Return a new vector of all the hunks that were removed from front and back, with partial hunks inserted,
/// along with the amount of hunks that go front, with the remaining going towards the back.
#[must_use]
pub fn zealously_contract_hunks(
a_hunks: &mut Vec,
b_hunks: &mut Vec,
input: &imara_diff::intern::InternedInput<&[u8]>,
current_tokens: &[imara_diff::intern::Token],
) -> (Vec, usize) {
let line_content = |token_idx: u32, side: Side| {
let tokens = match side {
Side::Current => current_tokens,
Side::Other => &input.after,
Side::Ancestor => &input.before,
};
&input.interner[tokens[token_idx as usize]]
};
let (mut last_a_hunk_idx, mut last_b_hunk_idx) = (0, 0);
let (mut out, hunks_in_front) = {
let (mut remove_leading_a_hunks_from, mut remove_leading_b_hunks_from) = (None, None);
let (mut a_hunk_token_equal_till, mut b_hunk_token_equal_till) = (None, None);
for ((a_token_idx, a_hunk_idx, a_hunk_side), (b_token_idx, b_hunk_idx, b_hunk_side)) in
iterate_hunks(a_hunks).zip(iterate_hunks(b_hunks))
{
let a_line = line_content(a_token_idx, a_hunk_side).as_bstr();
let b_line = line_content(b_token_idx, b_hunk_side).as_bstr();
if last_a_hunk_idx != a_hunk_idx {
a_hunk_token_equal_till = None;
last_a_hunk_idx = a_hunk_idx;
}
if last_b_hunk_idx != b_hunk_idx {
b_hunk_token_equal_till = None;
last_b_hunk_idx = b_hunk_idx;
}
if a_line == b_line {
(remove_leading_a_hunks_from, remove_leading_b_hunks_from) = (Some(a_hunk_idx), Some(b_hunk_idx));
(a_hunk_token_equal_till, b_hunk_token_equal_till) = (Some(a_token_idx), Some(b_token_idx));
} else {
break;
}
}
let mut out = Vec::with_capacity(remove_leading_a_hunks_from.unwrap_or_else(|| {
if a_hunk_token_equal_till.is_some() {
1
} else {
0
}
}));
truncate_hunks_from_from_front(
a_hunks,
remove_leading_a_hunks_from,
a_hunk_token_equal_till,
Some(&mut out),
);
truncate_hunks_from_from_front(b_hunks, remove_leading_b_hunks_from, b_hunk_token_equal_till, None);
let hunks_in_front = out.len();
(out, hunks_in_front)
};
(last_a_hunk_idx, last_b_hunk_idx) = (0, 0);
{
let (mut remove_trailing_a_hunks_from, mut remove_trailing_b_hunks_from) = (None, None);
let (mut a_hunk_token_equal_from, mut b_hunk_token_equal_from) = (None, None);
for ((a_token_idx, a_hunk_idx, a_hunk_side), (b_token_idx, b_hunk_idx, b_hunk_side)) in
iterate_hunks_rev(a_hunks).zip(iterate_hunks_rev(b_hunks))
{
let a_line = line_content(a_token_idx, a_hunk_side).as_bstr();
let b_line = line_content(b_token_idx, b_hunk_side).as_bstr();
if last_a_hunk_idx != a_hunk_idx {
a_hunk_token_equal_from = None;
last_a_hunk_idx = a_hunk_idx;
}
if last_b_hunk_idx != b_hunk_idx {
b_hunk_token_equal_from = None;
last_b_hunk_idx = b_hunk_idx;
}
if a_line == b_line {
(remove_trailing_a_hunks_from, remove_trailing_b_hunks_from) = (Some(a_hunk_idx), Some(b_hunk_idx));
(a_hunk_token_equal_from, b_hunk_token_equal_from) = (Some(a_token_idx), Some(b_token_idx));
} else {
break;
}
}
truncate_hunks_from_from_back(
a_hunks,
remove_trailing_a_hunks_from,
a_hunk_token_equal_from,
Some(&mut out),
);
truncate_hunks_from_from_back(b_hunks, remove_trailing_b_hunks_from, b_hunk_token_equal_from, None);
}
(out, hunks_in_front)
}
fn range_by_side(hunk: &mut Hunk) -> &mut Range {
match hunk.side {
Side::Current | Side::Other => &mut hunk.after,
Side::Ancestor => &mut hunk.before,
}
}
fn truncate_hunks_from_from_front(
hunks: &mut Vec,
hunks_to_remove_until_idx: Option,
hunk_token_equal_till: Option,
mut out_hunks: Option<&mut Vec>,
) {
let Some(hunks_to_remove_until_idx) = hunks_to_remove_until_idx else {
assert!(hunk_token_equal_till.is_none());
return;
};
let mut last_index_to_remove = Some(hunks_to_remove_until_idx);
let hunk = &mut hunks[hunks_to_remove_until_idx];
let range = range_by_side(hunk);
if let Some(hunk_token_equal_till) = hunk_token_equal_till {
let orig_start = range.start;
let new_start = hunk_token_equal_till + 1;
range.start = new_start;
if Range::::is_empty(range) {
range.start = orig_start;
} else if let Some(out) = out_hunks.as_deref_mut() {
last_index_to_remove = hunks_to_remove_until_idx.checked_sub(1);
let mut removed_hunk = hunk.clone();
let new_range = range_by_side(&mut removed_hunk);
new_range.start = orig_start;
new_range.end = new_start;
out.push(removed_hunk);
} else {
last_index_to_remove = hunks_to_remove_until_idx.checked_sub(1);
}
}
if let Some(last_index_to_remove) = last_index_to_remove {
let mut current_idx = 0;
hunks.retain(|hunk| {
if current_idx > last_index_to_remove {
true
} else {
current_idx += 1;
if let Some(out) = out_hunks.as_deref_mut() {
out.push(hunk.clone());
}
false
}
});
}
}
fn truncate_hunks_from_from_back(
hunks: &mut Vec,
remove_trailing_hunks_from_idx: Option,
hunk_token_equal_from: Option,
mut out_hunks: Option<&mut Vec>,
) {
let Some(mut remove_trailing_hunks_from_idx) = remove_trailing_hunks_from_idx else {
assert!(hunk_token_equal_from.is_none());
return;
};
let hunk = &mut hunks[remove_trailing_hunks_from_idx];
let range = range_by_side(hunk);
if let Some(hunk_token_equal_from) = hunk_token_equal_from {
let orig_end = range.end;
let new_end = hunk_token_equal_from;
range.end = new_end;
if Range::::is_empty(range) {
range.end = orig_end;
} else if let Some(out) = out_hunks.as_deref_mut() {
remove_trailing_hunks_from_idx += 1;
let mut removed_hunk = hunk.clone();
let new_range = range_by_side(&mut removed_hunk);
new_range.start = new_end;
new_range.end = orig_end;
out.push(removed_hunk);
} else {
remove_trailing_hunks_from_idx += 1;
}
}
if let Some(out) = out_hunks {
out.extend_from_slice(&hunks[remove_trailing_hunks_from_idx..]);
}
hunks.truncate(remove_trailing_hunks_from_idx);
}
/// Return an iterator over `(token_idx, hunk_idx, hunk_side)` from `hunks`.
fn iterate_hunks(hunks: &[Hunk]) -> impl Iterator + '_ {
hunks.iter().enumerate().flat_map(|(hunk_idx, hunk)| {
match hunk.side {
Side::Current | Side::Other => &hunk.after,
Side::Ancestor => &hunk.before,
}
.clone()
.map(move |idx| (idx, hunk_idx, hunk.side))
})
}
/// Return a reverse iterator over `(token_idx, hunk_idx, hunk_side)` from `hunks`.
fn iterate_hunks_rev(hunks: &[Hunk]) -> impl Iterator + '_ {
hunks.iter().enumerate().rev().flat_map(|(hunk_idx, hunk)| {
match hunk.side {
Side::Current | Side::Other => &hunk.after,
Side::Ancestor => &hunk.before,
}
.clone()
.rev()
.map(move |idx| (idx, hunk_idx, hunk.side))
})
}
pub fn write_hunks(
hunks: &[Hunk],
input: &imara_diff::intern::InternedInput<&[u8]>,
current_tokens: &[imara_diff::intern::Token],
out: &mut Vec,
) {
for hunk in hunks {
let (tokens, range) = match hunk.side {
Side::Current => (current_tokens, &hunk.after),
Side::Other => (input.after.as_slice(), &hunk.after),
Side::Ancestor => (input.before.as_slice(), &hunk.before),
};
write_tokens(&input.interner, &tokens[usize_range(range)], out);
}
}
fn usize_range(range: &Range) -> Range {
range.start as usize..range.end as usize
}
fn write_tokens(
interner: &imara_diff::intern::Interner<&[u8]>,
tokens: &[imara_diff::intern::Token],
out: &mut Vec,
) {
for token in tokens {
out.extend_from_slice(interner[*token]);
}
}
/// Find all hunks in `iter` which aren't from the same side as `hunk` and intersect with it.
/// Also put `hunk` into `input` so it's the first item, and possibly put more hunks of the side of `hunk` so
/// `iter` doesn't have any overlapping hunks left.
/// Return `true` if `intersecting` is non-empty after the operation, indicating overlapping hunks were found.
pub fn take_intersecting(
iter: &mut Peekable>,
input: &mut Vec,
intersecting: &mut Vec,
) -> Option<()> {
input.clear();
input.push(iter.next()?);
intersecting.clear();
fn left_overlaps_right(left: &Hunk, right: &Hunk) -> bool {
left.side != right.side
&& (right.before.contains(&left.before.start)
|| (right.before.is_empty() && right.before.start == left.before.start))
}
loop {
let hunk = input.last().expect("just pushed");
while iter.peek().filter(|b_hunk| left_overlaps_right(b_hunk, hunk)).is_some() {
intersecting.extend(iter.next());
}
// The hunks that overlap might themselves overlap with a following hunk of the other side.
// If so, split it so it doesn't overlap anymore.
let mut found_more_intersections = false;
while intersecting
.last_mut()
.zip(iter.peek_mut())
.filter(|(last_intersecting, candidate)| left_overlaps_right(candidate, last_intersecting))
.is_some()
{
input.extend(iter.next());
found_more_intersections = true;
}
if !found_more_intersections {
break;
}
}
Some(())
}
pub fn tokens(input: &[u8]) -> imara_diff::sources::ByteLines<'_, true> {
imara_diff::sources::byte_lines_with_terminator(input)
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Side {
Current,
Other,
/// A special marker that is just used to be able to mix-in hunks that only point to the ancestor.
/// Only `before` matters then.
Ancestor,
}
#[derive(Debug, Clone)]
pub struct Hunk {
pub before: Range,
pub after: Range,
pub side: Side,
}
pub struct CollectHunks {
pub hunks: Vec,
pub side: Side,
}
impl imara_diff::Sink for CollectHunks {
type Out = Vec;
fn process_change(&mut self, before: Range, after: Range) {
self.hunks.push(Hunk {
before,
after,
side: self.side,
});
}
fn finish(self) -> Self::Out {
self.hunks
}
}
gix-merge-0.2.0/src/blob/mod.rs 0000644 0000000 0000000 00000016764 10461020230 0014357 0 ustar 0000000 0000000 // TODO: remove this - only needed while &mut Vec isn't used.
#![allow(clippy::ptr_arg)]
use crate::blob::platform::{DriverChoice, ResourceRef};
use bstr::BString;
use std::path::PathBuf;
///
pub mod builtin_driver;
///
pub mod pipeline;
///
pub mod platform;
/// Define if a merge is conflicted or not.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum Resolution {
/// Everything could be resolved during the merge, and there was no conflict.
Complete,
/// Conflicts were resolved automatically, even thought the result is complete
/// and free of conflict markers.
/// This can only be the case for text-file content merges.
CompleteWithAutoResolvedConflict,
/// A conflict is still present in the form of conflict markers.
Conflict,
}
/// A way to classify the side of a resource for merging.
#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub enum ResourceKind {
/// Our side of the state.
CurrentOrOurs,
/// Their side of the state.
OtherOrTheirs,
/// The state of the common base of both ours and theirs.
CommonAncestorOrBase,
}
/// Define a built-in way of performing a three-way merge, including auto-resolution support.
///
/// Some values are related to diffing, some are related to conversions.
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum BuiltinDriver {
/// Perform a merge between text-sources such that conflicts are marked according to
/// `merge.conflictStyle` in the Git configuration.
///
/// If any of the inputs, *base*, *ours* or *theirs* looks like non-text/binary,
/// the [`Binary`](Self::Binary) driver will be used instead.
///
/// Also see [`builtin_driver::text::ConflictStyle`].
#[default]
Text,
/// Merge 'unmergable' content by choosing *ours* or *theirs*, without performing
/// an actual merge.
///
/// Note that if the merge operation is for virtual ancestor (a merge for merge-bases),
/// then *ours* will always be chosen.
Binary,
/// Merge text-sources and resolve conflicts by adding conflicting lines one after another,
/// in random order, without adding conflict markers either.
///
/// This can be useful for files that change a lot, but will remain usable merely by adding
/// all changed lines.
Union,
}
/// Define a driver program that performs a three-way merge.
///
/// Some values are related to diffing, some are related to conversions.
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct Driver {
/// The name of the driver, as referred to by `[merge "name"]` in the git configuration.
pub name: BString,
/// The human-readable version of `name`, only to be used for displaying driver-information to the user.
pub display_name: BString,
/// The command to execute to perform the merge entirely like ` %O %A %B %L %P %S %X %Y`.
///
/// * **%O**
/// - the common ancestor version, or *base*.
/// * **%A**
/// - the current version, or *ours*.
/// * **%B**
/// - the other version, or *theirs*.
/// * **%L**
/// - The conflict-marker size as positive number.
/// * **%P**
/// - The path in which the merged result would be stored, as workspace-relative path, of the current/ours side.
/// * **%S**
/// - The conflict-label for the common ancestor or *base*.
/// * **%X**
/// - The conflict-label for the current version or *ours*.
/// * **%Y**
/// - The conflict-label for the other version or *theirs*.
///
/// Note that conflict-labels are behind the conflict markers, to annotate them.
///
/// A typical invocation with all arguments substituted could then look like this:
///
/// ```sh
/// .merge_file_nR2Qs1 .merge_file_WYXCJe .merge_file_UWbzrm 7 file e2a2970 HEAD feature
/// ```
///
/// The driver is expected to leave its version in the file at `%A`, by overwriting it.
pub command: BString,
/// If `true`, this is the `name` of the driver to use when a virtual-merge-base is created, as a merge of all
/// available merge-bases if there are more than one.
///
/// This value can also be special built-in drivers named `text`, `binary` or `union`. Note that user-defined
/// drivers with the same name will be preferred over built-in ones, but only for files whose git attributes
/// specified the driver by *name*.
pub recursive: Option,
}
/// A conversion pipeline to take an object or path from what's stored in Git to what can be merged, while
/// following the guidance of git-attributes at the respective path to learn how the merge should be performed.
///
/// Depending on the source, different conversions are performed:
///
/// * `worktree on disk` -> `object for storage in git`
/// * `object` -> `possibly renormalized object`
/// - Renormalization means that the `object` is converted to what would be checked out into the work-tree,
/// just to turn it back into an object.
#[derive(Clone)]
pub struct Pipeline {
/// A way to read data directly from the worktree.
pub roots: pipeline::WorktreeRoots,
/// A pipeline to convert objects from the worktree to Git, and also from Git to the worktree, and back to Git.
pub filter: gix_filter::Pipeline,
/// Options affecting the way we read files.
pub options: pipeline::Options,
/// A buffer to produce disk-accessible paths from worktree roots.
path: PathBuf,
}
/// A utility for gathering and processing all state necessary to perform a three-way merge.
///
/// It can re-use buffers if all three parts of participating in the merge are
/// set repeatedly.
#[derive(Clone)]
pub struct Platform {
/// The current version (ours).
current: Option,
/// The ancestor version (base).
ancestor: Option,
/// The other version (theirs).
other: Option,
/// A way to convert objects into a diff-able format.
pub filter: Pipeline,
/// A way to access `.gitattributes`
pub attr_stack: gix_worktree::Stack,
/// Further configuration that affects the merge.
pub options: platform::Options,
/// All available merge drivers.
///
/// They are referenced in git-attributes by name, and we hand out indices into this array.
drivers: Vec,
/// Pre-configured attributes to obtain additional merge-related information.
attrs: gix_filter::attributes::search::Outcome,
/// The way we convert resources into mergeable states.
pub filter_mode: pipeline::Mode,
}
/// The product of a [`prepare_merge()`](Platform::prepare_merge()) call to finally
/// perform the merge and retrieve the merge results.
#[derive(Copy, Clone)]
pub struct PlatformRef<'parent> {
/// The platform that hosts the resources, used to access drivers.
pub(super) parent: &'parent Platform,
/// The current or our side of the merge operation.
pub current: ResourceRef<'parent>,
/// The ancestor or base of the merge operation.
pub ancestor: ResourceRef<'parent>,
/// The other or their side of the merge operation.
pub other: ResourceRef<'parent>,
/// Which driver to use according to the resource's configuration,
/// using the path of `current` to read git-attributes.
pub driver: DriverChoice,
/// Possibly processed options for use when performing the actual merge.
///
/// They may be inspected before the merge, or altered at will.
pub options: platform::merge::Options,
}
gix-merge-0.2.0/src/blob/pipeline.rs 0000644 0000000 0000000 00000037453 10461020230 0015403 0 ustar 0000000 0000000 use super::{Pipeline, ResourceKind};
use bstr::BStr;
use gix_filter::driver::apply::{Delay, MaybeDelayed};
use gix_filter::pipeline::convert::{ToGitOutcome, ToWorktreeOutcome};
use gix_object::tree::EntryKind;
use std::io::Read;
use std::path::{Path, PathBuf};
/// Options for use in a [`Pipeline`].
#[derive(Default, Clone, PartialEq, Eq, Debug, Hash, Ord, PartialOrd)]
pub struct Options {
/// The amount of bytes that an object has to reach before being treated as binary.
/// These objects will not be queried, nor will their data be processed in any way.
/// If `0`, no file is ever considered binary due to their size.
///
/// Note that for files stored in `git`, what counts is their stored, decompressed size,
/// thus `git-lfs` files would typically not be considered binary unless one explicitly sets
/// them.
/// However, if they are to be retrieved from the worktree, the worktree size is what matters,
/// even though that also might be a `git-lfs` file which is small in Git.
pub large_file_threshold_bytes: u64,
}
/// The specific way to convert a resource.
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum Mode {
/// Prepare resources as they are stored in `git`.
///
/// This is naturally the case when object-ids are used, but a conversion is needed
/// when data is read from a worktree.
#[default]
ToGit,
/// For sources that are object-ids, convert them to what *would* be stored in the worktree,
/// and back to what *would* be stored in Git.
///
/// Sources that are located in a worktree are merely converted to what *would* be stored in Git.
///
/// This is useful to prevent merge conflicts due to inconcistent whitespace.
Renormalize,
}
/// A way to access roots for different kinds of resources that are possibly located and accessible in a worktree.
#[derive(Clone, Debug, Default)]
pub struct WorktreeRoots {
/// The worktree root where the current (or our) version of the resource is present.
pub current_root: Option,
/// The worktree root where the other (or their) version of the resource is present.
pub other_root: Option,
/// The worktree root where containing the resource of the common ancestor of our and their version.
pub common_ancestor_root: Option,
}
impl WorktreeRoots {
/// Return the root path for the given `kind`
pub fn by_kind(&self, kind: ResourceKind) -> Option<&Path> {
match kind {
ResourceKind::CurrentOrOurs => self.current_root.as_deref(),
ResourceKind::CommonAncestorOrBase => self.common_ancestor_root.as_deref(),
ResourceKind::OtherOrTheirs => self.other_root.as_deref(),
}
}
/// Return `true` if all worktree roots are unset.
pub fn is_unset(&self) -> bool {
self.current_root.is_none() && self.other_root.is_none() && self.common_ancestor_root.is_none()
}
}
/// Lifecycle
impl Pipeline {
/// Create a new instance of a pipeline which produces blobs suitable for merging.
///
/// `roots` allow to read worktree files directly, and `worktree_filter` is used
/// to transform object database data directly.
/// `options` are used to further configure the way we act.
pub fn new(roots: WorktreeRoots, worktree_filter: gix_filter::Pipeline, options: Options) -> Self {
Pipeline {
roots,
filter: worktree_filter,
options,
path: Default::default(),
}
}
}
/// Access
impl Pipeline {}
/// Data as returned by [`Pipeline::convert_to_mergeable()`].
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
pub enum Data {
/// The data to use for merging was written into the buffer that was passed during the call to [`Pipeline::convert_to_mergeable()`].
Buffer,
/// The file or blob is above the big-file threshold and cannot be processed.
///
/// In this state, the file cannot be merged.
TooLarge {
/// The size of the object prior to performing any filtering or as it was found on disk.
///
/// Note that technically, the size isn't always representative of the same 'state' of the
/// content, as once it can be the size of the blob in git, and once it's the size of file
/// in the worktree - both can differ a lot depending on filters.
size: u64,
},
}
///
pub mod convert_to_mergeable {
use std::collections::TryReserveError;
use bstr::BString;
use gix_object::tree::EntryKind;
/// The error returned by [Pipeline::convert_to_mergeable()](super::Pipeline::convert_to_mergeable()).
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error {
#[error("Entry at '{rela_path}' must be regular file or symlink, but was {actual:?}")]
InvalidEntryKind { rela_path: BString, actual: EntryKind },
#[error("Entry at '{rela_path}' could not be read as symbolic link")]
ReadLink { rela_path: BString, source: std::io::Error },
#[error("Entry at '{rela_path}' could not be opened for reading or read from")]
OpenOrRead { rela_path: BString, source: std::io::Error },
#[error("Entry at '{rela_path}' could not be copied from a filter process to a memory buffer")]
StreamCopy { rela_path: BString, source: std::io::Error },
#[error(transparent)]
FindObject(#[from] gix_object::find::existing_object::Error),
#[error(transparent)]
ConvertToWorktree(#[from] gix_filter::pipeline::convert::to_worktree::Error),
#[error(transparent)]
ConvertToGit(#[from] gix_filter::pipeline::convert::to_git::Error),
#[error("Memory allocation failed")]
OutOfMemory(#[from] TryReserveError),
}
}
/// Conversion
impl Pipeline {
/// Convert the object at `id`, `mode`, `rela_path` and `kind`, providing access to `attributes` and `objects`.
/// The resulting merge-able data is written into `out`, if it's not too large.
/// The returned [`Data`] contains information on how to use `out`, which will be cleared if it is `None`, indicating
/// that no object was found at the location *on disk* - it's always an error to provide an object ID that doesn't exist
/// in the object database.
///
/// `attributes` must be returning the attributes at `rela_path` and is used for obtaining worktree filter settings,
/// and `objects` must be usable if `kind` is a resource in the object database,
/// i.e. if no worktree root is available. It's notable that if a worktree root is present for `kind`,
/// then a `rela_path` is used to access it on disk.
///
/// If `id` [is null](gix_hash::ObjectId::is_null()) or the file in question doesn't exist in the worktree in case
/// [a root](WorktreeRoots) is present, then `out` will be left cleared and the output data will be `None`.
/// This is useful to simplify the calling code as empty buffers signal that nothing is there.
///
/// Note that `mode` is trusted, and we will not re-validate that the entry in the worktree actually is of that mode.
/// Only blobs are allowed.
///
/// Use `convert` to control what kind of the resource will be produced.
#[allow(clippy::too_many_arguments)]
pub fn convert_to_mergeable(
&mut self,
id: &gix_hash::oid,
mode: EntryKind,
rela_path: &BStr,
kind: ResourceKind,
attributes: &mut dyn FnMut(&BStr, &mut gix_filter::attributes::search::Outcome),
objects: &dyn gix_object::FindObjectOrHeader,
convert: Mode,
out: &mut Vec,
) -> Result