diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b6b2a25..51fce6e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,9 +45,10 @@ jobs: - name: Test run: cargo test --verbose - # Guard Windows portability: the code must compile and the pure unit tests - # (ident / render / engine) must pass. The git-driven integration and view - # tests assume a POSIX shell and aren't run here yet. + # Verify Windows portability: the code must compile AND the full test suite + # (including the git-driven integration + view tests, which exercise the + # `cp`/binary-based rebase editors) must pass. Tests self-skip any git feature + # the runner's git lacks. windows: name: Windows runs-on: windows-latest @@ -56,7 +57,9 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 + - name: Git version + run: git --version - name: Build run: cargo build --verbose - - name: Test (portable unit tests) - run: cargo test --lib -- --skip view + - name: Test + run: cargo test --verbose diff --git a/README.md b/README.md index b3c8a4a..5f53981 100644 --- a/README.md +++ b/README.md @@ -52,9 +52,9 @@ works immediately (git's standard subcommand mechanism). `git queue setup` then walks through the optional extras interactively (see below). Prefer a prebuilt binary? Each [GitHub -release](https://github.com/freshtonic/git-queue/releases) ships archives and a -shell installer (built by [dist](https://opensource.axo.dev/cargo-dist/)) for -Linux and macOS (x86-64 and arm64): +release](https://github.com/freshtonic/git-queue/releases) ships archives and +install scripts (built by [dist](https://opensource.axo.dev/cargo-dist/)) for +macOS, Linux, and Windows. On macOS/Linux: ```sh curl --proto '=https' --tlsv1.2 -LsSf \ @@ -67,9 +67,14 @@ On macOS or Linux you can also use Homebrew: brew install freshtonic/git-queue/git-queue ``` -Or just download an archive, extract it, and put `git-queue` on your `PATH`. -(Windows isn't packaged as a prebuilt binary yet — build it with `cargo install -git-queue`. Windows support is still in progress; see issue #11.) +On Windows, use the PowerShell installer: + +```powershell +powershell -ExecutionPolicy ByPass -c "irm https://github.com/freshtonic/git-queue/releases/latest/download/git-queue-installer.ps1 | iex" +``` + +Or just download an archive for your platform (macOS/Linux `.tar.xz`, Windows +`.zip`), extract it, and put `git-queue` on your `PATH`. ### `git queue setup` diff --git a/dist-workspace.toml b/dist-workspace.toml index ef7720b..2539dc1 100644 --- a/dist-workspace.toml +++ b/dist-workspace.toml @@ -8,15 +8,14 @@ cargo-dist-version = "0.32.0" # CI backends to support ci = "github" # The installers to generate for each app. -# PowerShell/Windows are omitted until git-queue is Windows-portable (issue #11). -installers = ["homebrew", "shell"] +installers = ["homebrew", "shell", "powershell"] # Homebrew tap to publish the generated formula to. dist pushes the formula on # release using a token in the HOMEBREW_TAP_TOKEN secret (write access to the tap). tap = "freshtonic/homebrew-git-queue" # Actually run the formula-publishing job (not just generate the formula locally). publish-jobs = ["homebrew"] # Target platforms to build apps for (Rust target-triple syntax). -targets = ["aarch64-apple-darwin", "aarch64-unknown-linux-gnu", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu"] +targets = ["aarch64-apple-darwin", "aarch64-unknown-linux-gnu", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc"] # Path that installers should place binaries in install-path = "CARGO_HOME" # Where to host releases diff --git a/index.html b/index.html index 80e08d9..2dad869 100644 --- a/index.html +++ b/index.html @@ -524,6 +524,10 @@

$ curl --proto '=https' --tlsv1.2 -LsSf \ https://github.com/freshtonic/git-queue/releases/latest/download/git-queue-installer.sh | sh +
+
install script · recommended
+
> powershell -ExecutionPolicy ByPass -c "irm https://github.com/freshtonic/git-queue/releases/latest/download/git-queue-installer.ps1 | iex"
+
homebrew
$ brew install freshtonic/git-queue/git-queue
@@ -532,12 +536,9 @@

cargo · from crates.io

$ cargo install git-queue
-
+
prebuilt binary
-

Grab an archive for your platform (x86-64 or arm64) from the latest GitHub release, extract it, and put git-queue on your PATH. Archives and the install script are built by dist.

-
-
-

