From 4ea089fd107a0cf93a75cf1ddf99bcfc268fd07f Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 15 Oct 2025 11:26:50 +0200 Subject: [PATCH] Deduplicate packetline crates --- .github/workflows/ci.yml | 36 -- Cargo.lock | 14 +- Cargo.toml | 1 - etc/scripts/copy-packetline.sh | 152 ----- gix-filter/Cargo.toml | 2 +- gix-filter/src/driver/process/client.rs | 29 +- gix-filter/src/driver/process/mod.rs | 15 +- gix-filter/src/driver/process/server.rs | 22 +- gix-packetline-blocking/CHANGELOG.md | 589 ------------------ gix-packetline-blocking/Cargo.toml | 42 -- gix-packetline-blocking/LICENSE-APACHE | 1 - gix-packetline-blocking/LICENSE-MIT | 1 - gix-packetline-blocking/src/decode.rs | 147 ----- .../src/encode/async_io.rs | 214 ------- .../src/encode/blocking_io.rs | 75 --- gix-packetline-blocking/src/encode/mod.rs | 29 - gix-packetline-blocking/src/lib.rs | 108 ---- gix-packetline-blocking/src/line/async_io.rs | 49 -- .../src/line/blocking_io.rs | 46 -- gix-packetline-blocking/src/line/mod.rs | 90 --- gix-packetline-blocking/src/read/async_io.rs | 198 ------ .../src/read/blocking_io.rs | 189 ------ gix-packetline-blocking/src/read/mod.rs | 130 ---- .../src/read/sidebands/async_io.rs | 373 ----------- .../src/read/sidebands/blocking_io.rs | 210 ------- .../src/read/sidebands/mod.rs | 11 - gix-packetline-blocking/src/write/async_io.rs | 98 --- .../src/write/blocking_io.rs | 72 --- gix-packetline-blocking/src/write/mod.rs | 23 - gix-packetline/Cargo.toml | 7 +- gix-packetline/src/encode/async_io.rs | 39 +- gix-packetline/src/encode/blocking_io.rs | 38 +- gix-packetline/src/encode/mod.rs | 12 +- gix-packetline/src/lib.rs | 20 +- gix-packetline/src/line/async_io.rs | 47 -- gix-packetline/src/line/blocking_io.rs | 44 -- gix-packetline/src/line/mod.rs | 7 +- gix-packetline/src/read/async_io.rs | 101 ++- gix-packetline/src/read/blocking_io.rs | 103 ++- gix-packetline/src/read/mod.rs | 33 +- gix-packetline/src/read/sidebands/async_io.rs | 14 +- .../src/read/sidebands/blocking_io.rs | 17 +- gix-packetline/src/read/sidebands/mod.rs | 10 +- gix-packetline/src/write/async_io.rs | 4 +- gix-packetline/src/write/blocking_io.rs | 4 +- gix-packetline/src/write/mod.rs | 24 +- gix-packetline/tests/decode/mod.rs | 22 +- gix-packetline/tests/encode/mod.rs | 20 +- gix-packetline/tests/read/mod.rs | 25 +- gix-packetline/tests/read/sideband.rs | 16 +- gix-packetline/tests/write/mod.rs | 10 +- gix-protocol/tests/protocol/fetch/response.rs | 8 +- .../src/client/async_io/bufread_ext.rs | 10 +- gix-transport/src/client/async_io/request.rs | 33 +- gix-transport/src/client/async_io/traits.rs | 7 +- .../src/client/blocking_io/bufread_ext.rs | 10 +- .../src/client/blocking_io/connect.rs | 16 +- gix-transport/src/client/blocking_io/file.rs | 13 +- .../src/client/blocking_io/http/curl/mod.rs | 2 +- .../client/blocking_io/http/curl/remote.rs | 14 +- .../src/client/blocking_io/http/mod.rs | 21 +- .../src/client/blocking_io/http/traits.rs | 2 +- .../src/client/blocking_io/request.rs | 26 +- .../src/client/blocking_io/ssh/mod.rs | 2 +- .../client/blocking_io/ssh/program_kind.rs | 2 +- .../src/client/blocking_io/ssh/tests.rs | 8 +- .../src/client/blocking_io/traits.rs | 7 +- gix-transport/src/client/capabilities.rs | 20 +- gix-transport/src/client/git/async_io.rs | 81 ++- gix-transport/src/client/git/blocking_io.rs | 86 ++- gix-transport/src/client/git/mod.rs | 36 +- gix-transport/src/client/mod.rs | 4 +- gix-transport/src/client/non_io_types.rs | 10 +- gix-transport/src/client/traits.rs | 6 +- gix-transport/tests/client/capabilities.rs | 9 +- justfile | 6 +- 76 files changed, 650 insertions(+), 3372 deletions(-) delete mode 100755 etc/scripts/copy-packetline.sh delete mode 100644 gix-packetline-blocking/CHANGELOG.md delete mode 100644 gix-packetline-blocking/Cargo.toml delete mode 120000 gix-packetline-blocking/LICENSE-APACHE delete mode 120000 gix-packetline-blocking/LICENSE-MIT delete mode 100644 gix-packetline-blocking/src/decode.rs delete mode 100644 gix-packetline-blocking/src/encode/async_io.rs delete mode 100644 gix-packetline-blocking/src/encode/blocking_io.rs delete mode 100644 gix-packetline-blocking/src/encode/mod.rs delete mode 100644 gix-packetline-blocking/src/lib.rs delete mode 100644 gix-packetline-blocking/src/line/async_io.rs delete mode 100644 gix-packetline-blocking/src/line/blocking_io.rs delete mode 100644 gix-packetline-blocking/src/line/mod.rs delete mode 100644 gix-packetline-blocking/src/read/async_io.rs delete mode 100644 gix-packetline-blocking/src/read/blocking_io.rs delete mode 100644 gix-packetline-blocking/src/read/mod.rs delete mode 100644 gix-packetline-blocking/src/read/sidebands/async_io.rs delete mode 100644 gix-packetline-blocking/src/read/sidebands/blocking_io.rs delete mode 100644 gix-packetline-blocking/src/read/sidebands/mod.rs delete mode 100644 gix-packetline-blocking/src/write/async_io.rs delete mode 100644 gix-packetline-blocking/src/write/blocking_io.rs delete mode 100644 gix-packetline-blocking/src/write/mod.rs delete mode 100644 gix-packetline/src/line/async_io.rs delete mode 100644 gix-packetline/src/line/blocking_io.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b112907bc67..f3075b58e69 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -540,41 +540,6 @@ jobs: - name: gix-pack with all features (including wasm) run: cargo build -p gix-pack --all-features --target "$TARGET" - check-packetline: - strategy: - fail-fast: false - matrix: - os: - - ubuntu-latest - # We consider this script read-only and its effect is the same everywhere. - # However, when changes are made to `etc/scripts/copy-packetline.sh`, re-enable the other platforms for testing. - # - macos-latest - # - windows-latest - - runs-on: ${{ matrix.os }} - - defaults: - run: - # Use `bash` even on Windows, if we ever reenable `windows-latest` for testing. - shell: bash - - steps: - - uses: actions/checkout@v5 - with: - persist-credentials: false - - name: Check that working tree is initially clean - run: | - set -x - git status - git diff --exit-code - - name: Regenerate gix-packetline-blocking/src - run: etc/scripts/copy-packetline.sh - - name: Check that gix-packetline-blocking/src was already up to date - run: | - set -x - git status - git diff --exit-code - # Check that all `actions/checkout` in CI jobs have `persist-credentials: false`. check-no-persist-credentials: runs-on: ubuntu-latest @@ -655,7 +620,6 @@ jobs: - test-32bit-windows-size-doc - lint - cargo-deny - - check-packetline - check-no-persist-credentials - check-blocking diff --git a/Cargo.lock b/Cargo.lock index 551c640d792..fe3af7e6045 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1773,7 +1773,7 @@ dependencies = [ "gix-command", "gix-hash", "gix-object", - "gix-packetline-blocking", + "gix-packetline", "gix-path", "gix-quote", "gix-testtools", @@ -2118,18 +2118,6 @@ dependencies = [ "thiserror 2.0.17", ] -[[package]] -name = "gix-packetline-blocking" -version = "0.19.1" -dependencies = [ - "bstr", - "document-features", - "faster-hex", - "gix-trace", - "serde", - "thiserror 2.0.17", -] - [[package]] name = "gix-path" version = "0.10.20" diff --git a/Cargo.toml b/Cargo.toml index 377edbf63b0..1ab85975167 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -239,7 +239,6 @@ members = [ "gix-status", "gix-revision", "gix-packetline", - "gix-packetline-blocking", "gix-mailmap", "gix-macros", "gix-note", diff --git a/etc/scripts/copy-packetline.sh b/etc/scripts/copy-packetline.sh deleted file mode 100755 index 9ed5a0eea89..00000000000 --- a/etc/scripts/copy-packetline.sh +++ /dev/null @@ -1,152 +0,0 @@ -#!/usr/bin/env bash - -set -euC -o pipefail - -readonly input_dir='gix-packetline/src' -readonly output_parent_dir='gix-packetline-blocking' -readonly output_dir="$output_parent_dir/src" - -function fail () { - printf '%s: error: %s\n' "$0" "$1" >&2 - exit 1 -} - -function chdir_toplevel () { - local root_padded root - - # Find the working tree's root. (Padding covers the trailing-newline case.) - root_padded="$(git rev-parse --show-toplevel && echo -n .)" || - fail 'git-rev-parse failed to find top-level dir' - root="${root_padded%$'\n.'}" - - cd -- "$root" -} - -function merging () { - local git_dir_padded git_dir - - # Find the .git directory. (Padding covers the trailing-newline case.) - git_dir_padded="$(git rev-parse --git-dir && echo -n .)" || - fail 'git-rev-parse failed to find git dir' - git_dir="${git_dir_padded%$'\n.'}" - - test -e "$git_dir/MERGE_HEAD" -} - -function output_dir_status () { - git status --porcelain --ignored=traditional -- "$output_dir" || - fail 'git-status failed' -} - -function check_output_dir () { - if ! test -e "$output_dir"; then - # The destination does not exist on disk, so nothing will be lost. Proceed. - return - fi - - if merging; then - # In a merge, it would be confusing to replace anything at the destination. - if output_dir_status | grep -q '^'; then - fail 'output location exists, and a merge is in progress' - fi - else - # We can lose data if anything of value at the destination is not in the - # index. (This includes unstaged deletions, for two reasons. We could lose - # track of which files had been deleted. More importantly, replacing a - # staged symlink or regular file with an unstaged directory is shown by - # git-status as only a deletion, even if the directory is non-empty.) - if output_dir_status | grep -q '^.[^ ]'; then - fail 'output location exists, with unstaged changes or ignored files' - fi - fi -} - -function first_line_ends_crlf () { - # This is tricky to check portably. In Cygwin-like environments including - # MSYS2 and Git Bash, most text processing tools, including awk, sed, and - # grep, automatically ignore \r before \n. Some ignore \r everywhere. Some - # can be told to keep \r, but in non-portable ways that may affect other - # implementations. Bash ignores \r in some places even without "-o igncr", - # and ignores \r even more with it, including in all text from command - # substitution. Simple checks may be non-portable to other OSes. Fortunately, - # tools that treat input as binary data are exempt (even cat, but "-v" is - # non-portable, and unreliable in general because lines can end in "^M"). - # This may be doable without od, by using tr more heavily, but it could be - # hard to avoid false positives with unexpected characters or \r without \n. - - head -n 1 -- "$1" | # Get the longest prefix with no non-trailing \n byte. - od -An -ta | # Represent all bytes symbolically, without addresses. - tr -sd '\n' ' ' | # Scrunch into one line, so "cr nl" appears as such. - grep -q 'cr nl$' # Check if the result signifies a \r\n line ending. -} - -function make_header () { - local input_file endline - - input_file="$1" - endline="$2" - - # shellcheck disable=SC2016 # The backticks are intentionally literal. - printf '// DO NOT EDIT - this is a copy of %s. Run `just copy-packetline` to update it.%s%s' \ - "$input_file" "$endline" "$endline" -} - -function copy_with_header () { - local input_file output_file endline - - input_file="$1" - output_file="$2" - - if first_line_ends_crlf "$input_file"; then - endline=$'\r\n' - else - endline=$'\n' - fi - - make_header "$input_file" "$endline" | cat -- - "$input_file" >"$output_file" -} - -function generate_one () { - local input_file output_file - - input_file="$1" - output_file="$output_dir${input_file#"$input_dir"}" - - if test -d "$input_file"; then - mkdir -p -- "$output_file" - elif test -L "$input_file"; then - # Cover this case separately, for more useful error messages. - fail "input file is symbolic link: $input_file" - elif ! test -f "$input_file"; then - # This covers less common kinds of files we can't or shouldn't process. - fail "input file neither regular file nor directory: $input_file" - elif [[ "$input_file" =~ \.rs$ ]]; then - copy_with_header "$input_file" "$output_file" - else - fail "input file not named as Rust source code: $input_file" - fi -} - -function generate_all () { - local input_file - - if ! test -d "$input_dir"; then - fail "no input directory: $input_dir" - fi - if ! test -d "$output_parent_dir"; then - fail "no output parent directory: $output_parent_dir" - fi - check_output_dir - - rm -rf -- "$output_dir" # It may be a directory, symlink, or regular file. - if test -e "$output_dir"; then - fail 'unable to remove output location' - fi - - find "$input_dir" -print0 | while IFS= read -r -d '' input_file; do - generate_one "$input_file" - done -} - -chdir_toplevel -generate_all diff --git a/gix-filter/Cargo.toml b/gix-filter/Cargo.toml index 754823f609f..3888b7f53db 100644 --- a/gix-filter/Cargo.toml +++ b/gix-filter/Cargo.toml @@ -22,7 +22,7 @@ gix-command = { version = "^0.6.2", path = "../gix-command" } gix-quote = { version = "^0.6.0", path = "../gix-quote" } gix-utils = { version = "^0.3.0", path = "../gix-utils" } gix-path = { version = "^0.10.20", path = "../gix-path" } -gix-packetline-blocking = { version = "^0.19.1", path = "../gix-packetline-blocking" } +gix-packetline = { version = "^0.19.1", path = "../gix-packetline", features = ["blocking-io"] } gix-attributes = { version = "^0.27.0", path = "../gix-attributes" } encoding_rs = "0.8.32" diff --git a/gix-filter/src/driver/process/client.rs b/gix-filter/src/driver/process/client.rs index f2f2d8a8072..062f7e6b495 100644 --- a/gix-filter/src/driver/process/client.rs +++ b/gix-filter/src/driver/process/client.rs @@ -41,7 +41,7 @@ pub mod invoke { #[error("Failed to read or write to the process")] Io(#[from] std::io::Error), #[error(transparent)] - PacketlineDecode(#[from] gix_packetline_blocking::decode::Error), + PacketlineDecode(#[from] gix_packetline::decode::Error), } impl From for Error { @@ -65,18 +65,19 @@ impl Client { versions: &[usize], desired_capabilities: &[&str], ) -> Result { - let mut out = - gix_packetline_blocking::Writer::new(process.stdin.take().expect("configured stdin when spawning")); + let mut out = gix_packetline::write::blocking_io::Writer::new( + process.stdin.take().expect("configured stdin when spawning"), + ); out.write_all(format!("{welcome_prefix}-client").as_bytes())?; for version in versions { out.write_all(format!("version={version}").as_bytes())?; } - gix_packetline_blocking::encode::flush_to_write(out.inner_mut())?; + gix_packetline::encode::blocking_io::flush_to_write(out.inner_mut())?; out.flush()?; - let mut input = gix_packetline_blocking::StreamingPeekableIter::new( + let mut input = gix_packetline::read::blocking_io::StreamingPeekableIter::new( process.stdout.take().expect("configured stdout when spawning"), - &[gix_packetline_blocking::PacketLineRef::Flush], + &[gix_packetline::PacketLineRef::Flush], false, /* packet tracing */ ); let mut read = input.as_read(); @@ -126,10 +127,10 @@ impl Client { for capability in desired_capabilities { out.write_all(format!("capability={capability}").as_bytes())?; } - gix_packetline_blocking::encode::flush_to_write(out.inner_mut())?; + gix_packetline::encode::blocking_io::flush_to_write(out.inner_mut())?; out.flush()?; - read.reset_with(&[gix_packetline_blocking::PacketLineRef::Flush]); + read.reset_with(&[gix_packetline::PacketLineRef::Flush]); let mut capabilities = HashSet::new(); loop { buf.clear(); @@ -168,7 +169,7 @@ impl Client { ) -> Result { self.send_command_and_meta(command, meta)?; std::io::copy(content, &mut self.input)?; - gix_packetline_blocking::encode::flush_to_write(self.input.inner_mut())?; + gix_packetline::encode::blocking_io::flush_to_write(self.input.inner_mut())?; self.input.flush()?; Ok(self.read_status()?) } @@ -190,7 +191,7 @@ impl Client { inspect_line(line.as_bstr()); } } - self.out.reset_with(&[gix_packetline_blocking::PacketLineRef::Flush]); + self.out.reset_with(&[gix_packetline::PacketLineRef::Flush]); let status = self.read_status()?; Ok(status) } @@ -198,7 +199,7 @@ impl Client { /// Return a `Read` implementation that reads the server process output until the next flush package, and validates /// the status. If the status indicates failure, the last read will also fail. pub fn as_read(&mut self) -> impl std::io::Read + '_ { - self.out.reset_with(&[gix_packetline_blocking::PacketLineRef::Flush]); + self.out.reset_with(&[gix_packetline::PacketLineRef::Flush]); ReadProcessOutputAndStatus { inner: self.out.as_read(), } @@ -226,7 +227,7 @@ impl Client { buf.push_str(&value); self.input.write_all(&buf)?; } - gix_packetline_blocking::encode::flush_to_write(self.input.inner_mut())?; + gix_packetline::encode::blocking_io::flush_to_write(self.input.inner_mut())?; Ok(()) } } @@ -249,7 +250,7 @@ fn read_status(read: &mut PacketlineReader<'_>) -> std::io::Result 0 && matches!(status, process::Status::Previous) { status = process::Status::Unset; } - read.reset_with(&[gix_packetline_blocking::PacketLineRef::Flush]); + read.reset_with(&[gix_packetline::PacketLineRef::Flush]); Ok(status) } @@ -261,7 +262,7 @@ impl std::io::Read for ReadProcessOutputAndStatus<'_> { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { let num_read = self.inner.read(buf)?; if num_read == 0 { - self.inner.reset_with(&[gix_packetline_blocking::PacketLineRef::Flush]); + self.inner.reset_with(&[gix_packetline::PacketLineRef::Flush]); let status = read_status(&mut self.inner)?; if status.is_success() { Ok(0) diff --git a/gix-filter/src/driver/process/mod.rs b/gix-filter/src/driver/process/mod.rs index 9377f2f9b10..0961ad4e44b 100644 --- a/gix-filter/src/driver/process/mod.rs +++ b/gix-filter/src/driver/process/mod.rs @@ -12,9 +12,9 @@ pub struct Client { /// The negotiated version of the protocol. version: usize, /// A way to send packet-line encoded information to the process. - input: gix_packetline_blocking::Writer, + input: gix_packetline::write::blocking_io::Writer, /// A way to read information sent to us by the process. - out: gix_packetline_blocking::StreamingPeekableIter, + out: gix_packetline::read::blocking_io::StreamingPeekableIter, } /// A handle to facilitate typical server interactions that include the handshake and command-invocations. @@ -24,9 +24,9 @@ pub struct Server { /// The negotiated version of the protocol, it's the highest supported one. version: usize, /// A way to receive information from the client. - input: gix_packetline_blocking::StreamingPeekableIter>, + input: gix_packetline::read::blocking_io::StreamingPeekableIter>, /// A way to send information to the client. - out: gix_packetline_blocking::Writer>, + out: gix_packetline::write::blocking_io::Writer>, } /// The return status of an [invoked command][Client::invoke()]. @@ -109,8 +109,5 @@ pub mod client; /// pub mod server; -type PacketlineReader<'a, T = std::process::ChildStdout> = gix_packetline_blocking::read::WithSidebands< - 'a, - T, - fn(bool, &[u8]) -> gix_packetline_blocking::read::ProgressAction, ->; +type PacketlineReader<'a, T = std::process::ChildStdout> = + gix_packetline::read::blocking_io::WithSidebands<'a, T, fn(bool, &[u8]) -> gix_packetline::read::ProgressAction>; diff --git a/gix-filter/src/driver/process/server.rs b/gix-filter/src/driver/process/server.rs index 76a7f76686c..4ba48339c68 100644 --- a/gix-filter/src/driver/process/server.rs +++ b/gix-filter/src/driver/process/server.rs @@ -26,7 +26,7 @@ pub mod next_request { #[error("{msg} '{actual}'")] Protocol { msg: String, actual: BString }, #[error(transparent)] - PacketlineDecode(#[from] gix_packetline_blocking::decode::Error), + PacketlineDecode(#[from] gix_packetline::decode::Error), } } @@ -63,9 +63,9 @@ impl Server { pick_version: &mut dyn FnMut(&[usize]) -> Option, available_capabilities: &[&str], ) -> Result { - let mut input = gix_packetline_blocking::StreamingPeekableIter::new( + let mut input = gix_packetline::read::blocking_io::StreamingPeekableIter::new( stdin.lock(), - &[gix_packetline_blocking::PacketLineRef::Flush], + &[gix_packetline::PacketLineRef::Flush], false, /* packet tracing */ ); let mut read = input.as_read(); @@ -104,11 +104,11 @@ impl Server { ); } let version = pick_version(&versions).ok_or(handshake::Error::VersionMismatch { actual: versions })?; - read.reset_with(&[gix_packetline_blocking::PacketLineRef::Flush]); - let mut out = gix_packetline_blocking::Writer::new(stdout.lock()); + read.reset_with(&[gix_packetline::PacketLineRef::Flush]); + let mut out = gix_packetline::write::blocking_io::Writer::new(stdout.lock()); out.write_all(format!("{welcome_prefix}-server").as_bytes())?; out.write_all(format!("version={version}").as_bytes())?; - gix_packetline_blocking::encode::flush_to_write(out.inner_mut())?; + gix_packetline::encode::blocking_io::flush_to_write(out.inner_mut())?; out.flush()?; let mut capabilities = HashSet::new(); @@ -132,7 +132,7 @@ impl Server { for cap in &capabilities { out.write_all(format!("capability={cap}").as_bytes())?; } - gix_packetline_blocking::encode::flush_to_write(out.inner_mut())?; + gix_packetline::encode::blocking_io::flush_to_write(out.inner_mut())?; out.flush()?; drop(read); @@ -196,7 +196,7 @@ impl Server { } drop(read); - self.input.reset_with(&[gix_packetline_blocking::PacketLineRef::Flush]); + self.input.reset_with(&[gix_packetline::PacketLineRef::Flush]); Ok(Some(Request { parent: self, @@ -233,7 +233,7 @@ mod request { if let Some(message) = status.message() { out.write_all(format!("status={message}").as_bytes())?; } - gix_packetline_blocking::encode::flush_to_write(out.inner_mut())?; + gix_packetline::encode::blocking_io::flush_to_write(out.inner_mut())?; out.flush() } } @@ -248,7 +248,7 @@ mod request { } struct WriteAndFlushOnDrop<'a> { - inner: &'a mut gix_packetline_blocking::Writer>, + inner: &'a mut gix_packetline::write::blocking_io::Writer>, } impl std::io::Write for WriteAndFlushOnDrop<'_> { @@ -263,7 +263,7 @@ mod request { impl Drop for WriteAndFlushOnDrop<'_> { fn drop(&mut self) { - gix_packetline_blocking::encode::flush_to_write(self.inner.inner_mut()).ok(); + gix_packetline::encode::blocking_io::flush_to_write(self.inner.inner_mut()).ok(); self.inner.flush().ok(); } } diff --git a/gix-packetline-blocking/CHANGELOG.md b/gix-packetline-blocking/CHANGELOG.md deleted file mode 100644 index 1d7ef18b1c0..00000000000 --- a/gix-packetline-blocking/CHANGELOG.md +++ /dev/null @@ -1,589 +0,0 @@ -# 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.19.1 (2025-07-15) - -### Bug Fixes - - - Actually avoid `serde` as dependency, when the serde feature is off - `faster-hex` has serde as default dependency, and was not marked with - `default-features = false`. - -### Commit Statistics - - - - - 4 commits contributed to the release over the course of 79 calendar days. - - 79 days passed between releases. - - 1 commit was understood as [conventional](https://www.conventionalcommits.org). - - 0 issues like '(#ID)' were seen in commit messages - -### Commit Details - - - -
view details - - * **Uncategorized** - - Update changelogs prior to release ([`65037b5`](https://github.com/GitoxideLabs/gitoxide/commit/65037b56918b90ac07454a815b0ed136df2fca3b)) - - Merge pull request #2031 from bpeetz/serde ([`874cc38`](https://github.com/GitoxideLabs/gitoxide/commit/874cc388e1e6af558e7cab4e9238f447e8c122e1)) - - Actually avoid `serde` as dependency, when the serde feature is off ([`0d67c85`](https://github.com/GitoxideLabs/gitoxide/commit/0d67c8524058a718083d31eae827f7f60332b20a)) - - Merge pull request #1971 from GitoxideLabs/new-release ([`8d4c4d1`](https://github.com/GitoxideLabs/gitoxide/commit/8d4c4d1e09f84c962c29d98a686c64228196ac13)) -
- -## 0.19.0 (2025-04-26) - -### Commit Statistics - - - - - 3 commits contributed to the release. - - 0 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.10.1, gix-utils v0.3.0, gix-actor v0.35.1, gix-validate v0.10.0, gix-path v0.10.17, gix-features v0.42.1, gix-hash v0.18.0, gix-hashtable v0.8.1, gix-object v0.49.1, gix-glob v0.20.0, gix-quote v0.6.0, gix-attributes v0.26.0, gix-command v0.6.0, gix-packetline-blocking v0.19.0, gix-filter v0.19.1, gix-fs v0.15.0, gix-commitgraph v0.28.0, gix-revwalk v0.20.1, gix-traverse v0.46.1, gix-worktree-stream v0.21.1, gix-archive v0.21.1, gix-tempfile v17.1.0, gix-lock v17.1.0, gix-index v0.40.0, gix-config-value v0.15.0, gix-pathspec v0.11.0, gix-ignore v0.15.0, gix-worktree v0.41.0, gix-diff v0.52.1, gix-blame v0.2.1, gix-ref v0.52.1, gix-sec v0.11.0, gix-config v0.45.1, gix-prompt v0.11.0, gix-url v0.31.0, gix-credentials v0.29.0, gix-discover v0.40.1, gix-dir v0.14.1, gix-mailmap v0.27.1, gix-revision v0.34.1, gix-merge v0.5.1, gix-negotiate v0.20.1, gix-pack v0.59.1, gix-odb v0.69.1, gix-refspec v0.30.1, gix-shallow v0.4.0, gix-packetline v0.19.0, gix-transport v0.47.0, gix-protocol v0.50.1, gix-status v0.19.1, gix-submodule v0.19.1, gix-worktree-state v0.19.0, gix v0.72.1, gix-fsck v0.11.1, gitoxide-core v0.47.1, gitoxide v0.44.0 ([`e104545`](https://github.com/GitoxideLabs/gitoxide/commit/e104545b78951ca882481d4a58f4425a8bc81c87)) - - Bump all prior pratch levels to majors ([`5f7f805`](https://github.com/GitoxideLabs/gitoxide/commit/5f7f80570e1a5522e76ea58cccbb957249a0dffe)) - - Merge pull request #1969 from GitoxideLabs/new-release ([`631f07a`](https://github.com/GitoxideLabs/gitoxide/commit/631f07ad0c1cb93d9da42cf2c8499584fe91880a)) -
- -## 0.18.4 (2025-04-25) - - - -### Refactor - - - replace `futures_lite::ready!` with `std::task::ready!` - -### Commit Statistics - - - - - 14 commits contributed to the release. - - 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-path v0.10.16, gix-features v0.42.0, gix-hash v0.17.1, gix-object v0.49.0, gix-glob v0.19.1, gix-quote v0.5.1, gix-attributes v0.25.1, gix-command v0.5.1, gix-packetline-blocking v0.18.4, gix-filter v0.19.0, gix-fs v0.14.1, gix-commitgraph v0.27.1, gix-revwalk v0.20.0, gix-traverse v0.46.0, gix-worktree-stream v0.21.0, gix-archive v0.21.0, gix-tempfile v17.0.1, gix-lock v17.0.1, gix-index v0.39.1, gix-config-value v0.14.13, gix-pathspec v0.10.1, gix-ignore v0.14.1, gix-worktree v0.40.1, gix-diff v0.52.0, gix-blame v0.2.0, gix-ref v0.52.0, gix-sec v0.10.13, gix-config v0.45.0, gix-prompt v0.10.1, gix-url v0.30.1, gix-credentials v0.28.1, gix-discover v0.40.0, gix-dir v0.14.0, gix-mailmap v0.27.0, gix-revision v0.34.0, gix-merge v0.5.0, gix-negotiate v0.20.0, gix-pack v0.59.0, gix-odb v0.69.0, gix-refspec v0.30.0, gix-shallow v0.3.1, gix-packetline v0.18.5, gix-transport v0.46.1, gix-protocol v0.50.0, gix-status v0.19.0, gix-submodule v0.19.0, gix-worktree-state v0.18.1, gix v0.72.0, gix-fsck v0.11.0, gitoxide-core v0.47.0, gitoxide v0.43.0 ([`cc5b696`](https://github.com/GitoxideLabs/gitoxide/commit/cc5b696b7b73277ddcc3ef246714cf80a092cf76)) - - Release gix-date v0.10.0, gix-utils v0.2.1, gix-actor v0.35.0, gix-validate v0.9.5, gix-path v0.10.15, gix-features v0.42.0, gix-hash v0.17.1, gix-object v0.49.0, gix-glob v0.19.1, gix-quote v0.5.1, gix-attributes v0.25.0, gix-command v0.5.1, gix-packetline-blocking v0.18.4, gix-filter v0.19.0, gix-fs v0.14.0, gix-commitgraph v0.27.1, gix-revwalk v0.20.0, gix-traverse v0.46.0, gix-worktree-stream v0.21.0, gix-archive v0.21.0, gix-tempfile v17.0.1, gix-lock v17.0.1, gix-index v0.39.0, gix-config-value v0.14.13, gix-pathspec v0.10.1, gix-ignore v0.14.1, gix-worktree v0.40.0, gix-diff v0.52.0, gix-blame v0.2.0, gix-ref v0.51.0, gix-sec v0.10.13, gix-config v0.45.0, gix-prompt v0.10.1, gix-url v0.30.1, gix-credentials v0.28.1, gix-discover v0.40.0, gix-dir v0.14.0, gix-mailmap v0.27.0, gix-revision v0.34.0, gix-merge v0.5.0, gix-negotiate v0.20.0, gix-pack v0.59.0, gix-odb v0.69.0, gix-refspec v0.30.0, gix-shallow v0.3.1, gix-packetline v0.18.5, gix-transport v0.46.0, gix-protocol v0.50.0, gix-status v0.19.0, gix-submodule v0.19.0, gix-worktree-state v0.18.0, gix v0.72.0, gix-fsck v0.11.0, gitoxide-core v0.46.0, gitoxide v0.43.0, safety bump 30 crates ([`db0b095`](https://github.com/GitoxideLabs/gitoxide/commit/db0b0957930e3ebb1b3f05ed8d7e7a557eb384a2)) - - Update changelogs prior to release ([`0bf84db`](https://github.com/GitoxideLabs/gitoxide/commit/0bf84dbc041f59efba06adcf422c60b5d6e350f0)) - - Merge pull request #1950 from paolobarbolini/ready-macro-from-std ([`f1bb269`](https://github.com/GitoxideLabs/gitoxide/commit/f1bb269bc4949a5eb2bc84725e6699eed7b74a35)) - - Replace `futures_lite::ready!` with `std::task::ready!` ([`a86c201`](https://github.com/GitoxideLabs/gitoxide/commit/a86c2019f0f4f55b585d7d3686142d64d29960c1)) - - Merge pull request #1949 from GitoxideLabs/dependabot/cargo/cargo-6893e2988a ([`b5e9059`](https://github.com/GitoxideLabs/gitoxide/commit/b5e905991155ace32ef21464e69a8369a773f02b)) - - Bump the cargo group with 21 updates ([`68e6b2e`](https://github.com/GitoxideLabs/gitoxide/commit/68e6b2e54613fe788d645ea8c942c71a39c6ede1)) - - Merge pull request #1940 from EliahKagan/run-ci/ghost-feature-next ([`b34bdfa`](https://github.com/GitoxideLabs/gitoxide/commit/b34bdfa795e2811607e9d8b49baa5b655ec74035)) - - Have `gix-filter` depend on `gix-packetline-blocking` unaliased ([`2d560c7`](https://github.com/GitoxideLabs/gitoxide/commit/2d560c7c92197aa0518e1f36d018d4771e56456b)) - - Merge pull request #1939 from EliahKagan/run-ci/ghost-feature ([`62ec0e3`](https://github.com/GitoxideLabs/gitoxide/commit/62ec0e354d83e4dcdb5c1ea6e96a1eb95c2997d1)) - - Add a second "DO NOT USE" feature to `gix-packetline-blocking` ([`d5dd239`](https://github.com/GitoxideLabs/gitoxide/commit/d5dd239f11a4b88ca55ebdb8d8232bcfb8eacf0a)) - - Merge pull request #1919 from GitoxideLabs/release ([`420e730`](https://github.com/GitoxideLabs/gitoxide/commit/420e730f765b91e1d17daca6bb1f99bdb2e54fda)) - - Merge branch 'main' into release ([`1c4d973`](https://github.com/GitoxideLabs/gitoxide/commit/1c4d973ae46fc841b413c5c31e741030902bbab1)) - - Merge pull request #1918 from EliahKagan/fix-clippy ([`4d3946f`](https://github.com/GitoxideLabs/gitoxide/commit/4d3946fcbb86c0dffdd881d94ab93598889f77f5)) -
- -## 0.18.3 (2025-04-04) - -A maintenance release without user-facing changes. - -### Commit Statistics - - - - - 6 commits contributed to the release. - - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - - 0 issues like '(#ID)' were seen in commit messages - -### Thanks Clippy - - - -[Clippy](https://github.com/rust-lang/rust-clippy) helped 1 time to make code idiomatic. - -### Commit Details - - - -
view details - - * **Uncategorized** - - Release gix-date v0.9.4, gix-utils v0.2.0, gix-actor v0.34.0, gix-features v0.41.0, gix-hash v0.17.0, gix-hashtable v0.8.0, gix-path v0.10.15, gix-validate v0.9.4, gix-object v0.48.0, gix-glob v0.19.0, gix-quote v0.5.0, gix-attributes v0.25.0, gix-command v0.5.0, gix-packetline-blocking v0.18.3, gix-filter v0.18.0, gix-fs v0.14.0, gix-commitgraph v0.27.0, gix-revwalk v0.19.0, gix-traverse v0.45.0, gix-worktree-stream v0.20.0, gix-archive v0.20.0, gix-tempfile v17.0.0, gix-lock v17.0.0, gix-index v0.39.0, gix-config-value v0.14.12, gix-pathspec v0.10.0, gix-ignore v0.14.0, gix-worktree v0.40.0, gix-diff v0.51.0, gix-blame v0.1.0, gix-ref v0.51.0, gix-config v0.44.0, gix-prompt v0.10.0, gix-url v0.30.0, gix-credentials v0.28.0, gix-discover v0.39.0, gix-dir v0.13.0, gix-mailmap v0.26.0, gix-revision v0.33.0, gix-merge v0.4.0, gix-negotiate v0.19.0, gix-pack v0.58.0, gix-odb v0.68.0, gix-refspec v0.29.0, gix-shallow v0.3.0, gix-packetline v0.18.4, gix-transport v0.46.0, gix-protocol v0.49.0, gix-status v0.18.0, gix-submodule v0.18.0, gix-worktree-state v0.18.0, gix v0.71.0, gix-fsck v0.10.0, gitoxide-core v0.46.0, gitoxide v0.42.0, safety bump 48 crates ([`b41312b`](https://github.com/GitoxideLabs/gitoxide/commit/b41312b478b0d19efb330970cf36dba45d0fbfbd)) - - Update changelogs prior to release ([`38dff41`](https://github.com/GitoxideLabs/gitoxide/commit/38dff41d09b6841ff52435464e77cd012dce7645)) - - Fix clippy ([`1370420`](https://github.com/GitoxideLabs/gitoxide/commit/1370420a589800a9a0e0918fb3f147f0175405fa)) - - Merge pull request #1854 from GitoxideLabs/montly-report ([`16a248b`](https://github.com/GitoxideLabs/gitoxide/commit/16a248beddbfbd21621f2bb57aaa82dca35acb19)) - - Thanks clippy ([`8e96ed3`](https://github.com/GitoxideLabs/gitoxide/commit/8e96ed37db680855d194c10673ba2dab28655d95)) - - Merge pull request #1778 from GitoxideLabs/new-release ([`8df0db2`](https://github.com/GitoxideLabs/gitoxide/commit/8df0db2f8fe1832a5efd86d6aba6fb12c4c855de)) -
- -## 0.18.2 (2025-01-18) - - - -### Chore - - - bump `rust-version` to 1.70 - That way clippy will allow to use the fantastic `Option::is_some_and()` - and friends. - -### Commit Statistics - - - - - 7 commits contributed to the release over the course of 55 calendar days. - - 55 days passed between releases. - - 1 commit was understood as [conventional](https://www.conventionalcommits.org). - - 0 issues like '(#ID)' were seen in commit messages - -### Thanks Clippy - - - -[Clippy](https://github.com/rust-lang/rust-clippy) helped 1 time to make code idiomatic. - -### Commit Details - - - -
view details - - * **Uncategorized** - - Release gix-utils v0.1.14, gix-actor v0.33.2, gix-hash v0.16.0, gix-trace v0.1.12, gix-features v0.40.0, gix-hashtable v0.7.0, gix-path v0.10.14, gix-validate v0.9.3, gix-object v0.47.0, gix-glob v0.18.0, gix-quote v0.4.15, gix-attributes v0.24.0, gix-command v0.4.1, gix-packetline-blocking v0.18.2, gix-filter v0.17.0, gix-fs v0.13.0, gix-chunk v0.4.11, gix-commitgraph v0.26.0, gix-revwalk v0.18.0, gix-traverse v0.44.0, gix-worktree-stream v0.19.0, gix-archive v0.19.0, gix-bitmap v0.2.14, gix-tempfile v16.0.0, gix-lock v16.0.0, gix-index v0.38.0, gix-config-value v0.14.11, gix-pathspec v0.9.0, gix-ignore v0.13.0, gix-worktree v0.39.0, gix-diff v0.50.0, gix-blame v0.0.0, gix-ref v0.50.0, gix-sec v0.10.11, gix-config v0.43.0, gix-prompt v0.9.1, gix-url v0.29.0, gix-credentials v0.27.0, gix-discover v0.38.0, gix-dir v0.12.0, gix-mailmap v0.25.2, gix-revision v0.32.0, gix-merge v0.3.0, gix-negotiate v0.18.0, gix-pack v0.57.0, gix-odb v0.67.0, gix-refspec v0.28.0, gix-shallow v0.2.0, gix-packetline v0.18.3, gix-transport v0.45.0, gix-protocol v0.48.0, gix-status v0.17.0, gix-submodule v0.17.0, gix-worktree-state v0.17.0, gix v0.70.0, gix-fsck v0.9.0, gitoxide-core v0.45.0, gitoxide v0.41.0, safety bump 42 crates ([`dea106a`](https://github.com/GitoxideLabs/gitoxide/commit/dea106a8c4fecc1f0a8f891a2691ad9c63964d25)) - - Update all changelogs prior to release ([`1f6390c`](https://github.com/GitoxideLabs/gitoxide/commit/1f6390c53ba68ce203ae59eb3545e2631dd8a106)) - - Merge pull request #1762 from GitoxideLabs/fix-1759 ([`7ec21bb`](https://github.com/GitoxideLabs/gitoxide/commit/7ec21bb96ce05b29dde74b2efdf22b6e43189aab)) - - Bump `rust-version` to 1.70 ([`17835bc`](https://github.com/GitoxideLabs/gitoxide/commit/17835bccb066bbc47cc137e8ec5d9fe7d5665af0)) - - Merge pull request #1752 from GitoxideLabs/git-shell ([`1ca480a`](https://github.com/GitoxideLabs/gitoxide/commit/1ca480aa4093328a7e047e770fdffdb8cc6d8e8d)) - - Thanks clippy ([`9193b05`](https://github.com/GitoxideLabs/gitoxide/commit/9193b05b2528f62d829447ccc50314bd4cffc415)) - - Merge pull request #1701 from GitoxideLabs/release ([`e8b3b41`](https://github.com/GitoxideLabs/gitoxide/commit/e8b3b41dd79b8f4567670b1f89dd8867b6134e9e)) -
- -## 0.18.1 (2024-11-24) - -A maintenance release without user-facing changes. - -### Commit Statistics - - - - - 5 commits contributed to the release. - - 0 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.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 #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 #1642 from GitoxideLabs/new-release ([`db5c9cf`](https://github.com/GitoxideLabs/gitoxide/commit/db5c9cfce93713b4b3e249cff1f8cc1ef146f470)) -
- -## 0.18.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.) - -### Bug Fixes (BREAKING) - - - remove all workspace dependencies - The problem is that with them, we don't notice anymore if the crate changes, - because a dependency changes. That also means that older versions of the dependency - may stay even though some other crates might pick up a newer version. - - Ultimately, this will lead to drift and subtle incompatibilities. - - We declare this breaking to enforce a proper re-release. - -### Commit Statistics - - - - - 18 commits contributed to the release over the course of 60 calendar days. - - 61 days passed between releases. - - 2 commits were understood as [conventional](https://www.conventionalcommits.org). - - 0 issues like '(#ID)' were seen in commit messages - -### Thanks Clippy - - - -[Clippy](https://github.com/rust-lang/rust-clippy) helped 2 times to make code idiomatic. - -### Commit Details - - - -
view details - - * **Uncategorized** - - 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)) - - Thanks clippy ([`af03832`](https://github.com/GitoxideLabs/gitoxide/commit/af0383254422b70d53f27572c415eea2e4154447)) - - Merge pull request #1582 from Byron/gix-path-release ([`93e86f1`](https://github.com/GitoxideLabs/gitoxide/commit/93e86f12a8d0ab59ad5d885ce552d0dec9a6fba6)) - - Release gix-trace v0.1.10, gix-path v0.10.11 ([`012a754`](https://github.com/GitoxideLabs/gitoxide/commit/012a75455edebc857ff13c97c1e7603ea5ea6cdc)) - - Merge pull request #1566 from Byron/merge ([`d69c617`](https://github.com/GitoxideLabs/gitoxide/commit/d69c6175574f34d6df92b4488ed2c9a85df12c89)) - - Thanks clippy ([`b91d909`](https://github.com/GitoxideLabs/gitoxide/commit/b91d90989d0b31f532242f11b9f1324e412936b4)) - - Merge pull request #1557 from Byron/merge-base ([`649f588`](https://github.com/GitoxideLabs/gitoxide/commit/649f5882cbebadf1133fa5f310e09b4aab77217e)) - - Allow empty-docs ([`beba720`](https://github.com/GitoxideLabs/gitoxide/commit/beba7204a50a84b30e3eb81413d968920599e226)) - - Merge branch 'global-lints' ([`37ba461`](https://github.com/GitoxideLabs/gitoxide/commit/37ba4619396974ec9cc41d1e882ac5efaf3816db)) - - Run `just copy-packetline` (missing docs?) ([`05268eb`](https://github.com/GitoxideLabs/gitoxide/commit/05268eb9b41322041dace244cd24460ab2f9f081)) - - Workspace Clippy lint management ([`2e0ce50`](https://github.com/GitoxideLabs/gitoxide/commit/2e0ce506968c112b215ca0056bd2742e7235df48)) - - Merge pull request #1546 from nyurik/semilocons ([`f992fb7`](https://github.com/GitoxideLabs/gitoxide/commit/f992fb773b443454015bd14658cfaa2f3ac07997)) - - Add missing semicolons ([`ec69c88`](https://github.com/GitoxideLabs/gitoxide/commit/ec69c88fc119f3aa1967a7e7f5fca30e3ce97595)) - - Merge branch 'fixes' ([`46cd1ae`](https://github.com/GitoxideLabs/gitoxide/commit/46cd1aed7815d27cdc818edb87641b20b82ba048)) - - Remove all workspace dependencies ([`1757377`](https://github.com/GitoxideLabs/gitoxide/commit/17573779688e755a786546d5e42ab533088cd726)) -
- -## 0.17.5 (2024-08-22) - -A maintenance release without user-facing changes. - -### Commit Statistics - - - - - 5 commits contributed to the release over the course of 62 calendar days. - - 131 days passed between releases. - - 0 commits were understood as [conventional](https://www.conventionalcommits.org). - - 0 issues like '(#ID)' were seen in commit messages - -### Thanks Clippy - - - -[Clippy](https://github.com/rust-lang/rust-clippy) helped 1 time to make code idiomatic. - -### Commit Details - - - -
view details - - * **Uncategorized** - - Release gix-date v0.9.0, gix-actor v0.31.6, gix-validate v0.9.0, gix-object v0.43.0, gix-path v0.10.10, gix-attributes v0.22.4, gix-command v0.3.9, gix-packetline-blocking v0.17.5, gix-filter v0.12.0, gix-fs v0.11.3, gix-revwalk v0.14.0, gix-traverse v0.40.0, gix-worktree-stream v0.14.0, gix-archive v0.14.0, gix-ref v0.46.0, gix-config v0.39.0, gix-prompt v0.8.7, gix-url v0.27.5, gix-credentials v0.24.5, gix-ignore v0.11.4, gix-index v0.34.0, gix-worktree v0.35.0, gix-diff v0.45.0, gix-discover v0.34.0, gix-dir v0.7.0, gix-mailmap v0.23.6, gix-negotiate v0.14.0, gix-pack v0.52.0, gix-odb v0.62.0, gix-packetline v0.17.6, gix-transport v0.42.3, gix-protocol v0.45.3, gix-revision v0.28.0, gix-refspec v0.24.0, gix-status v0.12.0, gix-submodule v0.13.0, gix-worktree-state v0.12.0, gix v0.65.0, gix-fsck v0.5.0, gitoxide-core v0.40.0, gitoxide v0.38.0, safety bump 25 crates ([`d19af16`](https://github.com/GitoxideLabs/gitoxide/commit/d19af16e1d2031d4f0100e76b6cd410a5d252af1)) - - Prepare changelogs prior to release ([`0f25841`](https://github.com/GitoxideLabs/gitoxide/commit/0f2584178ae88e425f1c629eb85b69f3b4310d9f)) - - Merge branch 'improvements' ([`12313f2`](https://github.com/GitoxideLabs/gitoxide/commit/12313f2720bb509cb8fa5d7033560823beafb91c)) - - Thanks clippy ([`ae2b733`](https://github.com/GitoxideLabs/gitoxide/commit/ae2b733c1e4de33ddf442cd8044dae364307085d)) - - Merge branch 'main' into config-key-take-2 ([`9fa1054`](https://github.com/GitoxideLabs/gitoxide/commit/9fa1054a01071180d7b08c8c2b5bd61e9d0d32da)) -
- -## 0.17.4 (2024-04-13) - -A maintenance release without user-facing changes. - -### Commit Statistics - - - - - 5 commits contributed to the release. - - 0 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-trace v0.1.9, gix-utils v0.1.12, gix-packetline-blocking v0.17.4, gix-filter v0.11.1, gix-fs v0.10.2, gix-traverse v0.39.0, gix-worktree-stream v0.12.0, gix-archive v0.12.0, gix-config v0.36.1, gix-url v0.27.3, gix-index v0.32.0, gix-worktree v0.33.0, gix-diff v0.43.0, gix-pathspec v0.7.3, gix-dir v0.4.0, gix-pack v0.50.0, gix-odb v0.60.0, gix-transport v0.42.0, gix-protocol v0.45.0, gix-status v0.9.0, gix-worktree-state v0.10.0, gix v0.62.0, gix-fsck v0.4.0, gitoxide-core v0.37.0, gitoxide v0.35.0, safety bump 14 crates ([`095c673`](https://github.com/GitoxideLabs/gitoxide/commit/095c6739b2722a8b9af90776b435ef2da454c0e6)) - - Prepare changelogs prior to release ([`5755271`](https://github.com/GitoxideLabs/gitoxide/commit/57552717f46f96c35ba4ddc0a64434354ef845e9)) - - Merge branch 'copy-packetline' ([`a9b783d`](https://github.com/GitoxideLabs/gitoxide/commit/a9b783df45948eb0adf538251315f808f38008f6)) - - Rerun script to get `//` instead of `//!` comments ([`ec2427f`](https://github.com/GitoxideLabs/gitoxide/commit/ec2427fc78df81901a5636fb1213999a3a5ed969)) - - Replace gix-packetline-blocking/src with generated files ([`a793bde`](https://github.com/GitoxideLabs/gitoxide/commit/a793bde062553288bfc045bfda75addb25a7be76)) -
- -## 0.17.3 (2024-01-20) - -A maintenance release without user-facing changes. - -### Commit Statistics - - - - - 3 commits contributed to the release over the course of 4 calendar days. - - 20 days passed between releases. - - 0 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-utils v0.1.9, gix-features v0.38.0, gix-actor v0.30.0, gix-object v0.41.0, gix-path v0.10.4, gix-glob v0.16.0, gix-attributes v0.22.0, gix-command v0.3.3, gix-packetline-blocking v0.17.3, gix-filter v0.9.0, gix-fs v0.10.0, gix-commitgraph v0.24.0, gix-revwalk v0.12.0, gix-traverse v0.37.0, gix-worktree-stream v0.9.0, gix-archive v0.9.0, gix-config-value v0.14.4, gix-tempfile v13.0.0, gix-lock v13.0.0, gix-ref v0.41.0, gix-sec v0.10.4, gix-config v0.34.0, gix-url v0.27.0, gix-credentials v0.24.0, gix-ignore v0.11.0, gix-index v0.29.0, gix-worktree v0.30.0, gix-diff v0.40.0, gix-discover v0.29.0, gix-mailmap v0.22.0, gix-negotiate v0.12.0, gix-pack v0.47.0, gix-odb v0.57.0, gix-pathspec v0.6.0, gix-packetline v0.17.3, gix-transport v0.41.0, gix-protocol v0.44.0, gix-revision v0.26.0, gix-refspec v0.22.0, gix-status v0.5.0, gix-submodule v0.8.0, gix-worktree-state v0.7.0, gix v0.58.0, safety bump 39 crates ([`eb6aa8f`](https://github.com/GitoxideLabs/gitoxide/commit/eb6aa8f502314f886fc4ea3d52ab220763968208)) - - Prepare changelogs prior to release ([`6a2e0be`](https://github.com/GitoxideLabs/gitoxide/commit/6a2e0bebfdf012dc2ed0ff2604086081f2a0f96d)) - - Release gix-trace v0.1.7, gix-features v0.37.2, gix-commitgraph v0.23.2, gix-traverse v0.36.2, gix-index v0.28.2 ([`b6c04c8`](https://github.com/GitoxideLabs/gitoxide/commit/b6c04c87b426bf36a059df8dc52b56d384b27b79)) -
- -## 0.17.2 (2023-12-30) - - - -### Chore - - - change `rust-version` manifest field back to 1.65. - They didn't actually need to be higher to work, and changing them - unecessarily can break downstream CI. - - Let's keep this value as low as possible, and only increase it when - more recent features are actually used. - -### Commit Statistics - - - - - 3 commits contributed to the release. - - 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-date v0.8.3, gix-hash v0.14.1, gix-trace v0.1.6, gix-features v0.37.1, gix-actor v0.29.1, gix-validate v0.8.3, gix-object v0.40.1, gix-path v0.10.3, gix-glob v0.15.1, gix-quote v0.4.10, gix-attributes v0.21.1, gix-command v0.3.2, gix-packetline-blocking v0.17.2, gix-utils v0.1.8, gix-filter v0.8.1, gix-fs v0.9.1, gix-chunk v0.4.7, gix-commitgraph v0.23.1, gix-hashtable v0.5.1, gix-revwalk v0.11.1, gix-traverse v0.36.1, gix-worktree-stream v0.8.1, gix-archive v0.8.1, gix-config-value v0.14.3, gix-tempfile v12.0.1, gix-lock v12.0.1, gix-ref v0.40.1, gix-sec v0.10.3, gix-config v0.33.1, gix-prompt v0.8.2, gix-url v0.26.1, gix-credentials v0.23.1, gix-ignore v0.10.1, gix-bitmap v0.2.10, gix-index v0.28.1, gix-worktree v0.29.1, gix-diff v0.39.1, gix-discover v0.28.1, gix-macros v0.1.3, gix-mailmap v0.21.1, gix-negotiate v0.11.1, gix-pack v0.46.1, gix-odb v0.56.1, gix-pathspec v0.5.1, gix-packetline v0.17.2, gix-transport v0.40.1, gix-protocol v0.43.1, gix-revision v0.25.1, gix-refspec v0.21.1, gix-status v0.4.1, gix-submodule v0.7.1, gix-worktree-state v0.6.1, gix v0.57.1 ([`972241f`](https://github.com/GitoxideLabs/gitoxide/commit/972241f1904944e8b6e84c6aa1649a49be7a85c3)) - - Merge branch 'msrv' ([`8c492d7`](https://github.com/GitoxideLabs/gitoxide/commit/8c492d7b7e6e5d520b1e3ffeb489eeb88266aa75)) - - Change `rust-version` manifest field back to 1.65. ([`3bd09ef`](https://github.com/GitoxideLabs/gitoxide/commit/3bd09ef120945a9669321ea856db4079a5dab930)) -
- -## 0.17.1 (2023-12-29) - - - -### Chore - - - upgrade MSRV to v1.70 - Our MSRV follows the one of `helix`, which in turn follows Firefox. - -### Commit Statistics - - - - - 7 commits contributed to the release over the course of 21 calendar days. - - 22 days passed between releases. - - 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-date v0.8.2, gix-hash v0.14.0, gix-trace v0.1.5, gix-features v0.37.0, gix-actor v0.29.0, gix-validate v0.8.2, gix-object v0.40.0, gix-path v0.10.2, gix-glob v0.15.0, gix-quote v0.4.9, gix-attributes v0.21.0, gix-command v0.3.1, gix-packetline-blocking v0.17.1, gix-utils v0.1.7, gix-filter v0.8.0, gix-fs v0.9.0, gix-chunk v0.4.6, gix-commitgraph v0.23.0, gix-hashtable v0.5.0, gix-revwalk v0.11.0, gix-traverse v0.36.0, gix-worktree-stream v0.8.0, gix-archive v0.8.0, gix-config-value v0.14.2, gix-tempfile v12.0.0, gix-lock v12.0.0, gix-ref v0.40.0, gix-sec v0.10.2, gix-config v0.33.0, gix-prompt v0.8.1, gix-url v0.26.0, gix-credentials v0.23.0, gix-ignore v0.10.0, gix-bitmap v0.2.9, gix-index v0.28.0, gix-worktree v0.29.0, gix-diff v0.39.0, gix-discover v0.28.0, gix-macros v0.1.2, gix-mailmap v0.21.0, gix-negotiate v0.11.0, gix-pack v0.46.0, gix-odb v0.56.0, gix-pathspec v0.5.0, gix-packetline v0.17.1, gix-transport v0.40.0, gix-protocol v0.43.0, gix-revision v0.25.0, gix-refspec v0.21.0, gix-status v0.4.0, gix-submodule v0.7.0, gix-worktree-state v0.6.0, gix v0.57.0, gix-fsck v0.2.0, gitoxide-core v0.35.0, gitoxide v0.33.0, safety bump 40 crates ([`e1aae19`](https://github.com/GitoxideLabs/gitoxide/commit/e1aae191d7421c748913c92e2c5883274331dd20)) - - Prepare changelogs of next release ([`e78a92b`](https://github.com/GitoxideLabs/gitoxide/commit/e78a92bfeda168b2f35bb7ba9a94175cdece12f2)) - - Merge branch 'maintenance' ([`4454c9d`](https://github.com/GitoxideLabs/gitoxide/commit/4454c9d66c32a1de75a66639016c73edbda3bd34)) - - Upgrade MSRV to v1.70 ([`aea89c3`](https://github.com/GitoxideLabs/gitoxide/commit/aea89c3ad52f1a800abb620e9a4701bdf904ff7d)) - - Merge branch 'main' into fix-1183 ([`1691ba6`](https://github.com/GitoxideLabs/gitoxide/commit/1691ba669537f4a39ebb0891747dc509a6aedbef)) - - Merge branch '32bit' ([`ff1542c`](https://github.com/GitoxideLabs/gitoxide/commit/ff1542cedf3072a8c7c493d454aef5cc61de6d4c)) - - Update `faster-hex` crate to latest version ([`b0bfd01`](https://github.com/GitoxideLabs/gitoxide/commit/b0bfd01e6c65cd9f6458a97bfe1218a604cd6507)) -
- -## 0.17.0 (2023-12-06) - -### New Features (BREAKING) - - - make it possible to trace incoming and outgoing packetlines. - Due to the way this is (and has to be) setup, unfortunately one - has to integrate that with two crates, instead of just one. - - This changes touches multiple crates, most of which receive a single - boolean as last argument to indicate whether the tracing should - happen in the first place. - -### Commit Statistics - - - - - 10 commits contributed to the release. - - 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-date v0.8.1, gix-hash v0.13.2, gix-trace v0.1.4, gix-features v0.36.1, gix-actor v0.28.1, gix-validate v0.8.1, gix-object v0.39.0, gix-path v0.10.1, gix-glob v0.14.1, gix-quote v0.4.8, gix-attributes v0.20.1, gix-command v0.3.0, gix-packetline-blocking v0.17.0, gix-utils v0.1.6, gix-filter v0.7.0, gix-fs v0.8.1, gix-chunk v0.4.5, gix-commitgraph v0.22.1, gix-hashtable v0.4.1, gix-revwalk v0.10.0, gix-traverse v0.35.0, gix-worktree-stream v0.7.0, gix-archive v0.7.0, gix-config-value v0.14.1, gix-tempfile v11.0.1, gix-lock v11.0.1, gix-ref v0.39.0, gix-sec v0.10.1, gix-config v0.32.0, gix-prompt v0.8.0, gix-url v0.25.2, gix-credentials v0.22.0, gix-ignore v0.9.1, gix-bitmap v0.2.8, gix-index v0.27.0, gix-worktree v0.28.0, gix-diff v0.38.0, gix-discover v0.27.0, gix-macros v0.1.1, gix-mailmap v0.20.1, gix-negotiate v0.10.0, gix-pack v0.45.0, gix-odb v0.55.0, gix-pathspec v0.4.1, gix-packetline v0.17.0, gix-transport v0.39.0, gix-protocol v0.42.0, gix-revision v0.24.0, gix-refspec v0.20.0, gix-status v0.3.0, gix-submodule v0.6.0, gix-worktree-state v0.5.0, gix v0.56.0, gix-fsck v0.1.0, gitoxide-core v0.34.0, gitoxide v0.32.0, safety bump 27 crates ([`55d386a`](https://github.com/GitoxideLabs/gitoxide/commit/55d386a2448aba1dd22c73fb63b3fd5b3a8401c9)) - - Prepare changelogs prior to release ([`d3dcbe5`](https://github.com/GitoxideLabs/gitoxide/commit/d3dcbe5c4e3a004360d02fbfb74a8fad52f19b5e)) - - Merge branch 'check-cfg' ([`5a0d93e`](https://github.com/GitoxideLabs/gitoxide/commit/5a0d93e7522564d126c34ce5d569f9a385698513)) - - Refactor ([`fc23775`](https://github.com/GitoxideLabs/gitoxide/commit/fc2377583e6a6fbc6eaf58e6c58f33fe42245174)) - - Add missing async-io feature in gix-packetline-blocking ([`be4de0d`](https://github.com/GitoxideLabs/gitoxide/commit/be4de0dd10b71fea6e6c6db123b31f59b402e234)) - - Replace all docsrs config by the document-features feature ([`bb3224c`](https://github.com/GitoxideLabs/gitoxide/commit/bb3224c25abf6df50286b3bbdf2cdef01e9eeca1)) - - Merge branch 'size-optimization' ([`c0e72fb`](https://github.com/GitoxideLabs/gitoxide/commit/c0e72fbadc0a494f47a110aebb46462d7b9f5664)) - - Remove CHANGELOG.md from all packages ([`b65a80b`](https://github.com/GitoxideLabs/gitoxide/commit/b65a80b05c9372e752e7e67fcc5c073f71da164a)) - - Merge branch 'trace-packetlines' ([`e7de4c7`](https://github.com/GitoxideLabs/gitoxide/commit/e7de4c702a223ad9eb19b407391028dcb08d80c4)) - - Make it possible to trace incoming and outgoing packetlines. ([`c3edef1`](https://github.com/GitoxideLabs/gitoxide/commit/c3edef1c0c49accbb037bdf086dade3ed0e5e507)) -
- -## 0.16.6 (2023-09-08) - -A maintenance release without user-facing changes. - -### Commit Statistics - - - - - 3 commits contributed to the release over the course of 17 calendar days. - - 17 days passed between releases. - - 0 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.8.0, gix-hash v0.13.0, gix-features v0.34.0, gix-actor v0.26.0, gix-object v0.36.0, gix-path v0.10.0, gix-glob v0.12.0, gix-attributes v0.18.0, gix-packetline-blocking v0.16.6, gix-filter v0.4.0, gix-fs v0.6.0, gix-commitgraph v0.20.0, gix-hashtable v0.4.0, gix-revwalk v0.7.0, gix-traverse v0.32.0, gix-worktree-stream v0.4.0, gix-archive v0.4.0, gix-config-value v0.14.0, gix-tempfile v9.0.0, gix-lock v9.0.0, gix-ref v0.36.0, gix-sec v0.10.0, gix-config v0.29.0, gix-prompt v0.7.0, gix-url v0.23.0, gix-credentials v0.19.0, gix-diff v0.35.0, gix-discover v0.24.0, gix-ignore v0.7.0, gix-index v0.24.0, gix-macros v0.1.0, gix-mailmap v0.18.0, gix-negotiate v0.7.0, gix-pack v0.42.0, gix-odb v0.52.0, gix-pathspec v0.2.0, gix-packetline v0.16.6, gix-transport v0.36.0, gix-protocol v0.39.0, gix-revision v0.21.0, gix-refspec v0.17.0, gix-submodule v0.3.0, gix-worktree v0.25.0, gix-worktree-state v0.2.0, gix v0.53.0, safety bump 39 crates ([`8bd0456`](https://github.com/GitoxideLabs/gitoxide/commit/8bd045676bb2cdc02624ab93e73ff8518064ca38)) - - Prepare changelogs for release ([`375db06`](https://github.com/GitoxideLabs/gitoxide/commit/375db06a8442378c3f7a922fae38e2a6694d9d04)) - - Merge branch 'gix-submodule' ([`363ee77`](https://github.com/GitoxideLabs/gitoxide/commit/363ee77400805f473c9ad66eadad9214e7ab66f4)) -
- -## 0.16.5 (2023-08-22) - - - -### Chore - - - use `faster-hex` instead of `hex` - The performance here certainly doesn't make a difference, but we - try to avoid duplicate dependencies. - -### Commit Statistics - - - - - 4 commits contributed to the release over the course of 5 calendar days. - - 30 days passed between releases. - - 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-date v0.7.3, gix-hash v0.12.0, gix-features v0.33.0, gix-actor v0.25.0, gix-object v0.35.0, gix-path v0.9.0, gix-glob v0.11.0, gix-quote v0.4.7, gix-attributes v0.17.0, gix-command v0.2.9, gix-packetline-blocking v0.16.5, gix-filter v0.3.0, gix-fs v0.5.0, gix-commitgraph v0.19.0, gix-hashtable v0.3.0, gix-revwalk v0.6.0, gix-traverse v0.31.0, gix-worktree-stream v0.3.0, gix-archive v0.3.0, gix-config-value v0.13.0, gix-tempfile v8.0.0, gix-lock v8.0.0, gix-ref v0.35.0, gix-sec v0.9.0, gix-config v0.28.0, gix-prompt v0.6.0, gix-url v0.22.0, gix-credentials v0.18.0, gix-diff v0.34.0, gix-discover v0.23.0, gix-ignore v0.6.0, gix-bitmap v0.2.7, gix-index v0.22.0, gix-mailmap v0.17.0, gix-negotiate v0.6.0, gix-pack v0.41.0, gix-odb v0.51.0, gix-pathspec v0.1.0, gix-packetline v0.16.5, gix-transport v0.35.0, gix-protocol v0.38.0, gix-revision v0.20.0, gix-refspec v0.16.0, gix-submodule v0.2.0, gix-worktree v0.24.0, gix-worktree-state v0.1.0, gix v0.52.0, gitoxide-core v0.31.0, gitoxide v0.29.0, safety bump 41 crates ([`30b2761`](https://github.com/GitoxideLabs/gitoxide/commit/30b27615047692d3ced1b2d9c2ac15a80f79fbee)) - - Update changelogs prior to release ([`f23ea88`](https://github.com/GitoxideLabs/gitoxide/commit/f23ea8828f2d9ba7559973daca388c9591bcc5fc)) - - Merge branch 'faster-hex' ([`4a4fa0f`](https://github.com/GitoxideLabs/gitoxide/commit/4a4fa0fcdaa6e14b51d3f03f5d7c5b65042667bf)) - - Use `faster-hex` instead of `hex` ([`145125a`](https://github.com/GitoxideLabs/gitoxide/commit/145125ab79526a6191a9192a6faa7fe1a8826935)) -
- -## 0.16.4 (2023-07-22) - -A maintenance release without user-facing changes. - -### Commit Statistics - - - - - 5 commits contributed to the release over the course of 1 calendar day. - - 21 days passed between releases. - - 0 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-features v0.32.1, gix-actor v0.24.1, gix-validate v0.7.7, gix-object v0.33.1, gix-path v0.8.4, gix-glob v0.10.1, gix-quote v0.4.6, gix-attributes v0.16.0, gix-command v0.2.8, gix-packetline-blocking v0.16.4, gix-filter v0.2.0, gix-fs v0.4.1, gix-chunk v0.4.4, gix-commitgraph v0.18.1, gix-hashtable v0.2.4, gix-revwalk v0.4.1, gix-traverse v0.30.1, gix-worktree-stream v0.2.0, gix-archive v0.2.0, gix-config-value v0.12.5, gix-tempfile v7.0.1, gix-utils v0.1.5, gix-lock v7.0.2, gix-ref v0.33.1, gix-sec v0.8.4, gix-prompt v0.5.4, gix-url v0.21.1, gix-credentials v0.17.1, gix-diff v0.33.1, gix-discover v0.22.1, gix-ignore v0.5.1, gix-bitmap v0.2.6, gix-index v0.21.1, gix-mailmap v0.16.1, gix-negotiate v0.5.1, gix-pack v0.40.1, gix-odb v0.50.1, gix-packetline v0.16.4, gix-transport v0.34.1, gix-protocol v0.36.1, gix-revision v0.18.1, gix-refspec v0.14.1, gix-worktree v0.23.0, gix v0.50.0, safety bump 5 crates ([`16295b5`](https://github.com/GitoxideLabs/gitoxide/commit/16295b58e2581d2e8b8b762816f52baabe871c75)) - - Prepare more changelogs ([`c4cc5f2`](https://github.com/GitoxideLabs/gitoxide/commit/c4cc5f261d29f712a101033a18293a97a9d4ae85)) - - Release gix-date v0.7.1, gix-hash v0.11.4, gix-trace v0.1.3, gix-features v0.32.0, gix-actor v0.24.0, gix-validate v0.7.7, gix-object v0.33.0, gix-path v0.8.4, gix-glob v0.10.0, gix-quote v0.4.6, gix-attributes v0.15.0, gix-command v0.2.7, gix-packetline-blocking v0.16.3, gix-filter v0.1.0, gix-fs v0.4.0, gix-chunk v0.4.4, gix-commitgraph v0.18.0, gix-hashtable v0.2.4, gix-revwalk v0.4.0, gix-traverse v0.30.0, gix-worktree-stream v0.2.0, gix-archive v0.2.0, gix-config-value v0.12.4, gix-tempfile v7.0.1, gix-utils v0.1.5, gix-lock v7.0.2, gix-ref v0.33.0, gix-sec v0.8.4, gix-prompt v0.5.3, gix-url v0.21.0, gix-credentials v0.17.0, gix-diff v0.33.0, gix-discover v0.22.0, gix-ignore v0.5.0, gix-bitmap v0.2.6, gix-index v0.21.0, gix-mailmap v0.16.0, gix-negotiate v0.5.0, gix-pack v0.40.0, gix-odb v0.50.0, gix-packetline v0.16.4, gix-transport v0.34.0, gix-protocol v0.36.0, gix-revision v0.18.0, gix-refspec v0.14.0, gix-worktree v0.22.0, gix v0.49.1 ([`5cb3589`](https://github.com/GitoxideLabs/gitoxide/commit/5cb3589b74fc5376e02cbfe151e71344e1c417fe)) - - Update changelogs prior to release ([`2fc66b5`](https://github.com/GitoxideLabs/gitoxide/commit/2fc66b55097ed494b72d1af939ba5561f71fde97)) - - Update license field following SPDX 2.1 license expression standard ([`9064ea3`](https://github.com/GitoxideLabs/gitoxide/commit/9064ea31fae4dc59a56bdd3a06c0ddc990ee689e)) -
- -## 0.16.3 (2023-07-01) - -The initial release of a crate that is a copy of `gix-packtline`, but pre-configured to use blocking IO. - -### Commit Statistics - - - - - 4 commits contributed to the release over the course of 1 calendar day. - - 0 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-packetline-blocking v0.16.3, gix-filter v0.0.0 ([`fb3ad29`](https://github.com/GitoxideLabs/gitoxide/commit/fb3ad29967d08558e42cbe8e80de5dd0b38f12c5)) - - Prepare changelog prior to release of `gix-filter` ([`fcdb042`](https://github.com/GitoxideLabs/gitoxide/commit/fcdb0420511080ad95d417656aff68043acd6e54)) - - Merge branch 'filter-programs' ([`97f8e96`](https://github.com/GitoxideLabs/gitoxide/commit/97f8e960ed52538bb55b72f9dfc5f9d144d72885)) - - A duplicate of the `gix-packetline` crate that mirrors it, but pre-selects the `blocking-io` feature. ([`2d83d88`](https://github.com/GitoxideLabs/gitoxide/commit/2d83d8826bf3ebc6095288bd9770e338099a5017)) -
- diff --git a/gix-packetline-blocking/Cargo.toml b/gix-packetline-blocking/Cargo.toml deleted file mode 100644 index 3f386f663e2..00000000000 --- a/gix-packetline-blocking/Cargo.toml +++ /dev/null @@ -1,42 +0,0 @@ -lints.workspace = true - -[package] -name = "gix-packetline-blocking" -version = "0.19.1" -repository = "https://github.com/GitoxideLabs/gitoxide" -license = "MIT OR Apache-2.0" -description = "A duplicate of `gix-packetline` with the `blocking-io` feature pre-selected" -authors = ["Sebastian Thiel "] -edition = "2021" -include = ["src/**/*", "LICENSE-*"] -rust-version = "1.82" - -[lib] -doctest = false - -[features] -#! By default, all IO related capabilities will be missing unless one of the following is chosen. -default = ["blocking-io"] - -## If set, all IO will become blocking. The same types will be used preventing side-by-side usage of blocking and non-blocking IO. -blocking-io = [] - -## DO NOT USE, instead use `gix-packetline` directly. -async-io = [] - -#! ### Other -## Data structures implement `serde::Serialize` and `serde::Deserialize`. -serde = ["dep:serde", "bstr/serde", "faster-hex/serde"] - -[dependencies] -gix-trace = { version = "^0.1.13", path = "../gix-trace" } - -serde = { version = "1.0.114", optional = true, default-features = false, features = ["std", "derive"] } -thiserror = "2.0.17" -faster-hex = { version = "0.10.0", default-features = false, features = ["std"] } -bstr = { version = "1.12.0", default-features = false, features = ["std"] } - -document-features = { version = "0.2.0", optional = true } - -[package.metadata.docs.rs] -features = ["document-features", "blocking-io", "serde"] diff --git a/gix-packetline-blocking/LICENSE-APACHE b/gix-packetline-blocking/LICENSE-APACHE deleted file mode 120000 index 965b606f331..00000000000 --- a/gix-packetline-blocking/LICENSE-APACHE +++ /dev/null @@ -1 +0,0 @@ -../LICENSE-APACHE \ No newline at end of file diff --git a/gix-packetline-blocking/LICENSE-MIT b/gix-packetline-blocking/LICENSE-MIT deleted file mode 120000 index 76219eb72e8..00000000000 --- a/gix-packetline-blocking/LICENSE-MIT +++ /dev/null @@ -1 +0,0 @@ -../LICENSE-MIT \ No newline at end of file diff --git a/gix-packetline-blocking/src/decode.rs b/gix-packetline-blocking/src/decode.rs deleted file mode 100644 index 9273f1d7ba6..00000000000 --- a/gix-packetline-blocking/src/decode.rs +++ /dev/null @@ -1,147 +0,0 @@ -// DO NOT EDIT - this is a copy of gix-packetline/src/decode.rs. Run `just copy-packetline` to update it. - -use bstr::BString; - -use crate::{PacketLineRef, DELIMITER_LINE, FLUSH_LINE, MAX_DATA_LEN, MAX_LINE_LEN, RESPONSE_END_LINE, U16_HEX_BYTES}; - -/// The error used in the [`decode`][mod@crate::decode] module -#[derive(Debug, thiserror::Error)] -#[allow(missing_docs)] -pub enum Error { - #[error("Failed to decode the first four hex bytes indicating the line length: {err}")] - HexDecode { err: String }, - #[error("The data received claims to be larger than the maximum allowed size: got {length_in_bytes}, exceeds {MAX_DATA_LEN}")] - DataLengthLimitExceeded { length_in_bytes: usize }, - #[error("Received an invalid empty line")] - DataIsEmpty, - #[error("Received an invalid line of length 3")] - InvalidLineLength, - #[error("{data:?} - consumed {bytes_consumed} bytes")] - Line { data: BString, bytes_consumed: usize }, - #[error("Needing {bytes_needed} additional bytes to decode the line successfully")] - NotEnoughData { bytes_needed: usize }, -} - -/// -pub mod band { - /// The error used in [`PacketLineRef::decode_band()`][super::PacketLineRef::decode_band()]. - #[derive(Debug, thiserror::Error)] - #[allow(missing_docs)] - pub enum Error { - #[error("attempt to decode a non-side channel line or input was malformed: {band_id}")] - InvalidSideBand { band_id: u8 }, - #[error("attempt to decode a non-data line into a side-channel band")] - NonDataLine, - } -} - -/// A utility return type to support incremental parsing of packet lines. -#[derive(Debug, Clone)] -pub enum Stream<'a> { - /// Indicate a single packet line was parsed completely - Complete { - /// The parsed packet line - line: PacketLineRef<'a>, - /// The amount of bytes consumed from input - bytes_consumed: usize, - }, - /// A packet line could not yet be parsed due to missing bytes - Incomplete { - /// The amount of additional bytes needed for the parsing to complete - bytes_needed: usize, - }, -} - -/// The result of [`hex_prefix()`] indicating either a special packet line or the amount of wanted bytes -pub enum PacketLineOrWantedSize<'a> { - /// The special kind of packet line decoded from the hex prefix. It never contains actual data. - Line(PacketLineRef<'a>), - /// The amount of bytes indicated by the hex prefix of the packet line. - Wanted(u16), -} - -/// Decode the `four_bytes` packet line prefix provided in hexadecimal form and check it for validity. -pub fn hex_prefix(four_bytes: &[u8]) -> Result, Error> { - debug_assert_eq!(four_bytes.len(), 4, "need four hex bytes"); - for (line_bytes, line_type) in &[ - (FLUSH_LINE, PacketLineRef::Flush), - (DELIMITER_LINE, PacketLineRef::Delimiter), - (RESPONSE_END_LINE, PacketLineRef::ResponseEnd), - ] { - if four_bytes == *line_bytes { - return Ok(PacketLineOrWantedSize::Line(*line_type)); - } - } - - let mut buf = [0u8; U16_HEX_BYTES / 2]; - faster_hex::hex_decode(four_bytes, &mut buf).map_err(|err| Error::HexDecode { err: err.to_string() })?; - let wanted_bytes = u16::from_be_bytes(buf); - - if wanted_bytes == 3 { - return Err(Error::InvalidLineLength); - } - if wanted_bytes == 4 { - return Err(Error::DataIsEmpty); - } - debug_assert!( - wanted_bytes as usize > U16_HEX_BYTES, - "by now there should be more wanted bytes than prefix bytes" - ); - Ok(PacketLineOrWantedSize::Wanted(wanted_bytes - U16_HEX_BYTES as u16)) -} - -/// Obtain a `PacketLine` from `data` after assuring `data` is small enough to fit. -pub fn to_data_line(data: &[u8]) -> Result, Error> { - if data.len() > MAX_LINE_LEN { - return Err(Error::DataLengthLimitExceeded { - length_in_bytes: data.len(), - }); - } - - Ok(PacketLineRef::Data(data)) -} - -/// Decode `data` as packet line while reporting whether the data is complete or not using a [`Stream`]. -pub fn streaming(data: &[u8]) -> Result, Error> { - let data_len = data.len(); - if data_len < U16_HEX_BYTES { - return Ok(Stream::Incomplete { - bytes_needed: U16_HEX_BYTES - data_len, - }); - } - let wanted_bytes = match hex_prefix(&data[..U16_HEX_BYTES])? { - PacketLineOrWantedSize::Wanted(s) => s as usize, - PacketLineOrWantedSize::Line(line) => { - return Ok(Stream::Complete { - line, - bytes_consumed: 4, - }) - } - } + U16_HEX_BYTES; - if wanted_bytes > MAX_LINE_LEN { - return Err(Error::DataLengthLimitExceeded { - length_in_bytes: wanted_bytes, - }); - } - if data_len < wanted_bytes { - return Ok(Stream::Incomplete { - bytes_needed: wanted_bytes - data_len, - }); - } - - Ok(Stream::Complete { - line: to_data_line(&data[U16_HEX_BYTES..wanted_bytes])?, - bytes_consumed: wanted_bytes, - }) -} - -/// Decode an entire packet line from data or fail. -/// -/// Note that failure also happens if there is not enough data to parse a complete packet line, as opposed to [`streaming()`] decoding -/// succeeds in that case, stating how much more bytes are required. -pub fn all_at_once(data: &[u8]) -> Result, Error> { - match streaming(data)? { - Stream::Complete { line, .. } => Ok(line), - Stream::Incomplete { bytes_needed } => Err(Error::NotEnoughData { bytes_needed }), - } -} diff --git a/gix-packetline-blocking/src/encode/async_io.rs b/gix-packetline-blocking/src/encode/async_io.rs deleted file mode 100644 index 7e580e4d14f..00000000000 --- a/gix-packetline-blocking/src/encode/async_io.rs +++ /dev/null @@ -1,214 +0,0 @@ -// DO NOT EDIT - this is a copy of gix-packetline/src/encode/async_io.rs. Run `just copy-packetline` to update it. - -use std::{ - io, - pin::Pin, - task::{ready, Context, Poll}, -}; - -use futures_io::AsyncWrite; -use futures_lite::AsyncWriteExt; - -use super::u16_to_hex; -use crate::{encode::Error, Channel, DELIMITER_LINE, ERR_PREFIX, FLUSH_LINE, MAX_DATA_LEN, RESPONSE_END_LINE}; - -pin_project_lite::pin_project! { - /// A way of writing packet lines asynchronously. - pub struct LineWriter<'a, W> { - #[pin] - pub(crate) writer: W, - pub(crate) prefix: &'a [u8], - pub(crate) suffix: &'a [u8], - state: State<'a>, - } -} - -enum State<'a> { - Idle, - WriteHexLen([u8; 4], usize), - WritePrefix(&'a [u8]), - WriteData(usize), - WriteSuffix(&'a [u8]), -} - -impl<'a, W: AsyncWrite + Unpin> LineWriter<'a, W> { - /// Create a new line writer writing data with a `prefix` and `suffix`. - /// - /// Keep the additional `prefix` or `suffix` buffers empty if no prefix or suffix should be written. - pub fn new(writer: W, prefix: &'a [u8], suffix: &'a [u8]) -> Self { - LineWriter { - writer, - prefix, - suffix, - state: State::Idle, - } - } - - /// Consume self and reveal the inner writer. - pub fn into_inner(self) -> W { - self.writer - } -} - -impl AsyncWrite for LineWriter<'_, W> { - fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, data: &[u8]) -> Poll> { - let mut this = self.project(); - loop { - match &mut this.state { - State::Idle => { - let data_len = this.prefix.len() + data.len() + this.suffix.len(); - if data_len > MAX_DATA_LEN { - let err = Error::DataLengthLimitExceeded { - length_in_bytes: data_len, - }; - return Poll::Ready(Err(io::Error::other(err))); - } - if data.is_empty() { - let err = Error::DataIsEmpty; - return Poll::Ready(Err(io::Error::other(err))); - } - let data_len = data_len + 4; - let len_buf = u16_to_hex(data_len as u16); - *this.state = State::WriteHexLen(len_buf, 0); - } - State::WriteHexLen(hex_len, written) => { - while *written != hex_len.len() { - let n = ready!(this.writer.as_mut().poll_write(cx, &hex_len[*written..]))?; - if n == 0 { - return Poll::Ready(Err(io::ErrorKind::WriteZero.into())); - } - *written += n; - } - if this.prefix.is_empty() { - *this.state = State::WriteData(0); - } else { - *this.state = State::WritePrefix(this.prefix); - } - } - State::WritePrefix(buf) => { - while !buf.is_empty() { - let n = ready!(this.writer.as_mut().poll_write(cx, buf))?; - if n == 0 { - return Poll::Ready(Err(io::ErrorKind::WriteZero.into())); - } - let (_, rest) = std::mem::take(buf).split_at(n); - *buf = rest; - } - *this.state = State::WriteData(0); - } - State::WriteData(written) => { - while *written != data.len() { - let n = ready!(this.writer.as_mut().poll_write(cx, &data[*written..]))?; - if n == 0 { - return Poll::Ready(Err(io::ErrorKind::WriteZero.into())); - } - *written += n; - } - if this.suffix.is_empty() { - let written = 4 + this.prefix.len() + *written; - *this.state = State::Idle; - return Poll::Ready(Ok(written)); - } else { - *this.state = State::WriteSuffix(this.suffix); - } - } - State::WriteSuffix(buf) => { - while !buf.is_empty() { - let n = ready!(this.writer.as_mut().poll_write(cx, buf))?; - if n == 0 { - return Poll::Ready(Err(io::ErrorKind::WriteZero.into())); - } - let (_, rest) = std::mem::take(buf).split_at(n); - *buf = rest; - } - *this.state = State::Idle; - return Poll::Ready(Ok(4 + this.prefix.len() + data.len() + this.suffix.len())); - } - } - } - } - - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let this = self.project(); - this.writer.poll_flush(cx) - } - - fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let this = self.project(); - this.writer.poll_close(cx) - } -} - -async fn prefixed_and_suffixed_data_to_write( - prefix: &[u8], - data: &[u8], - suffix: &[u8], - mut out: impl AsyncWrite + Unpin, -) -> io::Result { - let data_len = prefix.len() + data.len() + suffix.len(); - if data_len > MAX_DATA_LEN { - let err = Error::DataLengthLimitExceeded { - length_in_bytes: data_len, - }; - return Err(io::Error::other(err)); - } - if data.is_empty() { - let err = Error::DataIsEmpty; - return Err(io::Error::other(err)); - } - - let data_len = data_len + 4; - let buf = u16_to_hex(data_len as u16); - - out.write_all(&buf).await?; - if !prefix.is_empty() { - out.write_all(prefix).await?; - } - out.write_all(data).await?; - if !suffix.is_empty() { - out.write_all(suffix).await?; - } - Ok(data_len) -} - -async fn prefixed_data_to_write(prefix: &[u8], data: &[u8], out: impl AsyncWrite + Unpin) -> io::Result { - prefixed_and_suffixed_data_to_write(prefix, data, &[], out).await -} - -/// Write a `text` message to `out`, which is assured to end in a newline. -pub async fn text_to_write(text: &[u8], out: impl AsyncWrite + Unpin) -> io::Result { - prefixed_and_suffixed_data_to_write(&[], text, b"\n", out).await -} - -/// Write a `data` message to `out`. -pub async fn data_to_write(data: &[u8], out: impl AsyncWrite + Unpin) -> io::Result { - prefixed_data_to_write(&[], data, out).await -} - -/// Write an error `message` to `out`. -pub async fn error_to_write(message: &[u8], out: impl AsyncWrite + Unpin) -> io::Result { - prefixed_data_to_write(ERR_PREFIX, message, out).await -} - -/// Write a response-end message to `out`. -pub async fn response_end_to_write(mut out: impl AsyncWrite + Unpin) -> io::Result { - out.write_all(RESPONSE_END_LINE).await?; - Ok(4) -} - -/// Write a delim message to `out`. -pub async fn delim_to_write(mut out: impl AsyncWrite + Unpin) -> io::Result { - out.write_all(DELIMITER_LINE).await?; - Ok(4) -} - -/// Write a flush message to `out`. -pub async fn flush_to_write(mut out: impl AsyncWrite + Unpin) -> io::Result { - out.write_all(FLUSH_LINE).await?; - Ok(4) -} - -/// Write `data` of `kind` to `out` using side-band encoding. -pub async fn band_to_write(kind: Channel, data: &[u8], out: impl AsyncWrite + Unpin) -> io::Result { - prefixed_data_to_write(&[kind as u8], data, out).await -} diff --git a/gix-packetline-blocking/src/encode/blocking_io.rs b/gix-packetline-blocking/src/encode/blocking_io.rs deleted file mode 100644 index 598b46e2661..00000000000 --- a/gix-packetline-blocking/src/encode/blocking_io.rs +++ /dev/null @@ -1,75 +0,0 @@ -// DO NOT EDIT - this is a copy of gix-packetline/src/encode/blocking_io.rs. Run `just copy-packetline` to update it. - -use std::io; - -use super::u16_to_hex; -use crate::{encode::Error, Channel, DELIMITER_LINE, ERR_PREFIX, FLUSH_LINE, MAX_DATA_LEN, RESPONSE_END_LINE}; - -/// Write a response-end message to `out`. -pub fn response_end_to_write(mut out: impl io::Write) -> io::Result { - out.write_all(RESPONSE_END_LINE).map(|_| 4) -} - -/// Write a delim message to `out`. -pub fn delim_to_write(mut out: impl io::Write) -> io::Result { - out.write_all(DELIMITER_LINE).map(|_| 4) -} - -/// Write a flush message to `out`. -pub fn flush_to_write(mut out: impl io::Write) -> io::Result { - out.write_all(FLUSH_LINE).map(|_| 4) -} - -/// Write an error `message` to `out`. -pub fn error_to_write(message: &[u8], out: impl io::Write) -> io::Result { - prefixed_data_to_write(ERR_PREFIX, message, out) -} - -/// Write `data` of `kind` to `out` using side-band encoding. -pub fn band_to_write(kind: Channel, data: &[u8], out: impl io::Write) -> io::Result { - prefixed_data_to_write(&[kind as u8], data, out) -} - -/// Write a `data` message to `out`. -pub fn data_to_write(data: &[u8], out: impl io::Write) -> io::Result { - prefixed_data_to_write(&[], data, out) -} - -/// Write a `text` message to `out`, which is assured to end in a newline. -pub fn text_to_write(text: &[u8], out: impl io::Write) -> io::Result { - prefixed_and_suffixed_data_to_write(&[], text, b"\n", out) -} - -fn prefixed_data_to_write(prefix: &[u8], data: &[u8], out: impl io::Write) -> io::Result { - prefixed_and_suffixed_data_to_write(prefix, data, &[], out) -} - -fn prefixed_and_suffixed_data_to_write( - prefix: &[u8], - data: &[u8], - suffix: &[u8], - mut out: impl io::Write, -) -> io::Result { - let data_len = prefix.len() + data.len() + suffix.len(); - if data_len > MAX_DATA_LEN { - return Err(io::Error::other(Error::DataLengthLimitExceeded { - length_in_bytes: data_len, - })); - } - if data.is_empty() { - return Err(io::Error::other(Error::DataIsEmpty)); - } - - let data_len = data_len + 4; - let buf = u16_to_hex(data_len as u16); - - out.write_all(&buf)?; - if !prefix.is_empty() { - out.write_all(prefix)?; - } - out.write_all(data)?; - if !suffix.is_empty() { - out.write_all(suffix)?; - } - Ok(data_len) -} diff --git a/gix-packetline-blocking/src/encode/mod.rs b/gix-packetline-blocking/src/encode/mod.rs deleted file mode 100644 index 238957cc4ca..00000000000 --- a/gix-packetline-blocking/src/encode/mod.rs +++ /dev/null @@ -1,29 +0,0 @@ -// DO NOT EDIT - this is a copy of gix-packetline/src/encode/mod.rs. Run `just copy-packetline` to update it. - -use crate::MAX_DATA_LEN; - -/// The error returned by most functions in the [`encode`][crate::encode] module -#[derive(Debug, thiserror::Error)] -#[allow(missing_docs)] -pub enum Error { - #[error("Cannot encode more than {MAX_DATA_LEN} bytes, got {length_in_bytes}")] - DataLengthLimitExceeded { length_in_bytes: usize }, - #[error("Empty lines are invalid")] - DataIsEmpty, -} - -#[cfg(all(not(feature = "blocking-io"), feature = "async-io"))] -mod async_io; -#[cfg(all(not(feature = "blocking-io"), feature = "async-io"))] -pub use async_io::*; - -#[cfg(feature = "blocking-io")] -mod blocking_io; -#[cfg(feature = "blocking-io")] -pub use blocking_io::*; - -pub(crate) fn u16_to_hex(value: u16) -> [u8; 4] { - let mut buf = [0u8; 4]; - faster_hex::hex_encode(&value.to_be_bytes(), &mut buf).expect("two bytes to 4 hex chars never fails"); - buf -} diff --git a/gix-packetline-blocking/src/lib.rs b/gix-packetline-blocking/src/lib.rs deleted file mode 100644 index 512b31b21a7..00000000000 --- a/gix-packetline-blocking/src/lib.rs +++ /dev/null @@ -1,108 +0,0 @@ -// DO NOT EDIT - this is a copy of gix-packetline/src/lib.rs. Run `just copy-packetline` to update it. - -//! Read and write the git packet line wire format without copying it. -//! -//! For reading the packet line format use the [`StreamingPeekableIter`], and for writing the [`Writer`]. -//! ## Feature Flags -#![cfg_attr( - all(doc, all(doc, feature = "document-features")), - doc = ::document_features::document_features!() -)] -#![cfg_attr(all(doc, feature = "document-features"), feature(doc_cfg, doc_auto_cfg))] -#![deny(missing_docs, rust_2018_idioms, unsafe_code)] - -const U16_HEX_BYTES: usize = 4; -const MAX_DATA_LEN: usize = 65516; -const MAX_LINE_LEN: usize = MAX_DATA_LEN + U16_HEX_BYTES; -const FLUSH_LINE: &[u8] = b"0000"; -const DELIMITER_LINE: &[u8] = b"0001"; -const RESPONSE_END_LINE: &[u8] = b"0002"; -const ERR_PREFIX: &[u8] = b"ERR "; - -/// One of three side-band types allowing to multiplex information over a single connection. -#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum Channel { - /// The usable data itself in any format. - Data = 1, - /// Progress information in a user-readable format. - Progress = 2, - /// Error information in a user readable format. Receiving it usually terminates the connection. - Error = 3, -} - -mod line; -/// -pub mod read; - -/// -#[cfg(any(feature = "async-io", feature = "blocking-io"))] -mod write; -#[cfg(all(not(feature = "blocking-io"), feature = "async-io"))] -pub use write::async_io::Writer; -#[cfg(feature = "blocking-io")] -pub use write::blocking_io::Writer; - -/// A borrowed packet line as it refers to a slice of data by reference. -#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum PacketLineRef<'a> { - /// A chunk of raw data. - Data(&'a [u8]), - /// A flush packet. - Flush, - /// A delimiter packet. - Delimiter, - /// The end of the response. - ResponseEnd, -} - -/// A packet line representing an Error in a side-band channel. -#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct ErrorRef<'a>(pub &'a [u8]); - -/// A packet line representing text, which may include a trailing newline. -#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct TextRef<'a>(pub &'a [u8]); - -/// A band in a side-band channel. -#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum BandRef<'a> { - /// A band carrying data. - Data(&'a [u8]), - /// A band carrying user readable progress information. - Progress(&'a [u8]), - /// A band carrying user readable errors. - Error(&'a [u8]), -} - -/// Read pack lines one after another, without consuming more than needed from the underlying -/// [`Read`][std::io::Read]. [`Flush`][PacketLineRef::Flush] lines cause the reader to stop producing lines forever, -/// leaving [`Read`][std::io::Read] at the start of whatever comes next. -/// -/// This implementation tries hard not to allocate at all which leads to quite some added complexity and plenty of extra memory copies. -pub struct StreamingPeekableIter { - read: T, - peek_buf: Vec, - #[cfg(any(feature = "blocking-io", feature = "async-io"))] - buf: Vec, - fail_on_err_lines: bool, - delimiters: &'static [PacketLineRef<'static>], - is_done: bool, - stopped_at: Option>, - #[cfg_attr(all(not(feature = "async-io"), not(feature = "blocking-io")), allow(dead_code))] - trace: bool, -} - -/// Utilities to help decoding packet lines -pub mod decode; -#[doc(inline)] -pub use decode::all_at_once as decode; -/// Utilities to encode different kinds of packet lines -pub mod encode; - -#[cfg(all(feature = "async-io", feature = "blocking-io"))] -compile_error!("Cannot set both 'blocking-io' and 'async-io' features as they are mutually exclusive"); diff --git a/gix-packetline-blocking/src/line/async_io.rs b/gix-packetline-blocking/src/line/async_io.rs deleted file mode 100644 index 83cf072023f..00000000000 --- a/gix-packetline-blocking/src/line/async_io.rs +++ /dev/null @@ -1,49 +0,0 @@ -// DO NOT EDIT - this is a copy of gix-packetline/src/line/async_io.rs. Run `just copy-packetline` to update it. - -use std::io; - -use futures_io::AsyncWrite; - -use crate::{encode, BandRef, Channel, ErrorRef, PacketLineRef, TextRef}; - -impl BandRef<'_> { - /// Serialize this instance to `out`, returning the amount of bytes written. - /// - /// The data written to `out` can be decoded with [`Borrowed::decode_band()]`. - pub async fn write_to(&self, out: impl AsyncWrite + Unpin) -> io::Result { - match self { - BandRef::Data(d) => encode::band_to_write(Channel::Data, d, out), - BandRef::Progress(d) => encode::band_to_write(Channel::Progress, d, out), - BandRef::Error(d) => encode::band_to_write(Channel::Error, d, out), - } - .await - } -} - -impl TextRef<'_> { - /// Serialize this instance to `out`, appending a newline if there is none, returning the amount of bytes written. - pub async fn write_to(&self, out: impl AsyncWrite + Unpin) -> io::Result { - encode::text_to_write(self.0, out).await - } -} - -impl ErrorRef<'_> { - /// Serialize this line as error to `out`. - /// - /// This includes a marker to allow decoding it outside of a side-band channel, returning the amount of bytes written. - pub async fn write_to(&self, out: impl AsyncWrite + Unpin) -> io::Result { - encode::error_to_write(self.0, out).await - } -} - -impl PacketLineRef<'_> { - /// Serialize this instance to `out` in git `packetline` format, returning the amount of bytes written to `out`. - pub async fn write_to(&self, out: impl AsyncWrite + Unpin) -> io::Result { - match self { - PacketLineRef::Data(d) => encode::data_to_write(d, out).await, - PacketLineRef::Flush => encode::flush_to_write(out).await, - PacketLineRef::Delimiter => encode::delim_to_write(out).await, - PacketLineRef::ResponseEnd => encode::response_end_to_write(out).await, - } - } -} diff --git a/gix-packetline-blocking/src/line/blocking_io.rs b/gix-packetline-blocking/src/line/blocking_io.rs deleted file mode 100644 index 749ccfce765..00000000000 --- a/gix-packetline-blocking/src/line/blocking_io.rs +++ /dev/null @@ -1,46 +0,0 @@ -// DO NOT EDIT - this is a copy of gix-packetline/src/line/blocking_io.rs. Run `just copy-packetline` to update it. - -use std::io; - -use crate::{encode, BandRef, Channel, ErrorRef, PacketLineRef, TextRef}; - -impl BandRef<'_> { - /// Serialize this instance to `out`, returning the amount of bytes written. - /// - /// The data written to `out` can be decoded with [`Borrowed::decode_band()]`. - pub fn write_to(&self, out: impl io::Write) -> io::Result { - match self { - BandRef::Data(d) => encode::band_to_write(Channel::Data, d, out), - BandRef::Progress(d) => encode::band_to_write(Channel::Progress, d, out), - BandRef::Error(d) => encode::band_to_write(Channel::Error, d, out), - } - } -} - -impl TextRef<'_> { - /// Serialize this instance to `out`, appending a newline if there is none, returning the amount of bytes written. - pub fn write_to(&self, out: impl io::Write) -> io::Result { - encode::text_to_write(self.0, out) - } -} - -impl ErrorRef<'_> { - /// Serialize this line as error to `out`. - /// - /// This includes a marker to allow decoding it outside a side-band channel, returning the amount of bytes written. - pub fn write_to(&self, out: impl io::Write) -> io::Result { - encode::error_to_write(self.0, out) - } -} - -impl PacketLineRef<'_> { - /// Serialize this instance to `out` in git `packetline` format, returning the amount of bytes written to `out`. - pub fn write_to(&self, out: impl io::Write) -> io::Result { - match self { - PacketLineRef::Data(d) => encode::data_to_write(d, out), - PacketLineRef::Flush => encode::flush_to_write(out), - PacketLineRef::Delimiter => encode::delim_to_write(out), - PacketLineRef::ResponseEnd => encode::response_end_to_write(out), - } - } -} diff --git a/gix-packetline-blocking/src/line/mod.rs b/gix-packetline-blocking/src/line/mod.rs deleted file mode 100644 index d584f0d3a8e..00000000000 --- a/gix-packetline-blocking/src/line/mod.rs +++ /dev/null @@ -1,90 +0,0 @@ -// DO NOT EDIT - this is a copy of gix-packetline/src/line/mod.rs. Run `just copy-packetline` to update it. - -use bstr::BStr; - -use crate::{decode, BandRef, Channel, ErrorRef, PacketLineRef, TextRef, ERR_PREFIX}; - -impl<'a> PacketLineRef<'a> { - /// Return this instance as slice if it's [`Data`][PacketLineRef::Data]. - pub fn as_slice(&self) -> Option<&'a [u8]> { - match self { - PacketLineRef::Data(d) => Some(d), - PacketLineRef::Flush | PacketLineRef::Delimiter | PacketLineRef::ResponseEnd => None, - } - } - /// Return this instance's [`as_slice()`][PacketLineRef::as_slice()] as [`BStr`]. - pub fn as_bstr(&self) -> Option<&'a BStr> { - self.as_slice().map(Into::into) - } - /// Interpret this instance's [`as_slice()`][PacketLineRef::as_slice()] as [`ErrorRef`]. - /// - /// This works for any data received in an error [channel][crate::Channel]. - /// - /// Note that this creates an unchecked error using the slice verbatim, which is useful to [serialize it][ErrorRef::write_to()]. - /// See [`check_error()`][PacketLineRef::check_error()] for a version that assures the error information is in the expected format. - pub fn as_error(&self) -> Option> { - self.as_slice().map(ErrorRef) - } - /// Check this instance's [`as_slice()`][PacketLineRef::as_slice()] is a valid [`ErrorRef`] and return it. - /// - /// This works for any data received in an error [channel][crate::Channel]. - pub fn check_error(&self) -> Option> { - self.as_slice().and_then(|data| { - if data.len() >= ERR_PREFIX.len() && &data[..ERR_PREFIX.len()] == ERR_PREFIX { - Some(ErrorRef(&data[ERR_PREFIX.len()..])) - } else { - None - } - }) - } - /// Return this instance as text, with the trailing newline truncated if present. - pub fn as_text(&self) -> Option> { - self.as_slice().map(Into::into) - } - - /// Interpret the data in this [`slice`][PacketLineRef::as_slice()] as [`BandRef`] according to the given `kind` of channel. - /// - /// Note that this is only relevant in a side-band channel. - /// See [`decode_band()`][PacketLineRef::decode_band()] in case `kind` is unknown. - pub fn as_band(&self, kind: Channel) -> Option> { - self.as_slice().map(|d| match kind { - Channel::Data => BandRef::Data(d), - Channel::Progress => BandRef::Progress(d), - Channel::Error => BandRef::Error(d), - }) - } - - /// Decode the band of this [`slice`][PacketLineRef::as_slice()] - pub fn decode_band(&self) -> Result, decode::band::Error> { - let d = self.as_slice().ok_or(decode::band::Error::NonDataLine)?; - Ok(match d[0] { - 1 => BandRef::Data(&d[1..]), - 2 => BandRef::Progress(&d[1..]), - 3 => BandRef::Error(&d[1..]), - band => return Err(decode::band::Error::InvalidSideBand { band_id: band }), - }) - } -} - -impl<'a> From<&'a [u8]> for TextRef<'a> { - fn from(d: &'a [u8]) -> Self { - let d = if d[d.len() - 1] == b'\n' { &d[..d.len() - 1] } else { d }; - TextRef(d) - } -} - -impl<'a> TextRef<'a> { - /// Return this instance's data. - pub fn as_slice(&self) -> &'a [u8] { - self.0 - } - /// Return this instance's data as [`BStr`]. - pub fn as_bstr(&self) -> &'a BStr { - self.0.into() - } -} - -#[cfg(all(not(feature = "blocking-io"), feature = "async-io"))] -mod async_io; -#[cfg(feature = "blocking-io")] -mod blocking_io; diff --git a/gix-packetline-blocking/src/read/async_io.rs b/gix-packetline-blocking/src/read/async_io.rs deleted file mode 100644 index 1b723ca13a5..00000000000 --- a/gix-packetline-blocking/src/read/async_io.rs +++ /dev/null @@ -1,198 +0,0 @@ -// DO NOT EDIT - this is a copy of gix-packetline/src/read/async_io.rs. Run `just copy-packetline` to update it. - -use std::io; - -use bstr::ByteSlice; -use futures_io::AsyncRead; -use futures_lite::AsyncReadExt; - -use crate::{ - decode, - read::{ExhaustiveOutcome, ProgressAction, WithSidebands}, - PacketLineRef, StreamingPeekableIter, MAX_LINE_LEN, U16_HEX_BYTES, -}; - -/// Non-IO methods -impl StreamingPeekableIter -where - T: AsyncRead + Unpin, -{ - #[allow(clippy::needless_lifetimes)] // TODO: remove once this is clippy false positive is fixed - async fn read_line_inner<'a>( - reader: &mut T, - buf: &'a mut [u8], - ) -> io::Result, decode::Error>> { - let (hex_bytes, data_bytes) = buf.split_at_mut(4); - reader.read_exact(hex_bytes).await?; - let num_data_bytes = match decode::hex_prefix(hex_bytes) { - Ok(decode::PacketLineOrWantedSize::Line(line)) => return Ok(Ok(line)), - Ok(decode::PacketLineOrWantedSize::Wanted(additional_bytes)) => additional_bytes as usize, - Err(err) => return Ok(Err(err)), - }; - - let (data_bytes, _) = data_bytes.split_at_mut(num_data_bytes); - reader.read_exact(data_bytes).await?; - match decode::to_data_line(data_bytes) { - Ok(line) => Ok(Ok(line)), - Err(err) => Ok(Err(err)), - } - } - - /// This function is needed to help the borrow checker allow us to return references all the time - /// It contains a bunch of logic shared between peek and `read_line` invocations. - async fn read_line_inner_exhaustive<'a>( - reader: &mut T, - buf: &'a mut Vec, - delimiters: &[PacketLineRef<'static>], - fail_on_err_lines: bool, - buf_resize: bool, - trace: bool, - ) -> ExhaustiveOutcome<'a> { - ( - false, - None, - Some(match Self::read_line_inner(reader, buf).await { - Ok(Ok(line)) => { - if trace { - match line { - #[allow(unused_variables)] - PacketLineRef::Data(d) => { - gix_trace::trace!("<< {}", d.as_bstr().trim().as_bstr()); - } - PacketLineRef::Flush => { - gix_trace::trace!("<< FLUSH"); - } - PacketLineRef::Delimiter => { - gix_trace::trace!("<< DELIM"); - } - PacketLineRef::ResponseEnd => { - gix_trace::trace!("<< RESPONSE_END"); - } - } - } - if delimiters.contains(&line) { - let stopped_at = delimiters.iter().find(|l| **l == line).copied(); - buf.clear(); - return (true, stopped_at, None); - } else if fail_on_err_lines { - if let Some(err) = line.check_error() { - let err = err.0.as_bstr().to_owned(); - buf.clear(); - return ( - true, - None, - Some(Err(io::Error::other(crate::read::Error { message: err }))), - ); - } - } - let len = line.as_slice().map_or(U16_HEX_BYTES, |s| s.len() + U16_HEX_BYTES); - if buf_resize { - buf.resize(len, 0); - } - Ok(Ok(crate::decode(buf).expect("only valid data here"))) - } - Ok(Err(err)) => { - buf.clear(); - Ok(Err(err)) - } - Err(err) => { - buf.clear(); - Err(err) - } - }), - ) - } - - /// Read a packet line into the internal buffer and return it. - /// - /// Returns `None` if the end of iteration is reached because of one of the following: - /// - /// * natural EOF - /// * ERR packet line encountered if [`fail_on_err_lines()`][StreamingPeekableIter::fail_on_err_lines()] is true. - /// * A `delimiter` packet line encountered - pub async fn read_line(&mut self) -> Option, decode::Error>>> { - if self.is_done { - return None; - } - if !self.peek_buf.is_empty() { - std::mem::swap(&mut self.peek_buf, &mut self.buf); - self.peek_buf.clear(); - Some(Ok(Ok(crate::decode(&self.buf).expect("only valid data in peek buf")))) - } else { - if self.buf.len() != MAX_LINE_LEN { - self.buf.resize(MAX_LINE_LEN, 0); - } - let (is_done, stopped_at, res) = Self::read_line_inner_exhaustive( - &mut self.read, - &mut self.buf, - self.delimiters, - self.fail_on_err_lines, - false, - self.trace, - ) - .await; - self.is_done = is_done; - self.stopped_at = stopped_at; - res - } - } - - /// Peek the next packet line without consuming it. Returns `None` if a stop-packet or an error - /// was encountered. - /// - /// Multiple calls to peek will return the same packet line, if there is one. - pub async fn peek_line(&mut self) -> Option, decode::Error>>> { - if self.is_done { - return None; - } - if self.peek_buf.is_empty() { - self.peek_buf.resize(MAX_LINE_LEN, 0); - let (is_done, stopped_at, res) = Self::read_line_inner_exhaustive( - &mut self.read, - &mut self.peek_buf, - self.delimiters, - self.fail_on_err_lines, - true, - self.trace, - ) - .await; - self.is_done = is_done; - self.stopped_at = stopped_at; - res - } else { - Some(Ok(Ok(crate::decode(&self.peek_buf).expect("only valid data here")))) - } - } - - /// Same as [`as_read_with_sidebands(…)`][StreamingPeekableIter::as_read_with_sidebands()], but for channels without side band support. - /// - /// Due to the preconfigured function type this method can be called without 'turbofish'. - #[allow(clippy::type_complexity)] - pub fn as_read(&mut self) -> WithSidebands<'_, T, fn(bool, &[u8]) -> ProgressAction> { - WithSidebands::new(self) - } - - /// Return this instance as implementor of [`Read`][io::Read] assuming side bands to be used in all received packet lines. - /// Each invocation of [`read_line()`][io::BufRead::read_line()] returns a packet line. - /// - /// Progress or error information will be passed to the given `handle_progress(is_error, text)` function, with `is_error: bool` - /// being true in case the `text` is to be interpreted as error. - /// - /// _Please note_ that side bands need to be negotiated with the server. - pub fn as_read_with_sidebands ProgressAction + Unpin>( - &mut self, - handle_progress: F, - ) -> WithSidebands<'_, T, F> { - WithSidebands::with_progress_handler(self, handle_progress) - } - - /// Same as [`as_read_with_sidebands(…)`][StreamingPeekableIter::as_read_with_sidebands()], but for channels without side band support. - /// - /// The type parameter `F` needs to be configured for this method to be callable using the 'turbofish' operator. - /// Use [`as_read()`][StreamingPeekableIter::as_read()]. - pub fn as_read_without_sidebands ProgressAction + Unpin>( - &mut self, - ) -> WithSidebands<'_, T, F> { - WithSidebands::without_progress_handler(self) - } -} diff --git a/gix-packetline-blocking/src/read/blocking_io.rs b/gix-packetline-blocking/src/read/blocking_io.rs deleted file mode 100644 index c81e7ee11d0..00000000000 --- a/gix-packetline-blocking/src/read/blocking_io.rs +++ /dev/null @@ -1,189 +0,0 @@ -// DO NOT EDIT - this is a copy of gix-packetline/src/read/blocking_io.rs. Run `just copy-packetline` to update it. - -use std::io; - -use bstr::ByteSlice; - -use crate::{ - decode, - read::{ExhaustiveOutcome, ProgressAction, WithSidebands}, - PacketLineRef, StreamingPeekableIter, MAX_LINE_LEN, U16_HEX_BYTES, -}; - -/// Non-IO methods -impl StreamingPeekableIter -where - T: io::Read, -{ - fn read_line_inner<'a>(reader: &mut T, buf: &'a mut [u8]) -> io::Result, decode::Error>> { - let (hex_bytes, data_bytes) = buf.split_at_mut(4); - reader.read_exact(hex_bytes)?; - let num_data_bytes = match decode::hex_prefix(hex_bytes) { - Ok(decode::PacketLineOrWantedSize::Line(line)) => return Ok(Ok(line)), - Ok(decode::PacketLineOrWantedSize::Wanted(additional_bytes)) => additional_bytes as usize, - Err(err) => return Ok(Err(err)), - }; - - let (data_bytes, _) = data_bytes.split_at_mut(num_data_bytes); - reader.read_exact(data_bytes)?; - match decode::to_data_line(data_bytes) { - Ok(line) => Ok(Ok(line)), - Err(err) => Ok(Err(err)), - } - } - - /// This function is needed to help the borrow checker allow us to return references all the time - /// It contains a bunch of logic shared between peek and `read_line` invocations. - fn read_line_inner_exhaustive<'a>( - reader: &mut T, - buf: &'a mut Vec, - delimiters: &[PacketLineRef<'static>], - fail_on_err_lines: bool, - buf_resize: bool, - trace: bool, - ) -> ExhaustiveOutcome<'a> { - ( - false, - None, - Some(match Self::read_line_inner(reader, buf) { - Ok(Ok(line)) => { - if trace { - match line { - #[allow(unused_variables)] - PacketLineRef::Data(d) => { - gix_trace::trace!("<< {}", d.as_bstr().trim().as_bstr()); - } - PacketLineRef::Flush => { - gix_trace::trace!("<< FLUSH"); - } - PacketLineRef::Delimiter => { - gix_trace::trace!("<< DELIM"); - } - PacketLineRef::ResponseEnd => { - gix_trace::trace!("<< RESPONSE_END"); - } - } - } - if delimiters.contains(&line) { - let stopped_at = delimiters.iter().find(|l| **l == line).copied(); - buf.clear(); - return (true, stopped_at, None); - } else if fail_on_err_lines { - if let Some(err) = line.check_error() { - let err = err.0.as_bstr().to_owned(); - buf.clear(); - return ( - true, - None, - Some(Err(io::Error::other(crate::read::Error { message: err }))), - ); - } - } - let len = line.as_slice().map_or(U16_HEX_BYTES, |s| s.len() + U16_HEX_BYTES); - if buf_resize { - buf.resize(len, 0); - } - // TODO(borrowchk): remove additional decoding of internal buffer which is needed only to make it past borrowchk - Ok(Ok(crate::decode(buf).expect("only valid data here"))) - } - Ok(Err(err)) => { - buf.clear(); - Ok(Err(err)) - } - Err(err) => { - buf.clear(); - Err(err) - } - }), - ) - } - - /// Read a packet line into the internal buffer and return it. - /// - /// Returns `None` if the end of iteration is reached because of one of the following: - /// - /// * natural EOF - /// * ERR packet line encountered if [`fail_on_err_lines()`][StreamingPeekableIter::fail_on_err_lines()] is true. - /// * A `delimiter` packet line encountered - pub fn read_line(&mut self) -> Option, decode::Error>>> { - if self.is_done { - return None; - } - if !self.peek_buf.is_empty() { - std::mem::swap(&mut self.peek_buf, &mut self.buf); - self.peek_buf.clear(); - Some(Ok(Ok(crate::decode(&self.buf).expect("only valid data in peek buf")))) - } else { - if self.buf.len() != MAX_LINE_LEN { - self.buf.resize(MAX_LINE_LEN, 0); - } - let (is_done, stopped_at, res) = Self::read_line_inner_exhaustive( - &mut self.read, - &mut self.buf, - self.delimiters, - self.fail_on_err_lines, - false, - self.trace, - ); - self.is_done = is_done; - self.stopped_at = stopped_at; - res - } - } - - /// Peek the next packet line without consuming it. Returns `None` if a stop-packet or an error - /// was encountered. - /// - /// Multiple calls to peek will return the same packet line, if there is one. - pub fn peek_line(&mut self) -> Option, decode::Error>>> { - if self.is_done { - return None; - } - if self.peek_buf.is_empty() { - self.peek_buf.resize(MAX_LINE_LEN, 0); - let (is_done, stopped_at, res) = Self::read_line_inner_exhaustive( - &mut self.read, - &mut self.peek_buf, - self.delimiters, - self.fail_on_err_lines, - true, - self.trace, - ); - self.is_done = is_done; - self.stopped_at = stopped_at; - res - } else { - Some(Ok(Ok(crate::decode(&self.peek_buf).expect("only valid data here")))) - } - } - - /// Return this instance as implementor of [`Read`][io::Read] assuming side bands to be used in all received packet lines. - /// Each invocation of [`read_line()`][io::BufRead::read_line()] returns a packet line. - /// - /// Progress or error information will be passed to the given `handle_progress(is_error, text)` function, with `is_error: bool` - /// being true in case the `text` is to be interpreted as error. - /// - /// _Please note_ that side bands need to be negotiated with the server. - pub fn as_read_with_sidebands ProgressAction>( - &mut self, - handle_progress: F, - ) -> WithSidebands<'_, T, F> { - WithSidebands::with_progress_handler(self, handle_progress) - } - - /// Same as [`as_read_with_sidebands(…)`][StreamingPeekableIter::as_read_with_sidebands()], but for channels without side band support. - /// - /// The type parameter `F` needs to be configured for this method to be callable using the 'turbofish' operator. - /// Use [`as_read()`][StreamingPeekableIter::as_read()]. - pub fn as_read_without_sidebands ProgressAction>(&mut self) -> WithSidebands<'_, T, F> { - WithSidebands::without_progress_handler(self) - } - - /// Same as [`as_read_with_sidebands(…)`][StreamingPeekableIter::as_read_with_sidebands()], but for channels without side band support. - /// - /// Due to the preconfigured function type this method can be called without 'turbofish'. - #[allow(clippy::type_complexity)] - pub fn as_read(&mut self) -> WithSidebands<'_, T, fn(bool, &[u8]) -> ProgressAction> { - WithSidebands::new(self) - } -} diff --git a/gix-packetline-blocking/src/read/mod.rs b/gix-packetline-blocking/src/read/mod.rs deleted file mode 100644 index 386fa492b80..00000000000 --- a/gix-packetline-blocking/src/read/mod.rs +++ /dev/null @@ -1,130 +0,0 @@ -// DO NOT EDIT - this is a copy of gix-packetline/src/read/mod.rs. Run `just copy-packetline` to update it. - -#[cfg(any(feature = "blocking-io", feature = "async-io"))] -use crate::MAX_LINE_LEN; -use crate::{PacketLineRef, StreamingPeekableIter, U16_HEX_BYTES}; - -/// Allow the read-progress handler to determine how to continue. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum ProgressAction { - /// Continue reading the next progress if available. - Continue, - /// Abort all IO even if more would be available, claiming the operation was interrupted. - Interrupt, -} - -#[cfg(any(feature = "blocking-io", feature = "async-io"))] -type ExhaustiveOutcome<'a> = ( - bool, // is_done - Option>, // stopped_at - Option, crate::decode::Error>>>, // actual method result -); - -mod error { - use std::fmt::{Debug, Display, Formatter}; - - use bstr::BString; - - /// The error representing an ERR packet line, as possibly wrapped into an `std::io::Error` in - /// [`read_line(…)`][super::StreamingPeekableIter::read_line()]. - #[derive(Debug)] - pub struct Error { - /// The contents of the ERR line, with `ERR` portion stripped. - pub message: BString, - } - - impl Display for Error { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - Display::fmt(&self.message, f) - } - } - - impl std::error::Error for Error {} -} -pub use error::Error; - -impl StreamingPeekableIter { - /// Return a new instance from `read` which will stop decoding packet lines when receiving one of the given `delimiters`. - /// If `trace` is `true`, all packetlines received or sent will be passed to the facilities of the `gix-trace` crate. - pub fn new(read: T, delimiters: &'static [PacketLineRef<'static>], trace: bool) -> Self { - StreamingPeekableIter { - read, - #[cfg(any(feature = "blocking-io", feature = "async-io"))] - buf: vec![0; MAX_LINE_LEN], - peek_buf: Vec::new(), - delimiters, - fail_on_err_lines: false, - is_done: false, - stopped_at: None, - trace, - } - } - - /// Modify the peek buffer, overwriting the byte at `position` with the given byte to `replace_with` while truncating - /// it to contain only bytes until the newly replaced `position`. - /// - /// This is useful if you would want to remove 'special bytes' hidden behind, say a NULL byte to disappear and allow - /// standard line readers to read the next line as usual. - /// - /// **Note** that `position` does not include the 4 bytes prefix (they are invisible outside the reader) - pub fn peek_buffer_replace_and_truncate(&mut self, position: usize, replace_with: u8) { - let position = position + U16_HEX_BYTES; - self.peek_buf[position] = replace_with; - - let new_len = position + 1; - self.peek_buf.truncate(new_len); - self.peek_buf[..4].copy_from_slice(&crate::encode::u16_to_hex((new_len) as u16)); - } - - /// Returns the packet line that stopped the iteration, or - /// `None` if the end wasn't reached yet, on EOF, or if [`fail_on_err_lines()`][StreamingPeekableIter::fail_on_err_lines()] was true. - pub fn stopped_at(&self) -> Option> { - self.stopped_at - } - - /// Reset all iteration state allowing to continue a stopped iteration that is not yet at EOF. - /// - /// This can happen once a delimiter is reached. - pub fn reset(&mut self) { - let delimiters = std::mem::take(&mut self.delimiters); - self.reset_with(delimiters); - } - - /// Similar to [`reset()`][StreamingPeekableIter::reset()] with support to changing the `delimiters`. - pub fn reset_with(&mut self, delimiters: &'static [PacketLineRef<'static>]) { - self.delimiters = delimiters; - self.is_done = false; - self.stopped_at = None; - } - - /// If `value` is `true` the provider will check for special `ERR` packet lines and stop iteration when one is encountered. - /// - /// Use [`stopped_at()]`[`StreamingPeekableIter::stopped_at()`] to inspect the cause of the end of the iteration. - /// ne - pub fn fail_on_err_lines(&mut self, value: bool) { - self.fail_on_err_lines = value; - } - - /// Replace the reader used with the given `read`, resetting all other iteration state as well. - pub fn replace(&mut self, read: T) -> T { - let prev = std::mem::replace(&mut self.read, read); - self.reset(); - self.fail_on_err_lines = false; - prev - } - - /// Return the inner read - pub fn into_inner(self) -> T { - self.read - } -} - -#[cfg(feature = "blocking-io")] -mod blocking_io; - -#[cfg(all(not(feature = "blocking-io"), feature = "async-io"))] -mod async_io; - -mod sidebands; -#[cfg(any(feature = "blocking-io", feature = "async-io"))] -pub use sidebands::WithSidebands; diff --git a/gix-packetline-blocking/src/read/sidebands/async_io.rs b/gix-packetline-blocking/src/read/sidebands/async_io.rs deleted file mode 100644 index 2d86567a87b..00000000000 --- a/gix-packetline-blocking/src/read/sidebands/async_io.rs +++ /dev/null @@ -1,373 +0,0 @@ -// DO NOT EDIT - this is a copy of gix-packetline/src/read/sidebands/async_io.rs. Run `just copy-packetline` to update it. - -use std::{ - future::Future, - pin::Pin, - task::{ready, Context, Poll}, -}; - -use futures_io::{AsyncBufRead, AsyncRead}; - -use crate::{decode, read::ProgressAction, BandRef, PacketLineRef, StreamingPeekableIter, TextRef, U16_HEX_BYTES}; - -type ReadLineResult<'a> = Option, decode::Error>>>; -/// An implementor of [`AsyncBufRead`] yielding packet lines on each call to [`read_line()`][AsyncBufRead::read_line()]. -/// It's also possible to hide the underlying packet lines using the [`Read`][AsyncRead] implementation which is useful -/// if they represent binary data, like the one of a pack file. -pub struct WithSidebands<'a, T, F> -where - T: AsyncRead, -{ - state: State<'a, T>, - handle_progress: Option, - pos: usize, - cap: usize, -} - -impl Drop for WithSidebands<'_, T, F> -where - T: AsyncRead, -{ - fn drop(&mut self) { - if let State::Idle { ref mut parent } = self.state { - parent - .as_mut() - .expect("parent is always available if we are idle") - .reset(); - } - } -} - -impl<'a, T> WithSidebands<'a, T, fn(bool, &[u8]) -> ProgressAction> -where - T: AsyncRead, -{ - /// Create a new instance with the given provider as `parent`. - pub fn new(parent: &'a mut StreamingPeekableIter) -> Self { - WithSidebands { - state: State::Idle { parent: Some(parent) }, - handle_progress: None, - pos: 0, - cap: 0, - } - } -} - -enum State<'a, T> { - Idle { - parent: Option<&'a mut StreamingPeekableIter>, - }, - ReadLine { - read_line: Pin> + 'a>>, - parent_inactive: Option<*mut StreamingPeekableIter>, - }, -} - -/// # SAFETY -/// It's safe because T is `Send` and we have a test that assures that our `StreamingPeekableIter` is `Send` as well, -/// hence the `*mut _` is `Send`. -/// `read_line` isn't send and we can't declare it as such as it forces `Send` in all places (BUT WHY IS THAT A PROBLEM, I don't recall). -/// However, it's only used when pinned and thus isn't actually sent anywhere, it's a secondary state of the future used after it was Send -/// to a thread possibly. -// TODO: Is it possible to declare it as it should be? -#[allow(unsafe_code, clippy::non_send_fields_in_send_ty)] -unsafe impl Send for State<'_, T> where T: Send {} - -impl<'a, T, F> WithSidebands<'a, T, F> -where - T: AsyncRead + Unpin, - F: FnMut(bool, &[u8]) -> ProgressAction + Unpin, -{ - /// Create a new instance with the given `parent` provider and the `handle_progress` function. - /// - /// Progress or error information will be passed to the given `handle_progress(is_error, text)` function, with `is_error: bool` - /// being true in case the `text` is to be interpreted as error. - pub fn with_progress_handler(parent: &'a mut StreamingPeekableIter, handle_progress: F) -> Self { - WithSidebands { - state: State::Idle { parent: Some(parent) }, - handle_progress: Some(handle_progress), - pos: 0, - cap: 0, - } - } - - /// Create a new instance without a progress handler. - pub fn without_progress_handler(parent: &'a mut StreamingPeekableIter) -> Self { - WithSidebands { - state: State::Idle { parent: Some(parent) }, - handle_progress: None, - pos: 0, - cap: 0, - } - } - - /// Forwards to the parent [`StreamingPeekableIter::reset_with()`] - pub fn reset_with(&mut self, delimiters: &'static [PacketLineRef<'static>]) { - if let State::Idle { ref mut parent } = self.state { - parent - .as_mut() - .expect("parent is always available if we are idle") - .reset_with(delimiters); - } - } - - /// Forwards to the parent [`StreamingPeekableIter::stopped_at()`] - pub fn stopped_at(&self) -> Option> { - match self.state { - State::Idle { ref parent } => { - parent - .as_ref() - .expect("parent is always available if we are idle") - .stopped_at - } - _ => None, - } - } - - /// Set or unset the progress handler. - pub fn set_progress_handler(&mut self, handle_progress: Option) { - self.handle_progress = handle_progress; - } - - /// Effectively forwards to the parent [`StreamingPeekableIter::peek_line()`], allowing to see what would be returned - /// next on a call to [`read_line()`][io::BufRead::read_line()]. - /// - /// # Warning - /// - /// This skips all sideband handling and may return an unprocessed line with sidebands still contained in it. - pub async fn peek_data_line(&mut self) -> Option>> { - match self.state { - State::Idle { ref mut parent } => match parent - .as_mut() - .expect("parent is always available if we are idle") - .peek_line() - .await - { - Some(Ok(Ok(PacketLineRef::Data(line)))) => Some(Ok(Ok(line))), - Some(Ok(Err(err))) => Some(Ok(Err(err))), - Some(Err(err)) => Some(Err(err)), - _ => None, - }, - _ => None, - } - } - - /// Read a packet line as string line. - pub fn read_line_to_string<'b>(&'b mut self, buf: &'b mut String) -> ReadLineFuture<'a, 'b, T, F> { - ReadLineFuture { parent: self, buf } - } - - /// Read a packet line from the underlying packet reader, returning empty lines if a stop-packetline was reached. - /// - /// # Warning - /// - /// This skips all sideband handling and may return an unprocessed line with sidebands still contained in it. - pub async fn read_data_line(&mut self) -> Option, decode::Error>>> { - match &mut self.state { - State::Idle { parent: Some(parent) } => { - assert_eq!( - self.cap, 0, - "we don't support partial buffers right now - read-line must be used consistently" - ); - parent.read_line().await - } - _ => None, - } - } -} - -#[allow(dead_code)] -pub struct ReadDataLineFuture<'a, 'b, T: AsyncRead, F> { - parent: &'b mut WithSidebands<'a, T, F>, - buf: &'b mut Vec, -} - -impl Future for ReadDataLineFuture<'_, '_, T, F> -where - T: AsyncRead + Unpin, - F: FnMut(bool, &[u8]) -> ProgressAction + Unpin, -{ - type Output = std::io::Result; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - assert_eq!( - self.parent.cap, 0, - "we don't support partial buffers right now - read-line must be used consistently" - ); - let Self { buf, parent } = &mut *self; - let line = ready!(Pin::new(parent).poll_fill_buf(cx))?; - buf.clear(); - buf.extend_from_slice(line); - let bytes = line.len(); - self.parent.cap = 0; - Poll::Ready(Ok(bytes)) - } -} - -pub struct ReadLineFuture<'a, 'b, T: AsyncRead, F> { - parent: &'b mut WithSidebands<'a, T, F>, - buf: &'b mut String, -} - -impl Future for ReadLineFuture<'_, '_, T, F> -where - T: AsyncRead + Unpin, - F: FnMut(bool, &[u8]) -> ProgressAction + Unpin, -{ - type Output = std::io::Result; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - assert_eq!( - self.parent.cap, 0, - "we don't support partial buffers right now - read-line must be used consistently" - ); - let Self { buf, parent } = &mut *self; - let line = std::str::from_utf8(ready!(Pin::new(parent).poll_fill_buf(cx))?).map_err(std::io::Error::other)?; - buf.clear(); - buf.push_str(line); - let bytes = line.len(); - self.parent.cap = 0; - Poll::Ready(Ok(bytes)) - } -} - -impl AsyncBufRead for WithSidebands<'_, T, F> -where - T: AsyncRead + Unpin, - F: FnMut(bool, &[u8]) -> ProgressAction + Unpin, -{ - fn poll_fill_buf(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - use std::io; - - use futures_lite::FutureExt; - { - let this = self.as_mut().get_mut(); - if this.pos >= this.cap { - let (ofs, cap) = loop { - match this.state { - State::Idle { ref mut parent } => { - let parent = parent.take().expect("parent to be present here"); - let inactive = parent as *mut _; - this.state = State::ReadLine { - read_line: parent.read_line().boxed_local(), - parent_inactive: Some(inactive), - } - } - State::ReadLine { - ref mut read_line, - ref mut parent_inactive, - } => { - let line = ready!(read_line.poll(cx)); - - this.state = { - let parent = parent_inactive.take().expect("parent pointer always set"); - // SAFETY: It's safe to recover the original mutable reference (from which - // the `read_line` future was created as the latter isn't accessible anymore - // once the state is set to Idle. In other words, either one or the other are - // accessible, never both at the same time. - // Also: We keep a pointer around which is protected by borrowcheck since it's created - // from a legal mutable reference which is moved into the read_line future - if it was manually - // implemented we would be able to re-obtain it from there. - #[allow(unsafe_code)] - let parent = unsafe { &mut *parent }; - State::Idle { parent: Some(parent) } - }; - - let line = match line { - Some(line) => line?.map_err(io::Error::other)?, - None => break (0, 0), - }; - - match this.handle_progress.as_mut() { - Some(handle_progress) => { - let band = line.decode_band().map_err(io::Error::other)?; - const ENCODED_BAND: usize = 1; - match band { - BandRef::Data(d) => { - if d.is_empty() { - continue; - } - break (U16_HEX_BYTES + ENCODED_BAND, d.len()); - } - BandRef::Progress(d) => { - let text = TextRef::from(d).0; - match handle_progress(false, text) { - ProgressAction::Continue => {} - ProgressAction::Interrupt => { - return Poll::Ready(Err(io::Error::other("interrupted by user"))) - } - } - } - BandRef::Error(d) => { - let text = TextRef::from(d).0; - match handle_progress(true, text) { - ProgressAction::Continue => {} - ProgressAction::Interrupt => { - return Poll::Ready(Err(io::Error::other("interrupted by user"))) - } - } - } - } - } - None => { - break match line.as_slice() { - Some(d) => (U16_HEX_BYTES, d.len()), - None => { - return Poll::Ready(Err(io::Error::other( - "encountered non-data line in a data-line only context", - ))) - } - } - } - } - } - } - }; - this.cap = cap + ofs; - this.pos = ofs; - } - } - let range = self.pos..self.cap; - match &self.get_mut().state { - State::Idle { parent } => Poll::Ready(Ok(&parent.as_ref().expect("parent always available").buf[range])), - State::ReadLine { .. } => unreachable!("at least in theory"), - } - } - - fn consume(self: Pin<&mut Self>, amt: usize) { - let this = self.get_mut(); - this.pos = std::cmp::min(this.pos + amt, this.cap); - } -} - -impl AsyncRead for WithSidebands<'_, T, F> -where - T: AsyncRead + Unpin, - F: FnMut(bool, &[u8]) -> ProgressAction + Unpin, -{ - fn poll_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8]) -> Poll> { - use std::io::Read; - let mut rem = ready!(self.as_mut().poll_fill_buf(cx))?; - let nread = rem.read(buf)?; - self.consume(nread); - Poll::Ready(Ok(nread)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - fn receiver(_i: T) {} - - /// We want to declare items containing pointers of `StreamingPeekableIter` `Send` as well, so it must be `Send` itself. - #[test] - fn streaming_peekable_iter_is_send() { - receiver(StreamingPeekableIter::new(Vec::::new(), &[], false)); - } - - #[test] - fn state_is_send() { - let mut s = StreamingPeekableIter::new(Vec::::new(), &[], false); - receiver(State::Idle { parent: Some(&mut s) }); - } -} diff --git a/gix-packetline-blocking/src/read/sidebands/blocking_io.rs b/gix-packetline-blocking/src/read/sidebands/blocking_io.rs deleted file mode 100644 index 9fb95a43c4b..00000000000 --- a/gix-packetline-blocking/src/read/sidebands/blocking_io.rs +++ /dev/null @@ -1,210 +0,0 @@ -// DO NOT EDIT - this is a copy of gix-packetline/src/read/sidebands/blocking_io.rs. Run `just copy-packetline` to update it. - -use std::{io, io::BufRead}; - -use crate::{read::ProgressAction, BandRef, PacketLineRef, StreamingPeekableIter, TextRef, U16_HEX_BYTES}; - -/// An implementor of [`BufRead`][io::BufRead] yielding packet lines on each call to [`read_line()`][io::BufRead::read_line()]. -/// It's also possible to hide the underlying packet lines using the [`Read`][io::Read] implementation which is useful -/// if they represent binary data, like the one of a pack file. -pub struct WithSidebands<'a, T, F> -where - T: io::Read, -{ - parent: &'a mut StreamingPeekableIter, - handle_progress: Option, - pos: usize, - cap: usize, -} - -impl Drop for WithSidebands<'_, T, F> -where - T: io::Read, -{ - fn drop(&mut self) { - self.parent.reset(); - } -} - -impl<'a, T> WithSidebands<'a, T, fn(bool, &[u8]) -> ProgressAction> -where - T: io::Read, -{ - /// Create a new instance with the given provider as `parent`. - pub fn new(parent: &'a mut StreamingPeekableIter) -> Self { - WithSidebands { - parent, - handle_progress: None, - pos: 0, - cap: 0, - } - } -} - -impl<'a, T, F> WithSidebands<'a, T, F> -where - T: io::Read, - F: FnMut(bool, &[u8]) -> ProgressAction, -{ - /// Create a new instance with the given `parent` provider and the `handle_progress` function. - /// - /// Progress or error information will be passed to the given `handle_progress(is_error, text)` function, with `is_error: bool` - /// being true in case the `text` is to be interpreted as error. - pub fn with_progress_handler(parent: &'a mut StreamingPeekableIter, handle_progress: F) -> Self { - WithSidebands { - parent, - handle_progress: Some(handle_progress), - pos: 0, - cap: 0, - } - } - - /// Create a new instance without a progress handler. - pub fn without_progress_handler(parent: &'a mut StreamingPeekableIter) -> Self { - WithSidebands { - parent, - handle_progress: None, - pos: 0, - cap: 0, - } - } - - /// Forwards to the parent [`StreamingPeekableIter::reset_with()`] - pub fn reset_with(&mut self, delimiters: &'static [PacketLineRef<'static>]) { - self.parent.reset_with(delimiters); - } - - /// Forwards to the parent [`StreamingPeekableIter::stopped_at()`] - pub fn stopped_at(&self) -> Option> { - self.parent.stopped_at - } - - /// Set or unset the progress handler. - pub fn set_progress_handler(&mut self, handle_progress: Option) { - self.handle_progress = handle_progress; - } - - /// Effectively forwards to the parent [`StreamingPeekableIter::peek_line()`], allowing to see what would be returned - /// next on a call to [`read_line()`][io::BufRead::read_line()]. - /// - /// # Warning - /// - /// This skips all sideband handling and may return an unprocessed line with sidebands still contained in it. - pub fn peek_data_line(&mut self) -> Option>> { - match self.parent.peek_line() { - Some(Ok(Ok(PacketLineRef::Data(line)))) => Some(Ok(Ok(line))), - Some(Ok(Err(err))) => Some(Ok(Err(err))), - Some(Err(err)) => Some(Err(err)), - _ => None, - } - } - - /// Read a whole packetline from the underlying reader, with empty lines indicating a stop packetline. - /// - /// # Warning - /// - /// This skips all sideband handling and may return an unprocessed line with sidebands still contained in it. - pub fn read_data_line(&mut self) -> Option, crate::decode::Error>>> { - assert_eq!( - self.cap, 0, - "we don't support partial buffers right now - read-line must be used consistently" - ); - self.parent.read_line() - } - - /// Like `BufRead::read_line()`, but will only read one packetline at a time. - /// - /// It will also be easier to call as sometimes it's unclear which implementation we get on a type like this with - /// plenty of generic parameters. - pub fn read_line_to_string(&mut self, buf: &mut String) -> io::Result { - assert_eq!( - self.cap, 0, - "we don't support partial buffers right now - read-line must be used consistently" - ); - let line = std::str::from_utf8(self.fill_buf()?).map_err(io::Error::other)?; - buf.push_str(line); - let bytes = line.len(); - self.cap = 0; - Ok(bytes) - } -} - -impl BufRead for WithSidebands<'_, T, F> -where - T: io::Read, - F: FnMut(bool, &[u8]) -> ProgressAction, -{ - fn fill_buf(&mut self) -> io::Result<&[u8]> { - if self.pos >= self.cap { - let (ofs, cap) = loop { - let line = match self.parent.read_line() { - Some(line) => line?.map_err(io::Error::other)?, - None => break (0, 0), - }; - match self.handle_progress.as_mut() { - Some(handle_progress) => { - let band = line.decode_band().map_err(io::Error::other)?; - const ENCODED_BAND: usize = 1; - match band { - BandRef::Data(d) => { - if d.is_empty() { - continue; - } - break (U16_HEX_BYTES + ENCODED_BAND, d.len()); - } - BandRef::Progress(d) => { - let text = TextRef::from(d).0; - match handle_progress(false, text) { - ProgressAction::Continue => {} - ProgressAction::Interrupt => { - return Err(std::io::Error::other("interrupted by user")) - } - } - } - BandRef::Error(d) => { - let text = TextRef::from(d).0; - match handle_progress(true, text) { - ProgressAction::Continue => {} - ProgressAction::Interrupt => { - return Err(std::io::Error::other("interrupted by user")) - } - } - } - } - } - None => { - break match line.as_slice() { - Some(d) => (U16_HEX_BYTES, d.len()), - None => { - return Err(io::Error::new( - io::ErrorKind::UnexpectedEof, - "encountered non-data line in a data-line only context", - )) - } - } - } - } - }; - self.cap = cap + ofs; - self.pos = ofs; - } - Ok(&self.parent.buf[self.pos..self.cap]) - } - - fn consume(&mut self, amt: usize) { - self.pos = std::cmp::min(self.pos + amt, self.cap); - } -} - -impl io::Read for WithSidebands<'_, T, F> -where - T: io::Read, - F: FnMut(bool, &[u8]) -> ProgressAction, -{ - fn read(&mut self, buf: &mut [u8]) -> io::Result { - let mut rem = self.fill_buf()?; - let nread = rem.read(buf)?; - self.consume(nread); - Ok(nread) - } -} diff --git a/gix-packetline-blocking/src/read/sidebands/mod.rs b/gix-packetline-blocking/src/read/sidebands/mod.rs deleted file mode 100644 index d9ff58f7809..00000000000 --- a/gix-packetline-blocking/src/read/sidebands/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -// DO NOT EDIT - this is a copy of gix-packetline/src/read/sidebands/mod.rs. Run `just copy-packetline` to update it. - -#[cfg(feature = "blocking-io")] -mod blocking_io; -#[cfg(feature = "blocking-io")] -pub use blocking_io::WithSidebands; - -#[cfg(all(not(feature = "blocking-io"), feature = "async-io"))] -mod async_io; -#[cfg(all(not(feature = "blocking-io"), feature = "async-io"))] -pub use async_io::WithSidebands; diff --git a/gix-packetline-blocking/src/write/async_io.rs b/gix-packetline-blocking/src/write/async_io.rs deleted file mode 100644 index 0c5a7ae8143..00000000000 --- a/gix-packetline-blocking/src/write/async_io.rs +++ /dev/null @@ -1,98 +0,0 @@ -// DO NOT EDIT - this is a copy of gix-packetline/src/write/async_io.rs. Run `just copy-packetline` to update it. - -use std::{ - io, - pin::Pin, - task::{ready, Context, Poll}, -}; - -use futures_io::AsyncWrite; - -use crate::{encode, MAX_DATA_LEN, U16_HEX_BYTES}; - -pin_project_lite::pin_project! { - /// An implementor of [`Write`][io::Write] which passes all input to an inner `Write` in packet line data encoding, - /// one line per `write(…)` call or as many lines as it takes if the data doesn't fit into the maximum allowed line length. - pub struct Writer { - #[pin] - inner: encode::LineWriter<'static, T>, - state: State, - } -} - -enum State { - Idle, - WriteData(usize), -} - -impl Writer { - /// Create a new instance from the given `write` - pub fn new(write: T) -> Self { - Writer { - inner: encode::LineWriter::new(write, &[], &[]), - state: State::Idle, - } - } - - /// Return the inner writer, consuming self. - pub fn into_inner(self) -> T { - self.inner.into_inner() - } - - /// Return a mutable reference to the inner writer, useful if packet lines should be serialized directly. - pub fn inner_mut(&mut self) -> &mut T { - &mut self.inner.writer - } -} - -/// Non-IO methods -impl Writer { - /// If called, each call to [`write()`][io::Write::write()] will write bytes as is. - pub fn enable_binary_mode(&mut self) { - self.inner.suffix = &[]; - } - /// If called, each call to [`write()`][io::Write::write()] will write the input as text, appending a trailing newline - /// if needed before writing. - pub fn enable_text_mode(&mut self) { - self.inner.suffix = b"\n"; - } -} - -impl AsyncWrite for Writer { - fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { - let mut this = self.project(); - loop { - match this.state { - State::Idle => { - if buf.is_empty() { - return Poll::Ready(Err(io::Error::other( - "empty packet lines are not permitted as '0004' is invalid", - ))); - } - *this.state = State::WriteData(0); - } - State::WriteData(written) => { - while *written != buf.len() { - let data = &buf[*written..*written + (buf.len() - *written).min(MAX_DATA_LEN)]; - let n = ready!(this.inner.as_mut().poll_write(cx, data))?; - if n == 0 { - return Poll::Ready(Err(io::ErrorKind::WriteZero.into())); - } - *written += n; - *written -= U16_HEX_BYTES + this.inner.suffix.len(); - } - *this.state = State::Idle; - return Poll::Ready(Ok(buf.len())); - } - } - } - } - - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - self.project().inner.poll_flush(cx) - } - - fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - self.project().inner.poll_close(cx) - } -} diff --git a/gix-packetline-blocking/src/write/blocking_io.rs b/gix-packetline-blocking/src/write/blocking_io.rs deleted file mode 100644 index 99201a13f42..00000000000 --- a/gix-packetline-blocking/src/write/blocking_io.rs +++ /dev/null @@ -1,72 +0,0 @@ -// DO NOT EDIT - this is a copy of gix-packetline/src/write/blocking_io.rs. Run `just copy-packetline` to update it. - -use std::io; - -use crate::{MAX_DATA_LEN, U16_HEX_BYTES}; - -/// An implementor of [`Write`][io::Write] which passes all input to an inner `Write` in packet line data encoding, -/// one line per `write(…)` call or as many lines as it takes if the data doesn't fit into the maximum allowed line length. -pub struct Writer { - /// the `Write` implementation to which to propagate packet lines - inner: T, - binary: bool, -} - -impl Writer { - /// Create a new instance from the given `write` - pub fn new(write: T) -> Self { - Writer { - inner: write, - binary: true, - } - } -} - -/// Non-IO methods -impl Writer { - /// If called, each call to [`write()`][io::Write::write()] will write bytes as is. - pub fn enable_binary_mode(&mut self) { - self.binary = true; - } - /// If called, each call to [`write()`][io::Write::write()] will write the input as text, appending a trailing newline - /// if needed before writing. - pub fn enable_text_mode(&mut self) { - self.binary = false; - } - /// Return the inner writer, consuming self. - pub fn into_inner(self) -> T { - self.inner - } - /// Return a mutable reference to the inner writer, useful if packet lines should be serialized directly. - pub fn inner_mut(&mut self) -> &mut T { - &mut self.inner - } -} - -impl io::Write for Writer { - fn write(&mut self, mut buf: &[u8]) -> io::Result { - if buf.is_empty() { - return Err(io::Error::other( - "empty packet lines are not permitted as '0004' is invalid", - )); - } - - let mut written = 0; - while !buf.is_empty() { - let (data, rest) = buf.split_at(buf.len().min(MAX_DATA_LEN)); - written += if self.binary { - crate::encode::data_to_write(data, &mut self.inner) - } else { - crate::encode::text_to_write(data, &mut self.inner) - }?; - // subtract header (and trailing NL) because write-all can't handle writing more than it passes in - written -= U16_HEX_BYTES + usize::from(!self.binary); - buf = rest; - } - Ok(written) - } - - fn flush(&mut self) -> io::Result<()> { - self.inner.flush() - } -} diff --git a/gix-packetline-blocking/src/write/mod.rs b/gix-packetline-blocking/src/write/mod.rs deleted file mode 100644 index 73585e91f47..00000000000 --- a/gix-packetline-blocking/src/write/mod.rs +++ /dev/null @@ -1,23 +0,0 @@ -// DO NOT EDIT - this is a copy of gix-packetline/src/write/mod.rs. Run `just copy-packetline` to update it. - -use crate::Writer; - -#[cfg(all(not(feature = "blocking-io"), feature = "async-io"))] -pub(crate) mod async_io; - -#[cfg(feature = "blocking-io")] -pub(crate) mod blocking_io; - -/// Common methods -impl Writer { - /// As [`enable_text_mode()`][Writer::enable_text_mode()], but suitable for chaining. - pub fn text_mode(mut self) -> Self { - self.enable_text_mode(); - self - } - /// As [`enable_binary_mode()`][Writer::enable_binary_mode()], but suitable for chaining. - pub fn binary_mode(mut self) -> Self { - self.enable_binary_mode(); - self - } -} diff --git a/gix-packetline/Cargo.toml b/gix-packetline/Cargo.toml index 618b15a66f5..284376b6e99 100644 --- a/gix-packetline/Cargo.toml +++ b/gix-packetline/Cargo.toml @@ -18,12 +18,9 @@ doctest = false #! By default, all IO related capabilities will be missing unless one of the following is chosen. default = [] -#! ### _Mutually exclusive_ -#! Specifying both causes a compile error, preventing the use of `--all-features`. - -## If set, all IO will become blocking. The same types will be used preventing side-by-side usage of blocking and non-blocking IO. +## Enable blocking I/O API. blocking-io = [] -## Implement IO traits from `futures-io`. +## Enable async I/O API using IO traits from `futures-io`. # no `dep:` for futures-lite (https://github.com/rust-secure-code/cargo-auditable/issues/124) async-io = ["dep:futures-io", "futures-lite", "dep:pin-project-lite"] diff --git a/gix-packetline/src/encode/async_io.rs b/gix-packetline/src/encode/async_io.rs index c2b10bc4775..abcf6a5200e 100644 --- a/gix-packetline/src/encode/async_io.rs +++ b/gix-packetline/src/encode/async_io.rs @@ -8,7 +8,10 @@ use futures_io::AsyncWrite; use futures_lite::AsyncWriteExt; use super::u16_to_hex; -use crate::{encode::Error, Channel, DELIMITER_LINE, ERR_PREFIX, FLUSH_LINE, MAX_DATA_LEN, RESPONSE_END_LINE}; +use crate::{ + encode::Error, BandRef, Channel, ErrorRef, PacketLineRef, TextRef, DELIMITER_LINE, ERR_PREFIX, FLUSH_LINE, + MAX_DATA_LEN, RESPONSE_END_LINE, +}; pin_project_lite::pin_project! { /// A way of writing packet lines asynchronously. @@ -210,3 +213,37 @@ pub async fn flush_to_write(mut out: impl AsyncWrite + Unpin) -> io::Result io::Result { prefixed_data_to_write(&[kind as u8], data, out).await } + +/// Serialize this instance to `out`, returning the amount of bytes written. +/// +/// The data written to `out` can be decoded with [`Borrowed::decode_band()]`. +pub async fn write_band(band: &BandRef<'_>, out: impl AsyncWrite + Unpin) -> io::Result { + match band { + BandRef::Data(d) => band_to_write(Channel::Data, d, out), + BandRef::Progress(d) => band_to_write(Channel::Progress, d, out), + BandRef::Error(d) => band_to_write(Channel::Error, d, out), + } + .await +} + +/// Serialize this instance to `out`, appending a newline if there is none, returning the amount of bytes written. +pub async fn write_text(text: &TextRef<'_>, out: impl AsyncWrite + Unpin) -> io::Result { + text_to_write(text.0, out).await +} + +/// Serialize this line as error to `out`. +/// +/// This includes a marker to allow decoding it outside of a side-band channel, returning the amount of bytes written. +pub async fn write_error(error: &ErrorRef<'_>, out: impl AsyncWrite + Unpin) -> io::Result { + error_to_write(error.0, out).await +} + +/// Serialize this instance to `out` in git `packetline` format, returning the amount of bytes written to `out`. +pub async fn write_packet_line(line: &PacketLineRef<'_>, out: impl AsyncWrite + Unpin) -> io::Result { + match line { + PacketLineRef::Data(d) => data_to_write(d, out).await, + PacketLineRef::Flush => flush_to_write(out).await, + PacketLineRef::Delimiter => delim_to_write(out).await, + PacketLineRef::ResponseEnd => response_end_to_write(out).await, + } +} diff --git a/gix-packetline/src/encode/blocking_io.rs b/gix-packetline/src/encode/blocking_io.rs index c8cbe592117..3569ec4dfe4 100644 --- a/gix-packetline/src/encode/blocking_io.rs +++ b/gix-packetline/src/encode/blocking_io.rs @@ -1,7 +1,10 @@ use std::io; use super::u16_to_hex; -use crate::{encode::Error, Channel, DELIMITER_LINE, ERR_PREFIX, FLUSH_LINE, MAX_DATA_LEN, RESPONSE_END_LINE}; +use crate::{ + encode::Error, BandRef, Channel, ErrorRef, PacketLineRef, TextRef, DELIMITER_LINE, ERR_PREFIX, FLUSH_LINE, + MAX_DATA_LEN, RESPONSE_END_LINE, +}; /// Write a response-end message to `out`. pub fn response_end_to_write(mut out: impl io::Write) -> io::Result { @@ -23,21 +26,54 @@ pub fn error_to_write(message: &[u8], out: impl io::Write) -> io::Result prefixed_data_to_write(ERR_PREFIX, message, out) } +/// Serialize this line as error to `out`. +/// +/// This includes a marker to allow decoding it outside a side-band channel, returning the amount of bytes written. +pub fn write_error(error: &ErrorRef<'_>, out: impl io::Write) -> io::Result { + error_to_write(error.0, out) +} + /// Write `data` of `kind` to `out` using side-band encoding. pub fn band_to_write(kind: Channel, data: &[u8], out: impl io::Write) -> io::Result { prefixed_data_to_write(&[kind as u8], data, out) } +/// Serialize [`BandRef`] to `out`, returning the amount of bytes written. +/// +/// The data written to `out` can be decoded with [`Borrowed::decode_band()]`. +pub fn write_band(band: &BandRef<'_>, out: impl io::Write) -> io::Result { + match band { + BandRef::Data(d) => band_to_write(Channel::Data, d, out), + BandRef::Progress(d) => band_to_write(Channel::Progress, d, out), + BandRef::Error(d) => band_to_write(Channel::Error, d, out), + } +} + /// Write a `data` message to `out`. pub fn data_to_write(data: &[u8], out: impl io::Write) -> io::Result { prefixed_data_to_write(&[], data, out) } +/// Serialize this instance to `out` in git `packetline` format, returning the amount of bytes written to `out`. +pub fn write_packet_line(line: &PacketLineRef<'_>, out: impl io::Write) -> io::Result { + match line { + PacketLineRef::Data(d) => data_to_write(d, out), + PacketLineRef::Flush => flush_to_write(out), + PacketLineRef::Delimiter => delim_to_write(out), + PacketLineRef::ResponseEnd => response_end_to_write(out), + } +} + /// Write a `text` message to `out`, which is assured to end in a newline. pub fn text_to_write(text: &[u8], out: impl io::Write) -> io::Result { prefixed_and_suffixed_data_to_write(&[], text, b"\n", out) } +/// Serialize this instance to `out`, appending a newline if there is none, returning the amount of bytes written. +pub fn write_text(text: &TextRef<'_>, out: impl io::Write) -> io::Result { + text_to_write(text.0, out) +} + fn prefixed_data_to_write(prefix: &[u8], data: &[u8], out: impl io::Write) -> io::Result { prefixed_and_suffixed_data_to_write(prefix, data, &[], out) } diff --git a/gix-packetline/src/encode/mod.rs b/gix-packetline/src/encode/mod.rs index 302c6d37096..d5ccd430233 100644 --- a/gix-packetline/src/encode/mod.rs +++ b/gix-packetline/src/encode/mod.rs @@ -10,15 +10,13 @@ pub enum Error { DataIsEmpty, } -#[cfg(all(not(feature = "blocking-io"), feature = "async-io"))] -mod async_io; -#[cfg(all(not(feature = "blocking-io"), feature = "async-io"))] -pub use async_io::*; +/// Async IO support +#[cfg(feature = "async-io")] +pub mod async_io; +/// Blocking IO support #[cfg(feature = "blocking-io")] -mod blocking_io; -#[cfg(feature = "blocking-io")] -pub use blocking_io::*; +pub mod blocking_io; pub(crate) fn u16_to_hex(value: u16) -> [u8; 4] { let mut buf = [0u8; 4]; diff --git a/gix-packetline/src/lib.rs b/gix-packetline/src/lib.rs index 95ecd59d25c..0447a8b7dcc 100644 --- a/gix-packetline/src/lib.rs +++ b/gix-packetline/src/lib.rs @@ -1,6 +1,5 @@ //! Read and write the git packet line wire format without copying it. //! -//! For reading the packet line format use the [`StreamingPeekableIter`], and for writing the [`Writer`]. //! ## Feature Flags #![cfg_attr( all(doc, all(doc, feature = "document-features")), @@ -32,14 +31,8 @@ pub enum Channel { mod line; /// pub mod read; - /// -#[cfg(any(feature = "async-io", feature = "blocking-io"))] -mod write; -#[cfg(all(not(feature = "blocking-io"), feature = "async-io"))] -pub use write::async_io::Writer; -#[cfg(feature = "blocking-io")] -pub use write::blocking_io::Writer; +pub mod write; /// A borrowed packet line as it refers to a slice of data by reference. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] @@ -77,12 +70,8 @@ pub enum BandRef<'a> { Error(&'a [u8]), } -/// Read pack lines one after another, without consuming more than needed from the underlying -/// [`Read`][std::io::Read]. [`Flush`][PacketLineRef::Flush] lines cause the reader to stop producing lines forever, -/// leaving [`Read`][std::io::Read] at the start of whatever comes next. -/// -/// This implementation tries hard not to allocate at all which leads to quite some added complexity and plenty of extra memory copies. -pub struct StreamingPeekableIter { +/// State for `StreamingPeekableIter` implementations. +pub struct StreamingPeekableIterState { read: T, peek_buf: Vec, #[cfg(any(feature = "blocking-io", feature = "async-io"))] @@ -101,6 +90,3 @@ pub mod decode; pub use decode::all_at_once as decode; /// Utilities to encode different kinds of packet lines pub mod encode; - -#[cfg(all(feature = "async-io", feature = "blocking-io"))] -compile_error!("Cannot set both 'blocking-io' and 'async-io' features as they are mutually exclusive"); diff --git a/gix-packetline/src/line/async_io.rs b/gix-packetline/src/line/async_io.rs deleted file mode 100644 index c2526326bd5..00000000000 --- a/gix-packetline/src/line/async_io.rs +++ /dev/null @@ -1,47 +0,0 @@ -use std::io; - -use futures_io::AsyncWrite; - -use crate::{encode, BandRef, Channel, ErrorRef, PacketLineRef, TextRef}; - -impl BandRef<'_> { - /// Serialize this instance to `out`, returning the amount of bytes written. - /// - /// The data written to `out` can be decoded with [`Borrowed::decode_band()]`. - pub async fn write_to(&self, out: impl AsyncWrite + Unpin) -> io::Result { - match self { - BandRef::Data(d) => encode::band_to_write(Channel::Data, d, out), - BandRef::Progress(d) => encode::band_to_write(Channel::Progress, d, out), - BandRef::Error(d) => encode::band_to_write(Channel::Error, d, out), - } - .await - } -} - -impl TextRef<'_> { - /// Serialize this instance to `out`, appending a newline if there is none, returning the amount of bytes written. - pub async fn write_to(&self, out: impl AsyncWrite + Unpin) -> io::Result { - encode::text_to_write(self.0, out).await - } -} - -impl ErrorRef<'_> { - /// Serialize this line as error to `out`. - /// - /// This includes a marker to allow decoding it outside of a side-band channel, returning the amount of bytes written. - pub async fn write_to(&self, out: impl AsyncWrite + Unpin) -> io::Result { - encode::error_to_write(self.0, out).await - } -} - -impl PacketLineRef<'_> { - /// Serialize this instance to `out` in git `packetline` format, returning the amount of bytes written to `out`. - pub async fn write_to(&self, out: impl AsyncWrite + Unpin) -> io::Result { - match self { - PacketLineRef::Data(d) => encode::data_to_write(d, out).await, - PacketLineRef::Flush => encode::flush_to_write(out).await, - PacketLineRef::Delimiter => encode::delim_to_write(out).await, - PacketLineRef::ResponseEnd => encode::response_end_to_write(out).await, - } - } -} diff --git a/gix-packetline/src/line/blocking_io.rs b/gix-packetline/src/line/blocking_io.rs deleted file mode 100644 index fa57d5ad797..00000000000 --- a/gix-packetline/src/line/blocking_io.rs +++ /dev/null @@ -1,44 +0,0 @@ -use std::io; - -use crate::{encode, BandRef, Channel, ErrorRef, PacketLineRef, TextRef}; - -impl BandRef<'_> { - /// Serialize this instance to `out`, returning the amount of bytes written. - /// - /// The data written to `out` can be decoded with [`Borrowed::decode_band()]`. - pub fn write_to(&self, out: impl io::Write) -> io::Result { - match self { - BandRef::Data(d) => encode::band_to_write(Channel::Data, d, out), - BandRef::Progress(d) => encode::band_to_write(Channel::Progress, d, out), - BandRef::Error(d) => encode::band_to_write(Channel::Error, d, out), - } - } -} - -impl TextRef<'_> { - /// Serialize this instance to `out`, appending a newline if there is none, returning the amount of bytes written. - pub fn write_to(&self, out: impl io::Write) -> io::Result { - encode::text_to_write(self.0, out) - } -} - -impl ErrorRef<'_> { - /// Serialize this line as error to `out`. - /// - /// This includes a marker to allow decoding it outside a side-band channel, returning the amount of bytes written. - pub fn write_to(&self, out: impl io::Write) -> io::Result { - encode::error_to_write(self.0, out) - } -} - -impl PacketLineRef<'_> { - /// Serialize this instance to `out` in git `packetline` format, returning the amount of bytes written to `out`. - pub fn write_to(&self, out: impl io::Write) -> io::Result { - match self { - PacketLineRef::Data(d) => encode::data_to_write(d, out), - PacketLineRef::Flush => encode::flush_to_write(out), - PacketLineRef::Delimiter => encode::delim_to_write(out), - PacketLineRef::ResponseEnd => encode::response_end_to_write(out), - } - } -} diff --git a/gix-packetline/src/line/mod.rs b/gix-packetline/src/line/mod.rs index 538630ecc7a..257394ee1eb 100644 --- a/gix-packetline/src/line/mod.rs +++ b/gix-packetline/src/line/mod.rs @@ -18,7 +18,7 @@ impl<'a> PacketLineRef<'a> { /// /// This works for any data received in an error [channel][crate::Channel]. /// - /// Note that this creates an unchecked error using the slice verbatim, which is useful to [serialize it][ErrorRef::write_to()]. + /// Note that this creates an unchecked error using the slice verbatim, which is useful to serialize it. /// See [`check_error()`][PacketLineRef::check_error()] for a version that assures the error information is in the expected format. pub fn as_error(&self) -> Option> { self.as_slice().map(ErrorRef) @@ -81,8 +81,3 @@ impl<'a> TextRef<'a> { self.0.into() } } - -#[cfg(all(not(feature = "blocking-io"), feature = "async-io"))] -mod async_io; -#[cfg(feature = "blocking-io")] -mod blocking_io; diff --git a/gix-packetline/src/read/async_io.rs b/gix-packetline/src/read/async_io.rs index bfe780738c1..fd9000d94a5 100644 --- a/gix-packetline/src/read/async_io.rs +++ b/gix-packetline/src/read/async_io.rs @@ -1,20 +1,41 @@ -use std::io; +use std::{ + io, + ops::{Deref, DerefMut}, +}; use bstr::ByteSlice; use futures_io::AsyncRead; use futures_lite::AsyncReadExt; +pub use super::sidebands::async_io::WithSidebands; use crate::{ decode, - read::{ExhaustiveOutcome, ProgressAction, WithSidebands}, - PacketLineRef, StreamingPeekableIter, MAX_LINE_LEN, U16_HEX_BYTES, + read::{ExhaustiveOutcome, ProgressAction}, + PacketLineRef, StreamingPeekableIterState, MAX_LINE_LEN, U16_HEX_BYTES, }; +/// Read pack lines one after another, without consuming more than needed from the underlying +/// [`AsyncRead`]. [`Flush`][PacketLineRef::Flush] lines cause the reader to stop producing lines forever, +/// leaving [`AsyncRead`] at the start of whatever comes next. +/// +/// This implementation tries hard not to allocate at all which leads to quite some added complexity and plenty of extra memory copies. +pub struct StreamingPeekableIter { + pub(super) state: StreamingPeekableIterState, +} + /// Non-IO methods impl StreamingPeekableIter where T: AsyncRead + Unpin, { + /// Return a new instance from `read` which will stop decoding packet lines when receiving one of the given `delimiters`. + /// If `trace` is `true`, all packetlines received or sent will be passed to the facilities of the `gix-trace` crate. + pub fn new(read: T, delimiters: &'static [PacketLineRef<'static>], trace: bool) -> Self { + Self { + state: StreamingPeekableIterState::new(read, delimiters, trace), + } + } + #[allow(clippy::needless_lifetimes)] // TODO: remove once this is clippy false positive is fixed async fn read_line_inner<'a>( reader: &mut T, @@ -109,28 +130,30 @@ where /// * ERR packet line encountered if [`fail_on_err_lines()`][StreamingPeekableIter::fail_on_err_lines()] is true. /// * A `delimiter` packet line encountered pub async fn read_line(&mut self) -> Option, decode::Error>>> { - if self.is_done { + if self.state.is_done { return None; } - if !self.peek_buf.is_empty() { - std::mem::swap(&mut self.peek_buf, &mut self.buf); - self.peek_buf.clear(); - Some(Ok(Ok(crate::decode(&self.buf).expect("only valid data in peek buf")))) + if !self.state.peek_buf.is_empty() { + std::mem::swap(&mut self.state.peek_buf, &mut self.state.buf); + self.state.peek_buf.clear(); + Some(Ok(Ok( + crate::decode(&self.state.buf).expect("only valid data in peek buf") + ))) } else { - if self.buf.len() != MAX_LINE_LEN { - self.buf.resize(MAX_LINE_LEN, 0); + if self.state.buf.len() != MAX_LINE_LEN { + self.state.buf.resize(MAX_LINE_LEN, 0); } let (is_done, stopped_at, res) = Self::read_line_inner_exhaustive( - &mut self.read, - &mut self.buf, - self.delimiters, - self.fail_on_err_lines, + &mut self.state.read, + &mut self.state.buf, + self.state.delimiters, + self.state.fail_on_err_lines, false, - self.trace, + self.state.trace, ) .await; - self.is_done = is_done; - self.stopped_at = stopped_at; + self.state.is_done = is_done; + self.state.stopped_at = stopped_at; res } } @@ -140,25 +163,27 @@ where /// /// Multiple calls to peek will return the same packet line, if there is one. pub async fn peek_line(&mut self) -> Option, decode::Error>>> { - if self.is_done { + if self.state.is_done { return None; } - if self.peek_buf.is_empty() { - self.peek_buf.resize(MAX_LINE_LEN, 0); + if self.state.peek_buf.is_empty() { + self.state.peek_buf.resize(MAX_LINE_LEN, 0); let (is_done, stopped_at, res) = Self::read_line_inner_exhaustive( - &mut self.read, - &mut self.peek_buf, - self.delimiters, - self.fail_on_err_lines, + &mut self.state.read, + &mut self.state.peek_buf, + self.state.delimiters, + self.state.fail_on_err_lines, true, - self.trace, + self.state.trace, ) .await; - self.is_done = is_done; - self.stopped_at = stopped_at; + self.state.is_done = is_done; + self.state.stopped_at = stopped_at; res } else { - Some(Ok(Ok(crate::decode(&self.peek_buf).expect("only valid data here")))) + Some(Ok(Ok( + crate::decode(&self.state.peek_buf).expect("only valid data here") + ))) } } @@ -194,3 +219,23 @@ where WithSidebands::without_progress_handler(self) } } + +impl StreamingPeekableIter { + /// Return the inner read + pub fn into_inner(self) -> T { + self.state.read + } +} + +impl Deref for StreamingPeekableIter { + type Target = StreamingPeekableIterState; + fn deref(&self) -> &Self::Target { + &self.state + } +} + +impl DerefMut for StreamingPeekableIter { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.state + } +} diff --git a/gix-packetline/src/read/blocking_io.rs b/gix-packetline/src/read/blocking_io.rs index 351f2cb46ea..ec14c81ef0c 100644 --- a/gix-packetline/src/read/blocking_io.rs +++ b/gix-packetline/src/read/blocking_io.rs @@ -1,18 +1,39 @@ -use std::io; +use std::{ + io, + ops::{Deref, DerefMut}, +}; use bstr::ByteSlice; +pub use super::sidebands::blocking_io::WithSidebands; use crate::{ decode, - read::{ExhaustiveOutcome, ProgressAction, WithSidebands}, - PacketLineRef, StreamingPeekableIter, MAX_LINE_LEN, U16_HEX_BYTES, + read::{ExhaustiveOutcome, ProgressAction}, + PacketLineRef, StreamingPeekableIterState, MAX_LINE_LEN, U16_HEX_BYTES, }; +/// Read pack lines one after another, without consuming more than needed from the underlying +/// [`Read`][std::io::Read]. [`Flush`][PacketLineRef::Flush] lines cause the reader to stop producing lines forever, +/// leaving [`Read`][std::io::Read] at the start of whatever comes next. +/// +/// This implementation tries hard not to allocate at all which leads to quite some added complexity and plenty of extra memory copies. +pub struct StreamingPeekableIter { + pub(super) state: StreamingPeekableIterState, +} + /// Non-IO methods impl StreamingPeekableIter where T: io::Read, { + /// Return a new instance from `read` which will stop decoding packet lines when receiving one of the given `delimiters`. + /// If `trace` is `true`, all packetlines received or sent will be passed to the facilities of the `gix-trace` crate. + pub fn new(read: T, delimiters: &'static [PacketLineRef<'static>], trace: bool) -> Self { + Self { + state: StreamingPeekableIterState::new(read, delimiters, trace), + } + } + fn read_line_inner<'a>(reader: &mut T, buf: &'a mut [u8]) -> io::Result, decode::Error>> { let (hex_bytes, data_bytes) = buf.split_at_mut(4); reader.read_exact(hex_bytes)?; @@ -101,30 +122,32 @@ where /// Returns `None` if the end of iteration is reached because of one of the following: /// /// * natural EOF - /// * ERR packet line encountered if [`fail_on_err_lines()`][StreamingPeekableIter::fail_on_err_lines()] is true. + /// * ERR packet line encountered if [`fail_on_err_lines()`][StreamingPeekableIterState::fail_on_err_lines()] is true. /// * A `delimiter` packet line encountered pub fn read_line(&mut self) -> Option, decode::Error>>> { - if self.is_done { + if self.state.is_done { return None; } - if !self.peek_buf.is_empty() { - std::mem::swap(&mut self.peek_buf, &mut self.buf); - self.peek_buf.clear(); - Some(Ok(Ok(crate::decode(&self.buf).expect("only valid data in peek buf")))) + if !self.state.peek_buf.is_empty() { + std::mem::swap(&mut self.state.peek_buf, &mut self.state.buf); + self.state.peek_buf.clear(); + Some(Ok(Ok( + crate::decode(&self.state.buf).expect("only valid data in peek buf") + ))) } else { - if self.buf.len() != MAX_LINE_LEN { - self.buf.resize(MAX_LINE_LEN, 0); + if self.state.buf.len() != MAX_LINE_LEN { + self.state.buf.resize(MAX_LINE_LEN, 0); } let (is_done, stopped_at, res) = Self::read_line_inner_exhaustive( - &mut self.read, - &mut self.buf, - self.delimiters, - self.fail_on_err_lines, + &mut self.state.read, + &mut self.state.buf, + self.state.delimiters, + self.state.fail_on_err_lines, false, - self.trace, + self.state.trace, ); - self.is_done = is_done; - self.stopped_at = stopped_at; + self.state.is_done = is_done; + self.state.stopped_at = stopped_at; res } } @@ -134,24 +157,26 @@ where /// /// Multiple calls to peek will return the same packet line, if there is one. pub fn peek_line(&mut self) -> Option, decode::Error>>> { - if self.is_done { + if self.state.is_done { return None; } - if self.peek_buf.is_empty() { - self.peek_buf.resize(MAX_LINE_LEN, 0); + if self.state.peek_buf.is_empty() { + self.state.peek_buf.resize(MAX_LINE_LEN, 0); let (is_done, stopped_at, res) = Self::read_line_inner_exhaustive( - &mut self.read, - &mut self.peek_buf, - self.delimiters, - self.fail_on_err_lines, + &mut self.state.read, + &mut self.state.peek_buf, + self.state.delimiters, + self.state.fail_on_err_lines, true, - self.trace, + self.state.trace, ); - self.is_done = is_done; - self.stopped_at = stopped_at; + self.state.is_done = is_done; + self.state.stopped_at = stopped_at; res } else { - Some(Ok(Ok(crate::decode(&self.peek_buf).expect("only valid data here")))) + Some(Ok(Ok( + crate::decode(&self.state.peek_buf).expect("only valid data here") + ))) } } @@ -185,3 +210,23 @@ where WithSidebands::new(self) } } + +impl StreamingPeekableIter { + /// Return the inner read + pub fn into_inner(self) -> T { + self.state.read + } +} + +impl Deref for StreamingPeekableIter { + type Target = StreamingPeekableIterState; + fn deref(&self) -> &Self::Target { + &self.state + } +} + +impl DerefMut for StreamingPeekableIter { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.state + } +} diff --git a/gix-packetline/src/read/mod.rs b/gix-packetline/src/read/mod.rs index c9d01c1ab9c..d4ea261037a 100644 --- a/gix-packetline/src/read/mod.rs +++ b/gix-packetline/src/read/mod.rs @@ -1,6 +1,6 @@ #[cfg(any(feature = "blocking-io", feature = "async-io"))] use crate::MAX_LINE_LEN; -use crate::{PacketLineRef, StreamingPeekableIter, U16_HEX_BYTES}; +use crate::{PacketLineRef, StreamingPeekableIterState, U16_HEX_BYTES}; /// Allow the read-progress handler to determine how to continue. #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -23,8 +23,7 @@ mod error { use bstr::BString; - /// The error representing an ERR packet line, as possibly wrapped into an `std::io::Error` in - /// [`read_line(…)`][super::StreamingPeekableIter::read_line()]. + /// The error representing an ERR packet line, as possibly wrapped into an `std::io::Error`. #[derive(Debug)] pub struct Error { /// The contents of the ERR line, with `ERR` portion stripped. @@ -41,11 +40,12 @@ mod error { } pub use error::Error; -impl StreamingPeekableIter { +impl StreamingPeekableIterState { /// Return a new instance from `read` which will stop decoding packet lines when receiving one of the given `delimiters`. /// If `trace` is `true`, all packetlines received or sent will be passed to the facilities of the `gix-trace` crate. - pub fn new(read: T, delimiters: &'static [PacketLineRef<'static>], trace: bool) -> Self { - StreamingPeekableIter { + #[cfg(any(feature = "blocking-io", feature = "async-io"))] + fn new(read: T, delimiters: &'static [PacketLineRef<'static>], trace: bool) -> Self { + Self { read, #[cfg(any(feature = "blocking-io", feature = "async-io"))] buf: vec![0; MAX_LINE_LEN], @@ -75,7 +75,7 @@ impl StreamingPeekableIter { } /// Returns the packet line that stopped the iteration, or - /// `None` if the end wasn't reached yet, on EOF, or if [`fail_on_err_lines()`][StreamingPeekableIter::fail_on_err_lines()] was true. + /// `None` if the end wasn't reached yet, on EOF, or if [`fail_on_err_lines()`][StreamingPeekableIterState::fail_on_err_lines()] was true. pub fn stopped_at(&self) -> Option> { self.stopped_at } @@ -88,7 +88,7 @@ impl StreamingPeekableIter { self.reset_with(delimiters); } - /// Similar to [`reset()`][StreamingPeekableIter::reset()] with support to changing the `delimiters`. + /// Similar to [`reset()`][StreamingPeekableIterState::reset()] with support to changing the `delimiters`. pub fn reset_with(&mut self, delimiters: &'static [PacketLineRef<'static>]) { self.delimiters = delimiters; self.is_done = false; @@ -97,7 +97,7 @@ impl StreamingPeekableIter { /// If `value` is `true` the provider will check for special `ERR` packet lines and stop iteration when one is encountered. /// - /// Use [`stopped_at()]`[`StreamingPeekableIter::stopped_at()`] to inspect the cause of the end of the iteration. + /// Use [`stopped_at()]`[`StreamingPeekableIterState::stopped_at()`] to inspect the cause of the end of the iteration. /// ne pub fn fail_on_err_lines(&mut self, value: bool) { self.fail_on_err_lines = value; @@ -110,19 +110,14 @@ impl StreamingPeekableIter { self.fail_on_err_lines = false; prev } - - /// Return the inner read - pub fn into_inner(self) -> T { - self.read - } } +/// Blocking IO support #[cfg(feature = "blocking-io")] -mod blocking_io; +pub mod blocking_io; -#[cfg(all(not(feature = "blocking-io"), feature = "async-io"))] -mod async_io; +/// Async IO support +#[cfg(feature = "async-io")] +pub mod async_io; mod sidebands; -#[cfg(any(feature = "blocking-io", feature = "async-io"))] -pub use sidebands::WithSidebands; diff --git a/gix-packetline/src/read/sidebands/async_io.rs b/gix-packetline/src/read/sidebands/async_io.rs index f43d647321e..854f77f6b74 100644 --- a/gix-packetline/src/read/sidebands/async_io.rs +++ b/gix-packetline/src/read/sidebands/async_io.rs @@ -6,7 +6,8 @@ use std::{ use futures_io::{AsyncBufRead, AsyncRead}; -use crate::{decode, read::ProgressAction, BandRef, PacketLineRef, StreamingPeekableIter, TextRef, U16_HEX_BYTES}; +use crate::read::async_io::StreamingPeekableIter; +use crate::{decode, read::ProgressAction, BandRef, PacketLineRef, TextRef, U16_HEX_BYTES}; type ReadLineResult<'a> = Option, decode::Error>>>; /// An implementor of [`AsyncBufRead`] yielding packet lines on each call to [`read_line()`][AsyncBufRead::read_line()]. @@ -31,6 +32,7 @@ where parent .as_mut() .expect("parent is always available if we are idle") + .state .reset(); } } @@ -105,6 +107,7 @@ where parent .as_mut() .expect("parent is always available if we are idle") + .state .reset_with(delimiters); } } @@ -116,6 +119,7 @@ where parent .as_ref() .expect("parent is always available if we are idle") + .state .stopped_at } _ => None, @@ -327,7 +331,9 @@ where } let range = self.pos..self.cap; match &self.get_mut().state { - State::Idle { parent } => Poll::Ready(Ok(&parent.as_ref().expect("parent always available").buf[range])), + State::Idle { parent } => { + Poll::Ready(Ok(&parent.as_ref().expect("parent always available").state.buf[range])) + } State::ReadLine { .. } => unreachable!("at least in theory"), } } @@ -360,12 +366,12 @@ mod tests { /// We want to declare items containing pointers of `StreamingPeekableIter` `Send` as well, so it must be `Send` itself. #[test] fn streaming_peekable_iter_is_send() { - receiver(StreamingPeekableIter::new(Vec::::new(), &[], false)); + receiver(StreamingPeekableIter::new(&[][..], &[], false)); } #[test] fn state_is_send() { - let mut s = StreamingPeekableIter::new(Vec::::new(), &[], false); + let mut s = StreamingPeekableIter::new(&[][..], &[], false); receiver(State::Idle { parent: Some(&mut s) }); } } diff --git a/gix-packetline/src/read/sidebands/blocking_io.rs b/gix-packetline/src/read/sidebands/blocking_io.rs index 740344411f7..7a6fd6c71da 100644 --- a/gix-packetline/src/read/sidebands/blocking_io.rs +++ b/gix-packetline/src/read/sidebands/blocking_io.rs @@ -1,6 +1,9 @@ use std::{io, io::BufRead}; -use crate::{read::ProgressAction, BandRef, PacketLineRef, StreamingPeekableIter, TextRef, U16_HEX_BYTES}; +use crate::{ + read::{blocking_io::StreamingPeekableIter, ProgressAction}, + BandRef, PacketLineRef, TextRef, U16_HEX_BYTES, +}; /// An implementor of [`BufRead`][io::BufRead] yielding packet lines on each call to [`read_line()`][io::BufRead::read_line()]. /// It's also possible to hide the underlying packet lines using the [`Read`][io::Read] implementation which is useful @@ -20,7 +23,7 @@ where T: io::Read, { fn drop(&mut self) { - self.parent.reset(); + self.parent.state.reset(); } } @@ -67,14 +70,14 @@ where } } - /// Forwards to the parent [`StreamingPeekableIter::reset_with()`] + /// Forwards to the parent [`crate::read::StreamingPeekableIterState::reset_with()`] pub fn reset_with(&mut self, delimiters: &'static [PacketLineRef<'static>]) { - self.parent.reset_with(delimiters); + self.parent.state.reset_with(delimiters); } - /// Forwards to the parent [`StreamingPeekableIter::stopped_at()`] + /// Forwards to the parent [`StreamingPeekableIterState::stopped_at()`][crate::read::StreamingPeekableIterState::stopped_at()] pub fn stopped_at(&self) -> Option> { - self.parent.stopped_at + self.parent.state.stopped_at } /// Set or unset the progress handler. @@ -186,7 +189,7 @@ where self.cap = cap + ofs; self.pos = ofs; } - Ok(&self.parent.buf[self.pos..self.cap]) + Ok(&self.parent.state.buf[self.pos..self.cap]) } fn consume(&mut self, amt: usize) { diff --git a/gix-packetline/src/read/sidebands/mod.rs b/gix-packetline/src/read/sidebands/mod.rs index a1b6628e147..6e1081b6ac4 100644 --- a/gix-packetline/src/read/sidebands/mod.rs +++ b/gix-packetline/src/read/sidebands/mod.rs @@ -1,9 +1,5 @@ #[cfg(feature = "blocking-io")] -mod blocking_io; -#[cfg(feature = "blocking-io")] -pub use blocking_io::WithSidebands; +pub(super) mod blocking_io; -#[cfg(all(not(feature = "blocking-io"), feature = "async-io"))] -mod async_io; -#[cfg(all(not(feature = "blocking-io"), feature = "async-io"))] -pub use async_io::WithSidebands; +#[cfg(feature = "async-io")] +pub(super) mod async_io; diff --git a/gix-packetline/src/write/async_io.rs b/gix-packetline/src/write/async_io.rs index 7a41211658e..e9aeb4c826b 100644 --- a/gix-packetline/src/write/async_io.rs +++ b/gix-packetline/src/write/async_io.rs @@ -13,7 +13,7 @@ pin_project_lite::pin_project! { /// one line per `write(…)` call or as many lines as it takes if the data doesn't fit into the maximum allowed line length. pub struct Writer { #[pin] - inner: encode::LineWriter<'static, T>, + inner: encode::async_io::LineWriter<'static, T>, state: State, } } @@ -27,7 +27,7 @@ impl Writer { /// Create a new instance from the given `write` pub fn new(write: T) -> Self { Writer { - inner: encode::LineWriter::new(write, &[], &[]), + inner: encode::async_io::LineWriter::new(write, &[], &[]), state: State::Idle, } } diff --git a/gix-packetline/src/write/blocking_io.rs b/gix-packetline/src/write/blocking_io.rs index 96139feb2a6..387f003f828 100644 --- a/gix-packetline/src/write/blocking_io.rs +++ b/gix-packetline/src/write/blocking_io.rs @@ -53,9 +53,9 @@ impl io::Write for Writer { while !buf.is_empty() { let (data, rest) = buf.split_at(buf.len().min(MAX_DATA_LEN)); written += if self.binary { - crate::encode::data_to_write(data, &mut self.inner) + crate::encode::blocking_io::data_to_write(data, &mut self.inner) } else { - crate::encode::text_to_write(data, &mut self.inner) + crate::encode::blocking_io::text_to_write(data, &mut self.inner) }?; // subtract header (and trailing NL) because write-all can't handle writing more than it passes in written -= U16_HEX_BYTES + usize::from(!self.binary); diff --git a/gix-packetline/src/write/mod.rs b/gix-packetline/src/write/mod.rs index f40a6bdaea3..fee164944c8 100644 --- a/gix-packetline/src/write/mod.rs +++ b/gix-packetline/src/write/mod.rs @@ -1,21 +1,7 @@ -use crate::Writer; - -#[cfg(all(not(feature = "blocking-io"), feature = "async-io"))] -pub(crate) mod async_io; +/// Support for async packet line writing. +#[cfg(feature = "async-io")] +pub mod async_io; +/// Support for blocking packet line writing. #[cfg(feature = "blocking-io")] -pub(crate) mod blocking_io; - -/// Common methods -impl Writer { - /// As [`enable_text_mode()`][Writer::enable_text_mode()], but suitable for chaining. - pub fn text_mode(mut self) -> Self { - self.enable_text_mode(); - self - } - /// As [`enable_binary_mode()`][Writer::enable_binary_mode()], but suitable for chaining. - pub fn binary_mode(mut self) -> Self { - self.enable_binary_mode(); - self - } -} +pub mod blocking_io; diff --git a/gix-packetline/tests/decode/mod.rs b/gix-packetline/tests/decode/mod.rs index ec15d7a638a..872f96ffbd8 100644 --- a/gix-packetline/tests/decode/mod.rs +++ b/gix-packetline/tests/decode/mod.rs @@ -26,15 +26,17 @@ mod streaming { use gix_packetline::{decode, decode::streaming, Channel, PacketLineRef}; use crate::decode::streaming::assert_complete; + #[cfg(all(feature = "async-io", not(feature = "blocking-io")))] + use gix_packetline::encode::async_io as encode_io; + #[cfg(all(feature = "blocking-io", not(feature = "async-io")))] + use gix_packetline::encode::blocking_io as encode_io; #[maybe_async::test(feature = "blocking-io", async(feature = "async-io", async_std::test))] async fn trailing_line_feeds_are_removed_explicitly() -> crate::Result { let line = decode::all_at_once(b"0006a\n")?; assert_eq!(line.as_text().expect("text").0.as_bstr(), b"a".as_bstr()); let mut out = Vec::new(); - line.as_text() - .expect("text") - .write_to(&mut out) + encode_io::write_text(&line.as_text().expect("text"), &mut out) .await .expect("write to memory works"); assert_eq!(out, b"0006a\n", "it appends a newline in text mode"); @@ -50,7 +52,7 @@ mod streaming { (PacketLineRef::Data(b"hello there"), 15), ] { let mut out = Vec::new(); - line.write_to(&mut out).await?; + encode_io::write_packet_line(line, &mut out).await?; assert_complete(streaming(&out), *bytes, *line)?; } Ok(()) @@ -59,11 +61,11 @@ mod streaming { #[maybe_async::test(feature = "blocking-io", async(feature = "async-io", async_std::test))] async fn error_line() -> crate::Result { let mut out = Vec::new(); - PacketLineRef::Data(b"the error") - .as_error() - .expect("data line") - .write_to(&mut out) - .await?; + encode_io::write_error( + &PacketLineRef::Data(b"the error").as_error().expect("data line"), + &mut out, + ) + .await?; let line = decode::all_at_once(&out)?; assert_eq!(line.check_error().expect("err").0, b"the error"); Ok(()) @@ -76,7 +78,7 @@ mod streaming { let band = PacketLineRef::Data(b"band data") .as_band(*channel) .expect("data is valid for band"); - band.write_to(&mut out).await?; + encode_io::write_band(&band, &mut out).await?; let line = decode::all_at_once(&out)?; assert_eq!(line.decode_band().expect("valid band"), band); } diff --git a/gix-packetline/tests/encode/mod.rs b/gix-packetline/tests/encode/mod.rs index aed25fc49f1..5857db32e6b 100644 --- a/gix-packetline/tests/encode/mod.rs +++ b/gix-packetline/tests/encode/mod.rs @@ -5,9 +5,12 @@ mod data_to_write { use bstr::ByteSlice; #[cfg(all(feature = "async-io", not(feature = "blocking-io")))] use futures_lite::io; - use gix_packetline::encode::data_to_write; use crate::assert_err_display; + #[cfg(all(feature = "async-io", not(feature = "blocking-io")))] + use gix_packetline::encode::async_io::data_to_write; + #[cfg(all(feature = "blocking-io", not(feature = "async-io")))] + use gix_packetline::encode::blocking_io::data_to_write; #[maybe_async::test(feature = "blocking-io", async(feature = "async-io", async_std::test))] async fn binary_and_non_binary() -> crate::Result { @@ -42,7 +45,10 @@ mod data_to_write { mod text_to_write { use bstr::ByteSlice; - use gix_packetline::encode::text_to_write; + #[cfg(all(feature = "async-io", not(feature = "blocking-io")))] + use gix_packetline::encode::async_io::text_to_write; + #[cfg(all(feature = "blocking-io", not(feature = "async-io")))] + use gix_packetline::encode::blocking_io::text_to_write; #[maybe_async::test(feature = "blocking-io", async(feature = "async-io", async_std::test))] async fn always_appends_a_newline() -> crate::Result { @@ -65,7 +71,10 @@ mod text_to_write { mod error { use bstr::ByteSlice; - use gix_packetline::encode::error_to_write; + #[cfg(all(feature = "async-io", not(feature = "blocking-io")))] + use gix_packetline::encode::async_io::error_to_write; + #[cfg(all(feature = "blocking-io", not(feature = "async-io")))] + use gix_packetline::encode::blocking_io::error_to_write; #[maybe_async::test(feature = "blocking-io", async(feature = "async-io", async_std::test))] async fn write_line() -> crate::Result { @@ -79,7 +88,10 @@ mod error { mod flush_delim_response_end { use bstr::ByteSlice; - use gix_packetline::encode::{delim_to_write, flush_to_write, response_end_to_write}; + #[cfg(all(feature = "async-io", not(feature = "blocking-io")))] + use gix_packetline::encode::async_io::{delim_to_write, flush_to_write, response_end_to_write}; + #[cfg(all(feature = "blocking-io", not(feature = "async-io")))] + use gix_packetline::encode::blocking_io::{delim_to_write, flush_to_write, response_end_to_write}; #[maybe_async::test(feature = "blocking-io", async(feature = "async-io", async_std::test))] async fn success_flush_delim_response_end() -> crate::Result { diff --git a/gix-packetline/tests/read/mod.rs b/gix-packetline/tests/read/mod.rs index 48c6d075095..b050c1fd3f7 100644 --- a/gix-packetline/tests/read/mod.rs +++ b/gix-packetline/tests/read/mod.rs @@ -4,6 +4,10 @@ pub mod streaming_peek_iter { use std::{io, path::PathBuf}; use bstr::ByteSlice; + #[cfg(all(feature = "async-io", not(feature = "blocking-io")))] + use gix_packetline::read::async_io::StreamingPeekableIter; + #[cfg(all(feature = "blocking-io", not(feature = "async-io")))] + use gix_packetline::read::blocking_io::StreamingPeekableIter; use gix_packetline::PacketLineRef; fn fixture_path(path: &str) -> PathBuf { @@ -20,7 +24,7 @@ pub mod streaming_peek_iter { #[maybe_async::test(feature = "blocking-io", async(feature = "async-io", async_std::test))] async fn peek_follows_read_line_delimiter_logic() -> crate::Result { - let mut rd = gix_packetline::StreamingPeekableIter::new(&b"0005a00000005b"[..], &[PacketLineRef::Flush], false); + let mut rd = StreamingPeekableIter::new(&b"0005a00000005b"[..], &[PacketLineRef::Flush], false); let res = rd.peek_line().await; assert_eq!(res.expect("line")??, PacketLineRef::Data(b"a")); rd.read_line().await; @@ -46,8 +50,7 @@ pub mod streaming_peek_iter { #[maybe_async::test(feature = "blocking-io", async(feature = "async-io", async_std::test))] async fn peek_follows_read_line_err_logic() -> crate::Result { - let mut rd = - gix_packetline::StreamingPeekableIter::new(&b"0005a0009ERR e0000"[..], &[PacketLineRef::Flush], false); + let mut rd = StreamingPeekableIter::new(&b"0005a0009ERR e0000"[..], &[PacketLineRef::Flush], false); rd.fail_on_err_lines(true); let res = rd.peek_line().await; assert_eq!(res.expect("line")??, PacketLineRef::Data(b"a")); @@ -74,8 +77,7 @@ pub mod streaming_peek_iter { #[maybe_async::test(feature = "blocking-io", async(feature = "async-io", async_std::test))] async fn peek_eof_is_none() -> crate::Result { - let mut rd = - gix_packetline::StreamingPeekableIter::new(&b"0005a0009ERR e0000"[..], &[PacketLineRef::Flush], false); + let mut rd = StreamingPeekableIter::new(&b"0005a0009ERR e0000"[..], &[PacketLineRef::Flush], false); rd.fail_on_err_lines(false); let res = rd.peek_line().await; assert_eq!(res.expect("line")??, PacketLineRef::Data(b"a")); @@ -96,8 +98,7 @@ pub mod streaming_peek_iter { #[maybe_async::test(feature = "blocking-io", async(feature = "async-io", async_std::test))] async fn peek_non_data() -> crate::Result { - let mut rd = - gix_packetline::StreamingPeekableIter::new(&b"000000010002"[..], &[PacketLineRef::ResponseEnd], false); + let mut rd = StreamingPeekableIter::new(&b"000000010002"[..], &[PacketLineRef::ResponseEnd], false); let res = rd.read_line().await; assert_eq!(res.expect("line")??, PacketLineRef::Flush); let res = rd.read_line().await; @@ -124,7 +125,7 @@ pub mod streaming_peek_iter { #[maybe_async::test(feature = "blocking-io", async(feature = "async-io", async_std::test))] async fn fail_on_err_lines() -> crate::Result { let input = b"00010009ERR e0002"; - let mut rd = gix_packetline::StreamingPeekableIter::new(&input[..], &[], false); + let mut rd = StreamingPeekableIter::new(&input[..], &[], false); let res = rd.read_line().await; assert_eq!(res.expect("line")??, PacketLineRef::Delimiter); let res = rd.read_line().await; @@ -134,7 +135,7 @@ pub mod streaming_peek_iter { "by default no special handling" ); - let mut rd = gix_packetline::StreamingPeekableIter::new(&input[..], &[], false); + let mut rd = StreamingPeekableIter::new(&input[..], &[], false); rd.fail_on_err_lines(true); let res = rd.read_line().await; assert_eq!(res.expect("line")??, PacketLineRef::Delimiter); @@ -162,7 +163,7 @@ pub mod streaming_peek_iter { #[maybe_async::test(feature = "blocking-io", async(feature = "async-io", async_std::test))] async fn peek() -> crate::Result { let bytes = fixture_bytes("v1/fetch/01-many-refs.response"); - let mut rd = gix_packetline::StreamingPeekableIter::new(&bytes[..], &[PacketLineRef::Flush], false); + let mut rd = StreamingPeekableIter::new(&bytes[..], &[PacketLineRef::Flush], false); let res = rd.peek_line().await; assert_eq!(res.expect("line")??, first_line(), "peek returns first line"); let res = rd.peek_line().await; @@ -199,7 +200,7 @@ pub mod streaming_peek_iter { async fn read_from_file_and_reader_advancement() -> crate::Result { let mut bytes = fixture_bytes("v1/fetch/01-many-refs.response"); bytes.extend(fixture_bytes("v1/fetch/01-many-refs.response")); - let mut rd = gix_packetline::StreamingPeekableIter::new(&bytes[..], &[PacketLineRef::Flush], false); + let mut rd = StreamingPeekableIter::new(&bytes[..], &[PacketLineRef::Flush], false); let res = rd.read_line().await; assert_eq!(res.expect("line")??, first_line()); let res = exhaust(&mut rd).await; @@ -223,7 +224,7 @@ pub mod streaming_peek_iter { } #[maybe_async::maybe_async] - async fn exhaust(rd: &mut gix_packetline::StreamingPeekableIter<&[u8]>) -> i32 { + async fn exhaust(rd: &mut StreamingPeekableIter<&[u8]>) -> i32 { let mut count = 0; while rd.read_line().await.is_some() { count += 1; diff --git a/gix-packetline/tests/read/sideband.rs b/gix-packetline/tests/read/sideband.rs index c5f33e93979..0b88fe44865 100644 --- a/gix-packetline/tests/read/sideband.rs +++ b/gix-packetline/tests/read/sideband.rs @@ -5,6 +5,10 @@ use bstr::{BString, ByteSlice}; #[cfg(all(not(feature = "blocking-io"), feature = "async-io"))] use futures_lite::io::AsyncReadExt; use gix_odb::pack; +#[cfg(all(feature = "async-io", not(feature = "blocking-io")))] +use gix_packetline::read::async_io::StreamingPeekableIter; +#[cfg(all(feature = "blocking-io", not(feature = "async-io")))] +use gix_packetline::read::blocking_io::StreamingPeekableIter; use gix_packetline::{read::ProgressAction, PacketLineRef}; use crate::read::streaming_peek_iter::fixture_bytes; @@ -38,7 +42,7 @@ mod util { #[maybe_async::test(feature = "blocking-io", async(feature = "async-io", async_std::test))] async fn read_pack_with_progress_extraction() -> crate::Result { let buf = fixture_bytes("v1/01-clone.combined-output"); - let mut rd = gix_packetline::StreamingPeekableIter::new(&buf[..], &[PacketLineRef::Flush], false); + let mut rd = StreamingPeekableIter::new(&buf[..], &[PacketLineRef::Flush], false); // Read without sideband decoding let mut out = Vec::new(); @@ -103,7 +107,7 @@ async fn read_pack_with_progress_extraction() -> crate::Result { async fn read_line_trait_method_reads_one_packet_line_at_a_time() -> crate::Result { let buf = fixture_bytes("v1/01-clone.combined-output-no-binary"); - let mut rd = gix_packetline::StreamingPeekableIter::new(&buf[..], &[PacketLineRef::Flush], false); + let mut rd = StreamingPeekableIter::new(&buf[..], &[PacketLineRef::Flush], false); let mut out = String::new(); let mut r = rd.as_read(); @@ -149,7 +153,7 @@ async fn read_line_trait_method_reads_one_packet_line_at_a_time() -> crate::Resu async fn readline_reads_one_packet_line_at_a_time() -> crate::Result { let buf = fixture_bytes("v1/01-clone.combined-output-no-binary"); - let mut rd = gix_packetline::StreamingPeekableIter::new(&buf[..], &[PacketLineRef::Flush], false); + let mut rd = StreamingPeekableIter::new(&buf[..], &[PacketLineRef::Flush], false); let mut r = rd.as_read(); let line = r.read_data_line().await.unwrap()??.as_bstr().unwrap(); @@ -194,7 +198,7 @@ async fn readline_reads_one_packet_line_at_a_time() -> crate::Result { #[maybe_async::test(feature = "blocking-io", async(feature = "async-io", async_std::test))] async fn peek_past_an_actual_eof_is_an_error() -> crate::Result { let input = b"0009ERR e"; - let mut rd = gix_packetline::StreamingPeekableIter::new(&input[..], &[], false); + let mut rd = StreamingPeekableIter::new(&input[..], &[], false); let mut reader = rd.as_read(); let res = reader.peek_data_line().await; assert_eq!(res.expect("one line")??, b"ERR e"); @@ -218,7 +222,7 @@ async fn peek_past_an_actual_eof_is_an_error() -> crate::Result { #[maybe_async::test(feature = "blocking-io", async(feature = "async-io", async_std::test))] async fn peek_past_a_delimiter_is_no_error() -> crate::Result { let input = b"0009hello0000"; - let mut rd = gix_packetline::StreamingPeekableIter::new(&input[..], &[PacketLineRef::Flush], false); + let mut rd = StreamingPeekableIter::new(&input[..], &[PacketLineRef::Flush], false); let mut reader = rd.as_read(); let res = reader.peek_data_line().await; assert_eq!(res.expect("one line")??, b"hello"); @@ -238,7 +242,7 @@ async fn peek_past_a_delimiter_is_no_error() -> crate::Result { #[maybe_async::test(feature = "blocking-io", async(feature = "async-io", async_std::test))] async fn handling_of_err_lines() { let input = b"0009ERR e0009ERR x0000"; - let mut rd = gix_packetline::StreamingPeekableIter::new(&input[..], &[], false); + let mut rd = StreamingPeekableIter::new(&input[..], &[], false); rd.fail_on_err_lines(true); let mut buf = [0u8; 2]; let mut reader = rd.as_read(); diff --git a/gix-packetline/tests/write/mod.rs b/gix-packetline/tests/write/mod.rs index 857373e0a2a..266291ca41d 100644 --- a/gix-packetline/tests/write/mod.rs +++ b/gix-packetline/tests/write/mod.rs @@ -4,7 +4,10 @@ use std::io::Write; use bstr::ByteSlice; #[cfg(all(feature = "async-io", not(feature = "blocking-io")))] use futures_lite::prelude::*; -use gix_packetline::Writer; +#[cfg(all(feature = "async-io", not(feature = "blocking-io")))] +use gix_packetline::write::async_io::Writer; +#[cfg(all(feature = "blocking-io", not(feature = "async-io")))] +use gix_packetline::write::blocking_io::Writer; const MAX_DATA_LEN: usize = 65516; const MAX_LINE_LEN: usize = 4 + MAX_DATA_LEN; @@ -24,9 +27,10 @@ async fn each_write_results_in_one_line() -> crate::Result { #[maybe_async::test(feature = "blocking-io", async(feature = "async-io", async_std::test))] async fn write_text_and_write_binary() -> crate::Result { let buf = { - let mut w = Writer::new(Vec::new()).text_mode(); + let mut w = Writer::new(Vec::new()); + w.enable_text_mode(); w.write_all(b"hello").await?; - w = w.binary_mode(); + w.enable_binary_mode(); w.write(b"world").await?; w.into_inner() }; diff --git a/gix-protocol/tests/protocol/fetch/response.rs b/gix-protocol/tests/protocol/fetch/response.rs index a0203ebfc4d..05bba86127e 100644 --- a/gix-protocol/tests/protocol/fetch/response.rs +++ b/gix-protocol/tests/protocol/fetch/response.rs @@ -1,9 +1,13 @@ use crate::fetch::Cursor; +#[cfg(all(feature = "async-client", not(feature = "blocking-client")))] +use gix_packetline::read::async_io::StreamingPeekableIter; +#[cfg(all(feature = "blocking-client", not(feature = "async-client")))] +use gix_packetline::read::blocking_io::StreamingPeekableIter; -fn mock_reader(path: &str) -> gix_packetline::StreamingPeekableIter { +fn mock_reader(path: &str) -> StreamingPeekableIter { use crate::fixture_bytes; let buf = fixture_bytes(path); - gix_packetline::StreamingPeekableIter::new(Cursor::new(buf), &[gix_packetline::PacketLineRef::Flush], false) + StreamingPeekableIter::new(Cursor::new(buf), &[gix_packetline::PacketLineRef::Flush], false) } fn id(hex: &str) -> gix_hash::ObjectId { diff --git a/gix-transport/src/client/async_io/bufread_ext.rs b/gix-transport/src/client/async_io/bufread_ext.rs index 00389811ebe..a88d1a31a22 100644 --- a/gix-transport/src/client/async_io/bufread_ext.rs +++ b/gix-transport/src/client/async_io/bufread_ext.rs @@ -90,7 +90,7 @@ impl<'a, T: ExtendedBufRead<'a> + ?Sized + 'a + Unpin> ExtendedBufRead<'a> for B #[async_trait(?Send)] impl ReadlineBufRead - for gix_packetline::read::WithSidebands<'_, T, for<'b> fn(bool, &'b [u8]) -> ProgressAction> + for gix_packetline::read::async_io::WithSidebands<'_, T, for<'b> fn(bool, &'b [u8]) -> ProgressAction> { async fn readline(&mut self) -> Option, gix_packetline::decode::Error>>> { self.read_data_line().await @@ -101,7 +101,9 @@ impl ReadlineBufRead } #[async_trait(?Send)] -impl<'a, T: AsyncRead + Unpin> ReadlineBufRead for gix_packetline::read::WithSidebands<'a, T, HandleProgress<'a>> { +impl<'a, T: AsyncRead + Unpin> ReadlineBufRead + for gix_packetline::read::async_io::WithSidebands<'a, T, HandleProgress<'a>> +{ async fn readline(&mut self) -> Option, gix_packetline::decode::Error>>> { self.read_data_line().await } @@ -111,7 +113,9 @@ impl<'a, T: AsyncRead + Unpin> ReadlineBufRead for gix_packetline::read::WithSid } #[async_trait(?Send)] -impl<'a, T: AsyncRead + Unpin> ExtendedBufRead<'a> for gix_packetline::read::WithSidebands<'a, T, HandleProgress<'a>> { +impl<'a, T: AsyncRead + Unpin> ExtendedBufRead<'a> + for gix_packetline::read::async_io::WithSidebands<'a, T, HandleProgress<'a>> +{ fn set_progress_handler(&mut self, handle_progress: Option>) { self.set_progress_handler(handle_progress); } diff --git a/gix-transport/src/client/async_io/request.rs b/gix-transport/src/client/async_io/request.rs index 6c6dedf9e3c..60c51d28efd 100644 --- a/gix-transport/src/client/async_io/request.rs +++ b/gix-transport/src/client/async_io/request.rs @@ -7,7 +7,7 @@ use std::{ use futures_io::AsyncWrite; use pin_project_lite::pin_project; -use crate::client::{ExtendedBufRead, MessageKind, WriteMode}; +use crate::client::{async_io::ExtendedBufRead, MessageKind, WriteMode}; pin_project! { /// A [`Write`][io::Write] implementation optimized for writing packet lines. @@ -16,7 +16,7 @@ pin_project! { pub struct RequestWriter<'a> { on_into_read: MessageKind, #[pin] - writer: gix_packetline::Writer>, + writer: gix_packetline::write::async_io::Writer>, reader: Box + Unpin + 'a>, trace: bool, } @@ -48,7 +48,7 @@ impl<'a> RequestWriter<'a> { on_into_read: MessageKind, trace: bool, ) -> Self { - let mut writer = gix_packetline::Writer::new(Box::new(writer) as Box); + let mut writer = gix_packetline::write::async_io::Writer::new(Box::new(writer) as Box); match write_mode { WriteMode::Binary => writer.enable_binary_mode(), WriteMode::OneLfTerminatedLinePerWriteCall => writer.enable_text_mode(), @@ -68,25 +68,31 @@ impl<'a> RequestWriter<'a> { if self.trace { gix_features::trace::trace!(">> FLUSH"); } - gix_packetline::PacketLineRef::Flush - .write_to(self.writer.inner_mut()) - .await + gix_packetline::encode::async_io::write_packet_line( + &gix_packetline::PacketLineRef::Flush, + self.writer.inner_mut(), + ) + .await } MessageKind::Delimiter => { if self.trace { gix_features::trace::trace!(">> DELIM"); } - gix_packetline::PacketLineRef::Delimiter - .write_to(self.writer.inner_mut()) - .await + gix_packetline::encode::async_io::write_packet_line( + &gix_packetline::PacketLineRef::Delimiter, + self.writer.inner_mut(), + ) + .await } MessageKind::ResponseEnd => { if self.trace { gix_features::trace::trace!(">> RESPONSE_END"); } - gix_packetline::PacketLineRef::ResponseEnd - .write_to(self.writer.inner_mut()) - .await + gix_packetline::encode::async_io::write_packet_line( + &gix_packetline::PacketLineRef::ResponseEnd, + self.writer.inner_mut(), + ) + .await } MessageKind::Text(t) => { #[allow(unused_variables, unused_imports)] @@ -94,7 +100,8 @@ impl<'a> RequestWriter<'a> { use bstr::ByteSlice; gix_features::trace::trace!(">> {}", t.as_bstr()); } - gix_packetline::TextRef::from(t).write_to(self.writer.inner_mut()).await + gix_packetline::encode::async_io::write_text(&gix_packetline::TextRef::from(t), self.writer.inner_mut()) + .await } } .map(|_| ()) diff --git a/gix-transport/src/client/async_io/traits.rs b/gix-transport/src/client/async_io/traits.rs index 6a5577d1725..592c0b971c5 100644 --- a/gix-transport/src/client/async_io/traits.rs +++ b/gix-transport/src/client/async_io/traits.rs @@ -5,7 +5,10 @@ use bstr::BString; use futures_lite::io::AsyncWriteExt; use crate::{ - client::{Capabilities, Error, ExtendedBufRead, MessageKind, TransportWithoutIO, WriteMode}, + client::{ + async_io::{ExtendedBufRead, ReadlineBufRead}, + Capabilities, Error, MessageKind, TransportWithoutIO, WriteMode, + }, Protocol, Service, }; @@ -16,7 +19,7 @@ pub struct SetServiceResponse<'a> { /// The capabilities parsed from the server response. pub capabilities: Capabilities, /// In protocol version one, this is set to a list of refs and their peeled counterparts. - pub refs: Option>, + pub refs: Option>, } /// All methods provided here must be called in the correct order according to the [communication protocol][Protocol] diff --git a/gix-transport/src/client/blocking_io/bufread_ext.rs b/gix-transport/src/client/blocking_io/bufread_ext.rs index 9054fb54290..7d807120c0a 100644 --- a/gix-transport/src/client/blocking_io/bufread_ext.rs +++ b/gix-transport/src/client/blocking_io/bufread_ext.rs @@ -79,7 +79,9 @@ impl<'a, T: ExtendedBufRead<'a> + ?Sized + 'a> ExtendedBufRead<'a> for Box { } } -impl ReadlineBufRead for gix_packetline::read::WithSidebands<'_, T, fn(bool, &[u8]) -> ProgressAction> { +impl ReadlineBufRead + for gix_packetline::read::blocking_io::WithSidebands<'_, T, fn(bool, &[u8]) -> ProgressAction> +{ fn readline(&mut self) -> Option, gix_packetline::decode::Error>>> { self.read_data_line() } @@ -89,7 +91,7 @@ impl ReadlineBufRead for gix_packetline::read::WithSidebands<'_, T, } } -impl<'a, T: io::Read> ReadlineBufRead for gix_packetline::read::WithSidebands<'a, T, HandleProgress<'a>> { +impl<'a, T: io::Read> ReadlineBufRead for gix_packetline::read::blocking_io::WithSidebands<'a, T, HandleProgress<'a>> { fn readline(&mut self) -> Option, gix_packetline::decode::Error>>> { self.read_data_line() } @@ -99,7 +101,9 @@ impl<'a, T: io::Read> ReadlineBufRead for gix_packetline::read::WithSidebands<'a } } -impl<'a, T: io::Read> ExtendedBufRead<'a> for gix_packetline::read::WithSidebands<'a, T, HandleProgress<'a>> { +impl<'a, T: io::Read> ExtendedBufRead<'a> + for gix_packetline::read::blocking_io::WithSidebands<'a, T, HandleProgress<'a>> +{ fn set_progress_handler(&mut self, handle_progress: Option>) { self.set_progress_handler(handle_progress); } diff --git a/gix-transport/src/client/blocking_io/connect.rs b/gix-transport/src/client/blocking_io/connect.rs index 59f9e02c33c..b2b552c5b33 100644 --- a/gix-transport/src/client/blocking_io/connect.rs +++ b/gix-transport/src/client/blocking_io/connect.rs @@ -1,13 +1,13 @@ pub use crate::client::non_io_types::connect::{Error, Options}; pub(crate) mod function { - use crate::client::{non_io_types::connect::Error, Transport}; + use crate::client::{blocking_io::Transport, non_io_types::connect::Error}; /// A general purpose connector connecting to a repository identified by the given `url`. /// /// This includes connections to - /// [local repositories][crate::client::file::connect()], - /// [repositories over ssh][crate::client::ssh::connect()], + /// [local repositories][crate::client::blocking_io::file::connect()], + /// [repositories over ssh][crate::client::blocking_io::ssh::connect()], /// [git daemons][crate::client::git::connect()], /// and if compiled in connections to [git repositories over https][crate::client::http::connect()]. /// @@ -45,7 +45,7 @@ pub(crate) mod function { } Box::new({ let path = std::mem::take(&mut url.path); - crate::client::git::connect( + crate::client::git::blocking_io::connect( url.host().expect("host is present in url"), path, options.version, @@ -58,9 +58,11 @@ pub(crate) mod function { #[cfg(not(any(feature = "http-client-curl", feature = "http-client-reqwest")))] gix_url::Scheme::Https | gix_url::Scheme::Http => return Err(Error::CompiledWithoutHttp(url.scheme)), #[cfg(any(feature = "http-client-curl", feature = "http-client-reqwest"))] - gix_url::Scheme::Https | gix_url::Scheme::Http => { - Box::new(crate::client::http::connect(url, options.version, options.trace)) - } + gix_url::Scheme::Https | gix_url::Scheme::Http => Box::new(crate::client::blocking_io::http::connect( + url, + options.version, + options.trace, + )), }) } } diff --git a/gix-transport/src/client/blocking_io/file.rs b/gix-transport/src/client/blocking_io/file.rs index 904edc4ffde..a83f83554bb 100644 --- a/gix-transport/src/client/blocking_io/file.rs +++ b/gix-transport/src/client/blocking_io/file.rs @@ -10,7 +10,12 @@ use std::{ use bstr::{io::BufReadExt, BStr, BString, ByteSlice}; use crate::{ - client::{self, git, ssh, MessageKind, RequestWriter, SetServiceResponse, WriteMode}, + client::{ + self, + blocking_io::{ssh, RequestWriter, SetServiceResponse}, + git::blocking_io::Connection, + MessageKind, WriteMode, + }, Protocol, Service, }; @@ -45,7 +50,7 @@ pub struct SpawnProcessOnDemand { /// The environment variables to set in the invoked command. envs: Vec<(&'static str, String)>, ssh_disallow_shell: bool, - connection: Option, process::ChildStdin>>, + connection: Option, process::ChildStdin>>, child: Option, trace: bool, } @@ -209,7 +214,7 @@ fn supervise_stderr( ReadStdoutFailOnError { read: stdout, recv } } -impl client::Transport for SpawnProcessOnDemand { +impl client::blocking_io::Transport for SpawnProcessOnDemand { fn handshake<'a>( &mut self, service: Service, @@ -263,7 +268,7 @@ impl client::Transport for SpawnProcessOnDemand { )), None => Box::new(child.stdout.take().expect("stdout configured")), }; - self.connection = Some(git::Connection::new_for_spawned_process( + self.connection = Some(Connection::new_for_spawned_process( stdout, child.stdin.take().expect("stdin configured"), self.desired_version, diff --git a/gix-transport/src/client/blocking_io/http/curl/mod.rs b/gix-transport/src/client/blocking_io/http/curl/mod.rs index ad980dd200c..99411fe20e7 100644 --- a/gix-transport/src/client/blocking_io/http/curl/mod.rs +++ b/gix-transport/src/client/blocking_io/http/curl/mod.rs @@ -5,7 +5,7 @@ use std::{ use gix_features::io; -use crate::client::{blocking_io::http, http::traits::PostBodyDataKind}; +use crate::client::blocking_io::http::{self, traits::PostBodyDataKind}; mod remote; diff --git a/gix-transport/src/client/blocking_io/http/curl/remote.rs b/gix-transport/src/client/blocking_io/http/curl/remote.rs index c2f7755dfe6..c6b918441c2 100644 --- a/gix-transport/src/client/blocking_io/http/curl/remote.rs +++ b/gix-transport/src/client/blocking_io/http/curl/remote.rs @@ -9,13 +9,13 @@ use std::{ use curl::easy::{Auth, Easy2}; use gix_features::io::pipe; -use crate::client::{ - blocking_io::http::{self, curl::Error, redirect}, - http::{ - curl::curl_is_spurious, - options::{FollowRedirects, HttpVersion, ProxyAuthMethod, SslVersion}, - traits::PostBodyDataKind, - }, +use crate::client::blocking_io::http::{ + self, + curl::curl_is_spurious, + curl::Error, + options::{FollowRedirects, HttpVersion, ProxyAuthMethod, SslVersion}, + redirect, + traits::PostBodyDataKind, }; enum StreamOrBuffer { diff --git a/gix-transport/src/client/blocking_io/http/mod.rs b/gix-transport/src/client/blocking_io/http/mod.rs index bfbcb9bf943..3c42925a85e 100644 --- a/gix-transport/src/client/blocking_io/http/mod.rs +++ b/gix-transport/src/client/blocking_io/http/mod.rs @@ -14,10 +14,13 @@ pub use traits::{Error, GetResponse, Http, PostBodyDataKind, PostResponse}; use crate::{ client::{ self, - blocking_io::bufread_ext::ReadlineBufRead, - capabilities, - http::options::{HttpVersion, SslVersionRangeInclusive}, - Capabilities, ExtendedBufRead, HandleProgress, MessageKind, RequestWriter, + blocking_io::{ + self, + bufread_ext::ReadlineBufRead, + http::options::{HttpVersion, SslVersionRangeInclusive}, + ExtendedBufRead, HandleProgress, RequestWriter, SetServiceResponse, + }, + capabilities, Capabilities, MessageKind, }, Protocol, Service, }; @@ -227,7 +230,7 @@ pub struct Transport { actual_version: Protocol, http: H, service: Option, - line_provider: Option>, + line_provider: Option>, identity: Option, trace: bool, } @@ -396,12 +399,12 @@ impl client::TransportWithoutIO for Transport { } } -impl client::Transport for Transport { +impl blocking_io::Transport for Transport { fn handshake<'a>( &mut self, service: Service, extra_parameters: &'a [(&'a str, Option<&'a str>)], - ) -> Result, client::Error> { + ) -> Result, client::Error> { let url = append_url(self.url.as_ref(), &format!("info/refs?service={}", service.as_str())); let static_headers = [Cow::Borrowed(self.user_agent_header)]; let mut dynamic_headers = Vec::>::new(); @@ -434,7 +437,7 @@ impl client::Transport for Transport { >::check_content_type(service, "advertisement", headers)?; let line_reader = self.line_provider.get_or_insert_with(|| { - gix_packetline::StreamingPeekableIter::new(body, &[PacketLineRef::Flush], self.trace) + gix_packetline::read::blocking_io::StreamingPeekableIter::new(body, &[PacketLineRef::Flush], self.trace) }); // the service announcement is only sent sometimes depending on the exact server/protocol version/used protocol (http?) @@ -466,7 +469,7 @@ impl client::Transport for Transport { } = Capabilities::from_lines_with_version_detection(line_reader)?; self.actual_version = actual_protocol; self.service = Some(service); - Ok(client::SetServiceResponse { + Ok(SetServiceResponse { actual_protocol, capabilities, refs, diff --git a/gix-transport/src/client/blocking_io/http/traits.rs b/gix-transport/src/client/blocking_io/http/traits.rs index df00b2a4bd8..b13473a38b0 100644 --- a/gix-transport/src/client/blocking_io/http/traits.rs +++ b/gix-transport/src/client/blocking_io/http/traits.rs @@ -21,7 +21,7 @@ impl crate::IsSpuriousError for Error { #[cfg(any(feature = "http-client-reqwest", feature = "http-client-curl"))] Error::InitHttpClient { source } => { #[cfg(feature = "http-client-curl")] - if let Some(err) = source.downcast_ref::() { + if let Some(err) = source.downcast_ref::() { return err.is_spurious(); } #[cfg(feature = "http-client-reqwest")] diff --git a/gix-transport/src/client/blocking_io/request.rs b/gix-transport/src/client/blocking_io/request.rs index f4b3a77a329..8342b0e3d53 100644 --- a/gix-transport/src/client/blocking_io/request.rs +++ b/gix-transport/src/client/blocking_io/request.rs @@ -1,13 +1,13 @@ use std::{io, io::Write}; -use crate::client::{ExtendedBufRead, MessageKind, WriteMode}; +use crate::client::{blocking_io::ExtendedBufRead, MessageKind, WriteMode}; /// A [`Write`][io::Write] implementation optimized for writing packet lines. /// A type implementing `Write` for packet lines, which when done can be transformed into a `Read` for /// obtaining the response. pub struct RequestWriter<'a> { on_into_read: MessageKind, - writer: gix_packetline::Writer>, + writer: gix_packetline::write::blocking_io::Writer>, reader: Box + Unpin + 'a>, trace: bool, } @@ -40,7 +40,7 @@ impl<'a> RequestWriter<'a> { on_into_read: MessageKind, trace: bool, ) -> Self { - let mut writer = gix_packetline::Writer::new(Box::new(writer) as Box); + let mut writer = gix_packetline::write::blocking_io::Writer::new(Box::new(writer) as Box); match write_mode { WriteMode::Binary => writer.enable_binary_mode(), WriteMode::OneLfTerminatedLinePerWriteCall => writer.enable_text_mode(), @@ -60,19 +60,28 @@ impl<'a> RequestWriter<'a> { if self.trace { gix_features::trace::trace!(">> FLUSH"); } - gix_packetline::PacketLineRef::Flush.write_to(self.writer.inner_mut()) + gix_packetline::encode::blocking_io::write_packet_line( + &gix_packetline::PacketLineRef::Flush, + self.writer.inner_mut(), + ) } MessageKind::Delimiter => { if self.trace { gix_features::trace::trace!(">> DELIM"); } - gix_packetline::PacketLineRef::Delimiter.write_to(self.writer.inner_mut()) + gix_packetline::encode::blocking_io::write_packet_line( + &gix_packetline::PacketLineRef::Delimiter, + self.writer.inner_mut(), + ) } MessageKind::ResponseEnd => { if self.trace { gix_features::trace::trace!(">> RESPONSE_END"); } - gix_packetline::PacketLineRef::ResponseEnd.write_to(self.writer.inner_mut()) + gix_packetline::encode::blocking_io::write_packet_line( + &gix_packetline::PacketLineRef::ResponseEnd, + self.writer.inner_mut(), + ) } MessageKind::Text(t) => { #[allow(unused_variables, unused_imports)] @@ -80,7 +89,10 @@ impl<'a> RequestWriter<'a> { use bstr::ByteSlice; gix_features::trace::trace!(">> {}", t.as_bstr()); } - gix_packetline::TextRef::from(t).write_to(self.writer.inner_mut()) + gix_packetline::encode::blocking_io::write_text( + &gix_packetline::TextRef::from(t), + self.writer.inner_mut(), + ) } } .map(|_| ()) diff --git a/gix-transport/src/client/blocking_io/ssh/mod.rs b/gix-transport/src/client/blocking_io/ssh/mod.rs index 5e2206155e8..4254ab38584 100644 --- a/gix-transport/src/client/blocking_io/ssh/mod.rs +++ b/gix-transport/src/client/blocking_io/ssh/mod.rs @@ -64,7 +64,7 @@ pub mod invocation { pub mod connect { use std::ffi::{OsStr, OsString}; - use crate::client::ssh::ProgramKind; + use crate::client::blocking_io::ssh::ProgramKind; /// The options for use when [connecting][super::connect()] via the `ssh` protocol. #[derive(Debug, Clone, Default)] diff --git a/gix-transport/src/client/blocking_io/ssh/program_kind.rs b/gix-transport/src/client/blocking_io/ssh/program_kind.rs index 194314dffe2..932b1163c41 100644 --- a/gix-transport/src/client/blocking_io/ssh/program_kind.rs +++ b/gix-transport/src/client/blocking_io/ssh/program_kind.rs @@ -4,7 +4,7 @@ use bstr::{BString, ByteSlice, ByteVec}; use gix_url::ArgumentSafety::*; use crate::{ - client::{ssh, ssh::ProgramKind}, + client::blocking_io::ssh::{self, ProgramKind}, Protocol, }; diff --git a/gix-transport/src/client/blocking_io/ssh/tests.rs b/gix-transport/src/client/blocking_io/ssh/tests.rs index f1956e157a0..3bffe1f0fc6 100644 --- a/gix-transport/src/client/blocking_io/ssh/tests.rs +++ b/gix-transport/src/client/blocking_io/ssh/tests.rs @@ -1,6 +1,6 @@ mod options { mod ssh_command { - use crate::client::ssh::{connect::Options, ProgramKind}; + use crate::client::blocking_io::ssh::{connect::Options, ProgramKind}; #[test] fn no_field_means_ssh() { @@ -46,7 +46,7 @@ mod program_kind { mod from_os_str { use std::ffi::OsStr; - use crate::client::ssh::ProgramKind; + use crate::client::blocking_io::ssh::ProgramKind; #[test] fn known_variants_are_derived_from_basename() { @@ -100,7 +100,7 @@ mod program_kind { use std::ffi::OsStr; use crate::{ - client::{ssh, ssh::ProgramKind}, + client::blocking_io::ssh::{self, ProgramKind}, Protocol, }; @@ -280,7 +280,7 @@ mod program_kind { mod line_to_err { use std::io::ErrorKind; - use crate::client::ssh::ProgramKind; + use crate::client::blocking_io::ssh::ProgramKind; #[test] fn all() { diff --git a/gix-transport/src/client/blocking_io/traits.rs b/gix-transport/src/client/blocking_io/traits.rs index 31fb83dc313..6287fb1adfc 100644 --- a/gix-transport/src/client/blocking_io/traits.rs +++ b/gix-transport/src/client/blocking_io/traits.rs @@ -3,7 +3,10 @@ use std::{io::Write, ops::DerefMut}; use bstr::BString; use crate::{ - client::{Capabilities, Error, ExtendedBufRead, MessageKind, TransportWithoutIO, WriteMode}, + client::{ + blocking_io::{ExtendedBufRead, ReadlineBufRead}, + Capabilities, Error, MessageKind, TransportWithoutIO, WriteMode, + }, Protocol, Service, }; @@ -14,7 +17,7 @@ pub struct SetServiceResponse<'a> { /// The capabilities parsed from the server response. pub capabilities: Capabilities, /// In protocol version one, this is set to a list of refs and their peeled counterparts. - pub refs: Option>, + pub refs: Option>, } /// All methods provided here must be called in the correct order according to the [communication protocol][Protocol] diff --git a/gix-transport/src/client/capabilities.rs b/gix-transport/src/client/capabilities.rs index 21513ace664..7206275a908 100644 --- a/gix-transport/src/client/capabilities.rs +++ b/gix-transport/src/client/capabilities.rs @@ -172,7 +172,10 @@ pub mod recv { use bstr::ByteVec; - use crate::{client, client::Capabilities, Protocol}; + use crate::{ + client::{self, blocking_io::ReadlineBufRead, Capabilities}, + Protocol, + }; /// Success outcome of [`Capabilities::from_lines_with_version_detection`]. pub struct Outcome<'a> { @@ -182,7 +185,7 @@ pub mod recv { /// /// This is `Some` only when protocol v1 is used. The [`io::BufRead`] must be exhausted by /// the caller. - pub refs: Option>, + pub refs: Option>, /// The [`Protocol`] the remote advertised. pub protocol: Protocol, } @@ -193,7 +196,7 @@ pub mod recv { /// If [`Protocol::V1`] was requested, or the remote decided to downgrade, the remote refs /// advertisement will also be included in the [`Outcome`]. pub fn from_lines_with_version_detection( - rd: &mut gix_packetline::StreamingPeekableIter, + rd: &mut gix_packetline::read::blocking_io::StreamingPeekableIter, ) -> Result, client::Error> { // NOTE that this is vitally important - it is turned on and stays on for all following requests so // we automatically abort if the server sends an ERR line anywhere. @@ -249,14 +252,17 @@ pub mod recv { } } -#[cfg(feature = "async-client")] +#[cfg(all(feature = "async-client", not(feature = "blocking-client")))] #[allow(missing_docs)] /// pub mod recv { use bstr::ByteVec; use futures_io::AsyncRead; - use crate::{client, client::Capabilities, Protocol}; + use crate::{ + client::{self, async_io::ReadlineBufRead, Capabilities}, + Protocol, + }; /// Success outcome of [`Capabilities::from_lines_with_version_detection`]. pub struct Outcome<'a> { @@ -266,7 +272,7 @@ pub mod recv { /// /// This is `Some` only when protocol v1 is used. The [`AsyncBufRead`] must be exhausted by /// the caller. - pub refs: Option>, + pub refs: Option>, /// The [`Protocol`] the remote advertised. pub protocol: Protocol, } @@ -277,7 +283,7 @@ pub mod recv { /// If [`Protocol::V1`] was requested, or the remote decided to downgrade, the remote refs /// advertisement will also be included in the [`Outcome`]. pub async fn from_lines_with_version_detection( - rd: &mut gix_packetline::StreamingPeekableIter, + rd: &mut gix_packetline::read::async_io::StreamingPeekableIter, ) -> Result, client::Error> { // NOTE that this is vitally important - it is turned on and stays on for all following requests so // we automatically abort if the server sends an ERR line anywhere. diff --git a/gix-transport/src/client/git/async_io.rs b/gix-transport/src/client/git/async_io.rs index 5bbb47eb0f6..4f88d15a43e 100644 --- a/gix-transport/src/client/git/async_io.rs +++ b/gix-transport/src/client/git/async_io.rs @@ -7,11 +7,44 @@ use futures_lite::AsyncWriteExt; use gix_packetline::PacketLineRef; use crate::{ - client::{self, capabilities, git, Capabilities, SetServiceResponse}, + client::{ + self, + async_io::{RequestWriter, SetServiceResponse}, + capabilities, + git::{self, ConnectionState}, + Capabilities, + }, Protocol, Service, }; -impl client::TransportWithoutIO for git::Connection +/// A TCP connection to either a `git` daemon or a spawned `git` process. +/// +/// When connecting to a daemon, additional context information is sent with the first line of the handshake. Otherwise that +/// context is passed using command line arguments to a [spawned `git` process][crate::client::file::SpawnProcessOnDemand]. +pub struct Connection { + pub(in crate::client) writer: W, + pub(in crate::client) line_provider: gix_packetline::read::async_io::StreamingPeekableIter, + pub(in crate::client) state: ConnectionState, +} + +impl Connection { + /// Optionally set the URL to be returned when asked for it if `Some` or calculate a default for `None`. + /// + /// The URL is required as parameter for authentication helpers which are called in transports + /// that support authentication. Even though plain git transports don't support that, this + /// may well be the case in custom transports. + pub fn custom_url(mut self, url: Option) -> Self { + self.state.custom_url = url; + self + } + + /// Return the inner reader and writer + pub fn into_inner(self) -> (R, W) { + (self.line_provider.into_inner(), self.writer) + } +} + +impl client::TransportWithoutIO for Connection where R: AsyncRead + Unpin, W: AsyncWrite + Unpin, @@ -21,8 +54,8 @@ where write_mode: client::WriteMode, on_into_read: client::MessageKind, trace: bool, - ) -> Result, client::Error> { - Ok(client::RequestWriter::new_from_bufread( + ) -> Result, client::Error> { + Ok(RequestWriter::new_from_bufread( &mut self.writer, Box::new(self.line_provider.as_read_without_sidebands()), write_mode, @@ -30,10 +63,11 @@ where trace, )) } + fn to_url(&self) -> Cow<'_, BStr> { - self.custom_url.as_ref().map_or_else( + self.state.custom_url.as_ref().map_or_else( || { - let mut possibly_lossy_url = self.path.clone(); + let mut possibly_lossy_url = self.state.path.clone(); possibly_lossy_url.insert_str(0, "file://"); Cow::Owned(possibly_lossy_url) }, @@ -51,7 +85,7 @@ where } #[async_trait(?Send)] -impl client::Transport for git::Connection +impl client::async_io::Transport for Connection where R: AsyncRead + Unpin, W: AsyncWrite + Unpin, @@ -61,14 +95,15 @@ where service: Service, extra_parameters: &'a [(&'a str, Option<&'a str>)], ) -> Result, client::Error> { - if self.mode == git::ConnectMode::Daemon { - let mut line_writer = gix_packetline::Writer::new(&mut self.writer).binary_mode(); + if self.state.mode == git::ConnectMode::Daemon { + let mut line_writer = gix_packetline::write::async_io::Writer::new(&mut self.writer); + line_writer.enable_binary_mode(); line_writer .write_all(&git::message::connect( service, - self.desired_version, - &self.path, - self.virtual_host.as_ref(), + self.state.desired_version, + &self.state.path, + self.state.virtual_host.as_ref(), extra_parameters, )) .await?; @@ -88,7 +123,7 @@ where } } -impl git::Connection +impl Connection where R: AsyncRead + Unpin, W: AsyncWrite + Unpin, @@ -107,14 +142,20 @@ where mode: git::ConnectMode, trace: bool, ) -> Self { - git::Connection { + Connection { writer: write, - line_provider: gix_packetline::StreamingPeekableIter::new(read, &[PacketLineRef::Flush], trace), - path: repository_path.into(), - virtual_host: virtual_host.map(|(h, p)| (h.into(), p)), - desired_version, - custom_url: None, - mode, + line_provider: gix_packetline::read::async_io::StreamingPeekableIter::new( + read, + &[PacketLineRef::Flush], + trace, + ), + state: ConnectionState { + path: repository_path.into(), + virtual_host: virtual_host.map(|(h, p)| (h.into(), p)), + desired_version, + custom_url: None, + mode, + }, } } } diff --git a/gix-transport/src/client/git/blocking_io.rs b/gix-transport/src/client/git/blocking_io.rs index 42f253dfb3a..06b76d2b7ad 100644 --- a/gix-transport/src/client/git/blocking_io.rs +++ b/gix-transport/src/client/git/blocking_io.rs @@ -4,11 +4,44 @@ use bstr::{BStr, BString, ByteVec}; use gix_packetline::PacketLineRef; use crate::{ - client::{self, capabilities, git, Capabilities, SetServiceResponse}, + client::{ + self, + blocking_io::{RequestWriter, SetServiceResponse}, + capabilities, + git::{self, ConnectionState}, + Capabilities, + }, Protocol, Service, }; -impl client::TransportWithoutIO for git::Connection +/// A TCP connection to either a `git` daemon or a spawned `git` process. +/// +/// When connecting to a daemon, additional context information is sent with the first line of the handshake. Otherwise that +/// context is passed using command line arguments to a [spawned `git` process][crate::client::file::SpawnProcessOnDemand]. +pub struct Connection { + pub(in crate::client) writer: W, + pub(in crate::client) line_provider: gix_packetline::read::blocking_io::StreamingPeekableIter, + pub(in crate::client) state: ConnectionState, +} + +impl Connection { + /// Optionally set the URL to be returned when asked for it if `Some` or calculate a default for `None`. + /// + /// The URL is required as parameter for authentication helpers which are called in transports + /// that support authentication. Even though plain git transports don't support that, this + /// may well be the case in custom transports. + pub fn custom_url(mut self, url: Option) -> Self { + self.state.custom_url = url; + self + } + + /// Return the inner reader and writer + pub fn into_inner(self) -> (R, W) { + (self.line_provider.into_inner(), self.writer) + } +} + +impl client::TransportWithoutIO for Connection where R: std::io::Read, W: std::io::Write, @@ -18,8 +51,8 @@ where write_mode: client::WriteMode, on_into_read: client::MessageKind, trace: bool, - ) -> Result, client::Error> { - Ok(client::RequestWriter::new_from_bufread( + ) -> Result, client::Error> { + Ok(RequestWriter::new_from_bufread( &mut self.writer, Box::new(self.line_provider.as_read_without_sidebands()), write_mode, @@ -29,9 +62,9 @@ where } fn to_url(&self) -> Cow<'_, BStr> { - self.custom_url.as_ref().map_or_else( + self.state.custom_url.as_ref().map_or_else( || { - let mut possibly_lossy_url = self.path.clone(); + let mut possibly_lossy_url = self.state.path.clone(); possibly_lossy_url.insert_str(0, "file://"); Cow::Owned(possibly_lossy_url) }, @@ -48,7 +81,7 @@ where } } -impl client::Transport for git::Connection +impl client::blocking_io::Transport for Connection where R: std::io::Read, W: std::io::Write, @@ -58,13 +91,14 @@ where service: Service, extra_parameters: &'a [(&'a str, Option<&'a str>)], ) -> Result, client::Error> { - if self.mode == git::ConnectMode::Daemon { - let mut line_writer = gix_packetline::Writer::new(&mut self.writer).binary_mode(); + if self.state.mode == git::ConnectMode::Daemon { + let mut line_writer = gix_packetline::write::blocking_io::Writer::new(&mut self.writer); + line_writer.enable_binary_mode(); line_writer.write_all(&git::message::connect( service, - self.desired_version, - &self.path, - self.virtual_host.as_ref(), + self.state.desired_version, + &self.state.path, + self.state.virtual_host.as_ref(), extra_parameters, ))?; line_writer.flush()?; @@ -83,7 +117,7 @@ where } } -impl git::Connection +impl Connection where R: std::io::Read, W: std::io::Write, @@ -102,14 +136,20 @@ where mode: git::ConnectMode, trace: bool, ) -> Self { - git::Connection { + Connection { writer: write, - line_provider: gix_packetline::StreamingPeekableIter::new(read, &[PacketLineRef::Flush], trace), - path: repository_path.into(), - virtual_host: virtual_host.map(|(h, p)| (h.into(), p)), - desired_version, - custom_url: None, - mode, + line_provider: gix_packetline::read::blocking_io::StreamingPeekableIter::new( + read, + &[PacketLineRef::Flush], + trace, + ), + state: ConnectionState { + path: repository_path.into(), + virtual_host: virtual_host.map(|(h, p)| (h.into(), p)), + desired_version, + custom_url: None, + mode, + }, } } pub(crate) fn new_for_spawned_process( @@ -137,7 +177,9 @@ pub mod connect { use bstr::BString; + use super::Connection; use crate::client::git; + /// The error used in [`connect()`]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] @@ -179,7 +221,7 @@ pub mod connect { desired_version: crate::Protocol, port: Option, trace: bool, - ) -> Result, Error> { + ) -> Result, Error> { let read = TcpStream::connect_timeout( &(host, port.unwrap_or(9418)) .to_socket_addrs()? @@ -193,7 +235,7 @@ pub mod connect { .map(parse_host) .transpose()? .unwrap_or_else(|| (host.to_owned(), port)); - Ok(git::Connection::new( + Ok(Connection::new( read, write, desired_version, diff --git a/gix-transport/src/client/git/mod.rs b/gix-transport/src/client/git/mod.rs index c05ff6ba4eb..0b62f69607b 100644 --- a/gix-transport/src/client/git/mod.rs +++ b/gix-transport/src/client/git/mod.rs @@ -11,13 +11,8 @@ pub enum ConnectMode { Process, } -/// A TCP connection to either a `git` daemon or a spawned `git` process. -/// -/// When connecting to a daemon, additional context information is sent with the first line of the handshake. Otherwise that -/// context is passed using command line arguments to a [spawned `git` process][crate::client::file::SpawnProcessOnDemand]. -pub struct Connection { - pub(in crate::client) writer: W, - pub(in crate::client) line_provider: gix_packetline::StreamingPeekableIter, +/// Connection state shared between blocking and async connections. +pub struct ConnectionState { pub(in crate::client) path: BString, pub(in crate::client) virtual_host: Option<(String, Option)>, pub(in crate::client) desired_version: Protocol, @@ -25,23 +20,6 @@ pub struct Connection { pub(in crate::client) mode: ConnectMode, } -impl Connection { - /// Return the inner reader and writer - pub fn into_inner(self) -> (R, W) { - (self.line_provider.into_inner(), self.writer) - } - - /// Optionally set the URL to be returned when asked for it if `Some` or calculate a default for `None`. - /// - /// The URL is required as parameter for authentication helpers which are called in transports - /// that support authentication. Even though plain git transports don't support that, this - /// may well be the case in custom transports. - pub fn custom_url(mut self, url: Option) -> Self { - self.custom_url = url; - self - } -} - mod message { use bstr::{BString, ByteVec}; @@ -183,10 +161,12 @@ mod message { } } -#[cfg(feature = "async-client")] -mod async_io; +#[cfg(all(feature = "async-client", not(feature = "blocking-client")))] +pub(crate) mod async_io; +#[cfg(all(feature = "async-client", not(feature = "blocking-client")))] +pub use async_io::Connection; #[cfg(feature = "blocking-client")] -mod blocking_io; +pub(crate) mod blocking_io; #[cfg(feature = "blocking-client")] -pub use blocking_io::connect; +pub use blocking_io::{connect, Connection}; diff --git a/gix-transport/src/client/mod.rs b/gix-transport/src/client/mod.rs index b5296543e41..b8970f99d6a 100644 --- a/gix-transport/src/client/mod.rs +++ b/gix-transport/src/client/mod.rs @@ -1,6 +1,6 @@ -#[cfg(feature = "async-client")] +#[cfg(all(feature = "async-client", not(feature = "blocking-client")))] mod async_io; -#[cfg(feature = "async-client")] +#[cfg(all(feature = "async-client", not(feature = "blocking-client")))] pub use async_io::{ connect, ExtendedBufRead, HandleProgress, ReadlineBufRead, RequestWriter, SetServiceResponse, Transport, TransportV2Ext, diff --git a/gix-transport/src/client/non_io_types.rs b/gix-transport/src/client/non_io_types.rs index fa2599c0e22..c28e628c34d 100644 --- a/gix-transport/src/client/non_io_types.rs +++ b/gix-transport/src/client/non_io_types.rs @@ -39,7 +39,7 @@ pub(crate) mod connect { pub version: crate::Protocol, #[cfg(feature = "blocking-client")] /// Options to use if the scheme of the URL is `ssh`. - pub ssh: crate::client::ssh::connect::Options, + pub ssh: crate::client::blocking_io::ssh::connect::Options, /// If `true`, all packetlines received or sent will be passed to the facilities of the `gix-trace` crate. pub trace: bool, } @@ -74,7 +74,7 @@ pub(crate) mod connect { match self { Error::Connection(err) => { #[cfg(feature = "blocking-client")] - if let Some(err) = err.downcast_ref::() { + if let Some(err) = err.downcast_ref::() { return err.is_spurious(); } if let Some(err) = err.downcast_ref::() { @@ -93,11 +93,11 @@ mod error { use bstr::BString; - use crate::client::capabilities; #[cfg(feature = "http-client")] - use crate::client::http; + use crate::client::blocking_io::http; #[cfg(feature = "blocking-client")] - use crate::client::ssh; + use crate::client::blocking_io::ssh; + use crate::client::capabilities; #[cfg(feature = "http-client")] type HttpError = http::Error; diff --git a/gix-transport/src/client/traits.rs b/gix-transport/src/client/traits.rs index bafdff0d49a..79c01a7425e 100644 --- a/gix-transport/src/client/traits.rs +++ b/gix-transport/src/client/traits.rs @@ -6,8 +6,12 @@ use std::{ use bstr::BStr; +#[cfg(all(feature = "async-client", not(feature = "blocking-client")))] +use crate::client::async_io::RequestWriter; +#[cfg(feature = "blocking-client")] +use crate::client::blocking_io::RequestWriter; #[cfg(any(feature = "blocking-client", feature = "async-client"))] -use crate::client::{MessageKind, RequestWriter, WriteMode}; +use crate::client::{MessageKind, WriteMode}; use crate::{client::Error, Protocol}; /// This trait represents all transport related functions that don't require any input/output to be done which helps diff --git a/gix-transport/tests/client/capabilities.rs b/gix-transport/tests/client/capabilities.rs index 5ed1f027c15..7cdc3d7347b 100644 --- a/gix-transport/tests/client/capabilities.rs +++ b/gix-transport/tests/client/capabilities.rs @@ -1,4 +1,8 @@ use bstr::ByteSlice; +#[cfg(all(feature = "async-client", not(feature = "blocking-client")))] +use gix_packetline::{encode::async_io as encode_io, read::async_io::StreamingPeekableIter}; +#[cfg(all(feature = "blocking-client", not(feature = "async-client")))] +use gix_packetline::{encode::blocking_io as encode_io, read::blocking_io::StreamingPeekableIter}; use gix_transport::client::Capabilities; #[test] @@ -53,9 +57,8 @@ fn from_bytes() -> crate::Result { #[maybe_async::test(feature = "blocking-client", async(feature = "async-client", async_std::test))] async fn from_lines_with_version_detection_v0() -> crate::Result { let mut buf = Vec::::new(); - gix_packetline::encode::flush_to_write(&mut buf).await?; - let mut stream = - gix_packetline::StreamingPeekableIter::new(buf.as_slice(), &[gix_packetline::PacketLineRef::Flush], false); + encode_io::flush_to_write(&mut buf).await?; + let mut stream = StreamingPeekableIter::new(buf.as_slice(), &[gix_packetline::PacketLineRef::Flush], false); let caps = Capabilities::from_lines_with_version_detection(&mut stream) .await .expect("we can parse V0 as very special case, useful for testing stateful connections in other crates") diff --git a/justfile b/justfile index 2cf337bb411..81ab31ac8d9 100755 --- a/justfile +++ b/justfile @@ -44,10 +44,10 @@ clippy-fix: check: cargo check --workspace cargo check --no-default-features --features small + cargo check -p gix-packetline --all-features 2>/dev/null # assure compile error occurs ! cargo check --features lean-async 2>/dev/null ! cargo check -p gitoxide-core --all-features 2>/dev/null - ! cargo check -p gix-packetline --all-features 2>/dev/null ! cargo check -p gix-transport --all-features 2>/dev/null ! cargo check -p gix-protocol --all-features 2>/dev/null # warning happens if nothing found, no exit code :/ @@ -293,10 +293,6 @@ check-mode: cargo build -p internal-tools cargo run -p internal-tools -- check-mode -# Delete `gix-packetline-blocking/src` and regenerate from `gix-packetline/src` -copy-packetline: - etc/scripts/copy-packetline.sh - # Get the unique `v*` tag at `HEAD`, or fail with an error unique-v-tag: etc/scripts/unique-v-tag.sh