Skip to content

chore(ci): add verify-commits make target #138

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 18, 2021
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
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,10 @@ verify-manifests: manifests
verify-nested-vendor:
./scripts/check-staging-vendor.sh

.PHONY: verify-commits
verify-commits:
./scripts/verify_commits.sh $(PULL_BASE_SHA) # see https://github.com/kubernetes/test-infra/blob/master/prow/jobs.md#job-environment-variables

.PHONY: verify
verify:
echo "Checking for unstaged root vendor changes"
Expand All @@ -155,6 +159,8 @@ verify:
$(MAKE) verify-manifests
echo "Checking for unsynced nested [go.mod,go.sum] files"
$(MAKE) verify-nested-vendor
echo "Checking commit integrity"
$(MAKE) verify-commits

.PHONY: help
help: ## Display this help.
Expand Down
140 changes: 140 additions & 0 deletions scripts/verify_commits.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
#!/bin/bash

set -o errexit
set -o pipefail
set -o nounset

function err() {
echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2
}

function info() {
echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*"
}

function verify_staging_sync() {
local remote="${1}"
local downstream_commit="${2}"
local staging_dir="staging/${remote}"

local outside_staging
outside_staging="$(git show --name-only "${downstream_commit}" -- ":!${staging_dir}" ':!vendor :!manifests')"
if [[ -n "${outside_staging}" ]]; then
err "downstream staging commit ${downstream_commit} changes files outside of ${staging_dir}, vendor, and manifests directories"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One quick thought: would we necessary group an upstream-specific commit and the requisite manifest changes in the same commit? Grouping the staging tree changes + any vendor changes makes sense to me, especially in the reversion case, but still trying to wrap my head around whether we'd like to enforce that manifest changes need to also live in that same commit. Any ideas?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but still trying to wrap my head around whether we'd like to enforce that manifest changes need to also live in that same commit. Any ideas?

The current code ensures that no staging commit touches a staging directory and a directory other than ./vendor or ./manifests. It doesn't enforce that changes to manifests occur in a staging commit, if that's the concern.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, my mistake likely glossed over too quickly - this implementation is fine with me.

err "${outside_staging}"
err "hint: factor out changes to these directories into a standalone commit"
return 1
fi
}

function verify_downstream_only() {
local downstream_commit="${1}"

local inside_staging
inside_staging="$(git show --name-only "${downstream_commit}" -- staging)"
if [[ -n "${inside_staging}" ]]; then
err "downstream non-staging commit ${downstream_commit} changes staging"
err "${inside_staging}"
err "only staging commits (i.e. from an upstream cherry-pick) may change staging"

return 1
fi
}

function upstream_ref() {
local downstream_commit="${1}"

local log
log="$(git log -n 1 "${downstream_commit}")"

local -a upstream_repos
mapfile -t upstream_repos < <(echo "${log}" | grep 'Upstream-repository:' | awk '{print $2}')

local -a upstream_commits
mapfile -t upstream_commits < <(echo "${log}" | grep 'Upstream-commit:' | awk '{print $2}')

if (( ${#upstream_repos[@]} < 1 && ${#upstream_commits[@]} < 1 )); then
# no upstream commit referenced
return 0
fi


local invalid
if (( ${#upstream_repos[@]} != 1 )); then
err "downstream staging commit ${downstream_commit} references an invalid number of repos:"
err "${upstream_repos[@]}"
err "staging commits must reference a single upstream repo"
invalid=true
fi

if (( ${#upstream_commits[@]} != 1 )); then
err "downstream staging commit ${downstream_commit} references an invalid number of upstream commits:"
err "${upstream_commits[@]}"
err "staging commits must reference a single upstream commit"
invalid=true
fi

if [[ "${invalid}" == true ]]; then
return 1
fi

if git branch -r --contains "${upstream_commits[0]}" | grep -vq "${upstream_repos[0]}/.*"; then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this cause problems if we need to revert commits upstream? I suppose in that case we need to explicitly revert the commit in order, but it could be a problem if we're on a release branch where that is not trivially possible

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this cause problems if we need to revert commits upstream?

reverting a commit is additive -- it creates a new commit that undoes the changes of the target -- so in theory, the original commit should still exist (if I didn't misunderstand the concern).

see https://git-scm.com/docs/git-revert#_description

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, we just need to enforce that we're not tossing history and it's not a problem. I think this is fine I just wanted to call it out as a potential problem if upstream ever got borked.

err "downstream staging commit ${downstream_commit} references upstream commit ${upstream_commits[0]} that doesn't exist in ${upstream_repos[0]}"
err "staging commits must reference a repository containing the given upstream commit"
return 1
fi

echo "${upstream_repos[0]}"
echo "${upstream_commits[0]}"
}

function fetch_remotes() {
local -a remotes
remotes=(
'api'
'operator-registry'
'operator-lifecycle-manager'
)
for r in "${remotes[@]}"; do
local url
url="https://github.com/operator-framework/${r}.git"

git remote add "${r}" "${url}" 2>/dev/null || git remote set-url "${r}" "${url}"
git fetch -q "${r}" master
done
}

function main() {
fetch_remotes || { err "failed to fetch remotes" && exit 1; }

local target_branch="${1:-master}"

# get all commits we're introducing into the target branch
local -a new_commits
mapfile -t new_commits < <(git rev-list --no-merges HEAD "^${target_branch}")
info "detected ${#new_commits[@]} commit(s) to verify"

local -a sr
local short
for commit in "${new_commits[@]}"; do
short="${commit:0:7}"
info "verifying ${short}..."
info "$(git log -n 1 "${commit}")"

mapfile -t sr < <(upstream_ref "${commit}" || exit 1)
if (( ${#sr[@]} < 2 )); then # the ref contains a tuple if the values were properly parsed from the commit message
# couldn't find upstream cherry-pick reference in the commit message
# assume it's downstream-only commit
info "${short} does not reference upstream commit, verifying as downstream-only"
verify_downstream_only "${commit}" || exit 1
else
# found upstream cherry-pick reference in commit message
# verify as an upstream sync
info "${short} references upstream commit (${sr[*]}), verifying as upstream staging sync"
verify_staging_sync "${sr[0]}" "${commit}" || exit 1
fi

done
}

main "$@"