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
103 changes: 65 additions & 38 deletions bash/git-check
Original file line number Diff line number Diff line change
Expand Up @@ -134,48 +134,75 @@ declare -a UNCOMMITTED=()
declare -a RELEASABLE=()
declare -a DEVELOPMENT=()
for repo in $(find "${base_path}${EXTRA}" -mindepth "${DEPTH}" -maxdepth "${DEPTH}" -name .git -exec dirname {} \; | sort); do
cd "$repo" || exit 1
base_name=$(base_name_for_repo "$base_path" "$repo")
orgname=$(dirname "$base_name")
branch_name=$(get_branch_name)
dirty_version=$(git::describe --tags 2>/dev/null)-dirty
version=$(git::describe --dirty --tags 2>/dev/null)
long_version=$(git::describe --long --tags 2>/dev/null)
# Per-repo work runs in a subshell so a failed or partial cd never leaves
# the outer script in another repository's directory (SUR-2470).
block=$(
cd "$repo" || exit 1
base_name=$(base_name_for_repo "$base_path" "$repo")
orgname=$(dirname "$base_name")
branch_name=$(get_branch_name)
dirty_version=$(git::describe --tags 2>/dev/null)-dirty
version=$(git::describe --dirty --tags 2>/dev/null)
long_version=$(git::describe --long --tags 2>/dev/null)

status=$(git::cmd status --short | wc -l)
fetch_if_possible "$base_name"
if [ "$status" = "0" ]; then
pull_if_different "$base_name" "$branch_name"
else
log::notice "Examining $base_name: dirty"
fi

len=${#base_name}
branch_len=${#branch_name}
if [ -z "$CLIENT" ]; then
pr_count=$(get_gh_pr_count "$orgname" "$base_name")
elif [ "$CLIENT" = "TASE" ]; then
pr_count=$(get_bb_pr_count "$CLIENT" "$base_name")
else
pr_count='.'
fi

# Tab-delimited record: name TAB pr_count TAB branch TAB version. Tabs
# are forbidden in any of these fields (repo paths and branch names use
# forward-slashes, not tabs), so the encoding is unambiguous.
if [ -z "$version" ]; then
version=$(git::cmd rev-parse --short HEAD)
printf '%s\n' UNCOMMITTED
printf '%s\n' "$(printf '%s\t%s\t%s\t%s' "$base_name" "$pr_count" "$branch_name" "$version")"
elif [ "$version" = "$dirty_version" ]; then
printf '%s\n' UNCOMMITTED
printf '%s\n' "$(printf '%s\t%s\t%s\t%s' "$base_name" "$pr_count" "$branch_name" "$version")"
elif [ "$version" != "$long_version" ]; then
printf '%s\n' RELEASABLE
printf '%s\n' "$(printf '%s\t%s\t%s\t%s' "$base_name" "$pr_count" "$branch_name" "$version")"
else
printf '%s\n' DEVELOPMENT
printf '%s\n' "$(printf '%s\t%s\t%s\t%s' "$base_name" "$pr_count" "$branch_name" "$version")"
fi
) || exit 1

bucket=${block%%$'\n'*}
record=${block#*$'\n'}
case "$bucket" in
UNCOMMITTED)
UNCOMMITTED+=("$record")
;;
RELEASABLE)
RELEASABLE+=("$record")
;;
DEVELOPMENT)
DEVELOPMENT+=("$record")
;;
*)
log::error "git-check: unexpected bucket from repo processing: $bucket"
exit 1
;;
esac