Windows support is still in progress (#11) — no prebuilt Windows binary or install script yet. cargo install git-queue above builds it from source.

+

Grab an archive for your platform from the latest GitHub release (macOS/Linux x86-64 & arm64 .tar.xz, Windows x86-64 .zip), extract it, and put git-queue on your PATH. Archives and install scripts are built by dist.

Because the binary is named git-queue, Git runs git queue … for you via its standard subcommand mechanism. Then git queue setup installs the man page — afterwards both man git-queue and git queue --help work (Git routes --help to the man page; use git queue help for inline output).

@@ -764,6 +765,7 @@

, ) -> Result<()> { let exe = std::env::current_exe().context("cannot locate the git-queue executable")?; + let exe = shell_path(&exe); let mut initial = Command::new("git"); initial.args([ "-c", @@ -570,10 +571,7 @@ pub(crate) fn rebase_reorder_persist( quiet_git(&mut initial); // Our own binary rewrites the todo; the spec travels via the environment. initial - .env( - "GIT_SEQUENCE_EDITOR", - format!("\"{}\" reorder-todo", exe.display()), - ) + .env("GIT_SEQUENCE_EDITOR", format!("\"{exe}\" reorder-todo")) .env("GIT_QUEUE_MOVE_SHAS", move_shas.join(" ")) .env("GIT_QUEUE_MOVE_AFTER", after.unwrap_or("")); let _ = initial.status().context("failed to spawn `git rebase`")?; @@ -604,7 +602,7 @@ pub(crate) fn rebase_with_todo_stop(base: &str, top_branch: &str, todo: &str) -> // git invokes `$GIT_SEQUENCE_EDITOR `, so `cp ` becomes // `cp ` — overwriting git's generated todo with ours. - let editor = format!("cp {}", shell_single_quote(&todo_path.to_string_lossy())); + let editor = format!("cp {}", shell_single_quote(&shell_path(&todo_path))); let mut cmd = Command::new("git"); cmd.args([ "rebase", @@ -638,6 +636,14 @@ fn shell_single_quote(s: &str) -> String { format!("'{}'", s.replace('\'', r"'\''")) } +/// Render a filesystem path for embedding in a shell command that git will run. +/// git executes `GIT_SEQUENCE_EDITOR`/`GIT_EDITOR` through a POSIX shell — its +/// bundled `sh` on Windows — which accepts forward slashes but mishandles +/// backslash separators, so normalise them. On Unix this is a no-op. +fn shell_path(p: &std::path::Path) -> String { + p.to_string_lossy().replace('\\', "/") +} + /// Rewrite `base..top_branch` with a caller-supplied todo (one commit marked /// `reword`) and supply the new commit message from `message`. Both are /// installed via `cp`-based editors, so no helper binary is needed. A reword is @@ -655,8 +661,8 @@ pub(crate) fn rebase_with_todo_message( std::fs::write(&todo_path, todo).context("failed to stage the rebase todo")?; std::fs::write(&msg_path, message).context("failed to stage the commit message")?; - let seq_editor = format!("cp {}", shell_single_quote(&todo_path.to_string_lossy())); - let msg_editor = format!("cp {}", shell_single_quote(&msg_path.to_string_lossy())); + let seq_editor = format!("cp {}", shell_single_quote(&shell_path(&todo_path))); + let msg_editor = format!("cp {}", shell_single_quote(&shell_path(&msg_path))); let mut cmd = Command::new("git"); cmd.args([ "rebase", @@ -700,8 +706,8 @@ pub(crate) fn rebase_squash_stop( std::fs::write(&todo_path, todo).context("failed to stage the rebase todo")?; std::fs::write(&msg_path, message).context("failed to stage the commit message")?; - let seq_editor = format!("cp {}", shell_single_quote(&todo_path.to_string_lossy())); - let msg_editor = format!("cp {}", shell_single_quote(&msg_path.to_string_lossy())); + let seq_editor = format!("cp {}", shell_single_quote(&shell_path(&todo_path))); + let msg_editor = format!("cp {}", shell_single_quote(&shell_path(&msg_path))); let mut cmd = Command::new("git"); cmd.args([ "rebase", @@ -799,7 +805,7 @@ fn drive_cherry_pick_to_completion(what: &str) -> Result<()> { /// trailer and exits). Content is untouched, so no conflicts can arise. pub(crate) fn rebase_stamp_ids(upstream: &str, branch: &str, shas: &[String]) -> Result<()> { let exe = std::env::current_exe().context("cannot locate the git-queue executable")?; - let exe = exe.display(); + let exe = shell_path(&exe); let mut initial = Command::new("git"); initial.args(["rebase", "-i", "--update-refs", upstream, branch]); initial diff --git a/tests/integration.rs b/tests/integration.rs index bf93bdf..aafb9ee 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -59,6 +59,11 @@ fn new_repo() -> TempDir { // with a key that isn't loaded. git(dir, &["config", "commit.gpgsign", "false"]); git(dir, &["config", "tag.gpgsign", "false"]); + // Pin line endings so working-tree byte assertions hold cross-platform + // (git-for-windows defaults core.autocrlf=true, which would rewrite LF→CRLF + // on checkout). + git(dir, &["config", "core.autocrlf", "false"]); + git(dir, &["config", "core.eol", "lf"]); commit(dir, "seed.txt"); tmp }