Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 26 additions & 4 deletions .github/workflows/nightly.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ name: nightly
# Slower checks that aren't worth running on every PR but should run before a
# release. Jobs:
# - feature-powerset: feature-flag unification, check + test suite (motivated by #2442)
# - full-tests: full nextest suite on the linux/macos/windows matrix — the
# cargo-affected safety net (see the job comment and ci.yaml's affected block)
# - full-tests: full nextest suite on the standard OS matrix and Git 2.43.0 —
# the cargo-affected safety net (see the job comment and ci.yaml's affected block)
# - release-target: PTY+shell suite on the release triples ci.yaml doesn't cover
# - check-unused-dependencies: cargo-udeps on nightly toolchain
# - minimal-versions: cargo check against minimum-version dep resolution
Expand Down Expand Up @@ -157,8 +157,9 @@ jobs:
# means affected missed something, and that must not redden main — nightly
# failures are non-blocking and Tend-fixable. Complements feature-powerset
# (Linux, feature combos) and release-target (cross triples) by covering
# the standard 3-OS matrix that neither runs. At the affected-only flip,
# ci.yaml's `test` job is deleted and this becomes the sole full run.
# the standard 3-OS matrix that neither runs. A fourth row runs the same
# job with the minimum supported Git. At the affected-only flip, ci.yaml's
# `test` job is deleted and this becomes the sole full run.
needs: gate
if: needs.gate.outputs.run == 'true'
strategy:
Expand All @@ -167,6 +168,9 @@ jobs:
include:
- os: ubuntu-24.04
name: linux
- os: ubuntu-24.04
name: git-2.43.0
git: '2.43.0'
- os: macos-15
name: macos
- os: windows-2022
Expand All @@ -178,6 +182,24 @@ jobs:

- uses: ./.github/actions/test-setup

- name: Install Git 2.43.0
if: matrix.git == '2.43.0'
shell: bash
run: |
sudo apt-get update
git_package_version="$(apt-cache madison git | awk '$3 ~ /^1:2[.]43[.]0-/ { print $3; exit }')"
if [ -z "$git_package_version" ]; then
echo "::error::Ubuntu package sources do not provide Git 2.43.0"
exit 1
fi
sudo apt-get install -y --allow-downgrades \
"git=$git_package_version" "git-man=$git_package_version"
echo "/usr/bin" >> "$GITHUB_PATH"

- name: Verify minimum Git
if: matrix.git == '2.43.0'
run: test "$(git --version)" = "git version 2.43.0"

- name: Install wt
uses: baptiste0928/cargo-install@v3
with:
Expand Down
4 changes: 2 additions & 2 deletions docs/src/content/docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,9 +278,9 @@ Confirm it with `wt config alias dry-run <name>`: if the value is already substi