IFS=$'\t' read -r name _ branch _ <<<"$record"
len=${#name}
branch_len=${#branch}
max_repo_len=$((len > max_repo_len ? len : max_repo_len))
max_branch_len=$((branch_len > max_branch_len ? branch_len : max_branch_len))
status=$(git::cmd status --short | wc -l)
fetch_if_possible "$base_name"
if [ "$status" = "0" ]; then
pull_if_different "$base_name" "$branch_name"
else
log::notice "Examining $base_name: dirty"
fi

if [ -z "$CLIENT" ]; then
pr_count=$(get_gh_pr_count "$orgname" "$base_name")
elif [ "$CLIENT" = "TASE" ]; then
pr_count=$(get_bb_pr_count "$CLIENT" "$base_name")
else
pr_count='.'
fi

# Tab-delimited record: name TAB pr_count TAB branch TAB version. Tabs
# are forbidden in any of these fields (repo paths and branch names use
# forward-slashes, not tabs), so the encoding is unambiguous.
if [ -z "$version" ]; then
version=$(git::cmd rev-parse --short HEAD)
UNCOMMITTED+=("$(printf '%s\t%s\t%s\t%s' "$base_name" "$pr_count" "$branch_name" "$version")")
elif [ "$version" = "$dirty_version" ]; then
UNCOMMITTED+=("$(printf '%s\t%s\t%s\t%s' "$base_name" "$pr_count" "$branch_name" "$version")")
elif [ "$version" != "$long_version" ]; then
RELEASABLE+=("$(printf '%s\t%s\t%s\t%s' "$base_name" "$pr_count" "$branch_name" "$version")")
else
DEVELOPMENT+=("$(printf '%s\t%s\t%s\t%s' "$base_name" "$pr_count" "$branch_name" "$version")")
fi
done

function output() {
Expand Down
70 changes: 70 additions & 0 deletions tests/aws.bats
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,73 @@ setup() {
# refresh_scan must NOT have been called — tag '1.2.3' is not '1X2X3' literally
[[ "$output" != *"CALLED"* ]]
}

# SUR-2477: aws::refresh_scan cache window and jq floor on fractional timestamps

@test "aws::refresh_scan skips aws::scan_image when scan is COMPLETE and within cache window (SUR-2477)" {
run bash -c "
export LOG_DISABLE_INFO=true
source '$REPO_ROOT/bash/includer.sh'
@include aws
date() { printf '%s\n' '2000000000'; }
aws::_describe_findings() {
printf '%s\n' '{\"imageScanStatus\":{\"status\":\"COMPLETE\"},\"imageScanFindings\":{\"imageScanCompletedAt\":1999990000.987}}'
}
aws::scan_image() { echo SCAN_IMAGE_CALLED; return 0; }
aws::refresh_scan myrepo mytag 7
"
[ "$status" -eq 0 ]
[[ "$output" != *"SCAN_IMAGE_CALLED"* ]]
}

@test "aws::refresh_scan calls aws::scan_image when COMPLETE scan is older than cache window (SUR-2477)" {
run bash -c "
export LOG_DISABLE_INFO=true
source '$REPO_ROOT/bash/includer.sh'
@include aws
date() { printf '%s\n' '2000000000'; }
aws::_describe_findings() {
printf '%s\n' '{\"imageScanStatus\":{\"status\":\"COMPLETE\"},\"imageScanFindings\":{\"imageScanCompletedAt\":1000.1}}'
}
aws::scan_image() { echo SCAN_IMAGE_CALLED; return 0; }
aws::refresh_scan myrepo mytag 7
"
[ "$status" -eq 0 ]
[[ "$output" == *"SCAN_IMAGE_CALLED"* ]]
}

@test "aws::refresh_scan calls aws::scan_image when scan is not COMPLETE (SUR-2477)" {
run bash -c "
export LOG_DISABLE_INFO=true
source '$REPO_ROOT/bash/includer.sh'
@include aws
date() { printf '%s\n' '2000000000'; }
aws::_describe_findings() {
printf '%s\n' '{\"imageScanStatus\":{\"status\":\"IN_PROGRESS\"}}'
}
aws::scan_image() { echo SCAN_IMAGE_CALLED; return 0; }
aws::refresh_scan myrepo mytag 7
"
[ "$status" -eq 0 ]
[[ "$output" == *"SCAN_IMAGE_CALLED"* ]]
}

@test "aws::refresh_scan uses jq floor so fractional scan time does not spuriously refresh (SUR-2477)" {
run bash -c "
export LOG_DISABLE_INFO=true
source '$REPO_ROOT/bash/includer.sh'
@include aws
# Window edge: earliest = 2000000000 - 604800 = 1999395200
# Integer part 1999395200 is not fresh; 1999395200.001 floor is still 1999395200,
# so earliest < completedAt is false — would refresh without careful equality.
# Use completedAt just above earliest as integer: 1999395201.999 floors to 1999395201.
date() { printf '%s\n' '2000000000'; }
aws::_describe_findings() {
printf '%s\n' '{\"imageScanStatus\":{\"status\":\"COMPLETE\"},\"imageScanFindings\":{\"imageScanCompletedAt\":1999395201.999}}'
}
aws::scan_image() { echo SCAN_IMAGE_CALLED; return 0; }
aws::refresh_scan myrepo mytag 7
"
[ "$status" -eq 0 ]
[[ "$output" != *"SCAN_IMAGE_CALLED"* ]]
}
87 changes: 87 additions & 0 deletions tests/docker.bats
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,90 @@ run_cp() {
[ "$status" -ne 0 ]
[[ "$output" == *"No docker credentials"* ]] || [[ "$output" == *"docker login"* ]]
}

# SUR-2478: docker::repo_tags_has and docker::cp_if_different same-image path

@test "docker::repo_tags_has returns 0 when inspect lists exact destination tag (SUR-2478)" {
run bash -c "
export LOG_DISABLE_INFO=true LOG_DISABLE_DEBUG=true LOGFILE_DISABLE=true
source '$REPO_ROOT/bash/includer.sh'
@include docker
docker::inspect() {
printf '%s\n' '[{\"RepoTags\":[\"registry.example/a:v1\",\"registry.example/b:v2\"]}]'
}
docker::repo_tags_has 'registry.example/a:v1' 'registry.example/b:v2'
"
[ "$status" -eq 0 ]
}

@test "docker::repo_tags_has returns 0 when tag matches after stripping index.docker.io prefix (SUR-2478)" {
run bash -c "
export LOG_DISABLE_INFO=true LOG_DISABLE_DEBUG=true LOGFILE_DISABLE=true
source '$REPO_ROOT/bash/includer.sh'
@include docker
docker::inspect() {
printf '%s\n' '[{\"RepoTags\":[\"foo/bar:tag\"]}]'
}
docker::repo_tags_has 'foo/bar:tag' 'index.docker.io/foo/bar:tag'
"
[ "$status" -eq 0 ]
}

@test "docker::repo_tags_has returns 1 when no RepoTags entry matches destination (SUR-2478)" {
run bash -c "
export LOG_DISABLE_INFO=true LOG_DISABLE_DEBUG=true LOGFILE_DISABLE=true
source '$REPO_ROOT/bash/includer.sh'
@include docker
docker::inspect() {
printf '%s\n' '[{\"RepoTags\":[\"registry.example/a:v1\"]}]'
}
docker::repo_tags_has 'registry.example/a:v1' 'registry.example/other:v9'
"
[ "$status" -ne 0 ]
}

@test "docker::cp_if_different skips tag and push when repo_tags_has matches (SUR-2478)" {
run bash -c "
export LOG_DISABLE_INFO=true LOG_DISABLE_DEBUG=true LOGFILE_DISABLE=true
source '$REPO_ROOT/bash/includer.sh'
@include docker
docker::pull() { return 0; }
docker::inspect() {
printf '%s\n' '[{\"RepoTags\":[\"from-img\",\"to-img\"]}]'
}
tag_calls=0
push_calls=0
docker::tag() { tag_calls=\$((tag_calls + 1)); return 0; }
docker::push() { push_calls=\$((push_calls + 1)); return 0; }
docker::cp_if_different from-img to-img
rc=\$?
echo \"tag_calls=\$tag_calls push_calls=\$push_calls rc=\$rc\"
"
[ "$status" -eq 0 ]
[[ "$output" == *"tag_calls=0"* ]]
[[ "$output" == *"push_calls=0"* ]]
[[ "$output" == *"rc=0"* ]]
}

@test "docker::cp_if_different pulls, tags, and pushes when images differ (SUR-2478)" {
run bash -c "
export LOG_DISABLE_INFO=true LOG_DISABLE_DEBUG=true LOGFILE_DISABLE=true
source '$REPO_ROOT/bash/includer.sh'
@include docker
docker::pull() { return 0; }
docker::inspect() {
printf '%s\n' '[{\"RepoTags\":[\"from-img\"]}]'
}
tag_calls=0
push_calls=0
docker::tag() { tag_calls=\$((tag_calls + 1)); return 0; }
docker::push() { push_calls=\$((push_calls + 1)); return 0; }
docker::cp_if_different from-img to-img
rc=\$?
echo \"tag_calls=\$tag_calls push_calls=\$push_calls rc=\$rc\"
"
[ "$status" -eq 0 ]
[[ "$output" == *"tag_calls=1"* ]]
[[ "$output" == *"push_calls=1"* ]]
[[ "$output" == *"rc=0"* ]]
}
5 changes: 5 additions & 0 deletions tests/git-check.bats
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
# loop variable because $2 was assigned without `local`. Drive git-check
# end-to-end against two repos on different branches and assert each
# per-repo line carries the correct branch.
#
# SUR-2470: per-repo work now runs in a subshell so a failed cd never leaves
# the outer loop in another repo's directory. The multi-repo test below
# provides indirect regression coverage: if iteration state bled between repos
# the branch columns would be wrong or absent.

setup() {
load 'helpers.bash'
Expand Down
Loading