Skip to content

Commit 9096cac

Browse files
committed
feat: add better support git heads
Signed-off-by: Josef Andersson <josef.andersson@digg.se>
1 parent 47207be commit 9096cac

File tree

4 files changed

+332
-17
lines changed

4 files changed

+332
-17
lines changed

linters/commits.sh

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,7 @@ set -uo pipefail
88

99
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
1010
source "${SCRIPT_DIR}/../utils/colors.sh"
11-
12-
get_default_branch() {
13-
git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed "s@^refs/remotes/origin/@@" || echo "main"
14-
}
15-
16-
has_commits_to_check() {
17-
local default_branch="$1"
18-
local count
19-
count=$(git rev-list --count "${default_branch}..HEAD" 2>/dev/null || echo 0)
20-
[[ "$count" -gt 0 ]]
21-
}
11+
source "${SCRIPT_DIR}/../utils/git-utils.sh"
2212

2313
main() {
2414
print_header "COMMIT HEALTH (CONFORM)"
@@ -33,7 +23,7 @@ main() {
3323
return 0
3424
fi
3525

36-
if ! has_commits_to_check "$default_branch"; then
26+
if ! has_commits_since "$default_branch"; then
3727
print_info "No commits to check on ${current_branch} (compared to ${default_branch})"
3828
return 0
3929
fi

linters/secrets.sh

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,7 @@ set -uo pipefail
88

99
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
1010
source "${SCRIPT_DIR}/../utils/colors.sh"
11-
12-
get_default_branch() {
13-
git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed "s@^refs/remotes/origin/@@" || echo "main"
14-
}
11+
source "${SCRIPT_DIR}/../utils/git-utils.sh"
1512

1613
main() {
1714
print_header "SECRET SCANNING (GITLEAKS)"
@@ -31,10 +28,14 @@ main() {
3128
print_info "On default branch, scanning all commits..."
3229
gitleaks detect --source=. --verbose --redact=50
3330
gitleaks_result=$?
34-
else
31+
elif branch_exists "$default_branch"; then
3532
print_info "Scanning commits different from ${default_branch}..."
3633
gitleaks detect --source=. --log-opts="${default_branch}..HEAD" --verbose --redact=50
3734
gitleaks_result=$?
35+
else
36+
print_info "No base branch found, scanning all commits..."
37+
gitleaks detect --source=. --verbose --redact=50
38+
gitleaks_result=$?
3839
fi
3940

4041
if [[ $gitleaks_result -eq 0 ]]; then

tests/utils-git.bats

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
#!/usr/bin/env bats
2+
3+
# SPDX-FileCopyrightText: 2025 Digg - Agency for Digital Government
4+
#
5+
# SPDX-License-Identifier: MIT
6+
7+
# Tests for utils/git-utils.sh
8+
#
9+
# These tests verify get_default_branch works correctly across different
10+
# repository configurations:
11+
# - Cloned repos (with origin/HEAD)
12+
# - Local repos pushed to remote (origin/main exists but no origin/HEAD)
13+
# - Pure local repos (no remote)
14+
# - Repos with master instead of main
15+
16+
bats_require_minimum_version 1.13.0
17+
18+
load "${BATS_TEST_DIRNAME}/libs/bats-support/load.bash"
19+
load "${BATS_TEST_DIRNAME}/libs/bats-assert/load.bash"
20+
load "${BATS_TEST_DIRNAME}/libs/bats-file/load.bash"
21+
load "${BATS_TEST_DIRNAME}/test_helper.bash"
22+
23+
setup() {
24+
common_setup
25+
export DEVTOOLS_ROOT="${BATS_TEST_DIRNAME}/.."
26+
source "${DEVTOOLS_ROOT}/utils/git-utils.sh"
27+
cd "$TEST_DIR"
28+
}
29+
30+
teardown() {
31+
common_teardown
32+
}
33+
34+
# =============================================================================
35+
# Helper: Create git repo with specific configuration
36+
# =============================================================================
37+
38+
# Create a bare remote repo to simulate origin
39+
create_bare_remote() {
40+
local remote_path="${TEST_DIR}/remote.git"
41+
git init -q --bare "$remote_path"
42+
echo "$remote_path"
43+
}
44+
45+
# =============================================================================
46+
# get_default_branch tests
47+
# =============================================================================
48+
49+
@test "get_default_branch: cloned repo with origin/HEAD returns correct branch" {
50+
# Simulate a cloned repo by setting up origin/HEAD symbolic ref
51+
init_git_repo
52+
local remote_path
53+
remote_path=$(create_bare_remote)
54+
git remote add origin "$remote_path"
55+
git push -u origin main 2>/dev/null
56+
# Manually set origin/HEAD like git clone does
57+
git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/main
58+
59+
run get_default_branch
60+
61+
assert_success
62+
assert_output "main"
63+
}
64+
65+
@test "get_default_branch: local repo pushed to remote (no origin/HEAD) returns main" {
66+
# This is the failing case: git init + git remote add + git push
67+
# origin/main exists but origin/HEAD does not
68+
init_git_repo
69+
local remote_path
70+
remote_path=$(create_bare_remote)
71+
git remote add origin "$remote_path"
72+
git push -u origin main 2>/dev/null
73+
# Do NOT set origin/HEAD - this simulates the real scenario
74+
75+
run get_default_branch
76+
77+
assert_success
78+
assert_output "main"
79+
}
80+
81+
@test "get_default_branch: local repo with master pushed to remote returns master" {
82+
# Same as above but with master branch
83+
export GIT_CONFIG_NOSYSTEM=1
84+
git init -q --initial-branch=master
85+
git config user.email "test@example.com"
86+
git config user.name "Test User"
87+
echo "initial" > file.txt
88+
git add file.txt
89+
git commit -q -m "Initial commit"
90+
91+
local remote_path
92+
remote_path=$(create_bare_remote)
93+
git remote add origin "$remote_path"
94+
git push -u origin master 2>/dev/null
95+
96+
run get_default_branch
97+
98+
assert_success
99+
assert_output "master"
100+
}
101+
102+
@test "get_default_branch: pure local repo with main returns main" {
103+
# No remote at all
104+
init_git_repo
105+
106+
run get_default_branch
107+
108+
assert_success
109+
assert_output "main"
110+
}
111+
112+
@test "get_default_branch: pure local repo with master returns master" {
113+
# No remote, default branch is master
114+
export GIT_CONFIG_NOSYSTEM=1
115+
git init -q --initial-branch=master
116+
git config user.email "test@example.com"
117+
git config user.name "Test User"
118+
echo "initial" > file.txt
119+
git add file.txt
120+
git commit -q -m "Initial commit"
121+
122+
run get_default_branch
123+
124+
assert_success
125+
assert_output "master"
126+
}
127+
128+
@test "get_default_branch: prefers origin/main over local master" {
129+
# Edge case: local master exists, but origin/main also exists
130+
export GIT_CONFIG_NOSYSTEM=1
131+
git init -q --initial-branch=master
132+
git config user.email "test@example.com"
133+
git config user.name "Test User"
134+
echo "initial" > file.txt
135+
git add file.txt
136+
git commit -q -m "Initial commit"
137+
138+
local remote_path
139+
remote_path=$(create_bare_remote)
140+
git remote add origin "$remote_path"
141+
# Rename to main for push, then create local master
142+
git branch -m master main
143+
git push -u origin main 2>/dev/null
144+
git checkout -b master 2>/dev/null
145+
146+
run get_default_branch
147+
148+
assert_success
149+
assert_output "main"
150+
}
151+
152+
@test "get_default_branch: empty repo falls back to main" {
153+
# Repo with no commits yet
154+
export GIT_CONFIG_NOSYSTEM=1
155+
git init -q
156+
157+
run get_default_branch
158+
159+
assert_success
160+
assert_output "main"
161+
}
162+
163+
# =============================================================================
164+
# branch_exists tests
165+
# =============================================================================
166+
167+
@test "branch_exists: returns true for existing local branch" {
168+
init_git_repo
169+
170+
run branch_exists "main"
171+
172+
assert_success
173+
}
174+
175+
@test "branch_exists: returns false for non-existing branch" {
176+
init_git_repo
177+
178+
run branch_exists "nonexistent"
179+
180+
assert_failure
181+
}
182+
183+
@test "branch_exists: returns true for remote tracking branch" {
184+
init_git_repo
185+
local remote_path
186+
remote_path=$(create_bare_remote)
187+
git remote add origin "$remote_path"
188+
git push -u origin main 2>/dev/null
189+
190+
run branch_exists "main"
191+
192+
assert_success
193+
}
194+
195+
# =============================================================================
196+
# has_commits_since tests
197+
# =============================================================================
198+
199+
@test "has_commits_since: returns true when commits exist on feature branch" {
200+
init_git_repo
201+
git checkout -b feature 2>/dev/null
202+
echo "feature" > feature.txt
203+
git add feature.txt
204+
git commit -q -m "Feature commit"
205+
206+
run has_commits_since "main"
207+
208+
assert_success
209+
}
210+
211+
@test "has_commits_since: returns false when no commits since branch" {
212+
init_git_repo
213+
git checkout -b feature 2>/dev/null
214+
# No new commits
215+
216+
run has_commits_since "main"
217+
218+
assert_failure
219+
}
220+
221+
@test "has_commits_since: returns false when base branch does not exist" {
222+
init_git_repo
223+
224+
run has_commits_since "nonexistent"
225+
226+
assert_failure
227+
}
228+
229+
@test "has_commits_since: works with remote tracking branch as base" {
230+
init_git_repo
231+
local remote_path
232+
remote_path=$(create_bare_remote)
233+
git remote add origin "$remote_path"
234+
git push -u origin main 2>/dev/null
235+
git checkout -b feature 2>/dev/null
236+
echo "feature" > feature.txt
237+
git add feature.txt
238+
git commit -q -m "Feature commit"
239+
240+
run has_commits_since "main"
241+
242+
assert_success
243+
}

utils/git-utils.sh

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#!/usr/bin/env bash
2+
3+
# SPDX-FileCopyrightText: 2025 Digg - Agency for Digital Government
4+
#
5+
# SPDX-License-Identifier: MIT
6+
7+
# Git utility functions for devbase-check linters
8+
#
9+
# This module provides reliable git branch detection that works across:
10+
# - Cloned repositories (with origin/HEAD set)
11+
# - Locally created repositories pushed to remote (no origin/HEAD)
12+
# - Pure local repositories (no remote at all)
13+
14+
# Get the default branch name for comparison operations
15+
#
16+
# Detection order:
17+
# 1. Remote origin HEAD symbolic ref (set by git clone)
18+
# 2. Remote tracking branches origin/main or origin/master
19+
# 3. Local branches main or master
20+
# 4. Fallback to "main"
21+
#
22+
# Usage: get_default_branch
23+
# Returns: Branch name on stdout, always succeeds
24+
get_default_branch() {
25+
local branch
26+
27+
# 1. Try remote origin HEAD symbolic ref (works for cloned repos)
28+
branch=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed "s@^refs/remotes/origin/@@")
29+
if [[ -n "$branch" ]]; then
30+
echo "$branch"
31+
return 0
32+
fi
33+
34+
# 2. No symbolic HEAD - check if origin/main or origin/master tracking branch exists
35+
# This handles repos created locally and pushed (git push -u origin main)
36+
for candidate in main master; do
37+
if git show-ref --verify --quiet "refs/remotes/origin/$candidate" 2>/dev/null; then
38+
echo "$candidate"
39+
return 0
40+
fi
41+
done
42+
43+
# 3. No remote branches - check local main/master branches
44+
# This handles pure local repos with no remote
45+
for candidate in main master; do
46+
if git show-ref --verify --quiet "refs/heads/$candidate" 2>/dev/null; then
47+
echo "$candidate"
48+
return 0
49+
fi
50+
done
51+
52+
# 4. Fallback - assume main (modern git default)
53+
echo "main"
54+
}
55+
56+
# Check if a branch exists (local or remote tracking)
57+
#
58+
# Usage: branch_exists <branch_name>
59+
# Returns: 0 if exists, 1 if not
60+
branch_exists() {
61+
local branch="$1"
62+
git show-ref --verify --quiet "refs/heads/$branch" 2>/dev/null ||
63+
git show-ref --verify --quiet "refs/remotes/origin/$branch" 2>/dev/null
64+
}
65+
66+
# Check if we have any commits to compare against a base branch
67+
#
68+
# Usage: has_commits_since <base_branch>
69+
# Returns: 0 if there are commits, 1 if none or error
70+
has_commits_since() {
71+
local base_branch="$1"
72+
local count
73+
74+
# Verify the base branch exists before comparing
75+
if ! branch_exists "$base_branch"; then
76+
return 1
77+
fi
78+
79+
count=$(git rev-list --count "${base_branch}..HEAD" 2>/dev/null || echo 0)
80+
[[ "$count" -gt 0 ]]
81+
}

0 commit comments

Comments
 (0)