pathRelative function
Relative path from base to target. Roadmap #163. Audited: 2026-06-12 11:26 EDT
Implementation
String pathRelative(String base, String target) {
// Normalize then drop empty segments so leading/trailing/double slashes do not
// create phantom segments that would throw off the common-prefix match below.
final List<String> b = pathNormalize(base).split('/').where((String s) => s.isNotEmpty).toList();
final List<String> t = pathNormalize(
target,
).split('/').where((String s) => s.isNotEmpty).toList();
// Advance through the shared leading segments; i ends at the first point where
// base and target diverge (or the end of the shorter path).
int i = 0;
while (i < b.length && i < t.length && b[i] == t[i]) {
i++;
}
// Climb out of every base segment past the divergence with "..", then descend
// into target's remaining segments — that concatenation is the relative path.
final List<String> ups = List<String>.filled(b.length - i, '..');
final List<String> rest = t.sublist(i);
// Join directly rather than via pathJoin: pathJoin treats a leading '..' as a
// pop with nothing above it and discards it, which would erase the very
// climb-out segments computed above (the bug where 'a/b/c' -> 'a/b/d' yielded
// 'd' instead of '../d'). The segments are already clean — target was
// normalized, base segments were consumed, and ups are literal '..' — so no
// pathJoin re-normalization is needed. Identical paths leave both lists empty,
// which joins to '' (the documented same-location result).
return <String>[...ups, ...rest].join('/');
}