Text written by Claude Opus 5. (I tested the scripts on macOS.)
Read the relative path stored in .jj/repo and resolve it against the current working directory.
Base directory: the path in .jj/repo is relative to the .jj directory, not to the CWD. All snippets below join against .jj/ for that reason.
rel=$(<.jj/repo)
abs=$(realpath ".jj/$rel") # portable across GNU and BSD$(<file)is a bash builtin read — nocatprocess. Like$(...), it strips trailing newlines.- Version: bash 5.3 (July 2025) is current; nothing here needs past 4.x.
realpath is not in POSIX and there are two incompatible implementations. The flags are the difference, not the command.
| GNU coreutils | BSD (macOS, FreeBSD) | |
|---|---|---|
| ships with | Linux distros, brew install coreutils → grealpath |
macOS base system, *BSD |
-m (no component need exist) |
yes | no — errors out |
-e (all components must exist) |
yes | no |
-s / --no-symlinks (lexical only) |
yes | no |
-q (suppress warnings) |
no | yes |
-- end-of-options |
yes | not reliably |
| no flags: missing final component | error | accepted |
| no flags: missing intermediate component | error | error |
So bare realpath runs on both, but with different behavior at the edges: on BSD a nonexistent leaf resolves fine; on GNU it fails. If the leaf is expected to exist — as .jj/repo's target normally does — the two agree and bare realpath is the portable choice.
Writing for both:
if realpath -m . >/dev/null 2>&1; then
abs=$(realpath -m -- ".jj/$rel") # GNU
else
abs=$(realpath ".jj/$rel") # BSD
fiOr sidestep the question entirely with the POSIX cd/pwd -P form at the bottom of this document, which needs no external realpath at all.
abs=".jj/$(<.jj/repo)"
abs=${abs:A}:A— absolute + normalize + resolve symlinks, no existence requirement.:a— same, lexical only (no symlink resolution).:P— full physical resolution (zsh ≥ 5.3).- Build the full string before applying the modifier;
:a/:Aresolve relative paths against$PWD. - Version: 5.9.x is the current stable line (5.9.1, May 2026); 5.10 is in test.
set rel (cat .jj/repo)
set abs (path resolve .jj/$rel)- No
$(<file)equivalent;catis idiomatic. Command substitution strips trailing newlines. path resolve(fish ≥ 3.5): absolute + normalize + resolve symlinks; keeps non-existent trailing components.path normalizeis lexical-only, but does not make a relative path absolute.- If the file had multiple lines,
set rel (...)produces a list and.jj/$relexpands to one path per element.set rel (cat .jj/repo | head -n1)avoids that. - Version: fish 4.8.1 (July 2026); 4.0 (Feb 2025) was the Rust rewrite.
let rel = (open .jj/repo | str trim)
let abs = ('.jj' | path join $rel | path expand)openon an extensionless file returns a string;str trimremoves the trailing newline.open --rawif you want to bypass content-type inference.path expand— absolute + normalize + resolve symlinks, relative to CWD.path expand --no-symlink(-n) skips symlink resolution;--strict(-s) errors if the path doesn't exist.- Version: 0.114.x (July 2026).
| lexical only | + resolve symlinks | requires existence | |
|---|---|---|---|
| bash + GNU realpath | realpath -sm |
realpath -m |
realpath -e |
| bash + BSD realpath | — | realpath |
— ² |
| zsh | ${p:a} |
${p:A} |
— |
| fish | path normalize¹ |
path resolve |
— |
| nushell | path expand -n |
path expand |
path expand -s |
¹ does not absolutize. ² BSD has no existence flags: intermediate components must exist, the final one need not.
rel=$(cat .jj/repo)
abs=$(cd ".jj/$(dirname "$rel")" && pwd -P)/$(basename "$rel")cd, pwd -P, dirname, basename are all POSIX; realpath is not (and BSD/macOS realpath lacks -m). This form resolves symlinks and fails if the directory doesn't exist.