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 @@
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
}