135 Commits

Author SHA1 Message Date
Sylvestre Ledru d92132e721 version 0.4.1 2024-04-27 13:12:58 +02:00
Sylvestre Ledru 99d4d02985 add missing copyright 2024-04-27 13:12:16 +02:00
Olivier Tilloy e7dc6558c6 Merge pull request #56 from TanmayPatil105/handle-directory-input
Handle directory-file and file-directory comparisons in the diff
2024-04-23 22:40:28 +02:00
Tanmay Patil 8c6a648aef Merge branch 'main' into handle-directory-input 2024-04-23 23:11:31 +05:30
Tanmay Patil 0304391bc5 Create test files in temporary directory 2024-04-23 22:44:06 +05:30
Sylvestre Ledru 8de0ca60d1 Merge pull request #52 from oSoMoN/long-options
Handle long option names for the supported output styles…
2024-04-23 18:44:47 +02:00
Sylvestre Ledru 43b9c524d9 Merge pull request #62 from oSoMoN/integration-no-hardcoded-filename
Un-hardcode a test filename in an integration test (fixes #61)
2024-04-23 18:36:33 +02:00
Olivier Tilloy 3dc3fdf5cd Un-hardcode a test filename in an integration test (fixes #61) 2024-04-23 18:00:56 +02:00
Olivier Tilloy b7261a43f4 Break out the logic to match context/unified diff params into separate functions, for improved readability 2024-04-22 18:01:00 +02:00
Olivier Tilloy 37fe1ae808 Handle --normal, -e and --ed options 2024-04-22 18:01:00 +02:00
Olivier Tilloy 22d973fce6 Parse all valid arguments accepted by GNU diff to request a regular context (with an optional number of lines) 2024-04-22 18:01:00 +02:00
Olivier Tilloy fe28610f21 Parse all valid arguments accepted by GNU diff to request a unified context (with an optional number of lines) 2024-04-22 18:01:00 +02:00
Sylvestre Ledru 3a8eddfe2c Fix typos 2024-04-21 16:07:01 +02:00
Tanmay Patil 476e69ee20 Windows: Fix tests 2024-04-21 18:06:15 +05:30
Tanmay Patil 65993d6a13 Add tests for diff FILE DIRECTORY 2024-04-21 16:10:48 +05:30
Tanmay Patil 39d2ece187 Handle directory-file and file-directory comparisons in the diff
GNU diff treats `diff DIRECTORY FILE` as `diff DIRECTORY/FILE FILE`
2024-04-21 16:10:48 +05:30
Sylvestre Ledru 46a26e896b Merge pull request #58 from oSoMoN/ed-diff-tests-fix-path
Move test assertions in the cfg block where they belong (fixes #3)
2024-04-21 09:37:58 +02:00
Olivier Tilloy 14799eea89 Move test assertions in the cfg block where they belong 2024-04-21 00:13:52 +02:00
Olivier Tilloy 831348d1fc Fix file path in ed diff tests 2024-04-21 00:12:43 +02:00
Sylvestre Ledru 00a5c0ba44 Merge pull request #57 from oSoMoN/windows-fix-path-for-ci
CI: On Windows, use GNU's patch.exe instead of Strawberry Perl patch
2024-04-20 19:44:40 +02:00
Olivier Tilloy bf104648c1 CI: On Windows, use GNU's patch.exe instead of Strawberry Perl patch 2024-04-20 19:30:34 +02:00
Olivier Tilloy 5669f164b3 Merge pull request #34 from uutils/renovate/regex-1.x
Update Rust crate regex to 1.10.4
2024-04-17 19:19:00 +02:00
Sylvestre Ledru 11bf271666 Merge pull request #9 from uutils/renovate/codecov-codecov-action-4.x
Update codecov/codecov-action action to v4
2024-04-16 22:18:53 +02:00
renovate[bot] 674974d5e6 Update Rust crate regex to 1.10.4 2024-04-16 19:54:53 +00:00
Sylvestre Ledru 2ba35db431 Merge pull request #4 from uutils/renovate/diff-0.x
Update Rust crate diff to 0.1.13
2024-04-16 21:53:45 +02:00
renovate[bot] fcec7277c9 Update codecov/codecov-action action to v4 2024-04-16 19:35:04 +00:00
renovate[bot] b8efad6b90 Update Rust crate diff to 0.1.13 2024-04-16 19:35:01 +00:00
Sylvestre Ledru 68e2f51983 Merge pull request #54 from uutils/renovate/chrono-0.x
Update Rust crate chrono to 0.4.38
2024-04-16 21:34:16 +02:00
Sylvestre Ledru 4edaee190f Merge pull request #55 from oSoMoN/use-codecov-token
Use the private Codecov token stored as a secret,
2024-04-16 21:34:01 +02:00
Olivier Tilloy 7f7821f558 Use the private Codecov token stored as a secret,
to work around rate-limiting issues like https://github.com/codecov/codecov-action/issues/557
2024-04-16 18:37:59 +02:00
renovate[bot] 1149a247dd Update Rust crate chrono to 0.4.38 2024-04-16 16:20:52 +00:00
Olivier Tilloy 1b311c6673 Merge pull request #33 from TanmayPatil105/context-diff-modification-time
Display modification times of input files in context and unified diff
2024-04-16 18:20:00 +02:00
Tanmay Patil aedd0684d1 Replace only the first two occurences of timestamp regex 2024-04-16 10:41:38 +05:30
Tanmay Patil 54c02bdf0b Use NamedTempFile instead of manually creating files 2024-04-16 10:17:09 +05:30
Tanmay Patil ba7cb0aef9 Do not create dummy files
Since we now returning SystemTime::now() for invalid file input,
there is no need to crate dummy files
2024-04-14 22:56:37 +05:30
Tanmay Patil 33783d094e Improve tests 2024-04-14 17:16:53 +05:30
Tanmay Patil 900e1c3a68 Tests: Replace modification time in diff with "TIMESTAMP" placeholder 2024-04-14 13:43:30 +05:30
Tanmay Patil 0a77fe12b9 Add tests for get_modification_time function 2024-04-13 21:31:13 +05:30
Tanmay Patil 86bd05c739 Merge branch 'context-diff-modification-time' of github.com:TanmayPatil105/diffutils into context-diff-modification-time 2024-04-10 22:31:09 +05:30
Tanmay Patil 00e18a6b0c Define assert_diff_eq macro for context&unified diff comparison 2024-04-10 22:20:48 +05:30
Tanmay f6eb0835b0 Merge branch 'main' into context-diff-modification-time 2024-04-10 22:13:18 +05:30
Sylvestre Ledru be66ff3299 Merge pull request #47 from oSoMoN/handle-stdin-filename
Handle the rewrite of "-" to "/dev/stdin" in main to leave the filenames unchanged (fixes #46)
2024-04-09 09:53:53 +02:00
Olivier Tilloy e1c319f96b Add an integration test for reading from "/dev/stdin" on unix-like systems 2024-04-08 22:36:14 +02:00
Olivier Tilloy 84ad116845 Use io::stdin() to read from standard input in a portable manner 2024-04-08 20:21:24 +02:00
Olivier Tilloy 6dc34fed44 Handle the rewrite of "-" to "/dev/stdin" in main to leave the filenames unchanged (fixes #46) 2024-04-08 20:21:24 +02:00
Sylvestre Ledru 9507ca28d7 Merge pull request #51 from oSoMoN/unit-tests-for-conflicting-output-style
Unit test to verify that conflicting output styles result in an error
2024-04-06 08:47:53 +02:00
Olivier Tilloy c325291696 Unit test to verify that conflicting output styles result in an error 2024-04-05 23:22:26 +02:00
Olivier Tilloy c08e0b6e1f Merge pull request #25 from uutils/renovate/tempfile-3.x
chore(deps): update rust crate tempfile to 3.10.1
2024-04-04 22:50:50 +02:00
Tanmay Patil 72da7fca40 Show current time if fs::metadata errors 2024-04-04 20:01:11 +05:30
Tanmay 61fb0657c1 Merge branch 'main' into context-diff-modification-time 2024-04-04 19:56:13 +05:30
Sylvestre Ledru 096aa1dad9 Merge pull request #50 from cakebaker/disable_tests_using_ed_on_windows
Disable tests on Windows that use `ed`
2024-04-04 08:48:41 +02:00
Daniel Hofstetter 2d9e625a5b Disable tests on Windows that use ed 2024-04-04 08:30:54 +02:00
Daniel Hofstetter d863fe443a Merge pull request #48 from uutils/clip
Run clippy pedantic fixes
2024-04-04 07:45:18 +02:00
renovate[bot] 6be94d8683 chore(deps): update rust crate tempfile to 3.10.1 2024-04-03 22:31:44 +00:00
Sylvestre Ledru 44ef772e4a release: version 0.4.0 2024-04-04 00:30:46 +02:00
Sylvestre Ledru bbfca84e17 chore: wow shiny new cargo-dist CI 2024-04-04 00:29:50 +02:00
Sylvestre Ledru 281098d751 Run clippy pedantic fixes 2024-04-03 23:25:48 +02:00
Tanmay Patil a3a372ff36 Display modification times of input files in unified diff 2024-04-04 00:13:41 +05:30
Tanmay Patil 5b814f8530 Fix tests 2024-04-03 10:50:52 +05:30
Sylvestre Ledru 34a5cc7340 Merge pull request #45 from oSoMoN/stdin-tests
Unit and integration tests for reading data from standard input
2024-04-03 00:02:21 +02:00
Olivier Tilloy 6a152cdc7f Add an integration test for reading data from stdin 2024-04-02 22:34:42 +02:00
Olivier Tilloy b8fada8faa Add unit tests for missing arguments 2024-04-02 21:10:41 +02:00
Olivier Tilloy a213272d0c Add tests for when '-' is used to signify to use standard input 2024-04-02 20:41:01 +02:00
Sylvestre Ledru 25e4a17421 Merge pull request #44 from oSoMoN/fix-codecov-badge
Fix the link to the Codecov badge
2024-04-02 18:34:51 +02:00
Olivier Tilloy 589039ab4c Fix the link to the Codecov badge 2024-04-02 18:24:09 +02:00
Sylvestre Ledru f83fccb542 Merge pull request #41 from uutils/sylvestre-patch-1
Disable the fail fast (closes: #40)
2024-04-02 07:33:43 +02:00
Sylvestre Ledru 76c4714f78 Disable the fail fast (closes: #40) 2024-04-01 23:15:23 +02:00
Tanmay Patil 88a7568b52 Merge branch 'main' into context-diff-modification-time 2024-04-01 13:05:37 +05:30
Sylvestre Ledru b135b6f218 Merge pull request #37 from oSoMoN/diff-arguments
Pass a Params reference to the various diff() functions, instead of a long list of arguments
2024-04-01 00:46:37 +02:00
Sylvestre Ledru 314e3a7320 Merge pull request #38 from oSoMoN/readme-grammar
README: minor grammar correction
2024-04-01 00:44:36 +02:00
Olivier Tilloy 6a73657b3a README: minor grammar correction 2024-04-01 00:26:30 +02:00
Olivier Tilloy e9f0630aaf Update fuzzers 2024-03-31 23:39:43 +02:00
Olivier Tilloy e6a0ba28c5 Pass a Params reference to the various diff() functions, instead of a long list of arguments 2024-03-31 21:27:28 +02:00
Tanmay Patil 80c9944bf7 Create foo/bar in target/context-diff 2024-03-31 22:57:51 +05:30
Sylvestre Ledru 043c5f9493 Merge branch 'main' into context-diff-modification-time 2024-03-31 16:17:23 +02:00
Tanmay Patil 9ff8f89626 Fix tests 2024-03-31 16:14:44 +05:30
Sylvestre Ledru 0ab824abda Merge pull request #36 from oSoMoN/default-params
Implement the Default trait for Params
2024-03-31 09:26:02 +02:00
Olivier Tilloy f60fefaf6e Implement the Default trait for Params 2024-03-30 23:50:58 +01:00
Sylvestre Ledru 14e77548fd Merge pull request #28 from oSoMoN/implement-expand-tabs-option
Implement `-t`/`--expand-tabs` option (fixes #26) and `--tabsize=NUM` option (fixes #27)
2024-03-30 15:28:53 +01:00
Olivier Tilloy f2fd2127ed Politely ask clippy to not complain about too many arguments 2024-03-28 18:10:31 +01:00
Olivier Tilloy cfc68d58bc Fix fuzzers' invokations 2024-03-28 18:10:31 +01:00
Olivier Tilloy e0283083f2 Implement --tabsize option 2024-03-28 18:10:29 +01:00
Olivier Tilloy 8d65c2badd Implement -t/--expand-tabs option 2024-03-28 18:09:03 +01:00
Tanmay Patil 42eb15b87a Display modification times of input files in context diff
Fixes #31
2024-03-27 22:46:23 +05:30
Sylvestre Ledru a304ac0a68 Merge pull request #30 from cakebaker/fix_clippy_warnings
clippy: fix warnings from useless_vec lint
2024-03-24 19:28:03 +01:00
Daniel Hofstetter f916f1ce86 clippy: fix warnings from useless_vec lint 2024-03-24 14:05:44 +01:00
Olivier Tilloy 4ed7ea1553 Implement -q/--brief option (fixes #19) (#20)
* Implement -q/--brief option

* Optimization: stop analyzing the files as soon as there are any differences

* Unit tests for the stop_early parameter

* Simplify checks
2024-03-19 11:45:06 +01:00
hanbings 62e10c6d6c ci: fuzzers in the Github Actions CI. (#29)
* Add fuzzing CI.

* fuzz dependency change: diffutils -> diffutilslib

* fix fuzz build.
2024-03-18 21:50:33 +01:00
Olivier Tilloy c68d386170 Implement -s/--report-identical-files option (fixes #23) 2024-03-01 14:45:41 +01:00
Olivier Tilloy a89f30afa0 Ed diff: compact ranges of lines where possible (fixes #21) 2024-02-24 11:55:54 +01:00
Olivier Tilloy 0a67bf9fb8 Add integration tests that check the exit codes and stdout/stderr 2024-02-22 17:26:24 +01:00
Olivier Tilloy 1241db4806 Match GNU diff's implementation for exit codes (fixes #17) 2024-02-22 17:26:24 +01:00
renovate[bot] 3bc8668f78 fix(deps): update rust crate libfuzzer-sys to 0.4 (#5)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-17 18:19:02 +01:00
Sylvestre Ledru c90eee442f Add the example section 2024-02-17 17:07:31 +01:00
Sylvestre Ledru 6c29f02527 improve the readme and example 2024-02-17 17:06:14 +01:00
Olivier Tilloy 790ef1e633 Run the GNU test suite in CI (fixes #8) (#13) 2024-02-17 15:27:52 +01:00
Sylvestre Ledru 4c1a752f11 Merge pull request #12 from oSoMoN/trivial-typo-error-message
Fix trivial typo in error message
2024-02-09 21:21:43 +01:00
Olivier Tilloy 54a5407bec Fix trivial typo in error message 2024-02-09 17:51:50 +01:00
Sylvestre Ledru 02632e915c Merge pull request #11 from oSoMoN/compact-range-of-lines
Normal diff: compact ranges of lines where possible (fixes #10)
2024-02-09 13:57:51 +01:00
Olivier Tilloy 3f9556aa05 Add comments to ease readability and maintainability 2024-02-09 10:28:39 +01:00
Olivier Tilloy a94c6a60cf Normal diff: compact ranges of lines where possible (fixes #10) 2024-02-09 10:28:39 +01:00
Sylvestre Ledru c28973c019 coverage: remove the fail fast to continue for linux even if windows fails 2024-02-09 10:28:27 +01:00
Sylvestre Ledru a660f7440c Merge pull request #1 from uutils/renovate/configure
Configure Renovate
2024-01-27 09:54:27 +01:00
Sylvestre Ledru 6a69a39852 add badges 2024-01-27 09:51:04 +01:00
Sylvestre Ledru bf13d528be Merge pull request #2 from cakebaker/ci_replace_actions_rs
ci: replace unmaintained `actions-rs` actions
2024-01-26 17:25:19 +01:00
Daniel Hofstetter 329a7e1f4a ci: use dtolnay/rust-toolchain instead of
unmaintained actions-rs/toolchain
2024-01-26 16:29:22 +01:00
Daniel Hofstetter e5de3cd93e ci: remove actions-rs/cargo & use cargo directly 2024-01-26 16:23:16 +01:00
Sylvestre Ledru 0e14b37e38 refresh the gitignore 2024-01-24 09:31:09 +01:00
Sylvestre Ledru 3c1176082e Ship with Cargo.lock 2024-01-24 09:30:47 +01:00
Sylvestre Ledru 865f97c78d upgrade the rust edition 2024-01-24 09:30:11 +01:00
Sylvestre Ledru f5b65a5720 add debug info 2024-01-23 12:49:20 +01:00
Sylvestre Ledru 1372c5386c use pretty assertions to help with Windows debug 2024-01-23 12:36:57 +01:00
Sylvestre Ledru d891e1034d run clippy pedantic
$ cargo +nightly clippy --allow-dirty --fix -- -W clippy::pedantic
2024-01-23 12:36:38 +01:00
Sylvestre Ledru fdc35f6b8e use mod tests + bring back the function used only for tests 2024-01-23 10:12:08 +01:00
Sylvestre Ledru 6648963df1 remove the unused function diff_w 2024-01-23 10:08:08 +01:00
Sylvestre Ledru a10ef621c8 Ajust the test paths 2024-01-23 10:06:02 +01:00
Sylvestre Ledru 7939749338 remove old config 2024-01-22 22:33:47 +01:00
Sylvestre Ledru 7b3001f1ff adjust the rule 2024-01-22 22:33:29 +01:00
Sylvestre Ledru 416a4be06c remove old files 2024-01-22 22:32:49 +01:00
Sylvestre Ledru 9084134f04 rustfmt the code 2024-01-22 22:32:32 +01:00
Sylvestre Ledru f42fc82f18 adjust the fuzzers to use the new structure 2024-01-22 22:32:11 +01:00
Sylvestre Ledru 0b2505d249 simplify the structure of the crate 2024-01-22 22:01:07 +01:00
Sylvestre Ledru 045435b803 add missing license headers 2024-01-22 21:49:07 +01:00
renovate[bot] b55cbf2ca2 Add renovate.json 2024-01-22 18:51:27 +01:00
Sylvestre Ledru 12f3f16792 add github action 2024-01-22 18:50:30 +01:00
Sylvestre Ledru e9e69b86db update of the README 2024-01-22 18:47:53 +01:00
Sylvestre Ledru 029d747e14 remove trailing spaces 2024-01-22 18:45:41 +01:00
Sylvestre Ledru e38055e5b2 rustfmt the code 2024-01-22 18:41:33 +01:00
Sylvestre Ledru 61cfe6eec4 Fix some clippy warnings 2024-01-22 18:41:01 +01:00
Michael Howell ea9376aaaf Add ed-formatted diff 2021-09-15 19:06:14 -07:00
Michael Howell 582259a867 Refactor into separate bin and lib folders 2021-07-21 13:40:52 -07:00
Michael Howell da05a5254b Fix missingnl behavior on normal-diff 2021-04-03 09:42:35 -07:00
Michael Howell a4f7642d7a Get rid of missing line ending tests
These don't work right with GNU patch. I get assertion errors.
2021-03-09 16:37:42 -07:00
Michael Howell e72ea046b7 Add context and normal diff modes 2021-03-09 16:34:57 -07:00
Michael Howell 2e84164d2f Get rid of sample binary 2021-03-09 11:07:44 -07:00
26 changed files with 5672 additions and 881 deletions
+161
View File
@@ -0,0 +1,161 @@
on: [push, pull_request]
name: Basic CI
env:
CARGO_TERM_COLOR: always
jobs:
check:
name: cargo check
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macOS-latest, windows-latest]
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: cargo check
test:
name: cargo test
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macOS-latest, windows-latest]
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: set up PATH on Windows
# Needed to use GNU's patch.exe instead of Strawberry Perl patch
if: runner.os == 'Windows'
run: echo "C:\Program Files\Git\usr\bin" >> $env:GITHUB_PATH
- run: cargo test
fmt:
name: cargo fmt --all -- --check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: rustup component add rustfmt
- run: cargo fmt --all -- --check
clippy:
name: cargo clippy -- -D warnings
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macOS-latest, windows-latest]
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: rustup component add clippy
- run: cargo clippy -- -D warnings
gnu-testsuite:
name: GNU test suite
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: cargo build --release
# do not fail, the report is merely informative (at least until all tests pass reliably)
- run: ./tests/run-upstream-testsuite.sh release || true
env:
TERM: xterm
- uses: actions/upload-artifact@v4
with:
name: test-results.json
path: tests/test-results.json
- run: ./tests/print-test-results.sh tests/test-results.json
coverage:
name: Code Coverage
runs-on: ${{ matrix.job.os }}
strategy:
fail-fast: false
matrix:
job:
- { os: ubuntu-latest , features: unix }
- { os: macos-latest , features: macos }
- { os: windows-latest , features: windows }
steps:
- uses: actions/checkout@v4
- name: Initialize workflow variables
id: vars
shell: bash
run: |
## VARs setup
outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; }
# toolchain
TOOLCHAIN="nightly" ## default to "nightly" toolchain (required for certain required unstable compiler flags) ## !maint: refactor when stable channel has needed support
# * specify gnu-type TOOLCHAIN for windows; `grcov` requires gnu-style code coverage data files
case ${{ matrix.job.os }} in windows-*) TOOLCHAIN="$TOOLCHAIN-x86_64-pc-windows-gnu" ;; esac;
# * use requested TOOLCHAIN if specified
if [ -n "${{ matrix.job.toolchain }}" ]; then TOOLCHAIN="${{ matrix.job.toolchain }}" ; fi
outputs TOOLCHAIN
# target-specific options
# * CARGO_FEATURES_OPTION
CARGO_FEATURES_OPTION='--all -- --check' ; ## default to '--all-features' for code coverage
# * CODECOV_FLAGS
CODECOV_FLAGS=$( echo "${{ matrix.job.os }}" | sed 's/[^[:alnum:]]/_/g' )
outputs CODECOV_FLAGS
- name: rust toolchain ~ install
uses: dtolnay/rust-toolchain@nightly
- name: set up PATH on Windows
# Needed to use GNU's patch.exe instead of Strawberry Perl patch
if: runner.os == 'Windows'
run: echo "C:\Program Files\Git\usr\bin" >> $env:GITHUB_PATH
- name: Test
run: cargo test ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --no-fail-fast
env:
CARGO_INCREMENTAL: "0"
RUSTC_WRAPPER: ""
RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort"
RUSTDOCFLAGS: "-Cpanic=abort"
- name: "`grcov` ~ install"
id: build_grcov
shell: bash
run: |
git clone https://github.com/mozilla/grcov.git ~/grcov/
cd ~/grcov
# Hardcode the version of crossbeam-epoch. See
# https://github.com/uutils/coreutils/issues/3680
sed -i -e "s|tempfile =|crossbeam-epoch = \"=0.9.8\"\ntempfile =|" Cargo.toml
cargo install --path .
cd -
# Uncomment when the upstream issue
# https://github.com/mozilla/grcov/issues/849 is fixed
# uses: actions-rs/install@v0.1
# with:
# crate: grcov
# version: latest
# use-tool-cache: false
- name: Generate coverage data (via `grcov`)
id: coverage
shell: bash
run: |
## Generate coverage data
COVERAGE_REPORT_DIR="target/debug"
COVERAGE_REPORT_FILE="${COVERAGE_REPORT_DIR}/lcov.info"
mkdir -p "${COVERAGE_REPORT_DIR}"
# display coverage files
grcov . --output-type files --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" | sort --unique
# generate coverage report
grcov . --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --branch --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()"
echo "report=${COVERAGE_REPORT_FILE}" >> $GITHUB_OUTPUT
- name: Upload coverage results (to Codecov.io)
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ${{ steps.coverage.outputs.report }}
## flags: IntegrationTests, UnitTests, ${{ steps.vars.outputs.CODECOV_FLAGS }}
flags: ${{ steps.vars.outputs.CODECOV_FLAGS }}
name: codecov-umbrella
fail_ci_if_error: false
+72
View File
@@ -0,0 +1,72 @@
name: Fuzzing
# spell-checker:ignore fuzzer
on:
pull_request:
push:
branches:
- main
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
fuzz-build:
name: Build the fuzzers
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
- name: Install `cargo-fuzz`
run: cargo install cargo-fuzz
- uses: Swatinem/rust-cache@v2
with:
shared-key: "cargo-fuzz-cache-key"
cache-directories: "fuzz/target"
- name: Run `cargo-fuzz build`
run: cargo +nightly fuzz build
fuzz-run:
needs: fuzz-build
name: Run the fuzzers
runs-on: ubuntu-latest
timeout-minutes: 5
env:
RUN_FOR: 60
strategy:
matrix:
test-target:
- { name: fuzz_ed, should_pass: true }
- { name: fuzz_normal, should_pass: true }
- { name: fuzz_patch, should_pass: true }
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
- name: Install `cargo-fuzz`
run: cargo install cargo-fuzz
- uses: Swatinem/rust-cache@v2
with:
shared-key: "cargo-fuzz-cache-key"
cache-directories: "fuzz/target"
- name: Restore Cached Corpus
uses: actions/cache/restore@v4
with:
key: corpus-cache-${{ matrix.test-target.name }}
path: |
fuzz/corpus/${{ matrix.test-target.name }}
- name: Run ${{ matrix.test-target.name }} for XX seconds
shell: bash
continue-on-error: ${{ !matrix.test-target.name.should_pass }}
run: |
cargo +nightly fuzz run ${{ matrix.test-target.name }} -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0
- name: Save Corpus Cache
uses: actions/cache/save@v4
with:
key: corpus-cache-${{ matrix.test-target.name }}
path: |
fuzz/corpus/${{ matrix.test-target.name }}
+266
View File
@@ -0,0 +1,266 @@
# Copyright 2022-2023, axodotdev
# SPDX-License-Identifier: MIT or Apache-2.0
#
# CI that:
#
# * checks for a Git Tag that looks like a release
# * builds artifacts with cargo-dist (archives, installers, hashes)
# * uploads those artifacts to temporary workflow zip
# * on success, uploads the artifacts to a Github Release
#
# Note that the Github Release will be created with a generated
# title/body based on your changelogs.
name: Release
permissions:
contents: write
# This task will run whenever you push a git tag that looks like a version
# like "1.0.0", "v0.1.0-prerelease.1", "my-app/0.1.0", "releases/v1.0.0", etc.
# Various formats will be parsed into a VERSION and an optional PACKAGE_NAME, where
# PACKAGE_NAME must be the name of a Cargo package in your workspace, and VERSION
# must be a Cargo-style SemVer Version (must have at least major.minor.patch).
#
# If PACKAGE_NAME is specified, then the announcement will be for that
# package (erroring out if it doesn't have the given version or isn't cargo-dist-able).
#
# If PACKAGE_NAME isn't specified, then the announcement will be for all
# (cargo-dist-able) packages in the workspace with that version (this mode is
# intended for workspaces with only one dist-able package, or with all dist-able
# packages versioned/released in lockstep).
#
# If you push multiple tags at once, separate instances of this workflow will
# spin up, creating an independent announcement for each one. However Github
# will hard limit this to 3 tags per commit, as it will assume more tags is a
# mistake.
#
# If there's a prerelease-style suffix to the version, then the release(s)
# will be marked as a prerelease.
on:
push:
tags:
- '**[0-9]+.[0-9]+.[0-9]+*'
pull_request:
jobs:
# Run 'cargo dist plan' (or host) to determine what tasks we need to do
plan:
runs-on: ubuntu-latest
outputs:
val: ${{ steps.plan.outputs.manifest }}
tag: ${{ !github.event.pull_request && github.ref_name || '' }}
tag-flag: ${{ !github.event.pull_request && format('--tag={0}', github.ref_name) || '' }}
publishing: ${{ !github.event.pull_request }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install cargo-dist
# we specify bash to get pipefail; it guards against the `curl` command
# failing. otherwise `sh` won't catch that `curl` returned non-0
shell: bash
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.12.0/cargo-dist-installer.sh | sh"
# sure would be cool if github gave us proper conditionals...
# so here's a doubly-nested ternary-via-truthiness to try to provide the best possible
# functionality based on whether this is a pull_request, and whether it's from a fork.
# (PRs run on the *source* but secrets are usually on the *target* -- that's *good*
# but also really annoying to build CI around when it needs secrets to work right.)
- id: plan
run: |
cargo dist ${{ (!github.event.pull_request && format('host --steps=create --tag={0}', github.ref_name)) || 'plan' }} --output-format=json > plan-dist-manifest.json
echo "cargo dist ran successfully"
cat plan-dist-manifest.json
echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
- name: "Upload dist-manifest.json"
uses: actions/upload-artifact@v4
with:
name: artifacts-plan-dist-manifest
path: plan-dist-manifest.json
# Build and packages all the platform-specific things
build-local-artifacts:
name: build-local-artifacts (${{ join(matrix.targets, ', ') }})
# Let the initial task tell us to not run (currently very blunt)
needs:
- plan
if: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix.include != null && (needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload') }}
strategy:
fail-fast: false
# Target platforms/runners are computed by cargo-dist in create-release.
# Each member of the matrix has the following arguments:
#
# - runner: the github runner
# - dist-args: cli flags to pass to cargo dist
# - install-dist: expression to run to install cargo-dist on the runner
#
# Typically there will be:
# - 1 "global" task that builds universal installers
# - N "local" tasks that build each platform's binaries and platform-specific installers
matrix: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix }}
runs-on: ${{ matrix.runner }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: swatinem/rust-cache@v2
- name: Install cargo-dist
run: ${{ matrix.install_dist }}
# Get the dist-manifest
- name: Fetch local artifacts
uses: actions/download-artifact@v4
with:
pattern: artifacts-*
path: target/distrib/
merge-multiple: true
- name: Install dependencies
run: |
${{ matrix.packages_install }}
- name: Build artifacts
run: |
# Actually do builds and make zips and whatnot
cargo dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest.json
echo "cargo dist ran successfully"
- id: cargo-dist
name: Post-build
# We force bash here just because github makes it really hard to get values up
# to "real" actions without writing to env-vars, and writing to env-vars has
# inconsistent syntax between shell and powershell.
shell: bash
run: |
# Parse out what we just built and upload it to scratch storage
echo "paths<<EOF" >> "$GITHUB_OUTPUT"
jq --raw-output ".artifacts[]?.path | select( . != null )" dist-manifest.json >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
- name: "Upload artifacts"
uses: actions/upload-artifact@v4
with:
name: artifacts-build-local-${{ join(matrix.targets, '_') }}
path: |
${{ steps.cargo-dist.outputs.paths }}
${{ env.BUILD_MANIFEST_NAME }}
# Build and package all the platform-agnostic(ish) things
build-global-artifacts:
needs:
- plan
- build-local-artifacts
runs-on: "ubuntu-20.04"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install cargo-dist
shell: bash
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.12.0/cargo-dist-installer.sh | sh"
# Get all the local artifacts for the global tasks to use (for e.g. checksums)
- name: Fetch local artifacts
uses: actions/download-artifact@v4
with:
pattern: artifacts-*
path: target/distrib/
merge-multiple: true
- id: cargo-dist
shell: bash
run: |
cargo dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json
echo "cargo dist ran successfully"
# Parse out what we just built and upload it to scratch storage
echo "paths<<EOF" >> "$GITHUB_OUTPUT"
jq --raw-output ".artifacts[]?.path | select( . != null )" dist-manifest.json >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
- name: "Upload artifacts"
uses: actions/upload-artifact@v4
with:
name: artifacts-build-global
path: |
${{ steps.cargo-dist.outputs.paths }}
${{ env.BUILD_MANIFEST_NAME }}
# Determines if we should publish/announce
host:
needs:
- plan
- build-local-artifacts
- build-global-artifacts
# Only run if we're "publishing", and only if local and global didn't fail (skipped is fine)
if: ${{ always() && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
runs-on: "ubuntu-20.04"
outputs:
val: ${{ steps.host.outputs.manifest }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install cargo-dist
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.12.0/cargo-dist-installer.sh | sh"
# Fetch artifacts from scratch-storage
- name: Fetch artifacts
uses: actions/download-artifact@v4
with:
pattern: artifacts-*
path: target/distrib/
merge-multiple: true
# This is a harmless no-op for Github Releases, hosting for that happens in "announce"
- id: host
shell: bash
run: |
cargo dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json
echo "artifacts uploaded and released successfully"
cat dist-manifest.json
echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
- name: "Upload dist-manifest.json"
uses: actions/upload-artifact@v4
with:
# Overwrite the previous copy
name: artifacts-dist-manifest
path: dist-manifest.json
# Create a Github Release while uploading all files to it
announce:
needs:
- plan
- host
# use "always() && ..." to allow us to wait for all publish jobs while
# still allowing individual publish jobs to skip themselves (for prereleases).
# "host" however must run to completion, no skipping allowed!
if: ${{ always() && needs.host.result == 'success' }}
runs-on: "ubuntu-20.04"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: "Download Github Artifacts"
uses: actions/download-artifact@v4
with:
pattern: artifacts-*
path: artifacts
merge-multiple: true
- name: Cleanup
run: |
# Remove the granular manifests
rm -f artifacts/*-dist-manifest.json
- name: Create Github Release
uses: ncipollo/release-action@v1
with:
tag: ${{ needs.plan.outputs.tag }}
name: ${{ fromJson(needs.host.outputs.val).announcement_title }}
body: ${{ fromJson(needs.host.outputs.val).announcement_github_body }}
prerelease: ${{ fromJson(needs.host.outputs.val).announcement_is_prerelease }}
artifacts: "artifacts/*"
-1
View File
@@ -1,3 +1,2 @@
/target
Cargo.lock
*.swp
Generated
+589
View File
File diff suppressed because it is too large Load Diff
+40 -12
View File
@@ -1,18 +1,46 @@
[package]
name = "unified-diff"
version = "0.2.1"
authors = [
"Michael Howell <michael@notriddle.com>",
"The Rust Project Developers"
]
edition = "2018"
description = "An implementation of the GNU unified diff format"
name = "diffutils"
version = "0.4.1"
edition = "2021"
description = "A CLI app for generating diff files"
license = "MIT OR Apache-2.0"
repository = "https://github.com/notriddle/rust-unified-diff"
exclude = [ "fuzz" ]
repository = "https://github.com/uutils/diffutils"
[lib]
name = "diffutilslib"
path = "src/lib.rs"
[[bin]]
name = "unified-diff"
name = "diffutils"
path = "src/main.rs"
[dependencies]
diff = "0.1.10"
chrono = "0.4.38"
diff = "0.1.13"
regex = "1.10.4"
same-file = "1.0.6"
unicode-width = "0.1.11"
[dev-dependencies]
pretty_assertions = "1"
assert_cmd = "2.0.14"
predicates = "3.1.0"
tempfile = "3.10.1"
# The profile that 'cargo dist' will build with
[profile.dist]
inherits = "release"
lto = "thin"
# Config for 'cargo dist'
[workspace.metadata.dist]
# The preferred cargo-dist version to use in CI (Cargo.toml SemVer syntax)
cargo-dist-version = "0.12.0"
# CI backends to support
ci = ["github"]
# The installers to generate for each app
installers = []
# Target platforms to build apps for (Rust target-triple syntax)
targets = ["aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc"]
# Publish jobs to run in CI
pr-run-mode = "plan"
+3
View File
@@ -1,3 +1,6 @@
Copyright (c) Michael Howell
Copyright (c) uutils developers
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
+3
View File
@@ -1,3 +1,6 @@
Copyright (c) Michael Howell
Copyright (c) uutils developers
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
+50 -35
View File
@@ -1,41 +1,56 @@
A GNU unified diff generator. Oracle tested against GNU patch 2.7.6
[![Crates.io](https://img.shields.io/crates/v/diffutils.svg)](https://crates.io/crates/diffutils)
[![Discord](https://img.shields.io/badge/discord-join-7289DA.svg?logo=discord&longCache=true&style=flat)](https://discord.gg/wQVJbvJ)
[![License](http://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/uutils/diffutils/blob/main/LICENSE)
[![dependency status](https://deps.rs/repo/github/uutils/diffutils/status.svg)](https://deps.rs/repo/github/uutils/diffutils)
Based on the incomplete diff generator in https://github.com/rust-lang/rust/blob/master/src/tools/compiletest/src/runtest.rs,
but it implements a different format.
[![CodeCov](https://codecov.io/gh/uutils/diffutils/branch/main/graph/badge.svg)](https://codecov.io/gh/uutils/diffutils)
The goal of this package is to be a drop-in replacement for the [diffutils commands](https://www.gnu.org/software/diffutils/) in Rust.
Based on the incomplete diff generator in https://github.com/rust-lang/rust/blob/master/src/tools/compiletest/src/runtest.rs, and made to be compatible with GNU's diff and patch tools.
## Installation
Ensure you have Rust installed on your system. You can install Rust through [rustup](https://rustup.rs/).
Clone the repository and build the project using Cargo:
```bash
git clone https://github.com/uutils/diffutils.git
cd diffutils
cargo build --release
```
~/unified-diff$ cargo run Cargo.lock Cargo.toml
## Example
```bash
cat <<EOF >fruits_old.txt
Apple
Banana
Cherry
EOF
cat <<EOF >fruits_new.txt
Apple
Fig
Cherry
EOF
$ cargo run -- -u fruits_old.txt fruits_new.txt
Finished dev [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/unified-diff Cargo.lock Cargo.toml`
--- Cargo.lock
+++ Cargo.toml
@@ -1,14 +1,14 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-[[package]]
-name = "diff"
-version = "0.1.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499"
-
-[[package]]
+[package]
name = "unified-diff"
version = "0.1.0"
-dependencies = [
- "diff",
+authors = [
+ "Michael Howell <michael@notriddle.com>",
+ "The Rust Project Developers"
]
+edition = "2018"
+
+[[bin]]
+name = "unified-diff"
+
+[dependencies]
+diff = "0.1.10"
~/unified-diff$ rustup override set nightly
~/unified-diff$ cargo fuzz run fuzz_patch
Running `target/debug/diffutils -u fruits_old.txt fruits_new.txt`
--- fruits_old.txt
+++ fruits_new.txt
@@ -1,3 +1,3 @@
Apple
-Banana
+Fig
Cherry
```
## License
diffutils is licensed under the MIT and Apache Licenses - see the `LICENSE-MIT` or `LICENSE-APACHE` files for details
+16 -5
View File
@@ -2,7 +2,6 @@
[package]
name = "unified-diff-fuzz"
version = "0.0.0"
authors = ["Automatically generated"]
publish = false
edition = "2018"
@@ -10,10 +9,8 @@ edition = "2018"
cargo-fuzz = true
[dependencies]
libfuzzer-sys = "0.3"
[dependencies.unified-diff]
path = ".."
libfuzzer-sys = "0.4"
diffutils = { path = "../" }
# Prevent this from interfering with workspaces
[workspace]
@@ -25,3 +22,17 @@ path = "fuzz_targets/fuzz_patch.rs"
test = false
doc = false
[[bin]]
name = "fuzz_normal"
path = "fuzz_targets/fuzz_normal.rs"
test = false
doc = false
[[bin]]
name = "fuzz_ed"
path = "fuzz_targets/fuzz_ed.rs"
test = false
doc = false
+78
View File
@@ -0,0 +1,78 @@
#![no_main]
#[macro_use]
extern crate libfuzzer_sys;
use diffutilslib::ed_diff;
use diffutilslib::ed_diff::DiffError;
use diffutilslib::params::Params;
use std::fs::{self, File};
use std::io::Write;
use std::process::Command;
fn diff_w(expected: &[u8], actual: &[u8], filename: &str) -> Result<Vec<u8>, DiffError> {
let mut output = ed_diff::diff(expected, actual, &Params::default())?;
writeln!(&mut output, "w {filename}").unwrap();
Ok(output)
}
fuzz_target!(|x: (Vec<u8>, Vec<u8>)| {
let (mut from, mut to) = x;
from.push(b'\n');
to.push(b'\n');
if let Ok(s) = String::from_utf8(from.clone()) {
if !s.is_ascii() {
return;
}
if s.find(|x| x < ' ' && x != '\n').is_some() {
return;
}
} else {
return;
}
if let Ok(s) = String::from_utf8(to.clone()) {
if !s.is_ascii() {
return;
}
if s.find(|x| x < ' ' && x != '\n').is_some() {
return;
}
} else {
return;
}
let diff = diff_w(&from, &to, "target/fuzz.file").unwrap();
File::create("target/fuzz.file.original")
.unwrap()
.write_all(&from)
.unwrap();
File::create("target/fuzz.file.expected")
.unwrap()
.write_all(&to)
.unwrap();
File::create("target/fuzz.file")
.unwrap()
.write_all(&from)
.unwrap();
File::create("target/fuzz.ed")
.unwrap()
.write_all(&diff)
.unwrap();
let output = Command::new("ed")
.arg("target/fuzz.file")
.stdin(File::open("target/fuzz.ed").unwrap())
.output()
.unwrap();
if !output.status.success() {
panic!(
"STDOUT:\n{}\nSTDERR:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
let result = fs::read("target/fuzz.file").unwrap();
if result != to {
panic!(
"STDOUT:\n{}\nSTDERR:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
});
+66
View File
@@ -0,0 +1,66 @@
#![no_main]
#[macro_use]
extern crate libfuzzer_sys;
use diffutilslib::normal_diff;
use diffutilslib::params::Params;
use std::fs::{self, File};
use std::io::Write;
use std::process::Command;
fuzz_target!(|x: (Vec<u8>, Vec<u8>)| {
let (from, to) = x;
/*if let Ok(s) = String::from_utf8(from.clone()) {
if !s.is_ascii() { return }
if s.find(|x| x < ' ' && x != '\n').is_some() { return }
} else {
return
}
if let Ok(s) = String::from_utf8(to.clone()) {
if !s.is_ascii() { return }
if s.find(|x| x < ' ' && x != '\n').is_some() { return }
} else {
return
}*/
let diff = normal_diff::diff(&from, &to, &Params::default());
File::create("target/fuzz.file.original")
.unwrap()
.write_all(&from)
.unwrap();
File::create("target/fuzz.file.expected")
.unwrap()
.write_all(&to)
.unwrap();
File::create("target/fuzz.file")
.unwrap()
.write_all(&from)
.unwrap();
File::create("target/fuzz.diff")
.unwrap()
.write_all(&diff)
.unwrap();
let output = Command::new("patch")
.arg("-p0")
.arg("--binary")
.arg("--fuzz=0")
.arg("--normal")
.arg("target/fuzz.file")
.stdin(File::open("target/fuzz.diff").unwrap())
.output()
.unwrap();
if !output.status.success() {
panic!(
"STDOUT:\n{}\nSTDERR:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
let result = fs::read("target/fuzz.file").unwrap();
if result != to {
panic!(
"STDOUT:\n{}\nSTDERR:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
});
+24 -7
View File
@@ -1,7 +1,8 @@
#![no_main]
#[macro_use] extern crate libfuzzer_sys;
extern crate unified_diff;
#[macro_use]
extern crate libfuzzer_sys;
use diffutilslib::params::Params;
use diffutilslib::unified_diff;
use std::fs::{self, File};
use std::io::Write;
use std::process::Command;
@@ -20,7 +21,16 @@ fuzz_target!(|x: (Vec<u8>, Vec<u8>, u8)| {
} else {
return
}*/
let diff = unified_diff::diff(&from, "a/fuzz.file", &to, "target/fuzz.file", context as usize);
let diff = unified_diff::diff(
&from,
&to,
&Params {
from: "a/fuzz.file".into(),
to: "target/fuzz.file".into(),
context_count: context as usize,
..Default::default()
}
);
File::create("target/fuzz.file.original")
.unwrap()
.write_all(&from)
@@ -45,11 +55,18 @@ fuzz_target!(|x: (Vec<u8>, Vec<u8>, u8)| {
.output()
.unwrap();
if !output.status.success() {
panic!("STDOUT:\n{}\nSTDERR:\n{}", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr));
panic!(
"STDOUT:\n{}\nSTDERR:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
let result = fs::read("target/fuzz.file").unwrap();
if result != to {
panic!("STDOUT:\n{}\nSTDERR:\n{}", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr));
panic!(
"STDOUT:\n{}\nSTDERR:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
});
+6
View File
@@ -0,0 +1,6 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended"
]
}
+796
View File
File diff suppressed because it is too large Load Diff
+449
View File
@@ -0,0 +1,449 @@
// This file is part of the uutils diffutils package.
//
// For the full copyright and license information, please view the LICENSE-*
// files that was distributed with this source code.
use std::io::Write;
use crate::params::Params;
use crate::utils::do_write_line;
#[derive(Debug, PartialEq)]
struct Mismatch {
pub line_number_expected: usize,
pub line_number_actual: usize,
pub expected: Vec<Vec<u8>>,
pub actual: Vec<Vec<u8>>,
}
#[derive(Debug, PartialEq, Eq)]
pub enum DiffError {
MissingNL,
}
impl std::fmt::Display for DiffError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
std::fmt::Display::fmt("No newline at end of file", f)
}
}
impl From<DiffError> for String {
fn from(_: DiffError) -> String {
"No newline at end of file".into()
}
}
impl Mismatch {
fn new(line_number_expected: usize, line_number_actual: usize) -> Mismatch {
Mismatch {
line_number_expected,
line_number_actual,
expected: Vec::new(),
actual: Vec::new(),
}
}
}
// Produces a diff between the expected output and actual output.
fn make_diff(expected: &[u8], actual: &[u8], stop_early: bool) -> Result<Vec<Mismatch>, DiffError> {
let mut line_number_expected = 1;
let mut line_number_actual = 1;
let mut results = Vec::new();
let mut mismatch = Mismatch::new(line_number_expected, line_number_actual);
let mut expected_lines: Vec<&[u8]> = expected.split(|&c| c == b'\n').collect();
let mut actual_lines: Vec<&[u8]> = actual.split(|&c| c == b'\n').collect();
debug_assert_eq!(b"".split(|&c| c == b'\n').count(), 1);
// ^ means that underflow here is impossible
let _expected_lines_count = expected_lines.len() - 1;
let _actual_lines_count = actual_lines.len() - 1;
if expected_lines.last() == Some(&&b""[..]) {
expected_lines.pop();
} else {
return Err(DiffError::MissingNL);
}
if actual_lines.last() == Some(&&b""[..]) {
actual_lines.pop();
} else {
return Err(DiffError::MissingNL);
}
for result in diff::slice(&expected_lines, &actual_lines) {
match result {
diff::Result::Left(str) => {
if !mismatch.actual.is_empty() {
results.push(mismatch);
mismatch = Mismatch::new(line_number_expected, line_number_actual);
}
mismatch.expected.push(str.to_vec());
line_number_expected += 1;
}
diff::Result::Right(str) => {
mismatch.actual.push(str.to_vec());
line_number_actual += 1;
}
diff::Result::Both(_str, _) => {
line_number_expected += 1;
line_number_actual += 1;
if !mismatch.actual.is_empty() || !mismatch.expected.is_empty() {
results.push(mismatch);
mismatch = Mismatch::new(line_number_expected, line_number_actual);
} else {
mismatch.line_number_expected = line_number_expected;
mismatch.line_number_actual = line_number_actual;
}
}
}
if stop_early && !results.is_empty() {
// Optimization: stop analyzing the files as soon as there are any differences
return Ok(results);
}
}
if !mismatch.actual.is_empty() || !mismatch.expected.is_empty() {
results.push(mismatch);
}
Ok(results)
}
pub fn diff(expected: &[u8], actual: &[u8], params: &Params) -> Result<Vec<u8>, DiffError> {
let mut output = Vec::new();
let diff_results = make_diff(expected, actual, params.brief)?;
if params.brief && !diff_results.is_empty() {
write!(&mut output, "\0").unwrap();
return Ok(output);
}
let mut lines_offset = 0;
for result in diff_results {
let line_number_expected: isize = result.line_number_expected as isize + lines_offset;
let _line_number_actual: isize = result.line_number_actual as isize + lines_offset;
let expected_count: isize = result.expected.len() as isize;
let actual_count: isize = result.actual.len() as isize;
match (expected_count, actual_count) {
(0, 0) => unreachable!(),
(0, _) => writeln!(&mut output, "{}a", line_number_expected - 1).unwrap(),
(_, 0) => writeln!(
&mut output,
"{},{}d",
line_number_expected,
expected_count + line_number_expected - 1
)
.unwrap(),
(1, _) => writeln!(&mut output, "{line_number_expected}c").unwrap(),
_ => writeln!(
&mut output,
"{},{}c",
line_number_expected,
expected_count + line_number_expected - 1
)
.unwrap(),
}
lines_offset += actual_count - expected_count;
if actual_count != 0 {
for actual in &result.actual {
if actual == b"." {
writeln!(&mut output, "..\n.\ns/.//\na").unwrap();
} else {
do_write_line(&mut output, actual, params.expand_tabs, params.tabsize).unwrap();
writeln!(&mut output).unwrap();
}
}
writeln!(&mut output, ".").unwrap();
}
}
Ok(output)
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
pub fn diff_w(expected: &[u8], actual: &[u8], filename: &str) -> Result<Vec<u8>, DiffError> {
let mut output = diff(expected, actual, &Params::default())?;
writeln!(&mut output, "w {filename}").unwrap();
Ok(output)
}
#[test]
fn test_basic() {
let from = b"a\n";
let to = b"b\n";
let diff = diff(from, to, &Params::default()).unwrap();
let expected = ["1c", "b", ".", ""].join("\n");
assert_eq!(diff, expected.as_bytes());
}
#[test]
fn test_permutations() {
let target = "target/ed-diff/";
// test all possible six-line files.
let _ = std::fs::create_dir(target);
for &a in &[0, 1, 2] {
for &b in &[0, 1, 2] {
for &c in &[0, 1, 2] {
for &d in &[0, 1, 2] {
for &e in &[0, 1, 2] {
for &f in &[0, 1, 2] {
use std::fs::File;
use std::io::Write;
let mut alef = Vec::new();
let mut bet = Vec::new();
alef.write_all(if a == 0 { b"a\n" } else { b"b\n" })
.unwrap();
if a != 2 {
bet.write_all(b"b\n").unwrap();
}
alef.write_all(if b == 0 { b"c\n" } else { b"d\n" })
.unwrap();
if b != 2 {
bet.write_all(b"d\n").unwrap();
}
alef.write_all(if c == 0 { b"e\n" } else { b"f\n" })
.unwrap();
if c != 2 {
bet.write_all(b"f\n").unwrap();
}
alef.write_all(if d == 0 { b"g\n" } else { b"h\n" })
.unwrap();
if d != 2 {
bet.write_all(b"h\n").unwrap();
}
alef.write_all(if e == 0 { b"i\n" } else { b"j\n" })
.unwrap();
if e != 2 {
bet.write_all(b"j\n").unwrap();
}
alef.write_all(if f == 0 { b"k\n" } else { b"l\n" })
.unwrap();
if f != 2 {
bet.write_all(b"l\n").unwrap();
}
// This test diff is intentionally reversed.
// We want it to turn the alef into bet.
let diff = diff_w(&alef, &bet, &format!("{target}/alef")).unwrap();
File::create(&format!("{target}/ab.ed"))
.unwrap()
.write_all(&diff)
.unwrap();
let mut fa = File::create(&format!("{target}/alef")).unwrap();
fa.write_all(&alef[..]).unwrap();
let mut fb = File::create(&format!("{target}/bet")).unwrap();
fb.write_all(&bet[..]).unwrap();
let _ = fa;
let _ = fb;
#[cfg(not(windows))] // there's no ed on windows
{
use std::process::Command;
let output = Command::new("ed")
.arg(&format!("{target}/alef"))
.stdin(File::open(&format!("{target}/ab.ed")).unwrap())
.output()
.unwrap();
assert!(output.status.success(), "{output:?}");
//println!("{}", String::from_utf8_lossy(&output.stdout));
//println!("{}", String::from_utf8_lossy(&output.stderr));
let alef = std::fs::read(&format!("{target}/alef")).unwrap();
assert_eq!(alef, bet);
}
}
}
}
}
}
}
}
#[test]
fn test_permutations_empty_lines() {
let target = "target/ed-diff/";
// test all possible six-line files with missing newlines.
let _ = std::fs::create_dir(target);
for &a in &[0, 1, 2] {
for &b in &[0, 1, 2] {
for &c in &[0, 1, 2] {
for &d in &[0, 1, 2] {
for &e in &[0, 1, 2] {
for &f in &[0, 1, 2] {
use std::fs::File;
use std::io::Write;
let mut alef = Vec::new();
let mut bet = Vec::new();
alef.write_all(if a == 0 { b"\n" } else { b"b\n" }).unwrap();
if a != 2 {
bet.write_all(b"b\n").unwrap();
}
alef.write_all(if b == 0 { b"\n" } else { b"d\n" }).unwrap();
if b != 2 {
bet.write_all(b"d\n").unwrap();
}
alef.write_all(if c == 0 { b"\n" } else { b"f\n" }).unwrap();
if c != 2 {
bet.write_all(b"f\n").unwrap();
}
alef.write_all(if d == 0 { b"\n" } else { b"h\n" }).unwrap();
if d != 2 {
bet.write_all(b"h\n").unwrap();
}
alef.write_all(if e == 0 { b"\n" } else { b"j\n" }).unwrap();
if e != 2 {
bet.write_all(b"j\n").unwrap();
}
alef.write_all(if f == 0 { b"\n" } else { b"l\n" }).unwrap();
if f != 2 {
bet.write_all(b"l\n").unwrap();
}
// This test diff is intentionally reversed.
// We want it to turn the alef into bet.
let diff = diff_w(&alef, &bet, &format!("{target}/alef_")).unwrap();
File::create(&format!("{target}/ab_.ed"))
.unwrap()
.write_all(&diff)
.unwrap();
let mut fa = File::create(&format!("{target}/alef_")).unwrap();
fa.write_all(&alef[..]).unwrap();
let mut fb = File::create(&format!("{target}/bet_")).unwrap();
fb.write_all(&bet[..]).unwrap();
let _ = fa;
let _ = fb;
#[cfg(not(windows))] // there's no ed on windows
{
use std::process::Command;
let output = Command::new("ed")
.arg(&format!("{target}/alef_"))
.stdin(File::open(&format!("{target}/ab_.ed")).unwrap())
.output()
.unwrap();
assert!(output.status.success(), "{output:?}");
//println!("{}", String::from_utf8_lossy(&output.stdout));
//println!("{}", String::from_utf8_lossy(&output.stderr));
let alef = std::fs::read(&format!("{target}/alef_")).unwrap();
assert_eq!(alef, bet);
}
}
}
}
}
}
}
}
#[test]
fn test_permutations_reverse() {
let target = "target/ed-diff/";
// test all possible six-line files.
let _ = std::fs::create_dir(target);
for &a in &[0, 1, 2] {
for &b in &[0, 1, 2] {
for &c in &[0, 1, 2] {
for &d in &[0, 1, 2] {
for &e in &[0, 1, 2] {
for &f in &[0, 1, 2] {
use std::fs::File;
use std::io::Write;
let mut alef = Vec::new();
let mut bet = Vec::new();
alef.write_all(if a == 0 { b"a\n" } else { b"f\n" })
.unwrap();
if a != 2 {
bet.write_all(b"a\n").unwrap();
}
alef.write_all(if b == 0 { b"b\n" } else { b"e\n" })
.unwrap();
if b != 2 {
bet.write_all(b"b\n").unwrap();
}
alef.write_all(if c == 0 { b"c\n" } else { b"d\n" })
.unwrap();
if c != 2 {
bet.write_all(b"c\n").unwrap();
}
alef.write_all(if d == 0 { b"d\n" } else { b"c\n" })
.unwrap();
if d != 2 {
bet.write_all(b"d\n").unwrap();
}
alef.write_all(if e == 0 { b"e\n" } else { b"b\n" })
.unwrap();
if e != 2 {
bet.write_all(b"e\n").unwrap();
}
alef.write_all(if f == 0 { b"f\n" } else { b"a\n" })
.unwrap();
if f != 2 {
bet.write_all(b"f\n").unwrap();
}
// This test diff is intentionally reversed.
// We want it to turn the alef into bet.
let diff = diff_w(&alef, &bet, &format!("{target}/alefr")).unwrap();
File::create(&format!("{target}/abr.ed"))
.unwrap()
.write_all(&diff)
.unwrap();
let mut fa = File::create(&format!("{target}/alefr")).unwrap();
fa.write_all(&alef[..]).unwrap();
let mut fb = File::create(&format!("{target}/betr")).unwrap();
fb.write_all(&bet[..]).unwrap();
let _ = fa;
let _ = fb;
#[cfg(not(windows))] // there's no ed on windows
{
use std::process::Command;
let output = Command::new("ed")
.arg(&format!("{target}/alefr"))
.stdin(File::open(&format!("{target}/abr.ed")).unwrap())
.output()
.unwrap();
assert!(output.status.success(), "{output:?}");
//println!("{}", String::from_utf8_lossy(&output.stdout));
//println!("{}", String::from_utf8_lossy(&output.stderr));
let alef = std::fs::read(&format!("{target}/alefr")).unwrap();
assert_eq!(alef, bet);
}
}
}
}
}
}
}
}
#[test]
fn test_stop_early() {
let from = ["a", "b", "c", ""].join("\n");
let to = ["a", "d", "c", ""].join("\n");
let diff_full = diff(from.as_bytes(), to.as_bytes(), &Params::default()).unwrap();
let expected_full = ["2c", "d", ".", ""].join("\n");
assert_eq!(diff_full, expected_full.as_bytes());
let diff_brief = diff(
from.as_bytes(),
to.as_bytes(),
&Params {
brief: true,
..Default::default()
},
)
.unwrap();
let expected_brief = "\0".as_bytes();
assert_eq!(diff_brief, expected_brief);
let nodiff_full = diff(from.as_bytes(), from.as_bytes(), &Params::default()).unwrap();
assert!(nodiff_full.is_empty());
let nodiff_brief = diff(
from.as_bytes(),
from.as_bytes(),
&Params {
brief: true,
..Default::default()
},
)
.unwrap();
assert!(nodiff_brief.is_empty());
}
}
+12 -781
View File
File diff suppressed because it is too large Load Diff
+25
View File
@@ -0,0 +1,25 @@
// asserts equality of the actual diff and expected diff
// considering datetime varitations
//
// It replaces the modification time in the actual diff
// with placeholder "TIMESTAMP" and then asserts the equality
//
// For eg.
// let brief = "*** fruits_old.txt\t2024-03-24 23:43:05.189597645 +0530\n
// --- fruits_new.txt\t2024-03-24 23:35:08.922581904 +0530\n";
//
// replaced = "*** fruits_old.txt\tTIMESTAMP\n
// --- fruits_new.txt\tTIMESTAMP\n";
#[macro_export]
macro_rules! assert_diff_eq {
($actual:expr, $expected:expr) => {{
use regex::Regex;
use std::str;
let diff = str::from_utf8(&$actual).unwrap();
let re = Regex::new(r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+ [+-]\d{4}").unwrap();
let actual = re.replacen(diff, 2, "TIMESTAMP");
assert_eq!(actual, $expected);
}};
}
+81 -40
View File
@@ -1,55 +1,96 @@
// Sample program. Do not use.
// This file is part of the uutils diffutils package.
//
// For the full copyright and license information, please view the LICENSE-*
// files that was distributed with this source code.
use crate::params::{parse_params, Format};
use std::env;
use std::ffi::OsString;
use std::fs;
use std::io::{self, Write};
use std::process;
fn main() {
let mut o = env::args_os();
// parse CLI
let exe = match o.next() {
Some(from) => from,
None => {
eprintln!("Usage: [exe] [from] [to]");
process::exit(1);
}
};
let from = match o.next() {
Some(from) => from,
None => {
eprintln!("Usage: {} [from] [to]", exe.to_string_lossy());
process::exit(1);
}
};
let to = match o.next() {
Some(from) => from,
None => {
eprintln!("Usage: {} [from] [to]", exe.to_string_lossy());
process::exit(1);
use std::io::{self, Read, Write};
use std::process::{exit, ExitCode};
mod context_diff;
mod ed_diff;
mod macros;
mod normal_diff;
mod params;
mod unified_diff;
mod utils;
// Exit codes are documented at
// https://www.gnu.org/software/diffutils/manual/html_node/Invoking-diff.html.
// An exit status of 0 means no differences were found,
// 1 means some differences were found,
// and 2 means trouble.
fn main() -> ExitCode {
let opts = env::args_os();
let params = parse_params(opts).unwrap_or_else(|error| {
eprintln!("{error}");
exit(2);
});
// if from and to are the same file, no need to perform any comparison
let maybe_report_identical_files = || {
if params.report_identical_files {
println!(
"Files {} and {} are identical",
params.from.to_string_lossy(),
params.to.to_string_lossy(),
);
}
};
if params.from == "-" && params.to == "-"
|| same_file::is_same_file(&params.from, &params.to).unwrap_or(false)
{
maybe_report_identical_files();
return ExitCode::SUCCESS;
}
// read files
let from_content = match fs::read(&from) {
fn read_file_contents(filepath: &OsString) -> io::Result<Vec<u8>> {
if filepath == "-" {
let mut content = Vec::new();
io::stdin().read_to_end(&mut content).and(Ok(content))
} else {
fs::read(filepath)
}
}
let from_content = match read_file_contents(&params.from) {
Ok(from_content) => from_content,
Err(e) => {
eprintln!("Failed to read from-file: {}", e);
process::exit(2);
eprintln!("Failed to read from-file: {e}");
return ExitCode::from(2);
}
};
let to_content = match fs::read(&to) {
let to_content = match read_file_contents(&params.to) {
Ok(to_content) => to_content,
Err(e) => {
eprintln!("Failed to read to-file: {}", e);
process::exit(2);
eprintln!("Failed to read to-file: {e}");
return ExitCode::from(2);
}
};
// run diff
io::stdout()
.write_all(&unified_diff::diff(
&from_content,
&from.to_string_lossy(),
&to_content,
&to.to_string_lossy(),
1,
))
.unwrap();
let result: Vec<u8> = match params.format {
Format::Normal => normal_diff::diff(&from_content, &to_content, &params),
Format::Unified => unified_diff::diff(&from_content, &to_content, &params),
Format::Context => context_diff::diff(&from_content, &to_content, &params),
Format::Ed => ed_diff::diff(&from_content, &to_content, &params).unwrap_or_else(|error| {
eprintln!("{error}");
exit(2);
}),
};
if params.brief && !result.is_empty() {
println!(
"Files {} and {} differ",
params.from.to_string_lossy(),
params.to.to_string_lossy()
);
} else {
io::stdout().write_all(&result).unwrap();
}
if result.is_empty() {
maybe_report_identical_files();
ExitCode::SUCCESS
} else {
ExitCode::from(1)
}
}
+584
View File
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More