To defer a variable to the nested command, wrap it as `{% raw %}{{ branch }}{% endraw %}`; for `wt step for-each`, also keep it inside a quoted `sh -c '…'` so the alias's shell doesn't word-split it. See [deferring expansion in an alias](/extending/#deferring-expansion-to-a-nested-wt-command). A repo-level variable like `{{ default_branch }}` is unaffected — it is identical in every worktree.

## Installation fails with C compilation errors
## What system dependencies are required?

Worktrunk requires Git 2.34 or newer.
Worktrunk requires Git 2.43 or newer.

Installing with Cargo and the default features also requires a C99 compiler for bash syntax highlighting. If tree-sitter or C compilation fails (C99 mode, `le16toh` undefined), install without syntax highlighting:

Expand Down
2 changes: 1 addition & 1 deletion docs/src/content/docs/step.md
Original file line number Diff line number Diff line change
Expand Up @@ -1103,7 +1103,7 @@ expected path. Untracked and gitignored files remain at the original location.
### Dirty worktrees

Linked worktrees relocate as-is — `git worktree move` carries uncommitted
changes along. Only the main worktree skips when dirty (its `git checkout`
changes along. Only the main worktree skips when dirty (its `git switch`
refuses), unless `--commit` is passed.

### Skipped worktrees
Expand Down
4 changes: 2 additions & 2 deletions plugins/worktrunk/skills/worktrunk/reference/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,9 +274,9 @@ Confirm it with `wt config alias dry-run <name>`: if the value is already substi

To defer a variable to the nested command, wrap it as `{% raw %}{{ branch }}{% endraw %}`; for `wt step for-each`, also keep it inside a quoted `sh -c '…'` so the alias's shell doesn't word-split it. See [deferring expansion in an alias](https://worktrunk.dev/extending/#deferring-expansion-to-a-nested-wt-command). A repo-level variable like `{{ default_branch }}` is unaffected — it is identical in every worktree.

## Installation fails with C compilation errors
## What system dependencies are required?

Worktrunk requires Git 2.34 or newer.
Worktrunk requires Git 2.43 or newer.

Installing with Cargo and the default features also requires a C99 compiler for bash syntax highlighting. If tree-sitter or C compilation fails (C99 mode, `le16toh` undefined), install without syntax highlighting:

Expand Down
2 changes: 1 addition & 1 deletion plugins/worktrunk/skills/worktrunk/reference/step.md
Original file line number Diff line number Diff line change
Expand Up @@ -1091,7 +1091,7 @@ expected path. Untracked and gitignored files remain at the original location.
### Dirty worktrees

Linked worktrees relocate as-is — `git worktree move` carries uncommitted
changes along. Only the main worktree skips when dirty (its `git checkout`
changes along. Only the main worktree skips when dirty (its `git switch`
refuses), unless `--commit` is passed.

### Skipped worktrees
Expand Down
4 changes: 2 additions & 2 deletions skills/worktrunk/reference/faq.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion skills/worktrunk/reference/step.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/cli/step.rs
Original file line number Diff line number Diff line change
Expand Up @@ -706,7 +706,7 @@ expected path. Untracked and gitignored files remain at the original location.
## Dirty worktrees

Linked worktrees relocate as-is — `git worktree move` carries uncommitted
changes along. Only the main worktree skips when dirty (its `git checkout`
changes along. Only the main worktree skips when dirty (its `git switch`
refuses), unless `--commit` is passed.

## Skipped worktrees
Expand Down
2 changes: 1 addition & 1 deletion src/commands/config/show.rs
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ fn render_runtime_info(out: &mut String) -> anyhow::Result<()> {
"{}",
info_message(cformat!("{cmd}: <bold>{version}</>"))
)?;
if let Ok(git_version) = crate::diagnostic::git_version() {
if let Ok(git_version) = worktrunk::git::git_version() {
writeln!(
out,
"{}",
Expand Down
8 changes: 4 additions & 4 deletions src/commands/relocate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ pub fn validate_candidates(
// along with the worktree, so for linked worktrees we don't need to
// require a clean state. The main worktree is different: it can't be
// moved with `git worktree move`, and the fallback path runs
// `git checkout <default-branch>` which refuses to switch over
// `git switch <default-branch>` which refuses to switch over
// uncommitted changes — so we still skip dirty main worktrees unless
// `--commit` was passed.
let worktree = repo.worktree_at(&candidate.wt.path);
Expand Down Expand Up @@ -658,7 +658,7 @@ impl<'a> RelocationExecutor<'a> {

/// Main worktree can't use `git worktree move`; must create new + switch.
///
/// If `worktree add` fails after the initial checkout, the main worktree
/// If `worktree add` fails after the initial switch, the main worktree
/// is left on `default_branch` rather than the original branch — the user
/// can recover with `git switch -`. Restoring it here would be best-effort
/// (no error surface) and the failure modes for `worktree add` (invalid
Expand All @@ -674,8 +674,8 @@ impl<'a> RelocationExecutor<'a> {
let main_wt = self.repo.worktree_at(self.repo.repo_path()?);

main_wt
.run_command(&["checkout", "--end-of-options", default_branch])
.with_context(|| format!("Failed to checkout default branch '{default_branch}'"))?;
.run_command(&["switch", "--end-of-options", default_branch])
.with_context(|| format!("Failed to switch to default branch '{default_branch}'"))?;

let dest = candidate.expected_path.to_string_lossy();
main_wt
Expand Down
50 changes: 1 addition & 49 deletions src/diagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,10 @@
use std::path::PathBuf;

use ansi_str::AnsiStr;
use anyhow::Context;
use color_print::cformat;
use minijinja::{Environment, context};
use worktrunk::config::ConfigFileKind;
use worktrunk::git::Repository;
use worktrunk::git::{Repository, git_version};
use worktrunk::path::format_path_for_display;
use worktrunk::shell_exec::Cmd;
use worktrunk::styling::{eprintln, hint_message, info_message, warning_message};
Expand Down Expand Up @@ -425,32 +424,6 @@ fn truncate_log(content: &str) -> String {
format!("(log truncated to last ~50KB)\n{}", &content[start..])
}

/// Get the version reported by `git --version`.
pub(crate) fn git_version() -> anyhow::Result<String> {
let output = Cmd::new("git")
.arg("--version")
.run()
.context("Failed to run git --version")?;

anyhow::ensure!(output.status.success(), "git --version failed");
parse_git_version(&output.stdout)
}

fn parse_git_version(stdout: &[u8]) -> anyhow::Result<String> {
let stdout = std::str::from_utf8(stdout)
.context("git --version returned invalid UTF-8")?
.trim();
let version = stdout
.strip_prefix("git version ")
.context("git --version returned an unexpected response")?;
anyhow::ensure!(
!version.is_empty() && !version.contains(['\r', '\n']),
"git --version returned an unexpected response"
);

Ok(version.to_string())
}

/// Render the curated environment variables ([`DIAGNOSTIC_ENV_VARS`]) plus git's
/// resolved `core.pager` as `KEY=value` lines for the diagnostic report.
///
Expand Down Expand Up @@ -633,27 +606,6 @@ mod tests {
assert!(render_trace_profile("not a trace line\nanother line\n").is_none());
}

#[test]
fn test_parse_git_version_requires_standard_single_line_output() {
assert_eq!(
parse_git_version(b"git version 2.47.1\n").unwrap(),
"2.47.1"
);
assert_eq!(
parse_git_version(b"git version 2.39.5 (Apple Git-154)\n").unwrap(),
"2.39.5 (Apple Git-154)"
);

for malformed in [
b"2.47.1".as_slice(),
b"git version \n",
b"git version 2.47.1\nextra",
b"git version 2.\xff",
] {
assert!(parse_git_version(malformed).is_err());
}
}

#[test]
fn test_truncate_log_small_content() {
let content = "small log content";
Expand Down
3 changes: 3 additions & 0 deletions src/git/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub mod remote_ref;
pub mod remove;
mod repository;
mod url;
mod version;

#[cfg(test)]
mod test;
Expand Down Expand Up @@ -87,6 +88,8 @@ pub use repository::{
pub use url::parse_owner_repo;
pub use url::{GitRemoteUrl, GitRepoInfo, GitRepoProvider};
pub(crate) use url::{canonical_url_path_segment, url_path_segments_eq};
pub use version::{git_version, require_minimum_git};

/// Why branch content is considered integrated into the target branch.
///
/// Used by both `wt list` (for status symbols) and `wt remove` (for messages).
Expand Down
Loading
Loading