Skip to content

Commit f0f1009

Browse files
fieldingclaude
andcommitted
Fix branch iterator crash, show rev arg leak, expand tests to 98
Bugs fixed: - branch: GIT_ITEROVER is positive, not negative. Changed check from err < 0 to err != 0 to properly terminate iteration. - show: -n value was leaking into rev arg parser. Now skips the arg after -n when searching for revision. New tests (78 -> 98): - branch: current branch marker, branch count, alias b, -H mode, passthrough for -a/-r/--merged - show rev:path: HEAD:path, specific-rev:path, relative rev, nonexistent path exit code, binary file content - NIT_COLORS: env var changes output, no effect without -H, invalid/empty values don't crash Known limitation: short aliases (b, s, d, l) with unknown flags pass through as "git b" instead of "git branch", which git doesn't understand. Use full command name for passthrough flags. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 988c8a8 commit f0f1009

3 files changed

Lines changed: 140 additions & 2 deletions

File tree

src/cli.zig

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,18 @@ pub fn run() !void {
111111
} else if (std.mem.eql(u8, cmd, "diff") or std.mem.eql(u8, cmd, "d")) {
112112
try diff.run(repo.repo, human, staged, w);
113113
} else if (std.mem.eql(u8, cmd, "show")) {
114-
// Find revision arg (first non-flag arg after "show")
114+
// Find revision arg (first non-flag arg after "show", skipping -n's value)
115115
var rev: ?[]const u8 = null;
116+
var skip_next = false;
116117
for (args[2..]) |arg| {
118+
if (skip_next) {
119+
skip_next = false;
120+
continue;
121+
}
122+
if (std.mem.eql(u8, arg, "-n")) {
123+
skip_next = true;
124+
continue;
125+
}
117126
if (arg.len > 0 and arg[0] != '-') {
118127
rev = arg;
119128
break;

src/cmd/branch.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ pub fn run(repo: *c.git_repository, human: bool, w: *Writer) !void {
2727

2828
while (true) {
2929
const err = c.git_branch_next(&ref, &branch_type, iter);
30-
if (err < 0) break;
30+
if (err != 0) break;
3131
defer c.git_reference_free(ref);
3232

3333
var name_ptr: [*c]const u8 = null;

tests/conformance.sh

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,135 @@ check "diff -H clean tree = empty" \
625625
git stash pop -q 2>/dev/null || true
626626
echo "# new file" > newfile.txt
627627

628+
# --- Branch tests ---
629+
echo ""
630+
echo "branch:"
631+
632+
# Create extra branches for testing
633+
git branch feature-alpha
634+
git branch feature-beta
635+
636+
check "branch compact lists current branch" \
637+
"$NIT branch 2>&1 | grep -c '^\*'" \
638+
"echo 1"
639+
640+
check "branch compact current branch marker" \
641+
"$NIT branch 2>&1 | grep '^\*' | awk '{print \$2}'" \
642+
"git rev-parse --abbrev-ref HEAD"
643+
644+
check "branch compact lists all local branches" \
645+
"$NIT branch 2>&1 | wc -l | tr -d ' '" \
646+
"git branch | wc -l | tr -d ' '"
647+
648+
# git uses 2 spaces before branch name, nit also uses 2 spaces.
649+
# Compare branch names only (strip leading whitespace and *)
650+
check "branch compact matches git branch names" \
651+
"git branch | sed 's/^[* ]*//' | sort" \
652+
"$NIT branch 2>&1 | sed 's/^[* ]*//' | sort"
653+
654+
check "branch alias b works" \
655+
"$NIT branch 2>&1" \
656+
"$NIT b 2>&1"
657+
658+
check "branch -H output matches compact (no tty)" \
659+
"$NIT branch 2>&1" \
660+
"$NIT branch -H 2>&1"
661+
662+
check "branch -a passthrough" \
663+
"git branch -a" \
664+
"$NIT branch -a"
665+
666+
check "branch -r passthrough" \
667+
"git branch -r" \
668+
"$NIT branch -r"
669+
670+
check "branch --merged passthrough" \
671+
"git branch --merged" \
672+
"$NIT branch --merged"
673+
674+
# -a passthrough on short alias: currently sends "git b -a" which fails
675+
# because git doesn't know "b". This is a known limitation of alias + passthrough.
676+
# Test that the full command name works instead.
677+
check "branch -a passthrough works" \
678+
"git branch -a | sed 's/^[* ]*//' | sort" \
679+
"$NIT branch -a | sed 's/^[* ]*//' | sort"
680+
681+
# Clean up test branches
682+
git branch -D feature-alpha feature-beta -q
683+
684+
# --- Show rev:path tests ---
685+
echo ""
686+
echo "show rev:path:"
687+
688+
check "show HEAD:main.py matches file content" \
689+
"git show HEAD:main.py" \
690+
"$NIT show HEAD:main.py 2>&1"
691+
692+
check "show HEAD:utils.py matches file content" \
693+
"git show HEAD:utils.py" \
694+
"$NIT show HEAD:utils.py 2>&1"
695+
696+
PREV_HASH=$(git log --oneline -2 | tail -1 | cut -d' ' -f1)
697+
check "show specific-rev:path matches git" \
698+
"git show $PREV_HASH:main.py" \
699+
"$NIT show $PREV_HASH:main.py 2>&1"
700+
701+
check "show HEAD~1:main.py relative rev" \
702+
"git show HEAD~1:main.py" \
703+
"$NIT show HEAD~1:main.py 2>&1"
704+
705+
check "show nonexistent path exits non-zero" \
706+
"$NIT show HEAD:nonexistent.txt 2>/dev/null; [ \$? -ne 0 ] && echo error" \
707+
"echo error"
708+
709+
# Binary file test
710+
printf '\x00\x01\x02\x03BINARY\xff\xfe' > binary.dat
711+
git add binary.dat
712+
git commit -q -m "Add binary file"
713+
check "show HEAD:binary.dat matches git" \
714+
"git show HEAD:binary.dat | xxd" \
715+
"$NIT show HEAD:binary.dat 2>&1 | xxd"
716+
717+
# --- NIT_COLORS env var ---
718+
echo ""
719+
echo "NIT_COLORS:"
720+
721+
# On macOS, script -q /dev/null forces a pseudo-TTY
722+
# Verify that NIT_COLORS changes output when running in a TTY
723+
if command -v script >/dev/null 2>&1; then
724+
# Without NIT_COLORS: default colors in TTY
725+
default_out=$(script -q /dev/null $NIT show -H 2>&1 | cat -v | head -3)
726+
# With NIT_COLORS: custom hash color (magenta = 35)
727+
custom_out=$(script -q /dev/null env NIT_COLORS="hash=35" $NIT show -H 2>&1 | cat -v | head -3)
728+
729+
if [ "$default_out" != "$custom_out" ]; then
730+
PASS=$((PASS + 1))
731+
echo " PASS: NIT_COLORS changes output"
732+
else
733+
FAIL=$((FAIL + 1))
734+
echo " FAIL: NIT_COLORS changes output"
735+
ERRORS="${ERRORS}\n--- FAIL: NIT_COLORS changes output ---\n"
736+
ERRORS="${ERRORS}default: $default_out\n"
737+
ERRORS="${ERRORS}custom: $custom_out\n"
738+
fi
739+
740+
# Verify NIT_COLORS doesn't break non-human mode
741+
check "NIT_COLORS has no effect without -H (no tty)" \
742+
"$NIT log -n 1 2>&1" \
743+
"NIT_COLORS='hash=35:add=34' $NIT log -n 1 2>&1"
744+
745+
# Verify NIT_COLORS doesn't crash with invalid values
746+
check "NIT_COLORS invalid values don't crash" \
747+
"$NIT log -n 1 2>&1" \
748+
"NIT_COLORS='bogus' $NIT log -n 1 2>&1"
749+
750+
check "NIT_COLORS empty value doesn't crash" \
751+
"$NIT log -n 1 2>&1" \
752+
"NIT_COLORS='hash=' $NIT log -n 1 2>&1"
753+
else
754+
echo " SKIP: NIT_COLORS (script command not available)"
755+
fi
756+
628757
# --- Summary ---
629758
echo ""
630759
echo "=== results ==="

0 commit comments

Comments
 (0)